Browse Source

初始化数据

15669368801 4 years ago
commit
79363fb668
100 changed files with 15044 additions and 0 deletions
  1. 327 0
      README.md
  2. 24 0
      app/AppService.php
  3. 68 0
      app/ExceptionHandle.php
  4. 19 0
      app/Request.php
  5. 15 0
      app/admin/route/route.php
  6. 1 0
      app/adminapi/.WeDrive
  7. 61 0
      app/adminapi/AdminApiExceptionHandle.php
  8. 121 0
      app/adminapi/common.php
  9. 1 0
      app/adminapi/config/.WeDrive
  10. 25 0
      app/adminapi/config/route.php
  11. 1 0
      app/adminapi/controller/.WeDrive
  12. 61 0
      app/adminapi/controller/AuthController.php
  13. 810 0
      app/adminapi/controller/Common.php
  14. 104 0
      app/adminapi/controller/Login.php
  15. 64 0
      app/adminapi/controller/Test.php
  16. 1 0
      app/adminapi/controller/v1/.WeDrive
  17. 1 0
      app/adminapi/controller/v1/agent/.WeDrive
  18. 417 0
      app/adminapi/controller/v1/agent/AgentManage.php
  19. 1 0
      app/adminapi/controller/v1/application/.WeDrive
  20. 1 0
      app/adminapi/controller/v1/application/routine/.WeDrive
  21. 151 0
      app/adminapi/controller/v1/application/routine/RoutineTemplate.php
  22. 1 0
      app/adminapi/controller/v1/application/wechat/.WeDrive
  23. 43 0
      app/adminapi/controller/v1/application/wechat/Menus.php
  24. 116 0
      app/adminapi/controller/v1/application/wechat/Reply.php
  25. 197 0
      app/adminapi/controller/v1/application/wechat/StoreService.php
  26. 44 0
      app/adminapi/controller/v1/application/wechat/WechatMessage.php
  27. 224 0
      app/adminapi/controller/v1/application/wechat/WechatNewsCategory.php
  28. 176 0
      app/adminapi/controller/v1/application/wechat/WechatTemplate.php
  29. 382 0
      app/adminapi/controller/v1/application/wechat/WechatUser.php
  30. 1 0
      app/adminapi/controller/v1/cms/.WeDrive
  31. 225 0
      app/adminapi/controller/v1/cms/Article.php
  32. 194 0
      app/adminapi/controller/v1/cms/ArticleCategory.php
  33. 1 0
      app/adminapi/controller/v1/export/.WeDrive
  34. 254 0
      app/adminapi/controller/v1/export/ExportExcel.php
  35. 1 0
      app/adminapi/controller/v1/file/.WeDrive
  36. 149 0
      app/adminapi/controller/v1/file/SystemAttachment.php
  37. 133 0
      app/adminapi/controller/v1/file/SystemAttachmentCategory.php
  38. 1 0
      app/adminapi/controller/v1/finance/.WeDrive
  39. 93 0
      app/adminapi/controller/v1/finance/Finance.php
  40. 158 0
      app/adminapi/controller/v1/finance/UserExtract.php
  41. 136 0
      app/adminapi/controller/v1/finance/UserRecharge.php
  42. 1 0
      app/adminapi/controller/v1/freight/.WeDrive
  43. 143 0
      app/adminapi/controller/v1/freight/Express.php
  44. 1 0
      app/adminapi/controller/v1/marketing/.WeDrive
  45. 104 0
      app/adminapi/controller/v1/marketing/PopScreen.php
  46. 183 0
      app/adminapi/controller/v1/marketing/StoreAssistance.php
  47. 154 0
      app/adminapi/controller/v1/marketing/StoreBargain.php
  48. 185 0
      app/adminapi/controller/v1/marketing/StoreCombination.php
  49. 218 0
      app/adminapi/controller/v1/marketing/StoreCoupon.php
  50. 92 0
      app/adminapi/controller/v1/marketing/StoreCouponIssue.php
  51. 58 0
      app/adminapi/controller/v1/marketing/StoreCouponUser.php
  52. 168 0
      app/adminapi/controller/v1/marketing/StoreRechargeCard.php
  53. 175 0
      app/adminapi/controller/v1/marketing/StoreSeckill.php
  54. 47 0
      app/adminapi/controller/v1/marketing/UserPoint.php
  55. 1 0
      app/adminapi/controller/v1/merchant/.WeDrive
  56. 176 0
      app/adminapi/controller/v1/merchant/SystemStore.php
  57. 177 0
      app/adminapi/controller/v1/merchant/SystemStoreStaff.php
  58. 56 0
      app/adminapi/controller/v1/merchant/SystemVerifyOrder.php
  59. 1 0
      app/adminapi/controller/v1/notification/.WeDrive
  60. 1 0
      app/adminapi/controller/v1/notification/sms/.WeDrive
  61. 62 0
      app/adminapi/controller/v1/notification/sms/SmsAdmin.php
  62. 131 0
      app/adminapi/controller/v1/notification/sms/SmsConfig.php
  63. 79 0
      app/adminapi/controller/v1/notification/sms/SmsPay.php
  64. 68 0
      app/adminapi/controller/v1/notification/sms/SmsPublicTemp.php
  65. 107 0
      app/adminapi/controller/v1/notification/sms/SmsTemplateApply.php
  66. 1 0
      app/adminapi/controller/v1/order/.WeDrive
  67. 763 0
      app/adminapi/controller/v1/order/StoreOrder.php
  68. 1 0
      app/adminapi/controller/v1/product/.WeDrive
  69. 928 0
      app/adminapi/controller/v1/product/CopyTaobao.php
  70. 220 0
      app/adminapi/controller/v1/product/StoreCategory.php
  71. 701 0
      app/adminapi/controller/v1/product/StoreProduct.php
  72. 124 0
      app/adminapi/controller/v1/product/StoreProductReply.php
  73. 95 0
      app/adminapi/controller/v1/product/StoreProductRule.php
  74. 1 0
      app/adminapi/controller/v1/setting/.WeDrive
  75. 87 0
      app/adminapi/controller/v1/setting/PushBinding.php
  76. 137 0
      app/adminapi/controller/v1/setting/ShippingTemplates.php
  77. 269 0
      app/adminapi/controller/v1/setting/SystemAdmin.php
  78. 145 0
      app/adminapi/controller/v1/setting/SystemCity.php
  79. 483 0
      app/adminapi/controller/v1/setting/SystemConfig.php
  80. 197 0
      app/adminapi/controller/v1/setting/SystemConfigTab.php
  81. 180 0
      app/adminapi/controller/v1/setting/SystemGroup.php
  82. 280 0
      app/adminapi/controller/v1/setting/SystemGroupData.php
  83. 271 0
      app/adminapi/controller/v1/setting/SystemMenus.php
  84. 119 0
      app/adminapi/controller/v1/setting/SystemRole.php
  85. 1 0
      app/adminapi/controller/v1/system/.WeDrive
  86. 79 0
      app/adminapi/controller/v1/system/Clear.php
  87. 278 0
      app/adminapi/controller/v1/system/SystemClearData.php
  88. 190 0
      app/adminapi/controller/v1/system/SystemDatabackup.php
  89. 256 0
      app/adminapi/controller/v1/system/SystemFile.php
  90. 45 0
      app/adminapi/controller/v1/system/SystemLog.php
  91. 210 0
      app/adminapi/controller/v1/system/SystemUpgradeClient.php
  92. 1 0
      app/adminapi/controller/v1/user/.WeDrive
  93. 1100 0
      app/adminapi/controller/v1/user/User.php
  94. 100 0
      app/adminapi/controller/v1/user/UserGroup.php
  95. 102 0
      app/adminapi/controller/v1/user/UserLabel.php
  96. 408 0
      app/adminapi/controller/v1/user/UserLevel.php
  97. 24 0
      app/adminapi/event.php
  98. 1 0
      app/adminapi/lang/.WeDrive
  99. 55 0
      app/adminapi/lang/zh-cn.php
  100. 1 0
      app/adminapi/middleware/.WeDrive

+ 327 - 0
README.md

@@ -0,0 +1,327 @@
+CRMEB PRO
+===============
+
+> 运行环境要求PHP7.1 ~ 7.3。
+
+## 开发规范
+#### 命名规范
+ThinkPHP6.0遵循PSR-2命名规范和PSR-4自动加载规范,并且注意如下规范:
+
+1. 目录和文件
+2. 目录使用小写+下划线;
+3. 类库、函数文件统一以.php为后缀;
+4. 类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致;
+5. 类(包含接口和Trait)文件采用驼峰法命名(首字母大写),其它文件采用小写+下划线命名;
+6. 类名(包括接口和Trait)和文件名保持一致,统一采用驼峰法命名(首字母大写);
+
+#### 函数和类、属性命名
+
+1. 类的命名采用驼峰法(首字母大写),例如 User、UserType;
+2. common函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 get_client_ip;
+3. 控制器里面的方法使用小写字母和下划线(小写字母开头)的方式,例如 get_client_ip
+4. 方法的命名使用驼峰法(首字母小写),例如 getUserName;
+5. 属性的命名使用驼峰法(首字母小写),例如 tableName、instance;
+6. 特例:以双下划线__打头的函数或方法作为魔术方法,例如 __call 和 __autoload;
+
+#### 常量和配置
+1. 常量以大写字母和下划线命名,例如 APP_PATH;
+2. 配置参数以小写字母和下划线命名,例如 url_route_on 和url_convert;
+3. 环境变量定义使用大写字母和下划线命名,例如APP_DEBUG;
+
+#### 数据表和字段
+1. 数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 think_user 表和 user_name字段,不建议使用驼峰和中文作为数据表及字段命名
+
+注意:请理解并尽量遵循以上命名规范,可以减少在开发过程中出现不必要的错误
+
+#### 语法规范
+1. 尽量使用php7新语法
+2. 每个 namespace 命名空间声明语句和 use 声明语句块后面,必须 插入一个空白行
+3. 类的开始花括号({) 必须 写在类声明后自成一行,结束花括号(})也 必须 写在类主体后自成一行
+4. 方法的开始花括号({) 必须 写在函数声明后自成一行,结束花括号(})也 必须 写在函数主体后自成一行。
+5. 类的属性和方法 必须 添加访问修饰符(private、protected 以及 public),abstract 以及 final 必须 声明在访问修饰符之前,而 static 必须 声明在访问修饰符之后
+6. 控制结构的关键字后 必须 要有一个空格符,而调用方法或函数时则 一定不可 有
+7. 控制结构的开始花括号({) 必须 写在声明的同一行,而结束花括号(}) 必须 写在主体后自成一行
+8. 纯 PHP 代码文件 必须 省略最后的 ?> 结束标签
+9. 所有方法,类,控制器类,都 必须 添加访问修饰符
+    ~~~
+    
+    /**
+     * 中文注释
+     * @param string $str 声明类型
+     * @param array $arr
+     * @return bool
+     */
+    public function action(string $str, array $arr)
+    {
+         return true;
+    }
+    ~~~
+10. 参数列表中,每个逗号后面 必须 要有一个空格,而逗号前面 一定不可 有空格
+    ~~~
+     function foo($arg1, &$arg2, $arg3 = [])
+     {
+            // method body
+     }
+    ~~~
+11. 参数 可以 分列成多行,此时包括第一个参数在内的每个参数都 尽量 单独成行。
+    ~~~
+    <?php
+    $foo->bar(
+        $longArgument,
+        $longerArgument,
+        $muchLongerArgument
+    );
+    ~~~
+12. 标准的 if 结构如下代码所示,请留意「括号」、「空格」以及「花括号」的位置,
+    注意 else 和 elseif 都与前面的结束花括号在同一行
+    ~~~
+    <?php
+    if ($expr1) {
+        // if body
+    } elseif ($expr2) {
+        // elseif body
+    } else {
+        // else body;
+    }
+    ~~~
+13. 赋值等号前后必须加空格符
+    ~~~
+    <?php
+    $arr = [];
+    ~~~
+
+    
+#### PHP 7.1+ 常用新语法
+
+1. 三元运算符
+   ~~~
+   <?php
+   
+   $arr = ['crmeb'=>true];
+   之前
+   echo isset($arr['crmeb']) ? $arr['crmeb'] : '';
+   之后
+   echo $arr['crmeb'] ?? '';
+   ~~~
+2.  define() 定义常量数组
+   ~~~
+   <?php
+    define('ARR',['a','b']);
+   ~~~
+3.  命名空间优化
+   ~~~
+    <?php
+    //PHP7之前语法
+    use FooLibrary\Bar\Baz\ClassA; 
+    use FooLibrary\Bar\Baz\ClassB; 
+    // PHP7新语法写法 
+    use FooLibrary\Bar\Baz\{ ClassA, ClassB};
+    
+   ~~~
+#### CRMEB PRO规范
+ 1. 所有控制器继承app\adminapi\controller\AuthController类
+ 2. 所有数据验证放在模块下的 validates 目录下
+ 3. JSON返回使用父级 AuthController类中的success 和 fail
+ 4. 错误判断抛出异常,由一个错误类统一控制输出
+    ~~~
+    <?php
+    
+        throw new AuthException('错误信息',400);
+    ~~~
+ 5. 错误码和错误提示语应该统一管理,方便切换多语言
+ 6. 数据库操作使用模型类,不能使用Db::table()
+ 7. 获取表单数据使用 crmeb\services\UtilService
+    ~~~
+    <?php
+    use crmeb\services\UtilService;
+    
+    //获取提交的数据,并以二维数组形式返回
+    $arr = UtilService::getMore([
+        'name',
+        'nickname'
+    ]);
+    //获取提交的数据,并以二维数组形式返回并附加默认值
+    $arr = UtilService::getMore([
+       ['name','123'],
+       ['nickname','0']
+    ]);
+    //获取提交的数据,并以一维数组形式返回并附加默认值
+    [$name, $nickname] = UtilService::getMore([
+       ['name','123'],
+       ['nickname','0']
+    ],null,true);
+    ~~~
+ 8. 所有控制器类命令和表名对应,按照大驼峰命名规范
+ 9. 所有文件夹命名按照小写字母加下划线定义
+ 10. 所有属性名,变量名尽量遵守小驼峰命名规范 
+ 11. 复杂逻辑,多状态应适当添加行内注释
+ 
+## AdminApi目录结构详细说明
+~~~
+  adminapi
+    ├─ config 配置文件存放
+    ├─ controller 控制器文件存放
+    │    ├─ v1 版本号
+    │    │    ├─ product 产品管理
+    │    │    ├─ user 用户管理
+    │    │    ├─ order 系统订单管理
+    │    │    ├─ setting 系统设置维护 系统权限管理、系统菜单管理 客服管理
+    │    │    ├─ chat 客服管理(列表,添加,删除,编辑)
+    │    │    ├─ application 各个应用模块功能管理公众号、小程序、支付宝、百度小程序、今日头条小程序
+    │    │    ├─ system 系统更新日志 数据库管理
+    │    │    ├─ finance  财务管理
+    │    │    ├─ agent 分销管理
+    │    │    ├─ marketing 优惠券、积分、拼团、砍价、秒杀
+    │    │    ├─ echarts 数据统计分析
+    │    │    ├─ notification  消息通知管理、模版消息(列表,通知,添加,编辑)、短信
+    │    │    ├─ file 附件文件管理
+    │    │    ├─ freight 运费模版管理 物流公司
+    │    │    ├─ merchant 商户管理
+    │    │    ├─ widget 组件 小插件
+    │    │    └─ cms 文章管理
+    │    ├─  AuthController.php  所有控制器继承类
+    │    ├─  CommonApi.php 公共接口
+    │    └─ Login.php 登陆接口
+    ├─ lang 语言包
+    │    └─ zh-cn.php 中文语言包
+    ├─ validates 数据验证目录
+    ├─ AdminApiExceptionHandle.php 错误捕获
+    ├─ common.php 公共函数存放处
+    ├─ event.php 事件挂载
+    └─ provider.php 容器
+~~~
+## 主要特性
+
+### 开源无加密
+源码开源无加密,有详细的代码注释,有完整系统手册
+### TP6框架
+使用最新的 ThinkPHP 6.0框架开发
+### 前端采用Vue CLI框架
+前端使用Vue CLI框架nodejs打包,页面加载更流畅,用户体验更好
+### 标准接口
+标准接口、前后端分离,二次开发更方便
+### 支持队列
+降低流量高峰,解除耦合,高可用
+### 长连接
+减少CPU及内存使用及网络堵塞,减少请求响应时长
+### 无缝事件机制
+行为扩展更方便,方便二次开发
+### 后台快速生成表单
+后台应用form-builder 无需写页面快速增删改查
+### 数据表格导出
+PHPExcel数据导出,导出表格更加美观可视;
+### 数据统计分析
+后台使用ECharts图表统计,实现用户、产品、订单、资金等统计分析
+### 强大的后台权限管理
+后台多种角色、多重身份权限管理,权限可以控制到每一步操作
+### 一件安装
+自动检查系统环境一键安装
+
+## 安装
+
+## 一键安装
+上传你的代码,站点入口目录设置/public
+在浏览器中输入你的域名或IP(例如:www.yourdomain.com),
+安装程序会自动执行安装。期间系统会提醒你输入数据库信息以完成安装,安装完成后建议删除install目录下index.php文件或将其改名。
+
+后台访问地址:
+1.域名/admin
+2.域名/index.php/admin
+3.域名/index.php?s=/admin
+公众号和H5首页访问地址:
+1.域名/
+提示:正常访问是第一中模式,第一种访问不了请检测[URL重写](http://help.crmeb.net/895486)是否配置好
+安装过程中请牢记您的账号密码!
+
+## 重新安装
+1. 清除数据库
+2. 删除/public/install/install.lock 文件
+
+## 手动安装
+1.创建数据库,倒入数据库文件
+数据库文件目录/public/install/crmeb.sql
+2.修改数据库连接文件
+配置文件路径/.env
+~~~
+APP_DEBUG = true
+
+[APP]
+DEFAULT_TIMEZONE = Asia/Shanghai
+
+[DATABASE]
+TYPE = mysql
+HOSTNAME = 127.0.0.1 #数据库连接地址
+DATABASE = test #数据库名称
+USERNAME = username #数据库登录账号
+PASSWORD = password #数据库登录密码
+HOSTPORT = 3306 #数据库端口
+CHARSET = utf8
+DEBUG = true
+
+[LANG]
+default_lang = zh-cn
+~~~
+3.修改目录权限(linux系统)777
+/public
+/runtime
+4.后台登录:
+http://域名/admin
+默认账号:admin 密码:crmeb.com
+
+## 定时任务
+在自动收货,库存预警等功能使用到
+```sh
+php think timer [ status ] [ --d ]
+```
+参数
+- status: 状态
+    - start: 启动
+    - stop: 关闭
+    - restart: 重启
+- --d : 后台执行
+## 长连接服务
+在h5聊天,后台管理员消息通知等功能使用到
+```sh
+php think workerman [ status ] [ server ] [ --d ]
+```
+windows环境下需要分三步执行
+```sh
+# 内部通讯服务
+php think workerman start channel
+# h5端聊天服务
+php think workerman start chat
+# 后台管理员通知
+php think workerman start admin
+```
+参数
+- status: 状态
+    - start: 启动
+    - stop: 关闭
+    - restart: 重启
+- server: 服务 (windows)
+    - channel: 内部通讯
+    - chat: h5
+    - admin: 后台
+
+- --d : 后台执行
+
+## 文档
+
+[使用手册](https://help.crmeb.net)
+[TP6开发手册](https://www.kancloud.cn/manual/thinkphp6_0/content)
+
+
+## 参与开发
+
+请参阅 [CRMEB](https://github.com/crmeb/CRMEB)。
+
+## 版权信息
+
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2017-2019 by CRMEB (http://www.crmeb.com)
+
+All rights reserved。
+
+CRMEB® 商标和著作权所有者为西安众邦网络科技有限公司。

+ 24 - 0
app/AppService.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace app;
+
+use crmeb\services\SystemConfigService;
+use crmeb\services\GroupDataService;
+use crmeb\utils\Json;
+use think\facade\Db;
+use think\Service;
+
+class AppService extends Service
+{
+
+    public $bind = [
+        'json' => Json::class,
+        'sysConfig' => SystemConfigService::class,
+        'sysGroupData' => GroupDataService::class
+    ];
+
+    public function boot()
+    {
+
+    }
+}

+ 68 - 0
app/ExceptionHandle.php

@@ -0,0 +1,68 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace app;
+
+use think\db\exception\DataNotFoundException;
+use think\db\exception\ModelNotFoundException;
+use think\exception\Handle;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\exception\ValidateException;
+use think\Response;
+use Throwable;
+
+/**
+ * 应用异常处理类
+ */
+class ExceptionHandle extends Handle
+{
+    /**
+     * 不需要记录信息(日志)的异常类列表
+     * @var array
+     */
+    protected $ignoreReport = [
+        HttpException::class,
+        HttpResponseException::class,
+        ModelNotFoundException::class,
+        DataNotFoundException::class,
+        ValidateException::class,
+    ];
+
+    /**
+     * 记录异常信息(包括日志或者其它方式记录)
+     *
+     * @access public
+     * @param  Throwable $exception
+     * @return void
+     */
+    public function report(Throwable $exception): void
+    {
+        // 使用内置的方式记录异常日志
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @access public
+     * @param \think\Request   $request
+     * @param Throwable $e
+     * @return Response
+     */
+    public function render($request, Throwable $e): Response
+    {
+        // 添加自定义异常处理机制
+
+        // 其他错误交给系统处理
+        return parent::render($request, $e);
+    }
+}

+ 19 - 0
app/Request.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace app;
+
+use Spatie\Macroable\Macroable;
+
+class Request extends \think\Request
+{
+    use Macroable;
+}

+ 15 - 0
app/admin/route/route.php

@@ -0,0 +1,15 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+use think\facade\Route;
+
+Route::miss(function () {
+    return view(app()->getRootPath() . 'public' . DS . 'admin/index.html');
+});

+ 1 - 0
app/adminapi/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi

+ 61 - 0
app/adminapi/AdminApiExceptionHandle.php

@@ -0,0 +1,61 @@
+<?php
+
+
+namespace app\adminapi;
+
+
+use crmeb\exceptions\AdminException;
+use crmeb\exceptions\AuthException;
+use crmeb\exceptions\UtilException;
+use think\db\exception\DbException;
+use think\exception\Handle;
+use think\exception\ValidateException;
+use think\facade\Env;
+use think\Response;
+use Throwable;
+
+class AdminApiExceptionHandle extends Handle
+{
+
+    /**
+     * 记录异常信息(包括日志或者其它方式记录)
+     *
+     * @access public
+     * @param Throwable $exception
+     * @return void
+     */
+    public function report(Throwable $exception): void
+    {
+        // 使用内置的方式记录异常日志
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @access public
+     * @param \think\Request $request
+     * @param Throwable $e
+     * @return Response
+     */
+    public function render($request, Throwable $e): Response
+    {
+        $massageData = Env::get('app_debug', false) ? [
+            'file' => $e->getFile(),
+            'line' => $e->getLine(),
+            'trace' => $e->getTrace(),
+            'previous' => $e->getPrevious(),
+        ] : [];
+        // 添加自定义异常处理机制
+        if ($e instanceof DbException) {
+            return app('json')->fail('数据获取失败', $massageData);
+        } elseif ($e instanceof AuthException || $e instanceof ValidateException || $e instanceof UtilException) {
+            return app('json')->make($e->getCode() ?: 400, $e->getMessage());
+        } elseif ($e instanceof AdminException) {
+            return app('json')->fail($e->getMessage(), $massageData);
+        } else {
+            return app('json')->code(200)->make(400, $e->getMessage(), $massageData);
+        }
+    }
+
+}

+ 121 - 0
app/adminapi/common.php

@@ -0,0 +1,121 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 流年 <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+if(!function_exists('get_this_class_methods'))
+{
+    /**获取当前类方法
+     * @param $class
+     * @return array
+     */
+    function get_this_class_methods($class,$unarray = []) {
+        $arrayall = get_class_methods($class);
+        if ($parent_class = get_parent_class($class)) {
+            $arrayparent = get_class_methods($parent_class);
+            $arraynow = array_diff($arrayall, $arrayparent);//去除父级的
+        } else {
+            $arraynow = $arrayall;
+        }
+        return array_diff($arraynow, $unarray);//去除无用的
+    }
+}
+
+if(!function_exists('get_tree_children'))
+{
+    /**
+     * tree 子菜单
+     * @param $data
+     * @param int $pid
+     * @return array
+     */
+    function get_tree_children($data, $childrenname = 'children')
+    {
+        $list = array();
+        foreach($data as $value){
+            $list[$value['id']] = $value;
+        }
+        static $tree = array(); //格式化好的树
+        foreach ($list as $item){
+            if (isset($list[$item['pid']])){
+                $list[$item['pid']][$childrenname][] = &$list[$item['id']];
+            }
+            else{
+                $tree[] = &$list[$item['id']];
+            }
+        }
+        return $tree;
+    }
+}
+
+if (!function_exists('attr_format')) {
+    /**
+     * 格式化属性
+     * @param $arr
+     * @return array
+     */
+    function attr_format($arr)
+    {
+        $data = [];
+        $res = [];
+        $count = count($arr);
+        if ($count > 1) {
+            for ($i = 0; $i < $count - 1; $i++) {
+                if ($i == 0) $data = $arr[$i]['detail'];
+                //替代变量1
+                $rep1 = [];
+                foreach ($data as $v) {
+                    foreach ($arr[$i + 1]['detail'] as $g) {
+                        //替代变量2
+                        $rep2 = ($i != 0 ? '' : $arr[$i]['value'] . '_$_') . $v . '-$-' . $arr[$i + 1]['value'] . '_$_' . $g;
+                        $tmp[] = $rep2;
+                        if ($i == $count - 2) {
+                            foreach (explode('-$-', $rep2) as $k => $h) {
+                                //替代变量3
+                                $rep3 = explode('_$_', $h);
+                                //替代变量4
+                                $rep4['detail'][$rep3[0]] = isset($rep3[1]) ? $rep3[1] : '';
+                            }
+                            if($count == count($rep4['detail']))
+                                $res[] = $rep4;
+                        }
+                    }
+                }
+                $data = isset($tmp) ? $tmp : [];
+            }
+        } else {
+            $dataArr = [];
+            foreach ($arr as $k => $v) {
+                foreach ($v['detail'] as $kk => $vv) {
+                    $dataArr[$kk] = $v['value'] . '_' . $vv;
+                    $res[$kk]['detail'][$v['value']] = $vv;
+                }
+            }
+            $data[] = implode('-', $dataArr);
+        }
+        return [$data, $res];
+    }
+}
+
+if (!function_exists('verify_domain')) {
+
+    /**
+     * 验证域名是否合法
+     * @param string $domain
+     * @return bool
+     */
+    function verify_domain(string $domain): bool
+    {
+        $res = "/^(?=^.{3,255}$)(http(s)?:\/\/)(www\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\d+)*(\/\w+\.\w+)*$/";
+        if (preg_match($res, $domain))
+            return true;
+        else
+            return false;
+    }
+}

+ 1 - 0
app/adminapi/config/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/config

+ 25 - 0
app/adminapi/config/route.php

@@ -0,0 +1,25 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 应用设置
+// +----------------------------------------------------------------------
+
+return [
+    // 是否强制使用路由
+    'url_route_must'        => true,
+    // 合并路由规则
+    'route_rule_merge'      => true,
+    // 路由是否完全匹配
+    'route_complete_match'  => true,
+    // 是否自动转换URL中的控制器和操作名
+    'url_convert'           => false,
+];

+ 1 - 0
app/adminapi/controller/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller

+ 61 - 0
app/adminapi/controller/AuthController.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace app\adminapi\controller;
+
+
+use crmeb\basic\BaseController;
+
+/**
+ * 基类 所有控制器继承的类
+ * Class AuthController
+ * @package app\adminapi\controller
+ */
+class AuthController extends BaseController
+{
+    /**
+     * 当前登陆管理员信息
+     * @var
+     */
+    protected $adminInfo;
+
+    /**
+     * 当前登陆管理员ID
+     * @var
+     */
+    protected $adminId;
+
+    /**
+     * 当前登录的地址
+     * @var
+     */
+    protected $merId;
+
+    /**
+     * 当前管理员权限
+     * @var array
+     */
+    protected $auth = [];
+
+    /**
+     * 模型类名
+     * @var null
+     */
+    protected $bindModel = null;
+
+    /**
+     * 初始化
+     */
+    protected function initialize()
+    {
+        parent::initialize();
+
+        $this->adminId = $this->request->adminId();
+        $this->adminInfo = $this->request->adminInfo();
+        $this->merId = $this->request->mer_id();
+
+        $this->auth = $this->request->adminInfo['rule'] ?? [];
+
+        event('AdminVisit', [$this->adminInfo, 'system', $this->merId]);
+    }
+
+}

+ 810 - 0
app/adminapi/controller/Common.php

@@ -0,0 +1,810 @@
+<?php
+
+namespace app\adminapi\controller;
+
+use app\models\merchant\MerchantMiniprogram;
+use app\models\store\StoreOrder;
+use app\models\store\StoreProduct;
+use app\models\store\StoreProductAttrValue;
+use app\models\user\User;
+use crmeb\repositories\NoticeRepositories;
+
+/**
+ * 公共接口基类 主要存放公共接口
+ * Class Common
+ * @package app\adminapi\controller
+ */
+class Common extends AuthController
+{
+
+    /**
+     * @return mixed
+     */
+    public function check_auth()
+    {
+        return $this->checkAuthDecrypt();
+    }
+
+    /**
+     * @return mixed
+     */
+    public function auth()
+    {
+        return $this->getAuth();
+    }
+
+    /**
+     * 首页头部统计数据
+     * @return mixed
+     */
+    public function homeStatics()
+    {
+        $mer_id = $this->merId ?: '';
+        //TODO 销售额
+        //今日销售额
+        $today_sales = StoreOrder::where('paid', 1)
+            ->where('mer_id', $mer_id)
+            ->where('is_del', 0)
+            ->where('refund_status', 0)
+            ->whereDay('pay_time')
+            ->sum('pay_price');
+        //昨日销售额
+        $yesterday_sales = StoreOrder::where('paid', 1)
+            ->where('mer_id', $mer_id)
+            ->where('is_del', 0)
+            ->where('refund_status', 0)
+            ->whereDay('pay_time', 'yesterday')
+            ->sum('pay_price');
+        //日同比
+        $sales_today_ratio = $this->growth($today_sales, $yesterday_sales);
+        //周销售额
+        //本周
+        $this_week_sales = StoreOrder::where('paid', 1)
+            ->where('mer_id', $mer_id)
+            ->where('is_del', 0)
+            ->where('refund_status', 0)
+            ->whereWeek('pay_time')
+            ->sum('pay_price');
+        //上周
+        $last_week_sales = StoreOrder::where('paid', 1)
+            ->where('mer_id', $mer_id)
+            ->where('is_del', 0)
+            ->where('refund_status', 0)
+            ->whereWeek('pay_time', 'last week')
+            ->sum('pay_price');
+        //周同比
+        $sales_week_ratio = $this->growth($this_week_sales, $last_week_sales);
+        //总销售额
+        $total_sales = StoreOrder::where('paid', 1)
+            ->where('mer_id', $mer_id)
+            ->where('is_del', 0)
+            ->where('refund_status', 0)
+            ->sum('pay_price');
+        $sales = [
+            'today' => $today_sales,
+            'yesterday' => $yesterday_sales,
+            'today_ratio' => $sales_today_ratio,
+            'week' => $this_week_sales,
+            'last_week' => $last_week_sales,
+            'week_ratio' => $sales_week_ratio,
+            'total' => $total_sales . '元',
+            'date' => '昨日'
+        ];
+        //TODO:用户访问量
+        //今日访问量
+        $today_visits = User::where('mer_id', $mer_id)->WhereDay('last_time')->count();
+        //昨日访问量
+        $yesterday_visits = User::where('mer_id', $mer_id)->whereDay('last_time', 'yesterday')->count();
+        //日同比
+        $visits_today_ratio = $this->growth($today_visits, $yesterday_visits);
+        //本周访问量
+        $this_week_visits = User::where('mer_id', $mer_id)->WhereWeek('last_time')->count();
+        //上周访问量
+        $last_week_visits = User::where('mer_id', $mer_id)->WhereWeek('last_time', 'last week')->count();
+        //周同比
+        $visits_week_ratio = $this->growth($this_week_visits, $last_week_visits);
+        //总访问量
+        $total_visits = User::where('mer_id', $mer_id)->count();
+        $visits = [
+            'today' => $today_visits,
+            'yesterday' => $yesterday_visits,
+            'today_ratio' => $visits_today_ratio,
+            'week' => $this_week_visits,
+            'last_week' => $last_week_visits,
+            'week_ratio' => $visits_week_ratio,
+            'total' => $total_visits . 'Pv',
+            'date' => '昨日'
+        ];
+        //TODO 订单量
+        //今日订单量
+        $today_order = StoreOrder::where('mer_id', $mer_id)->whereDay('add_time')->count();
+        //昨日订单量
+        $yesterday_order = StoreOrder::where('mer_id', $mer_id)->whereDay('add_time', 'yesterday')->count();
+        //订单日同比
+        $order_today_ratio = $this->growth($today_order, $yesterday_order);
+        //本周订单量
+        $this_week_order = StoreOrder::where('mer_id', $mer_id)->whereWeek('add_time')->count();
+        //上周订单量
+        $last_week_order = StoreOrder::where('mer_id', $mer_id)->whereWeek('add_time', 'last week')->count();
+        //订单周同比
+        $order_week_ratio = $this->growth($this_week_order, $last_week_order);
+        //总订单量
+        $total_order = StoreOrder::where('mer_id', $mer_id)->where('is_system_del', 0)->count();
+        $order = [
+            'today' => $today_order,
+            'yesterday' => $yesterday_order,
+            'today_ratio' => $order_today_ratio,
+            'week' => $this_week_order,
+            'last_week' => $last_week_order,
+            'week_ratio' => $order_week_ratio,
+            'total' => $total_order . '单',
+            'date' => '昨日'
+        ];
+        //TODO 用户
+        //今日新增用户
+        $today_user = User::where('mer_id', $mer_id)->whereDay('add_time')->count();
+        //昨日新增用户
+        $yesterday_user = User::where('mer_id', $mer_id)->whereDay('add_time', 'yesterday')->count();
+        //新增用户日同比
+        $user_today_ratio = $this->growth($today_user, $yesterday_user);
+        //本周新增用户
+        $this_week_user = User::where('mer_id', $mer_id)->whereWeek('add_time')->count();
+        //上周新增用户
+        $last_week_user = User::where('mer_id', $mer_id)->whereWeek('add_time', 'last week')->count();
+        //新增用户周同比
+        $user_week_ratio = $this->growth($this_week_user, $last_week_user);
+        //所有用户
+        $total_user = User::where('mer_id', $mer_id)->count();
+        $user = [
+            'today' => $today_user,
+            'yesterday' => $yesterday_user,
+            'today_ratio' => $user_today_ratio,
+            'week' => $this_week_user,
+            'last_week' => $last_week_user,
+            'week_ratio' => $user_week_ratio,
+            'total' => $total_user . '人',
+            'date' => '昨日'
+        ];
+        // $qrcode = MerchantMiniprogram::vaildWhere()->where('mer_id', $this->merId)->field('test_qrcode_url,qrcode_url')->find();
+        $info = array_values(compact('sales', 'visits', 'order', 'user'));
+        $info[0]['title'] = '销售额';
+        $info[1]['title'] = '用户访问量';
+        $info[2]['title'] = '订单量';
+        $info[3]['title'] = '新增用户';
+        $info[0]['total_name'] = '总销售额';
+        $info[1]['total_name'] = '总访问量';
+        $info[2]['total_name'] = '总订单量';
+        $info[3]['total_name'] = '总用户';
+        return $this->success(compact('info'));
+    }
+
+    //增长率
+    public function growth($left, $right)
+    {
+        if ($right)
+            $ratio = bcmul(bcdiv(bcsub($left, $right, 2), $right, 4), 100, 2);
+        else {
+            if ($left)
+                $ratio = 100;
+            else
+                $ratio = 0;
+        }
+        return $ratio;
+    }
+
+    /**
+     * 订单图表
+     */
+    public function orderChart()
+    {
+        $mer_id = $this->merId ?: '';
+        $cycle = $this->request->param('cycle') ?: 'thirtyday';//默认30天
+        $datalist = [];
+        switch ($cycle) {
+            case 'thirtyday':
+                $datebefor = date('Y-m-d', strtotime('-30 day'));
+                $dateafter = date('Y-m-d');
+                //上期
+                $pre_datebefor = date('Y-m-d', strtotime('-60 day'));
+                $pre_dateafter = date('Y-m-d', strtotime('-30 day'));
+                for ($i = -30; $i < 0; $i++) {
+                    $datalist[date('m-d', strtotime($i . ' day'))] = date('m-d', strtotime($i . ' day'));
+                }
+                $order_list = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$datebefor, $dateafter])
+                    ->field("FROM_UNIXTIME(add_time,'%m-%d') as day,count(*) as count,sum(pay_price) as price")
+                    ->group("FROM_UNIXTIME(add_time, '%Y%m%d')")
+                    ->order('add_time asc')
+                    ->select()->toArray();
+                if (empty($order_list)) return app('json')->success();
+                foreach ($order_list as $k => &$v) {
+                    $order_list[$v['day']] = $v;
+                }
+                $cycle_list = [];
+                foreach ($datalist as $dk => $dd) {
+                    if (!empty($order_list[$dd])) {
+                        $cycle_list[$dd] = $order_list[$dd];
+                    } else {
+                        $cycle_list[$dd] = ['count' => 0, 'day' => $dd, 'price' => ''];
+                    }
+                }
+                $chartdata = [];
+                $data = [];//临时
+                $chartdata['yAxis']['maxnum'] = 0;//最大值数量
+                $chartdata['yAxis']['maxprice'] = 0;//最大值金额
+                foreach ($cycle_list as $k => $v) {
+                    $data['day'][] = $v['day'];
+                    $data['count'][] = $v['count'];
+                    $data['price'][] = round($v['price'], 2);
+                    if ($chartdata['yAxis']['maxnum'] < $v['count'])
+                        $chartdata['yAxis']['maxnum'] = $v['count'];//日最大订单数
+                    if ($chartdata['yAxis']['maxprice'] < $v['price'])
+                        $chartdata['yAxis']['maxprice'] = $v['price'];//日最大金额
+                }
+                $chartdata['legend'] = ['订单金额', '订单数'];//分类
+                $chartdata['xAxis'] = $data['day'];//X轴值
+                //,'itemStyle'=>$series
+//                $series = ['normal' => ['label' => ['show' => true, 'position' => 'top']]];
+                $series1 = ['normal' => ['color' => [
+                    'x' => 0, 'y' => 0, 'x2' => 0, 'y2' => 1,
+                    'colorStops' => [
+                        [
+                            'offset' => 0,
+                            'color' => '#69cdff'
+                        ],
+                        [
+                            'offset' => 0.5,
+                            'color' => '#3eb3f7'
+                        ],
+                        [
+                            'offset' => 1,
+                            'color' => '#1495eb'
+                        ]
+                    ]
+                ]]
+                ];
+                $series2 = ['normal' => ['color' => [
+                    'x' => 0, 'y' => 0, 'x2' => 0, 'y2' => 1,
+                    'colorStops' => [
+                        [
+                            'offset' => 0,
+                            'color' => '#6fdeab'
+                        ],
+                        [
+                            'offset' => 0.5,
+                            'color' => '#44d693'
+                        ],
+                        [
+                            'offset' => 1,
+                            'color' => '#2cc981'
+                        ]
+                    ]
+                ]]
+                ];
+                $chartdata['series'][] = ['name' => $chartdata['legend'][0], 'type' => 'bar', 'itemStyle' => $series1, 'data' => $data['price']];//分类1值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][1], 'type' => 'bar', 'itemStyle' => $series2, 'data' => $data['count']];//分类2值
+                //统计总数上期
+                $pre_total = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$pre_datebefor, $pre_dateafter])
+                    ->field("count(*) as count,sum(pay_price) as price")
+                    ->find();
+                if ($pre_total) {
+                    $chartdata['pre_cycle']['count'] = [
+                        'data' => $pre_total['count'] ?: 0
+                    ];
+                    $chartdata['pre_cycle']['price'] = [
+                        'data' => $pre_total['price'] ?: 0
+                    ];
+                }
+                //统计总数
+                $total = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$datebefor, $dateafter])
+                    ->field("count(*) as count,sum(pay_price) as price")
+                    ->find();
+                if ($total) {
+                    $cha_count = intval($pre_total['count']) - intval($total['count']);
+                    $pre_total['count'] = $pre_total['count'] == 0 ? 1 : $pre_total['count'];
+                    $chartdata['cycle']['count'] = [
+                        'data' => $total['count'] ?: 0,
+                        'percent' => round((abs($cha_count) / intval($pre_total['count']) * 100), 2),
+                        'is_plus' => $cha_count > 0 ? -1 : ($cha_count == 0 ? 0 : 1)
+                    ];
+                    $cha_price = round($pre_total['price'], 2) - round($total['price'], 2);
+                    $pre_total['price'] = $pre_total['price'] == 0 ? 1 : $pre_total['price'];
+                    $chartdata['cycle']['price'] = [
+                        'data' => $total['price'] ?: 0,
+                        'percent' => round(abs($cha_price) / $pre_total['price'] * 100, 2),
+                        'is_plus' => $cha_price > 0 ? -1 : ($cha_price == 0 ? 0 : 1)
+                    ];
+                }
+                return $this->success($chartdata);
+                break;
+            case 'week':
+                $weekarray = array(['周日'], ['周一'], ['周二'], ['周三'], ['周四'], ['周五'], ['周六']);
+                $datebefor = date('Y-m-d', strtotime('-1 week Monday'));
+                $dateafter = date('Y-m-d', strtotime('-1 week Sunday'));
+                $order_list = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$datebefor, $dateafter])
+                    ->field("FROM_UNIXTIME(add_time,'%w') as day,count(*) as count,sum(pay_price) as price")
+                    ->group("FROM_UNIXTIME(add_time, '%Y%m%e')")
+                    ->order('add_time asc')
+                    ->select()->toArray();
+                //数据查询重新处理
+                $new_order_list = [];
+                foreach ($order_list as $k => $v) {
+                    $new_order_list[$v['day']] = $v;
+                }
+                $now_datebefor = date('Y-m-d', (time() - ((date('w') == 0 ? 7 : date('w')) - 1) * 24 * 3600));
+                $now_dateafter = date('Y-m-d', strtotime("+1 day"));
+                $now_order_list = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$now_datebefor, $now_dateafter])
+                    ->field("FROM_UNIXTIME(add_time,'%w') as day,count(*) as count,sum(pay_price) as price")
+                    ->group("FROM_UNIXTIME(add_time, '%Y%m%e')")
+                    ->order('add_time asc')
+                    ->select()->toArray();
+                //数据查询重新处理 key 变为当前值
+                $new_now_order_list = [];
+                foreach ($now_order_list as $k => $v) {
+                    $new_now_order_list[$v['day']] = $v;
+                }
+                foreach ($weekarray as $dk => $dd) {
+                    if (!empty($new_order_list[$dk])) {
+                        $weekarray[$dk]['pre'] = $new_order_list[$dk];
+                    } else {
+                        $weekarray[$dk]['pre'] = ['count' => 0, 'day' => $weekarray[$dk][0], 'price' => '0'];
+                    }
+                    if (!empty($new_now_order_list[$dk])) {
+                        $weekarray[$dk]['now'] = $new_now_order_list[$dk];
+                    } else {
+                        $weekarray[$dk]['now'] = ['count' => 0, 'day' => $weekarray[$dk][0], 'price' => '0'];
+                    }
+                }
+                $chartdata = [];
+                $data = [];//临时
+                $chartdata['yAxis']['maxnum'] = 0;//最大值数量
+                $chartdata['yAxis']['maxprice'] = 0;//最大值金额
+                foreach ($weekarray as $k => $v) {
+                    $data['day'][] = $v[0];
+                    $data['pre']['count'][] = $v['pre']['count'];
+                    $data['pre']['price'][] = round($v['pre']['price'], 2);
+                    $data['now']['count'][] = $v['now']['count'];
+                    $data['now']['price'][] = round($v['now']['price'], 2);
+                    if ($chartdata['yAxis']['maxnum'] < $v['pre']['count'] || $chartdata['yAxis']['maxnum'] < $v['now']['count']) {
+                        $chartdata['yAxis']['maxnum'] = $v['pre']['count'] > $v['now']['count'] ? $v['pre']['count'] : $v['now']['count'];//日最大订单数
+                    }
+                    if ($chartdata['yAxis']['maxprice'] < $v['pre']['price'] || $chartdata['yAxis']['maxprice'] < $v['now']['price']) {
+                        $chartdata['yAxis']['maxprice'] = $v['pre']['price'] > $v['now']['price'] ? $v['pre']['price'] : $v['now']['price'];//日最大金额
+                    }
+                }
+                $chartdata['legend'] = ['上周金额', '本周金额', '上周订单数', '本周订单数'];//分类
+                $chartdata['xAxis'] = $data['day'];//X轴值
+                //,'itemStyle'=>$series
+//                $series = ['normal' => ['label' => ['show' => true, 'position' => 'top']]];
+                $series1 = ['normal' => ['color' => [
+                    'x' => 0, 'y' => 0, 'x2' => 0, 'y2' => 1,
+                    'colorStops' => [
+                        [
+                            'offset' => 0,
+                            'color' => '#69cdff'
+                        ],
+                        [
+                            'offset' => 0.5,
+                            'color' => '#3eb3f7'
+                        ],
+                        [
+                            'offset' => 1,
+                            'color' => '#1495eb'
+                        ]
+                    ]
+                ]]
+                ];
+                $series2 = ['normal' => ['color' => [
+                    'x' => 0, 'y' => 0, 'x2' => 0, 'y2' => 1,
+                    'colorStops' => [
+                        [
+                            'offset' => 0,
+                            'color' => '#6fdeab'
+                        ],
+                        [
+                            'offset' => 0.5,
+                            'color' => '#44d693'
+                        ],
+                        [
+                            'offset' => 1,
+                            'color' => '#2cc981'
+                        ]
+                    ]
+                ]]
+                ];
+                $chartdata['series'][] = ['name' => $chartdata['legend'][0], 'type' => 'bar', 'itemStyle' => $series1, 'data' => $data['pre']['price']];//分类1值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][1], 'type' => 'bar', 'itemStyle' => $series1, 'data' => $data['now']['price']];//分类1值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][2], 'type' => 'line', 'itemStyle' => $series2, 'data' => $data['pre']['count']];//分类2值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][3], 'type' => 'line', 'itemStyle' => $series2, 'data' => $data['now']['count']];//分类2值
+
+                //统计总数上期
+                $pre_total = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$datebefor, $dateafter])
+                    ->field("count(*) as count,sum(pay_price) as price")
+                    ->find();
+                if ($pre_total) {
+                    $chartdata['pre_cycle']['count'] = [
+                        'data' => $pre_total['count'] ?: 0
+                    ];
+                    $chartdata['pre_cycle']['price'] = [
+                        'data' => $pre_total['price'] ?: 0
+                    ];
+                }
+                //统计总数
+                $total = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$now_datebefor, $now_dateafter])
+                    ->field("count(*) as count,sum(pay_price) as price")
+                    ->find();
+                if ($total) {
+                    $cha_count = intval($pre_total['count']) - intval($total['count']);
+                    $pre_total['count'] = $pre_total['count'] == 0 ? 1 : $pre_total['count'];
+                    $chartdata['cycle']['count'] = [
+                        'data' => $total['count'] ?: 0,
+                        'percent' => round((abs($cha_count) / intval($pre_total['count']) * 100), 2),
+                        'is_plus' => $cha_count > 0 ? -1 : ($cha_count == 0 ? 0 : 1)
+                    ];
+                    $cha_price = round($pre_total['price'], 2) - round($total['price'], 2);
+                    $pre_total['price'] = $pre_total['price'] == 0 ? 1 : $pre_total['price'];
+                    $chartdata['cycle']['price'] = [
+                        'data' => $total['price'] ?: 0,
+                        'percent' => round(abs($cha_price) / $pre_total['price'] * 100, 2),
+                        'is_plus' => $cha_price > 0 ? -1 : ($cha_price == 0 ? 0 : 1)
+                    ];
+                }
+                return $this->success($chartdata);
+                break;
+            case 'month':
+                $weekarray = array('01' => ['1'], '02' => ['2'], '03' => ['3'], '04' => ['4'], '05' => ['5'], '06' => ['6'], '07' => ['7'], '08' => ['8'], '09' => ['9'], '10' => ['10'], '11' => ['11'], '12' => ['12'], '13' => ['13'], '14' => ['14'], '15' => ['15'], '16' => ['16'], '17' => ['17'], '18' => ['18'], '19' => ['19'], '20' => ['20'], '21' => ['21'], '22' => ['22'], '23' => ['23'], '24' => ['24'], '25' => ['25'], '26' => ['26'], '27' => ['27'], '28' => ['28'], '29' => ['29'], '30' => ['30'], '31' => ['31']);
+
+                $datebefor = date('Y-m-01', strtotime('-1 month'));
+                $dateafter = date('Y-m-d', strtotime(date('Y-m-01')));
+                $order_list = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$datebefor, $dateafter])
+                    ->field("FROM_UNIXTIME(add_time,'%d') as day,count(*) as count,sum(pay_price) as price")
+                    ->group("FROM_UNIXTIME(add_time, '%Y%m%e')")
+                    ->order('add_time asc')
+                    ->select()->toArray();
+                //数据查询重新处理
+                $new_order_list = [];
+                foreach ($order_list as $k => $v) {
+                    $new_order_list[$v['day']] = $v;
+                }
+                $now_datebefor = date('Y-m-01');
+                $now_dateafter = date('Y-m-d', strtotime("+1 day"));
+                $now_order_list = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$now_datebefor, $now_dateafter])
+                    ->field("FROM_UNIXTIME(add_time,'%d') as day,count(*) as count,sum(pay_price) as price")
+                    ->group("FROM_UNIXTIME(add_time, '%Y%m%e')")
+                    ->order('add_time asc')
+                    ->select()->toArray();
+                //数据查询重新处理 key 变为当前值
+                $new_now_order_list = [];
+                foreach ($now_order_list as $k => $v) {
+                    $new_now_order_list[$v['day']] = $v;
+                }
+                foreach ($weekarray as $dk => $dd) {
+                    if (!empty($new_order_list[$dk])) {
+                        $weekarray[$dk]['pre'] = $new_order_list[$dk];
+                    } else {
+                        $weekarray[$dk]['pre'] = ['count' => 0, 'day' => $weekarray[$dk][0], 'price' => '0'];
+                    }
+                    if (!empty($new_now_order_list[$dk])) {
+                        $weekarray[$dk]['now'] = $new_now_order_list[$dk];
+                    } else {
+                        $weekarray[$dk]['now'] = ['count' => 0, 'day' => $weekarray[$dk][0], 'price' => '0'];
+                    }
+                }
+                $chartdata = [];
+                $data = [];//临时
+                $chartdata['yAxis']['maxnum'] = 0;//最大值数量
+                $chartdata['yAxis']['maxprice'] = 0;//最大值金额
+                foreach ($weekarray as $k => $v) {
+                    $data['day'][] = $v[0];
+                    $data['pre']['count'][] = $v['pre']['count'];
+                    $data['pre']['price'][] = round($v['pre']['price'], 2);
+                    $data['now']['count'][] = $v['now']['count'];
+                    $data['now']['price'][] = round($v['now']['price'], 2);
+                    if ($chartdata['yAxis']['maxnum'] < $v['pre']['count'] || $chartdata['yAxis']['maxnum'] < $v['now']['count']) {
+                        $chartdata['yAxis']['maxnum'] = $v['pre']['count'] > $v['now']['count'] ? $v['pre']['count'] : $v['now']['count'];//日最大订单数
+                    }
+                    if ($chartdata['yAxis']['maxprice'] < $v['pre']['price'] || $chartdata['yAxis']['maxprice'] < $v['now']['price']) {
+                        $chartdata['yAxis']['maxprice'] = $v['pre']['price'] > $v['now']['price'] ? $v['pre']['price'] : $v['now']['price'];//日最大金额
+                    }
+
+                }
+                $chartdata['legend'] = ['上月金额', '本月金额', '上月订单数', '本月订单数'];//分类
+                $chartdata['xAxis'] = $data['day'];//X轴值
+                //,'itemStyle'=>$series
+//                $series = ['normal' => ['label' => ['show' => true, 'position' => 'top']]];
+                $series1 = ['normal' => ['color' => [
+                    'x' => 0, 'y' => 0, 'x2' => 0, 'y2' => 1,
+                    'colorStops' => [
+                        [
+                            'offset' => 0,
+                            'color' => '#69cdff'
+                        ],
+                        [
+                            'offset' => 0.5,
+                            'color' => '#3eb3f7'
+                        ],
+                        [
+                            'offset' => 1,
+                            'color' => '#1495eb'
+                        ]
+                    ]
+                ]]
+                ];
+                $series2 = ['normal' => ['color' => [
+                    'x' => 0, 'y' => 0, 'x2' => 0, 'y2' => 1,
+                    'colorStops' => [
+                        [
+                            'offset' => 0,
+                            'color' => '#6fdeab'
+                        ],
+                        [
+                            'offset' => 0.5,
+                            'color' => '#44d693'
+                        ],
+                        [
+                            'offset' => 1,
+                            'color' => '#2cc981'
+                        ]
+                    ]
+                ]]
+                ];
+                $chartdata['series'][] = ['name' => $chartdata['legend'][0], 'type' => 'bar', 'itemStyle' => $series1, 'data' => $data['pre']['price']];//分类1值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][1], 'type' => 'bar', 'itemStyle' => $series1, 'data' => $data['now']['price']];//分类1值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][2], 'type' => 'line', 'itemStyle' => $series2, 'data' => $data['pre']['count']];//分类2值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][3], 'type' => 'line', 'itemStyle' => $series2, 'data' => $data['now']['count']];//分类2值
+
+                //统计总数上期
+                $pre_total = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$datebefor, $dateafter])
+                    ->field("count(*) as count,sum(pay_price) as price")
+                    ->find();
+                if ($pre_total) {
+                    $chartdata['pre_cycle']['count'] = [
+                        'data' => $pre_total['count'] ?: 0
+                    ];
+                    $chartdata['pre_cycle']['price'] = [
+                        'data' => $pre_total['price'] ?: 0
+                    ];
+                }
+                //统计总数
+                $total = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$now_datebefor, $now_dateafter])
+                    ->field("count(*) as count,sum(pay_price) as price")
+                    ->find();
+                if ($total) {
+                    $cha_count = intval($pre_total['count']) - intval($total['count']);
+                    $pre_total['count'] = $pre_total['count'] == 0 ? 1 : $pre_total['count'];
+                    $chartdata['cycle']['count'] = [
+                        'data' => $total['count'] ?: 0,
+                        'percent' => round((abs($cha_count) / intval($pre_total['count']) * 100), 2),
+                        'is_plus' => $cha_count > 0 ? -1 : ($cha_count == 0 ? 0 : 1)
+                    ];
+                    $cha_price = round($pre_total['price'], 2) - round($total['price'], 2);
+                    $pre_total['price'] = $pre_total['price'] == 0 ? 1 : $pre_total['price'];
+                    $chartdata['cycle']['price'] = [
+                        'data' => $total['price'] ?: 0,
+                        'percent' => round(abs($cha_price) / $pre_total['price'] * 100, 2),
+                        'is_plus' => $cha_price > 0 ? -1 : ($cha_price == 0 ? 0 : 1)
+                    ];
+                }
+                return $this->success($chartdata);
+                break;
+            case 'year':
+                $weekarray = array('01' => ['一月'], '02' => ['二月'], '03' => ['三月'], '04' => ['四月'], '05' => ['五月'], '06' => ['六月'], '07' => ['七月'], '08' => ['八月'], '09' => ['九月'], '10' => ['十月'], '11' => ['十一月'], '12' => ['十二月']);
+                $datebefor = date('Y-01-01', strtotime('-1 year'));
+                $dateafter = date('Y-12-31', strtotime('-1 year'));
+                $order_list = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$datebefor, $dateafter])
+                    ->field("FROM_UNIXTIME(add_time,'%m') as day,count(*) as count,sum(pay_price) as price")
+                    ->group("FROM_UNIXTIME(add_time, '%Y%m')")
+                    ->order('add_time asc')
+                    ->select()->toArray();
+                //数据查询重新处理
+                $new_order_list = [];
+                foreach ($order_list as $k => $v) {
+                    $new_order_list[$v['day']] = $v;
+                }
+                $now_datebefor = date('Y-01-01');
+                $now_dateafter = date('Y-m-d');
+                $now_order_list = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$now_datebefor, $now_dateafter])
+                    ->field("FROM_UNIXTIME(add_time,'%m') as day,count(*) as count,sum(pay_price) as price")
+                    ->group("FROM_UNIXTIME(add_time, '%Y%m')")
+                    ->order('add_time asc')
+                    ->select()->toArray();
+                //数据查询重新处理 key 变为当前值
+                $new_now_order_list = [];
+                foreach ($now_order_list as $k => $v) {
+                    $new_now_order_list[$v['day']] = $v;
+                }
+                foreach ($weekarray as $dk => $dd) {
+                    if (!empty($new_order_list[$dk])) {
+                        $weekarray[$dk]['pre'] = $new_order_list[$dk];
+                    } else {
+                        $weekarray[$dk]['pre'] = ['count' => 0, 'day' => $weekarray[$dk][0], 'price' => '0'];
+                    }
+                    if (!empty($new_now_order_list[$dk])) {
+                        $weekarray[$dk]['now'] = $new_now_order_list[$dk];
+                    } else {
+                        $weekarray[$dk]['now'] = ['count' => 0, 'day' => $weekarray[$dk][0], 'price' => '0'];
+                    }
+                }
+                $chartdata = [];
+                $data = [];//临时
+                $chartdata['yAxis']['maxnum'] = 0;//最大值数量
+                $chartdata['yAxis']['maxprice'] = 0;//最大值金额
+                foreach ($weekarray as $k => $v) {
+                    $data['day'][] = $v[0];
+                    $data['pre']['count'][] = $v['pre']['count'];
+                    $data['pre']['price'][] = round($v['pre']['price'], 2);
+                    $data['now']['count'][] = $v['now']['count'];
+                    $data['now']['price'][] = round($v['now']['price'], 2);
+                    if ($chartdata['yAxis']['maxnum'] < $v['pre']['count'] || $chartdata['yAxis']['maxnum'] < $v['now']['count']) {
+                        $chartdata['yAxis']['maxnum'] = $v['pre']['count'] > $v['now']['count'] ? $v['pre']['count'] : $v['now']['count'];//日最大订单数
+                    }
+                    if ($chartdata['yAxis']['maxprice'] < $v['pre']['price'] || $chartdata['yAxis']['maxprice'] < $v['now']['price']) {
+                        $chartdata['yAxis']['maxprice'] = $v['pre']['price'] > $v['now']['price'] ? $v['pre']['price'] : $v['now']['price'];//日最大金额
+                    }
+                }
+                $chartdata['legend'] = ['去年金额', '今年金额', '去年订单数', '今年订单数'];//分类
+                $chartdata['xAxis'] = $data['day'];//X轴值
+                //,'itemStyle'=>$series
+//                $series = ['normal' => ['label' => ['show' => true, 'position' => 'top']]];
+                $series1 = ['normal' => ['color' => [
+                    'x' => 0, 'y' => 0, 'x2' => 0, 'y2' => 1,
+                    'colorStops' => [
+                        [
+                            'offset' => 0,
+                            'color' => '#69cdff'
+                        ],
+                        [
+                            'offset' => 0.5,
+                            'color' => '#3eb3f7'
+                        ],
+                        [
+                            'offset' => 1,
+                            'color' => '#1495eb'
+                        ]
+                    ]
+                ]]
+                ];
+                $series2 = ['normal' => ['color' => [
+                    'x' => 0, 'y' => 0, 'x2' => 0, 'y2' => 1,
+                    'colorStops' => [
+                        [
+                            'offset' => 0,
+                            'color' => '#6fdeab'
+                        ],
+                        [
+                            'offset' => 0.5,
+                            'color' => '#44d693'
+                        ],
+                        [
+                            'offset' => 1,
+                            'color' => '#2cc981'
+                        ]
+                    ]
+                ]]
+                ];
+                $chartdata['series'][] = ['name' => $chartdata['legend'][0], 'type' => 'bar', 'itemStyle' => $series1, 'data' => $data['pre']['price']];//分类1值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][1], 'type' => 'bar', 'itemStyle' => $series1, 'data' => $data['now']['price']];//分类1值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][2], 'type' => 'line', 'itemStyle' => $series2, 'data' => $data['pre']['count']];//分类2值
+                $chartdata['series'][] = ['name' => $chartdata['legend'][3], 'type' => 'line', 'itemStyle' => $series2, 'data' => $data['now']['count']];//分类2值
+
+                //统计总数上期
+                $pre_total = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$datebefor, $dateafter])
+                    ->field("count(*) as count,sum(pay_price) as price")
+                    ->find();
+                if ($pre_total) {
+                    $chartdata['pre_cycle']['count'] = [
+                        'data' => $pre_total['count'] ?: 0
+                    ];
+                    $chartdata['pre_cycle']['price'] = [
+                        'data' => $pre_total['price'] ?: 0
+                    ];
+                }
+                //统计总数
+                $total = StoreOrder::where('mer_id', $mer_id)->where('add_time', 'between time', [$now_datebefor, $now_dateafter])
+                    ->field("count(*) as count,sum(pay_price) as price")
+                    ->find();
+                if ($total) {
+                    $cha_count = intval($pre_total['count']) - intval($total['count']);
+                    $pre_total['count'] = $pre_total['count'] == 0 ? 1 : $pre_total['count'];
+                    $chartdata['cycle']['count'] = [
+                        'data' => $total['count'] ?: 0,
+                        'percent' => round((abs($cha_count) / intval($pre_total['count']) * 100), 2),
+                        'is_plus' => $cha_count > 0 ? -1 : ($cha_count == 0 ? 0 : 1)
+                    ];
+                    $cha_price = round($pre_total['price'], 2) - round($total['price'], 2);
+                    $pre_total['price'] = $pre_total['price'] == 0 ? 1 : $pre_total['price'];
+                    $chartdata['cycle']['price'] = [
+                        'data' => $total['price'] ?: 0,
+                        'percent' => round(abs($cha_price) / $pre_total['price'] * 100, 2),
+                        'is_plus' => $cha_price > 0 ? -1 : ($cha_price == 0 ? 0 : 1)
+                    ];
+                }
+                return $this->success($chartdata);
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * 用户图表
+     */
+    public function userChart()
+    {
+        $mer_id = $this->merId ?: '';
+        $starday = date('Y-m-d', strtotime('-30 day'));
+        $yesterday = date('Y-m-d');
+
+        $user_list = User::where('mer_id', $mer_id)->where('add_time', 'between time', [$starday, $yesterday])
+            ->field("FROM_UNIXTIME(add_time,'%m-%e') as day,count(*) as count")
+            ->group("FROM_UNIXTIME(add_time, '%Y%m%e')")
+            ->order('add_time asc')
+            ->select()->toArray();
+        $chartdata = [];
+        $data = [];
+        $chartdata['legend'] = ['用户数'];//分类
+        $chartdata['yAxis']['maxnum'] = 0;//最大值数量
+        $chartdata['xAxis'] = [date('m-d')];//X轴值
+        $chartdata['series'] = [0];//分类1值
+        if (!empty($user_list)) {
+            foreach ($user_list as $k => $v) {
+                $data['day'][] = $v['day'];
+                $data['count'][] = $v['count'];
+                if ($chartdata['yAxis']['maxnum'] < $v['count'])
+                    $chartdata['yAxis']['maxnum'] = $v['count'];
+            }
+            $chartdata['xAxis'] = $data['day'];//X轴值
+            $chartdata['series'] = $data['count'];//分类1值
+        }
+        $chartdata['bing_xdata'] = ['未消费用户', '消费一次用户', '留存客户', '回流客户'];
+        $color = ['#5cadff', '#b37feb', '#19be6b', '#ff9900'];
+        $pay[0] = User::where('mer_id', $mer_id)->where('pay_count', 0)->count();
+        $pay[1] = User::where('mer_id', $mer_id)->where('pay_count', 1)->count();
+        $pay[2] = User::where('mer_id', $mer_id)->where('pay_count', '>', 0)->where('pay_count', '<', 4)->count();
+        $pay[3] = User::where('mer_id', $mer_id)->where('pay_count', '>', 4)->count();
+        foreach ($pay as $key => $item) {
+            $bing_data[] = ['name' => $chartdata['bing_xdata'][$key], 'value' => $pay[$key], 'itemStyle' => ['color' => $color[$key]]];
+        }
+        $chartdata['bing_data'] = $bing_data;
+        return $this->success($chartdata);
+    }
+
+    /**
+     * 交易额排行
+     * @return mixed
+     */
+    public function purchaseRanking()
+    {
+        $mer_id = $this->merId ?: '';
+        $dlist = StoreProductAttrValue::alias('v')
+            ->where('p.mer_id', $mer_id)
+            ->join('store_product p', 'v.product_id=p.id')
+            ->field('v.product_id,p.store_name,sum(v.sales * v.price) as val')->group('v.product_id')->order('val', 'desc')->limit(20)->select()->toArray();
+        $slist = StoreProduct::where('mer_id', $mer_id)->field('id as product_id,store_name,sales * price as val')->where('is_del', 0)->order('val', 'desc')->limit(20)->select()->toArray();
+        $data = array_merge($dlist, $slist);
+        $last_names = array_column($data, 'val');
+        array_multisort($last_names, SORT_DESC, $data);
+        $list = array_splice($data, 0, 20);
+        return $this->success(compact('list'));
+    }
+
+    /**
+     * 待办事统计
+     * @return mixed
+     */
+    public function jnotice()
+    {
+        $mer_id = $this->merId ?: '';
+        return $this->success(NoticeRepositories::jnotice($mer_id));
+    }
+
+    //获取待发货订单数量
+    public function getOrderNum()
+    {
+        $mer_id = $this->merId ?: '';
+        $ordernum = StoreOrder::where('mer_id', $mer_id)->where('paid', 1)->where('status', 0)
+            ->where('shipping_type', 1)->where('refund_status', 0)
+            ->where('is_del', 0)->count();
+        return $this->success($ordernum);
+    }
+
+}

+ 104 - 0
app/adminapi/controller/Login.php

@@ -0,0 +1,104 @@
+<?php
+
+namespace app\adminapi\controller;
+
+
+use app\models\system\SystemAdmin;
+use app\models\system\SystemConfig;
+use app\models\system\SystemMenus;
+use crmeb\basic\BaseController;
+use crmeb\repositories\AdminRuleRepositories;
+use crmeb\services\GroupDataService;
+use crmeb\services\UtilService;
+use crmeb\traits\RestFulTrait;
+use crmeb\utils\Captcha;
+use think\facade\Cache;
+use think\facade\Session;
+
+/**
+ * 后台登陆
+ * Class Login
+ * @package app\adminapi\controller
+ */
+class Login extends BaseController
+{
+
+    /**
+     * 验证码
+     * @return $this|\think\Response
+     */
+    public function captcha()
+    {
+        return (new Captcha())->create();
+    }
+
+    /**
+     * 登陆
+     * @return mixed
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function login()
+    {
+        [$account, $pwd, $imgcode] = UtilService::postMore([
+            'account', 'pwd', ['imgcode', '']
+        ], $this->request, true);
+
+        if (!(new Captcha)->check($imgcode)) {
+            return app('json')->fail('验证码错误,请重新输入');
+        }
+
+        $this->validate(['account' => $account, 'pwd' => $pwd], \app\adminapi\validates\setting\SystemAdminValidata::class, 'get');
+
+        $error = Cache::get('login_error') ?: ['num' => 0, 'time' => time()];
+        $error['num'] = 0;
+        if ($error['num'] >= 5 && $error['time'] > strtotime('- 5 minutes'))
+            return $this->fail('错误次数过多,请稍候再试!');
+        $adminInfo = SystemAdmin::login($account, $pwd);
+        if ($adminInfo) {
+            $token = SystemAdmin::createToken($adminInfo, 'admin');
+            if ($token === false) {
+                return app('json')->fail(SystemAdmin::getErrorInfo());
+            }
+            Cache::set('login_error', null);
+            //获取用户菜单
+            $menusModel = new SystemMenus();
+            $menus = $menusModel->getRoute($adminInfo->roles, $adminInfo['level']);
+            $unique_auth = $menusModel->getUniqueAuth($adminInfo->roles, $adminInfo['level']);
+            return app('json')->success([
+                'token' => $token['token'],
+                'expires_time' => $token['params']['exp'],
+                'menus' => $menus,
+                'unique_auth' => $unique_auth,
+                'user_info' => [
+                    'id' => $adminInfo->getData('id'),
+                    'account' => $adminInfo->getData('account'),
+                    'head_pic' => $adminInfo->getData('head_pic'),
+                ],
+                'logo' => sys_config('site_logo'),
+                'logo_square' => sys_config('site_logo_square'),
+                'version' => get_crmeb_version()
+            ]);
+        } else {
+            $error['num'] += 1;
+            $error['time'] = time();
+            Cache::set('login_error', $error);
+            return app('json')->fail(SystemAdmin::getErrorInfo('用户名错误,请重新输入'));
+        }
+    }
+
+    /**
+     * 获取后台登录页轮播图以及LOGO
+     * @return mixed
+     */
+    public function info()
+    {
+        $data['slide'] = GroupDataService::getData('admin_login_slide', 0, false, $this->merId) ?? [];
+        $data['logo_square'] = sys_config('site_logo_square');//透明
+        $data['logo_rectangle'] = sys_config('site_logo');//方形
+        $data['login_logo'] = sys_config('login_logo');//登陆
+        return app('json')->success($data);
+    }
+}

+ 64 - 0
app/adminapi/controller/Test.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace app\adminapi\controller;
+
+
+use crmeb\basic\BaseBusiness;
+use crmeb\business\order\StoreOrder;
+use crmeb\repositories\AuthRepository;
+use crmeb\services\CacheService;
+use Firebase\JWT\JWT;
+use app\models\system\SystemAdmin;
+use think\facade\Cache;
+use think\facade\Log;
+use think\Request;
+use \crmeb\utils\Captcha;
+use think\facade\Config;
+
+class Test
+{
+
+    public function index()
+    {
+//        $token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjcm1lYnByby5uZXQiLCJhdWQiOiJjcm1lYnByby5uZXQiLCJpYXQiOjE1ODY0MTIwMDUsIm5iZiI6MTU4NjQxMjAwNSwiZXhwIjoxNTg2NDIyODA1LCJqdGkiOnsiaWQiOjEsInR5cGUiOiJhZG1pbiJ9fQ.k3lM-a2w9x9angt1Dkt5UrvcuXg04kfmTYv3wBdacyM";
+//
+//
+//        $tks = explode('.', $token);
+//        if (count($tks) != 3) {
+//            //throw new UnexpectedValueException('Wrong number of segments');
+//        }
+//        list($headb64, $bodyb64, $cryptob64) = explode('.', $token);
+//        $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64));
+//        dump($payload, date('Y-m-d H:i:s', $payload->exp), $payload->jti->id);
+//        $order = new StoreOrder();
+//        $order->getList(['page' => \request()->param('page', 1), 'limit' => \request()->param('limit', 10)]);
+
+//        $res = AuthRepository::getTokenBucket('3132');
+//        $redis = Cache::store('redis');
+//        dump($redis->get('3123'));
+//        Log::info('订单支付成功下发队列消息成功订单号为:WXQWEWQREWREWR123123123' );
+//        $res = \think\facade\Queue::push(\crmeb\jobs\OrderJob::class, ['data' => [['order_id'=>'wx34234324'],13213]]);
+//        var_dump($res);
+        var_dump(CacheService::get('captcha'));
+    }
+
+    public function show()
+    {
+        $captcha = new Captcha();
+        $generate = $captcha->generate();
+        CacheService::set('captcha', $generate, 1800);
+        return $captcha->create('', $generate, false);
+    }
+
+    public function login(Request $request)
+    {
+        dump($request->adminInfo());
+    }
+
+    public function test()
+    {
+        $systemAdmin = SystemAdmin::login('123456', '123456');
+        $res = SystemAdmin::createToken($systemAdmin, 'admin', ['exp' => time() + 30]);
+        dump($res, date('Y-m-d H:i:s', $res['params']['exp']));
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1

+ 1 - 0
app/adminapi/controller/v1/agent/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/agent

+ 417 - 0
app/adminapi/controller/v1/agent/AgentManage.php

@@ -0,0 +1,417 @@
+<?php
+
+namespace app\adminapi\controller\v1\agent;
+
+use app\adminapi\controller\AuthController;
+use Exception;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use app\models\user\{
+    User, UserBill
+};
+use app\models\routine\{
+    RoutineCode, RoutineQrcode
+};
+use app\models\store\StoreOrder;
+use app\models\system\SystemAttachment;
+use app\models\wechat\WechatUser as UserModel;
+use crmeb\services\{
+    QrcodeService, UploadService, UtilService as Util
+};
+
+/**
+ * 分销商管理控制器
+ * Class AgentManage
+ * @package app\adminapi\controller\v1\agent
+ */
+class AgentManage extends AuthController
+{
+    /**
+     * 分销管理列表
+     * @return mixed
+     * @throws Exception
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['nickname', ''],
+            ['data', ''],
+            ['excel', ''],
+            ['page', 1],
+            ['limit', 20],
+        ]);
+        $where['mer_id'] = $this->merId;
+        return $this->success(UserModel::agentSystemPage($where));
+    }
+
+    /**
+     * 分销头部统计
+     * @return mixed
+     * @throws Exception
+     */
+    public function get_badge()
+    {
+        $where = Util::getMore([
+            ['data', ''],
+            ['nickname', ''],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $res = UserModel::getSpreadBadge($where);
+        return $this->success(compact('res'));
+    }
+
+    /**
+     * 推广人列表
+     * @return mixed
+     * @throws Exception
+     */
+    public function get_stair_list()
+    {
+        $where = Util::getMore([
+            ['uid', 0],
+            ['data', ''],
+            ['nickname', ''],
+            ['type', ''],
+            ['page', 1],
+            ['limit', 20],
+        ]);
+        if (!$where['uid'] || !User::merSet($this->merId)->where('uid', $where['uid'])->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        return $this->success(UserModel::getStairList($where));
+    }
+
+    /**
+     * 推广人列表头部统计
+     * @return mixed
+     * @throws Exception
+     */
+    public function get_stair_badge()
+    {
+        $where = Util::getMore([
+            ['uid', ''],
+            ['data', ''],
+            ['nickname', ''],
+            ['type', ''],
+        ]);
+        if (!$where['uid'] || !User::merSet($this->merId)->where('uid', $where['uid'])->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        $res = UserModel::getSairBadge($where);
+        return $this->success(compact('res'));
+    }
+
+    /**
+     *  统计推广订单列表
+     * @return mixed
+     * @throws Exception
+     */
+    public function get_stair_order_list()
+    {
+        $where = Util::getMore([
+            ['uid', 0],
+            ['data', ''],
+            ['order_id', ''],
+            ['type', ''],
+            ['page', 1],
+            ['limit', 20],
+        ]);
+        if (!$where['uid'] || !User::merSet($this->merId)->where('uid', $where['uid'])->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        return $this->success(UserModel::getStairOrderList($where));
+    }
+
+    /**
+     * 统计推广订单头部统计
+     * @return mixed
+     * @throws Exception
+     */
+    public function get_stair_order_badge()
+    {
+        $where = Util::getMore([
+            ['uid', ''],
+            ['data', ''],
+            ['order_id', ''],
+            ['type', ''],
+        ]);
+        if (!$where['uid'] || !User::merSet($this->merId)->where('uid', $where['uid'])->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        return $this->success(UserModel::getStairOrderBadge($where));
+    }
+
+    /**
+     * 二级推荐人页面
+     * @param string $uid
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function stair_two($uid = '')
+    {
+        if ($uid == '') return $this->fail('参数错误');
+        if (!User::merSet($this->merId)->where('uid', $uid)->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        $spread_uid = User::where('spread_uid', $uid)->column('uid', 'uid');
+        if (count($spread_uid))
+            $spread_uid_two = User::where('spread_uid', 'in', $spread_uid)->column('uid', 'uid');
+        else
+            $spread_uid_two = [0];
+        $list = User::alias('u')
+            ->where('u.uid', 'in', $spread_uid_two)
+            ->field('u.avatar,u.nickname,u.now_money,u.spread_time,u.uid')
+            ->where('u.status', 1)
+            ->order('u.add_time DESC')
+            ->select()
+            ->toArray();
+        foreach ($list as $key => $value) $list[$key]['orderCount'] = StoreOrder::getOrderCount($value['uid']) ?: 0;
+        return $this->success(compact('list'));
+    }
+
+    /**
+     * 批量清除推广权限
+     * @return mixed
+     * @throws Exception
+     */
+    public function delete_promoter()
+    {
+        list($uids) = Util::postMore([
+            ['uids', []]
+        ], $this->request, true);
+        if (!count($uids)) return $this->fail('请选择需要解除推广权限的用户!');
+        User::beginTrans();
+        try {
+            if (User::merSet($this->merId)->where('uid', 'in', $uids)->update(['is_promoter' => 0])) {
+                User::commitTrans();
+                return $this->success('解除成功');
+            } else {
+                User::rollbackTrans();
+                return $this->fail('解除失败');
+            }
+        } catch (\PDOException $e) {
+            User::rollbackTrans();
+            return $this->fail('数据库操作错误', ['line' => $e->getLine(), 'message' => $e->getMessage()]);
+        } catch (Exception $e) {
+            User::rollbackTrans();
+            return $this->fail('系统错误', ['line' => $e->getLine(), 'message' => $e->getMessage()]);
+        }
+
+    }
+
+    /**
+     * 查看公众号推广二维码
+     * @param string $uid
+     * @param string $action
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function look_code($uid = '', $action = '')
+    {
+        if (!$uid || !$action) return $this->fail('缺少参数');
+        if (!User::merSet($this->merId)->where('uid', $uid)->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        try {
+            if (method_exists($this, $action)) {
+                $res = $this->$action($uid);
+                if ($res)
+                    return $this->success($res);
+                else
+                    return $this->fail(isset($res['msg']) ? $res['msg'] : '获取失败,请稍后再试!');
+            } else
+                return $this->fail('暂无此方法');
+        } catch (Exception $e) {
+            return $this->fail('获取推广二维码失败,请检查您的微信配置', ['line' => $e->getLine(), 'messag' => $e->getMessage()]);
+        }
+    }
+
+    /**
+     * 获取小程序二维码
+     * @param $uid
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws \think\Exception
+     * @throws DbException
+     */
+    public function routine_code($uid)
+    {
+        $userInfo = User::getUserInfos($uid, $this->merId);
+        if (!$userInfo) {
+            return $this->fail('查看的用户不存在');
+        }
+        $name = $userInfo['uid'] . '_' . $userInfo['is_promoter'] . '_user.jpg';
+        $imageInfo = SystemAttachment::getInfo($name, 'name');
+        if (!$imageInfo) {
+            $res = RoutineCode::getShareCode($uid, 'spread', '', '', $this->merId);
+            if (!$res) throw new \think\Exception('二维码生成失败');
+            $upload = UploadService::init(null, $this->merId);
+            if ($upload->to('routine/spread/code')->stream($res['res'], $name) === false) {
+                return $upload->getError();
+            }
+            $imageInfo = $upload->getUploadInfo();
+            SystemAttachment::attachmentAdd($imageInfo['name'], $imageInfo['size'], $imageInfo['type'], $imageInfo['dir'], $imageInfo['thumb_path'], 1, $imageInfo['image_type'], $imageInfo['time']);
+            RoutineQrcode::setRoutineQrcodeFind($res['id'], ['status' => 1, 'time' => time(), 'qrcode_url' => $imageInfo['dir']]);
+            $urlCode = $imageInfo['dir'];
+        } else $urlCode = $imageInfo['att_dir'];
+        return ['code_src' => $urlCode];
+    }
+
+    /**
+     * 获取公众号二维码
+     * @param $uid
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws \think\Exception
+     */
+    public function wechant_code($uid)
+    {
+        if (!$uid || !User::merSet($this->merId)->where('uid', $uid)->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        $qr_code = QrcodeService::getTemporaryQrcode('spread', $uid);
+        if (isset($qr_code['url']))
+            return ['code_src' => $qr_code['url']];
+        else
+            throw new \think\Exception('获取失败,请稍后再试!');
+    }
+
+    /**
+     * TODO 查看小程序推广二维码
+     * @param string $uid
+     * @return string
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function look_xcx_code($uid = '')
+    {
+        if (!$uid || !User::merSet($this->merId)->where('uid', $uid)->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        if (!strlen(trim($uid))) return $this->fail('缺少参数');
+        try {
+            $userInfo = User::getUserInfos($uid);
+            $name = $userInfo['uid'] . '_' . $userInfo['is_promoter'] . '_user.jpg';
+            $imageInfo = SystemAttachment::getInfo($name, 'name');
+            if (!$imageInfo) {
+                $res = RoutineCode::getShareCode($uid, 'spread', '', '', $this->merId);
+                if (!$res) return $this->fail('二维码生成失败');
+                $upload = UploadService::init(null, $this->merId);
+                if ($upload->to('routine/spread/code')->stream($res['res'], $name) === false) {
+                    return $upload->getError();
+                }
+                $imageInfo = $upload->getUploadInfo();
+                SystemAttachment::attachmentAdd($imageInfo['name'], $imageInfo['size'], $imageInfo['type'], $imageInfo['dir'], $imageInfo['thumb_path'], 1, $imageInfo['image_type'], $imageInfo['time']);
+                RoutineQrcode::setRoutineQrcodeFind($res['id'], ['status' => 1, 'time' => time(), 'qrcode_url' => $imageInfo['dir']]);
+                $urlCode = $imageInfo['dir'];
+            } else $urlCode = $imageInfo['att_dir'];
+            return $this->success(['code_src' => $urlCode]);
+        } catch (Exception $e) {
+            return $this->fail('查看推广二维码失败!', ['line' => $e->getLine(), 'meassge' => $e->getMessage()]);
+        }
+    }
+
+    /**
+     * 查看H5推广二维码
+     * @param string $uid
+     * @return mixed|string
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function look_h5_code($uid = '')
+    {
+        if (!$uid || !User::merSet($this->merId)->where('uid', $uid)->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        if (!strlen(trim($uid))) return $this->fail('缺少参数');
+        try {
+            $userInfo = User::getUserInfos($uid);
+            $name = $userInfo['uid'] . '_h5_' . $userInfo['is_promoter'] . '_user.jpg';
+            $imageInfo = SystemAttachment::getInfo($name, 'name');
+            if (!$imageInfo) {
+                $urlCode = QrcodeService::getWechatQrcodePath($uid . '_h5_' . $userInfo['is_promoter'] . '_user.jpg', '');
+            } else $urlCode = $imageInfo['att_dir'];
+            return $this->success(['code_src' => $urlCode]);
+        } catch (Exception $e) {
+            return $this->fail('查看推广二维码失败!', ['line' => $e->getLine(), 'meassge' => $e->getMessage()]);
+        }
+    }
+
+    /**
+     * 解除单个用户的推广权限
+     * @param int $uid
+     * @return
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function delete_spread($uid)
+    {
+        if (!$uid || !User::merSet($this->merId)->where('uid', $uid)->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        $res = User::where('uid', $uid)->update(['spread_uid' => 0]);
+        if ($res !== false)
+            return $this->success('解除成功');
+        else
+            return $this->fail('解除失败');
+    }
+
+    /**
+     * 清除推广人
+     * @param int $uid
+     * @return
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function empty_spread($uid = 0)
+    {
+        if (!$uid) return $this->fail('缺少参数');
+        if (!$uid || !User::merSet($this->merId)->where('uid', $uid)->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        $res = true;
+        $spread_uid = User::where('spread_uid', $uid)->column('uid', 'uid');
+        if (count($spread_uid)) $res = $res && false !== User::where('spread_uid', 'in', $spread_uid)->update(['spread_uid' => 0]);
+        $res = $res && false !== User::where('spread_uid', $uid)->update(['spread_uid' => 0]);
+        if ($res)
+            return $this->success('清除成功');
+        else
+            return $this->fail('清除失败');
+    }
+
+    /**
+     * 个人资金详情页面
+     * @param string $uid
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function now_money($uid = '')
+    {
+        if ($uid == '') return $this->fail('参数错误');
+        if (!$uid || !User::merSet($this->merId)->where('uid', $uid)->find()) {
+            return $this->fail('查看的用户不存在');
+        }
+        $list = UserBill::where('uid', $uid)->where('category', 'now_money')
+            ->field('mark,pm,number,add_time')
+            ->where('status', 1)->order('add_time DESC')->select()->toArray();
+        foreach ($list as &$v) {
+            $v['add_time'] = date('Y-m-d H:i:s', $v['add_time']);
+        }
+        return $this->success($list);
+    }
+
+}

+ 1 - 0
app/adminapi/controller/v1/application/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/application

+ 1 - 0
app/adminapi/controller/v1/application/routine/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/application/routine

+ 151 - 0
app/adminapi/controller/v1/application/routine/RoutineTemplate.php

@@ -0,0 +1,151 @@
+<?php
+
+namespace app\adminapi\controller\v1\application\routine;
+
+use think\Request;
+use think\facade\Route as Url;
+use app\adminapi\controller\AuthController;
+use crmeb\services\{FormBuilder as Form, UtilService as Util};
+use app\models\routine\RoutineTemplate as RoutineTemplateModel;
+
+class RoutineTemplate extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['name', ''],
+            ['status', '']
+        ], $this->request);
+        $list = RoutineTemplateModel::SystemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $f = array();
+        $f[] = Form::input('tempkey', '模板编号');
+        $f[] = Form::input('tempid', '模板ID');
+        $f[] = Form::input('name', '模板名');
+        $f[] = Form::input('content', '回复内容')->type('textarea');
+        $f[] = Form::radio('status', '状态', 1)->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
+        return $this->makePostForm('添加模板消息', $f, Url::buildUrl('/app/routine'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $data = Util::postMore([
+            'tempkey',
+            'tempid',
+            'name',
+            'content',
+            ['status', 0]
+        ]);
+        if ($data['tempkey'] == '') return $this->fail('请输入模板编号');
+        if ($data['tempkey'] != '' && RoutineTemplateModel::be($data['tempkey'], 'tempkey'))
+            return $this->fail('请输入模板编号已存在,请重新输入');
+        if ($data['tempid'] == '') return $this->fail('请输入模板ID');
+        if ($data['name'] == '') return $this->fail('请输入模板名');
+        if ($data['content'] == '') return $this->fail('请输入回复内容');
+        $data['add_time'] = time();
+        RoutineTemplateModel::create($data);
+        return $this->success('添加模板消息成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        //
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $product = RoutineTemplateModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        $f = array();
+        $f[] = Form::input('tempkey', '模板编号', $product->getData('tempkey'))->disabled(1);
+        $f[] = Form::input('name', '模板名', $product->getData('name'))->disabled(1);
+        $f[] = Form::input('tempid', '模板ID', $product->getData('tempid'));
+        $f[] = Form::radio('status', '状态', $product->getData('status'))->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
+        return $this->makePostForm('编辑模板消息', $f, Url::buildUrl('/app/routine/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param \think\Request $request
+     * @param int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $data = Util::postMore([
+            'tempid',
+            ['status', 0]
+        ]);
+        if ($data['tempid'] == '') return $this->fail('请输入模板ID');
+        if (!$id) return $this->fail('数据不存在');
+        $product = RoutineTemplateModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        RoutineTemplateModel::edit($data, $id);
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在!');
+        if (!RoutineTemplateModel::del($id))
+            return $this->fail(RoutineTemplateModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        RoutineTemplateModel::where(['id' => $id])->update(['status' => $status]);
+
+        return $this->success($status == 0 ? '关闭成功' : '开启成功');
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/application/wechat/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/application/wechat

+ 43 - 0
app/adminapi/controller/v1/application/wechat/Menus.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace app\adminapi\controller\v1\application\wechat;
+
+
+use app\adminapi\controller\AuthController;
+use app\models\system\Cache;
+use crmeb\services\WechatService;
+
+/**
+ * 微信菜单  控制器
+ * Class Menus
+ * @package app\admin\controller\wechat
+ */
+class Menus extends AuthController
+{
+    public function index()
+    {
+        $menus = Cache::where('key', 'wechat_menus')->value('result');
+        $menus = empty($menus) ? [] : json_decode($menus, true);
+//        $menus = $menus ?: [];
+        return $this->success(compact('menus'));
+    }
+
+    public function save()
+    {
+        $buttons = request()->post('button/a', []);
+        if (!count($buttons)) return $this->fail('请添加至少一个按钮');
+        try {
+            WechatService::menuService()->add($buttons);
+            $count = Cache::where('key', 'wechat_menus')->count();
+            if ($count) {
+                $count = Cache::where('key', 'wechat_menus')->where('result', json_encode($buttons))->count();
+                if (!$count)
+                    Cache::where('key', 'wechat_menus')->update(['result' => json_encode($buttons), 'add_time' => time()]);
+            } else
+                Cache::insert(['key' => 'wechat_menus', 'result' => json_encode($buttons), 'add_time' => time()], true);
+            return $this->success('修改成功!');
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+    }
+}

+ 116 - 0
app/adminapi/controller/v1/application/wechat/Reply.php

@@ -0,0 +1,116 @@
+<?php
+
+namespace app\adminapi\controller\v1\application\wechat;
+
+use app\models\wechat\WechatKey;
+use app\models\wechat\WechatReply;
+use app\adminapi\controller\AuthController;
+use EasyWeChat\Core\Exceptions\HttpException;
+use crmeb\services\{UtilService as Util};
+
+/**
+ * 关键字管理  控制器
+ * Class Reply
+ * @package app\admin\controller\wechat
+ */
+class Reply extends AuthController
+{
+    /**关注回复
+     * @return mixed|void
+     */
+    public function reply()
+    {
+        $where = Util::getMore([
+            ['key', ''],
+        ]);
+        if ($where['key'] == '') return $this->fail('请输入参数key');
+        $info = WechatReply::getDataByKey($where['key']);
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 关键字回复列表
+     * */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 15],
+            ['key', ''],
+            ['type', ''],
+        ]);
+        $list = WechatReply::getKeyAll($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 关键字详情
+     * */
+    public function read($id)
+    {
+        $info = WechatReply::getKeyInfo($id);
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 保存关键字
+     * */
+    public function save($id = 0)
+    {
+        $data = Util::postMore([
+            'key',
+            'type',
+            ['status', 0],
+            ['data', []],
+        ]);
+        try {
+            if (!isset($data['key']) && empty($data['key']))
+                return $this->fail('请输入关键字');
+            if (!isset($data['type']) && empty($data['type']))
+                return $this->fail('请选择回复类型');
+            if (!in_array($data['type'], WechatReply::$reply_type))
+                return $this->fail('回复类型有误!');
+
+            if (!isset($data['data']) || !is_array($data['data']))
+                return $this->fail('回复消息参数有误!');
+            $res = WechatReply::redact($data['data'], $id, $data['key'], $data['type'], $data['status']);
+            if (!$res)
+                return $this->fail(WechatReply::getErrorInfo());
+            else
+                return $this->success('保存成功!', $data);
+        } catch (HttpException $e) {
+            return $this->fail($e->getMessage());
+        }
+    }
+
+    /**
+     * 删除关键字
+     * */
+    public function delete($id)
+    {
+        if (!WechatReply::del($id))
+            return $this->fail(WechatReply::getErrorInfo('删除失败,请稍候再试!'));
+        else{
+            $res = WechatKey::where('reply_id',$id)->delete();
+            if (!$res){
+                return $this->fail(WechatKey::getErrorInfo('删除失败,请稍候再试!'));
+            }
+        }
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        WechatReply::where(['id' => $id])->update(['status' => $status]);
+
+        return $this->success($status == 0 ? '禁用成功' : '启用成功');
+    }
+
+}

+ 197 - 0
app/adminapi/controller/v1/application/wechat/StoreService.php

@@ -0,0 +1,197 @@
+<?php
+
+namespace app\adminapi\controller\v1\application\wechat;
+
+use app\adminapi\controller\AuthController;
+use crmeb\services\CacheService;
+use crmeb\services\FormBuilder as Form;
+use crmeb\services\UtilService as Util;
+use think\facade\Route as Url;
+use app\models\wechat\StoreService as ServiceModel;
+use app\models\wechat\StoreServiceLog as StoreServiceLog;
+use app\models\wechat\WechatUser as UserModel;
+
+/**
+ * 客服管理
+ * Class StoreService
+ * @package app\admin\controller\store
+ */
+class StoreService extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20]
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = ServiceModel::getList($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+            ['data', ''],
+            ['type', '']
+//            ['tagid_list',''],
+//            ['groupid','-1'],
+//            ['sex',''],
+//            ['export',''],
+//            ['stair',''],
+//            ['second',''],
+//            ['order_stair',''],
+//            ['order_second',''],
+//            ['subscribe',''],
+//            ['now_money',''],
+//            ['is_promoter',''],
+        ], $this->request);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = UserModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 保存新建的资源
+     */
+    public function save()
+    {
+        $params = Util::postMore([
+            'uids'
+        ]);
+        if (count($params["uids"]) <= 0) return $this->fail('请选择要添加的用户!');
+        if (ServiceModel::where('mer_id', 0)->where(array("uid" => array("in", implode(',', $params["uids"]))))->count()) return $this->fail('添加用户中存在已有的客服!');
+        foreach ($params["uids"] as $key => $value) {
+            $now_user = UserModel::get($value);
+            $data[$key]["mer_id"] = $this->merId ?: '';
+            $data[$key]["uid"] = $now_user["uid"];
+            $data[$key]["avatar"] = $now_user["headimgurl"];
+            $data[$key]["nickname"] = $now_user["nickname"];
+            $data[$key]["add_time"] = time();
+        }
+        ServiceModel::setAll($data);
+        return $this->success('添加成功!');
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        $service = ServiceModel::get($id);
+        if (!$service) return $this->fail('数据不存在!');
+        $f = array();
+        $f[] = Form::frameImageOne('avatar', '客服头像', Url::buildUrl('admin/widget.images/index', array('fodder' => 'avatar')), $service['avatar'])->icon('ios-add')->width('60%')->height('435px');
+        $f[] = Form::input('nickname', '客服名称', $service["nickname"]);
+        $f[] = Form::radio('customer', '统计管理', $service['customer'])->options([['value' => 1, 'label' => '开启'], ['value' => 0, 'label' => '关闭']]);
+        $f[] = Form::radio('notify', '订单通知', $service['notify'])->options([['value' => 1, 'label' => '开启'], ['value' => 0, 'label' => '关闭']]);
+        $f[] = Form::radio('status', '客服状态', $service['status'])->options([['value' => 1, 'label' => '显示'], ['value' => 0, 'label' => '隐藏']]);
+        return $this->makePostForm('编辑客服', $f, Url::buildUrl('/app/wechat/kefu/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function update($id)
+    {
+        $params = Util::postMore([
+            ['avatar', ''],
+            ['nickname', ''],
+            ['status', 1],
+            ['notify', 1],
+            ['customer', 1]
+        ]);
+        if ($params["nickname"] == '') return $this->fail("客服名称不能为空!");
+        $data = array("avatar" => $params["avatar"]
+        , "nickname" => $params["nickname"]
+        , 'status' => $params['status']
+        , 'notify' => $params['notify']
+        , 'customer' => $params['customer']
+        );
+        ServiceModel::edit($data, $id);
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!ServiceModel::del($id))
+            return $this->fail(ServiceModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        ServiceModel::where(['id' => $id])->update(['status' => $status]);
+
+        return $this->success($status == 0 ? '隐藏成功' : '显示成功');
+    }
+
+    /**
+     * 聊天记录
+     *
+     * @return \think\Response
+     */
+    public function chat_user($id)
+    {
+        $page = $this->request->param('page',1);
+        $limit = $this->request->param('limit',20);
+        $now_service = ServiceModel::get($id);
+        if (!$now_service) return $this->fail('数据不存在!');
+        $list = ServiceModel::getChatUser($now_service->toArray(), 0,$page,$limit);
+        return $this->success(compact('list'));
+    }
+
+    /**
+     * @param int $uid
+     * @param int $to_uid
+     * @return string
+     */
+    public function chat_list()
+    {
+        $where = Util::getMore([
+            ['uid', 0],
+            ['to_uid', 0],
+            ['id', 0]
+        ]);
+        if ($where['uid']){
+            $arr = $where;
+            CacheService::set('admin_chat_list' . $this->adminId, $arr);
+        }
+        $where = CacheService::get('admin_chat_list' . $this->adminId);
+        $list = StoreServiceLog::getChatList($where, 0);
+        return $this->success($list);
+    }
+}

+ 44 - 0
app/adminapi/controller/v1/application/wechat/WechatMessage.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace app\adminapi\controller\v1\application\wechat;
+
+
+use app\models\wechat\WechatMessage as MessageModel;
+use app\adminapi\controller\AuthController;
+use crmeb\services\UtilService as Util;
+
+/**
+ * 用户扫码点击事件
+ * Class SystemMessage
+ * @package app\admin\controller\system
+ */
+class WechatMessage extends AuthController
+
+{
+
+    /**
+     * 显示操作记录
+     */
+    public function index(){
+        $where = Util::getMore([
+            ['page',1],
+            ['limit',20],
+            ['nickname',''],
+            ['type',''],
+            ['data',''],
+        ],$this->request);
+        $list = MessageModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 操作名称列表
+     * @return mixed
+     */
+    public function operate(){
+        $operate = MessageModel::$mold;
+        return $this->success(compact('operate'));
+    }
+
+}
+

+ 224 - 0
app/adminapi/controller/v1/application/wechat/WechatNewsCategory.php

@@ -0,0 +1,224 @@
+<?php
+
+namespace app\adminapi\controller\v1\application\wechat;
+
+use app\adminapi\controller\AuthController;
+use app\models\article\Article as ArticleModel;
+use app\models\article\ArticleContent;
+use crmeb\services\{UtilService as Util, WechatService};
+use app\models\wechat\{WechatReply, WechatNewsCategory as WechatNewsCategoryModel, WechatUser};
+
+/**
+ * 图文信息
+ * Class WechatNewsCategory
+ * @package app\admin\controller\wechat
+ *
+ */
+class WechatNewsCategory extends AuthController
+{
+    /**
+     * 图文消息列表
+     * @return mixed
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['cate_name', '']
+        ], $this->request);
+        $list = WechatNewsCategoryModel::getAll($where);
+        return $this->success($list);
+    }
+
+
+    /**
+     * 图文详情
+     * @param $id
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function read($id)
+    {
+        $info = WechatNewsCategoryModel::where('id', $id)->find();
+        $new = ArticleModel::alias('a')->where('id', 'in', $info['new_id'])
+            ->join('article_content c', 'c.nid=a.id')
+            ->field('a.*,c.content')
+            ->where('hide', 0)
+            ->select();
+        if ($new) $new = $new->toArray();
+        $info['new'] = $new;
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 删除图文
+     * @param $id
+     * @return mixed
+     */
+    public function delete($id)
+    {
+        if (!WechatNewsCategoryModel::del($id))
+            return $this->fail(WechatNewsCategoryModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 新增或编辑保存
+     * @return mixed
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function save()
+    {
+        $data = Util::postMore([
+            ['list', []],
+            ['id', 0]
+        ]);
+        try {
+            $id = [];
+            $countList = count($data['list']);
+            if (!$countList) return $this->fail('请添加图文');
+            ArticleModel::beginTrans();
+            foreach ($data['list'] as $k => $v) {
+                if ($v['title'] == '') return $this->fail('标题不能为空');
+                if ($v['author'] == '') return $this->fail('作者不能为空');
+                if ($v['content'] == '') return $this->fail('正文不能为空');
+                if ($v['synopsis'] == '') return $this->fail('摘要不能为空');
+                $v['status'] = 1;
+                $v['add_time'] = time();
+                if ($v['id']) {
+                    $idC = $v['id'];
+                    unset($v['id']);
+                    ArticleModel::edit($v, $idC);
+                    ArticleContent::where('nid', $idC)->update(['content' => $v['content']]);
+                    $data['list'][$k]['id'] = $idC;
+                    $id[] = $idC;
+                } else {
+                    unset($v['id']);
+                    $res = ArticleModel::create($v)->toArray();
+                    $id[] = $res['id'];
+                    $data['list'][$k]['id'] = $res['id'];
+                    ArticleContent::create(['content' => $v['content'], 'nid' => $res['id']]);
+                }
+            }
+            $countId = count($id);
+            if ($countId != $countList) {
+                ArticleModel::checkTrans(false);
+                if ($data['id']) return $this->fail('修改失败');
+                else return $this->fail('添加失败');
+            } else {
+                ArticleModel::checkTrans(true);
+                $newsCategory['cate_name'] = $data['list'][0]['title'];
+                $newsCategory['new_id'] = implode(',', $id);
+                $newsCategory['sort'] = 0;
+                $newsCategory['add_time'] = time();
+                $newsCategory['status'] = 1;
+                if ($data['id']) {
+                    WechatNewsCategoryModel::edit($newsCategory, $data['id']);
+                    return $this->success('修改成功');
+                } else {
+                    WechatNewsCategoryModel::create($newsCategory);
+                    return $this->success('添加成功');
+                }
+            }
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+    }
+
+    /**
+     * 发送消息
+     * @param int $id
+     * @param string $wechat
+     * $wechat  不为空  发消息  /  空 群发消息
+     */
+    public function push()
+    {
+        $data = Util::postMore([
+            ['id', 0],
+            ['user_ids', '']
+        ]);
+        if (!$data['id']) return $this->fail('参数错误');
+        $list = WechatNewsCategoryModel::getWechatNewsItem($data['id']);
+        $wechatNews = [];
+        if ($list) {
+            if ($list['new'] && is_array($list['new'])) {
+                foreach ($list['new'] as $kk => $vv) {
+                    $wechatNews[$kk]['title'] = $vv['title'];
+                    $wechatNews[$kk]['image'] = $vv['image_input'];
+                    $wechatNews[$kk]['date'] = date('m月d日', time());
+                    $wechatNews[$kk]['description'] = $vv['synopsis'];
+                    $wechatNews[$kk]['id'] = $vv['id'];
+                }
+            }
+        }
+        if ($data['user_ids'] != '') {//客服消息
+            $wechatNews = WechatReply::tidyNews($wechatNews);
+            $message = WechatService::newsMessage($wechatNews);
+            $errorLog = [];//发送失败的用户
+            $user = WechatUser::where('uid', 'IN', $data['user_ids'])->column('nickname,subscribe,openid', 'uid');
+            if ($user) {
+                foreach ($user as $v) {
+                    if ($v['subscribe'] && $v['openid']) {
+                        try {
+                            WechatService::staffService()->message($message)->to($v['openid'])->send();
+                        } catch (\Exception $e) {
+                            $errorLog[] = $v['nickname'] . '发送失败';
+                        }
+                    } else {
+                        $errorLog[] = $v['nickname'] . '没有关注发送失败(不是微信公众号用户)';
+                    }
+                }
+            } else return $this->fail('发送失败,参数不正确');
+            if (!count($errorLog)) return $this->success('全部发送成功');
+            else return $this->success(implode(',', $errorLog) . ',剩余的发送成功');
+        } else {//群发消息
+//        if($list){
+//               if($list['new'] && is_array($list['new'])){
+//                   foreach ($list['new'] as $kk=>$vv){
+//                       $wechatNews[$kk]['title'] = $vv['title'];
+//                       $wechatNews[$kk]['thumb_media_id'] = $vv['image_input'];
+//                       $wechatNews[$kk]['author'] = $vv['author'];
+//                       $wechatNews[$kk]['digest'] = $vv['synopsis'];
+//                       $wechatNews[$kk]['show_cover_pic'] = 1;
+//                       $wechatNews[$kk]['content'] = Db::name('articleContent')->where('nid',$vv["id"])->value('content');
+//                       $wechatNews[$kk]['content_source_url'] = $vv['url'];
+//                   }
+//            }
+//        }
+            //6sFx6PzPF2v_Lv4FGOMzz-oQunU2Z3wrOWb-7zS508E
+            //6sFx6PzPF2v_Lv4FGOMzz7SUUuamgWwlqdVfhQ5ALT4
+//        foreach ($wechatNews as $k=>$v){
+//            $material = WechatService::materialService()->uploadImage(UtilService::urlToPath($v['thumb_media_id']));
+//            dump($material);
+//            $wechatNews[$k]['thumb_media_id'] = $material->media_id;
+//        }
+//        $mediaIdNews = WechatService::uploadNews($wechatNews);
+//        $res = WechatService::sendNewsMessage($mediaIdNews->media_id);
+//        if($res->errcode) return Json::fail($res->errmsg);
+//        else return Json::successful('推送成功');
+//        dump($mediaIdNews);
+//        dump($res);
+        }
+    }
+
+    /**
+     * 发送消息图文列表
+     * @return mixed
+     */
+    public function send_news()
+    {
+//        if($id == '') return $this->fail('参数错误');
+        $where = Util::getMore([
+            ['cate_name', ''],
+            ['page', 1],
+            ['limit', 10]
+        ], $this->request);
+        return $this->success(WechatNewsCategoryModel::list($where));
+    }
+
+}

+ 176 - 0
app/adminapi/controller/v1/application/wechat/WechatTemplate.php

@@ -0,0 +1,176 @@
+<?php
+
+namespace app\adminapi\controller\v1\application\wechat;
+
+use app\models\wechat\WechatTemplate as WechatTemplateModel;
+use app\adminapi\controller\AuthController;
+use crmeb\services\{
+    FormBuilder as Form, UtilService as Util, WechatTemplateService
+};
+use think\facade\Route as Url;
+use think\Request;
+use think\facade\Cache;
+
+class WechatTemplate extends AuthController
+{
+    protected $cacheTag = '_system_wechat';
+
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['name', ''],
+            ['status', '']
+        ], $this->request);
+        $data = WechatTemplateModel::SystemPage($where);
+        $industry = Cache::tag($this->cacheTag)->remember('_wechat_industry', function () {
+            try {
+                $cache = WechatTemplateService::getIndustry();
+                if (!$cache) return [];
+                Cache::tag($this->cacheTag, ['_wechat_industry']);
+                return $cache->toArray();
+            } catch (\Exception $e) {
+                return $e->getMessage();
+            }
+        }, 0) ?: [];
+        !is_array($industry) && $industry = [];
+        $industry['primary_industry'] = isset($industry['primary_industry']) ? $industry['primary_industry']['first_class'] . ' | ' . $industry['primary_industry']['second_class'] : '未选择';
+        $industry['secondary_industry'] = isset($industry['secondary_industry']) ? $industry['secondary_industry']['first_class'] . ' | ' . $industry['secondary_industry']['second_class'] : '未选择';
+        $lst = array(
+            'industry' => $industry,
+            'count' => $data['count'],
+            'list' => $data['list']
+        );
+        return $this->success($lst);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $f = array();
+        $f[] = Form::input('tempkey', '模板编号');
+        $f[] = Form::input('tempid', '模板ID');
+        $f[] = Form::input('name', '模板名');
+        $f[] = Form::input('content', '回复内容')->type('textarea');
+        $f[] = Form::radio('status', '状态', 1)->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
+        return $this->makePostForm('添加模板消息', $f, Url::buildUrl('/app/wechat/template'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $data = Util::postMore([
+            'tempkey',
+            'tempid',
+            'name',
+            'content',
+            ['status', 0],
+            ['type', 1]
+        ]);
+        if ($data['tempkey'] == '') return $this->fail('请输入模板编号');
+        if ($data['tempkey'] != '' && WechatTemplateModel::be($data['tempkey'], 'tempkey'))
+            return $this->fail('请输入模板编号已存在,请重新输入');
+        if ($data['tempid'] == '') return $this->fail('请输入模板ID');
+        if ($data['name'] == '') return $this->fail('请输入模板名');
+        if ($data['content'] == '') return $this->fail('请输入回复内容');
+        $data['add_time'] = time();
+        WechatTemplateModel::create($data);
+        return $this->success('添加模板消息成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        //
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $product = WechatTemplateModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        $f = array();
+        $f[] = Form::input('tempkey', '模板编号', $product->getData('tempkey'))->disabled(1);
+        $f[] = Form::input('name', '模板名', $product->getData('name'))->disabled(1);
+        $f[] = Form::input('tempid', '模板ID', $product->getData('tempid'));
+        $f[] = Form::radio('status', '状态', $product->getData('status'))->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
+        return $this->makePostForm('编辑模板消息', $f, Url::buildUrl('/app/wechat/template/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param \think\Request $request
+     * @param int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $data = Util::postMore([
+            'tempid',
+            ['status', 0],
+            ['type', 1]
+        ]);
+        if ($data['tempid'] == '') return $this->fail('请输入模板ID');
+        if (!$id) return $this->fail('数据不存在');
+        $product = WechatTemplateModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        WechatTemplateModel::edit($data, $id);
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在!');
+        if (!WechatTemplateModel::del($id))
+            return $this->fail(WechatTemplateModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        WechatTemplateModel::where(['id' => $id])->update(['status' => $status]);
+
+        return $this->success($status == 0 ? '关闭成功' : '开启成功');
+    }
+}

+ 382 - 0
app/adminapi/controller/v1/application/wechat/WechatUser.php

@@ -0,0 +1,382 @@
+<?php
+
+namespace app\adminapi\controller\v1\application\wechat;
+
+use app\adminapi\controller\AuthController;
+use app\models\store\StoreOrder;
+use app\models\user\{User, UserBill};
+use app\models\wechat\WechatUser as UserModel;
+use crmeb\services\{UtilService as Util, FormBuilder as Form, WechatService};
+use think\Collection;
+use think\facade\Route as Url;
+
+/**
+ * 微信用户管理
+ * Class WechatUser
+ * @package app\admin\controller\wechat
+ */
+class WechatUser extends AuthController
+
+{
+    /**
+     * 显示操作记录
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+            ['data', ''],
+            ['tagid_list', ''],
+            ['groupid', '-1'],
+            ['sex', ''],
+            ['export', ''],
+            ['subscribe', '']
+        ], $this->request);
+        $tagidList = explode(',', $where['tagid_list']);
+        foreach ($tagidList as $k => $v) {
+            if (!$v) {
+                unset($tagidList[$k]);
+            }
+        }
+        $tagidList = array_unique($tagidList);
+        $where['tagid_list'] = implode(',', $tagidList);
+        $list = UserModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 获取标签和分组
+     * @return mixed
+     */
+    public function get_tag_group(){
+        try {
+            $groupList = UserModel::getUserGroup();
+            $tagList = UserModel::getUserTag();
+        } catch (\Exception $e) {
+            $groupList = [];
+            $tagList = [];
+        }
+        return $this->success(compact('groupList','tagList'));
+    }
+
+    /**
+     * 修改用户标签表单
+     * @param $openid
+     * @return mixed|string
+     */
+    public function edit_user_tag($openid)
+    {
+        if (!$openid) return $this->fail('参数错误!');
+        $list = Collection::make(UserModel::getUserTag())->each(function ($item) {
+            return ['value' => $item['id'], 'label' => $item['name']];
+        });
+        $tagList = UserModel::where('openid', $openid)->value('tagid_list');
+
+        $tagList = explode(',', $tagList) ?: [];
+        $f = [Form::select('tag_id', '用户标签', $tagList)->setOptions($list->toArray())->multiple(1)];
+        return $this->makePostForm('编辑用户标签', $f, Url::buildUrl('/app/wechat/user_tag/' . $openid), 'PUT');
+    }
+
+    /**
+     * 修改用户标签
+     * @param $openid
+     * @return mixed
+     */
+    public function update_user_tag($openid)
+    {
+        if (!$openid) return $this->fail('参数错误!');
+        $tagId = request()->post('tag_id/a', []);
+        if (!$tagId) return $this->fail('请选择用户标签!');
+        $tagList = explode(',', UserModel::where('openid', $openid)->value('tagid_list')) ?: [];
+        UserModel::edit(['tagid_list' => $tagId], $openid, 'openid');
+        if (!$tagId[0]) unset($tagId[0]);
+        UserModel::edit(['tagid_list' => $tagId], $openid, 'openid');
+        try {
+            foreach ($tagList as $tag) {
+                if ($tag) WechatService::userTagService()->batchUntagUsers([$openid], $tag);
+            }
+            foreach ($tagId as $tag) {
+                WechatService::userTagService()->batchTagUsers([$openid], $tag);
+            }
+        } catch (\Exception $e) {
+            UserModel::rollbackTrans();
+            return $this->fail($e->getMessage());
+        }
+        UserModel::commitTrans();
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 修改用户分组表单
+     * @param $openid
+     * @return mixed|string
+     */
+    public function edit_user_group($openid)
+    {
+        if (!$openid) return $this->fail('参数错误!');
+        $list = Collection::make(UserModel::getUserGroup())->each(function ($item) {
+            return ['value' => $item['id'], 'label' => $item['name']];
+        });
+        $groupId = UserModel::where('openid', $openid)->value('groupid');
+        $f = [Form::select('group_id', '用户分组', (string)$groupId)->setOptions($list->toArray())];
+        return $this->makePostForm('编辑用户标签', $f, Url::buildUrl('/app/wechat/user_group/' . $openid), 'PUT');
+    }
+
+    /**
+     * 修改用户分组
+     * @param $openid
+     * @return mixed
+     */
+    public function update_user_group($openid)
+    {
+        if (!$openid) return $this->fail('参数错误!');
+        $groupId = request()->post('group_id');
+//        if(!$groupId) return $this->fail('请选择用户分组!');
+        UserModel::beginTrans();
+        UserModel::edit(['groupid' => $groupId], $openid, 'openid');
+        try {
+            WechatService::userGroupService()->moveUser($openid, $groupId);
+        } catch (\Exception $e) {
+            UserModel::rollbackTrans();
+            return $this->fail($e->getMessage());
+        }
+        UserModel::commitTrans();
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 用户标签列表
+     */
+    public function tag($refresh = 0)
+    {
+        $list = [];
+        if ($refresh == 1) {
+            UserModel::clearUserTag();
+            $this->redirect(Url::buildUrl('tag'));
+        }
+        try {
+            $list = UserModel::getUserTag();
+        } catch (\Exception $e) {
+        }
+        return $this->success(compact('list'));
+    }
+
+    /**
+     * 添加标签表单
+     * @return mixed
+     */
+    public function create_tag()
+    {
+        $f = [Form::input('name', '标签名称')];
+        return $this->makePostForm('添加标签', $f, Url::buildUrl('/app/wechat/tag'), 'POST');
+    }
+
+    /**
+     * 添加
+     */
+    public function save_tag()
+    {
+        $tagName = request()->post('name');
+        if (!$tagName) return $this->fail('请输入标签名称!');
+        try {
+            WechatService::userTagService()->create($tagName);
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+        UserModel::clearUserTag();
+        return $this->success('添加标签成功!');
+    }
+
+    /**
+     * 修改标签表单
+     * @param $id
+     * @return mixed
+     */
+    public function edit_tag($id)
+    {
+        $f = [Form::input('name', '标签名称')];
+        return $this->makePostForm('编辑标签', $f, Url::buildUrl('/app/wechat/tag/' . $id), 'PUT');
+    }
+
+    /**
+     * 修改标签
+     * @param $id
+     */
+    public function update_tag($id)
+    {
+        $tagName = request()->post('name');
+        if (!$tagName) return $this->fail('请输入标签名称!');
+        try {
+            WechatService::userTagService()->update($id, $tagName);
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+        UserModel::clearUserTag();
+        return $this->success('修改标签成功!');
+    }
+
+    /**
+     * 删除标签
+     * @param $id
+     * @return \think\response\Json
+     */
+    public function delete_tag($id)
+    {
+        try {
+            WechatService::userTagService()->delete($id);
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+        UserModel::clearUserTag();
+        return $this->success('删除标签成功!');
+    }
+
+    /**
+     * 用户分组列表
+     */
+
+    public function group($refresh = 0)
+    {
+        $list = [];
+        try {
+            if ($refresh == 1) {
+                UserModel::clearUserGroup();
+                $this->redirect(Url::buildUrl('group'));
+            }
+            $list = UserModel::getUserGroup();
+        } catch (\Exception $e) {
+        }
+        return $this->success(compact('list'));
+    }
+
+    /**
+     * 添加分组表单
+     * @return mixed
+     */
+    public function create_group()
+    {
+        $f = [Form::input('name', '分组名称')];
+        return $this->makePostForm('添加分组', $f, Url::buildUrl('/app/wechat/group'), 'POST');
+    }
+
+    /**
+     * 添加
+     */
+    public function save_group()
+    {
+        $tagName = request()->post('name');
+        if (!$tagName) return $this->fail('请输入分组名称!');
+        try {
+            WechatService::userGroupService()->create($tagName);
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+        UserModel::clearUserGroup();
+        return $this->success('添加分组成功!');
+    }
+
+    /**
+     * 修改分组表单
+     * @param $id
+     * @return mixed
+     */
+    public function edit_group($id)
+    {
+        $f = [Form::input('name', '分组名称')];
+        return $this->makePostForm('编辑分组', $f, Url::buildUrl('/app/wechat/group/' . $id), 'PUT');
+    }
+
+    /**
+     * 修改分组
+     * @param $id
+     */
+    public function update_group($id)
+    {
+        $tagName = request()->post('name');
+        if (!$tagName) return $this->fail('请输入分组名称!');
+        try {
+            WechatService::userGroupService()->update($id, $tagName);
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+        UserModel::clearUserGroup();
+        return $this->success('修改分组成功!');
+    }
+
+    /**
+     * 删除分组
+     * @param $id
+     * @return \think\response\Json
+     */
+    public function delete_group($id)
+    {
+        try {
+            WechatService::userTagService()->delete($id);
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+        UserModel::clearUserGroup();
+        return $this->success('删除分组成功!');
+    }
+
+    /**
+     * 同步标签
+     * @param $openid
+     * @return mixed
+     */
+    public function syn_tag($openid)
+    {
+        if (!$openid) return $this->fail('参数错误!');
+        $data = array();
+        if (UserModel::be($openid, 'openid')) {
+            try {
+                $tag = WechatService::userTagService()->userTags($openid)->toArray();
+            } catch (\Exception $e) {
+                return $this->fail($e->getMessage());
+            }
+            if ($tag['tagid_list']) $data['tagid_list'] = implode(',', $tag['tagid_list']);
+            else $data['tagid_list'] = '';
+            $res = UserModel::edit($data, $openid, 'openid');
+            if ($res) return $this->success('同步成功');
+            else return $this->fail('同步失败!');
+        } else  return $this->fail('参数错误!');
+    }
+
+    /**
+     * 一级推荐人页面
+     * @return mixed
+     */
+    public function stair($uid = '')
+    {
+        if ($uid == '') return $this->fail('参数错误');
+        $list = User::alias('u')
+            ->where('u.spread_uid', $uid)
+            ->field('u.avatar,u.nickname,u.now_money,u.add_time,u.uid')
+            ->where('u.status', 1)
+            ->order('u.add_time DESC')
+            ->select()
+            ->toArray();
+        foreach ($list as $key => $value) $list[$key]['orderCount'] = StoreOrder::getOrderCount($value['uid']);
+        return $this->success(compact('list'));
+    }
+
+    /**
+     * 个人资金详情页面
+     * @return mixed
+     */
+    public function now_money($uid = '')
+    {
+        if ($uid == '') return $this->fail('参数错误');
+        $list = UserBill::where('uid', $uid)->where('category', 'now_money')
+            ->field('mark,pm,number,add_time')
+            ->where('status', 1)->order('add_time DESC')->select()->toArray();
+        foreach ($list as &$v) {
+            $v['add_time'] = date('Y-m-d H:i:s', $v['add_time']);
+        }
+        return $this->success(compact('list'));
+    }
+
+}
+

+ 1 - 0
app/adminapi/controller/v1/cms/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/cms

+ 225 - 0
app/adminapi/controller/v1/cms/Article.php

@@ -0,0 +1,225 @@
+<?php
+
+namespace app\adminapi\controller\v1\cms;
+
+use app\adminapi\controller\AuthController;
+use crmeb\services\{UtilService as Util};
+use app\models\article\{ArticleCategory as ArticleCategoryModel, Article as ArticleModel};
+use app\models\system\SystemAttachment;
+use think\Request;
+
+/**
+ * 文章管理
+ * Class Article
+ * @package app\adminapi\controller\v1\cms
+ */
+class Article extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['title', ''],
+            ['pid', 0],
+            ['page', 1],
+            ['limit', 10]
+        ], $this->request);
+        $where['cid'] = '';
+        $pid = $where['pid'];
+        $where['merchant'] = 0;//区分是管理员添加的图文显示  0 还是 商户添加的图文显示  1
+        $where['mer_id'] = $this->merId ?: '';
+        $cateList = ArticleCategoryModel::getArticleCategoryList($where['mer_id']);
+        //获取分类列表
+        if (count($cateList)) {
+            $tree = sort_list_tier($cateList);
+            if ($pid) {
+                $pids = Util::getChildrenPid($tree, $pid);
+                $where['cid'] = ltrim($pid . $pids);
+            }
+        }
+        $list = ArticleModel::getAll($where);
+        return $this->success($list);
+    }
+
+    /**
+     *
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $data = Util::postMore([
+            ['id', 0],
+            ['cid',''],
+            'title',
+            'author',
+            'image_input',
+            'content',
+            'synopsis',
+            'share_title',
+            'share_synopsis',
+            ['visit', 0],
+            ['sort', 0],
+            'url',
+            ['is_banner', 0],
+            ['is_hot', 0],
+            ['status', 1],]);
+        if (!$data['title']) return $this->fail('缺少参数');
+//        $data['cid'] = implode(',', $data['cid']);
+        $content = $data['content'];
+        unset($data['content']);
+        $data['mer_id'] = $this->merId ?: '';
+        if ($data['id']) {
+            $id = $data['id'];
+            unset($data['id']);
+            $res = false;
+            ArticleModel::beginTrans();
+            $res1 = ArticleModel::edit($data, $id, 'id');
+            $res2 = ArticleModel::setContent($id, $content);
+            if ($res1 && $res2) {
+                $res = true;
+            }
+            ArticleModel::checkTrans($res);
+            if ($res)
+                return $this->success('修改成功!', ['id' => $id]);
+            else
+                return $this->fail('修改失败,您并没有修改什么!', ['id' => $id]);
+        } else {
+            $data['add_time'] = time();
+            $data['admin_id'] = $this->adminId;
+//            $data['admin_id'] = 1;
+            $res = false;
+            ArticleModel::beginTrans();
+            $res1 = ArticleModel::create($data);
+            $res2 = false;
+            if ($res1)
+                $res2 = ArticleModel::setContent($res1->id, $content);
+            if ($res1 && $res2) {
+                $res = true;
+            }
+            ArticleModel::checkTrans($res);
+            if ($res)
+                return $this->success('添加成功!', ['id' => $res1->id]);
+            else
+                return $this->success('添加失败!', ['id' => $res1->id]);
+        }
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        if ($id) {
+            $info = ArticleModel::where('n.id', $id)->alias('n')->field('n.*,c.content')->join('ArticleContent c', 'c.nid=n.id', 'left')->find();
+            if (!$info) return $this->fail('数据不存在!');
+            $info['cid'] = intval($info['cid']);
+        }
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param \think\Request $request
+     * @param int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        $res = ArticleModel::del($id);
+        if (!$res)
+            return $this->fail('删除失败,请稍候再试!');
+        else
+            return $this->success('删除成功!');
+    }
+
+    //TODO 暂留
+
+    /**
+     * 分类显示列表
+     * $param int $id 分类id
+     * @return mixed
+     */
+    public function merchantIndex($id = 0)
+    {
+        $where = Util::getMore([
+            ['title', '']
+        ], $this->request);
+        if ($id) $where['cid'] = $id;
+        $where['merchant'] = 1;//区分是管理员添加的图文显示  0 还是 商户添加的图文显示  1
+        $where['mer_id'] = $this->merId ?: '';
+        $list = ArticleModel::getAll($where);
+        return $this->success(compact('list'));
+    }
+
+    /**
+     * 关联商品
+     * @param int $id 文章id
+     */
+    public function relation($id = 0)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        list($product_id) = Util::postMore([
+            ['product_id', 0]
+        ], $this->request, true);
+        if (ArticleModel::edit(['product_id' => $product_id], ['id' => $id]))
+            return $this->success('保存成功');
+        else
+            return $this->fail('保存失败');
+    }
+
+    /**
+     * 取消绑定的商品id
+     * @param int $id
+     */
+    public function unrelation($id = 0)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        if (ArticleModel::edit(['product_id' => 0], $id))
+            return $this->success('取消关联成功!');
+        else
+            return $this->fail('取消失败');
+    }
+}

+ 194 - 0
app/adminapi/controller/v1/cms/ArticleCategory.php

@@ -0,0 +1,194 @@
+<?php
+
+namespace app\adminapi\controller\v1\cms;
+
+use app\adminapi\controller\AuthController;
+use think\Request;
+use think\facade\Route as Url;
+use app\models\system\SystemAttachment;
+use crmeb\services\{
+    FormBuilder as Form, UtilService as Util
+};
+use app\models\article\{
+    ArticleCategory as ArticleCategoryModel, Article as ArticleModel
+};
+
+/**
+ * 文章分类管理
+ * Class ArticleCategory
+ * @package app\adminapi\controller\v1\cms
+ */
+class ArticleCategory extends AuthController
+{
+    /**
+     * 显示资源列表
+     * @param $type 0-列表形式 1-树形形式
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 15],
+            ['status', ''],
+            ['title', ''],
+            ['type', 0]
+        ], $this->request);
+        $type = $where['type'];
+        unset($where['type']);
+        $where['mer_id'] = $this->merId ?: '';
+        if ($type) {
+            $data1 = ArticleCategoryModel::getArticleCategoryList($where['mer_id']);
+            $list = ArticleCategoryModel::tidyTree($data1);
+        } else {
+            //查出顶级分类列表 分页根据顶级分类
+            $list = ArticleCategoryModel::systemPage($where);
+        }
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $f = array();
+//        $f[] = Form::select('pid', '父级id')->setOptions(function () {
+//            $list = ArticleCategoryModel::getTierList();
+//            $menus[] = ['value' => 0, 'label' => '顶级分类'];
+//            foreach ($list as $menu) {
+//                $menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['title']];
+//            }
+//            return $menus;
+//        })->filterable(1);
+        $f[] = Form::input('title', '分类名称');
+        $f[] = Form::input('intr', '分类简介')->type('textarea');
+        $f[] = Form::frameImageOne('image', '分类图片', Url::buildUrl('admin/widget.images/index', array('fodder' => 'image')))->icon('ios-add')->width('60%')->height('435px');
+        $f[] = Form::number('sort', '排序', 0);
+        $f[] = Form::radio('status', '状态', 1)->options([['value' => 1, 'label' => '显示'], ['value' => 0, 'label' => '隐藏']]);
+        return $this->makePostForm('添加分类', $f, Url::buildUrl('/cms/category'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $data = Util::postMore([
+            'title',
+            ['pid', 0],
+            'intr',
+            ['new_id', []],
+            ['image', []],
+            ['sort', 0],
+            'status',]);
+        if (!$data['title']) return $this->fail('请输入分类名称');
+        if (count($data['image']) != 1) return $this->fail('请选择分类图片,并且只能上传一张');
+        if ($data['sort'] < 0) return $this->fail('排序不能是负数');
+        $data['add_time'] = time();
+        $data['image'] = $data['image'][0];
+        $new_id = $data['new_id'];
+        unset($data['new_id']);
+        $data['mer_id'] = $this->merId ?: '';
+        $res = ArticleCategoryModel::create($data);
+        if (!ArticleModel::saveBatchCid($res['id'], implode(',', $new_id))) return $this->fail('文章分类添加失败');
+        return $this->success('添加分类成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        //
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        if (!$id) return $this->fail('参数错误');
+        $article = ArticleCategoryModel::get($id)->getData();
+        if (!$article) return $this->fail('数据不存在!');
+        $f = array();
+        $f[] = Form::select('pid', '父级id', (string)$article['pid'])->setOptions(function () {
+            $list = ArticleCategoryModel::getTierList();
+            $menus[] = ['value' => 0, 'label' => '顶级分类'];
+            foreach ($list as $menu) {
+                $menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['title']];
+            }
+            return $menus;
+        })->filterable(1);
+        $f[] = Form::input('title', '分类名称', $article['title']);
+        $f[] = Form::input('intr', '分类简介', $article['intr'])->type('textarea');
+        $f[] = Form::frameImageOne('image', '分类图片', Url::buildUrl('admin/widget.images/index', array('fodder' => 'image')), $article['image'])->icon('ios-add')->width('60%')->height('435px');
+        $f[] = Form::number('sort', '排序', 0);
+        $f[] = Form::radio('status', '状态', $article['status'])->options([['value' => 1, 'label' => '显示'], ['value' => 0, 'label' => '隐藏']]);
+        return $this->makePostForm('编辑分类', $f, Url::buildUrl('/cms/category/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param \think\Request $request
+     * @param int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $data = Util::postMore([
+            'pid',
+            'title',
+            'intr',
+            ['image', []],
+            ['sort', 0],
+            'status',]);
+        if (!$data['title']) return $this->fail('请输入分类名称');
+        if (count($data['image']) != 1) return $this->fail('请选择分类图片,并且只能上传一张');
+        if ($data['sort'] < 0) return $this->fail('排序不能是负数');
+        $data['image'] = $data['image'][0];
+        if (!ArticleCategoryModel::get($id)) return $this->fail('编辑的记录不存在!');
+        ArticleCategoryModel::edit($data, $id);
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        $res = ArticleCategoryModel::delArticleCategory($id);
+        if (!$res)
+            return $this->fail(ArticleCategoryModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        ArticleCategoryModel::where(['id' => $id])->update(['status' => $status]);
+        return $this->success($status == 0 ? '隐藏成功' : '显示成功');
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/export/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/export

+ 254 - 0
app/adminapi/controller/v1/export/ExportExcel.php

@@ -0,0 +1,254 @@
+<?php
+
+namespace app\adminapi\controller\v1\export;
+
+use app\adminapi\controller\AuthController;
+use app\adminapi\controller\v1\order\StoreOrder;
+use crmeb\services\UtilService as Util;
+use app\models\finance\FinanceModel;
+use app\models\user\{
+    User, UserBill, UserPoint, UserRecharge as UserRechargeModel
+};
+use app\models\wechat\WechatUser;
+use app\models\store\{
+    StoreBargain, StoreCombination, StoreAssistance, StoreProduct, StoreSeckill, StoreOrder as StoreOrderModel
+};
+use app\models\system\SystemStore;
+
+use crmeb\repositories\ExportRepositories;
+use think\Request;
+
+/**
+ * 导出excel类
+ * Class ExportExcel
+ * @package app\adminapi\controller\v1\export
+ */
+class ExportExcel extends AuthController
+{
+
+    /**
+     *保存用户资金监控的excel表格
+     */
+    public function userFinance()
+    {
+        $where = Util::getMore([
+            ['start_time', ''],
+            ['end_time', ''],
+            ['nickname', ''],
+            ['type', ''],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = FinanceModel::exportData($where);
+        return $this->success(ExportRepositories::userFinance($data));
+    }
+
+    /**
+     * 用户佣金
+     */
+    public function userCommission()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+            ['price_max', ''],
+            ['price_min', ''],
+            ['excel', '1'],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = User::exportData($where);
+        return $this->success(ExportRepositories::userCommission($data));
+    }
+
+    /**
+     * 用户积分
+     */
+    public function userPoint()
+    {
+        $where = Util::getMore([
+            ['start_time', ''],
+            ['end_time', ''],
+            ['nickname', ''],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = UserPoint::exportData($where);
+        return $this->success(ExportRepositories::userPoint($data));
+    }
+
+    /**
+     * 用户充值
+     */
+    public function userRecharge()
+    {
+        $where = Util::getMore([
+            ['data', ''],
+            ['paid', ''],
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+            ['excel', '1'],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = UserRechargeModel::exportData($where);
+        return $this->success(ExportRepositories::userRecharge($data));
+    }
+
+    /**
+     * 分销管理 用户推广
+     */
+    public function userAgent()
+    {
+        $where = Util::getMore([
+            ['nickname', ''],
+            ['data', ''],
+            ['excel', '1'],
+            ['page', 1],
+            ['limit', 20],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = WechatUser::exportAgentData($where);
+        return $this->success(ExportRepositories::userAgent($data));
+    }
+
+    /**
+     * 微信用户导出
+     */
+    public function wechatUser()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+            ['data', ''],
+            ['tagid_list', ''],
+            ['groupid', '-1'],
+            ['sex', ''],
+            ['export', '1'],
+            ['subscribe', '']
+        ], $this->request);
+        $tagidList = explode(',', $where['tagid_list']);
+        foreach ($tagidList as $k => $v) {
+            if (!$v) {
+                unset($tagidList[$k]);
+            }
+        }
+        $tagidList = array_unique($tagidList);
+        $where['tagid_list'] = implode(',', $tagidList);
+        $data = WechatUser::exportData($where);
+        return $this->success(ExportRepositories::wechatUser($data));
+    }
+
+    /**
+     * 商铺砍价活动导出
+     */
+    public function storeBargain()
+    {
+        $where = Util::getMore([
+            [['page', 'd'], 1],
+            [['limit', 'd'], 20],
+            ['status', ''],
+            ['store_name', ''],
+            ['export', 1],
+            ['data', ''],
+        ], $this->request);
+        $where['mer_id'] = $this->merId;
+        $data = StoreBargain::exportData($where);
+        return $this->success(ExportRepositories::storeBargain($data));
+    }
+
+    /**
+     * 商铺拼团导出
+     */
+    public function storeCombination()
+    {
+        $where = Util::getMore([
+            ['is_show', ''],
+            ['store_name', ''],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = StoreCombination::exportData($where);
+        return $this->success(ExportRepositories::storeCombination($data));
+    }
+
+    /**
+     * 商铺助力导出
+     */
+    public function storeAssistance()
+    {
+        $where = Util::getMore([
+            ['is_show', ''],
+            ['store_name', ''],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = StoreAssistance::exportData($where);
+        return $this->success(ExportRepositories::storeAssistance($data));
+    }
+
+    /**
+     * 商铺秒杀导出
+     */
+    public function storeSeckill()
+    {
+        $where = Util::getMore([
+            ['status', ''],
+            ['store_name', '']
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = StoreSeckill::exportData($where);
+        return $this->success(ExportRepositories::storeSeckill($data));
+    }
+
+    /**
+     * 商铺产品导出
+     */
+    public function storeProduct()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['store_name', ''],
+            ['cate_id', ''],
+            ['excel', 1],
+            ['type', 1]
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = StoreProduct::exportData($where);
+        return $this->success(ExportRepositories::storeProduct($data));
+    }
+
+    /**
+     * 订单列表导出
+     * @return mixed
+     */
+    public function storeOrder()
+    {
+        $where = Util::getMore([
+            ['status', ''],
+            ['real_name', ''],
+            ['is_del', 0],
+            ['data', ''],
+            ['type', ''],
+            ['pay_type', ''],
+            ['order', ''],
+            ['page', 1],
+            ['limit', 10],
+        ], $this->request);
+        $where['mer_id'] = $this->merId;
+        $data = StoreOrderModel::exportData($where, $this->merId);
+        return $this->success(ExportRepositories::storeOrder($data));
+    }
+    /**
+     * 获取提货点
+     * @return mixed
+     */
+    public function storeMerchant()
+    {
+        $where = Util::getMore([
+            [['keywords', 's'], ''],
+            [['type', 'd'], 0],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $data = SystemStore::exportData($where);
+        return $this->success(ExportRepositories::storeMerchant($data));
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/file/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/file

+ 149 - 0
app/adminapi/controller/v1/file/SystemAttachment.php

@@ -0,0 +1,149 @@
+<?php
+
+namespace app\adminapi\controller\v1\file;
+
+use app\adminapi\controller\AuthController;
+use think\facade\Route as Url;
+use crmeb\services\{
+    UtilService as Util, FormBuilder as Form
+};
+use app\models\system\{
+    SystemAttachment as SystemAttachmentModel, SystemAttachmentCategory as Category
+};
+use think\Request;
+use crmeb\services\UploadService;
+
+/**
+ * 图片管理类
+ * Class SystemAttachment
+ * @package app\adminapi\controller\v1\file
+ */
+class SystemAttachment extends AuthController
+{
+
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 18],
+            ['pid', 0]
+        ]);
+        $where['mer_id'] = $this->merId;
+        return $this->success(SystemAttachmentModel::getImageList($where));
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param string $ids
+     * @return \think\Response
+     */
+    public function delete()
+    {
+        $request = app('request');
+        $ids = $request->post('ids');
+        $ids = explode(',', $ids);
+        if (empty($ids))
+            return $this->fail('请选择要删除的图片');
+        foreach ($ids as $v) {
+            self::deleteImg($v);
+        }
+        return $this->success('删除成功');
+    }
+
+    /**
+     * 图片管理上传图片
+     * @return \think\response\Json
+     */
+    public function upload($upload_type = 0)
+    {
+        [$pid, $file] = Util::postMore([
+            ['pid', 0],
+            ['file', 'file']
+        ], $this->request, true);
+        if ($upload_type == 0) {
+            $upload_type = sys_config('upload_type', 1, $this->merId);
+        }
+        try {
+            $path = make_path('attach', 2, true);
+            $upload = UploadService::init($upload_type, $this->merId);
+            $res = $upload->to($path)->validate()->move($file);
+            if ($res === false) {
+                return $this->fail($upload->getError());
+            } else {
+                $fileInfo = $upload->getUploadInfo();
+                if ($fileInfo) {
+                    SystemAttachmentModel::attachmentAdd($fileInfo['name'], $fileInfo['size'], $fileInfo['type'], $fileInfo['dir'], $fileInfo['thumb_path'], $pid, $upload_type, $fileInfo['time'], 1, $this->merId);
+                }
+                return $this->success('上传成功', ['src' => $res->filePath]);
+            }
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+    }
+
+    /**删除图片和数据记录
+     * @param $att_id
+     */
+    public function deleteImg($att_id)
+    {
+        $attinfo = SystemAttachmentModel::get($att_id);
+        if ($attinfo) {
+            try {
+                $upload = UploadService::init($attinfo['image_type']);
+                if ($attinfo['image_type'] == 1) {
+                    if (strpos($attinfo['att_dir'], '/') == 0) {
+                        $attinfo['att_dir'] = substr($attinfo['att_dir'], 1);
+                    }
+                    $upload->delete($attinfo['att_dir']);
+                } else {
+                    $upload->delete($attinfo['name']);
+                }
+            } catch (\Throwable $e) {
+            }
+            SystemAttachmentModel::where('att_id', $att_id)->delete();
+        }
+    }
+
+    /**
+     * 移动图片分类显示
+     */
+    public function move($images)
+    {
+        $formbuider = [];
+        $formbuider[] = Form::hidden('images', $images);
+        $formbuider[] = Form::select('pid', '选择分类')->setOptions(function () {
+            $list = Category::getCateList();
+            $options = [['value' => 0, 'label' => '所有分类']];
+            foreach ($list as $id => $cateName) {
+                $options[] = ['label' => $cateName['html'] . $cateName['name'], 'value' => $cateName['id']];
+            }
+            return $options;
+        })->filterable(1);
+        return $this->makePostForm('编辑分类', $formbuider, Url::buildUrl('/file/do_move'), 'PUT');
+    }
+
+    /**
+     * 移动图片分类操作
+     */
+    public function moveImageCate()
+    {
+        $data = Util::postMore([
+            'pid',
+            'images'
+        ]);
+        if ($data['images'] == '') return $this->fail('请选择图片');
+        if (!$data['pid']) return $this->fail('请选择分类');
+        $res = SystemAttachmentModel::where('att_id', 'in', $data['images'])->update(['pid' => $data['pid']]);
+        if ($res)
+            return $this->success('移动成功');
+        else
+            return $this->fail('移动失败!');
+    }
+
+}

+ 133 - 0
app/adminapi/controller/v1/file/SystemAttachmentCategory.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace app\adminapi\controller\v1\file;
+
+use app\adminapi\controller\AuthController;
+use think\Request;
+use think\facade\Route as Url;
+use crmeb\services\{UtilService as Util,FormBuilder as Form};
+use app\models\system\{SystemAttachment as SystemAttachmentModel,SystemAttachmentCategory as Category};
+
+/**
+ * 图片分类管理类
+ * Class SystemAttachmentCategory
+ * @package app\adminapi\controller\v1\file
+ */
+class SystemAttachmentCategory extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index($name='')
+    {
+        return $this->success(Category::getAll($name, $this->merId));
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $id=$this->request->param('id');
+        $id=$id ?? 0;
+        $mer_id = $this->merId;
+        $formbuider = [];
+        $formbuider[] = Form::select('pid','上级分类',(string)$id)->setOptions(function () use ($mer_id){
+            $list = Category::getCateList(0, $mer_id);
+            $options =  [['value'=>0,'label'=>'所有分类']];
+            foreach ($list as $id=>$cateName){
+                $options[] = ['label'=>$cateName['html'].$cateName['name'],'value'=>$cateName['id']];
+            }
+            return $options;
+        })->filterable(1);
+        $formbuider[] = Form::input('name','分类名称');
+        return $this->makePostForm('添加分类', $formbuider, Url::buildUrl('/file/category'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param  \think\Request  $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $request = app('request');
+        $post = $request->post();
+        $data['pid'] = $post['pid'];
+        $data['name'] = $post['name'];
+        $data['mer_id'] = $this->merId;
+        if(empty($post['name'] ))
+            return $this->fail('分类名称不能为空!');
+        $res = Category::create($data);
+        if($res)
+            return $this->success('添加成功');
+        else
+            return $this->fail('添加失败!');
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        $Category = Category::get($id);
+        if(!$Category) return $this->fail('数据不存在!');
+        $formbuider = [];
+        $formbuider[] = Form::hidden('id',$id);
+        $formbuider[] = Form::select('pid','上级分类',(string)$Category->getData('pid'))->setOptions(function ()use($id){
+            $list = Category::getCateList();
+            $options =  [['value'=>0,'label'=>'所有分类']];
+            foreach ($list as $id=>$cateName){
+                $options[] = ['label'=>$cateName['html'].$cateName['name'],'value'=>$cateName['id']];
+            }
+            return $options;
+        })->filterable(1);
+        $formbuider[] = Form::input('name','分类名称',$Category->getData('name'));
+        return $this->makePostForm('编辑分类', $formbuider, Url::buildUrl('/file/category/'.$id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param  \think\Request  $request
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $data = Util::postMore([
+            'pid',
+            'name'
+        ]);
+        if($data['pid'] == '') return $this->fail('请选择父类');
+        if(!$data['name']) return $this->fail('请输入分类名称');
+        Category::edit($data,$id);
+        return $this->success('分类编辑成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        $chdcount = Category::where('pid',$id)->count();
+        if($chdcount) return $this->fail('有子栏目不能删除');
+        $chdcount = SystemAttachmentModel::where('pid',$id)->count();
+        if($chdcount) return $this->fail('栏目内有图片不能删除');
+        if(Category::del($id))
+            return $this->success('删除成功!');
+        else
+            return $this->fail('删除失败');
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/finance/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/finance

+ 93 - 0
app/adminapi/controller/v1/finance/Finance.php

@@ -0,0 +1,93 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: lofate
+ * Date: 2019/11/28
+ * TIME: 10:16
+ */
+
+namespace app\adminapi\controller\v1\finance;
+
+use app\models\finance\FinanceModel;
+use app\models\user\{User,UserBill};
+use app\adminapi\controller\AuthController;
+use crmeb\services\UtilService as Util;
+
+class Finance extends AuthController
+{
+    /**
+     * 筛选类型
+     */
+    public function bill_type()
+    {
+        $list = UserBill::where('type', 'not in', ['gain', 'system_sub', 'deduction', 'sign'])
+            ->where('mer_id', $this->merId)
+            ->where('category', 'not in', 'integral')
+            ->field(['title', 'type'])
+            ->group('type')
+            ->distinct(true)
+            ->select();
+        return $this->success(compact('list'));
+    }
+
+    /**
+     * 资金记录
+     */
+    public function list()
+    {
+        $where = Util::getMore([
+            ['start_time', ''],
+            ['end_time', ''],
+            ['nickname', ''],
+            ['limit', 20],
+            ['page', 1],
+            ['type', ''],
+        ]);
+        $where['mer_id'] = $this->merId;
+        return $this->success(FinanceModel::getBillList($where));
+    }
+
+    /**
+     * 佣金记录
+     */
+    public function get_commission_list()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+            ['price_max', ''],
+            ['price_min', ''],
+            ['excel', ''],
+        ]);
+        $where['mer_id'] = $this->merId;
+        return $this->success(User::getCommissionList($where));
+    }
+
+    /**
+     * 佣金详情用户信息
+     */
+    public function user_info($id = '')
+    {
+        if ($id == '') return $this->fail('缺少参数');
+        $user_info=User::userInfo($id);
+        return $this->success(compact('user_info'));
+    }
+
+    /**
+     * 佣金提现记录个人列表
+     */
+    public function get_extract_list($id = '')
+    {
+        if ($id == '') return $this->fail('缺少参数');
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['start_time', ''],
+            ['end_time', ''],
+            ['nickname', '']
+        ]);
+        return $this->success(UserBill::getExtrctOneList($where, $id));
+    }
+
+}

+ 158 - 0
app/adminapi/controller/v1/finance/UserExtract.php

@@ -0,0 +1,158 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: lofate
+ * Date: 2019/11/28
+ * TIME: 12:27
+ */
+namespace app\adminapi\controller\v1\finance;
+
+use app\adminapi\controller\AuthController;
+use app\models\user\UserExtract as UserExtractModel;
+use crmeb\services\{FormBuilder as Form,UtilService as Util};
+use think\facade\Route as Url;
+use think\Request;
+
+class UserExtract extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page',1],
+            ['limit',20],
+            ['status',''],
+            ['extract_type',''],
+            ['nireid',''],
+            ['data',''],
+        ],$this->request);
+        $map['data'] = $where['data'];
+        $extract_statistics=UserExtractModel::extractStatistics($map, $this->merId);
+        $list=UserExtractModel::systemPage($where, $this->merId);
+        return $this->success(compact('extract_statistics','list'));
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        if(!$id) return $this->fail('数据不存在');
+        $UserExtract = UserExtractModel::get($id);
+        if(!$UserExtract) return $this->fail('数据不存在!');
+        $f = array();
+        $f[] = Form::input('real_name','姓名',$UserExtract['real_name']);
+        $f[] = Form::number('extract_price','提现金额',$UserExtract['extract_price'])->precision(2);
+        if($UserExtract['extract_type']=='alipay'){
+            $f[] = Form::input('alipay_code','支付宝账号',$UserExtract['alipay_code']);
+        }else if($UserExtract['extract_type']=='weixin'){
+            $f[] = Form::input('wechat','微信号',$UserExtract['wechat']);
+        }else{
+            $f[] = Form::input('bank_code','银行卡号',$UserExtract['bank_code']);
+            $f[] = Form::input('bank_address','开户行',$UserExtract['bank_address']);
+        }
+        $f[] = Form::input('mark','备注',$UserExtract['mark'])->type('textarea');
+        return $this->makePostForm('编辑', $f, Url::buildUrl('/finance/extract/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param  \think\Request  $request
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $UserExtract = UserExtractModel::get($id);
+        if(!$UserExtract) return $this->fail('数据不存在!');
+        if($UserExtract['extract_type']=='alipay'){
+            $data = Util::postMore([
+                'real_name',
+                'mark',
+                'extract_price',
+                'alipay_code',
+            ]);
+            if(!$data['real_name']) return $this->fail('请输入姓名');
+            if($data['extract_price']<=-1) return $this->fail('请输入提现金额');
+            if(!$data['alipay_code']) return $this->fail('请输入支付宝账号');
+        }else if($UserExtract['extract_type']=='weixin'){
+            $data = Util::postMore([
+                'real_name',
+                'mark',
+                'extract_price',
+                'wechat',
+            ]);
+            if($data['extract_price']<=-1) return $this->fail('请输入提现金额');
+            if(!$data['wechat']) return $this->fail('请输入微信账号');
+        }else{
+            $data = Util::postMore([
+                'real_name',
+                'extract_price',
+                'mark',
+                'bank_code',
+                'bank_address',
+            ]);
+            if(!$data['real_name']) return $this->fail('请输入姓名');
+            if($data['extract_price']<=-1) return $this->fail('请输入提现金额');
+            if(!$data['bank_code']) return $this->fail('请输入银行卡号');
+            if(!$data['bank_address']) return $this->fail('请输入开户行');
+        }
+        if(!UserExtractModel::edit($data,$id))
+            return $this->fail(UserExtractModel::getErrorInfo('修改失败'));
+        else
+            return $this->success('修改成功!');
+    }
+
+    /**
+     * 拒绝
+     * @param $id
+     * @return mixed
+     */
+    public function refuse($id)
+    {
+        if(!UserExtractModel::be(['id'=>$id,'status'=>0])) return $this->fail('操作记录不存在或状态错误!');
+        $fail_msg =request()->post();
+        $extract=UserExtractModel::get($id);
+        if(!$extract)  return $this->fail('操作记录不存在!');
+        if($extract->status==1)  return $this->fail('已经提现,错误操作');
+        if($extract->status==-1)  return $this->fail('您的提现申请已被拒绝,请勿重复操作!');
+        $res = UserExtractModel::changeFail($id,$fail_msg['message']);
+        if($res){
+            return $this->success('操作成功!');
+        }else{
+            return $this->fail('操作失败!');
+        }
+    }
+
+    /**
+     * 通过
+     * @param $id
+     * @return mixed
+     */
+    public function adopt($id)
+    {
+        if(!UserExtractModel::be(['id'=>$id,'status'=>0]))
+            return $this->fail('操作记录不存在或状态错误!');
+        UserExtractModel::beginTrans();
+        $extract=UserExtractModel::get($id);
+        if(!$extract)  return $this->fail('操作记录不存!');
+        if($extract->status == 1)  return $this->fail('您已提现,请勿重复提现!');
+        if($extract->status == -1)  return $this->fail('您的提现申请已被拒绝!');
+        $res = UserExtractModel::changeSuccess($id);
+        if($res){
+            UserExtractModel::commitTrans();
+            return $this->success('操作成功!');
+        }else{
+            UserExtractModel::rollbackTrans();
+            return $this->fail('操作失败!');
+        }
+    }
+}

+ 136 - 0
app/adminapi/controller/v1/finance/UserRecharge.php

@@ -0,0 +1,136 @@
+<?php
+
+namespace app\adminapi\controller\v1\finance;
+
+use app\adminapi\controller\AuthController;
+use app\models\routine\RoutineTemplate;
+use app\models\user\{User, UserRecharge as UserRechargeModel, UserBill, WechatUser as WechatUserWap};
+use app\models\wechat\WechatTemplate;
+use crmeb\services\{FormBuilder as Form, MiniProgramService, UtilService as Util, WechatService, WechatTemplateService};
+use think\facade\Route as Url;
+
+class UserRecharge extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['data', ''],
+            ['paid', ''],
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+            ['excel', ''],
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        return $this->success(UserRechargeModel::getUserRechargeList($where));
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        $rechargInfo = UserRechargeModel::get($id);
+        if ($rechargInfo->paid) return $this->fail('已支付的订单记录无法删除');
+        if (UserRechargeModel::del($id))
+            return $this->success('删除成功');
+        else
+            return $this->fail('删除失败');
+    }
+
+    /**
+     * 获取用户充值数据
+     * @return array
+     */
+    public function user_recharge()
+    {
+        $where = Util::getMore([
+            ['data', ''],
+            ['paid', ''],
+            ['nickname', ''],
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        return $this->success(UserRechargeModel::getDataList($where));
+    }
+
+    /**退款表单
+     * @param $id
+     * @return mixed|void
+     */
+    public function refund_edit($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $UserRecharge = UserRechargeModel::get($id);
+        if (!$UserRecharge) return $this->fail('数据不存在!');
+        if ($UserRecharge['paid'] == 1) {
+            $f = array();
+            $f[] = Form::input('order_id', '退款单号', $UserRecharge->getData('order_id'))->disabled(1);
+            $f[] = Form::number('refund_price', '退款金额', $UserRecharge->getData('price'))->precision(2)->min(0)->max($UserRecharge->getData('price'))->value(0);
+            return $this->makePostForm('编辑', $f, Url::buildUrl('/recharge/' . $id), 'PUT');
+        } else return $this->fail('数据不存在!');
+    }
+
+    /**
+     * 退款操作
+     * @param $id
+     * @return
+     */
+    public function refund_update($id)
+    {
+        $data = Util::postMore([
+            'refund_price',
+        ]);
+        if (!$id) return $this->fail('数据不存在');
+        $UserRecharge = UserRechargeModel::get($id);
+        if (!$UserRecharge) return $this->fail('数据不存在!');
+        if ($UserRecharge['price'] == $UserRecharge['refund_price']) return $this->fail('已退完支付金额!不能再退款了');
+        if (!$data['refund_price']) return $this->fail('请输入退款金额');
+        $refund_price = $data['refund_price'];
+        $data['refund_price'] = bcadd($data['refund_price'], $UserRecharge['refund_price'], 2);
+        $bj = bccomp((float)$UserRecharge['price'], (float)$data['refund_price'], 2);
+        if ($bj < 0) return $this->fail('退款金额大于支付金额,请修改退款金额');
+        $refund_data['pay_price'] = $UserRecharge['price'];
+        $refund_data['refund_price'] = $refund_price;
+//        $refund_data['refund_account']='REFUND_SOURCE_RECHARGE_FUNDS';
+        try {
+            $recharge_type = UserRechargeModel::where('order_id', $UserRecharge['order_id'])->value('recharge_type');
+            if ($recharge_type == 'weixin') {
+                WechatService::payOrderRefund($UserRecharge['order_id'], $refund_data);
+            } else {
+                MiniProgramService::payOrderRefund($UserRecharge['order_id'], $refund_data, $this->merId);
+            }
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+        UserRechargeModel::edit($data, $id);
+        User::bcDec($UserRecharge['uid'], 'now_money', $refund_price, 'uid');
+        switch (strtolower($UserRecharge['recharge_type'])) {
+            case 'weixin':
+//                WechatTemplateService::sendTemplate(WechatUserWap::where('uid', $UserRecharge['uid'])->value('openid'), WechatTemplateService::ORDER_REFUND_STATUS, [
+//                    'first' => '亲,您充值的金额已退款,本次退款' .
+//                        $data['refund_price'] . '金额',
+//                    'keyword1' => $UserRecharge['order_id'],
+//                    'keyword2' => $UserRecharge['price'],
+//                    'keyword3' => date('Y-m-d H:i:s', $UserRecharge['add_time']),
+//                    'remark' => '点击查看订单详情'
+//                ], Url::buildUrl('wap/My/balance', '', true, true));
+                $wechatTemplate = new WechatTemplate();
+                $wechatTemplate->sendRechargeRefundStatus($data, $UserRecharge);
+                break;
+            case 'routine':
+                RoutineTemplate::sendRechargeSuccess($UserRecharge, $data['refund_price']);
+                break;
+        }
+        UserBill::expend('系统退款', $UserRecharge['uid'], 'now_money', 'user_recharge_refund', $refund_price, $id, $UserRecharge['price'], '退款给用户' . $refund_price . '元');
+        return $this->success('退款成功!');
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/freight/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/freight

+ 143 - 0
app/adminapi/controller/v1/freight/Express.php

@@ -0,0 +1,143 @@
+<?php
+
+namespace app\adminapi\controller\v1\freight;
+
+use app\models\system\Express as ExpressModel;
+use app\adminapi\controller\AuthController;
+use crmeb\services\FormBuilder as Form;
+use crmeb\services\UtilService as Util;
+use think\facade\Route as Url;
+use think\Request;
+
+class Express extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $params = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['keyword', '']
+        ], $this->request);
+        $list = ExpressModel::systemPage($params);
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $formbuider = [
+            Form::input('name', '公司名称')->required('公司名称名称必填'),
+            Form::input('code', '编码'),
+            Form::number('sort', '排序', 0),
+            Form::radio('is_show', '是否启用', 1)->options([['value' => 0, 'label' => '隐藏'], ['value' => 1, 'label' => '启用']]),
+        ];
+        return $this->makePostForm('添加物流公司', $formbuider, Url::buildUrl('/freight/express'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $data = Util::postMore([
+            'name',
+            'code',
+            ['sort', 0],
+            ['is_show', 0]]);
+        if (!$data['name']) return $this->fail('请输入公司名称');
+        ExpressModel::create($data);
+        return $this->success('添加公司成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        //
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        $menu = ExpressModel::get($id);
+        if (!$menu) return $this->fail('数据不存在!');
+        $formbuider = [
+            Form::input('name', '公司名称', $menu['name']),
+            Form::input('code', '编码', $menu['code']),
+            Form::number('sort', '排序', $menu['sort']),
+            Form::radio('is_show', '是否启用', $menu['is_show'])->options([['value' => 0, 'label' => '隐藏'], ['value' => 1, 'label' => '启用']])
+        ];
+        return $this->makePostForm('编辑物流公司', $formbuider, Url::buildUrl('/freight/express/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param \think\Request $request
+     * @param int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $data = Util::postMore([
+            'name',
+            'code',
+            ['sort', 0],
+            ['is_show', 0]]);
+        if (!$data['name']) return $this->fail('请输入公司名称');
+        if (!ExpressModel::get($id)) return $this->fail('编辑的记录不存在!');
+        ExpressModel::edit($data, $id);
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('参数错误,请重新打开');
+        $res = ExpressModel::destroy($id);
+        if (!$res)
+            return $this->fail(ExpressModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param int $id
+     * @param string $status
+     * @return mixed
+     */
+    public function set_status($id = 0, $status = '')
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        ExpressModel::where(['id' => $id])->update(['is_show' => $status]);
+
+        return $this->success($status == 0 ? '隐藏成功' : '显示成功');
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/marketing/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/marketing

+ 104 - 0
app/adminapi/controller/v1/marketing/PopScreen.php

@@ -0,0 +1,104 @@
+<?php
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\adminapi\controller\AuthController;
+use crmeb\traits\CurdControllerTrait;
+use crmeb\services\UtilService as Util;
+use app\models\store\{PopScreen as PopScreenModel};
+
+/**
+ * 弹屏广告管理
+ * Class PopScreen
+ * @package app\admin\controller\store
+ */
+class PopScreen extends AuthController
+{
+
+	use CurdControllerTrait;
+
+	protected $bindModel = PopScreenModel::class;
+
+	/**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['store_name', '']
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = PopScreenModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 详情
+     * @param $id
+     * @return mixed
+     */
+    public function read($id)
+    {
+        $info = PopScreenModel::getOne($id);
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 保存新建的资源
+     * @param int $id
+     */
+    public function save($id = 0)
+    {
+        $data = Util::postMore([
+            [['mer_id', 'd'], ''],
+            [['title', 's'], ''],
+            [['site_url', 's'], ''],
+            ['pop_image', ''],
+            [['command', 's'], ''],
+            [['template_id', 'd'], ''],
+            ['bg_image', ''],
+            [['share_title', 's'], ''],
+            [['share_describe', 's'], ''],
+            [['domain_name', 's'], ''],
+            ['add_time', ''],
+            ['click', 0]
+        ]);
+        $data['mer_id'] = $this->merId ?: '';
+        if ($id) {
+            $record = PopScreenModel::get($id);
+            if (!$record) return $this->fail('数据不存在!');
+            PopScreenModel::edit($data, $id);
+            return $this->success('编辑成功!');
+        } else {
+            $data['add_time'] = time();
+            $id = PopScreenModel::insertGetId($data);
+            $data['domain_name']='http://pop'.$id.'.lipinmama.cn/';
+            PopScreenModel::edit($data, $id);
+            return $this->success('添加弹屏广告成功!');
+        }
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $record = PopScreenModel::get($id);
+        if (!$record) return $this->fail('数据不存在!');
+        if ($record['is_del']) return $this->fail('已删除!');
+        $data['is_del'] = 1;
+        if (!PopScreenModel::edit($data, $id))
+            return $this->fail(PopScreenModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+}

+ 183 - 0
app/adminapi/controller/v1/marketing/StoreAssistance.php

@@ -0,0 +1,183 @@
+<?php
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\adminapi\controller\AuthController;
+use crmeb\traits\CurdControllerTrait;
+use crmeb\services\UtilService as Util;
+use app\models\store\{StoreDescription, StoreAssistanceActive, StoreAssistance as StoreAssistanceModel, StoreProductAttr};
+
+/**
+ * 助力管理
+ * Class StoreAssistance
+ * @package app\admin\controller\store
+ */
+class StoreAssistance extends AuthController
+{
+
+	use CurdControllerTrait;
+
+	protected $bindModel = StoreAssistanceModel::class;
+
+	/**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['is_show', ''],
+            ['store_name', '']
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = StoreAssistanceModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 拼团统计
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function statistics()
+    {
+        $mer_id = $this->merId ?: '';
+        $info = StoreAssistanceModel::getStatistics($mer_id);
+        return $this->success($info);
+    }
+
+    /**
+     * 详情
+     * @param $id
+     * @return mixed
+     */
+    public function read($id)
+    {
+        $info = StoreAssistanceModel::getOne($id);
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 保存新建的资源
+     * @param int $id
+     */
+    public function save($id = 0)
+    {
+        $data = Util::postMore([
+            [['product_id', 'd'], 0],
+            [['mer_id', 'd'], ''],
+            [['title', 's'], ''],
+            [['info', 's'], ''],
+            [['unit_name', 's'], ''],
+            ['image', ''],
+            ['images', []],
+            ['section_time', []],
+            [['is_host', 'd'], 0],
+            [['is_show', 'd'], 0],
+            [['num', 'd'], 0],
+            [['temp_id', 'd'], 0],
+            [['effective_time', 'd'], 0],
+            [['people', 'd'], 0],
+            [['description', 's'], ''],
+            [['p_separate_account', 'd'], 0],
+            [['c_separate_account', 'd'], 0],
+            ['attrs', []],
+            ['items', []],
+            ['num', 1],
+            ['sort', 0]
+        ]);
+        $this->validate($data, \app\adminapi\validates\marketing\StoreAssistanceValidate::class, 'save');
+        $description = $data['description'];
+        $detail = $data['attrs'];
+        $items = $data['items'];
+        $data['start_time'] = strtotime($data['section_time'][0]);
+        $data['stop_time'] = strtotime($data['section_time'][1]);
+        $data['images'] = json_encode($data['images']);
+        $data['price'] = min(array_column($detail, 'price'));
+        $data['quota'] = $data['quota_show'] = array_sum(array_column($detail, 'quota'));
+        $data['stock'] = array_sum(array_column($detail, 'stock'));
+        $data['mer_id'] = $this->merId ?: '';
+        unset($data['section_time'], $data['description'], $data['attrs'], $data['items']);
+        if ($id) {
+            $product = StoreAssistanceModel::get($id);
+            if (!$product) return $this->fail('数据不存在!');
+            $data['product_id'] = $product['product_id'];
+            StoreAssistanceModel::edit($data, $id);
+            StoreProductAttr::createProductAttr($items, $detail, $id, 4);
+            StoreDescription::saveDescription($description, $id, 4);
+            return $this->success('编辑成功!');
+        } else {
+            $data['add_time'] = time();
+            $assistance_id = StoreAssistanceModel::insertGetId($data);
+            StoreProductAttr::createProductAttr($items, $detail, $assistance_id, 4);
+            StoreDescription::saveDescription($description, $assistance_id, 4);
+            return $this->success('添加助力成功!');
+        }
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $product = StoreAssistanceModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        if ($product['is_del']) return $this->fail('已删除!');
+        $data['is_del'] = 1;
+        if (!StoreAssistanceModel::edit($data, $id))
+            return $this->fail(StoreAssistanceModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        StoreAssistanceModel::where(['id' => $id])->update(['is_show' => $status]);
+        return $this->success($status == 0 ? '关闭成功' : '开启成功');
+    }
+
+    /**助力列表
+     * @return mixed
+     */
+    public function assistance_list()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['status', ''],
+            ['data', ''],
+        ], $this->request);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = StoreAssistanceActive::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**助力人列表
+     * @return mixed
+     */
+    public function order_assistance($id)
+    {
+        $mer_id = $this->merId ?: '';
+        $StoreAssistance = StoreAssistanceActive::getAssistanceUserOne($id, $mer_id);
+        if (!$StoreAssistance) return $this->fail('数据不存在!');
+        $list = StoreAssistanceActive::getAssistanceMember($id);
+        $list[] = $StoreAssistance;
+        return $this->success(compact('list'));
+    }
+}

+ 154 - 0
app/adminapi/controller/v1/marketing/StoreBargain.php

@@ -0,0 +1,154 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: lofate
+ * Date: 2019/12/18
+ * Time: 12:24
+ */
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\adminapi\controller\AuthController;
+use app\models\store\StoreDescription;
+use app\models\store\StoreProductAttr;
+use crmeb\services\UtilService as Util;
+use crmeb\services\FormBuilder as Form;
+use crmeb\traits\CurdControllerTrait;
+use think\facade\Route as Url;
+use app\models\store\StoreProduct as ProductModel;
+use app\models\store\StoreBargain as StoreBargainModel;
+
+/**
+ * 砍价管理
+ * Class StoreBargain
+ * @package app\adminapi\controller\v1\marketing
+ */
+class StoreBargain extends AuthController
+{
+    use CurdControllerTrait;
+
+    protected $bindModel = StoreBargainModel::class;
+
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            [['page', 'd'], 1],
+            [['limit', 'd'], 20],
+            ['status', ''],
+            ['store_name', ''],
+            ['export', 0],
+            ['data', ''],
+        ], $this->request);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = StoreBargainModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save($id)
+    {
+        $data = Util::postMore([
+            ['title', ''],
+            ['info', ''],
+            ['unit_name', ''],
+            ['section_time', []],
+            ['image', ''],
+            ['images', []],
+            ['bargain_max_price', 0],
+            ['bargain_min_price', 0],
+            ['sort', 0],
+            ['give_integral', 0],
+            ['is_hot', 0],
+            ['status', 0],
+            ['product_id', 0],
+            ['description', ''],
+            ['attrs', []],
+            ['items', []],
+            ['temp_id', 0],
+            ['rule', ''],
+            ['num', 1]
+        ]);
+        $this->validate($data, \app\adminapi\validates\marketing\StoreBargainValidate::class, 'save');
+        $description = $data['description'];
+        $detail = $data['attrs'];
+        $items = $data['items'];
+        $data['start_time'] = strtotime($data['section_time'][0]);
+        $data['stop_time'] = strtotime($data['section_time'][1]);
+        $data['images'] = json_encode($data['images']);
+        $data['stock'] = $detail[0]['stock'];
+        $data['quota'] = $detail[0]['quota'];
+        $data['quota_show'] = $detail[0]['quota'];
+        $data['price'] = $detail[0]['price'];
+        $data['min_price'] = $detail[0]['min_price'];
+        $data['mer_id'] = $this->merId ?: '';
+        unset($data['section_time'], $data['description'], $data['attrs'], $data['items'], $detail[0]['min_price'], $detail[0]['_index'], $detail[0]['_rowKey']);
+        if ($id) {
+            $product = StoreBargainModel::get($id);
+            if (!$product) return $this->fail('数据不存在!');
+            StoreBargainModel::edit($data, $id);
+            StoreProductAttr::createProductAttr($items, $detail, $id, 2);
+            StoreDescription::saveDescription($description, $id, 2);
+            return $this->success('修改成功');
+        } else {
+            $data['add_time'] = time();
+            $bargain_id = StoreBargainModel::insertGetId($data);
+            StoreProductAttr::createProductAttr($items, $detail, $bargain_id, 2);
+            StoreDescription::saveDescription($description, $bargain_id, 2);
+            return $this->success('添加成功');
+        }
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        $info = StoreBargainModel::getOne($id);
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $product = StoreBargainModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        if ($product['is_del']) return $this->fail('已删除!');
+        $data['is_del'] = 1;
+        if (StoreBargainModel::edit($data, $id))
+            return $this->success('删除成功!');
+        else
+            return $this->fail(StoreBargainModel::getErrorInfo('删除失败,请稍候再试!'));
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        StoreBargainModel::where(['id' => $id])->update(['status' => $status]);
+        return $this->success($status == 0 ? '关闭成功' : '开启成功');
+    }
+}

+ 185 - 0
app/adminapi/controller/v1/marketing/StoreCombination.php

@@ -0,0 +1,185 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: lofate
+ * Date: 2019/12/19
+ * Time: 09:30
+ */
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\adminapi\controller\AuthController;
+use crmeb\traits\CurdControllerTrait;
+use crmeb\services\UtilService as Util;
+use app\models\store\{StoreDescription, StorePink, StoreCombination as StoreCombinationModel, StoreProductAttr};
+
+/**
+ * 拼团管理
+ * Class StoreCombination
+ * @package app\admin\controller\store
+ */
+class StoreCombination extends AuthController
+{
+
+    use CurdControllerTrait;
+
+    protected $bindModel = StoreCombinationModel::class;
+
+    /**
+     * 拼团列表
+     * @return mixed
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['is_show', ''],
+            ['store_name', '']
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = StoreCombinationModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 拼团统计
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function statistics()
+    {
+        $mer_id = $this->merId ?: '';
+        $info = StoreCombinationModel::getStatistics($mer_id);
+        return $this->success($info);
+    }
+
+    /**
+     * 详情
+     * @param $id
+     * @return mixed
+     */
+    public function read($id)
+    {
+        $info = StoreCombinationModel::getOne($id);
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 保存新建的资源
+     * @param int $id
+     */
+    public function save($id = 0)
+    {
+        $data = Util::postMore([
+            [['product_id', 'd'], 0],
+            [['title', 's'], ''],
+            [['info', 's'], ''],
+            [['unit_name', 's'], ''],
+            ['image', ''],
+            ['images', []],
+            ['section_time', []],
+            [['is_host', 'd'], 0],
+            [['is_show', 'd'], 0],
+            [['num', 'd'], 0],
+            [['temp_id', 'd'], 0],
+            [['effective_time', 'd'], 0],
+            [['people', 'd'], 0],
+            [['description', 's'], ''],
+            ['attrs', []],
+            ['items', []],
+            ['num', 1],
+            ['sort', 0]
+        ]);
+        $this->validate($data, \app\adminapi\validates\marketing\StoreCombinationValidate::class, 'save');
+        $description = $data['description'];
+        $detail = $data['attrs'];
+        $items = $data['items'];
+        $data['start_time'] = strtotime($data['section_time'][0]);
+        $data['stop_time'] = strtotime($data['section_time'][1]);
+        $data['images'] = json_encode($data['images']);
+        $data['price'] = min(array_column($detail, 'price'));
+        $data['quota'] = $data['quota_show'] = array_sum(array_column($detail, 'quota'));
+        $data['stock'] = array_sum(array_column($detail, 'stock'));
+        $data['mer_id'] = $this->merId ?: '';
+        unset($data['section_time'], $data['description'], $data['attrs'], $data['items']);
+        if ($id) {
+            $product = StoreCombinationModel::get($id);
+            if (!$product) return $this->fail('数据不存在!');
+            $data['product_id'] = $product['product_id'];
+            StoreCombinationModel::edit($data, $id);
+            StoreProductAttr::createProductAttr($items, $detail, $id, 3);
+            StoreDescription::saveDescription($description, $id, 3);
+            return $this->success('编辑成功!');
+        } else {
+            $data['add_time'] = time();
+            $combination_id = StoreCombinationModel::insertGetId($data);
+            StoreProductAttr::createProductAttr($items, $detail, $combination_id, 3);
+            StoreDescription::saveDescription($description, $combination_id, 3);
+            return $this->success('添加拼团成功!');
+        }
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $product = StoreCombinationModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        if ($product['is_del']) return $this->fail('已删除!');
+        $data['is_del'] = 1;
+        if (!StoreCombinationModel::edit($data, $id))
+            return $this->fail(StoreCombinationModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        StoreCombinationModel::where(['id' => $id])->update(['is_show' => $status]);
+        return $this->success($status == 0 ? '关闭成功' : '开启成功');
+    }
+
+    /**拼团列表
+     * @return mixed
+     */
+    public function combine_list()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['status', ''],
+            ['data', ''],
+        ], $this->request);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = StorePink::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**拼团人列表
+     * @return mixed
+     */
+    public function order_pink($id)
+    {
+        $StorePink = StorePink::getPinkUserOne($id);
+        if (!$StorePink) return $this->fail('数据不存在!');
+        $list = StorePink::getPinkMember($id);
+        $list[] = $StorePink;
+        return $this->success(compact('list'));
+    }
+
+}

+ 218 - 0
app/adminapi/controller/v1/marketing/StoreCoupon.php

@@ -0,0 +1,218 @@
+<?php
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\adminapi\controller\AuthController;
+use app\models\store\{
+    StoreCategory, StoreCouponIssue, StoreCoupon as CouponModel
+};
+use crmeb\services\{
+    FormBuilder as Form, UtilService as Util
+};
+use think\facade\Route as Url;
+use think\Request;
+
+/**
+ * 优惠券制作
+ * Class StoreCoupon
+ * @package app\adminapi\controller\v1\marketing
+ */
+class StoreCoupon extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['status', ''],
+            ['title', ''],
+        ], $this->request);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = CouponModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $data = Util::getMore(['type']);//接收参数
+        $f[] = Form::input('title', '优惠券名称');
+        switch ($data['type']) {
+            case 1://品类券
+                $f[] = Form::select('category_id', '选择品类')->setOptions(function () {
+                    $mer_id = $this->merId ?: '';
+                    $list = StoreCategory::getTierList(null, 1, $mer_id);
+                    $menus = [];
+                    foreach ($list as $menu) {
+                        $menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['cate_name'], 'disabled' => $menu['pid'] ? false : true];
+                    }
+                    return $menus;
+                })->filterable(1)->col(12);
+                break;
+            case 2://商品券
+                $f[] = Form::frameImages('image', '商品', Url::buildUrl('admin/store.StoreProduct/index', array('fodder' => 'image', 'type' => 'many')))->icon('ios-add')->width('60%')->height('600px')->setProps(['srcKey' => 'image']);
+                $f[] = Form::hidden('product_id', '');
+                break;
+        }
+        $f[] = Form::number('coupon_price', '优惠券面值', 0)->min(0);
+        $f[] = Form::number('use_min_price', '优惠券最低消费', 0)->min(0);
+        $f[] = Form::number('coupon_time', '优惠券有效期限', 0)->min(0);
+        $f[] = Form::number('sort', '排序')->value(0);
+        $f[] = Form::radio('status', '状态', 1)->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
+        $f[] = Form::hidden('type', $data['type']);
+        return $this->makePostForm('添加优惠券', $f, Url::buildUrl('/marketing/coupon/save'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $data = Util::postMore([
+            ['title', ''],
+            ['image', ''],
+            ['category_id', ''],
+            ['coupon_price', ''],
+            ['use_min_price', ''],
+            ['coupon_time', ''],
+            ['sort', ''],
+            ['status', 0],
+            ['type', 0]
+        ]);
+        $data['mer_id'] = $this->merId ?: '';
+        if ($data['type'] == 1) {
+            $this->validate($data, \app\adminapi\validates\marketing\StoreCouponValidate::class, 'type');
+        } elseif ($data['type'] == 2) {
+            $this->validate($data, \app\adminapi\validates\marketing\StoreCouponValidate::class, 'product');
+            $data['product_id'] = implode(',', array_column($data['image'], 'product_id'));
+        } else {
+            $this->validate($data, \app\adminapi\validates\marketing\StoreCouponValidate::class, 'save');
+        }
+        $data['add_time'] = time();
+        unset($data['image']);
+        CouponModel::create($data);
+        return $this->success('添加优惠券成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在!');
+        $data['is_del'] = 1;
+        if (!CouponModel::edit($data, $id))
+            return $this->fail(CouponModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态 立即失效
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function status($id)
+    {
+        if (!$id) return $this->fail('数据不存在!');
+        if (!CouponModel::editIsDel($id))
+            return $this->fail(CouponModel::getErrorInfo('修改失败,请稍候再试!'));
+        else
+            return $this->success('修改成功!');
+    }
+
+    /**
+     * 发布优惠券表单
+     * @param $id
+     * @return mixed
+     * @throws \FormBuilder\exception\FormBuilderException
+     */
+    public function issue($id)
+    {
+        if (!CouponModel::be(['id' => $id, 'status' => 1, 'is_del' => 0]))
+            return $this->fail('发布的优惠劵已失效或不存在!');
+        $f = [];
+        $f[] = Form::input('id', '优惠劵ID', $id)->disabled(1);
+        $f[] = Form::dateTimeRange('range_date', '领取时间')->placeholder('不填为永久有效');
+        $f[] = Form::radio('is_permanent', '是否限量', 1)->options([['label' => '不限量', 'value' => 1], ['label' => '限量', 'value' => 0]]);
+        $f[] = Form::number('count', '发布数量', 0)->min(0)->placeholder('不填或填0,为不限量');
+        $f[] = Form::radio('is_type', '赠送活动', 0)->options([['label' => '关闭', 'value' => 0], ['label' => '消费满赠', 'value' => 1], ['label' => '首次关注赠送', 'value' => 2]]);
+        $f[] = Form::number('full_reduction', '满赠金额', 0)->min(0)->placeholder('赠送优惠券的最低消费金额');
+//        $f[] = Form::radio('is_give_subscribe', '首次关注赠送', 0)->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
+        $f[] = Form::radio('status', '状态', 1)->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
+        return $this->makePostForm('发布优惠券', $f, Url::buildUrl('/marketing/coupon/issue/' . $id), 'POST');
+    }
+
+    /**
+     * 发布优惠券
+     * @param $id
+     * @return mixed
+     */
+    public function update_issue($id)
+    {
+        list($_id, $rangeTime, $count, $status, $is_permanent, $full_reduction, $is_give_subscribe, $is_full_give, $is_type) = Util::postMore([
+            'id',
+            ['range_date', ['', '']],
+            ['count', 0],
+            ['status', 0],
+            ['is_permanent', 0],
+            ['full_reduction', 0],
+            ['is_give_subscribe', 0],
+            ['is_full_give', 0],
+            ['is_type', 0]
+        ], null, true);
+        $mer_id = $this->merId ?: '';
+        if ($is_type == 1) {
+            $is_full_give = 1;
+        } elseif ($is_type == 2) {
+            $is_give_subscribe = 1;
+        }
+        if ($_id != $id) return $this->fail('操作失败,信息不对称');
+        if (!$count) $count = 0;
+        if (!CouponModel::be(['id' => $id, 'status' => 1, 'is_del' => 0])) return $this->fail('发布的优惠劵已失效或不存在!');
+        if (count($rangeTime) != 2) return $this->fail('请选择正确的时间区间');
+        list($startTime, $endTime) = $rangeTime;
+        if (!$startTime) $startTime = 0;
+        if (!$endTime) $endTime = 0;
+        if (!$startTime && $endTime) return $this->fail('请选择正确的开始时间');
+        if ($startTime && !$endTime) return $this->fail('请选择正确的结束时间');
+        if (StoreCouponIssue::setIssue($id, $count, strtotime($startTime), strtotime($endTime), $count, $status, $is_permanent, $full_reduction, $is_give_subscribe, $is_full_give, $mer_id))
+            return $this->success('发布优惠劵成功!');
+        else
+            return $this->fail('发布优惠劵失败!');
+    }
+
+    /**
+     * 发送优惠券列表
+     * @param $id
+     */
+    public function grant()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['status', ''],
+            ['title', ''],
+            ['is_del', 0],
+        ], $this->request);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = CouponModel::systemPageCoupon($where);
+        return $this->success($list);
+    }
+}

+ 92 - 0
app/adminapi/controller/v1/marketing/StoreCouponIssue.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\adminapi\controller\AuthController;
+use crmeb\services\{FormBuilder as Form, UtilService as Util};
+use app\models\store\{StoreCouponIssue as CouponIssueModel, StoreCouponIssueUser};
+use think\facade\Route as Url;
+use crmeb\traits\CurdControllerTrait;
+
+/**
+ * 已发布优惠券管理
+ * Class StoreCouponIssue
+ * @package app\adminapi\controller\v1\marketing
+ */
+class StoreCouponIssue extends AuthController
+{
+    use CurdControllerTrait;
+
+    protected $bindModel = CouponIssueModel::class;
+
+    /**
+     * 列表
+     * @return mixed
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['status', ''],
+            ['coupon_title', '']
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = CouponIssueModel::sysPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 删除
+     * @param string $id
+     * @return mixed
+     */
+    public function delete($id)
+    {
+        if (CouponIssueModel::edit(['is_del' => 1], $id, 'id'))
+            return $this->success('删除成功!');
+        else
+            return $this->fail('删除失败!');
+    }
+
+    /**
+     * @param string $id
+     * @return mixed|string
+     * @throws \FormBuilder\exception\FormBuilderException
+     */
+    public function edit($id)
+    {
+        $issueInfo = CouponIssueModel::get($id);
+        if (-1 == $issueInfo['status'] || 1 == $issueInfo['is_del']) return $this->fail('状态错误,无法修改');
+        $f = [Form::radio('status', '是否开启', $issueInfo['status'])->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]])];
+        return $this->makePostForm('状态修改', $f, Url::buildUrl('/marketing/coupon/released/status/' . $id), 'PUT');
+    }
+
+    /**修改状态
+     * @param $id
+     */
+    public function status($id)
+    {
+        $data = Util::postMore([
+            'status'
+        ]);
+        if (!isset($data['status'])) return $this->fail('缺少参数');
+        CouponIssueModel::where(['id' => $id])->update(['status' => $data['status']]);
+        return $this->success('修改成功');
+    }
+
+    /**
+     * 领取记录
+     * @param string $id
+     * @return mixed|string
+     */
+    public function issue_log($id)
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20]
+        ]);
+        $list = StoreCouponIssueUser::systemCouponIssuePage($id, $where);
+        return $this->success($list);
+    }
+}

+ 58 - 0
app/adminapi/controller/v1/marketing/StoreCouponUser.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\models\store\StoreCoupon as CouponModel;
+use app\adminapi\controller\AuthController;
+use crmeb\services\UtilService as Util;
+use app\models\store\StoreCouponUser as CouponUserModel;
+
+/**
+ * 优惠券发放记录控制器
+ * Class StoreCategory
+ * @package app\admin\controller\system
+ */
+class StoreCouponUser extends AuthController
+{
+
+    /**
+     * @return mixed
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page',1],
+            ['limit',20],
+            ['status', ''],
+//            ['is_fail', ''],
+            ['coupon_title', ''],
+            ['nickname', ''],
+        ], $this->request);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = CouponUserModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 发放优惠券到指定个人
+     * @param $id
+     * @param $uid
+     * @return \think\response\Json
+     */
+    public function grant(){
+        $data = Util::postMore([
+            'id',
+            'uid'
+        ]);
+        if(!$data['id']) return $this->fail('数据不存在!');
+        $coupon = CouponModel::get($data['id'])->toArray();
+        if(!$coupon) return $this->fail('数据不存在!');
+        $user = explode(',',$data['uid']);
+        $mer_id = $this->merId ?: '';
+        if(!CouponUserModel::setCoupon($coupon,$user,$mer_id))
+            return $this->fail(CouponUserModel::getErrorInfo('发放失败,请稍候再试!'));
+        else
+            return $this->success('发放成功!');
+
+    }
+}

+ 168 - 0
app/adminapi/controller/v1/marketing/StoreRechargeCard.php

@@ -0,0 +1,168 @@
+<?php
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\adminapi\controller\AuthController;
+use crmeb\traits\CurdControllerTrait;
+use crmeb\services\UtilService as Util;
+use app\models\store\{StoreRechargeCard as StoreRechargeCardModel, StoreRechargeCardBrokerage};
+use app\models\user\{UserRechargeCard as UserRechargeCardModel, UserBrokerage as UserBrokerageModel};
+
+/**
+ * 充值卡管理
+ * Class StoreRechargeCard
+ * @package app\admin\controller\store
+ */
+class StoreRechargeCard extends AuthController
+{
+
+	use CurdControllerTrait;
+
+	/**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['is_brokerage', ''],
+            ['status', ''],
+            ['card_name', '']
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = StoreRechargeCardModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 详情
+     * @param $id
+     * @return mixed
+     */
+    public function read($id)
+    {
+        $info = StoreRechargeCardModel::getOne($id);
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 保存新建的资源
+     * @param int $id
+     */
+    public function save($id = 0)
+    {
+        $data = Util::postMore([
+            [['title', 's'], ''],
+            ['poster', []],
+            [['explain', 's'], ''],
+            [['is_brokerage', 'd'], 0],
+            ['brokerage', []],
+            [['price', 'f'], 0],
+            [['give_money', 'f'], 0],
+            [['status', 'd'], 0],
+            [['sort', 'd'], 0],
+            ['section_time', []],
+            [['poundage', 'f'], 3]
+        ]);
+        $this->validate($data, \app\adminapi\validates\marketing\StoreRechargeCardValidate::class, 'save');
+        if (count($data['section_time']) != 2) return $this->fail('请选择正确的时间区间');
+        $data['start_time'] = strtotime($data['section_time'][0]) ?: 0;
+        $data['end_time'] = strtotime($data['section_time'][1]) ?: 0;
+        if (!$data['start_time'] && $data['end_time']) return $this->fail('请选择正确的开始时间');
+        if ($data['start_time'] && !$data['end_time']) return $this->fail('请选择正确的结束时间');
+        $data['poster'] = json_encode($data['poster']);
+        $data['mer_id'] = $this->merId ?: '';
+        $brokerage = $data['brokerage'];
+        unset($data['brokerage'], $data['section_time']);
+        if ($id) {
+            $RechargeCard = StoreRechargeCardModel::get($id);
+            if (!$RechargeCard) return $this->fail('数据不存在!');
+            StoreRechargeCardModel::edit($data, $id);
+            foreach($brokerage as $key => $value){
+                $brokerage[$key]['cid'] = !empty($value['cid']) ? $value['cid'] : $id;
+            }
+            if($data['is_brokerage'] == 1 && !empty($brokerage)){
+                StoreRechargeCardBrokerage::saveRechargeCardBrokerage($brokerage, $id);
+            }
+            return $this->success('编辑充值卡成功!');
+        } else {
+            $rechargeCard = StoreRechargeCardModel::create($data);
+            foreach($brokerage as $key => $value){
+                $brokerage[$key]['cid'] = (int)($rechargeCard['id']);
+            }
+            if($data['is_brokerage'] == 1 && !empty($brokerage)){
+                StoreRechargeCardBrokerage::saveRechargeCardBrokerage($brokerage, $rechargeCard['id']);
+            }
+            return $this->success('添加充值卡成功!');
+        }
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $rechargeCard = StoreRechargeCardModel::get($id);
+        if (!$rechargeCard) return $this->fail('数据不存在!');
+        if ($rechargeCard['is_del']) return $this->fail('已删除!');
+        $data['is_del'] = 1;
+        if (!StoreRechargeCardModel::edit($data, $id))
+            return $this->fail(StoreRechargeCardModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        StoreRechargeCardModel::where(['id' => $id])->update(['status' => $status]);
+        return $this->success($status == 0 ? '关闭成功' : '开启成功');
+    }
+
+    /**
+     * 获取用户充值记录
+     */
+    public function user_recharge_card()
+    {
+        $where = Util::getMore([
+            ['data', ''],
+            ['paid', ''],
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        return $this->success(UserRechargeCardModel::getUsereRechargeCardList($where));
+    }
+
+    /**
+     * 获取用户返现记录
+     */
+    public function user_brokerage()
+    {
+        $where = Util::getMore([
+            ['data', ''],
+            ['role', ''],
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+            ['uid', ''],
+            ['order_id', ''],
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        return $this->success(UserBrokerageModel::getUserBrokerageList($where));
+    }
+}

+ 175 - 0
app/adminapi/controller/v1/marketing/StoreSeckill.php

@@ -0,0 +1,175 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: lofate
+ * Date: 2019/12/19
+ * Time: 16:30
+ */
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\adminapi\controller\AuthController;
+use app\models\store\StoreDescription;
+use app\models\store\StoreProductAttr;
+use app\models\store\StoreProductAttrResult;
+use app\models\system\SystemGroupData;
+use app\models\system\SystemGroupDataMerchant;
+use crmeb\traits\CurdControllerTrait;
+use crmeb\services\UtilService as Util;
+use app\models\store\StoreSeckill as StoreSeckillModel;
+
+/**
+ * 限时秒杀  控制器
+ * Class StoreSeckill
+ * @package app\admin\controller\store
+ */
+class StoreSeckill extends AuthController
+{
+
+    use CurdControllerTrait;
+
+    protected $bindModel = StoreSeckillModel::class;
+
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            [['page','d'], 1],
+            [['limit','d'], 20],
+            [['status','s'], ''],
+            [['store_name','s'], '']
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        $seckillList = StoreSeckillModel::systemPage($where);
+        if (is_object($seckillList['list'])) $seckillList['list'] = $seckillList['list']->toArray();
+        $data = $seckillList['list']['data'];
+        foreach ($data as $k => $v) {
+            $end_time = $v['stop_time'] ? date('Y/m/d', $v['stop_time']) : '';
+            if ($end_time) {
+                $config = SystemGroupDataMerchant::get($v['time_id']);
+                if ($config) {
+                    $arr = json_decode($config->value, true);
+                    $start_hour = $arr['time']['value'];
+                    $continued = $arr['continued']['value'];
+                    $end_hour = $start_hour + $continued;
+                    $end_time = $end_time . ' ' . $end_hour . ':00:00';
+                }
+            }
+            $data[$k]['_stop_time'] = $end_time;
+        }
+        $count = $seckillList['list']['total'];
+        $list = $data;
+        return $this->success(compact('count', 'list'));
+    }
+
+    /**
+     * 详情
+     * @param $id
+     * @return mixed
+     */
+    public function read($id)
+    {
+        return $this->success(StoreSeckillModel::getOne($id));
+    }
+
+    /**
+     * 保存秒杀商品
+     * @param int $id
+     */
+    public function save($id)
+    {
+        $data = Util::postMore([
+            [['product_id', 'd'], 0],
+            [['title', 's'], ''],
+            [['info', 's'], ''],
+            [['unit_name', 's'], ''],
+            ['image', ''],
+            ['images', []],
+            [['give_integral', 'd'], 0],
+            ['section_time', []],
+            [['is_hot', 'd'], 0],
+            [['status', 'd'], 0],
+            [['num', 'd'], 0],
+            [['time_id', 'd'], 0],
+            [['temp_id', 'd'], 0],
+            [['sort', 'd'], 0],
+            [['description', 's'], ''],
+            ['attrs', []],
+            ['items', []],
+        ]);
+        $this->validate($data, \app\adminapi\validates\marketing\StoreSeckillValidate::class, 'save');
+        $description = $data['description'];
+        $detail = $data['attrs'];
+        $items = $data['items'];
+        $data['start_time'] = strtotime($data['section_time'][0]);
+        $data['stop_time'] = strtotime($data['section_time'][1]);
+        $data['images'] = json_encode($data['images']);
+        $data['price'] = min(array_column($detail, 'price'));
+        $data['ot_price'] = min(array_column($detail, 'ot_price'));
+        $data['quota'] = $data['quota_show'] = array_sum(array_column($detail, 'quota'));
+        $data['stock'] = array_sum(array_column($detail, 'stock'));
+        $data['mer_id'] = $this->merId ?: '';
+        unset($data['section_time'], $data['description'], $data['attrs'], $data['items']);
+        if ($id) {
+            $product = StoreSeckillModel::get($id);
+            if (!$product) return $this->fail('数据不存在!');
+            StoreSeckillModel::edit($data, $id);
+            StoreProductAttr::createProductAttr($items, $detail, $id, 1);
+            StoreDescription::saveDescription($description, $id, 1);
+            return $this->success('编辑成功!');
+        } else {
+            $data['add_time'] = time();
+            $seckill_id = StoreSeckillModel::insertGetId($data);
+            StoreProductAttr::createProductAttr($items, $detail, $seckill_id, 1);
+            StoreDescription::saveDescription($description, $seckill_id, 1);
+            return $this->success('添加成功!');
+        }
+
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        $product = StoreSeckillModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        if ($product['is_del']) return $this->fail('已删除!');
+        $data['is_del'] = 1;
+        if (!StoreSeckillModel::edit($data, $id))
+            return $this->fail(StoreSeckillModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        StoreSeckillModel::where(['id' => $id])->update(['status' => $status]);
+        return $this->success($status == 0 ? '关闭成功' : '开启成功');
+    }
+
+    /**
+     * 秒杀时间段列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function time_list()
+    {
+        $list = SystemGroupDataMerchant::getGroupData('routine_seckill_time', 0, $this->merId);
+        return $this->success(compact('list'));
+    }
+}

+ 47 - 0
app/adminapi/controller/v1/marketing/UserPoint.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace app\adminapi\controller\v1\marketing;
+
+use app\adminapi\controller\AuthController;
+use crmeb\services\UtilService as Util;
+use app\models\user\UserPoint as UserPointModel;
+
+/**
+ * 积分控制器
+ * Class StoreCategory
+ * @package app\admin\controller\system
+ */
+class UserPoint extends AuthController
+{
+
+    /**
+     * @return mixed
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['start_time', ''],
+            ['end_time', ''],
+            ['nickname', ''],
+            ['page', 1],
+            ['limit', 10],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $list = UserPointModel::getpointlist($where);
+        return $this->success($list);
+    }
+
+    //获取积分日志头部信息
+    public function integral_statistics()
+    {
+        $where = Util::getMore([
+            ['start_time', ''],
+            ['end_time', ''],
+            ['nickname', ''],
+        ]);
+        $where['mer_id'] = $this->merId;
+        $res = UserPointModel::getUserpointBadgelist($where);
+        return $this->success(compact('res'));
+    }
+
+}

+ 1 - 0
app/adminapi/controller/v1/merchant/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/merchant

+ 176 - 0
app/adminapi/controller/v1/merchant/SystemStore.php

@@ -0,0 +1,176 @@
+<?php
+
+namespace app\adminapi\controller\v1\merchant;
+
+use app\models\system\SystemConfig;
+use think\facade\Route as Url;
+use app\adminapi\controller\AuthController;
+use app\models\system\{SystemStore as SystemStoreModel, SystemStoreStaff, SystemProductStore};
+use crmeb\services\{FormBuilder as Form, UtilService as Util};
+
+/**
+ * 门店管理控制器
+ * Class SystemAttachment
+ * @package app\admin\controller\system
+ *
+ */
+class SystemStore extends AuthController
+{
+
+    /**
+     * 获取门店列表1
+     * @return mixed
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            [['page', 'd'], 1],
+            [['limit', 'd'], 15],
+            [['keywords', 's'], ''],
+            [['type', 'd'], 0],
+        ]);
+        $mer_id = $this->merId ?: '';
+        return $this->success(SystemStoreModel::lst(0, 0, $where['page'], $where['limit'], $where['keywords'], $where['type'], $mer_id));
+    }
+
+    /**
+     * 获取门店列表(不分页)
+     */
+    public function get_store_list()
+    {
+        $mer_id = $this->merId ?: '';
+        return $this->success(SystemStoreModel::where('mer_id', $mer_id)->where('is_del', 0)->where('is_show', 1)->select()->toArray());
+    }
+
+    /**
+     * 获取门店头部        
+     * @return mixed
+     */
+    public function get_header()
+    {
+        $count['show']['name'] = '显示中的提货点';
+        $count['hide']['name'] = '隐藏中的提货点';
+        $count['recycle']['name'] = '回收站的提货点';
+        $count['show']['num'] = SystemStoreModel::merSet($this->merId)->where('is_show', 1)->where('is_del', 0)->count('id');//显示中的门店
+        $count['hide']['num'] = SystemStoreModel::merSet($this->merId)->where('is_show', 0)->count('id');//隐藏的门店
+        $count['recycle']['num'] = SystemStoreModel::merSet($this->merId)->where('is_del', 1)->count('id');//删除的门店
+        return $this->success(compact('count'));
+    }
+
+    /*
+     * 门店设置
+     * */
+    public function get_info()
+    {
+        list($id) = Util::getMore([
+            [['id', 'd'], 0],
+        ], $this->request, true);
+        $info = SystemStoreModel::getStoreInfo($id);
+        return $this->success(compact('info'));
+    }
+
+    /*
+     * 位置选择
+     * */
+    public function select_address()
+    {
+        $key = SystemConfig::getConfigValue('tengxun_map_key');
+        if (!$key) return $this->fail('请前往设置->系统设置->物流配置 配置腾讯地图KEY');
+        return $this->success(compact('key'));
+    }
+
+    /**
+     * 设置单个门店是否显示
+     * @param string $is_show
+     * @param string $id
+     * @return json
+     */
+    public function set_show($is_show = '', $id = '')
+    {
+        ($is_show == '' || $id == '') && $this->fail('缺少参数');
+        $res = SystemStoreModel::where(['id' => $id])->update(['is_show' => (int)$is_show]);
+        if ($res) {
+            return $this->success($is_show == 1 ? '设置显示成功' : '设置隐藏成功');
+        } else {
+            return $this->fail($is_show == 1 ? '设置显示失败' : '设置隐藏失败');
+        }
+    }
+
+    /*
+     * 保存修改门店信息
+     * param int $id
+     * */
+    public function save($id = 0)
+    {
+        $data = Util::postMore([
+            ['name', ''],
+            ['introduction', ''],
+            ['image', ''],
+            ['phone', ''],
+            ['address', ''],
+            ['detailed_address', ''],
+            ['latlng', ''],
+            ['day_time', []],
+            ['pids', []]
+        ]);
+        $this->validate($data, \app\adminapi\validates\merchant\SystemStoreValidate::class, 'save');
+        if($this->merId){
+            $data['mer_id'] = $this->merId;
+        }
+        SystemStoreModel::beginTrans();
+        try {
+            $data['address'] = implode(',', $data['address']);
+            $data['latlng'] = explode(',', $data['latlng']);
+            if (!isset($data['latlng'][0]) || !isset($data['latlng'][1])) return $this->fail('请选择门店位置');
+            $data['latitude'] = $data['latlng'][0];
+            $data['longitude'] = $data['latlng'][1];
+            $data['day_time'] = implode(' - ', $data['day_time']);
+            unset($data['latlng']);
+            if ($data['image'] && strstr($data['image'], 'http') === false) {
+                $site_url = SystemConfig::getConfigValue('site_url');
+                $data['image'] = $site_url . $data['image'];
+            }
+            $pids = $data['pids'];
+            unset($data['pids']);
+            if ($id) {
+                SystemStoreModel::where('id', $id)->update($data);
+                SystemProductStore::saveStoreProduct($pids, $id);
+                SystemStoreModel::commitTrans();
+                return $this->success('修改成功');
+            } else {
+                $data['add_time'] = time();
+                $data['is_show'] = 1;
+                $res = SystemStoreModel::create($data);
+                SystemProductStore::saveStoreProduct($pids, $res->id);
+                SystemStoreModel::commitTrans();
+                return $this->success('保存成功', ['id' => $res->id]);
+            }
+        } catch (\Exception $e) {
+            SystemStoreModel::rollbackTrans();
+            return $this->fail($e->getMessage());
+        }
+    }
+
+    /**
+     * 删除恢复门店
+     * @param $id
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        if (!SystemStoreModel::be(['id' => $id])) return $this->fail('数据不存在');
+        if (SystemStoreModel::be(['id' => $id, 'is_del' => 1])) {
+            $data['is_del'] = 0;
+            if (!SystemStoreModel::edit($data, $id))
+                return $this->fail(SystemStoreModel::getErrorInfo('恢复失败,请稍候再试!'));
+            else
+                return $this->success('恢复门店成功!');
+        } else {
+            $data['is_del'] = 1;
+            if (!SystemStoreModel::edit($data, $id))
+                return $this->fail(SystemStoreModel::getErrorInfo('删除失败,请稍候再试!'));
+            else
+                return $this->success('删除门店成功!');
+        }
+    }
+}

+ 177 - 0
app/adminapi/controller/v1/merchant/SystemStoreStaff.php

@@ -0,0 +1,177 @@
+<?php
+
+namespace app\adminapi\controller\v1\merchant;
+
+use think\facade\Route as Url;
+use app\adminapi\controller\AuthController;
+use app\models\system\{
+    SystemStore, SystemStoreStaff as StoreStaffModel
+};
+use crmeb\services\{
+    FormBuilder as Form, UtilService as Util
+};
+
+class SystemStoreStaff extends AuthController
+{
+    /**
+     * 获取店员列表
+     * @return mixed
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            [['page', 'd'], 1],
+            [['limit', 'd'], 15],
+            [['store_id', 'd'], 0],
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        return $this->success(StoreStaffModel::getList($where));
+    }
+
+    /**
+     * 门店列表
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function store_list()
+    {
+        return $this->success(SystemStore::verificWhere($this->merId)->field(['id', 'name'])->select()->toArray());
+    }
+
+    /**
+     * 店员新增表单
+     * @return mixed
+     * @throws \FormBuilder\exception\FormBuilderException
+     */
+    public function create()
+    {
+        $field = [
+            Form::frameImageOne('image', '商城用户', Url::buildUrl('admin/system.User/list', array('fodder' => 'image')))->icon('ios-add')->width('50%')->height('500px')->setProps(['srcKey'=>'image']),
+            Form::hidden('uid', 0),
+            Form::hidden('avatar', ''),
+            Form::select('store_id', '所属提货点')->setOptions(function () {
+                $mer_id = $this->merId ?: '';
+                $list = SystemStore::dropList($mer_id);
+                $menus = [];
+                foreach ($list as $menu) {
+                    $menus[] = ['value' => $menu['id'], 'label' => $menu['name']];
+                }
+                return $menus;
+            })->filterable(1),
+            Form::input('staff_name', '核销员名称')->col(Form::col(24)),
+            Form::input('phone', '手机号码')->col(Form::col(24)),
+            Form::radio('verify_status', '核销开关', 1)->options([['value' => 1, 'label' => '开启'], ['value' => 0, 'label' => '关闭']]),
+            Form::radio('status', '状态', 1)->options([['value' => 1, 'label' => '开启'], ['value' => 0, 'label' => '关闭']])
+        ];
+        return $this->makePostForm('添加核销员', $field, Url::buildUrl('/merchant/store_staff/save/0')->suffix(false));
+    }
+
+    /**
+     * 店员修改表单
+     * @return mixed
+     * @throws \FormBuilder\exception\FormBuilderException
+     */
+    public function edit()
+    {
+        list($id) = Util::getMore([
+            [['id', 'd'], 0],
+        ], $this->request, true);
+        $info = StoreStaffModel::get($id);
+        if (!$info) return $this->fail('参数错误');
+        $field = [
+            Form::frameImageOne('image', '商城用户', Url::buildUrl('admin/system.User/list'), $info['avatar'])->icon('ios-add')->width('50%')->height('500px')->allowRemove(false),
+            Form::hidden('uid', $info['uid']),
+            Form::hidden('avatar', $info['avatar']),
+            Form::select('store_id', '所属提货点', (string)$info->getData('store_id'))->setOptions(function () {
+                $mer_id = $this->merId ?: '';
+                $list = SystemStore::dropList($mer_id);
+                $menus = [];
+                foreach ($list as $menu) {
+                    $menus[] = ['value' => $menu['id'], 'label' => $menu['name']];
+                }
+                return $menus;
+            })->filterable(1),
+            Form::input('staff_name', '核销员名称', $info['staff_name'])->col(Form::col(24)),
+            Form::input('phone', '手机号码', $info['phone'])->col(Form::col(24)),
+            Form::radio('verify_status', '核销开关', $info['verify_status'])->options([['value' => 1, 'label' => '开启'], ['value' => 0, 'label' => '关闭']]),
+            Form::radio('status', '状态', $info['status'])->options([['value' => 1, 'label' => '开启'], ['value' => 0, 'label' => '关闭']])
+        ];
+        return $this->makePostForm('修改核销员', $field, Url::buildUrl('/merchant/store_staff/save/' . $info['id'])->suffix(false));
+    }
+
+    /**
+     * 保存店员信息
+     */
+    public function save($id = 0)
+    {
+        $data = Util::postMore([
+            ['image',''],
+            ['uid', 0],
+            ['avatar', ''],
+            ['store_id', ''],
+            ['staff_name', ''],
+            ['phone', ''],
+            ['verify_status', 1],
+            ['status', 1],
+        ]);
+        if (!$id) {
+            if($data['image']=='') return $this->fail('请选择用户');
+            if (StoreStaffModel::where('uid', $data['uid'])->count()) return $this->fail('添加的核销员用户已存在!');
+            $data['uid'] = $data['image']['uid'];
+            $data['avatar'] = $data['image']['image'];
+        }
+        if ($data['uid'] == 0) return $this->fail('请选择用户');
+        if ($data['store_id'] == '') return $this->fail('请选择所属提货点');
+        $data['mer_id'] = $this->merId ?: '';
+        unset($data['image']);
+        if ($id) {
+            $res = StoreStaffModel::edit($data, $id);
+            if ($res) {
+                return $this->success('编辑成功');
+            } else {
+                return $this->fail('编辑失败');
+            }
+        } else {
+            $data['add_time'] = time();
+            $res = StoreStaffModel::create($data);
+            if ($res) {
+                return $this->success('核销员添加成功');
+            } else {
+                return $this->fail('核销员添加失败,请稍后再试');
+            }
+        }
+    }
+
+    /**
+     * 设置单个店员是否开启
+     * @param string $is_show
+     * @param string $id
+     * @return mixed
+     */
+    public function set_show($is_show = '', $id = '')
+    {
+        ($is_show == '' || $id == '') && $this->fail('缺少参数');
+        $res = StoreStaffModel::where(['id' => $id])->update(['status' => (int)$is_show]);
+        if ($res) {
+            return $this->success($is_show == 1 ? '开启成功' : '关闭成功');
+        } else {
+            return $this->fail($is_show == 1 ? '开启失败' : '关闭失败');
+        }
+    }
+
+    /**
+     * 删除店员
+     * @param $id
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        if (!StoreStaffModel::be(['id' => $id])) return $this->failed('数据不存在');
+        if (!StoreStaffModel::del($id))
+            return $this->fail(StoreStaffModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+}

+ 56 - 0
app/adminapi/controller/v1/merchant/SystemVerifyOrder.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace app\adminapi\controller\v1\merchant;
+
+use app\adminapi\controller\AuthController;
+use app\models\store\StoreOrder;
+use crmeb\services\UtilService as Util;
+
+class SystemVerifyOrder extends AuthController
+{
+    /**
+     * 获取核销订单列表
+     * return json
+     */
+    public function list()
+    {
+        $where = Util::getMore([
+            ['data', ''],
+            ['real_name', ''],
+            ['store_id', ''],
+            ['page', 1],
+            ['limit', 20],
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        return $this->success(StoreOrder::VerifyOrder($where));
+    }
+
+    /**
+     * 获取核销订单头部
+     * @return mixed
+     */
+    public function getVerifyBadge()
+    {
+        $where = Util::postMore([
+            ['real_name', ''],
+            ['is_del', 0],
+            ['data', ''],
+            ['store_id', ''],
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        return $this->success(StoreOrder::getVerifyBadge($where));
+    }
+
+    /**
+     * 订单列表推荐人详细
+     * @param $uid
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function order_spread_user($uid)
+    {
+        return $this->success(StoreOrder::order_spread_user($uid));
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/notification/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/notification

+ 1 - 0
app/adminapi/controller/v1/notification/sms/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/notification/sms

+ 62 - 0
app/adminapi/controller/v1/notification/sms/SmsAdmin.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace app\adminapi\controller\v1\notification\sms;
+
+use app\adminapi\controller\AuthController;
+use app\models\system\SystemConfig;
+use crmeb\services\{
+    HttpService, sms\Sms, UtilService, CacheService
+};
+
+/**
+ * 短信账号
+ * Class SmsAdmin
+ * @package app\adminapi\controller\v1\sms
+ */
+class SmsAdmin extends AuthController
+{
+
+    /**
+     * 发送验证码
+     * @return mixed
+     */
+    public function captcha()
+    {
+        if (!request()->isPost()) return $this->fail('发生失败');
+        $phone = request()->param('phone');
+        if (!trim($phone)) return $this->fail('请填写手机号');
+        $sms = new Sms('yunxin');
+        $res = json_decode(HttpService::getRequest($sms->getSmsUrl(), compact('phone')), true);
+        if (!isset($res['status']) && $res['status'] !== 200)
+            return $this->fail(isset($res['data']['message']) ? $res['data']['message'] : $res['msg']);
+        return $this->success(isset($res['data']['message']) ? $res['data']['message'] : $res['msg']);
+    }
+
+    /**
+     * 修改/注册短信平台账号
+     */
+    public function save()
+    {
+        list($account, $password, $phone, $code, $url, $sign) = UtilService::postMore([
+            ['account', ''],
+            ['password', ''],
+            ['phone', ''],
+            ['code', ''],
+            ['url', ''],
+            ['sign', ''],
+        ], null, true);
+        $signLen = mb_strlen(trim($sign));
+        if (!strlen(trim($account))) return $this->fail('请填写账号');
+        if (!strlen(trim($password))) return $this->fail('请填写密码');
+        if (!$signLen) return $this->fail('请填写短信签名');
+        if ($signLen > 8) return $this->fail('短信签名最长为8位');
+        if (!strlen(trim($code))) return $this->fail('请填写验证码');
+        if (!strlen(trim($url))) return $this->fail('请填写域名');
+        $sms = new Sms('yunxin');
+        $status = $sms->register($account, md5(trim($password)), $url, $phone, $code, $sign);
+        if ($status['status'] == 400) return $this->fail('短信平台:' . $status['msg']);
+        CacheService::clear();
+        SystemConfig::setConfigSmsInfo($account, $password);
+        return $this->success('短信平台:' . $status['msg']);
+    }
+}

+ 131 - 0
app/adminapi/controller/v1/notification/sms/SmsConfig.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace app\adminapi\controller\v1\notification\sms;
+
+use app\models\sms\SmsRecord;
+use app\models\system\SystemConfig;
+use crmeb\services\CacheService;
+use crmeb\services\sms\Sms;
+use crmeb\services\UtilService;
+use app\adminapi\controller\AuthController;
+use app\models\system\SystemConfig as ConfigModel;
+use EasyWeChat\Foundation\Config;
+use think\facade\Cache;
+
+/**
+ * 短信配置
+ * Class SmsConfig
+ * @package app\admin\controller\sms
+ */
+class SmsConfig extends AuthController
+{
+
+    /**
+     * @var Sms
+     */
+    protected $smsHandle;
+
+
+    /**
+     * 保存短信配置
+     * @return mixed
+     */
+    public function save_basics()
+    {
+        [$account, $token] = UtilService::postMore([
+            ['sms_account', ''],
+            ['sms_token', '']
+        ], $this->request, true);
+
+        $this->validate(['sms_account' => $account, 'sms_token' => $token], \app\adminapi\validates\notification\SmsConfigValidate::class);
+
+        $this->smsHandle = new Sms('yunxin', [
+            'sms_account' => $account,
+            'sms_token' => $token,
+            'site_url' => sys_config('site_url')
+        ]);
+
+        ConfigModel::edit(['value' => json_encode($account)], 'sms_account', 'menu_name');
+        ConfigModel::edit(['value' => json_encode($token)], 'sms_token', 'menu_name');
+
+        //添加公共短信模板
+        $templateList = $this->smsHandle->publictemp([]);
+        if ($templateList['status'] != 400) {
+            if ($templateList['data']['data']) {
+                foreach ($templateList['data']['data'] as $v) {
+                    if ($v['is_have'] == 0)
+                        $this->smsHandle->use($v['id'], $v['templateid']);
+                }
+            }
+            CacheService::redisHandler()->set('sms_account', $account);
+            return $this->success('登录成功');
+        } else {
+            return $this->fail('账号或密码错误');
+        }
+    }
+
+    /**
+     * 检测登录
+     * @return mixed
+     */
+    public function is_login()
+    {
+        $sms_info = CacheService::redisHandler()->get('sms_account');
+        if ($sms_info) {
+            return $this->success(['status' => true, 'info' => $sms_info]);
+        } else {
+            return $this->success(['status' => false]);
+        }
+    }
+
+    /**
+     * 退出
+     * @return mixed
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function logout()
+    {
+        $res = CacheService::redisHandler()->delete('sms_account');
+        if ($res)
+            return $this->success('退出成功');
+        else
+            return $this->fail('退出失败');
+    }
+
+    /**
+     * 短信发送记录
+     * @return mixed
+     */
+    public function record()
+    {
+        $where = UtilService::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['type', '']
+        ]);
+        return $this->success(SmsRecord::getRecordList($where));
+    }
+
+    /**
+     * @return mixed
+     */
+    public function data()
+    {
+        $this->smsHandle = new Sms('yunxin', [
+            'sms_account' => sys_config('sms_account'),
+            'sms_token' => sys_config('sms_token'),
+            'site_url' => sys_config('site_url')
+        ]);
+        $countInfo = $this->smsHandle->count();
+        if ($countInfo['status'] == 400) {
+            $info['number'] = 0;
+            $info['total_number'] = 0;
+        } else {
+            $info['number'] = $countInfo['data']['number'];
+            $info['total_number'] = $countInfo['data']['send_total'];
+        }
+        $info['record_number'] = SmsRecord::count();
+        $info['sms_account'] = sys_config('sms_account');
+        return $this->success($info);
+    }
+}

+ 79 - 0
app/adminapi/controller/v1/notification/sms/SmsPay.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace app\adminapi\controller\v1\notification\sms;
+
+use app\models\system\SystemConfig;
+use crmeb\exceptions\AdminException;
+use think\facade\Route;
+use app\adminapi\controller\AuthController;
+use crmeb\services\{
+    sms\Sms, FormBuilder, UtilService
+};
+
+/**
+ * 短信购买
+ * Class SmsPay
+ * @package app\admin\controller\sms
+ */
+class SmsPay extends AuthController
+{
+    /**
+     * @var Sms
+     */
+    protected $smsHandle;
+
+    public function initialize()
+    {
+        parent::initialize(); // TODO: Change the autogenerated stub
+        $this->smsHandle = new Sms('yunxin', [
+            'sms_account' => sys_config('sms_account'),
+            'sms_token' => sys_config('sms_token'),
+            'site_url' => sys_config('site_url')
+        ]);
+        if (!$this->smsHandle->isLogin()) {
+            throw new AdminException('请先填写短息配置');
+        }
+    }
+
+    /**
+     *  获取账号信息
+     */
+    public function number()
+    {
+        $countInfo = $this->smsHandle->count();
+        if ($countInfo['status'] == 400) return $this->fail($countInfo['msg']);
+        $info['account'] = SystemConfig::getConfigValue('sms_account');
+        $info['number'] = $countInfo['data']['number'];
+        $info['send_total'] = $countInfo['data']['send_total'];
+        return $this->success($info);
+    }
+
+    /**
+     *  获取支付套餐
+     */
+    public function price()
+    {
+        list($page, $limit) = UtilService::getMore([
+            ['page', 1],
+            ['limit', 20],
+        ], null, true);
+        $mealInfo = $this->smsHandle->meal($page, $limit);
+        if ($mealInfo['status'] == 400) return $this->fail($mealInfo['msg']);
+        return $this->success($mealInfo['data']);
+    }
+
+    /**
+     * 获取支付码
+     */
+    public function pay()
+    {
+        list($payType, $mealId, $price) = UtilService::postMore([
+            ['payType', 'weixin'],
+            ['mealId', 0],
+            ['price', 0],
+        ], null, true);
+        $payInfo = $this->smsHandle->pay($payType, $mealId, $price, $this->adminId);
+        if ($payInfo['status'] == 400) return $this->fail($payInfo['msg']);
+        return $this->success($payInfo['data']);
+    }
+}

+ 68 - 0
app/adminapi/controller/v1/notification/sms/SmsPublicTemp.php

@@ -0,0 +1,68 @@
+<?php
+
+namespace app\adminapi\controller\v1\notification\sms;
+
+use app\adminapi\controller\AuthController;
+use crmeb\exceptions\AdminException;
+use crmeb\services\sms\Sms;
+use crmeb\services\UtilService;
+
+/**
+ * 公共短信模板
+ * Class SmsPublicTemp
+ * @package app\admin\controller\sms
+ */
+class SmsPublicTemp extends AuthController
+{
+    /**
+     * @var Sms
+     */
+    protected $smsHandle;
+
+    public function initialize()
+    {
+        parent::initialize(); // TODO: Change the autogenerated stub
+        $this->smsHandle = new Sms('yunxin', [
+            'sms_account' => sys_config('sms_account'),
+            'sms_token' => sys_config('sms_token'),
+            'site_url' => sys_config('site_url')
+        ]);
+        if (!$this->smsHandle->isLogin()) {
+            throw new AdminException('请先填写短息配置');
+        }
+    }
+
+    /**
+     * 异步获取公共模板列表
+     */
+    public function index()
+    {
+        $where = UtilService::getMore([
+            ['is_have', ''],
+            ['page', 1],
+            ['limit', 20],
+        ]);
+        $templateList = $this->smsHandle->publictemp($where);
+        if ($templateList['status'] == 400) return $this->fail($templateList['msg']);
+        $arr = $templateList['data']['data'];
+        foreach ($arr as $key => $value) {
+            switch ($value['type']) {
+                case 1:
+                    $arr[$key]['type'] = '验证码';
+                    break;
+                case 2:
+                    $arr[$key]['type'] = '通知';
+                    break;
+                case 3:
+                    $arr[$key]['type'] = '推广';
+                    break;
+                default:
+                    $arr[$key]['type'] = '';
+                    break;
+            }
+        }
+        $templateList['data']['data'] = $arr;
+        return $this->success($templateList['data']);
+    }
+
+}

+ 107 - 0
app/adminapi/controller/v1/notification/sms/SmsTemplateApply.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace app\adminapi\controller\v1\notification\sms;
+
+use app\adminapi\controller\AuthController;
+use crmeb\exceptions\AdminException;
+use crmeb\services\{
+    FormBuilder, FormBuilder as Form, sms\Sms, UtilService
+};
+use think\facade\Route as Url;
+
+/**
+ * 短信模板申请
+ * Class SmsTemplateApply
+ * @package app\admin\controller\sms
+ */
+class SmsTemplateApply extends AuthController
+{
+    /**
+     * @var Sms
+     */
+    protected $smsHandle;
+
+    /**
+     * 构造函数 验证是否配置了短信
+     * @return mixed|void
+     */
+    public function initialize()
+    {
+        parent::initialize();
+        $this->smsHandle = new Sms('yunxin', [
+            'sms_account' => sys_config('sms_account'),
+            'sms_token' => sys_config('sms_token'),
+            'site_url' => sys_config('site_url')
+        ]);
+        if (!$this->smsHandle->isLogin()) {
+            throw new AdminException('请先填写短息配置');
+        }
+    }
+
+    /**
+     * 异步获取模板列表
+     */
+    public function index()
+    {
+        $where = UtilService::getMore([
+            ['status', ''],
+            ['title', ''],
+            ['page', 1],
+            ['limit', 20],
+        ]);
+        $templateList = $this->smsHandle->template($where);
+        if ($templateList['status'] == 400) return $this->fail($templateList['msg']);
+        $arr = $templateList['data']['data'];
+        foreach ($arr as $key => $value) {
+            switch ($value['type']) {
+                case 1:
+                    $arr[$key]['type'] = '验证码';
+                    break;
+                case 2:
+                    $arr[$key]['type'] = '通知';
+                    break;
+                case 3:
+                    $arr[$key]['type'] = '推广';
+                    break;
+                default:
+                    $arr[$key]['type'] = '';
+                    break;
+            }
+        }
+        $templateList['data']['data'] = $arr;
+        return $this->success($templateList['data']);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return string
+     * @throws \FormBuilder\exception\FormBuilderException
+     */
+    public function create()
+    {
+        $field = [
+            FormBuilder::input('title', '模板名称'),
+            FormBuilder::input('content', '模板内容')->type('textarea'),
+            FormBuilder::radio('type', '模板类型', 1)->options([['label' => '验证码', 'value' => 1], ['label' => '通知', 'value' => 2], ['label' => '推广', 'value' => 3]])
+        ];
+        return $this->makePostForm('申请短信模板', $field, Url::buildUrl('/notify/sms/temp'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     */
+    public function save()
+    {
+        $data = UtilService::postMore([
+            ['title', ''],
+            ['content', ''],
+            ['type', 0]
+        ]);
+        if (!strlen(trim($data['title']))) return $this->fail('请输入模板名称');
+        if (!strlen(trim($data['content']))) return $this->fail('请输入模板内容');
+        $applyStatus = $this->smsHandle->apply($data['title'], $data['content'], $data['type']);
+        if ($applyStatus['status'] == 400) return $this->fail($applyStatus['msg']);
+        return $this->success('申请成功');
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/order/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/order

+ 763 - 0
app/adminapi/controller/v1/order/StoreOrder.php

@@ -0,0 +1,763 @@
+<?php
+
+namespace app\adminapi\controller\v1\order;
+
+
+use app\adminapi\controller\AuthController;
+use app\adminapi\validates\order\StoreOrderValidate;
+use Exception;
+use app\models\system\{Express, SystemStore};
+use crmeb\traits\CurdControllerTrait;
+use FormBuilder\exception\FormBuilderException;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\response\Json;
+use app\models\user\{UserBill, User};
+use crmeb\repositories\OrderRepository;
+use app\models\store\{StoreOrder as StoreOrderModel, StoreOrderStatus, StorePink};
+use crmeb\services\{CacheService,
+    ExpressService,
+    MiniProgramService,
+    SystemConfigService,
+    WechatService,
+    UtilService,
+    FormBuilder as Form};
+use think\facade\Route as Url;
+
+class StoreOrder extends AuthController
+{
+
+    use CurdControllerTrait;
+
+    protected function initialize()
+    {
+        parent::initialize(); // TODO: Change the autogenerated stub
+        $this->bindModel = StoreOrderModel::class;
+    }
+
+    /**
+     * 获取订单类型数量
+     * @return mixed
+     * @throws Exception
+     */
+    public function chart()
+    {
+        $where = UtilService::getMore([
+            ['data', ''],
+        ], $this->request);
+        $where['mer_id'] = $this->merId;
+        return $this->success(StoreOrderModel::orderCount($where));
+    }
+
+    /**
+     * 获取订单列表
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function lst()
+    {
+        $where = UtilService::getMore([
+            ['status', ''],
+            ['real_name', ''],
+            ['is_del', 0],
+            ['data', ''],
+            ['type', ''],
+            ['pay_type', ''],
+            ['order', ''],
+            ['page', 1],
+            ['limit', 10],
+        ], $this->request);
+        $where['mer_id'] = $this->merId;
+        return $this->success(StoreOrderModel::getAdminOrderList($where));
+    }
+
+    /**
+     * 核销码核销
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws DbException
+     * @throws Exception
+     */
+    public function write_order()
+    {
+        [$code, $confirm] = UtilService::getMore([
+            ['code', ''],
+            ['confirm', 0]
+        ], $this->request, true);
+        if (!$code) return $this->fail('Lack of write-off code');
+        StoreOrderModel::beginTrans();
+        $orderInfo = StoreOrderModel::merSet($this->merId)->where('verify_code', $code)->where('paid', 1)->field(['status', 'uid', 'order_id'])->where('refund_status', 0)->find();
+        if (!$orderInfo) return $this->fail('Write off order does not exist');
+        if ($orderInfo->status > 0) return $this->fail('Order written off');
+        if ($orderInfo->combination_id && $orderInfo->pink_id) {
+            $res = StorePink::where('id', $orderInfo->pink_id)->where('status', '<>', 2)->count();
+            if ($res) return $this->fail('Failed to write off the group order');
+        }
+        if ($confirm == 0) {
+            $orderInfo['nickname'] = User::where(['uid' => $orderInfo['uid']])->value('nickname');
+            return $this->success($orderInfo->toArray());
+        }
+        $orderInfo->status = 2;
+        if ($orderInfo->save()) {
+            StoreOrderModel::commitTrans();
+            return $this->success('Write off successfully');
+        } else {
+            StoreOrderModel::rollbackTrans();
+            return $this->fail('Write off failure');
+        }
+    }
+
+    /**
+     * 修改支付金额等
+     * @param $id
+     * @return mixed|Json|void
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function edit($id)
+    {
+        if (!$id) return $this->fail('Data does not exist!');
+        $product = StoreOrderModel::merSet($this->merId)->where('id', $id)->find();
+        if (!$product) return $this->fail('Data does not exist!');
+        $f = [];
+        $f[] = Form::input('order_id', '订单编号', $product->getData('order_id'))->disabled(1);
+        $f[] = Form::number('total_price', '商品总价', $product->getData('total_price'))->min(0);
+        $f[] = Form::number('total_postage', '原始邮费', $product->getData('total_postage'))->min(0);
+        $f[] = Form::number('pay_price', '实际支付金额', $product->getData('pay_price'))->min(0);
+        $f[] = Form::number('pay_postage', '实际支付邮费', $product->getData('pay_postage'));
+        $f[] = Form::number('gain_integral', '赠送积分', $product->getData('gain_integral'));
+        return $this->makePostForm('修改订单', $f, Url::buildUrl('/order/update/' . $id), 'PUT');
+    }
+
+    /**
+     * 修改订单
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function update($id)
+    {
+        if (!$id) return $this->fail('Missing order ID');
+        $product = StoreOrderModel::merSet($this->merId)->where('id', $id)->find();
+        if (!$product) return $this->fail('Data does not exist!');
+        $data = UtilService::postMore([
+            ['order_id', ''],
+            ['total_price', 0],
+            ['total_postage', 0],
+            ['pay_price', 0],
+            ['pay_postage', 0],
+            ['gain_integral', 0],
+        ], $this->request);
+
+        $this->validate($data, StoreOrderValidate::class);
+
+        if ($data['total_price'] < 0) return $this->fail('Please enter the total price');
+        if ($data['pay_price'] < 0) return $this->fail('Please enter the actual payment amount');
+
+        StoreOrderModel::beginTrans();
+        $data['order_id'] = StoreOrderModel::changeOrderId($data['order_id']);
+        $res = StoreOrderModel::edit($data, $id);
+        $res = $res && StoreOrderStatus::status($id, 'order_edit', '修改商品总价为:' . $data['total_price'] . ' 实际支付金额' . $data['pay_price']);
+        if ($res) {
+            event('StoreProductOrderEditAfter', [$data, $id]);
+            StoreOrderModel::commitTrans();
+            return $this->success('Modified success');
+        } else {
+            StoreOrderModel::rollbackTrans();
+            return $this->fail('Modification failed');
+        }
+    }
+
+    /**
+     * 获取快递公司
+     * @return mixed
+     */
+    public function express()
+    {
+        $list = Express::where('is_show', 1)->order('sort desc')->column('name', 'id');
+        $data = [];
+        foreach ($list as $key => $value) {
+            $data[] = ['id' => $key, 'value' => $value];
+        }
+        return $this->success($data);
+    }
+
+    /**
+     * 批量删除用户已经删除的订单
+     * @return mixed
+     * @throws Exception
+     */
+    public function del_orders()
+    {
+        [$ids] = UtilService::postMore([
+            ['ids', []],
+        ], $this->request, true);
+        if (!count($ids)) return $this->fail('请选择需要删除的订单');
+        if (StoreOrderModel::merSet($this->merId)->where('is_del', 0)->whereIn('id', $ids)->count())
+            return $this->fail('您选择的的订单存在用户未删除的订单');
+        if (StoreOrderModel::merSet($this->merId)->whereIn('id', $ids)->update(['is_system_del' => 1]))
+            return $this->success('SUCCESS');
+        else
+            return $this->fail('ERROR');
+    }
+
+    //批量设置标签
+    public function setLevel()
+    {
+        $data = UtilService::postMore([
+            ['ids', []],
+            ['level', '']
+        ], $this->request);
+        if (!count($data['ids'])) return $this->fail('请选择需要设置的订单');
+        if (StoreOrderModel::merSet($this->merId)->whereIn('id', $data['ids'])->update(['level' => $data['level']]))
+            return $this->success('SUCCESS');
+        else
+            return $this->fail('ERROR');
+    }
+
+    /**
+     * 删除订单
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function del($id)
+    {
+        if (!$id || !($orderInfo = StoreOrderModel::merSet($this->merId)->where('id', $id)->find()))
+            return $this->fail('订单不存在');
+        if (!$orderInfo->is_del)
+            return $this->fail('订单用户未删除无法删除');
+        $orderInfo->is_system_del = 1;
+        if ($orderInfo->save())
+            return $this->success('SUCCESS');
+        else
+            return $this->fail('ERROR');
+    }
+
+    /**
+     * 订单发送货
+     * @param int $id 订单id
+     * @return mixed
+     * @throws Exception
+     */
+    public function update_delivery($id)
+    {
+        if (!$id) return $this->fail('订单不存在');
+        $product = StoreOrderModel::merSet($this->merId)->where('id', $id)->find();
+        if (!$product) return $this->fail('订单不存在');
+        $data = UtilService::postMore([
+            ['type', 1],
+            ['delivery_name', ''],
+            ['delivery_id', ''],
+            ['sh_delivery_name', ''],
+            ['sh_delivery_id', ''],
+        ], $this->request);
+        StoreOrderModel::beginTrans();
+        $type = (int)$data['type'];
+        unset($data['type']);
+        switch ($type) {
+            case 1:
+                //发货
+                $data['delivery_type'] = 'express';
+                if (!$data['delivery_name']) return $this->fail('请选择快递公司');
+                if (!$data['delivery_id']) return $this->fail('请输入快递单号');
+                $data['status'] = 1;
+                StoreOrderModel::edit($data, $id);
+                event('StoreProductOrderDeliveryGoodsAfter', [$data, $id]);
+                StoreOrderStatus::setStatus($id, 'delivery_goods', '已发货 快递公司:' . $data['delivery_name'] . ' 快递单号:' . $data['delivery_id']);
+                break;
+            case 2:
+                $data['delivery_type'] = 'send';
+                $data['delivery_name'] = $data['sh_delivery_name'];
+                $data['delivery_id'] = $data['sh_delivery_id'];
+                unset($data['sh_delivery_name'], $data['sh_delivery_id']);
+                if (!$data['delivery_name']) return $this->fail('请输入送货人姓名');
+                if (!$data['delivery_id']) return $this->fail('请输入送货人电话号码');
+                if (!preg_match("/^1[3456789]{1}\d{9}$/", $data['delivery_id'])) return $this->fail('请输入正确的送货人电话号码');
+                $data['status'] = 1;
+                StoreOrderModel::edit($data, $id);
+                event('StoreProductOrderDeliveryAfter', [$data, $id]);
+                StoreOrderStatus::setStatus($id, 'delivery', '已配送 发货人:' . $data['delivery_name'] . ' 发货人电话:' . $data['delivery_id']);
+                break;
+            case 3:
+                $data['delivery_type'] = 'fictitious';
+                $data['status'] = 1;
+                unset($data['sh_delivery_name'], $data['sh_delivery_id'], $data['delivery_name'], $data['delivery_id']);
+                StoreOrderModel::edit($data, $id);
+                event('StoreProductOrderDeliveryAfter', [$data, $id]);
+                StoreOrderStatus::setStatus($id, 'delivery_fictitious', '已虚拟发货');
+                break;
+            default:
+                return $this->fail('暂时不支持其他发货类型');
+                break;
+        }
+        //短信发送
+        event('ShortMssageSend', [StoreOrderModel::where('id', $id)->value('order_id'), 'Deliver']);
+        StoreOrderModel::commitTrans();
+        return $this->success('SUCCESS');
+    }
+
+
+    /**
+     * 确认收货
+     * @param int $id 订单id
+     * @return mixed
+     * @throws Exception
+     */
+    public function take_delivery($id)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        $order = StoreOrderModel::merSet($this->merId)->where('id', $id)->find();
+        if (!$order) return $this->fail('订单不存在');
+        if ($order['status'] == 2)
+            return $this->fail('不能重复收货!');
+        if ($order['paid'] == 1 && $order['status'] == 1)
+            $data['status'] = 2;
+        else if ($order['pay_type'] == 'offline')
+            $data['status'] = 2;
+        else
+            return $this->fail('请先发货或者送货!');
+        StoreOrderModel::beginTrans();
+        if (!StoreOrderModel::edit($data, $id)) {
+            StoreOrderModel::rollbackTrans();
+            return $this->fail('收货失败,请稍候再试!');
+        } else {
+            OrderRepository::storeProductOrderTakeDeliveryAdmin($order);
+            StoreOrderStatus::setStatus($id, 'take_delivery', '已收货');
+            StoreOrderModel::commitTrans();
+            //发送短信
+            event('ShortMssageSend', [$order['order_id'], 'Receiving']);
+            return $this->success('收货成功');
+        }
+    }
+
+    /**
+     * 退款表单生成
+     * @param int $id 订单id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws FormBuilderException
+     * @throws ModelNotFoundException
+     */
+    public function refund($id)
+    {
+        if (!$id) return $this->fail('Data does not exist!');
+        $order = StoreOrderModel::merSet($this->merId)->where('id', $id)->find();
+        if (!$order) return $this->fail('未查到订单');
+        if (!$order['paid']) return $this->fail('未支付无法退款');
+        if ($order['pay_price'] <= $order['refund_price']) return $this->fail('订单已退款');
+        $f[] = Form::input('order_id', '退款单号', $order->getData('order_id'))->disabled(1);
+        $f[] = Form::number('refund_price', '退款金额', $order->getData('pay_price'))->precision(2)->min(0.01)->required('请输入退款金额');
+        $f[] = Form::radio('type', '状态', 1)->options([['label' => '直接退款', 'value' => 1], ['label' => '退款后,返回原状态', 'value' => 2]]);
+        return $this->makePostForm('退款处理', $f, Url::buildUrl('/order/refund/' . $id), 'PUT');
+    }
+
+    /**
+     * 订单退款
+     * @param int $id 订单id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws DbException
+     * @throws Exception
+     */
+    public function update_refund($id)
+    {
+        $data = UtilService::postMore([['refund_price', 0], ['type', 1]], $this->request);
+        if (!$id) return $this->fail('Data does not exist!');
+        $order = StoreOrderModel::merSet($this->merId)->where('id', $id)->find();
+        if (!$order) return $this->fail('未查到订单');
+        if ($order['pay_price'] == $order['refund_price']) return $this->fail('已退完支付金额!不能再退款了');
+        if (!$data['refund_price']) return $this->fail('请输入退款金额');
+        $refund_price = $data['refund_price'];
+        $data['refund_price'] = bcadd($data['refund_price'], $order['refund_price'], 2);
+        $bj = bccomp((float)$order['pay_price'], (float)$data['refund_price'], 2);
+        if ($bj < 0) return $this->fail('退款金额大于支付金额,请修改退款金额');
+
+        if ($data['type'] == 1)
+            $data['refund_status'] = 2;
+        else if ($data['type'] == 2)
+            $data['refund_status'] = 0;
+        $type = $data['type'];
+        unset($data['type']);
+        $refund_data['pay_price'] = $order['pay_price'];
+        $refund_data['refund_price'] = $refund_price;
+        //退款处理
+        StoreOrderModel::beginTrans();
+        $res = true;
+        switch ($order['pay_type']) {
+            case 'weixin':
+                if ($order['is_channel'] == 1)
+                    MiniProgramService::payOrderRefund($order['order_id'], $refund_data, $this->merId);//小程序
+                else
+                    WechatService::payOrderRefund($order['order_id'], $refund_data);//公众号
+                break;
+            case 'yue':
+                $usermoney = User::where('uid', $order['uid'])->value('now_money');
+                $res = User::bcInc($order['uid'], 'now_money', $refund_price, 'uid') &&
+                    UserBill::income(
+                        '商品退款',
+                        $order['uid'],
+                        'now_money',
+                        'pay_product_refund',
+                        $refund_price, $order['id'],
+                        bcadd($usermoney, $refund_price, 2),
+                        '订单退款到余额' . floatval($refund_price) . '元',
+                        1,
+                        $this->merId
+                    );
+                OrderRepository::storeOrderYueRefund($order, $refund_data);
+                break;
+        }
+        if (!$res) return $this->fail('余额退款失败');
+        //修改订单退款状态
+        $res = true;
+        if (StoreOrderModel::edit($data, $id)) {
+            $data['type'] = $type;
+            if ($data['type'] == 1) $res = StorePink::setRefundPink($id);
+            if (!$res) return $this->fail('拼团修改失败');
+            OrderRepository::storeProductOrderRefundY($data, $id);
+            StoreOrderStatus::setStatus($id, 'refund_price', '退款给用户' . $refund_price . '元');
+            //退佣金
+            $brokerage_list = UserBill::where('category', 'now_money')
+                ->where('type', 'brokerage')
+                ->where('link_id', $id)
+                ->where('pm', 1)
+                ->select();
+
+            if ($brokerage_list) {
+                $brokerage_list = $brokerage_list->toArray();
+                foreach ($brokerage_list as $item) {
+                    $usermoney = User::where('uid', $item['uid'])->value('brokerage_price');
+                    if ($item['number'] > $usermoney)
+                        $item['number'] = $usermoney;
+                    User::bcDec($item['uid'], 'brokerage_price', $item['number'], 'uid');
+                    UserBill::expend('退款退佣金', $item['uid'], 'now_money', 'brokerage', $item['number'], $id, bcsub($usermoney, $item['number'], 2), '订单退款扣除佣金' . floatval($item['number']) . '元', 1, $this->merId);
+                }
+            }
+            //退款扣除用户积分
+            $bill_integral = UserBill::where('category', 'integral')
+                ->where('type', 'gain')
+                ->where('link_id', $id)
+                ->where('pm', 1)
+                ->find();
+            if ($bill_integral) {
+                $bill_integral = $bill_integral->toArray();
+                //用户积分
+                $user_integral = User::where('uid', $bill_integral['uid'])->value('integral');
+                if ($bill_integral['number'] > $user_integral)
+                    $bill_integral['number'] = $user_integral;
+                User::bcDec($bill_integral['uid'], 'integral', $bill_integral['number'], 'uid');
+                UserBill::expend('退款扣除积分', $bill_integral['uid'], 'integral', 'gain', $bill_integral['number'], $id, bcsub($user_integral, $bill_integral['number'], 2), '订单退款扣除积分' . floatval($bill_integral['number']) . '积分', 1, $this->merId);
+            }
+            StoreOrderModel::commitTrans();
+            return $this->success('退款成功');
+        } else {
+            StoreOrderModel::rollbackTrans();
+            StoreOrderStatus::setStatus($id, 'refund_price', '退款给用户' . $refund_price . '元失败');
+            return $this->fail('退款失败');
+        }
+    }
+
+    /**
+     * 订单详情
+     * @param int $id 订单id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function order_info($id)
+    {
+        if (!$id || !($orderInfo = StoreOrderModel::merSet($this->merId)->where('id', $id)->find()))
+            return $this->fail('订单不存在');
+        $userInfo = User::merSet($this->merId)->where('uid', $orderInfo['uid'])->find();
+        if (!$userInfo) return $this->fail('用户信息不存在');
+        $userInfo = $userInfo->hidden(['pwd', 'add_ip', 'last_ip', 'login_type']);
+        $userInfo['spread_name'] = '';
+        if ($userInfo['spread_uid'])
+            $userInfo['spread_name'] = User::where('uid', $userInfo['spread_uid'])->value('nickname');
+        $orderInfo = StoreOrderModel::tidyOrder($orderInfo->toArray(), false, false, $this->merId);
+        if ($orderInfo['store_id'] && $orderInfo['shipping_type'] == 2)
+            $orderInfo['_store_name'] = SystemStore::where('id', $orderInfo['store_id'])->value('name');
+        else
+            $orderInfo['_store_name'] = '';
+        $userInfo = $userInfo->toArray();
+        return $this->success(compact('orderInfo', 'userInfo'));
+    }
+
+    /**
+     * 查询物流信息
+     * @param int $id 订单id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function get_express($id)
+    {
+        if (!$id || !($orderInfo = StoreOrderModel::merSet($this->merId)->where('id', $id)->find()))
+            return $this->fail('订单不存在');
+        if ($orderInfo['delivery_type'] != 'express' || !$orderInfo['delivery_id'])
+            return $this->fail('该订单不存在快递单号');
+
+        $cacheName = $orderInfo['order_id'] . $orderInfo['delivery_id'];
+        if (!$result = CacheService::get($cacheName, null)) {
+            $result = ExpressService::query($orderInfo['delivery_id']);
+            if (is_array($result) &&
+                isset($result['result']) &&
+                isset($result['result']['deliverystatus']) &&
+                $result['result']['deliverystatus'] >= 3)
+                $cacheTime = 0;
+            else
+                $cacheTime = 1800;
+            CacheService::set($cacheName, $result, $cacheTime);
+        }
+        $data['delivery_name'] = $orderInfo['delivery_name'];
+        $data['delivery_id'] = $orderInfo['delivery_id'];
+        $data['result'] = $result['result']['list'] ?? [];
+        return $this->success($data);
+    }
+
+
+    /**
+     * 获取修改配送信息表单结构
+     * @param int $id 订单id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function distribution($id)
+    {
+        if (!$id || !($orderInfo = StoreOrderModel::merSet($this->merId)->where('id', $id)->find()))
+            return $this->fail('订单不存在');
+
+        $f[] = Form::input('order_id', '订单号', $orderInfo->getData('order_id'))->disabled(1);
+        switch ($orderInfo['delivery_type']) {
+            case 'send':
+                $f[] = Form::input('delivery_name', '送货人姓名', $orderInfo->getData('delivery_name'))->required('请输入送货人姓名');
+                $f[] = Form::input('delivery_id', '送货人电话', $orderInfo->getData('delivery_id'))->required('请输入送货人电话');
+                break;
+            case 'express':
+                $f[] = Form::select('delivery_name', '快递公司', $orderInfo->getData('delivery_name'))->setOptions(function () {
+                    $list = Express::where('is_show', 1)->column('name', 'id');
+                    $menus = [];
+                    foreach ($list as $k => $v) {
+                        $menus[] = ['value' => $v, 'label' => $v];
+                    }
+                    return $menus;
+                })->required('请选择快递公司');
+                $f[] = Form::input('delivery_id', '快递单号', $orderInfo->getData('delivery_id'))->required('请填写快递单号');
+                break;
+        }
+        return $this->makePostForm('配送信息', $f, Url::buildUrl('/order/distribution/' . $id), 'PUT');
+    }
+
+    /**
+     * 修改配送信息
+     * @param int $id 订单id
+     * @return mixed
+     * @throws Exception
+     */
+    public function update_distribution($id)
+    {
+        $data = UtilService::postMore([['delivery_name', ''], ['delivery_id', '']], $this->request);
+        if (!$id) return $this->fail('Data does not exist!');
+        $order = StoreOrderModel::merSet($this->merId)->where('id', $id)->find();
+        if (!$order) return $this->fail('数据不存在!');
+        switch ($order['delivery_type']) {
+            case 'send':
+                if (!$data['delivery_name']) return $this->fail('请输入送货人姓名');
+                if (!$data['delivery_id']) return $this->fail('请输入送货人电话号码');
+                if (!preg_match("/^1[3456789]{1}\d{9}$/", $data['delivery_id'])) return $this->fail('请输入正确的送货人电话号码');
+                break;
+            case 'express':
+                if (!$data['delivery_name']) return $this->fail('请选择快递公司');
+                if (!$data['delivery_id']) return $this->fail('请输入快递单号');
+                break;
+            default:
+                return $this->fail('未发货,请先发货再修改配送信息');
+                break;
+        }
+        StoreOrderModel::edit($data, $id);
+        event('StoreProductOrderDistributionAfter', [$data, $id]);
+        StoreOrderStatus::setStatus($id, 'distribution', '修改发货信息为' . $data['delivery_name'] . '号' . $data['delivery_id']);
+        return $this->success('Modified success');
+    }
+
+    /**
+     * 不退款表单结构
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function no_refund($id)
+    {
+        if (!$id) return $this->fail('Data does not exist!');
+        $order = StoreOrderModel::merSet($this->merId)->where('id', $id)->find();
+        if (!$order) return $this->fail('Data does not exist!');
+        $f[] = Form::input('order_id', '不退款单号', $order->getData('order_id'))->disabled(1);
+        $f[] = Form::input('refund_reason', '不退款原因')->type('textarea')->required('请填写不退款原因');
+        return $this->makePostForm('不退款原因', $f, Url::buildUrl('order/no_refund/' . $id)->suffix(false), 'PUT');
+    }
+
+    /**
+     * 订单不退款
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function update_un_refund($id)
+    {
+        if (!$id || !($orderInfo = StoreOrderModel::merSet($this->merId)->where('id', $id)->find()))
+            return $this->fail('订单不存在');
+        [$refund_reason] = UtilService::postMore([['refund_reason', '']], $this->request, true);
+        if (!$refund_reason) return $this->fail('请输入不退款原因');
+        StoreOrderModel::edit(['refund_reason' => $refund_reason, 'refund_status' => 0], $id);
+        event('StoreProductOrderRefundNAfter', [$refund_reason, $id]);
+        StoreOrderStatus::setStatus($id, 'refund_n', '不退款原因:' . $refund_reason);
+        return $this->success('Modified success');
+    }
+
+    /**
+     * 线下支付
+     * @param int $id 订单id
+     * @return mixed
+     */
+    public function pay_offline($id)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        $res = StoreOrderModel::updateOffline($id, $this->merId);
+        if ($res) {
+            event('StoreProductOrderOffline', [$id]);
+            StoreOrderStatus::setStatus($id, 'offline', '线下付款');
+            return $this->success('Modified success');
+        } else {
+            return $this->fail(StoreOrderModel::getErrorInfo('Modification failed'));
+        }
+    }
+
+    /**
+     * 退积分表单获取
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function refund_integral($id)
+    {
+        if (!$id || !($orderInfo = StoreOrderModel::merSet($this->merId)->where('id', $id)->find()))
+            return $this->fail('订单不存在');
+        if ($orderInfo->use_integral < 0 || $orderInfo->use_integral == $orderInfo->back_integral)
+            return $this->fail('积分已退或者积分为零无法再退');
+        if (!$orderInfo->paid)
+            return $this->fail('未支付无法退积分');
+        $f[] = Form::input('order_id', '退款单号', $orderInfo->getData('order_id'))->disabled(1);
+        $f[] = Form::number('use_integral', '使用的积分', (float)$orderInfo->getData('use_integral'))->min(0)->disabled(1);
+        $f[] = Form::number('use_integrals', '已退积分', (float)$orderInfo->getData('back_integral'))->min(0)->disabled(1);
+        $f[] = Form::number('back_integral', '可退积分', (float)bcsub($orderInfo->getData('use_integral'), $orderInfo->getData('use_integral')))->min(0)->required('请输入可退积分');
+        return $this->makePostForm('退积分', $f, Url::buildUrl('/order/refund_integral/' . $id), 'PUT');
+    }
+
+    /**
+     * 退积分保存
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function update_refund_integral($id)
+    {
+        [$back_integral] = UtilService::postMore([['back_integral', 0]], $this->request, true);
+        if (!$id || !($orderInfo = StoreOrderModel::merSet($this->merId)->where('id', $id)->find()))
+            return $this->fail('订单不存在');
+        if ($back_integral <= 0)
+            return $this->fail('请输入积分');
+        if ($orderInfo['use_integral'] == $orderInfo['back_integral'])
+            return $this->fail('已退完积分!不能再积分了');
+
+        $data['back_integral'] = bcadd($back_integral, $orderInfo['back_integral'], 2);
+        $bj = bccomp((float)$orderInfo['use_integral'], (float)$data['back_integral'], 2);
+        if ($bj < 0) return $this->fail('退积分大于支付积分,请修改退积分');
+        //积分退款处理
+        StoreOrderModel::beginTrans();
+        $integral = User::where('uid', $orderInfo['uid'])->value('integral');
+        $res1 = User::bcInc($orderInfo['uid'], 'integral', $back_integral, 'uid');
+        $res2 = UserBill::income('商品退积分', $orderInfo['uid'], 'integral', 'pay_product_integral_back', $back_integral, $orderInfo['id'], bcadd($integral, $back_integral, 2), '订单退积分' . floatval($back_integral) . '积分到用户积分', 1, $this->merId);
+        event('StoreOrderIntegralBack', [$orderInfo, $back_integral]);
+        OrderRepository::storeOrderIntegralBack($orderInfo, $back_integral);
+        if ($res1 && $res2) {
+            StoreOrderModel::edit($data, $id);
+            StoreOrderStatus::setStatus($id, 'integral_back', '商品退积分:' . $data['back_integral']);
+            StoreOrderModel::commitTrans();
+            return $this->success('退积分成功');
+        } else {
+            StoreOrderModel::rollbackTrans();
+            return $this->fail('退积分失败');
+        }
+    }
+
+    /**
+     * 修改备注
+     * @param $id
+     * @return mixed
+     * @throws Exception
+     */
+    public function remark($id)
+    {
+        $data = UtilService::postMore([['remark', '']], $this->request);
+        if (!$data['remark'])
+            return $this->fail('请输入要备注的内容');
+        if (!$id)
+            return $this->fail('缺少参数');
+        if (!StoreOrderModel::merSet($this->merId)->where('id', $id)->find()) {
+            return $this->fail('找不到订单');
+        }
+        $res = $this->model_save($id, $data);
+        if ($res) {
+            return $this->success('备注成功');
+        } else
+            return $this->fail($this->getErrorInfo());
+    }
+
+    /**
+     * 获取订单状态列表并分页
+     * @param $id
+     * @return mixed
+     * @throws Exception
+     */
+    public function status($id)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        if (!StoreOrderModel::merSet($this->merId)->where('id', $id)->find()) {
+            return $this->fail('找不到订单');
+        }
+        [$page, $limit] = UtilService::getMore([['page', 1], ['limit', 10]], $this->request, true);
+        return $this->success(StoreOrderStatus::getOrderList($id, $page, $limit));
+    }
+
+    /**
+     * 获取商品下的门店
+     */
+
+}

+ 1 - 0
app/adminapi/controller/v1/product/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/product

+ 928 - 0
app/adminapi/controller/v1/product/CopyTaobao.php

@@ -0,0 +1,928 @@
+<?php
+/**
+ * Project: 快速复制 淘宝、天猫、1688、京东 商品到CRMEB系统
+ * Author: 有一片天 <810806442@qq.com>  微信:szktor
+ * Date: 2019-04-25
+ */
+
+namespace app\adminapi\controller\v1\product;
+
+use app\adminapi\controller\AuthController;
+use app\models\store\{StoreProduct as ProductModel,
+    StoreDescription,
+    StoreProductAttr,
+    StoreProductAttrValue,
+    StoreProductCate};
+use crmeb\services\{HttpService, UploadService, UtilService};
+use think\exception\PDOException;
+use app\models\system\SystemAttachment;
+use app\models\system\SystemAttachmentCategory;
+
+
+
+/**
+ * 商品管理
+ * Class StoreProduct
+ * @package app\admin\controller\store
+ */
+class CopyTaobao extends AuthController
+{
+
+//    use CurdControllerTrait;
+
+    protected $bindModel = ProductModel::class;
+    //错误信息
+    protected $errorInfo = true;
+    //商品默认字段
+    protected $productInfo = [
+        'cate_id' => '',
+        'store_name' => '',
+        'store_info' => '',
+        'unit_name' => '件',
+        'price' => 0,
+        'keyword' => '',
+        'ficti' => 0,
+        'ot_price' => 0,
+        'give_integral' => 0,
+        'postage' => 0,
+        'cost' => 0,
+        'image' => '',
+        'slider_image' => '',
+        'add_time' => 0,
+        'stock' => 0,
+        'description' => '',
+        'soure_link' => '',
+        'temp_id' => 0
+    ];
+    //抓取网站主域名
+    protected $grabName = [
+        'taobao',
+        '1688',
+        'tmall',
+        'jd'
+    ];
+    //远程下载附件图片分类名称
+    protected $AttachmentCategoryName = '远程下载';
+
+    //cookie 采集前请配置自己的 cookie,获取方式浏览器登录平台,F12或查看元素 network->headers 查看Request Headers 复制cookie 到下面变量中
+    protected $webcookie = [
+        //淘宝
+        'taobao' => 'cookie: miid=8289590761042824660; thw=cn; cna=bpdDExs9KGgCAXuLszWnEXxS; hng=CN%7Czh-CN%7CCNY%7C156; tracknick=taobaorongyao; _cc_=WqG3DMC9EA%3D%3D; tg=0; enc=WQPStocTopRI3wEBOPpj8VUDkqSw4Ph81ASG9053SgG8xBMzaOuq6yMe8KD4xPBlNfQST7%2Ffsk9M9GDtGmn6iQ%3D%3D; t=4bab065740d964a05ad111f5057078d4; cookie2=1965ea371faf24b163093f31af4120c2; _tb_token_=5d3380e119d6e; v=0; mt=ci%3D-1_1; _m_h5_tk=61bf01c61d46a64c98209a7e50e9e1df_1572349453522; _m_h5_tk_enc=9d9adfcbd7af7e2274c9b331dc9bae9b; l=dBgc_jG4vxuski7DBOCgCuI8aj7TIIRAguPRwN0viOCKUxT9CgCDAJt5v8PWVNKO7t1nNetzvui3udLHRntW6KTK6MK9zd9snxf..; isg=BJWVXJ3FZGyiWUENfGCuywlwpJePOkncAk8hmRc6WoxbbrVg3-Jadf0uODL97mFc',
+        //阿里巴巴 1688
+        'alibaba' => '',
+        //天猫 可以和淘宝一样
+        'tmall' => 'cookie: miid=8289590761042824660; thw=cn; cna=bpdDExs9KGgCAXuLszWnEXxS; hng=CN%7Czh-CN%7CCNY%7C156; tracknick=taobaorongyao; _cc_=WqG3DMC9EA%3D%3D; tg=0; enc=WQPStocTopRI3wEBOPpj8VUDkqSw4Ph81ASG9053SgG8xBMzaOuq6yMe8KD4xPBlNfQST7%2Ffsk9M9GDtGmn6iQ%3D%3D; t=4bab065740d964a05ad111f5057078d4; cookie2=1965ea371faf24b163093f31af4120c2; _tb_token_=5d3380e119d6e; v=0; mt=ci%3D-1_1; _m_h5_tk=61bf01c61d46a64c98209a7e50e9e1df_1572349453522; _m_h5_tk_enc=9d9adfcbd7af7e2274c9b331dc9bae9b; l=dBgc_jG4vxuski7DBOCgCuI8aj7TIIRAguPRwN0viOCKUxT9CgCDAJt5v8PWVNKO7t1nNetzvui3udLHRntW6KTK6MK9zd9snxf..; isg=BJWVXJ3FZGyiWUENfGCuywlwpJePOkncAk8hmRc6WoxbbrVg3-Jadf0uODL97mFc',
+        //京东 可不用配置
+        'jd' => ''
+    ];
+
+    //请求平台名称 taobao alibaba tmall jd
+    protected $webnname = 'taobao';
+
+    /*
+     * 设置错误信息
+     * @param string $msg 错误信息
+     * */
+    public function setErrorInfo($msg = '')
+    {
+        $this->errorInfo = $msg;
+        return false;
+    }
+
+    /*
+     * 设置字符串字符集
+     * @param string $str 需要设置字符集的字符串
+     * @return string
+     * */
+    public function Utf8String($str)
+    {
+        $encode = mb_detect_encoding($str, array("ASCII", 'UTF-8', 'UTF-32BE', "GB2312", "GBK", 'BIG5'));
+        if (strtoupper($encode) != 'UTF-8') $str = mb_convert_encoding($str, 'utf-8', $encode);
+        return $str;
+    }
+
+    /**
+     * 获取资源,并解析出对应的商品参数
+     * @return json
+     */
+    public function get_request_contents()
+    {
+        list($link) = UtilService::postMore([
+            ['link', '']
+        ], $this->request, true);
+        $url = $this->checkurl($link);
+        if ($url === false) return $this->fail($this->errorInfo);
+        $this->errorInfo = true;
+        $html = $this->curl_Get($url, 60);
+        if (!$html) return $this->fail('商品HTML信息获取失败');
+        $html = $this->Utf8String($html);
+        preg_match('/<title>([^<>]*)<\/title>/', $html, $title);
+        //商品标题
+        $this->productInfo['store_name'] = isset($title['1']) ? str_replace(['-淘宝网', '-tmall.com天猫', ' - 阿里巴巴', ' ', '-', '【图片价格品牌报价】京东', '京东', '【行情报价价格评测】'], '', trim($title['1'])) : '';
+        $this->productInfo['store_info'] = $this->productInfo['store_name'];
+        try {
+            //获取url信息
+            $pathinfo = pathinfo($url);
+            if (!isset($pathinfo['dirname'])) return $this->fail('解析URL失败');
+            //提取域名
+            $parse_url = parse_url($pathinfo['dirname']);
+            if (!isset($parse_url['host'])) return $this->fail('获取域名失败');
+            //获取第一次.出现的位置
+            $strLeng = strpos($parse_url['host'], '.') + 1;
+            //截取域名中的真实域名不带.com后的
+            $funsuffix = substr($parse_url['host'], $strLeng, strrpos($parse_url['host'], '.') - $strLeng);
+            if (!in_array($funsuffix, $this->grabName)) return $this->fail('您输入的地址不在复制范围内!');
+            //设拼接设置商品函数
+            $funName = "setProductInfo" . ucfirst($funsuffix);
+            //执行方法
+            if (method_exists($this, $funName))
+                $this->$funName($html);
+            else
+                return $this->fail('设置商品函数不存在');
+            if (!$this->productInfo['slider_image']) return $this->fail('未能获取到商品信息,请确保商品信息有效!');
+            return $this->success(['info' => $this->productInfo]);
+        } catch (\Exception $e) {
+            return $this->fail('系统错误', ['line' => $e->getLine(), 'meass' => $e->getMessage()]);
+        }
+    }
+
+    /*
+     * 淘宝设置商品
+     * @param string $html 网页内容
+     * */
+    public function setProductInfoTaobao($html)
+    {
+        $this->webnname = 'taobao';
+        //获取轮播图
+        $images = $this->getTaobaoImg($html);
+        $images = array_merge(is_array($images) ? $images : []);
+        $this->productInfo['slider_image'] = isset($images['gaoqing']) ? $images['gaoqing'] : (array)$images;
+        $this->productInfo['slider_image'] = array_slice($this->productInfo['slider_image'], 0, 5);
+        //获取商品详情请求链接
+        $link = $this->getTaobaoDesc($html);
+        //获取请求内容
+        $desc_json = HttpService::getRequest($link);
+        //转换字符集
+        $desc_json = $this->Utf8String($desc_json);
+        //截取掉多余字符
+        $this->productInfo['test'] = $desc_json;
+        $desc_json = str_replace('var desc=\'', '', $desc_json);
+        $desc_json = str_replace(["\n", "\t", "\r"], '', $desc_json);
+        $content = substr($desc_json, 0, -2);
+        $this->productInfo['description'] = $content;
+        //获取详情图
+        $description_images = $this->decodedesc($this->productInfo['description']);
+        $this->productInfo['description_images'] = is_array($description_images) ? $description_images : [];
+        $this->productInfo['image'] = is_array($this->productInfo['slider_image']) && isset($this->productInfo['slider_image'][0]) ? $this->productInfo['slider_image'][0] : '';
+    }
+
+    /*
+     * 天猫设置商品
+     * @param string $html 网页内容
+     * */
+    public function setProductInfoTmall($html)
+    {
+        $this->webnname = 'tmall';
+        //获取轮播图
+        $images = $this->getTianMaoImg($html);
+        $images = array_merge(is_array($images) ? $images : []);
+        $this->productInfo['slider_image'] = $images;
+        $this->productInfo['slider_image'] = array_slice($this->productInfo['slider_image'], 0, 5);
+        $this->productInfo['image'] = is_array($this->productInfo['slider_image']) && isset($this->productInfo['slider_image'][0]) ? $this->productInfo['slider_image'][0] : '';
+        //获取商品详情请求链接
+        $link = $this->getTianMaoDesc($html);
+        //获取请求内容
+        $desc_json = HttpService::getRequest($link);
+        //转换字符集
+        $desc_json = $this->Utf8String($desc_json);
+        //截取掉多余字符
+        $desc_json = str_replace('var desc=\'', '', $desc_json);
+        $desc_json = str_replace(["\n", "\t", "\r"], '', $desc_json);
+        $content = substr($desc_json, 0, -2);
+        $this->productInfo['description'] = $content;
+        //获取详情图
+        $description_images = $this->decodedesc($this->productInfo['description']);
+        $this->productInfo['description_images'] = is_array($description_images) ? $description_images : [];
+    }
+
+    /*
+     * 1688设置商品
+     * @param string $html 网页内容
+     * */
+    public function setProductInfo1688($html)
+    {
+        $this->webnname = 'alibaba';
+        //获取轮播图
+        $images = $this->get1688Img($html);
+        if (isset($images['gaoqing'])) {
+            $images['gaoqing'] = array_merge($images['gaoqing']);
+            $this->productInfo['slider_image'] = $images['gaoqing'];
+        } else
+            $this->productInfo['slider_image'] = $images;
+        if (!is_array($this->productInfo['slider_image'])) {
+            $this->productInfo['slider_image'] = [];
+        }
+        $this->productInfo['slider_image'] = array_slice($this->productInfo['slider_image'], 0, 5);
+        $this->productInfo['image'] = is_array($this->productInfo['slider_image']) && isset($this->productInfo['slider_image'][0]) ? $this->productInfo['slider_image'][0] : '';
+        //获取商品详情请求链接
+        $link = $this->get1688Desc($html);
+        //获取请求内容
+        $desc_json = HttpService::getRequest($link);
+        //转换字符集
+        $desc_json = $this->Utf8String($desc_json);
+        $this->productInfo['test'] = $desc_json;
+        //截取掉多余字符
+        $desc_json = str_replace('var offer_details=', '', $desc_json);
+        $desc_json = str_replace(["\n", "\t", "\r"], '', $desc_json);
+        $desc_json = substr($desc_json, 0, -1);
+        $descArray = json_decode($desc_json, true);
+        if (!isset($descArray['content'])) $descArray['content'] = '';
+        $this->productInfo['description'] = $descArray['content'];
+        //获取详情图
+        $description_images = $this->decodedesc($this->productInfo['description']);
+        $this->productInfo['description_images'] = is_array($description_images) ? $description_images : [];
+    }
+
+    /*
+     * JD设置商品
+     * @param string $html 网页内容
+     * */
+    public function setProductInfoJd($html)
+    {
+        $this->webnname = 'jd';
+        //获取商品详情请求链接
+        $desc_url = $this->getJdDesc($html);
+        //获取请求内容
+        $desc_json = HttpService::getRequest($desc_url);
+        //转换字符集
+        $desc_json = $this->Utf8String($desc_json);
+        //截取掉多余字符
+        if (substr($desc_json, 0, 8) == 'showdesc') $desc_json = str_replace('showdesc', '', $desc_json);
+        $desc_json = str_replace('data-lazyload=', 'src=', $desc_json);
+        $descArray = json_decode($desc_json, true);
+        if (!$descArray) $descArray = ['content' => ''];
+        //获取轮播图
+        $images = $this->getJdImg($html);
+        $images = array_merge(is_array($images) ? $images : []);
+        $this->productInfo['slider_image'] = $images;
+        $this->productInfo['image'] = is_array($this->productInfo['slider_image']) ? ($this->productInfo['slider_image'][0] ?? '') : '';
+        $this->productInfo['description'] = $descArray['content'];
+        //获取详情图
+        $description_images = $this->decodedesc($descArray['content']);
+        $this->productInfo['description_images'] = is_array($description_images) ? $description_images : [];
+    }
+
+    /*
+    * 检查淘宝,天猫,1688的商品链接
+    * @return string
+    */
+    public function checkurl($link)
+    {
+        $link = strtolower(htmlspecialchars_decode($link));
+        if (!$link) return $this->setErrorInfo('请输入链接地址');
+        if (substr($link, 0, 4) != 'http') return $this->setErrorInfo('链接地址必须以http开头');
+        $arrLine = explode('?', $link);
+        if (!count($arrLine)) return $this->setErrorInfo('链接地址有误(ERR:1001)');
+        if (!isset($arrLine[1])) {
+            if (strpos($link, '1688') !== false && strpos($link, 'offer') !== false) return trim($arrLine[0]);
+            else if (strpos($link, 'item.jd') !== false) return trim($arrLine[0]);
+            else return $this->setErrorInfo('链接地址有误(ERR:1002)');
+        }
+        if (strpos($link, '1688') !== false && strpos($link, 'offer') !== false) return trim($arrLine[0]);
+        if (strpos($link, 'item.jd') !== false) return trim($arrLine[0]);
+        $arrLineValue = explode('&', $arrLine[1]);
+        if (!is_array($arrLineValue)) return $this->setErrorInfo('链接地址有误(ERR:1003)');
+        if (!strpos(trim($arrLine[0]), 'item.htm')) $this->setErrorInfo('链接地址有误(ERR:1004)');
+        //链接参数
+        $lastStr = '';
+        foreach ($arrLineValue as $k => $v) {
+            if (substr(strtolower($v), 0, 3) == 'id=') {
+                $lastStr = trim($v);
+                break;
+            }
+        }
+        if (!$lastStr) return $this->setErrorInfo('链接地址有误(ERR:1005)');
+        return trim($arrLine[0]) . '?' . $lastStr;
+    }
+
+    /*
+     * 保存图片保存商品信息
+     * */
+    public function save_product()
+    {
+        $data = UtilService::postMore([
+            ['cate_id', ''],
+            ['store_name', ''],
+            ['store_info', ''],
+            ['keyword', ''],
+            ['unit_name', ''],
+            ['image', ''],
+            ['slider_image', []],
+//            ['price', ''],
+//            ['ot_price', ''],
+            ['give_integral', ''],
+            ['postage', ''],
+//            ['sales', ''],
+            ['ficti', ''],
+//            ['stock', ''],
+//            ['cost', ''],
+            ['description_images', []],
+            ['description', ''],
+            ['is_show', 0],
+            ['soure_link', ''],
+            ['temp_id', 0],
+            ['spec_type', 0],
+            ['items', []],
+            ['attrs', []]
+        ]);
+        $data['mer_id'] = $this->merId ?: '';
+        $detail = $data['items'];
+        unset($data['items']);
+        $attrs = $data['attrs'];
+        unset($data['attrs']);
+        if (!$data['cate_id']) return $this->fail('请选择分类!');
+        if (!$data['store_name']) return $this->fail('请填写商品名称');
+        if (!$data['unit_name']) return $this->fail('请填写商品单位');
+        if (!$data['image']) return $this->fail('商品主图暂无,无法保存商品,您可选择其他链接进行复制商品');
+        if (!$data['temp_id']) return $this->fail('请选择运费模板');
+        //查询附件分类
+        $AttachmentCategory = SystemAttachmentCategory::where('name', $this->AttachmentCategoryName)->find();
+        //不存在则创建
+        if (!$AttachmentCategory) $AttachmentCategory = SystemAttachmentCategory::create(['pid' => '0', 'name' => $this->AttachmentCategoryName, 'enname' => '']);
+        //生成附件目录
+        try {
+            if (make_path('attach', 3, true) === '')
+                return $this->fail('无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
+
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage() . '或无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
+        }
+        ini_set("max_execution_time", 600);
+        //开始图片下载处理
+        ProductModel::beginTrans();
+        try {
+            //放入主图
+            $images = [
+                ['w' => 305, 'h' => 305, 'line' => $data['image'], 'valuename' => 'image']
+            ];
+            //放入轮播图
+            foreach ($data['slider_image'] as $item) {
+                $value = ['w' => 640, 'h' => 640, 'line' => $item, 'valuename' => 'slider_image', 'isTwoArray' => true];
+                array_push($images, $value);
+            }
+            //执行下载
+            $res = $this->uploadImage($images, false, 0, $AttachmentCategory['id']);
+            if (!is_array($res)) return $this->fail($this->errorInfo ? $this->errorInfo : '保存图片失败');
+            if (isset($res['image'])) $data['image'] = $res['image'];
+            if (isset($res['slider_image'])) $data['slider_image'] = $res['slider_image'];
+            $data['slider_image'] = count($data['slider_image']) ? json_encode($data['slider_image']) : '';
+            //替换并下载详情里面的图片默认下载全部图片
+            $data['description'] = preg_replace('#<style>.*?</style>#is', '', $data['description']);
+            $data['description'] = $this->uploadImage($data['description_images'], $data['description'], 1, $AttachmentCategory['id']);
+            unset($data['description_images']);
+            $description = $data['description'];
+            unset($data['description']);
+            $data['add_time'] = time();
+            $cate_id = $data['cate_id'];
+            $data['cate_id'] = implode(',',$data['cate_id']);
+            //商品存在
+            if ($productInfo = ProductModel::where('soure_link', $data['soure_link'])->find()) {
+                $productInfo->slider_image = $data['slider_image'];
+                $productInfo->image = $data['image'];
+                $productInfo->store_name = $data['store_name'];
+                StoreDescription::saveDescription($description, $productInfo->id);
+                $productInfo->save();
+                ProductModel::commitTrans();
+                return $this->success('商品存在,信息已被更新成功');
+            } else {
+                //不存在时新增
+                if ($productId = ProductModel::insertGetId($data)) {
+                    $cateList = [];
+                    foreach ($cate_id as $cid) {
+                        $cateList [] = ['product_id' => $productId, 'cate_id' => $cid, 'add_time' => time()];
+                    }
+                    StoreProductCate::insertAll($cateList);
+                    $attr = [
+                        [
+                            'value' => '规格',
+                            'detailValue' => '',
+                            'attrHidden' => '',
+                            'detail' => ['默认']
+                        ]
+                    ];
+                    $detail[0]['value1'] = '规格';
+                    $detail[0]['detail'] = ['规格' => '默认'];
+                    $detail[0]['price'] = $attrs[0]['price'];
+                    $detail[0]['stock'] = $attrs[0]['stock'];
+                    $detail[0]['cost'] = $attrs[0]['cost'];
+                    $detail[0]['pic'] = $attrs[0]['pic'];
+                    $detail[0]['ot_price'] = $attrs[0]['price'];
+                    $attr_res = StoreProductAttr::createProductAttr($attr, $detail, $productId);
+                    if ($attr_res) {
+                        StoreDescription::saveDescription($description, $productId);
+                        ProductModel::commitTrans();
+                        return $this->success('生成商品成功');
+                    } else {
+                        ProductModel::rollbackTrans();
+                        return $this->fail(StoreProductAttr::getErrorInfo('生成商品失败'));
+                    }
+                } else {
+                    ProductModel::rollbackTrans();
+                    return $this->fail('生成商品失败');
+                }
+            }
+        } catch (\PDOException $e) {
+            ProductModel::rollbackTrans();
+            return $this->fail('插入数据库错误', ['line' => $e->getLine(), 'messag' => $e->getMessage()]);
+        } catch (\Exception $e) {
+            ProductModel::rollbackTrans();
+            return $this->fail('系统错误', ['line' => $e->getLine(), 'messag' => $e->getMessage(), 'file' => $e->getFile()]);
+        }
+    }
+
+    /*
+     * 上传图片处理
+     * @param array $image 图片路径
+     * @param int $uploadType 上传方式 0=远程下载
+     * */
+    public function uploadImage(array $images = [], $html = '', $uploadType = 0, $AttachmentCategoryId = 0)
+    {
+        $uploadImage = [];
+        $siteUrl = sys_config('site_url');
+        switch ($uploadType) {
+            case 0:
+                foreach ($images as $item) {
+                    //下载图片文件
+                    if ($item['w'] && $item['h'])
+                        $uploadValue = $this->downloadImage($item['line'], '', 0, 30, $item['w'], $item['h']);
+                    else
+                        $uploadValue = $this->downloadImage($item['line']);
+                    //下载成功更新数据库
+                    if (is_array($uploadValue)) {
+                        //TODO 拼接图片地址
+                        if ($uploadValue['image_type'] == 1) $imagePath = $siteUrl . $uploadValue['path'];
+                        else $imagePath = $uploadValue['path'];
+                        //写入数据库
+                        if (!$uploadValue['is_exists'] && $AttachmentCategoryId) SystemAttachment::attachmentAdd($uploadValue['name'], $uploadValue['size'], $uploadValue['mime'], $imagePath, $imagePath, $AttachmentCategoryId, $uploadValue['image_type'], time(), 1);
+                        //组装数组
+                        if (isset($item['isTwoArray']) && $item['isTwoArray'])
+                            $uploadImage[$item['valuename']][] = $imagePath;
+                        else
+                            $uploadImage[$item['valuename']] = $imagePath;
+                    }
+                }
+                break;
+            case 1:
+                preg_match_all('#<img.*?src="([^"]*)"[^>]*>#i', $html, $match);
+                if (isset($match[1])) {
+                    foreach ($match[1] as $item) {
+                        if (is_int(strpos($item, 'http')))
+                            $arcurl = $item;
+                        else
+                            $arcurl = 'http://' . ltrim($item, '\//');
+                        $uploadValue = $this->downloadImage($arcurl);
+                        //下载成功更新数据库
+                        if (is_array($uploadValue)) {
+                            //TODO 拼接图片地址
+                            if ($uploadValue['image_type'] == 1) $imagePath = $siteUrl . $uploadValue['path'];
+                            else $imagePath = $uploadValue['path'];
+                            //写入数据库
+                            if (!$uploadValue['is_exists'] && $AttachmentCategoryId) SystemAttachment::attachmentAdd($uploadValue['name'], $uploadValue['size'], $uploadValue['mime'], $imagePath, $imagePath, $AttachmentCategoryId, $uploadValue['image_type'], time(), 1);
+                            //替换图片
+                            $html = str_replace($item, $imagePath, $html);
+                        } else {
+                            //替换掉没有下载下来的图片
+                            $html = preg_replace('#<img.*?src="' . $item . '"*>#i', '', $html);
+                        }
+                    }
+                }
+                return $html;
+                break;
+            default:
+                return $this->setErrorInfo('上传方式错误');
+                break;
+        }
+        return $uploadImage;
+    }
+
+    //提取商品描述中的所有图片
+    public function decodedesc($desc = '')
+    {
+        $desc = trim($desc);
+        if (!$desc) return '';
+        preg_match_all('/<img[^>]*?src="([^"]*?)"[^>]*?>/i', $desc, $match);
+        if (!isset($match[1]) || count($match[1]) <= 0) {
+            preg_match_all('/:url(([^"]*?));/i', $desc, $match);
+            if (!isset($match[1]) || count($match[1]) <= 0) return $desc;
+        } else {
+            preg_match_all('/:url(([^"]*?));/i', $desc, $newmatch);
+            if (isset($newmatch[1]) && count($newmatch[1]) > 0) $match[1] = array_merge($match[1], $newmatch[1]);
+        }
+        $match[1] = array_unique($match[1]); //去掉重复
+        foreach ($match[1] as $k => &$v) {
+            $_tmp_img = str_replace([')', '(', ';'], '', $v);
+            $_tmp_img = strpos($_tmp_img, 'http') ? $_tmp_img : 'http:' . $_tmp_img;
+            if (strpos($v, '?')) {
+                $_tarr = explode('?', $v);
+                $_tmp_img = trim($_tarr[0]);
+            }
+            $_urls = str_replace(['\'', '"'], '', $_tmp_img);
+            if ($this->_img_exists($_urls)) $v = $_urls;
+        }
+        return $match[1];
+    }
+
+    //获取京东商品组图
+    public function getJdImg($html = '')
+    {
+        //获取图片服务器网址
+        preg_match('/<img(.*?)id="spec-img"(.*?)data-origin=\"(.*?)\"[^>]*>/', $html, $img);
+        if (!isset($img[3])) return '';
+        $info = parse_url(trim($img[3]));
+        if (!$info['host']) return '';
+        if (!$info['path']) return '';
+        $_tmparr = explode('/', trim($info['path']));
+        $url = 'http://' . $info['host'] . '/' . $_tmparr[1] . '/' . str_replace(['jfs', ' '], '', trim($_tmparr[2]));
+        preg_match('/imageList:(.*?)"],/is', $html, $img);
+        if (!isset($img[1])) {
+            return '';
+        }
+        $_arr = explode(',', $img[1]);
+        foreach ($_arr as $k => &$v) {
+            $_str = $url . str_replace(['"', '[', ']', ' '], '', trim($v));
+            if (strpos($_str, '?')) {
+                $_tarr = explode('?', $_str);
+                $_str = trim($_tarr[0]);
+            }
+            if ($this->_img_exists($_str)) {
+                $v = $_str;
+            } else {
+                unset($_arr[$k]);
+            }
+        }
+        return array_unique($_arr);
+    }
+
+    //获取京东商品描述
+    public function getJdDesc($html = '')
+    {
+        preg_match('/,(.*?)desc:([^<>]*)\',/i', $html, $descarr);
+        if (!isset($descarr[1]) && !isset($descarr[2])) return '';
+        $tmpArr = explode(',', $descarr[2]);
+        if (count($tmpArr) > 0) {
+            $descarr[2] = trim($tmpArr[0]);
+        }
+        $replace_arr = ['\'', '\',', ' ', ',', '/*', '*/'];
+        if (isset($descarr[2])) {
+            $d_url = str_replace($replace_arr, '', $descarr[2]);
+            return $this->formatDescUrl(strpos($d_url, 'http') ? $d_url : 'http:' . $d_url);
+        }
+        $d_url = str_replace($replace_arr, '', $descarr[1]);
+        $d_url = $this->formatDescUrl($d_url);
+        $d_url = rtrim(rtrim($d_url, "?"), "&");
+        return substr($d_url, 0, 4) == 'http' ? $d_url : 'http:' . $d_url;
+    }
+
+    //处理下京东商品描述网址
+    public function formatDescUrl($url = '')
+    {
+        if (!$url) return '';
+        $url = substr($url, 0, 4) == 'http' ? $url : 'http:' . $url;
+        if (!strpos($url, '&')) {
+            $_arr = explode('?', $url);
+            if (!is_array($_arr) || count($_arr) <= 0) return $url;
+            return trim($_arr[0]);
+        } else {
+            $_arr = explode('&', $url);
+        }
+        if (!is_array($_arr) || count($_arr) <= 0) return $url;
+        unset($_arr[count($_arr) - 1]);
+        $new_url = '';
+        foreach ($_arr as $k => $v) {
+            $new_url .= $v . '&';
+        }
+        return !$new_url ? $url : $new_url;
+    }
+
+    //获取1688商品组图
+    public function get1688Img($html = '')
+    {
+        preg_match('/<ul class=\"nav nav-tabs fd-clr\">(.*?)<\/ul>/is', $html, $img);
+        if (!isset($img[0])) {
+            return '';
+        }
+        preg_match_all('/preview":"(.*?)\"\}\'>/is', $img[0], $arrb);
+        if (!isset($arrb[1]) || count($arrb[1]) <= 0) {
+            return '';
+        }
+        $thumb = [];
+        $gaoqing = [];
+        $res = ['thumb' => '', 'gaoqing' => ''];  //缩略图片和高清图片
+        foreach ($arrb[1] as $k => $v) {
+            $_str = str_replace(['","original":"'], '*', $v);
+            $_arr = explode('*', $_str);
+            if (is_array($_arr) && isset($_arr[0]) && isset($_arr[1])) {
+                if (strpos($_arr[0], '?')) {
+                    $_tarr = explode('?', $_arr[0]);
+                    $_arr[0] = trim($_tarr[0]);
+                }
+                if (strpos($_arr[1], '?')) {
+                    $_tarr = explode('?', $_arr[1]);
+                    $_arr[1] = trim($_tarr[0]);
+                }
+                if ($this->_img_exists($_arr[0])) $thumb[] = trim($_arr[0]);
+                if ($this->_img_exists($_arr[1])) $gaoqing[] = trim($_arr[1]);
+            }
+        }
+        $res = ['thumb' => array_unique($thumb), 'gaoqing' => array_unique($gaoqing)];  //缩略图片和高清图片
+        return $res;
+    }
+
+    //获取1688商品描述
+    public function get1688Desc($html = '')
+    {
+        preg_match('/data-tfs-url="([^<>]*)data-enable="true"/', $html, $descarr);
+        if (!isset($descarr[1])) return '';
+        return str_replace(['"', ' '], '', $descarr[1]);
+    }
+
+    //获取天猫商品组图
+    public function getTianMaoImg($html = '')
+    {
+        $pic_size = '430';
+        preg_match('/<img[^>]*id="J_ImgBooth"[^r]*rc=\"([^"]*)\"[^>]*>/', $html, $img);
+        if (isset($img[1])) {
+            $_arr = explode('x', $img[1]);
+            $filename = $_arr[count($_arr) - 1];
+            $pic_size = intval(substr($filename, 0, 3));
+        }
+        preg_match('|<ul id="J_UlThumb" class="tb-thumb tm-clear">(.*)</ul>|isU', $html, $match);
+        preg_match_all('/<img src="(.*?)" \//', $match[1], $images);
+        if (!isset($images[1])) return '';
+        foreach ($images[1] as $k => &$v) {
+            $tmp_v = trim($v);
+            $_arr = explode('x', $tmp_v);
+            $_fname = $_arr[count($_arr) - 1];
+            $_size = intval(substr($_fname, 0, 3));
+            if (strpos($tmp_v, '://')) {
+                $_arr = explode(':', $tmp_v);
+                $r_url = trim($_arr[1]);
+            } else {
+                $r_url = $tmp_v;
+            }
+            $str = str_replace($_size, $pic_size, $r_url);
+            if (strpos($str, '?')) {
+                $_tarr = explode('?', $str);
+                $str = trim($_tarr[0]);
+            }
+            $_i_url = strpos($str, 'http') ? $str : 'http:' . $str;
+            if ($this->_img_exists($_i_url)) {
+                $v = $_i_url;
+            } else {
+                unset($images[1][$k]);
+            }
+        }
+        return array_unique($images[1]);
+    }
+
+    //获取天猫商品描述
+    public function getTianMaoDesc($html = '')
+    {
+        preg_match('/descUrl":"([^<>]*)","httpsDescUrl":"/', $html, $descarr);
+        if (!isset($descarr[1])) {
+            preg_match('/httpsDescUrl":"([^<>]*)","fetchDcUrl/', $html, $descarr);
+            if (!isset($descarr[1])) return '';
+        }
+        return strpos($descarr[1], 'http') ? $descarr[1] : 'http:' . $descarr[1];
+    }
+
+    //获取淘宝商品组图
+    public function getTaobaoImg($html = '')
+    {
+        preg_match('/auctionImages([^<>]*)"]/', $html, $imgarr);
+        if (!isset($imgarr[1])) return '';
+        $arr = explode(',', $imgarr[1]);
+        foreach ($arr as $k => &$v) {
+            $str = trim($v);
+            $str = str_replace(['"', ' ', '', ':['], '', $str);
+            if (strpos($str, '?')) {
+                $_tarr = explode('?', $str);
+                $str = trim($_tarr[0]);
+            }
+            $_i_url = strpos($str, 'http') ? $str : 'http:' . $str;
+            if ($this->_img_exists($_i_url)) {
+                $v = $_i_url;
+            } else {
+                unset($arr[$k]);
+            }
+        }
+        return array_unique($arr);
+    }
+
+    //获取淘宝商品描述
+    public function getTaobaoDesc($html = '')
+    {
+        preg_match('/descUrl([^<>]*)counterApi/', $html, $descarr);
+        if (!isset($descarr[1])) return '';
+        $arr = explode(':', $descarr[1]);
+        $url = [];
+        foreach ($arr as $k => $v) {
+            if (strpos($v, '//')) {
+                $str = str_replace(['\'', ',', ' ', '?', ':'], '', $v);
+                $url[] = trim($str);
+            }
+        }
+        if ($url) {
+            return strpos($url[0], 'http') ? $url[0] : 'http:' . $url[0];
+        } else {
+            return '';
+        }
+    }
+
+    /**
+     * GET 请求
+     * @param string $url
+     */
+    public function curl_Get($url = '', $time_out = 25)
+    {
+        if (!$url) return '';
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查  
+        if (stripos($url, "https://") !== FALSE) {
+            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);  // 从证书中检查SSL加密算法是否存在
+        }
+        $headers = ['user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'];
+        if ($this->webnname) {
+            $headers[] = $this->webcookie["$this->webnname"];
+        }
+
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($ch, CURLOPT_TIMEOUT, $time_out);
+        $response = curl_exec($ch);
+        if ($error = curl_error($ch)) {
+            return false;
+        }
+        curl_close($ch);
+//        return mb_convert_encoding($response, 'utf-8', 'GB2312');
+        return $response;
+    }
+
+    //检测远程文件是否存在
+    public function _img_exists($url = '')
+    {
+        ini_set("max_execution_time", 0);
+        $str = @file_get_contents($url, 0, null, 0, 1);
+        if (strlen($str) <= 0) return false;
+        if ($str)
+            return true;
+        else
+            return false;
+    }
+
+    //TODO 下载图片
+    public function downloadImage($url = '', $name = '', $type = 0, $timeout = 30, $w = 0, $h = 0)
+    {
+        if (!strlen(trim($url))) return '';
+        if (!strlen(trim($name))) {
+            //TODO 获取要下载的文件名称
+            $downloadImageInfo = $this->getImageExtname($url);
+            $name = $downloadImageInfo['file_name'];
+            if (!strlen(trim($name))) return '';
+        }
+        //TODO 获取远程文件所采用的方法
+        if ($type) {
+            $ch = curl_init();
+            curl_setopt($ch, CURLOPT_URL, $url);
+            curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
+            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
+            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //TODO 跳过证书检查
+            if (stripos($url, "https://") !== FALSE) curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);  //TODO 从证书中检查SSL加密算法是否存在
+            curl_setopt($ch, CURLOPT_HTTPHEADER, array('user-agent:' . $_SERVER['HTTP_USER_AGENT']));
+            if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);//TODO 是否采集301、302之后的页面
+            $content = curl_exec($ch);
+            curl_close($ch);
+        } else {
+            try {
+                ob_start();
+                readfile($url);
+                $content = ob_get_contents();
+                ob_end_clean();
+            } catch (\Exception $e) {
+                return $e->getMessage();
+            }
+        }
+        $size = strlen(trim($content));
+        if (!$content || $size <= 2) return '图片流获取失败';
+        $date_dir = date('Y') . DS . date('m') . DS . date('d');
+        $upload_type = sys_config('upload_type', 1);
+        $upload = UploadService::init();
+        if ($upload->to('attach/' . $date_dir)->stream($content, $name) === false) {
+            return $upload->getError();
+        }
+        $imageInfo = $upload->getUploadInfo();
+        $date['path'] = $imageInfo['dir'];
+        $date['name'] = $imageInfo['name'];
+        $date['size'] = $imageInfo['size'];
+        $date['mime'] = $imageInfo['type'];
+        $date['image_type'] = $upload_type;
+        $date['is_exists'] = false;
+        return $date;
+    }
+
+    //获取即将要下载的图片扩展名
+    public function getImageExtname($url = '', $ex = 'jpg')
+    {
+        $_empty = ['file_name' => '', 'ext_name' => $ex];
+        if (!$url) return $_empty;
+        if (strpos($url, '?')) {
+            $_tarr = explode('?', $url);
+            $url = trim($_tarr[0]);
+        }
+        $arr = explode('.', $url);
+        if (!is_array($arr) || count($arr) <= 1) return $_empty;
+        $ext_name = trim($arr[count($arr) - 1]);
+        $ext_name = !$ext_name ? $ex : $ext_name;
+        return ['file_name' => md5($url) . '.' . $ext_name, 'ext_name' => $ext_name];
+    }
+
+    /*
+      $filepath = 绝对路径,末尾有斜杠 /
+      $name = 图片文件名
+      $maxwidth 定义生成图片的最大宽度(单位:像素)
+      $maxheight 生成图片的最大高度(单位:像素)
+      $filetype 最终生成的图片类型(.jpg/.png/.gif)
+    */
+    public function resizeImage($filepath = '', $name = '', $maxwidth = 0, $maxheight = 0)
+    {
+        $pic_file = $filepath . $name; //图片文件
+        $img_info = getimagesize($pic_file); //索引 2 是图像类型的标记:1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,
+        if ($img_info[2] == 1) {
+            $im = imagecreatefromgif($pic_file); //打开图片
+            $filetype = '.gif';
+        } elseif ($img_info[2] == 2) {
+            $im = imagecreatefromjpeg($pic_file); //打开图片
+            $filetype = '.jpg';
+        } elseif ($img_info[2] == 3) {
+            $im = imagecreatefrompng($pic_file); //打开图片
+            $filetype = '.png';
+        } else {
+            return ['path' => $filepath, 'file' => $name, 'mime' => ''];
+        }
+        $file_name = md5('_tmp_' . microtime() . '_' . rand(0, 10)) . $filetype;
+        $pic_width = imagesx($im);
+        $pic_height = imagesy($im);
+        $resizewidth_tag = false;
+        $resizeheight_tag = false;
+        if (($maxwidth && $pic_width > $maxwidth) || ($maxheight && $pic_height > $maxheight)) {
+            if ($maxwidth && $pic_width > $maxwidth) {
+                $widthratio = $maxwidth / $pic_width;
+                $resizewidth_tag = true;
+            }
+            if ($maxheight && $pic_height > $maxheight) {
+                $heightratio = $maxheight / $pic_height;
+                $resizeheight_tag = true;
+            }
+            if ($resizewidth_tag && $resizeheight_tag) {
+                if ($widthratio < $heightratio)
+                    $ratio = $widthratio;
+                else
+                    $ratio = $heightratio;
+            }
+            if ($resizewidth_tag && !$resizeheight_tag)
+                $ratio = $widthratio;
+            if ($resizeheight_tag && !$resizewidth_tag)
+                $ratio = $heightratio;
+            $newwidth = $pic_width * $ratio;
+            $newheight = $pic_height * $ratio;
+            if (function_exists("imagecopyresampled")) {
+                $newim = imagecreatetruecolor($newwidth, $newheight);
+                imagecopyresampled($newim, $im, 0, 0, 0, 0, $newwidth, $newheight, $pic_width, $pic_height);
+            } else {
+                $newim = imagecreate($newwidth, $newheight);
+                imagecopyresized($newim, $im, 0, 0, 0, 0, $newwidth, $newheight, $pic_width, $pic_height);
+            }
+            if ($filetype == '.png') {
+                imagepng($newim, $filepath . $file_name);
+            } else if ($filetype == '.gif') {
+                imagegif($newim, $filepath . $file_name);
+            } else {
+                imagejpeg($newim, $filepath . $file_name);
+            }
+            imagedestroy($newim);
+        } else {
+            if ($filetype == '.png') {
+                imagepng($im, $filepath . $file_name);
+            } else if ($filetype == '.gif') {
+                imagegif($im, $filepath . $file_name);
+            } else {
+                imagejpeg($im, $filepath . $file_name);
+            }
+            imagedestroy($im);
+        }
+        @unlink($pic_file);
+        return ['path' => $filepath, 'file' => $file_name, 'mime' => $img_info['mime']];
+    }
+}

+ 220 - 0
app/adminapi/controller/v1/product/StoreCategory.php

@@ -0,0 +1,220 @@
+<?php
+
+namespace app\adminapi\controller\v1\product;
+
+use app\adminapi\controller\AuthController;
+use app\models\article\ArticleCategory;
+use crmeb\services\FormBuilder as Form;
+use crmeb\services\UtilService as Util;
+use think\Request;
+use app\models\store\StoreCategory as CategoryModel;
+use think\facade\Route as Url;
+
+/**
+ * 商品分类控制器
+ * Class StoreCategory
+ * @package app\admin\controller\system
+ */
+class StoreCategory extends AuthController
+{
+
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['is_show', ''],
+            ['pid', ''],
+            ['cate_name', ''],
+            ['type', 0]
+        ]);
+        if($this->merId){
+            $where['mer_id'] = $this->merId;
+        }
+        if ($where['type']) {
+            $data1 = CategoryModel::getCategoryList();
+            $list = ArticleCategory::tidyTree($data1);
+        } else {
+            $list = CategoryModel::CategoryList($where);
+            if ($where['pid']=='' || $where['cate_name']==''){
+//                $list['list'] = ArticleCategory::tidyTree($list['list']);
+                $list['list'] = array_slice(ArticleCategory::tidyTree($list['list']), ((int)$where['page'] - 1) * $where['limit'], $where['limit']);
+            }
+
+        }
+        return $this->success($list);
+    }
+
+    /**
+     * 树形列表
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function tree_list($type)
+    {
+        $mer_id = $this->merId ?: '';
+        $list = CategoryModel::getTierList(null, $type, $mer_id);
+        return $this->success($list);
+    }
+
+    /**
+     * 修改状态
+     * @param string $is_show
+     * @param string $id
+     */
+    public function set_show($is_show = '', $id = '')
+    {
+        ($is_show == '' || $id == '') && $this->fail('缺少参数');
+        $mer_id = $this->merId ?: '';
+        if (CategoryModel::setCategoryShow($id, (int)$is_show, $mer_id)) {
+            return $this->success($is_show == 1 ? '显示成功' : '隐藏成功');
+        } else {
+            return $this->fail(CategoryModel::getErrorInfo($is_show == 1 ? '显示失败' : '隐藏失败'));
+        }
+    }
+
+    /**
+     * 快速编辑
+     * @param string $field
+     * @param string $id
+     * @param string $value
+     */
+    public function set_category($id)
+    {
+        $data = Util::postMore([
+            ['field', 'cate_name'],
+            ['value', '']
+        ]);
+        $data['field'] == '' || $id == '' || $data['value'] == '' && $this->fail('缺少参数');
+        if (CategoryModel::where('id', $id)->update([$data['field'] => $data['value']]))
+            return $this->success('保存成功');
+        else
+            return $this->fail('保存失败');
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $field = [
+            Form::select('pid', '父级')->setOptions(function () {
+                $mer_id = $this->merId ?: '';
+                $list = CategoryModel::getTierList(null, 0, $mer_id);
+                $menus = [['value' => 0, 'label' => '顶级菜单']];
+                foreach ($list as $menu) {
+                    $menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['cate_name']];
+                }
+                return $menus;
+            })->filterable(1),
+            Form::input('cate_name', '分类名称'),
+            Form::frameImageOne('pic', '分类图标(180*180)', Url::buildUrl('admin/widget.images/index', array('fodder' => 'pic')))->icon('ios-add')->width('60%')->height('435px'),
+            Form::number('sort', '排序')->value(0),
+            Form::radio('is_show', '状态', 1)->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]])
+        ];
+        return $this->makePostForm('添加分类', $field, Url::buildUrl('/product/category'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $data = Util::postMore([
+            'pid',
+            'cate_name',
+            ['pic', []],
+            'sort',
+            ['is_show', 0]
+        ], $request);
+        $data['mer_id'] = $this->merId ?: 0;
+        if ($data['pid'] == '') return $this->fail('请选择父类');
+        if (!$data['cate_name']) return $this->fail('请输入分类名称');
+        if (count($data['pic']) < 1) return $this->fail('请上传分类图标');
+        if ($data['sort'] < 0) $data['sort'] = 0;
+        $data['pic'] = $data['pic'][0];
+        $data['add_time'] = time();
+        CategoryModel::create($data);
+        return $this->success('添加分类成功!');
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        $c = CategoryModel::get($id);
+        if (!$c) return $this->fail('数据不存在!');
+        $field = [
+            Form::select('pid', '父级', (string)$c->getData('pid'))->setOptions(function () use ($id) {
+                $mer_id = $this->merId ?: '';
+                $list = CategoryModel::getTierList(CategoryModel::where('id', '<>', $id), 0, $mer_id);
+//                $list = (Util::sortListTier(CategoryModel::where('id','<>',$id)->select()->toArray(),'顶级','pid','cate_name'));
+                $menus = [['value' => 0, 'label' => '顶级菜单']];
+                foreach ($list as $menu) {
+                    $menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['cate_name']];
+                }
+                return $menus;
+            })->filterable(1),
+            Form::input('cate_name', '分类名称', $c->getData('cate_name')),
+            Form::frameImageOne('pic', '分类图标', Url::buildUrl('admin/widget.images/index', array('fodder' => 'pic')), $c->getData('pic'))->icon('ios-add')->width('60%')->height('435px'),
+            Form::number('sort', '排序', $c->getData('sort')),
+            Form::radio('is_show', '状态', $c->getData('is_show'))->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]])
+        ];
+        return $this->makePostForm('编辑分类', $field, Url::buildUrl('/product/category/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param \think\Request $request
+     * @param int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $data = Util::postMore([
+            'pid',
+            'cate_name',
+            ['pic', []],
+            'sort',
+            ['is_show', 0]
+        ], $request);
+        if ($data['pid'] == '') return $this->fail('请选择父类');
+        if (!$data['cate_name']) return $this->fail('请输入分类名称');
+        if (count($data['pic']) < 1) return $this->fail('请上传分类图标');
+        if ($data['sort'] < 0) $data['sort'] = 0;
+        $data['pic'] = $data['pic'][0];
+        CategoryModel::edit($data, $id);
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!CategoryModel::delCategory($id))
+            return $this->fail(CategoryModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+}

+ 701 - 0
app/adminapi/controller/v1/product/StoreProduct.php

@@ -0,0 +1,701 @@
+<?php
+
+namespace app\adminapi\controller\v1\product;
+
+use app\adminapi\controller\AuthController;
+use app\models\store\{
+    StoreBargain,
+    StoreCombination,
+    StoreProductAttrValue,
+    StoreProductCate,
+    StoreProductAttr,
+    StoreProductAttrResult,
+    StoreProductRelation,
+    StoreDescription,
+    StoreProduct as ProductModel,
+    StoreCategory,
+    StoreSeckill,
+    StoreGiftCode
+};
+use app\models\routine\RoutineCode;
+use app\models\system\ShippingTemplates;
+use app\models\system\SystemProductStore;
+use app\models\system\SystemStore;
+use crmeb\services\UploadService;
+use crmeb\traits\CurdControllerTrait;
+use crmeb\services\UtilService as Util;
+
+/**
+ * 商品管理
+ * Class StoreProduct
+ * @package app\admin\controller\store
+ */
+class StoreProduct extends AuthController
+{
+
+    use CurdControllerTrait;
+
+    protected $bindModel = ProductModel::class;
+
+    /**
+     * 显示资源列表头部
+     *
+     * @return \think\Response
+     */
+    public function type_header()
+    {
+        $mer_id = $this->merId ?: '';
+        //出售中商品
+        $onsale = ProductModel::merSet($this->merId)->where('is_del', 0)->where('is_show', 1)->count();
+        //待上架商品
+        $forsale = ProductModel::merSet($this->merId)->where('is_del', 0)->where('is_show', 0)->count();
+        //仓库中商品
+        $warehouse = ProductModel::merSet($this->merId)->where('is_del', 0)->count();
+        //已经售馨产品
+        $outofstock = ProductModel::getModelObject(['type' => 4, 'mer_id' => $mer_id])->count();
+        //警戒库存
+        $policeforce = ProductModel::getModelObject(['type' => 5, 'mer_id' => $mer_id])->count();
+        //回收站
+        $recycle = ProductModel::where('mer_id', $mer_id)->where('is_del', 1)->count();
+        //新人专享商品
+        $newExclusive = ProductModel::where('mer_id', $mer_id)->where('is_del', 0)->where('is_one', 1)->count();
+        $list = [
+            ['type' => 1, 'name' => '出售中商品', 'count' => $onsale],
+            ['type' => 2, 'name' => '仓库中商品', 'count' => $forsale],
+//            ['type' => 3, 'name' => '仓库中商品', 'count' => $warehouse],
+            ['type' => 4, 'name' => '已经售馨商品', 'count' => $outofstock],
+            ['type' => 5, 'name' => '警戒库存', 'count' => $policeforce],
+            ['type' => 6, 'name' => '商品回收站', 'count' => $recycle],
+            ['type' => 7, 'name' => '新人专享商品', 'count' => $newExclusive],
+        ];
+        return $this->success(compact('list'));
+    }
+
+    /**
+     * 显示资源列表
+     * @return mixed
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['store_name', ''],
+            ['cate_id', ''],
+            ['excel', 0],
+            ['type', 1]
+        ]);
+        if($this->merId){
+            $where['mer_id'] = $this->merId;
+        }
+        return $this->success(ProductModel::ProductList($where));
+    }
+
+    /**
+     * 修改状态
+     * @param string $is_show
+     * @param string $id
+     * @return mixed
+     */
+    public function set_show($is_show = '', $id = '')
+    {
+        ($is_show == '' || $id == '') && $this->fail('缺少参数');
+        if (ProductModel::be(['id' => $id, 'is_del' => 1])) return $this->fail('商品已删除,不能上架');
+        $res = ProductModel::where(['id' => $id])->update(['is_show' => (int)$is_show]);
+        if ($res) {
+            return $this->success($is_show == 1 ? '上架成功' : '下架成功');
+        } else {
+            return $this->fail($is_show == 1 ? '上架失败' : '下架失败');
+        }
+    }
+
+    /**
+     * 快速编辑
+     * @param string $field
+     * @param string $id
+     * @param string $value
+     * @return mixed
+     */
+    public function set_product($id = '')
+    {
+        $data = Util::postMore([
+            ['field', ''],
+            ['value', '']
+        ]);
+        $data['field'] == '' || $id == '' || $data['value'] == '' && $this->fail('缺少参数');
+        if (ProductModel::where(['id' => $id])->update([$data['field'] => $data['value']]))
+            return $this->success('保存成功');
+        else
+            return $this->fail('保存失败');
+    }
+
+    /**
+     * 设置批量商品上架
+     * @return mixed
+     */
+    public function product_show()
+    {
+        $post = Util::postMore([
+            ['ids', []]
+        ]);
+        if (empty($post['ids'])) {
+            return $this->fail('请选择需要上架的商品');
+        } else {
+            $res = ProductModel::where('id', 'in', $post['ids'])->update(['is_show' => 1]);
+            if ($res !== false)
+                return $this->success('上架成功');
+            else
+                return $this->fail('上架失败');
+        }
+    }
+
+    /**
+     * 获取规则属性模板
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function get_rule()
+    {
+        return $this->success(\app\models\store\StoreProductRule::merSet($this->merId)->field(['rule_name', 'rule_value'])->select()->each(function ($item) {
+            $item['rule_value'] = json_decode($item['rule_value'], true);
+        })->toArray());
+    }
+
+    /**
+     * 获取商品详细信息
+     * @param int $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function get_product_info($id = 0)
+    {
+        $mer_id = $this->merId ?: '';
+        $list = StoreCategory::getTierList(null, 1, $mer_id);
+        $menus = [];
+        foreach ($list as $menu) {
+            $menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['cate_name'], 'disabled' => $menu['pid'] == 0 ? 0 : 1];//,'disabled'=>$menu['pid']== 0];
+        }
+        $data['tempList'] = ShippingTemplates::order('sort', 'desc')->field(['id', 'name'])->select()->toArray();
+        $data['cateList'] = $menus;
+        $data['storeList'] = SystemStore::where('mer_id', $mer_id)->where('is_del', 0)->where('is_show', 1)->select()->toArray();
+        $data['productInfo'] = [];
+        if ($id) {
+            $productInfo = ProductModel::get($id);
+            if (!$productInfo) {
+                return $this->fail('修改的商品不存在');
+            }
+            $productInfo['cate_id'] = explode(',', $productInfo['cate_id']);
+            $productInfo['give_integral'] = floatval($productInfo['give_integral']);
+            $productInfo['description'] = StoreDescription::getDescription($id);
+            $productInfo['slider_image'] = is_string($productInfo['slider_image']) ? json_decode($productInfo['slider_image'], true) : [];
+            $productInfo['store_id'] = SystemProductStore::where('product_id', $id)->column('store_id');
+            if ($productInfo['spec_type'] == 1) {
+                $result = StoreProductAttrResult::getResult($id);
+                foreach ($result['value'] as $k => $v) {
+                    $num = 1;
+                    foreach ($v['detail'] as $dv) {
+                        $result['value'][$k]['value' . $num] = $dv;
+                        $num++;
+                    }
+                }
+                $productInfo['items'] = $result['attr'];
+                $productInfo['attrs'] = $result['value'];
+                $productInfo['attr'] = ['pic' => '', 'price' => 0, 'cost' => 0, 'ot_price' => 0, 'stock' => 0, 'bar_code' => '', 'weight' => 0, 'volume' => 0, 'brokerage' => 0, 'brokerage_two' => 0];
+            } else {
+                $result = StoreProductAttrValue::where('product_id', $id)->where('type', 0)->find();
+                $productInfo['items'] = [];
+                $productInfo['attrs'] = [];
+                $productInfo['attr'] = [
+                    'pic' => $result['image'] ?? '',
+                    'price' => $result['price'] ? floatval($result['price']) : 0,
+                    'cost' => $result['cost'] ? floatval($result['cost']) : 0,
+                    'ot_price' => $result['ot_price'] ? floatval($result['ot_price']) : 0,
+                    'stock' => $result['stock'] ? floatval($result['stock']) : 0,
+                    'bar_code' => $result['bar_code'] ?? '',
+                    'weight' => $result['weight'] ? floatval($result['weight']) : 0,
+                    'volume' => $result['volume'] ? floatval($result['volume']) : 0,
+                    'brokerage' => $result['brokerage'] ? floatval($result['brokerage']) : 0,
+                    'brokerage_two' => $result['brokerage_two'] ? floatval($result['brokerage_two']) : 0
+                ];
+            }
+            if ($productInfo['activity']) {
+                $activity = explode(',', $productInfo['activity']);
+                foreach ($activity as $k => $v) {
+                    if ($v == 1) {
+                        $activity[$k] = '秒杀';
+                    } elseif ($v == 2) {
+                        $activity[$k] = '砍价';
+                    } elseif ($v == 3) {
+                        $activity[$k] = '拼团';
+                    }
+                }
+                $productInfo['activity'] = $activity;
+            } else {
+                $productInfo['activity'] = ['秒杀', '砍价', '拼团'];
+            }
+            $data['productInfo'] = $productInfo;
+        }
+        return $this->success($data);
+    }
+
+    /**
+     * 保存新建或编辑
+     * @param $id
+     * @return mixed
+     * @throws \Exception
+     */
+    public function save($id)
+    {
+        $data = Util::postMore([
+            ['cate_id', []],
+            'store_name',
+            'store_info',
+            'keyword',
+            ['unit_name', '件'],
+            ['image', []],
+            ['slider_image', []],
+            ['postage', 0],
+            ['is_sub', 0],
+            ['sort', 0],
+            ['sales', 0],
+            ['ficti', 100],
+            ['give_integral', 0],
+            ['is_show', 0],
+            ['temp_id', 0],
+            ['is_hot', 0],
+            ['is_benefit', 0],
+            ['is_best', 0],
+            ['is_new', 0],
+            ['is_one', 0],
+            ['is_ban', 0],
+            ['mer_use', 0],
+            ['is_postage', 0],
+            ['is_good', 0],
+            ['description', ''],
+            ['spec_type', 0],
+            ['video_link', ''],
+            ['items', []],
+            ['attrs', []],
+            ['activity', []],
+            ['is_alone', 0],
+            ['delivery', ''],
+            ['store_id', []],
+        ]);
+        $data['mer_id'] = $this->merId ?: '';
+        foreach ($data['activity'] as $k => $v) {
+            if ($v == '秒杀') {
+                $data['activity'][$k] = 1;
+            } elseif ($v == '砍价') {
+                $data['activity'][$k] = 2;
+            } else {
+                $data['activity'][$k] = 3;
+            }
+        }
+        $data['activity'] = implode(',', $data['activity']);
+        $detail = $data['attrs'];
+        $data['price'] = min(array_column($detail, 'price'));
+        $data['ot_price'] = min(array_column($detail, 'ot_price'));
+        $data['cost'] = min(array_column($detail, 'cost'));
+        $attr = $data['items'];
+        unset($data['items'], $data['video'], $data['attrs']);
+        if (count($data['cate_id']) < 1) return $this->fail('请选择商品分类');
+        $cate_id = $data['cate_id'];
+        $data['cate_id'] = implode(',', $data['cate_id']);
+        if (!$data['store_name']) return $this->fail('请输入商品名称');
+        if (count($data['image']) < 1) return $this->fail('请上传商品图片');
+        if (count($data['slider_image']) < 1) return $this->fail('请上传商品轮播图');
+        $data['image'] = $data['image'][0];
+        $data['slider_image'] = json_encode($data['slider_image']);
+        $data['stock'] = array_sum(array_column($detail, 'stock'));
+        ProductModel::beginTrans();
+        foreach ($detail as &$item) {
+            if (($item['brokerage'] + $item['brokerage_two']) > $item['price']) {
+                return $this->fail('一二级返佣相加不能大于商品售价');
+            }
+        }
+        if ($id) {
+            unset($data['sales']);
+            ProductModel::edit($data, $id);
+            $description = $data['description'];
+            unset($data['description']);
+            StoreDescription::saveDescription($description, $id);
+            StoreProductCate::where('product_id', $id)->delete();
+            $cateData = [];
+            foreach ($cate_id as $cid) {
+                $cateData[] = ['product_id' => $id, 'cate_id' => $cid, 'add_time' => time()];
+            }
+            StoreProductCate::insertAll($cateData);
+            SystemProductStore::saveProductStore($data['store_id'], $id);
+            if ($data['spec_type'] == 0) {
+                $attr = [
+                    [
+                        'value' => '规格',
+                        'detailValue' => '',
+                        'attrHidden' => '',
+                        'detail' => ['默认']
+                    ]
+                ];
+                $detail[0]['value1'] = '规格';
+                $detail[0]['detail'] = ['规格' => '默认'];
+            }
+
+            $attr_res = StoreProductAttr::createProductAttr($attr, $detail, $id);
+            if ($attr_res) {
+                ProductModel::commitTrans();
+                return $this->success('修改成功!');
+            } else {
+                ProductModel::rollbackTrans();
+                return $this->fail(StoreProductAttr::getErrorInfo());
+            }
+        } else {
+            $data['add_time'] = time();
+            $data['code_path'] = '';
+            $res = ProductModel::create($data);
+            $description = $data['description'];
+            StoreDescription::saveDescription($description, $res['id']);
+            $cateData = [];
+            foreach ($cate_id as $cid) {
+                $cateData[] = ['product_id' => $res['id'], 'cate_id' => $cid, 'add_time' => time()];
+            }
+            StoreProductCate::insertAll($cateData);
+            SystemProductStore::saveProductStore($data['store_id'], $res['id']);
+            if ($data['spec_type'] == 0) {
+                $attr = [
+                    [
+                        'value' => '规格',
+                        'detailValue' => '',
+                        'attrHidden' => '',
+                        'detail' => ['默认']
+                    ]
+                ];
+                $detail[0]['value1'] = '规格';
+                $detail[0]['detail'] = ['规格' => '默认'];
+            }
+            $attr_res = StoreProductAttr::createProductAttr($attr, $detail, $res['id']);
+            if ($attr_res) {
+                ProductModel::commitTrans();
+                return $this->success('添加商品成功!');
+            } else {
+                ProductModel::rollbackTrans();
+                return $this->fail(StoreProductAttr::getErrorInfo());
+            }
+        }
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        if (!ProductModel::be(['id' => $id])) return $this->fail('商品数据不存在');
+        if (ProductModel::be(['id' => $id, 'is_del' => 1])) {
+            $data['is_del'] = 0;
+            if (!ProductModel::edit($data, $id))
+                return $this->fail(ProductModel::getErrorInfo('恢复失败,请稍候再试!'));
+            else
+                return $this->success('成功恢复商品!');
+        } else {
+            $data['is_del'] = 1;
+            $data['is_show'] = 0;
+            if (!ProductModel::edit($data, $id))
+                return $this->fail(ProductModel::getErrorInfo('删除失败,请稍候再试!'));
+            else
+                return $this->success('成功移到回收站!');
+        }
+    }
+
+    /**
+     * 生成属性
+     * @param int $id
+     */
+    public function is_format_attr($id)
+    {
+        $data = Util::postMore([
+            ['attrs', []],
+            ['items', []]
+        ]);
+        $attr = $data['attrs'];
+        $value = attr_format($attr)[1];
+        $valueNew = [];
+        $count = 0;
+        foreach ($value as $key => $item) {
+            $detail = $item['detail'];
+            sort($item['detail'], SORT_STRING);
+            $suk = implode(',', $item['detail']);
+            if ($id) {
+                $sukValue = StoreProductAttrValue::where('product_id', $id)->where('type', 0)->where('suk', $suk)->column('bar_code,cost,price,ot_price,stock,image as pic,weight,volume,brokerage,brokerage_two', 'suk');
+                if (!count($sukValue)) {
+                    $sukValue[$suk]['pic'] = '';
+                    $sukValue[$suk]['price'] = 0;
+                    $sukValue[$suk]['cost'] = 0;
+                    $sukValue[$suk]['ot_price'] = 0;
+                    $sukValue[$suk]['stock'] = 0;
+                    $sukValue[$suk]['bar_code'] = '';
+                    $sukValue[$suk]['weight'] = 0;
+                    $sukValue[$suk]['volume'] = 0;
+                    $sukValue[$suk]['brokerage'] = 0;
+                    $sukValue[$suk]['brokerage_two'] = 0;
+                }
+            } else {
+                $sukValue[$suk]['pic'] = '';
+                $sukValue[$suk]['price'] = 0;
+                $sukValue[$suk]['cost'] = 0;
+                $sukValue[$suk]['ot_price'] = 0;
+                $sukValue[$suk]['stock'] = 0;
+                $sukValue[$suk]['bar_code'] = '';
+                $sukValue[$suk]['weight'] = 0;
+                $sukValue[$suk]['volume'] = 0;
+                $sukValue[$suk]['brokerage'] = 0;
+                $sukValue[$suk]['brokerage_two'] = 0;
+            }
+            foreach (array_keys($detail) as $k => $title) {
+                $header[$k]['title'] = $title;
+                $header[$k]['align'] = 'center';
+                $header[$k]['minWidth'] = 120;
+            }
+            foreach (array_values($detail) as $k => $v) {
+                $valueNew[$count]['value' . ($k + 1)] = $v;
+                $header[$k]['key'] = 'value' . ($k + 1);
+            }
+            $valueNew[$count]['detail'] = $detail;
+            $valueNew[$count]['pic'] = $sukValue[$suk]['pic'] ?? '';
+            $valueNew[$count]['price'] = $sukValue[$suk]['price'] ? floatval($sukValue[$suk]['price']) : 0;
+            $valueNew[$count]['cost'] = $sukValue[$suk]['cost'] ? floatval($sukValue[$suk]['cost']) : 0;
+            $valueNew[$count]['ot_price'] = isset($sukValue[$suk]['ot_price']) ? floatval($sukValue[$suk]['ot_price']) : 0;
+            $valueNew[$count]['stock'] = $sukValue[$suk]['stock'] ? intval($sukValue[$suk]['stock']) : 0;
+            $valueNew[$count]['bar_code'] = $sukValue[$suk]['bar_code'] ?? '';
+            $valueNew[$count]['weight'] = $sukValue[$suk]['weight'] ? floatval($sukValue[$suk]['weight']) : 0;
+            $valueNew[$count]['volume'] = $sukValue[$suk]['volume'] ? floatval($sukValue[$suk]['volume']) : 0;
+            $valueNew[$count]['brokerage'] = $sukValue[$suk]['brokerage'] ? floatval($sukValue[$suk]['brokerage']) : 0;
+            $valueNew[$count]['brokerage_two'] = $sukValue[$suk]['brokerage_two'] ? floatval($sukValue[$suk]['brokerage_two']) : 0;
+            $count++;
+        }
+        $header[] = ['title' => '图片', 'slot' => 'pic', 'align' => 'center', 'minWidth' => 80];
+        $header[] = ['title' => '售价', 'slot' => 'price', 'align' => 'center', 'minWidth' => 95];
+        $header[] = ['title' => '成本价', 'slot' => 'cost', 'align' => 'center', 'minWidth' => 95];
+        $header[] = ['title' => '原价', 'slot' => 'ot_price', 'align' => 'center', 'minWidth' => 95];
+        $header[] = ['title' => '库存', 'slot' => 'stock', 'align' => 'center', 'minWidth' => 95];
+        $header[] = ['title' => '商品编号', 'slot' => 'bar_code', 'align' => 'center', 'minWidth' => 120];
+        $header[] = ['title' => '重量(KG)', 'slot' => 'weight', 'align' => 'center', 'minWidth' => 95];
+        $header[] = ['title' => '体积(m³)', 'slot' => 'volume', 'align' => 'center', 'minWidth' => 95];
+        $header[] = ['title' => '操作', 'slot' => 'action', 'align' => 'center', 'minWidth' => 70];
+        $info = ['attr' => $attr, 'value' => $valueNew, 'header' => $header];
+        return $this->success(compact('info'));
+    }
+
+    public function set_attr($id)
+    {
+        if (!$id) return $this->fail('商品不存在!');
+        list($attr, $detail) = Util::postMore([
+            ['items', []],
+            ['attrs', []]
+        ], null, true);
+        $res = StoreProductAttr::createProductAttr($attr, $detail, $id);
+        if ($res)
+            return $this->success('编辑属性成功!');
+        else
+            return $this->fail(StoreProductAttr::getErrorInfo());
+    }
+
+    public function clear_attr($id)
+    {
+        if (!$id) return $this->fail('商品不存在!');
+        if (false !== StoreProductAttr::clearProductAttr($id) && false !== StoreProductAttrResult::clearResult($id))
+            return $this->success('清空商品属性成功!');
+        else
+            return $this->fail(StoreProductAttr::getErrorInfo('清空商品属性失败!'));
+    }
+
+    /**
+     * 点赞
+     * @param $id
+     * @return mixed|\think\response\Json|void
+     */
+    public function collect($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $product = ProductModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        $this->assign(StoreProductRelation::getCollect($id));
+        return $this->fetch();
+    }
+
+    /**
+     * 收藏
+     * @param $id
+     * @return mixed|\think\response\Json|void
+     */
+    public function like($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $product = ProductModel::get($id);
+        if (!$product) return $this->fail('数据不存在!');
+        $this->assign(StoreProductRelation::getLike($id));
+        return $this->fetch();
+    }
+
+    /**
+     * 修改商品价格
+     */
+    public function edit_product_price()
+    {
+        $data = Util::postMore([
+            ['id', 0],
+            ['price', 0],
+        ]);
+        if (!$data['id']) return $this->fail('参数错误');
+        $res = ProductModel::edit(['price' => $data['price']], $data['id']);
+        if ($res) return $this->success('修改成功');
+        else return $this->fail('修改失败');
+    }
+
+    /**
+     * 修改商品库存
+     *
+     */
+    public function edit_product_stock()
+    {
+        $data = Util::postMore([
+            ['id', 0],
+            ['stock', 0],
+        ]);
+        if (!$data['id']) return $this->fail('参数错误');
+        $res = ProductModel::edit(['stock' => $data['stock']], $data['id']);
+        if ($res) return $this->success('修改成功');
+        else return $this->fail('修改失败');
+    }
+
+    /**
+     * 获取选择的商品列表
+     * @return mixed
+     */
+    public function search_list()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['cate_id', 0],
+            ['store_name', '']
+        ]);
+        if($this->merId){
+            $where['mer_id'] = $this->merId;
+        }
+        $list = ProductModel::getList($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 获取某个商品规格
+     * @return mixed
+     */
+    public function get_attrs()
+    {
+        list($id, $type) = Util::getMore([
+            ['id', 0],
+            ['type', 0],
+        ], $this->request, true);
+        return $this->success(ProductModel::getAttrs($id, $type));
+    }
+
+    /**
+     * 商品添加修改获取运费模板
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function get_template()
+    {
+        return $this->success(ShippingTemplates::merSet($this->merId)->order('sort desc,id desc')->field(['id', 'name'])->select()->toArray());
+    }
+
+    public function getTempKeys()
+    {
+        $upload = UploadService::init();
+        return $this->success($upload->getTempKeys());
+    }
+
+    /**
+     * 检测商品是否开活动
+     * @param $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function check_activity($id)
+    {
+        if ($id != 0) {
+            $res1 = StoreSeckill::where('product_id', $id)->where('is_del', 0)->find();
+            $res2 = StoreBargain::where('product_id', $id)->where('is_del', 0)->find();
+            $res3 = StoreCombination::where('product_id', $id)->where('is_del', 0)->find();
+            if ($res1 || $res2 || $res3) {
+                return $this->success('该商品有活动开启,无法删除属性');
+            } else {
+                return $this->fail();
+            }
+        } else {
+            return $this->fail();
+        }
+    }
+
+    /**
+     * 生成礼品唯一二维码
+     */
+    public function gift_code($id)
+    {
+        $mer_id = 11;
+        if (!$id || !($storeInfo = ProductModel::getValidOneProduct($id, 'id', $mer_id))) return app('json')->fail('礼品不存在或已下架');
+        $siteUrl = sys_config('site_url', '', $mer_id);
+        $code = makeOrderNo();
+        $codeData = 'id=' . $id;
+        $codeData .= '&code='. $code;
+        $name = $id . '_' . $code . '_product.jpg';
+        $res = RoutineCode::getPageCode('pages/subs/goods_details/index', $codeData, 280, $mer_id);
+        if (!$res) return app('json')->fail('二维码生成失败');
+        $uploadType = (int)sys_config('upload_type', 1, $mer_id);
+        $upload = UploadService::init(null, $mer_id);
+        $res = $upload->to('routine/product')->validate()->stream($res, $name);
+        if ($res === false) {
+            return app('json')->fail($upload->getError());
+        }
+        $imageInfo = $upload->getUploadInfo();
+        $imageInfo['image_type'] = $uploadType;
+        $url = $imageInfo['dir'];
+        if ($imageInfo['image_type'] == 1) $url = $siteUrl . $url;
+        $data['code'] = $code;
+        $data['is_use'] = 0;
+        $data['mer_id'] = $mer_id;
+        $result = StoreGiftCode::create($data);
+        if($result){
+            return app('json')->successful(['code' => $url]);
+        }
+        return app('json')->fail('二维码保存失败');
+    }
+
+    public function product_code($id)
+    {
+        $mer_id = 11;
+        $siteUrl = sys_config('site_url', '', $mer_id);
+        $codeData = 'id=' . $id;
+        $name = $id . '_product.jpg';
+        $res = RoutineCode::getPageCode('pages/subs/goods_details/index', $codeData, 280, $mer_id);
+        if(!$res) return app('json')->fail('二维码生成失败');
+        $uploadType = (int)sys_config('upload_type', 1, $mer_id);
+        $upload = UploadService::init(null, $mer_id);
+        $res = $upload->to('routine/product')->validate()->stream($res, $name);
+        if ($res === false) {
+            return app('json')->fail($upload->getError());
+        }
+        $imageInfo = $upload->getUploadInfo();
+        $imageInfo['image_type'] = $uploadType;
+        $url = $imageInfo['dir'];
+        if ($imageInfo['image_type'] == 1) $url = $siteUrl . $url;
+        return app('json')->successful(['code' => $url]);
+    }
+}

+ 124 - 0
app/adminapi/controller/v1/product/StoreProductReply.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace app\adminapi\controller\v1\product;
+
+use app\adminapi\controller\AuthController;
+use crmeb\services\FormBuilder as Form;
+use crmeb\services\UtilService;
+use crmeb\traits\CurdControllerTrait;
+use crmeb\services\UtilService as Util;
+use app\models\store\StoreProductReply as ProductReplyModel;
+use think\facade\Route as Url;
+
+/**
+ * 评论管理 控制器
+ * Class StoreProductReply
+ * @package app\admin\controller\store
+ */
+class StoreProductReply extends AuthController
+{
+
+    use CurdControllerTrait;
+
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = UtilService::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['is_reply', ''],
+            ['store_name', ''],
+            ['account', ''],
+            ['data', ''],
+            ['product_id', 0]
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        $list = ProductReplyModel::sysPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return \think\response\Json|void
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $data['is_del'] = 1;
+        if (!ProductReplyModel::edit($data, $id))
+            return $this->fail(ProductReplyModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 回复评论
+     */
+    public function set_reply($id)
+    {
+        $data = Util::postMore([
+            'content',
+        ]);
+        if ($data['content'] == '') return $this->fail('请输入回复内容');
+        $save['merchant_reply_content'] = $data['content'];
+        $save['merchant_reply_time'] = time();
+        $save['is_reply'] = 1;
+        $res = ProductReplyModel::edit($save, $id);
+        if (!$res)
+            return $this->fail(ProductReplyModel::getErrorInfo('回复失败,请稍候再试!'));
+        else
+            return $this->success('回复成功!');
+    }
+
+    /**
+     * 添加虚拟评论表单
+     * @return mixed
+     */
+    public function fictitious_reply()
+    {
+        $field = [
+            Form::frameImageOne('image', '商品', Url::buildUrl('admin/store.StoreProduct/index', array('fodder' => 'image')))->icon('ios-add')->width('60%')->height('536px')->setProps(['srcKey' => 'image']),
+            Form::hidden('product_id', ''),
+            Form::input('nickname', '用户名称')->col(Form::col(24)),
+            Form::input('comment', '评价文字')->type('textarea'),
+            Form::number('product_score', '商品分数')->col(8)->value(5)->min(1)->max(5),
+            Form::number('service_score', '服务分数')->col(8)->value(5)->min(1)->max(5),
+            Form::frameImageOne('avatar', '用户头像', Url::buildUrl('admin/widget.images/index', array('fodder' => 'avatar')))->icon('ios-add')->width('50%')->height('396px'),
+            Form::frameImages('pics', '评价图片', Url::buildUrl('admin/widget.images/index', array('fodder' => 'pics', 'type' => 'many')))->maxLength(5)->icon('ios-add')->width('50%')->height('396px')->spin(0)->setProps(['srcKey' => 'att_dir']),
+        ];
+        return $this->makePostForm('添加虚拟评论', $field, Url::buildUrl('/product/reply/save_fictitious_reply'), 'POST');
+    }
+
+    /**
+     * 添加虚拟评论
+     * @return mixed
+     */
+    public function save_fictitious_reply()
+    {
+        $data = Util::postMore([
+            ['image', ''],
+            ['nickname', ''],
+            ['avatar', ''],
+            ['comment', ''],
+            ['pics', []],
+            ['product_score', 0],
+            ['service_score', 0],
+        ]);
+        $this->validate(['image' => $data['image'], 'nickname' => $data['nickname'], 'avatar' => $data['avatar'], 'comment' => $data['comment'], 'product_score' => $data['product_score'], 'service_score' => $data['service_score']], \app\adminapi\validates\product\StoreProductReplyValidate::class, 'save');
+        $data['product_id'] = $data['image']['product_id'];
+        $data['uid'] = 0;
+        $data['oid'] = 0;
+        $data['unique'] = uniqid();
+        $data['reply_type'] = 'product';
+        $data['add_time'] = time();
+        $data['pics'] = json_encode(array_column($data['pics'],'att_dir'));
+        unset($data['image']);
+        ProductReplyModel::create($data);
+        return $this->success('添加成功!');
+    }
+}

+ 95 - 0
app/adminapi/controller/v1/product/StoreProductRule.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace app\adminapi\controller\v1\product;
+
+use app\adminapi\controller\AuthController;
+use app\models\store\StoreProductRule as ProductRuleModel;
+use crmeb\services\UtilService;
+use think\Request;
+
+/**
+ * 规则管理
+ * Class StoreProductRule
+ * @package app\adminapi\controller\v1\product
+ */
+class StoreProductRule extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = UtilService::getMore([
+            ['page', 1],
+            ['limit', 15],
+            ['rule_name','']
+        ]);
+        if($this->merId){
+            $where['mer_id'] = $this->merId;
+        }
+        $list = ProductRuleModel::sysPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request,$id)
+    {
+        $data = UtilService::postMore([
+            ['rule_name',''],
+            ['spec',[]]
+        ]);
+        $data['mer_id'] = $this->merId ?: '';
+        if ($data['rule_name'] == '') return $this->fail('请输入规则名称');
+        if (!$data['spec']) return $this->fail('缺少规则值');
+        $data['rule_value'] = json_encode($data['spec']);
+        unset($data['spec']);
+        if ($id){
+            $rule = ProductRuleModel::get($id);
+            if (!$rule) return $this->fail('数据不存在');
+            ProductRuleModel::edit($data, $id);
+            return $this->success('编辑成功!');
+        }else{
+            ProductRuleModel::create($data);
+            return $this->success('规则添加成功!');
+        }
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        $info = ProductRuleModel::sysInfo($id);
+        return $this->success($info);
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete()
+    {
+        $data = UtilService::postMore([
+            ['ids','']
+        ]);
+        if ($data['ids']=='') return $this->fail('请至少选择一条数据');
+        $ids = strval($data['ids']);
+        $res = ProductRuleModel::whereIn('id',$ids)->delete();
+        if (!$res)
+            return $this->fail(ProductRuleModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/setting/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/setting

+ 87 - 0
app/adminapi/controller/v1/setting/PushBinding.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+use app\adminapi\controller\AuthController;
+use Exception;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\Request;
+use crmeb\services\UtilService as Util;
+use crmeb\services\QrcodeService;
+use app\models\user\User;
+use app\models\user\BindUser;
+use app\models\system\MerchantDailyReport;
+
+class PushBinding extends AuthController
+{
+	/**
+	 * 生成h5页面二维码
+	 */
+	public function get_qrcode()
+	{
+		try {
+			$token = time();
+			// $urlCode = QrcodeService::getQrcodePath('qrcode_' . $this->merId . '_' . time() . '.jpg', '/api/wechat/wxAuth?mer_id=' . $this->merId, false, $this->merId);
+            $urlCode = QrcodeService::getQrcodePath('qrcode_' . $this->merId . '_' . time() . '.jpg', '/bind/auth.html?token=' . $token . '&mer_id=' . $this->merId, false, $this->merId);
+            return $this->success(['code_src' => $urlCode]);
+        } catch (Exception $e) {
+            return $this->fail('查看推广二维码失败!', ['line' => $e->getLine(), 'meassge' => $e->getMessage()]);
+        }
+	}
+
+	/**
+	 * 获取绑定推送用户列表
+	 */
+	public function getBindUserList()
+	{
+		$where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        return $this->success(BindUser::systemPage($where));
+	}
+
+	/**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        BindUser::where(['id' => $id])->update(['status' => $status]);
+        return $this->success($status == 0 ? '关闭成功' : '开启成功');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $bindUser = BindUser::get($id);
+        if (!$bindUser) return $this->fail('数据不存在!');
+        if ($bindUser['is_del']) return $this->fail('已删除!');
+        $data['is_del'] = 1;
+        if (!BindUser::edit($data, $id))
+            return $this->fail(BindUser::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 发送商户报表模板消息
+     */
+    public function sendMerchantMessage()
+    {
+    	MerchantDailyReport::sendMerchantMessage();
+    }
+}

+ 137 - 0
app/adminapi/controller/v1/setting/ShippingTemplates.php

@@ -0,0 +1,137 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+use app\models\system\ShippingTemplatesFree;
+use app\models\system\ShippingTemplatesRegion;
+use app\models\system\SystemCity;
+use app\models\system\ShippingTemplates as STModel;
+
+use app\adminapi\controller\AuthController;
+use crmeb\services\UtilService;
+
+class ShippingTemplates extends AuthController
+{
+    /**
+     * 运费模板列表
+     * @return mixed
+     */
+    public function temp_list()
+    {
+        $where = UtilService::getMore([
+            [['page','d'], 1],
+            [['limit','d'], 20],
+            [['name','s'], '']
+        ]);
+        $where['mer_id'] = $this->merId ?: '';
+        return $this->success(STModel::getList($where));
+    }
+
+    /**
+     * 修改
+     * @return string
+     * @throws \Exception
+     */
+    public function edit($id = 0)
+    {
+        $templates = STModel::get($id);
+        if (!$templates) {
+            return $this->fail('修改的模板不存在');
+        }
+        $data['appointList'] = ShippingTemplatesFree::getFreeList($id);
+        $data['templateList'] = ShippingTemplatesRegion::getRegionList($id);
+        if (!isset($data['templateList'][0]['region'])) {
+            $data['templateList'][0]['region'] = ['city_id' => 0, 'name' => '默认全国'];
+        }
+        $data['formData'] = [
+            'name' => $templates->name,
+            'type' => $templates->getData('type'),
+            'appoint_check' => $templates->getData('appoint'),
+            'sort' => $templates->getData('sort'),
+        ];
+        return $this->success($data);
+    }
+
+    /**
+     * 保存或者修改
+     * @param int $id
+     */
+    public function save($id = 0)
+    {
+        $data = UtilService::postMore([
+            [['region_info','a'], []],
+            [['appoint_info','a'], []],
+            [['sort','d'], 0],
+            [['type','d'], 0],
+            [['name','s'], ''],
+            [['appoint','d'], 0],
+        ]);
+        $this->validate($data, \app\adminapi\validates\setting\ShippingTemplatesValidate::class, 'save');
+        $temp['name'] = $data['name'];
+        $temp['type'] = $data['type'];
+        $temp['appoint'] = $data['appoint'];
+        $temp['sort'] = $data['sort'];
+        $temp['add_time'] = time();
+        $temp['mer_id'] = $this->merId ?: '';
+        STModel::beginTrans();
+        $res = true;
+        try {
+            if ($id) {
+                $res = STModel::where('id', $id)->update($temp);
+            } else {
+                $id = STModel::insertGetId($temp);
+            }
+            //设置区域配送
+            $res = $res && ShippingTemplatesRegion::saveRegion($data['region_info'], $data['type'], $id);
+            if (!$res) {
+                STModel::rollbackTrans();
+                return $this->fail(ShippingTemplatesRegion::getErrorInfo());
+            }
+            //设置指定包邮
+            if ($data['appoint']) {
+                $res = $res && ShippingTemplatesFree::saveFree($data['appoint_info'], $data['type'], $id);
+            }
+            if ($res) {
+                STModel::commitTrans();
+                return $this->success('保存成功');
+            } else {
+                STModel::rollbackTrans();
+                return $this->fail(ShippingTemplatesFree::getErrorInfo('保存失败'));
+            }
+        } catch (\Throwable $e) {
+            STModel::rollbackTrans();
+            return $this->fail($e->getMessage());
+        }
+    }
+
+    /**
+     * 删除运费模板
+     */
+    public function delete()
+    {
+        $data = UtilService::getMore([
+            [['id','d'], 0],
+        ]);
+        if ($data['id'] == 1) {
+            return $this->fail('默认模板不能删除');
+        } else {
+            STModel::del($data['id']);
+            ShippingTemplatesRegion::where('temp_id', $data['id'])->delete();
+            ShippingTemplatesFree::where('temp_id', $data['id'])->delete();
+            return $this->success('删除成功');
+        }
+    }
+
+    /**
+     * 城市数据
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function city_list()
+    {
+        $list = SystemCity::with('children')->where('parent_id', 0)->order('id asc')->select();
+        return $this->success($list->toArray());
+    }
+}

+ 269 - 0
app/adminapi/controller/v1/setting/SystemAdmin.php

@@ -0,0 +1,269 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+use app\adminapi\controller\AuthController;
+use crmeb\services\CacheService;
+use crmeb\services\FormBuilder as Form;
+use crmeb\services\UtilService;
+use Exception;
+use FormBuilder\exception\FormBuilderException;
+use Psr\SimpleCache\InvalidArgumentException;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\facade\Config;
+use think\facade\Route as Url;
+use app\models\system\SystemRole;
+use app\models\system\SystemAdmin as SystemAdminModel;
+use think\Request;
+use think\Response;
+
+class SystemAdmin extends AuthController
+{
+    /**
+     * 显示管理员资源列表
+     *
+     * @return Response
+     * @throws Exception
+     */
+    public function index()
+    {
+        [$name, $roles, $page, $limit] = UtilService::getMore([
+            ['name', ''],
+            ['roles', ''],
+            ['page', 1],
+            ['limit', 10],
+        ], $this->request, true);
+        return $this->success(SystemAdminModel::getAdminList($name, bcadd($this->adminInfo['level'], 1, 0), $roles, $page, $limit, $this->merId));
+    }
+
+    /**
+     * 创建表单
+     * @return mixed
+     * @throws FormBuilderException
+     */
+    public function create()
+    {
+        $f[] = Form::input('account', '管理员账号')->required('请填写管理员账号');
+        $f[] = Form::input('pwd', '管理员密码')->type('password')->required('请填写管理员密码');
+        $f[] = Form::input('conf_pwd', '确认密码')->type('password')->required('请输入确认密码');
+        $f[] = Form::input('real_name', '管理员姓名')->required('请输入管理员姓名');
+        $list = SystemRole::getRole(bcadd($this->adminInfo['level'], 1, 0));
+        $options = [];
+        foreach ($list as $id => $roleName) {
+            $options[] = ['label' => $roleName, 'value' => $id];
+        }
+        $f[] = Form::select('roles', '管理员身份')->setOptions($options)->multiple(true)->required('请选择管理员身份');
+        $f[] = Form::radio('status', '状态', 1)->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
+        return $this->makePostForm('管理员添加', $f, Url::buildUrl('/setting/admin')->suffix(false));
+    }
+
+    /**
+     * 保存管理员
+     * @param Request $request
+     * @return mixed
+     * @throws Exception
+     */
+    public function save(Request $request)
+    {
+        $data = UtilService::postMore([
+            ['account', ''],
+            ['conf_pwd', ''],
+            ['pwd', ''],
+            ['real_name', ''],
+            ['roles', []],
+            ['status', 0],
+        ], $request);
+
+        $this->validate($data, \app\adminapi\validates\setting\SystemAdminValidata::class);
+
+        if ($data['conf_pwd'] != $data['pwd']) return $this->fail('两次输入的密码不相同');
+        unset($data['conf_pwd']);
+        if (SystemAdminModel::be(['account' => $data['account']])) return $this->fail('管理员账号已存在');
+
+        $data['pwd'] = password_hash($data['pwd'], PASSWORD_BCRYPT);
+        $data['add_time'] = time();
+        $data['level'] = $this->adminInfo['level'] + 1;
+        $data['mer_id'] = $this->merId;
+        $data['roles'] = implode(',', $data['roles']);
+        if (SystemAdminModel::create($data))
+            return $this->success('添加成功');
+        else
+            return $this->fail('添加失败');
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return Response
+     * @throws FormBuilderException
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function edit($id)
+    {
+        if (!$id || !($adminInfo = SystemAdminModel::where("mer_id", 'in', [0, $this->merId])->where('id', $id)->find()))
+            return $this->fail('管理员信息读取失败');
+        $f[] = Form::input('account', '管理员账号', $adminInfo->getData('account'))->required('请填写管理员账号');
+        $f[] = Form::input('pwd', '管理员密码')->type('password')->placeholder('请填写管理员密码');
+        $f[] = Form::input('conf_pwd', '确认密码')->type('password')->placeholder('请输入确认密码');
+        $f[] = Form::input('real_name', '管理员姓名', $adminInfo->getData('real_name'))->required('请输入管理员姓名');
+        $list = SystemRole::getRole(bcadd($this->adminInfo['level'], 1, 0));
+        $options = [];
+        foreach ($list as $k => $roleName) {
+            $options[] = ['label' => $roleName, 'value' => $k];
+        }
+        $f[] = Form::select('roles', '管理员身份', $adminInfo->roles)->setOptions($options)->multiple(true)->required('请选择管理员身份');
+        $f[] = Form::radio('status', '状态', $adminInfo->getData('status'))->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
+        return $this->makePostForm('管理员修改', $f, Url::buildUrl('/setting/admin/' . $id)->suffix(false), 'PUT');
+    }
+
+    /**
+     * 修改管理员信息
+     * @param Request $request
+     * @param $id
+     * @return mixed
+     * @throws Exception
+     */
+    public function update(Request $request, $id)
+    {
+        $data = UtilService::postMore([
+            ['account', ''],
+            ['conf_pwd', ''],
+            ['pwd', ''],
+            ['real_name', ''],
+            ['roles', []],
+            ['status', 0],
+        ], $request);
+
+        $this->validate($data, \app\adminapi\validates\setting\SystemAdminValidata::class, 'update');
+
+        if (!$adminInfo = SystemAdminModel::where("mer_id", 'in', [0, $this->merId])->where('id', $id)->find())
+            return $this->fail('管理员不存在,无法修改');
+        if ($data['pwd']) {
+            if (!$data['conf_pwd'])
+                return $this->fail('请输入确认密码');
+            if ($data['conf_pwd'] != $data['pwd'])
+                return $this->fail('上次输入的密码不相同');
+            $adminInfo->pwd = password_hash($data['pwd'], PASSWORD_BCRYPT);
+        }
+        if (SystemAdminModel::where(['account' => $data['account']])->where('id', '<>', $id)->count())
+            return $this->fail('管理员账号已存在');
+
+        $adminInfo->roles = implode(',', $data['roles']);
+        $adminInfo->real_name = $data['real_name'];
+        $adminInfo->account = $data['account'];
+        $adminInfo->status = $data['status'];
+        if ($adminInfo->save())
+            return $this->success('修改成功');
+        else
+            return $this->fail('修改失败');
+    }
+
+    /**
+     * 删除管理员
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function delete($id)
+    {
+        if (!$id) return $this->fail('删除失败,缺少参数');
+        if (!SystemAdminModel::where("mer_id", 'in', [0, $this->merId])->where('id', $id)->find()) {
+            if (!$id) return $this->fail('删除失败');
+        }
+        if (SystemAdminModel::edit(['is_del' => 1, 'status' => 0], $id, 'id'))
+            return $this->success('删除成功!');
+        else
+            return $this->fail('删除失败');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function set_status($id, $status)
+    {
+        if (!$id) return $this->fail('修改失败,缺少参数');
+        if (!SystemAdminModel::where("mer_id", 'in', [0, $this->merId])->where('id', $id)->find()) {
+            if (!$id) return $this->fail('修改失败');
+        }
+        SystemAdminModel::where(['id' => $id])->update(['status' => $status]);
+        return $this->success($status == 0 ? '关闭成功' : '开启成功');
+    }
+
+    /**
+     * 获取当前登陆管理员的信息
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws DbException
+     * @throws DbException
+     */
+    public function info()
+    {
+        return $this->success(SystemAdminModel::where(['id' => $this->adminId])->find()->hidden(['pwd', 'is_del', 'status'])->toArray());
+    }
+
+    /**
+     * 修改当前登陆admin信息
+     * @return mixed
+     * @throws Exception
+     */
+    public function update_admin()
+    {
+        $data = UtilService::postMore([
+            ['real_name', ''],
+            ['head_pic', ''],
+            ['pwd', ''],
+            ['new_pwd', ''],
+            ['conf_pwd', ''],
+        ], $this->request);
+
+        $adminInfo = SystemAdminModel::get($this->adminId);
+        if (!$adminInfo)
+            return $this->fail('管理员信息未查到');
+        if (!$data['real_name'])
+            return $this->fail('管理员姓名不能为空');
+        if ($data['pwd']) {
+            if (!password_verify($data['pwd'], $this->adminInfo['pwd']))
+                return $this->fail('原始密码错误');
+            if (!$data['new_pwd'])
+                return $this->fail('请输入新密码');
+            if (!$data['conf_pwd'])
+                return $this->fail('请输入确认密码');
+            if ($data['new_pwd'] != $data['conf_pwd'])
+                return $this->fail('两次输入的密码不一致');
+            $adminInfo->pwd = password_hash($data['new_pwd'], PASSWORD_BCRYPT);
+        }
+
+        $adminInfo->real_name = $data['real_name'];
+        $adminInfo->head_pic = $data['head_pic'];
+        if ($adminInfo->save())
+            return $this->success('修改成功');
+        else
+            return $this->fail('修改失败');
+    }
+
+    /**
+     * 退出登陆
+     * @return mixed
+     * @throws InvalidArgumentException
+     */
+    public function logout()
+    {
+        $key = trim(ltrim($this->request->header(Config::get('cookie.token_name')), 'Bearer'));
+        $res = CacheService::redisHandler()->delete($key);
+        return $this->success();
+    }
+}

+ 145 - 0
app/adminapi/controller/v1/setting/SystemCity.php

@@ -0,0 +1,145 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+use app\adminapi\controller\AuthController;
+use think\Request;
+use think\facade\Route as Url;
+use crmeb\services\{CacheService, FormBuilder as Form, UtilService as Util};
+use app\models\system\SystemCity as CityModel;
+
+class SystemCity extends AuthController
+{
+    /**
+     * 城市列表
+     * @return string
+     * @throws \Exception
+     */
+    public function index()
+    {
+        $params = Util::getMore([
+            [['parent_id','d'], 0]
+        ], $this->request);
+        return $this->success(CityModel::getList($params));
+    }
+
+    /**
+     * 添加城市
+     * @return string
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function add()
+    {
+        $data = Util::getMore([
+            [['parent_id','d'], 0]
+        ]);
+        if ($data['parent_id'] != 0) {
+            $info = CityModel::where('city_id', $data['parent_id'])->find()->toArray();
+        } else {
+            $info = [
+                "level" => 0,
+                "city_id" => 0,
+                "name" => '中国'
+            ];
+        }
+        $field[] = Form::hidden('level', $info['level']);
+        $field[] = Form::hidden('parent_id', $info['city_id']);
+        $field[] = Form::input('parent_name', '上级名称', $info['name'])->readonly(true);
+        $field[] = Form::input('name', '名称')->required('请填写城市名称');
+//        $field[] = Form::input('merger_name', '合并名称')->placeholder('格式:陕西,西安,雁塔')->required('请填写合并名称');
+//        $field[] = Form::input('area_code', '区号');
+//        $field[] = Form::input('lng', '经度');
+//        $field[] = Form::input('lat', '纬度');
+        return $this->makePostForm('添加城市', $field, Url::buildUrl('/setting/city/save')->suffix(false));
+    }
+
+    /**
+     * 保存
+     */
+    public function save()
+    {
+        $data = Util::postMore([
+            [['id','d'], 0],
+            [['name','s'], ''],
+            [['merger_name','s'], ''],
+            [['area_code','s'], ''],
+            [['lng','s'], ''],
+            [['lat','s'], ''],
+            [['level','d'], 0],
+            [['parent_id','d'], 0],
+        ]);
+        $this->validate($data, \app\adminapi\validates\setting\SystemCityValidate::class, 'save');
+        if($data['parent_id'] == 0){
+            $data['merger_name'] = $data['name'];
+        } else {
+            $data['merger_name'] = CityModel::where('id',$data['parent_id'])->value('name').','.$data['name'];
+        }
+        if ($data['id'] == 0) {
+            unset($data['id']);
+            $data['level'] = $data['level'] + 1;
+            $data['city_id'] = intval(CityModel::max('city_id') + 1);
+            CityModel::create($data);
+            return $this->success('添加城市成功!');
+        } else {
+            unset($data['level']);
+            unset($data['parent_id']);
+            CityModel::where('id', $data['id'])->update($data);
+            return $this->success('修改城市成功!');
+        }
+    }
+
+    /**
+     * 修改城市
+     * @return string
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function edit()
+    {
+        $data = Util::getMore([
+            [['id','d'], 0]
+        ]);
+        $info = CityModel::get($data['id'])->toArray();
+        $info['parent_name'] = CityModel::where('city_id', $info['parent_id'])->value('name') ?: '中国';
+        $field[] = Form::hidden('id', $info['id']);
+        $field[] = Form::hidden('level', $info['level']);
+        $field[] = Form::hidden('parent_id', $info['parent_id']);
+        $field[] = Form::input('parent_name', '上级名称', $info['parent_name'])->readonly(true);
+        $field[] = Form::input('name', '名称', $info['name'])->required('请填写城市名称');
+        $field[] = Form::input('merger_name', '合并名称', $info['merger_name'])->placeholder('格式:陕西,西安,雁塔')->required('请填写合并名称');
+//        $field[] = Form::input('area_code', '区号', $info['area_code']);
+//        $field[] = Form::input('lng', '经度', $info['lng']);
+//        $field[] = Form::input('lat', '纬度', $info['lat']);
+        return $this->makePostForm('修改城市', $field, Url::buildUrl('/setting/city/save')->suffix(false));
+    }
+
+    /**
+     * 删除城市
+     * @throws \Exception
+     */
+    public function delete()
+    {
+        $data = Util::getMore([
+            [['city_id','d'], 0]
+        ]);
+        CityModel::where('city_id', $data['city_id'])->whereOr('parent_id', $data['city_id'])->delete();
+        return $this->success('删除成功!');
+    }
+    
+    /**
+     * 清除城市缓存
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function clean_cache()
+    {
+        $res = CacheService::delete('CITY_LIST');
+        if ($res) {
+            return $this->success('清除成功!');
+        } else {
+            return $this->fail('清除失败或缓存未生成!');
+        }
+    }
+}

+ 483 - 0
app/adminapi/controller/v1/setting/SystemConfig.php

@@ -0,0 +1,483 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+use app\adminapi\controller\AuthController;
+use Exception;
+use FormBuilder\exception\FormBuilderException;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\Request;
+use think\facade\Route as Url;
+use crmeb\services\{FormBuilder as Form, MiniProgramService, UtilService as Util};
+use app\models\system\{
+    SystemConfigTab as ConfigTabModel, SystemConfig as ConfigModel, SystemConfigMerchant as ConfigMerchantModel
+};
+use think\Response;
+
+class SystemConfig extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return Response
+     * @throws Exception
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['tab_id', 0],
+            ['page', 1],
+            ['limit', 15]
+        ]);
+        if (!$where['tab_id']) return $this->fail('参数错误');
+        $list = ConfigModel::getPageList($where, $this->merId);
+//        foreach ($list as $k => $v) {
+//            $list[$k]['value'] = json_decode($v['value'], true) ?: '';
+//            if ($v['type'] == 'radio' || $v['type'] == 'checkbox') {
+//                $list[$k]['value'] = ConfigTabModel::getRadioOrCheckboxValueInfo($v['menu_name'], $v['value']);
+//            }
+//            if ($v['type'] == 'upload' && !empty($v['value'])) {
+//                if ($v['upload_type'] == 1 || $v['upload_type'] == 3) $list[$k]['value'] = explode(',', $v['value']);
+//            }
+//        }
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     * @param $type
+     * @return Response
+     * @throws FormBuilderException
+     * @throws Exception
+     */
+    public function create()
+    {
+        $data = Util::getMore(['type',]);//接收参数
+        $tab_id = !empty(request()->param('tab_id')) ? request()->param('tab_id') : 1;
+        $formbuider = array();
+        switch ($data['type']) {
+            case 0://文本框
+                $formbuider = ConfigModel::createInputRule($tab_id);
+                break;
+            case 1://多行文本框
+                $formbuider = ConfigModel::createTextAreaRule($tab_id);
+                break;
+            case 2://单选框
+                $formbuider = ConfigModel::createRadioRule($tab_id);
+                break;
+            case 3://文件上传
+                $formbuider = ConfigModel::createUploadRule($tab_id);
+                break;
+            case 4://多选框
+                $formbuider = ConfigModel::createCheckboxRule($tab_id);
+                break;
+            case 5://下拉框
+                $formbuider = ConfigModel::createSelectRule($tab_id);
+                break;
+        }
+        return $this->makePostForm('添加字段', $formbuider, Url::buildUrl('adminapi/setting/config')->domain(true)->build(), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param Request $request
+     * @return Response
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function save(Request $request)
+    {
+        $data = Util::postMore([
+            'menu_name',
+            'type',
+            'input_type',
+            'config_tab_id',
+            'parameter',
+            'upload_type',
+            'required',
+            'width',
+            'high',
+            'value',
+            'info',
+            'desc',
+            'sort',
+            'status',]);
+        if (!$data['info']) return $this->fail('请输入配置名称');
+        if (!$data['menu_name']) return $this->fail('请输入字段名称');
+        if ($data['menu_name']) {
+            $oneConfig = ConfigModel::getOneConfig('menu_name', $data['menu_name']);
+            if (!empty($oneConfig)) return $this->fail('请重新输入字段名称,之前的已经使用过了');
+        }
+        if (!$data['desc']) return $this->fail('请输入配置简介');
+        if ($data['sort'] < 0) {
+            $data['sort'] = 0;
+        }
+        if ($data['type'] == 'text') {
+            if (!ConfigModel::valiDateTextRole($data)) return $this->fail(ConfigModel::getErrorInfo());
+        }
+        if ($data['type'] == 'textarea') {
+            if (!ConfigModel::valiDateTextareaRole($data)) return $this->fail(ConfigModel::getErrorInfo());
+        }
+        if ($data['type'] == 'radio' || $data['type'] == 'checkbox') {
+            if (!$data['parameter']) return $this->fail('请输入配置参数');
+            if (!ConfigModel::valiDateRadioAndCheckbox($data)) return $this->fail(ConfigModel::getErrorInfo());
+            $data['value'] = json_encode($data['value']);
+        }
+        ConfigModel::create($data);
+        $data['mer_id'] = $this->merId;
+        ConfigMerchantModel::create($data);
+        \crmeb\services\CacheService::clear();
+        return $this->success('添加配置成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return Response
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function read($id)
+    {
+        if (!$id) return $this->fail('参数错误,请重新打开');
+        $info = ConfigModel::getAll($id, 1, $this->merId);
+        foreach ($info as $k => $v) {
+            if (!is_null(json_decode($v['value'])))
+                $info[$k]['value'] = json_decode($v['value'], true);
+            if ($v['type'] == 'upload' && !empty($v['value'])) {
+                if ($v['upload_type'] == 1 || $v['upload_type'] == 3) $info[$k]['value'] = explode(',', $v['value']);
+            }
+        }
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return Response
+     * @throws DataNotFoundException
+     * @throws FormBuilderException
+     * @throws ModelNotFoundException
+     * @throws DbException
+     */
+    public function edit($id)
+    {
+        if(!$menu = ConfigMerchantModel::get($id)) $menu = ConfigModel::get($id);
+        if (!$menu) return $this->fail('数据不存在!');
+        $formbuider = array();
+        $formbuider[] = Form::input('menu_name', '字段变量', $menu['menu_name'])->disabled(1);
+        $formbuider[] = Form::hidden('type', $menu['type']);
+        $formbuider[] = Form::select('config_tab_id', '分类', (string)$menu['config_tab_id'])->setOptions(ConfigModel::getConfigTabAll(-1));
+        $formbuider[] = Form::input('info', '配置名称', $menu['info'])->autofocus(1);
+        $formbuider[] = Form::input('desc', '配置简介', $menu['desc']);
+        switch ($menu['type']) {
+            case 'text':
+                $menu['value'] = json_decode($menu['value'], true);
+                $formbuider[] = Form::select('input_type', '类型', $menu['input_type'])->setOptions(ConfigModel::texttype());
+                //输入框验证规则
+                $formbuider[] = Form::input('value', '默认值', $menu['value']);
+                if (!empty($menu['required'])) {
+                    $formbuider[] = Form::number('width', '文本框宽(%)', $menu['width']);
+                    $formbuider[] = Form::input('required', '验证规则', $menu['required'])->placeholder('多个请用,隔开例如:required:true,url:true');
+                }
+                break;
+            case 'textarea':
+                $menu['value'] = json_decode($menu['value'], true);
+                //多行文本
+                if (!empty($menu['high'])) {
+                    $formbuider[] = Form::textarea('value', '默认值', $menu['value'])->rows(5);
+                    $formbuider[] = Form::number('width', '文本框宽(%)', $menu['width']);
+                    $formbuider[] = Form::number('high', '多行文本框高(%)', $menu['high']);
+                } else {
+                    $formbuider[] = Form::input('value', '默认值', $menu['value']);
+                }
+                break;
+            case 'radio':
+                $menu['value'] = json_decode($menu['value'], true);
+                $parameter = explode("\n", $menu['parameter']);
+                $options = [];
+                if ($parameter) {
+                    foreach ($parameter as $v) {
+                        $data = explode("=>", $v);
+                        $options[] = ['label' => $data[1], 'value' => $data[0]];
+                    }
+                    $formbuider[] = Form::radio('value', '默认值', $menu['value'])->options($options);
+                }
+                //单选和多选参数配置
+                if (!empty($menu['parameter'])) {
+                    $formbuider[] = Form::textarea('parameter', '配置参数', $menu['parameter'])->placeholder("参数方式例如:\n1=白色\n2=红色\n3=黑色");
+                }
+                break;
+            case 'checkbox':
+                $menu['value'] = json_decode($menu['value'], true) ?: [];
+                $parameter = explode("\n", $menu['parameter']);
+                $options = [];
+                if ($parameter) {
+                    foreach ($parameter as $v) {
+                        $data = explode("=>", $v);
+                        $options[] = ['label' => $data[1], 'value' => $data[0]];
+                    }
+                    $formbuider[] = Form::checkbox('value', '默认值', $menu['value'])->options($options);
+                }
+                //单选和多选参数配置
+                if (!empty($menu['parameter'])) {
+                    $formbuider[] = Form::textarea('parameter', '配置参数', $menu['parameter'])->placeholder("参数方式例如:\n1=白色\n2=红色\n3=黑色");
+                }
+                break;
+            case 'upload':
+                if ($menu['upload_type'] == 1) {
+                    $menu['value'] = json_decode($menu['value'], true);
+                    $formbuider[] = Form::frameImageOne('value', '图片', Url::buildUrl('admin/widget.images/index', array('fodder' => 'value')), (string)$menu['value'])->icon('ios-image')->width('60%')->height('435px');
+                } elseif ($menu['upload_type'] == 2) {
+                    $menu['value'] = json_decode($menu['value'], true) ?: [];
+                    $formbuider[] = Form::frameImages('value', '多图片', Url::buildUrl('admin/widget.images/index', array('fodder' => 'value')), $menu['value'])->maxLength(5)->icon('ios-images')->width('60%')->height('435px')->spin(0);
+                } else {
+                    $menu['value'] = json_decode($menu['value'], true);
+                    $formbuider[] = Form::uploadFileOne('value', '文件', Url::buildUrl('/adminapi/file/upload/1')->domain(true)->build(), $menu['value'])->name('file')->headers([
+                        'Authori-zation' => $this->request->header('Authori-zation')
+                    ]);
+                }
+                //上传类型选择
+                if (!empty($menu['upload_type'])) {
+                    $formbuider[] = Form::radio('upload_type', '上传类型', $menu['upload_type'])->options([['value' => 1, 'label' => '单图'], ['value' => 2, 'label' => '多图'], ['value' => 3, 'label' => '文件']]);
+                }
+                break;
+
+        }
+        $formbuider[] = Form::number('sort', '排序', $menu['sort']);
+        $formbuider[] = Form::radio('status', '状态', $menu['status'])->options([['value' => 1, 'label' => '显示'], ['value' => 2, 'label' => '隐藏']]);
+        return $this->makePostForm('编辑字段', $formbuider, Url::buildUrl('/setting/config/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param Request $request
+     * @param int $id
+     * @return Response
+     * @throws Exception
+     */
+    public function update(Request $request, $id)
+    {
+        $type = request()->post('type');
+        if ($type == 'text' || $type == 'textarea' || $type == 'radio' || ($type == 'upload' && (request()->post('upload_type') == 1 || request()->post('upload_type') == 3))) {
+            $value = request()->post('value');
+        } else {
+            $value = request()->post('value/a');
+        }
+        $data = Util::postMore(['status', 'info', 'desc', 'sort', 'config_tab_id', 'required', 'parameter', ['value', $value], 'upload_type', 'input_type']);
+        $data['value'] = json_encode($data['value']);
+        if(!$menu = ConfigMerchantModel::get($id)) $menu = ConfigModel::get($id);
+        if (!$menu) return $this->fail('编辑的记录不存在!');
+        ConfigMerchantModel::edit($data, $id);
+        \crmeb\services\CacheService::clear();
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return Response
+     */
+    public function delete($id)
+    {
+        if (!ConfigMerchantModel::del($id))
+            return $this->fail(ConfigMerchantModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        ConfigModel::where(['id' => $id])->update(['status' => $status]);
+        return $this->success($status == 0 ? '隐藏成功' : '显示成功');
+    }
+
+    /**
+     * 基础配置
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws FormBuilderException
+     * @throws ModelNotFoundException
+     */
+    public function edit_basics()
+    {
+        $tab_id = $this->request->param('tab_id');
+        if (!$tab_id) $tab_id = 1;
+        $list = ConfigModel::getAll($tab_id, 1, $this->merId);
+        $title = ConfigTabModel::where('id', $tab_id)->value('title');
+        $formbuider = [];
+        foreach ($list as $data) {
+            switch ($data['type']) {
+                case 'text'://文本框
+                    switch ($data['input_type']) {
+                        case 'number':
+                            $data['value'] = json_decode($data['value'], true) ?: 0;
+                            $formbuider[] = Form::number($data['menu_name'], $data['info'], $data['value'])->info($data['desc']);
+                            break;
+                        case 'dateTime':
+                            $formbuider[] = Form::dateTime($data['menu_name'], $data['info'], $data['value'])->info($data['desc']);
+                            break;
+                        case 'color':
+                            $data['value'] = json_decode($data['value'], true) ?: '';
+                            $formbuider[] = Form::color($data['menu_name'], $data['info'], $data['value'])->info($data['desc']);
+                            break;
+                        default:
+                            $data['value'] = json_decode($data['value'], true) ?: '';
+                            $formbuider[] = Form::input($data['menu_name'], $data['info'], $data['value'])->info($data['desc'])->placeholder($data['desc'])->col(13);
+                            break;
+                    }
+                    break;
+                case 'textarea'://多行文本框
+                    $data['value'] = json_decode($data['value'], true) ?: '';
+                    $formbuider[] = Form::textarea($data['menu_name'], $data['info'], $data['value'])->placeholder($data['desc'])->info($data['desc'])->rows(6)->col(13);
+                    break;
+                case 'radio'://单选框
+                    $data['value'] = json_decode($data['value'], true) ?: '0';
+                    $parameter = explode("\n", $data['parameter']);
+                    $options = [];
+                    if ($parameter) {
+                        foreach ($parameter as $v) {
+                            $pdata = explode("=>", $v);
+                            $options[] = ['label' => $pdata[1], 'value' => $pdata[0]];
+                        }
+                        $formbuider[] = Form::radio($data['menu_name'], $data['info'], $data['value'])->options($options)->info($data['desc'])->col(13);
+                    }
+                    break;
+                case 'upload'://文件上传
+                    switch ($data['upload_type']) {
+                        case 1:
+                            $data['value'] = json_decode($data['value'], true) ?: '';
+                            $formbuider[] = Form::frameImageOne($data['menu_name'], $data['info'], Url::buildUrl('admin/widget.images/index', array('fodder' => $data['menu_name'])), $data['value'])->icon('ios-image')->width('60%')->height('435px')->info($data['desc'])->col(13);
+                            break;
+                        case 2:
+                            $data['value'] = json_decode($data['value'], true) ?: [];
+                            $formbuider[] = Form::frameImages($data['menu_name'], $data['info'], Url::buildUrl('admin/widget.images/index', array('fodder' => $data['menu_name'])), $data['value'])->maxLength(5)->icon('ios-image')->width('60%')->height('435px')->info($data['desc'])->col(13);
+                            break;
+                        case 3:
+                            $data['value'] = json_decode($data['value'], true);
+                            $formbuider[] = Form::uploadFileOne($data['menu_name'], $data['info'], Url::buildUrl('/adminapi/file/upload/1')->domain(true)->build(), $data['value'])->name('file')->info($data['desc'])->col(13)->headers([
+                                'Authori-zation' => $this->request->header('Authori-zation'),
+                            ]);
+                            break;
+                    }
+
+                    break;
+                case 'checkbox'://多选框
+                    $data['value'] = json_decode($data['value'], true) ?: [];
+                    $parameter = explode("\n", $data['parameter']);
+                    $options = [];
+                    if ($parameter) {
+                        foreach ($parameter as $v) {
+                            $pdata = explode("=>", $v);
+                            $options[] = ['label' => $pdata[1], 'value' => $pdata[0]];
+                        }
+                        $formbuider[] = Form::checkbox($data['menu_name'], $data['info'], $data['value'])->options($options)->info($data['desc'])->col(13);
+                    }
+                    break;
+                case 'select'://多选框
+                    $data['value'] = json_decode($data['value'], true) ?: [];
+                    $parameter = explode("\n", $data['parameter']);
+                    $options = [];
+                    if ($parameter) {
+                        foreach ($parameter as $v) {
+                            $pdata = explode("=>", $v);
+                            $options[] = ['label' => $pdata[1], 'value' => $pdata[0]];
+                        }
+                        $formbuider[] = Form::select($data['menu_name'], $data['info'], $data['value'])->options($options)->info($data['desc'])->col(13);
+                    }
+                    break;
+            }
+        }
+        return $this->makePostForm($title, $formbuider, Url::buildUrl('/setting/config/save_basics'), 'POST');
+    }
+
+    /**
+     * 保存数据
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function save_basics()
+    {
+        $request = $this->request;
+        if ($request->isPost()) {
+            $post = $request->post();
+            foreach ($post as $k => $v) {
+                if (is_array($v)) {
+                    $res = ConfigMerchantModel::merSet($this->merId)->where('menu_name', $k)->column('upload_type', 'type');
+                    if (!$res) {
+                        $res = ConfigModel::where('menu_name', $k)->column('upload_type', 'type');
+                    }
+                    foreach ($res as $kk => $vv) {
+                        if ($kk == 'upload') {
+                            if ($vv == 1 || $vv == 3) {
+                                $post[$k] = $v[0];
+                            }
+                        }
+                    }
+                }
+            }
+            foreach ($post as $k => $v) {
+                if($this->merId){
+                    if (!ConfigMerchantModel::merSet($this->merId)->where(['menu_name' => $k])->find()) {
+                        $data = ConfigModel::where(['menu_name' => $k])->find()->toArray();
+                        unset($data['id']);
+                        $data = array_merge($data, ['mer_id' => $this->merId, 'value' => json_encode($v)]);
+                        ConfigMerchantModel::create($data);
+                    } else {
+                        ConfigMerchantModel::merSet($this->merId)->where(['menu_name' => $k])->update(['value' => json_encode($v)]);
+                    }
+                } else {
+                    ConfigModel::edit(['value' => json_encode($v)], $k, 'menu_name');
+                }
+            }
+            return $this->success('修改成功');
+        }
+        return $this->fail('修改失败');
+    }
+
+    /**
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function header_basics()
+    {
+        [$type] = Util::getMore([
+            [['type', 'd'], 0],
+        ], $this->request, true);
+        if ($type == 3) {//其它分类
+            $config_tab = [];
+        } else {
+            $config_tab = ConfigModel::getConfigTabAll($type);
+            if (is_object($config_tab)) {
+                $config_tab = $config_tab->toArray();
+            }
+            foreach ($config_tab as &$item) {
+                $item['children'] = ConfigModel::getConfigChildrenTabAll($item['value']);
+            }
+        }
+        return $this->success(compact('config_tab'));
+    }
+
+}

+ 197 - 0
app/adminapi/controller/v1/setting/SystemConfigTab.php

@@ -0,0 +1,197 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+use app\adminapi\controller\AuthController;
+use think\Request;
+use think\facade\Route as Url;
+use crmeb\services\{
+    FormBuilder as Form, UtilService as Util
+};
+use app\models\system\{
+    SystemConfigTab as ConfigTabModel, SystemConfig as ConfigModel
+};
+
+/**
+ * 配置分类
+ * Class SystemConfigTab
+ * @package app\adminapi\controller\v1\setting
+ */
+class SystemConfigTab extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 15],
+            ['status', ''],
+            ['title', '']
+        ], $this->request);
+        $list = ConfigTabModel::getSystemConfigTabPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $form[] = Form::select('pid', '父级分类', 0)->setOptions(function () {
+            $menuList = ConfigTabModel::field(['id', 'pid', 'title'])->select()->toArray();//var_dump($menuList);
+            $list = sort_list_tier($menuList, '顶级', 'pid', 'id');//var_dump($list);
+            $menus = [['value' => 0, 'label' => '顶级按钮']];
+            foreach ($list as $menu) {
+                $menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['title']];
+            }
+            return $menus;
+        })->filterable(1);
+        $form[] = Form::input('title', '分类昵称');
+        $form[] = Form::input('eng_title', '分类字段英文');
+        $form[] = Form::frameInputOne('icon', '图标', Url::buildUrl('admin/widget.widgets/icon', array('fodder' => 'icon')))->icon('ios-ionic')->height('435px');
+        $form[] = Form::radio('type', '类型', 0)->options(self::getConfigType());
+        $form[] = Form::radio('status', '状态', 1)->options([['value' => 1, 'label' => '显示'], ['value' => 2, 'label' => '隐藏']]);
+        $form[] = Form::number('sort', '排序', 0);
+        return $this->makePostForm('添加配置分类', $form, Url::buildUrl('/setting/config_class'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $data = Util::postMore([
+            'eng_title',
+            'status',
+            'title',
+            'icon',
+            ['type', 0],
+            ['sort', 0],
+            ['pid', 0],
+        ]);
+        if (!$data['title']) return $this->fail('请输入按钮名称');
+        ConfigTabModel::create($data);
+        return $this->success('添加配置分类成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        //
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        $menu = ConfigTabModel::get($id)->getData();
+        if (!$menu) return $this->fail('数据不存在!');
+        $form[] = Form::select('pid', '父级分类', (string)$menu['pid'])->setOptions(function () {
+            $menuList = ConfigTabModel::field(['id', 'pid', 'title'])->select()->toArray();//var_dump($menuList);
+            $list = sort_list_tier($menuList, '顶级', 'pid', 'id');//var_dump($list);
+            $menus = [['value' => 0, 'label' => '顶级按钮']];
+            foreach ($list as $menu) {
+                $menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['title']];
+            }
+            return $menus;
+        })->filterable(1);
+        $form[] = Form::input('title', '分类昵称', $menu['title']);
+        $form[] = Form::input('eng_title', '分类字段英文', $menu['eng_title']);
+        $form[] = Form::frameInputOne('icon', '图标', Url::buildUrl('admin/widget.widgets/icon', array('fodder' => 'icon')), $menu['icon'])->icon('ios-ionic')->height('435px');
+        $form[] = Form::radio('type', '类型', $menu['type'])->options(self::getConfigType());
+        $form[] = Form::radio('status', '状态', $menu['status'])->options([['value' => 1, 'label' => '显示'], ['value' => 2, 'label' => '隐藏']]);
+        $form[] = Form::number('sort', '排序', $menu['sort']);
+        return $this->makePostForm('编辑配置分类', $form, Url::buildUrl('/setting/config_class/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param \think\Request $request
+     * @param int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $data = Util::postMore([
+            'title',
+            'status',
+            'eng_title',
+            'icon',
+            ['type', 0],
+            ['sort', 0],
+            ['pid', 0],
+        ]);
+        if (!$data['title']) return $this->fail('请输入分类昵称');
+        if (!$data['eng_title']) return $this->fail('请输入分类字段');
+        if (!ConfigTabModel::get($id)) return $this->fail('编辑的记录不存在!');
+        ConfigTabModel::edit($data, $id);
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response 
+     */
+    public function delete($id)
+    {
+        if (ConfigModel::where('config_tab_id', $id)->count()) return $this->fail('存在下级配置,无法删除!');
+        if (!ConfigTabModel::del($id))
+            return $this->fail(ConfigTabModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /** 定义配置分类,需要添加分类可以手动添加
+     * @return array
+     */
+    public function getConfigType()
+    {
+        return [
+            ['value' => 0, 'label' => '系统']
+            , ['value' => 1, 'label' => '应用']
+            , ['value' => 2, 'label' => '支付']
+            , ['value' => 3, 'label' => '其它']
+        ];
+//        return [
+//            ['value'=>0,'label'=>'系统']
+//            ,['value'=>1,'label'=>'公众号']
+//            ,['value'=>2,'label'=>'小程序']
+//            ,['value'=>3,'label'=>'其它']
+//        ];
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        ConfigTabModel::where(['id' => $id])->update(['status' => $status]);
+
+        return $this->success($status == 0 ? '隐藏成功' : '显示成功');
+    }
+}

+ 180 - 0
app/adminapi/controller/v1/setting/SystemGroup.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+use app\adminapi\controller\AuthController;
+use think\Request;
+use crmeb\services\UtilService as Util;
+use app\models\system\SystemGroup as GroupModel;
+use app\models\system\SystemGroupData as GroupDataModel;
+use app\models\system\SystemGroupDataMerchant as GroupDataMerchantModel;
+
+/**
+ * 组合数据
+ * Class SystemGroup
+ * @package app\adminapi\controller\v1\setting
+ */
+class SystemGroup extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where=Util::getMore([
+            ['page',1],
+            ['limit',15],
+            ['keyword','']
+        ]);
+        $list = GroupModel::list($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $params = Util::postMore([
+            ['name', ''],
+            ['config_name', ''],
+            ['info', ''],
+            ['typelist', []],
+        ], $this->request);
+
+        //数据组名称判断
+        if (!$params['name']) return $this->fail('请输入数据组名称!');
+        if (!$params['config_name']) return $this->fail('请输入配置名称!');
+        $data["name"] = $params['name'];
+        $data["config_name"] = $params['config_name'];
+        $data["info"] = $params['info'];
+        //字段信息判断
+        if (!count($params['typelist']))
+            return $this->fail('字段至少存在一个!');
+        else {
+            $validate = ["name", "type", "title", "description"];
+            foreach ($params["typelist"] as $key => $value) {
+                foreach ($value as $name => $field) {
+                    if (empty($field["value"]) && in_array($name, $validate))
+                        return $this->fail("字段" . ($key + 1) . ":" . $field["placeholder"] . "不能为空!");
+                    else
+                        $data["fields"][$key][$name] = $field["value"];
+                }
+            }
+        }
+        $data["fields"] = json_encode($data["fields"]);
+        GroupModel::create($data);
+        \crmeb\services\CacheService::clear();
+        return $this->success('添加数据组成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        $info = GroupModel::get($id);
+        $fields = json_decode($info['fields'], true);
+        $type_list = [];
+        foreach ($fields as $key => $v) {
+            $type_list[$key]['name']['value'] = $v['name'];
+            $type_list[$key]['title']['value'] = $v['title'];
+            $type_list[$key]['type']['value'] = $v['type'];
+            $type_list[$key]['param']['value'] = $v['param'];
+        }
+        $info['typelist'] = $type_list;
+        unset($info['fields']);
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param \think\Request $request
+     * @param int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $params = Util::postMore([
+            ['name', ''],
+            ['config_name', ''],
+            ['info', ''],
+            ['typelist', []],
+        ], $this->request);
+
+        //数据组名称判断
+        if (!$params['name']) return $this->fail('请输入数据组名称!');
+        if (!$params['config_name']) return $this->fail('请输入配置名称!');
+        //判断ID是否存在,存在就是编辑,不存在就是添加
+        if (!$id) {
+            if (GroupModel::be($params['config_name'], 'config_name')) return $this->fail('数据关键字已存在!');
+        }
+        $data["name"] = $params['name'];
+        $data["config_name"] = $params['config_name'];
+        $data["info"] = $params['info'];
+        //字段信息判断
+        if (!count($params['typelist']))
+            return $this->fail('字段至少存在一个!');
+        else {
+            $validate = ["name", "type", "title", "description"];
+            foreach ($params["typelist"] as $key => $value) {
+                foreach ($value as $name => $field) {
+                    if (empty($field["value"]) && in_array($name, $validate))
+                        return $this->fail("字段" . ($key + 1) . ":" . $field["placeholder"] . "不能为空!");
+                    else
+                        $data["fields"][$key][$name] = $field["value"];
+                }
+            }
+        }
+        $data["fields"] = json_encode($data["fields"]);
+        GroupModel::edit($data, $id);
+        \crmeb\services\CacheService::clear();
+        return $this->success('编辑数据组成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!GroupModel::del($id))
+            return $this->fail(GroupModel::getErrorInfo('删除失败,请稍候再试!'));
+        else {
+            GroupDataMerchantModel::del(["gid" => $id]);
+            return $this->success('删除成功!');
+        }
+    }
+}

+ 280 - 0
app/adminapi/controller/v1/setting/SystemGroupData.php

@@ -0,0 +1,280 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+use app\adminapi\controller\AuthController;
+use think\Request;
+use crmeb\services\{
+    FormBuilder as Form, UtilService as Util
+};
+use think\facade\Route as Url;
+use app\models\system\{
+    SystemGroup as GroupModel, SystemGroupData as GroupDataModel, SystemGroupDataMerchant as GroupDataMerchantModel
+};
+
+/**
+ * 数据管理
+ * Class SystemGroupData
+ * @package app\adminapi\controller\v1\setting
+ */
+class SystemGroupData extends AuthController
+{
+    /**
+     * 获取数据列表头
+     * @return mixed
+     */
+    public function header()
+    {
+        $gid = $this->request->param('gid');
+        if (!$gid) return $this->fail('参数错误');
+        $header = GroupDataModel::getHeader($gid);
+        return $this->success($header);
+    }
+
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['gid', 0],
+            ['page', 1],
+            ['limit', 15],
+            ['status', ''],
+        ], $this->request);
+        $mer_id = $this->merId ?: '';
+        $list = GroupDataMerchantModel::getList($where, $mer_id);
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $gid = $this->request->param('gid');
+        $Fields = GroupModel::getField($gid);
+        $f = array();
+        $f[] = Form::hidden('gid', $gid);
+        foreach ($Fields["fields"] as $key => $value) {
+            $info = [];
+            if (isset($value["param"])) {
+                $value["param"] = str_replace("\r\n", "\n", $value["param"]);//防止不兼容
+                $params = explode("\n", $value["param"]);
+                if (is_array($params) && !empty($params)) {
+                    foreach ($params as $index => $v) {
+                        $vl = explode('=>', $v);
+                        if (isset($vl[0]) && isset($vl[1])) {
+                            $info[$index]["value"] = $vl[0];
+                            $info[$index]["label"] = $vl[1];
+                        }
+                    }
+                }
+            }
+
+            switch ($value["type"]) {
+                case 'input':
+                    $f[] = Form::input($value["title"], $value["name"]);
+                    break;
+                case 'textarea':
+                    $f[] = Form::input($value["title"], $value["name"])->type('textarea')->placeholder($value['param']);
+                    break;
+                case 'radio':
+                    $f[] = Form::radio($value["title"], $value["name"], $info[0]["value"] ?? '')->options($info);
+                    break;
+                case 'checkbox':
+                    $f[] = Form::checkbox($value["title"], $value["name"], $info[0] ?? '')->options($info);
+                    break;
+                case 'select':
+                    $f[] = Form::select($value["title"], $value["name"], $info[0] ?? '')->options($info)->multiple(false);
+                    break;
+                case 'upload':
+                    $f[] = Form::frameImageOne($value["title"], $value["name"], Url::buildUrl('admin/widget.images/index', array('fodder' => $value["title"], 'big' => 1)))->icon('ios-image')->width('60%')->height('435px');
+                    break;
+                case 'uploads':
+                    $f[] = Form::frameImages($value["title"], $value["name"], Url::buildUrl('admin/widget.images/index', array('fodder' => $value["title"], 'big' => 1)))->maxLength(5)->icon('ios-images')->width('60%')->height('435px')->spin(0);
+                    break;
+                default:
+                    $f[] = Form::input($value["title"], $value["name"]);
+                    break;
+
+            }
+        }
+        $f[] = Form::number('sort', '排序', 1);
+        $f[] = Form::radio('status', '状态', 1)->options([['value' => 1, 'label' => '显示'], ['value' => 2, 'label' => '隐藏']]);
+        return $this->makePostForm('添加数据', $f, Url::buildUrl('/setting/group_data'), 'POST');
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $params = request()->post();
+        $Fields = GroupModel::getField($params['gid']);
+        foreach ($params as $key => $param) {
+            foreach ($Fields['fields'] as $index => $field) {
+                if ($key == $field["title"]) {
+//                    if($param == "" || count($param) == 0)
+                    if ($param == "")
+                        return $this->fail($field["name"] . "不能为空!");
+                    else {
+                        $value[$key]["type"] = $field["type"];
+                        $value[$key]["value"] = $param;
+                    }
+                }
+            }
+        }
+        $data = array("gid" => $params['gid'], "add_time" => time(), "value" => json_encode($value), "sort" => $params["sort"], "status" => $params["status"]);
+        $data['mer_id'] = $this->merId ?: '';
+        GroupDataMerchantModel::create($data);
+        return $this->success('添加数据成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        //
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        $gid = $this->request->param('gid');
+        $GroupData = GroupDataMerchantModel::get($id);
+        $GroupDataValue = json_decode($GroupData["value"], true);
+        $Fields = GroupModel::getField($gid);
+        $f = array();
+        $f[] = Form::hidden('gid', $gid);
+        if (!isset($Fields['fields'])) return $this->fail('数据解析失败!');
+        foreach ($Fields['fields'] as $key => $value) {
+            $info = [];
+            if (isset($value["param"])) {
+                $value["param"] = str_replace("\r\n", "\n", $value["param"]);//防止不兼容
+                $params = explode("\n", $value["param"]);
+                if (is_array($params) && !empty($params)) {
+                    foreach ($params as $index => $v) {
+                        $vl = explode('=>', $v);
+                        if (isset($vl[0]) && isset($vl[1])) {
+                            $info[$index]["value"] = $vl[0];
+                            $info[$index]["label"] = $vl[1];
+                        }
+                    }
+                }
+            }
+            $fvalue = isset($GroupDataValue[$value['title']]['value']) ? $GroupDataValue[$value['title']]['value'] : '';
+            switch ($value['type']) {
+                case 'input':
+                    $f[] = Form::input($value['title'], $value['name'], $fvalue);
+                    break;
+                case 'textarea':
+                    $f[] = Form::input($value['title'], $value['name'], $fvalue)->type('textarea');
+                    break;
+                case 'radio':
+
+                    $f[] = Form::radio($value['title'], $value['name'], $fvalue)->options($info);
+                    break;
+                case 'checkbox':
+                    $f[] = Form::checkbox($value['title'], $value['name'], $fvalue)->options($info);
+                    break;
+                case 'upload':
+                    if (!empty($fvalue)) {
+                        $image = is_string($fvalue) ? $fvalue : $fvalue[0];
+                    } else {
+                        $image = '';
+                    }
+                    $f[] = Form::frameImageOne($value['title'], $value['name'], Url::buildUrl('admin/widget.images/index', array('fodder' => $value['title'], 'big' => 1)), $image)->icon('ios-image')->width('60%')->height('435px');
+                    break;
+                case 'uploads':
+                    $images = !empty($fvalue) ? $fvalue : [];
+                    $f[] = Form::frameImages($value['title'], $value['name'], Url::buildUrl('admin/widget.images/index', array('fodder' => $value['title'], 'big' => 1)), $images)->maxLength(5)->icon('ios-images')->width('60%')->height('435px')->spin(0);
+                    break;
+                case 'select':
+                    $f[] = Form::select($value['title'], $value['name'], $fvalue)->setOptions($info);
+                    break;
+                default:
+                    $f[] = Form::input($value['title'], $value['name'], $fvalue);
+                    break;
+
+            }
+        }
+        $f[] = Form::number('sort', '排序', $GroupData["sort"]);
+        $f[] = Form::radio('status', '状态', $GroupData["status"])->options([['value' => 1, 'label' => '显示'], ['value' => 0, 'label' => '隐藏']]);
+        return $this->makePostForm('编辑数据', $f, Url::buildUrl('/setting/group_data/' . $id), 'PUT');
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param \think\Request $request
+     * @param int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $GroupData = GroupDataMerchantModel::get($id);
+        $Fields = GroupModel::getField($GroupData["gid"]);
+        $params = request()->post();
+        foreach ($params as $key => $param) {
+            foreach ($Fields['fields'] as $index => $field) {
+                if ($key == $field["title"]) {
+                    if (trim($param) == '')
+                        return $this->fail($field["name"] . "不能为空!");
+                    else {
+                        $value[$key]["type"] = $field["type"];
+                        $value[$key]["value"] = $param;
+                    }
+                }
+            }
+        }
+        $data = array("value" => json_encode($value), "sort" => $params["sort"], "status" => $params["status"]);
+        $data['mer_id'] = $this->merId ?: '';
+        GroupDataMerchantModel::edit($data, $id);
+        return $this->success('修改成功!');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!GroupDataMerchantModel::merSet($this->merId)->del($id))
+            return $this->fail(GroupDataMerchantModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        GroupDataMerchantModel::merSet($this->merId)->where(['id' => $id])->update(['status' => $status]);
+
+        return $this->success($status == 0 ? '隐藏成功' : '显示成功');
+    }
+}

+ 271 - 0
app/adminapi/controller/v1/setting/SystemMenus.php

@@ -0,0 +1,271 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+
+use app\adminapi\controller\AuthController;
+use crmeb\services\FormBuilder as Form;
+use crmeb\services\UtilService;
+use app\models\system\SystemMenus as SystemMenusAdmin;
+use crmeb\services\UtilService as Util;
+use think\facade\Route as Url;
+use think\Request;
+
+class SystemMenus extends AuthController
+{
+
+
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = UtilService::getMore([
+            ['is_show', ''],
+            ['pid', ''],
+            ['keyword', ''],
+        ]);
+        return $this->success(SystemMenusAdmin::getAuthList($where));
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+
+        $field[] = Form::input('menu_name', '按钮名称')->required('按钮名称必填');
+        $field[] = Form::select('pid', '父级id')->setOptions(function () {
+            $list = sort_list_tier(SystemMenusAdmin::getMenusArray(SystemMenusAdmin::all(function ($m) {
+                $m->order('sort DESC,id ASC')->where('is_del', 0);
+            })), '0', 'pid', 'id');
+            $menus = [['value' => 0, 'label' => '顶级按钮']];
+            foreach ($list as $menu) {
+                $menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['menu_name']];
+            }
+            return $menus;
+        })->filterable(1);
+        $field[] = Form::select('module', '模块名')->options([['label' => '总后台', 'value' => 'admin']]);
+        $field[] = Form::input('controller', '控制器名');
+        $field[] = Form::input('action', '方法名');
+        $field[] = Form::input('menu_path', '路由名称')->placeholder('请输入前台跳转路由地址')->required('请填写前台路由地址');
+        $field[] = Form::input('unique_auth', '权限标识')->placeholder('不填写则后台自动生成');
+        $field[] = Form::input('params', '参数')->placeholder('举例:a/123/b/234');
+        $field[] = Form::frameInputOne('icon', '图标', Url::buildUrl('admin/widget.widgets/icon', array('fodder' => 'icon')))->icon('md-add')->height('500px');
+        $field[] = Form::number('sort', '排序', 0);
+        $field[] = Form::radio('auth_type', '类型', 1)->options([['value' => 2, 'label' => '接口'], ['value' => 1, 'label' => '菜单(菜单只显示三级)']]);
+        $field[] = Form::radio('is_header', '是否顶部菜单', 0)->options([['value' => 0, 'label' => '否'], ['value' => 1, 'label' => '是']]);
+        $field[] = Form::radio('is_show', '状态', 0)->options([['value' => 0, 'label' => '关闭'], ['value' => 1, 'label' => '开启']]);
+        $field[] = Form::radio('is_show_path', '是否为前端隐藏菜单', 0)->options([['value' => 1, 'label' => '是'], ['value' => 0, 'label' => '否']]);
+        $field[] = Form::select('header', '顶部菜单')->setOptions(function () {
+            $menulist = SystemMenusAdmin::all(function ($m) {
+                $m->where(['is_header' => 1])->order('sort DESC,id ASC');
+            })->toArray();
+            $list = sort_list_tier($menulist, '顶级', 'pid', 'id');
+            $menus = [['value' => '', 'label' => '请选择']];
+            foreach ($list as $menu) {
+                $menus[] = ['value' => $menu['header'], 'label' => $menu['menu_name']];
+            }
+            return $menus;
+        })->filterable(1);
+        return $this->makePostForm('添加权限', $field, Url::buildUrl('/setting/save')->suffix(false));
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param  \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request)
+    {
+        $data = UtilService::getMore([
+            ['menu_name', ''],
+            ['controller', ''],
+            ['module', 'admin'],
+            ['action', ''],
+            ['icon', ''],
+            ['params', ''],
+            ['menu_path', ''],
+            ['api_url', ''],
+            ['methods', ''],
+            ['unique_auth', ''],
+            ['header', ''],
+            ['is_header', 0],
+            ['pid', 0],
+            ['sort', 0],
+            ['auth_type', 0],
+            ['access', 1],
+            ['is_show', 0],
+            ['is_show_path', 0],
+        ], $request);
+
+        if (!$data['menu_name'])
+            return $this->fail('请填写按钮名称');
+        if (SystemMenusAdmin::create($data))
+            return $this->success('添加成功');
+        else
+            return $this->fail('添加失败');
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param  int $id
+     * @return \think\Response
+     */
+    public function read($id)
+    {
+        if (!$id || !($menu = SystemMenusAdmin::get($id)))
+            return $this->fail('数据不存在');
+        $menu = $menu->getData();
+        $menu['pid'] = (string)$menu['pid'];
+        $menu['auth_type'] = (string)$menu['auth_type'];
+        $menu['is_header'] = (string)$menu['is_header'];
+        $menu['is_show'] = (string)$menu['is_show'];
+        $menu['is_show_path'] = (string)$menu['is_show_path'];
+        return $this->success($menu);
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param  int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        if (!$id || !($menu = SystemMenusAdmin::get($id)))
+            return $this->fail('数据不存在');
+        $field[] = Form::input('menu_name', '按钮名称', $menu['menu_name']);
+        $field[] = Form::select('pid', '父级id', (string)$menu->getData('pid'))->setOptions(function () use ($id) {
+            $list = (Util::sortListTier(SystemMenusAdmin::all(function ($m) {
+                $m->order('sort DESC,id ASC')->where('is_del', 0);
+            })->toArray(), '顶级', 'pid', 'menu_name'));
+            $menus = [['value' => 0, 'label' => '顶级按钮']];
+            foreach ($list as $menu) {
+                $menus[] = ['value' => (int)$menu['id'], 'label' => $menu['html'] . $menu['menu_name']];
+            }
+            return $menus;
+        })->filterable(1);
+        $field[] = Form::select('module', '模块名', $menu['module'])->options([['label' => '总后台', 'value' => 'admin']]);
+        $field[] = Form::input('controller', '控制器名', $menu['controller']);
+        if (!empty($menu['controller'])) {
+            $controller = preg_replace_callback('/([.]+([a-z]{1}))/i', function ($matches) {
+                return '\\' . strtoupper($matches[2]);
+            }, $menu['controller']);
+            if (class_exists('\app\adminapi\v1\controller\\' . $controller)) {
+                $list = get_this_class_methods('\app\adminapi\v1\controller\\' . $controller);
+
+                $field[] = Form::select('action', '方法名', (string)$menu->getData('action'))->setOptions(function () use ($list) {
+                    $menus = [['value' => 0, 'label' => '默认函数']];
+                    foreach ($list as $menu) {
+                        $menus[] = ['value' => $menu, 'label' => $menu];
+                    }
+                    return $menus;
+                })->filterable(1);
+            } else {
+                $field[] = Form::input('action', '方法名', $menu['action']);
+            }
+        } else {
+            $field[] = Form::input('action', '方法名');
+        }
+        $field[] = Form::input('params', '参数', SystemMenusAdmin::paramStr($menu['params']))->placeholder('举例:a/123/b/234');
+        $field[] = Form::input('menu_path', '路由名称', $menu['menu_path'])->placeholder('请输入前台跳转路由地址')->required('请填写前台路由地址');
+        $field[] = Form::input('unique_auth', '权限标识', $menu['unique_auth'])->placeholder('不填写则后台自动生成');
+        $field[] = Form::frameInputOne('icon', '图标', Url::buildUrl('admin/widget.widgets/icon', array('fodder' => 'icon')), $menu['icon'])->icon('ionic')->height('500px');
+        $field[] = Form::number('sort', '排序', $menu['sort']);
+        $field[] = Form::radio('auth_type', '是否菜单', $menu['is_show'])->options([['value' => 0, 'label' => '隐藏'], ['value' => 1, 'label' => '显示(菜单只显示三级)']]);
+        $field[] = Form::radio('is_header', '是否顶部菜单', $menu['is_header'])->options([['value' => 0, 'label' => '否'], ['value' => 1, 'label' => '是']]);
+        $field[] = Form::radio('is_show_path', '是否为前端隐藏菜单', $menu['is_show_path'])->options([['value' => 0, 'label' => '否'], ['value' => 1, 'label' => '是']]);
+        $field[] = Form::select('header', '顶部菜单', (string)$menu->getData('header'))->setOptions(function () use ($id) {
+            $list = (Util::sortListTier(SystemMenusAdmin::all(function ($m) {
+                $m->where(['is_header' => 1]);
+            })->toArray(), '顶级', 'header', 'menu_name'));
+            $list = SystemMenusAdmin::where(['is_header' => 1])->select()->toArray();
+            dd($list);
+            $menus = [['value' => 0, 'label' => '请选择']];
+            foreach ($list as $menu) {
+                $menus[] = ['value' => (int)$menu['header'], 'label' => $menu['html'] . $menu['menu_name']];
+            }
+            return $menus;
+        })->filterable(1);
+        return $this->makePostForm('修改权限', $field, Url::buildUrl('/setting/update/' . $id)->suffix(false));
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param  \think\Request $request
+     * @param  int $id
+     * @return \think\Response
+     */
+    public function update(Request $request, $id)
+    {
+        if (!$id || !($menu = SystemMenusAdmin::get($id)))
+            return $this->fail('数据不存在');
+        $data = UtilService::postMore([
+            'menu_name',
+            'controller',
+            ['module', 'admin'],
+            'action',
+            'params',
+            ['icon', ''],
+            ['menu_path', ''],
+            ['api_url', ''],
+            ['methods', ''],
+            ['unique_auth', ''],
+            ['sort', 0],
+            ['pid', 0],
+            ['is_header', 0],
+            ['header', ''],
+            ['auth_type', 0],
+            ['access', 1],
+            ['is_show', 0],
+            ['is_show_path', 0],
+        ], $request);
+        if (!$data['menu_name'])
+            return $this->fail('请输入按钮名称');
+        if (SystemMenusAdmin::edit($data, $id))
+            return $this->success('修改成功');
+        else
+            return $this->fail('修改失败');
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param  int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!$id)
+            return $this->fail('参数错误,请重新打开');
+        $res = SystemMenusAdmin::delMenu($id);
+        if (!$res)
+            return $this->fail(SystemMenusAdmin::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 显示和隐藏
+     * @param $id
+     * @return mixed
+     */
+    public function show($id)
+    {
+        if (!$id)
+            return $this->fail('参数错误,请重新打开');
+        [$show] = UtilService::postMore([['is_show', 0]], $this->request, true);
+        if (SystemMenusAdmin::edit(['is_show' => $show], $id))
+            return $this->success('修改成功');
+        else
+            return $this->fail('修改失败');
+    }
+}

+ 119 - 0
app/adminapi/controller/v1/setting/SystemRole.php

@@ -0,0 +1,119 @@
+<?php
+
+namespace app\adminapi\controller\v1\setting;
+
+use app\models\system\SystemRole as RoleModel;
+use app\adminapi\controller\AuthController;
+use app\models\system\SystemMenus;
+use crmeb\services\UtilService as Util;
+use think\Request;
+
+class SystemRole extends AuthController
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['status', ''],
+            ['role_name', ''],
+        ], $this->request);
+        $where['level'] = $this->adminInfo['level'];
+//        $where['level'] = -1;
+        $list = RoleModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create()
+    {
+        $menus = $this->adminInfo['level'] == 0 ? SystemMenus::ruleList() : SystemMenus::rolesByRuleList($this->adminInfo['roles']);
+        return $this->success(compact('menus'));
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param \think\Request $request
+     * @return \think\Response
+     */
+    public function save(Request $request, $id)
+    {
+        $data = Util::postMore([
+            'role_name',
+            ['status', 0],
+            ['checked_menus', [], '', 'rules']
+        ]);
+        if (!$data['role_name']) return $this->fail('请输入身份名称');
+        if (!is_array($data['rules']) || !count($data['rules']))
+            return $this->fail('请选择最少一个权限');
+        $data['rules'] = implode(',', $data['rules']);
+        if ($id) {
+            if (!RoleModel::edit($data, $id)) return $this->fail('修改失败!');
+            return $this->success('修改成功!');
+        } else {
+            $data['level'] = $this->adminInfo['level'] + 1;
+            if (!RoleModel::create($data)) return $this->fail('添加身份失败!');
+            return $this->success('添加身份成功!');
+        }
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function edit($id)
+    {
+        $role = RoleModel::get($id);
+        $menus = $this->adminInfo['level'] == 0 ? SystemMenus::ruleList() : SystemMenus::rolesByRuleList($this->adminInfo['roles']);
+        return $this->success(['role' => $role, 'menus' => $menus]);
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param int $id
+     * @return \think\Response
+     */
+    public function delete($id)
+    {
+        if (!RoleModel::del($id))
+            return $this->fail(RoleModel::getErrorInfo('删除失败,请稍候再试!'));
+        else
+            return $this->success('删除成功!');
+    }
+
+    /**
+     * 修改状态
+     * @param $id
+     * @param $status
+     * @return mixed
+     */
+    public function set_status($id, $status)
+    {
+        if (!$id) {
+            return $this->fail('缺少参数');
+        }
+        $role = RoleModel::get($id);
+        if (!$role) {
+            return $this->fail('没有查到此身份');
+        }
+        $role->status = $status;
+        if ($role->save()) {
+            return $this->success('修改成功');
+        } else {
+            return $this->fail('修改失败');
+        }
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/system/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/system

+ 79 - 0
app/adminapi/controller/v1/system/Clear.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace app\adminapi\controller\v1\system;
+
+use app\adminapi\controller\AuthController;
+
+/**
+ * 首页控制器
+ * Class Clear
+ * @package app\admin\controller
+ *
+ */
+class Clear extends AuthController
+{
+    /**
+     * 刷新数据缓存
+     */
+    public function refresh_cache()
+    {
+        $root = app()->getRootPath() . 'runtime' . DS;
+        $adminRoute = $root . 'admin';
+        $apiRoute = $root . 'api';
+        $cacheRoute = $root . 'cache';
+        $cache = [];
+
+        if (is_dir($adminRoute))
+            $cache[$adminRoute] = scandir($adminRoute);
+        if (is_dir($apiRoute))
+            $cache[$apiRoute] = scandir($apiRoute);
+        if (is_dir($cacheRoute))
+            $cache[$cacheRoute] = scandir($cacheRoute);
+
+        foreach ($cache as $p => $list) {
+            foreach ($list as $file) {
+                if (!in_array($file, ['.', '..', 'log', 'schema', 'route.php'])) {
+                    $path = $p . DS . $file;
+                    if (is_file($path)) {
+                        @unlink($path);
+                    } else {
+                        $this->delDirAndFile($path . DS);
+                    }
+                }
+            }
+        }
+        return $this->success('数据缓存刷新成功!');
+    }
+
+
+    /**
+     * 删除日志
+     */
+    public function delete_log()
+    {
+        $root = app()->getRootPath() . 'runtime' . DS;
+        $this->delDirAndFile($root . 'admin' . DS . 'log' . DS);
+        $this->delDirAndFile($root . 'api' . DS . 'log' . DS);
+        $this->delDirAndFile($root . 'log' . DS);
+
+        return $this->success('数据缓存刷新成功!');
+    }
+
+    /** 递归删除文件
+     * @param $dirName
+     * @param bool $subdir
+     */
+    protected function delDirAndFile($dirName)
+    {
+        $list = glob($dirName . '*');
+        foreach ($list as $file) {
+            if (is_dir($file))
+                $this->delDirAndFile($file . DS);
+            else
+                @unlink($file);
+        }
+        @rmdir($dirName);
+    }
+}
+
+

+ 278 - 0
app/adminapi/controller/v1/system/SystemClearData.php

@@ -0,0 +1,278 @@
+<?php
+
+namespace app\adminapi\controller\v1\system;
+
+use app\adminapi\controller\AuthController;
+use app\models\store\StoreProduct;
+use app\models\system\SystemAttachment;
+use crmeb\services\UtilService;
+use think\facade\Db;
+use think\facade\Config;
+
+/**
+ * 清除默认数据理控制器
+ * Class SystemClearData
+ * @package app\admin\controller\system
+ */
+class SystemClearData extends AuthController
+{
+    /**
+     * 统一方法
+     * @param $type
+     */
+    public function index($type)
+    {
+        switch ($type) {
+            case 'temp':
+                return $this->userTemp();
+                break;
+            case 'recycle':
+                return $this->recycleProduct();
+                break;
+            case 'user':
+                return $this->userRelevantData();
+                break;
+            case 'store':
+                return $this->storeData();
+                break;
+            case 'category':
+                return $this->categoryData();
+                break;
+            case 'order':
+                return $this->orderData();
+                break;
+            case 'kefu':
+                return $this->kefuData();
+                break;
+            case 'wechat':
+                return $this->wechatData();
+                break;
+            case 'attachment':
+                return $this->attachmentData();
+                break;
+            case 'wechatuser':
+                return $this->wechatuserData();
+                break;
+            case 'article':
+                return $this->articledata();
+                break;
+            case 'system':
+                return $this->systemdata();
+                break;
+            default:
+                return $this->fail('参数有误');
+        }
+    }
+
+    /**
+     * 清除用户生成的临时附件
+     * @param int $type
+     * @throws \Exception
+     */
+    public function userTemp()
+    {
+        SystemAttachment::where('module_type', 2)->delete();
+        return $this->success('清除数据成功!');
+    }
+
+    //清除回收站商品
+    public function recycleProduct()
+    {
+        StoreProduct::where('is_del', 1)->delete();
+        return $this->success('清除数据成功!');
+    }
+
+    //清除用户数据
+    public function userRelevantData()
+    {
+        self::clearData('user_recharge', 1);
+        self::clearData('user_address', 1);
+        self::clearData('user_bill', 1);
+        self::clearData('user_enter', 1);
+        self::clearData('user_extract', 1);
+        self::clearData('user_notice', 1);
+        self::clearData('user_notice_see', 1);
+        self::clearData('wechat_qrcode', 1);
+        self::clearData('wechat_message', 1);
+        self::clearData('store_visit', 1);
+        self::clearData('store_coupon_user', 1);
+        self::clearData('store_coupon_issue_user', 1);
+        self::clearData('store_bargain_user', 1);
+        self::clearData('store_bargain_user_help', 1);
+        self::clearData('store_product_reply', 1);
+        self::clearData('store_product_cate', 1);
+        self::clearData('routine_qrcode', 1);
+        self::clearData('routine_form_id', 1);
+        self::clearData('user_sign', 1);
+        self::clearData('user_task_finish', 1);
+        self::clearData('user_level', 1);
+        self::clearData('user_token', 1);
+        self::clearData('user_group', 1);
+        self::clearData('user_visit', 1);
+        $this->delDirAndFile('./public/uploads/store/comment');
+        self::clearData('store_product_relation', 1);
+        return $this->success('清除数据成功!');
+    }
+
+    //清除商城数据
+    public function storeData()
+    {
+        self::clearData('store_coupon', 1);
+        self::clearData('store_coupon_issue', 1);
+        self::clearData('store_bargain', 1);
+        self::clearData('store_combination', 1);
+        self::clearData('store_product_attr', 1);
+        self::clearData('store_product_cate', 1);
+        self::clearData('store_product_attr_result', 1);
+        self::clearData('store_product_attr_value', 1);
+        self::clearData('store_product_description', 1);
+        self::clearData('store_product_rule', 1);
+        self::clearData('store_seckill', 1);
+        self::clearData('store_product', 1);
+        self::clearData('store_visit', 1);
+        return $this->success('清除数据成功!');
+    }
+
+    //清除商品分类
+    public function categoryData()
+    {
+        self::clearData('store_category', 1);
+        return $this->success('清除数据成功!');
+    }
+
+    //清除订单数据
+    public function orderData()
+    {
+        self::clearData('store_order', 1);
+        self::clearData('store_order_cart_info', 1);
+        self::clearData('store_order_status', 1);
+        self::clearData('store_pink', 1);
+        self::clearData('store_cart', 1);
+        self::clearData('store_order_status', 1);
+        return $this->success('清除数据成功!');
+    }
+
+    //清除客服数据
+    public function kefuData()
+    {
+        self::clearData('store_service', 1);
+        $this->delDirAndFile('./public/uploads/store/service');
+        self::clearData('store_service_log', 1);
+        return $this->success('清除数据成功!');
+    }
+
+    //清除微信管理数据
+    public function wechatData()
+    {
+        self::clearData('wechat_media', 1);
+        self::clearData('wechat_reply', 1);
+        self::clearData('cache', 1);
+        $this->delDirAndFile('./public/uploads/wechat');
+        return $this->success('清除数据成功!');
+    }
+
+    //清除所有附件
+    public function attachmentData()
+    {
+        self::clearData('system_attachment', 1);
+        self::clearData('system_attachment_category', 1);
+        $this->delDirAndFile('./public/uploads/');
+        return $this->success('清除上传文件成功!');
+    }
+
+    //清除微信用户
+    public function wechatuserData()
+    {
+        self::clearData('wechat_user', 1);
+        self::clearData('user', 1);
+        return $this->success('清除数据成功!');
+    }
+
+    //清除内容分类
+    public function articledata()
+    {
+        self::clearData('article_category', 1);
+        self::clearData('article', 1);
+        self::clearData('article_content', 1);
+        return $this->success('清除数据成功!');
+    }
+
+    //清除系统记录
+    public function systemdata()
+    {
+        self::clearData('system_notice_admin', 1);
+        self::clearData('system_log', 1);
+        return $this->success('清除数据成功!');
+    }
+
+    //清除制定表数据
+    public function clearData($table_name, $status)
+    {
+        $table_name = config('database.connections.' . config('database.default'))['prefix'] . $table_name;
+        if ($status) {
+            @db::execute('TRUNCATE TABLE ' . $table_name);
+        } else {
+            @db::execute('DELETE FROM' . $table_name);
+        }
+
+    }
+
+    //递归删除文件
+    function delDirAndFile($dirName, $subdir = true)
+    {
+        if ($handle = @opendir("$dirName")) {
+            while (false !== ($item = readdir($handle))) {
+                if ($item != "." && $item != "..") {
+                    if (is_dir("$dirName/$item"))
+                        $this->delDirAndFile("$dirName/$item", false);
+                    else
+                        @unlink("$dirName/$item");
+                }
+            }
+            closedir($handle);
+            if (!$subdir) @rmdir($dirName);
+        }
+    }
+
+    /**
+     * 替换域名方法
+     * @return mixed
+     */
+    public function replaceSiteUrl()
+    {
+        list($url) = UtilService::postMore([
+            ['url','']
+        ], $this->request, true);
+        if (!$url)
+            return $this->fail('请输入需要更换的域名');
+        if (!verify_domain($url))
+            return $this->fail('域名不合法');
+        $siteUrl = sys_config('site_url');
+        $siteUrlJosn = str_replace('http://', 'http:\\/\\/', $siteUrl);
+        $valueJosn = str_replace('http://', 'http:\\/\\/', $url);
+        $prefix = Config::get('database.connections.' . Config::get('database.default') . '.prefix');
+        $sql = [
+            "UPDATE `{$prefix}system_attachment` SET `att_dir` = replace(att_dir ,'{$siteUrl}','{$url}'),`satt_dir` = replace(satt_dir ,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}store_product` SET `image` = replace(image ,'{$siteUrl}','{$url}'),`slider_image` = replace(slider_image ,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}store_product_attr_value` SET `image` = replace(image ,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}store_seckill` SET `image` = replace(image ,'{$siteUrl}','{$url}'),`images` = replace(images,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}store_combination` SET `image` = replace(image ,'{$siteUrl}','{$url}'),`images` = replace(images,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}store_bargain` SET `image` = replace(image ,'{$siteUrl}','{$url}'),`images` = replace(images,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}system_config` SET `value` = replace(value ,'{$siteUrlJosn}','{$valueJosn}')",
+            "UPDATE `{$prefix}article_category` SET `image` = replace(`image` ,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}article` SET `image_input` = replace(`image_input` ,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}article_content` SET `content` = replace(`content` ,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}store_category` SET `pic` = replace(`pic` ,'{$siteUrl}','{$url}')",
+            "UPDATE `{$prefix}system_group_data` SET `value` = replace(value ,'{$siteUrlJosn}','{$valueJosn}')",
+            "UPDATE `{$prefix}store_product_description` SET `description`= replace(description,'{$siteUrl}','{$url}')"
+        ];
+        try {
+            foreach ($sql as $item) {
+                db::execute($item);
+            }
+        } catch (\Throwable $e) {
+            return $this->fail('替换失败,失败原因:' . $e->getMessage());
+        }
+        return $this->success('替换成功!');
+    }
+}

+ 190 - 0
app/adminapi/controller/v1/system/SystemDatabackup.php

@@ -0,0 +1,190 @@
+<?php
+
+namespace app\adminapi\controller\v1\system;
+
+use app\adminapi\controller\AuthController;
+use app\models\system\SystemConfig;
+use \crmeb\services\MysqlBackupService as Backup;
+use crmeb\services\UtilService;
+use think\facade\Env;
+use think\facade\Session;
+use think\facade\Db;
+
+/**
+ * 数据备份
+ * Class SystemDatabackup
+ * @package app\admin\controller\system
+ *
+ */
+class SystemDatabackup extends AuthController
+{
+    /**
+     * @var Backup
+     */
+    protected $DB;
+
+    public function initialize()
+    {
+        parent::initialize();
+        $config = array(
+            //数据库备份卷大小
+            'compress' => 1,
+            //数据库备份文件是否启用压缩 0不压缩 1 压缩
+            'level' => 5,
+        );
+        $this->DB = new Backup($config);
+    }
+
+    /**
+     * 获取数据库表
+     */
+    public function index()
+    {
+        $db = $this->DB;
+        $list = $db->dataList();
+        $count = count($list);
+        return $this->success(compact('count', 'list'));
+    }
+
+    /**
+     * 查看表结构 详情
+     */
+    public function read()
+    {
+        $database = Env::get("database.database");
+        $tablename = request()->param('tablename', '', 'htmlspecialchars');
+        $res = Db::query("select * from information_schema.columns where table_name = '" . $tablename . "' and table_schema = '" . $database . "'");
+        $count = count($res);
+        foreach ($res AS $key => $f) {
+            $res[$key]['EXTRA'] = ($f['EXTRA'] == 'auto_increment' ? '是' : ' ');
+        }
+        return $this->success(['count' => $count, 'list' => $res]);
+    }
+
+    /**
+     * 优化表
+     */
+    public function optimize()
+    {
+        $tables = $this->request->param('tables');
+        $db = $this->DB;
+        $res = $db->optimize($tables);
+        return $this->success($res ? '优化成功' : '优化失败');
+    }
+
+    /**
+     * 修复表
+     */
+    public function repair()
+    {
+        $tables = $this->request->param('tables');
+        $db = $this->DB;
+        $res = $db->repair($tables);
+        return $this->success($res ? '修复成功' : '修复失败');
+    }
+
+    /**
+     * 备份表
+     */
+    public function backup()
+    {
+        $tables = $this->request->param('tables');
+        $tables = explode(',', $tables);
+        $db = $this->DB;
+        $data = '';
+        foreach ($tables as $t) {
+            $res = $db->backup($t, 0);
+            if ($res == false && $res != 0) {
+                $data .= $t . '|';
+            }
+        }
+        return $this->success($data ? '备份失败' . $data : '备份成功');
+    }
+
+    /**
+     * 获取备份记录表
+     */
+    public function fileList()
+    {
+        $db = $this->DB;
+        $files = $db->fileList();
+        $data = [];
+        foreach ($files as $key => $t) {
+            $data[$key]['filename'] = $t['filename'];
+            $data[$key]['part'] = $t['part'];
+            $data[$key]['size'] = $t['size'] . 'B';
+            $data[$key]['compress'] = $t['compress'];
+            $data[$key]['backtime'] = $key;
+            $data[$key]['time'] = $t['time'];
+        }
+        krsort($data);//根据时间降序
+        return $this->success(['count' => count($data), 'list' => array_values($data)]);
+    }
+
+    /**
+     * 删除备份记录表
+     */
+    public function delFile()
+    {
+        $filename = intval(request()->post('filename'));
+        $files = $this->DB->delFile($filename);
+        return $this->success('删除成功');
+    }
+
+    /**
+     * 导入备份记录表
+     */
+    public function import()
+    {
+        [$part, $start, $time] = UtilService::postMore([
+            [['part', 'd'], 0],
+            [['start', 'd'], 0],
+            [['time', 'd'], 0],
+        ], null, true);
+        $db = $this->DB;
+        if (is_numeric($time) && !$start) {
+            $list = $db->getFile('timeverif', $time);
+            if (is_array($list)) {
+                session::set('backup_list', $list);
+                return $this->success('初始化完成!', array('part' => 1, 'start' => 0));
+            } else {
+                return $this->fail('备份文件可能已经损坏,请检查!');
+            }
+        } else if (is_numeric($part) && is_numeric($start) && $part && $start) {
+            $list = session::get('backup_list');
+            $start = $db->setFile($list)->import($start);
+            if (false === $start) {
+                return $this->fail('还原数据出错!');
+            } elseif (0 === $start) {
+                if (isset($list[++$part])) {
+                    $data = array('part' => $part, 'start' => 0);
+                    return $this->success("正在还原...#{$part}", $data);
+                } else {
+                    session::delete('backup_list');
+                    return $this->success('还原完成!');
+                }
+            } else {
+                $data = array('part' => $part, 'start' => $start[0]);
+                if ($start[1]) {
+                    $rate = floor(100 * ($start[0] / $start[1]));
+                    return $this->success("正在还原...#{$part}({$rate}%)", $data);
+                } else {
+                    $data['gz'] = 1;
+                    return $this->success("正在还原...#{$part}", $data);
+                }
+                return $this->success("正在还原...#{$part}");
+            }
+        } else {
+            return $this->fail('参数错误!');
+        }
+    }
+
+    /**
+     * 下载备份记录表
+     */
+    public function downloadFile()
+    {
+        $time = intval(request()->param('time'));
+        $this->DB->downloadFile($time);
+    }
+}

+ 256 - 0
app/adminapi/controller/v1/system/SystemFile.php

@@ -0,0 +1,256 @@
+<?php
+
+namespace app\adminapi\controller\v1\system;
+
+use app\adminapi\controller\AuthController;
+use app\models\system\SystemFile as SystemFileModel;
+use crmeb\services\FileService as FileClass;
+
+/**
+ * 文件校验控制器
+ * Class SystemFile
+ * @package app\admin\controller\system
+ *
+ */
+class SystemFile extends AuthController
+{
+    /**
+     * 文件校验记录
+     * @return mixed
+     */
+    public function index()
+    {
+        $rootPath = app()->getRootPath();
+        $app = $this->getDir($rootPath . 'app');
+        $extend = $this->getDir($rootPath . 'crmeb');
+        $public = $this->getDir($rootPath . 'public');
+        $arr = array();
+        $arr = array_merge($app, $extend);
+        $arr = array_merge($arr, $public);
+        $fileAll = array();//本地文件
+        $cha = array();//不同的文件
+        foreach ($arr as $k => $v) {
+            $fp = fopen($v, 'r');
+            if (filesize($v)) $ct = fread($fp, filesize($v));
+            else $ct = null;
+            fclose($fp);
+            $cthash = md5($ct);
+            $update_time = stat($v);
+            $fileAll[$k]['cthash'] = $cthash;
+            $fileAll[$k]['filename'] = substr($v, strlen($rootPath));
+            $fileAll[$k]['atime'] = $update_time['atime'];
+            $fileAll[$k]['mtime'] = $update_time['mtime'];
+            $fileAll[$k]['ctime'] = $update_time['ctime'];
+        }
+        $file = SystemFileModel::all(function ($query) {
+            $query->order('atime', 'desc');
+        })->toArray();//数据库中的文件
+        if (empty($file)) {
+            $data_num = array_chunk($fileAll, 10);
+            SystemFileModel::beginTrans();
+            $res = true;
+            foreach ($data_num as $k => $v) {
+                $res = $res && SystemFileModel::insertAll($v);
+            }
+            SystemFileModel::checkTrans($res);
+            if ($res) {
+                $cha = array();//不同的文件
+            } else {
+                $cha = $fileAll;
+            }
+        } else {
+            $cha = array();//差异文件
+            foreach ($file as $k => $v) {
+                foreach ($fileAll as $ko => $vo) {
+                    if ($v['filename'] == $vo['filename']) {
+                        if ($v['cthash'] != $vo['cthash']) {
+//                            $cha[$k]['filename'] = $v['filename'];
+//                            $cha[$k]['cthash'] = $v['cthash'];
+//                            $cha[$k]['atime'] = $v['atime'];
+//                            $cha[$k]['mtime'] = $v['mtime'];
+//                            $cha[$k]['ctime'] = $v['ctime'];
+//                            $cha[$k]['type'] = '已修改';
+                            $cha[] = [
+                                'filename' => $v['filename'],
+                                'cthash' => $v['cthash'],
+                                'atime' => date('Y-m-d H:i:s', $v['atime']),
+                                'mtime' => date('Y-m-d H:i:s', $v['mtime']),
+                                'ctime' => date('Y-m-d H:i:s', $v['ctime']),
+                                'type' => '已修改',
+
+                            ];
+                        }
+                        unset($fileAll[$ko]);
+                        unset($file[$k]);
+                    }
+                }
+
+            }
+            foreach ($file as $k => $v) {
+//                $cha[$k]['filename'] = $v['filename'];
+//                $cha[$k]['cthash'] = $v['cthash'];
+//                $cha[$k]['atime'] = $v['atime'];
+//                $cha[$k]['mtime'] = $v['mtime'];
+//                $cha[$k]['ctime'] = $v['ctime'];
+//                $cha[$k]['type'] = '已删除';
+                $cha[] = [
+                    'filename' => $v['filename'],
+                    'cthash' => $v['cthash'],
+                    'atime' => date('Y-m-d H:i:s', $v['atime']),
+                    'mtime' => date('Y-m-d H:i:s', $v['mtime']),
+                    'ctime' => date('Y-m-d H:i:s', $v['ctime']),
+                    'type' => '已删除',
+
+                ];
+            }
+            foreach ($fileAll as $k => $v) {
+//                $cha[$k]['filename'] = $v['filename'];
+//                $cha[$k]['cthash'] = $v['cthash'];
+//                $cha[$k]['atime'] = $v['atime'];
+//                $cha[$k]['mtime'] = $v['mtime'];
+//                $cha[$k]['ctime'] = $v['ctime'];
+//                $cha[$k]['type'] = '新增的';
+                $cha[] = [
+                    'filename' => $v['filename'],
+                    'cthash' => $v['cthash'],
+                    'atime' => date('Y-m-d H:i:s', $v['atime']),
+                    'mtime' => date('Y-m-d H:i:s', $v['mtime']),
+                    'ctime' => date('Y-m-d H:i:s', $v['ctime']),
+                    'type' => '新增的',
+
+                ];
+            }
+
+        }
+        array_multisort(array_column($cha, 'ctime'), SORT_DESC, $cha);
+        return $this->success(['list' => $cha]);
+    }
+
+    /**
+     * 获取文件夹中的文件 包括子文件 不能直接用  直接使用  $this->getDir()方法 P156
+     * @param $path
+     * @param $data
+     */
+    public function searchDir($path, &$data)
+    {
+        if (is_dir($path) && !strpos($path, 'uploads')) {
+            $dp = dir($path);
+            while ($file = $dp->read()) {
+                if ($file != '.' && $file != '..') {
+                    $this->searchDir($path . '/' . $file, $data);
+                }
+            }
+            $dp->close();
+        }
+        if (is_file($path)) {
+            $data[] = $path;
+        }
+    }
+
+    /**
+     * 获取文件夹中的文件 包括子文件
+     * @param $dir
+     * @return array
+     */
+    public function getDir($dir)
+    {
+        $data = array();
+        $this->searchDir($dir, $data);
+        return $data;
+    }
+
+    //打开目录
+    public function opendir()
+    {
+        $fileAll = array('dir' => [], 'file' => []);
+        //根目录
+        $rootdir = app()->getRootPath();
+//        return $rootdir;
+        //当前目录
+        $request_dir = app('request')->param('dir');
+        //防止查看站点以外的目录
+        if (strpos($request_dir, $rootdir) === false) {
+            $request_dir = $rootdir;
+        }
+        //判断是否是返回上级
+        if (app('request')->param('superior') && !empty($request_dir)) {
+            if (strpos(dirname($request_dir), $rootdir) !== false) {
+                $dir = dirname($request_dir);
+            } else {
+                $dir = $rootdir;
+            }
+
+        } else {
+            $dir = !empty($request_dir) ? $request_dir : $rootdir;
+            $dir = rtrim($dir, DS) . DS . app('request')->param('filedir');
+        }
+        $list = scandir($dir);
+        foreach ($list as $key => $v) {
+            if ($v != '.' && $v != '..') {
+                if (is_dir($dir . DS . $v)) {
+                    $fileAll['dir'][] = FileClass::listInfo($dir . DS . $v);
+                }
+                if (is_file($dir . DS . $v)) {
+                    $fileAll['file'][] = FileClass::listInfo($dir . DS . $v);
+                }
+            }
+        }
+        //兼容windows
+        $uname = php_uname('s');
+        if (strstr($uname, 'Windows') !== false) {
+            $dir = ltrim($dir, '\\');
+            $rootdir = str_replace('\\', '\\\\', $rootdir);
+        }
+        $list = array_merge($fileAll['dir'], $fileAll['file']);
+        foreach ($list as $key => $value) {
+            $list[$key]['real_path'] = str_replace($rootdir, '', $value['pathname']);
+            $list[$key]['mtime'] = date('Y-m-d H:i:s',$value['mtime']);
+        }
+        return $this->success(compact('dir', 'list'));
+    }
+
+    //读取文件
+    public function openfile()
+    {
+        $file = $this->request->param('filepath');
+        if (empty($file)) return $this->fail('出现错误');
+        $filepath = $file;
+        $content = FileClass::readFile($filepath);//防止页面内嵌textarea标签
+        $ext = FileClass::getExt($filepath);
+        $extarray = [
+            'js' => 'text/javascript'
+            , 'php' => 'text/x-php'
+            , 'html' => 'text/html'
+            , 'sql' => 'text/x-mysql'
+            , 'css' => 'text/x-scss'];
+        $mode = empty($extarray[$ext]) ? '' : $extarray[$ext];
+        return $this->success(compact('content', 'mode', 'filepath'));
+    }
+
+    //保存文件
+    public function savefile()
+    {
+        $comment = $this->request->param('comment');
+        $filepath = $this->request->param('filepath');
+        if (!empty($comment) && !empty($filepath)) {
+            //兼容windows
+            $uname = php_uname('s');
+            if (strstr($uname, 'Windows') !== false)
+                $filepath = ltrim(str_replace('/', DS, $filepath), '.');
+            if (FileClass::isWritable($filepath)) {
+                $res = FileClass::writeFile($filepath, $comment);
+                if ($res) {
+                    return $this->success('保存成功!');
+                } else {
+                    return $this->fail('保存失败');
+                }
+            } else {
+                return $this->fail('没有权限!');
+            }
+
+        } else {
+            return $this->fail('出现错误');
+        }
+
+    }
+}

+ 45 - 0
app/adminapi/controller/v1/system/SystemLog.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace app\adminapi\controller\v1\system;
+
+use app\models\system\SystemAdmin;
+use app\models\system\SystemLog as LogModel;
+use app\adminapi\controller\AuthController;
+use crmeb\services\UtilService as Util;
+
+/**
+ * 管理员操作记录表控制器
+ * Class SystemLog
+ * @package app\adminapi\controller\v1\system
+ */
+class SystemLog extends AuthController
+{
+
+    /**
+     * 显示操作记录
+     */
+    public function index()
+    {
+        LogModel::deleteLog();
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['pages', ''],
+            ['path', ''],
+            ['ip', ''],
+            ['admin_id', ''],
+            ['data', ''],
+        ], $this->request);
+        $where['level'] = $this->adminInfo['level'];
+        $list = LogModel::systemPage($where);
+        return $this->success($list);
+    }
+
+    public function search_admin()
+    {
+        $info = SystemAdmin::getOrdAdmin('id,real_name', $this->adminInfo['level']);
+        return $this->success(compact('info'));
+    }
+
+}
+

+ 210 - 0
app/adminapi/controller/v1/system/SystemUpgradeClient.php

@@ -0,0 +1,210 @@
+<?php
+
+namespace app\adminapi\controller\v1\system;
+
+use app\adminapi\controller\AuthController;
+use crmeb\services\UpgradeService as uService;
+use app\models\system\SystemConfig;
+use crmeb\services\UtilService;
+use think\facade\Db;
+
+/**
+ * 在线升级控制器
+ * Class SystemUpgradeclient
+ * @package app\admin\controller\system
+ *
+ */
+class SystemUpgradeClient extends AuthController
+{
+
+    protected $serverweb = array('version' => '1.0', 'version_code' => 0);//本站点信息
+
+    public function initialize()
+    {
+        parent::initialize();
+        //屏蔽所有错误避免操作文件夹发生错误提示
+        ini_set('display_errors', 0);
+        error_reporting(0);
+        self::snyweninfo();//更新站点信息
+        $this->assign(['auth' => self::isauth(), 'app' => uService::isWritable(app()->getRootPath()), 'extend' => uService::isWritable(EXTEND_PATH), 'public' => uService::isWritable(app()->getRootPath() . 'public')]);
+    }
+
+    //同步更新站点信息
+    public function snyweninfo()
+    {
+        $this->serverweb['ip'] = $this->request->ip();
+        $this->serverweb['host'] = $this->request->host();
+        $this->serverweb['https'] = !empty($this->request->domain()) ? $this->request->domain() : SystemConfig::getConfigValue('site_url');
+        $this->serverweb['webname'] = SystemConfig::getConfigValue('site_name');
+        $local = uService::getVersion();
+        if ($local['code'] == 200 && isset($local['msg']['version']) && isset($local['msg']['version_code'])) {
+            $this->serverweb['version'] = uService::replace($local['msg']['version']);
+            $this->serverweb['version_code'] = (int)uService::replace($local['msg']['version_code']);
+        }
+        uService::snyweninfo($this->serverweb);
+    }
+
+    //是否授权
+    public function isauth()
+    {
+        return uService::isauth();
+    }
+
+    /**
+     * 升级列表
+     */
+    public function index()
+    {
+        $where = UtilService::getMore([
+            ['page', 1],
+            ['limit', 20]
+        ]);
+        $list = uService::request_post(uService::$isList, ['page' => $where['page'], 'limit' => $where['limit']]);
+        if (is_array($list) && isset($list['code']) && isset($list['data']) && $list['code'] == 200) {
+            $list = $list['data'];
+        } else {
+            $list = [];
+        }
+        return $this->success($list);
+    }
+
+    //删除备份文件
+    public function setcopydel()
+    {
+        $post = input('post.');
+        if (!isset($post['id'])) $this->fail('删除备份文件失败,缺少参数ID');
+        if (!isset($post['ids'])) $this->fail('删除备份文件失败,缺少参数IDS');
+        $fileservice = new uService;
+        if (is_array($post['ids'])) {
+            foreach ($post['ids'] as $file) {
+                $fileservice->del_dir(app()->getRootPath() . 'public' . DS . 'copyfile' . $file);
+            }
+        }
+        if ($post['id']) {
+            $copyFile = app()->getRootPath() . 'public' . DS . 'copyfile' . $post['id'];
+            $fileservice->del_dir($copyFile);
+        }
+        return $this->success('删除成功');
+    }
+
+    public function get_new_version_conte()
+    {
+        $post = $this->request->post();
+        if (!isset($post['id'])) $this->fail('缺少参数ID');
+        $versionInfo = uService::request_post(uService::$NewVersionCount, ['id' => $post['id']]);
+        if (isset($versionInfo['code']) && isset($versionInfo['data']['count']) && $versionInfo['code'] == 200) {
+            return $this->success(['count' => $versionInfo['data']['count']]);
+        } else {
+            return $this->fail('服务器异常');
+        }
+    }
+
+    //一键升级
+    public function auto_upgrade()
+    {
+        $prefix = config('database.prefix');
+        $fileservice = new uService;
+        $post = $this->request->post();
+        if (!isset($post['id'])) return $this->fail('缺少参数ID');
+        $versionInfo = $fileservice->request_post(uService::$isNowVersion, ['id' => $post['id']]);
+        if ($versionInfo === null) return $this->fail('服务器异常,请稍后再试');
+        if (isset($versionInfo['code']) && $versionInfo['code'] == 400) return $this->fail(isset($versionInfo['msg']) ? $versionInfo['msg'] : '您暂时没有权限升级,请联系管理员!');
+        if (is_array($versionInfo) && isset($versionInfo['data'])) {
+            $list = $versionInfo['data'];
+            $id = [];
+            foreach ($list as $key => $val) {
+                $savefile = app()->getRootPath() . 'public' . DS . 'upgrade_lv';
+                //1,检查远程下载文件,并下载
+                if (($save_path = $fileservice->check_remote_file_exists($val['zip_name'], $savefile)) === false) $this->fail('远程升级包不存在');
+                //2,首先解压文件
+                $savename = app()->getRootPath() . 'public' . DS . 'upgrade_lv' . DS . time();
+                $fileservice->zipOpen($save_path, $savename);
+                //3,执行SQL文件
+                Db::startTrans();
+                try {
+                    //参数3不介意大小写的
+                    $sqlfile = $fileservice->listDirInfo($savename . DS, true, 'sql');
+                    if (is_array($sqlfile) && !empty($sqlfile)) {
+                        foreach ($sqlfile as $file) {
+                            if (file_exists($file)) {
+                                //为一键安装做工作记得表前缀要改为[#DB_PREFIX#]哦
+                                $execute_sql = explode(";\r", str_replace(['[#DB_PREFIX#]', "\n"], [$prefix, "\r"], file_get_contents($file)));
+                                foreach ($execute_sql as $_sql) {
+                                    if ($query_string = trim(str_replace(array(
+                                        "\r",
+                                        "\n",
+                                        "\t"
+                                    ), '', $_sql))) Db::execute($query_string);
+                                }
+                                //执行完sql记得删掉哦
+                                $fileservice->unlinkFile($file);
+                            }
+                        }
+                    }
+                    Db::commit();
+                } catch (\Exception $e) {
+                    Db::rollback();
+                    //删除解压下的文件
+                    $fileservice->del_dir(app()->getRootPath() . 'public' . DS . 'upgrade_lv');
+                    //删除压缩包
+                    $fileservice->unlinkFile($save_path);
+                    //升级失败发送错误信息
+                    $fileservice->request_post(uService::$isInsertLog, [
+                        'content' => '升级失败,错误信息为:' . $e->getMessage(),
+                        'add_time' => time(),
+                        'ip' => $this->request->ip(),
+                        'http' => $this->request->domain(),
+                        'type' => 'error',
+                        'version' => $val['version']
+                    ]);
+                    return $this->fail('升级失败SQL文件执行有误');
+                }
+                //4,备份文件
+                $copyFile = app()->getRootPath() . 'public' . DS . 'copyfile' . $val['id'];
+                $copyList = $fileservice->getDirs($savename . DS);
+                if (isset($copyList['dir'])) {
+                    if ($copyList['dir'][0] == '.' && $copyList['dir'][1] == '..') {
+                        array_shift($copyList['dir']);
+                        array_shift($copyList['dir']);
+                    }
+                    foreach ($copyList['dir'] as $dir) {
+                        if (file_exists(app()->getRootPath() . $dir, $copyFile . DS . $dir)) {
+                            $fileservice->copyDir(app()->getRootPath() . $dir, $copyFile . DS . $dir);
+                        }
+                    }
+                }
+                //5,覆盖文件
+                $fileservice->handleDir($savename, app()->getRootPath());
+                //6,删除升级生成的目录
+                $fileservice->del_dir(app()->getRootPath() . 'public' . DS . 'upgrade_lv');
+                //7,删除压缩包
+                $fileservice->unlinkFile($save_path);
+                //8,改写本地升级文件
+                $handle = fopen(app()->getRootPath() . '.version', 'w+');
+                if ($handle === false) return $this->fail(app()->getRootPath() . '.version' . '无法写入打开');
+                $content = <<<EOT
+version={$val['version']}
+version_code={$val['id']}
+EOT;
+                if (fwrite($handle, $content) === false) return $this->fail('升级包写入失败');
+                fclose($handle);
+                //9,向服务端发送升级日志
+                $posts = [
+                    'ip' => $this->request->ip(),
+                    'https' => $this->request->domain(),
+                    'update_time' => time(),
+                    'content' => '一键升级成功,升级版本号为:' . $val['version'] . '。版本code为:' . $val['id'],
+                    'type' => 'log',
+                    'versionbefor' => $this->serverweb['version'],
+                    'versionend' => $val['version']
+                ];
+                $inset = $fileservice->request_post(uService::$isInsertLog, $posts);
+                $id[] = $val['id'];
+            }
+            //10,升级完成
+            return $this->success('升级成功', ['code' => end($id), 'version' => $val['version']]);
+        } else {
+            return $this->fail('服务器异常,请稍后再试');
+        }
+    }
+}

+ 1 - 0
app/adminapi/controller/v1/user/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/controller/v1/user

+ 1100 - 0
app/adminapi/controller/v1/user/User.php

@@ -0,0 +1,1100 @@
+<?php
+
+namespace app\adminapi\controller\v1\user;
+
+use app\adminapi\controller\AuthController;
+use Exception;
+use FormBuilder\exception\FormBuilderException;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\Response;
+use think\response\Json;
+use app\models\user\{
+    User as UserModel,
+    UserLabelRelation,
+    UserLabel,
+    UserLevel,
+    UserBill as UserBillAdmin,
+    UserTaskFinish,
+    UserGroup
+};
+use app\models\system\{
+    SystemUserLevel, SystemUserTask
+};
+use app\models\store\{
+    StoreOrder, StoreVisit, StoreCouponUser
+};
+use app\models\wechat\WechatMessage;
+use crmeb\basic\BaseModel;
+use crmeb\repositories\UserRepository;
+use crmeb\services\{
+    FormBuilder as Form, UtilService as Util
+};
+use think\Request;
+use think\facade\Route as Url;
+
+class User extends AuthController
+{
+    /**
+     * 显示资源列表头部
+     *
+     * @return Response
+     */
+    public function type_header()
+    {
+        //全部会员
+        $all = UserModel::merSet($this->merId)->count();
+        //小程序会员
+        $routine = UserModel::merSet($this->merId)->alias('u')->join('WechatUser w', 'u.uid=w.uid')->where('w.routine_openid', '<>', '')->count();
+        //公众号会员
+        $wechat = UserModel::merSet($this->merId)->alias('u')->join('WechatUser w', 'u.uid=w.uid')->where('w.openid', '<>', '')->count();
+        //H5会员
+        $h5 = UserModel::merSet($this->merId)->alias('u')->join('WechatUser w', 'u.uid=w.uid')->where(['w.routine_openid' => '', 'w.openid' => ''])->where('u.user_type', 'h5')->count();
+        $list = [
+            ['user_type' => '', 'name' => '全部会员', 'count' => $all],
+            ['user_type' => 'routine', 'name' => '小程序会员', 'count' => $routine],
+            ['user_type' => 'wechat', 'name' => '公众号会员', 'count' => $wechat],
+            ['user_type' => 'h5', 'name' => 'H5会员', 'count' => $h5],
+        ];
+        return $this->success(compact('list'));
+    }
+
+
+    /**
+     * 显示资源列表
+     *
+     * @return Response
+     * @throws Exception
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+            ['nickname', ''],
+            ['status', ''],
+            ['pay_count', ''],
+            ['is_promoter', ''],
+            ['order', ''],
+            ['data', ''],
+            ['user_type', ''],
+            ['country', ''],
+            ['province', ''],
+            ['city', ''],
+            ['user_time_type', ''],
+            ['user_time', ''],
+            ['sex', ''],
+            [['level', 0], 0],
+            [['group_id', 'd'], 0],
+            [['label_id', 'd'], 0]
+        ]);
+        $where['mer_id'] = $this->merId;
+        return $this->success(UserModel::getUserList($where));
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return void
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param Request $request
+     * @return void
+     */
+    public function save(Request $request)
+    {
+        //
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param int $id
+     * @return Response
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function read($id)
+    {
+        if (!$id) {
+            return $this->fail('参数错误');
+        }
+        if (!UserModel::be(['uid' => $id, 'mer_id' => $this->merId])) {
+            return $this->fail('参数错误');
+        }
+        $info = [
+            'uid' => $id,
+            'userinfo' => UserModel::getUserDetailed($id),
+            'is_layui' => true,
+            'headerList' => UserModel::getHeaderList($id),
+            'count' => UserModel::getCountInfo($id),
+            'ps_info' => UserModel::where('uid', $id)->find()
+        ];
+        return $this->success($info);
+    }
+
+    /**
+     * 赠送会员等级
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function give_level($id)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        $mer_id = $this->merId ?: '';
+        if (!UserModel::be(['uid' => $id, 'mer_id' => $mer_id])) {
+            return $this->fail('参数错误');
+        }
+        $level = UserLevel::getUserLevel($id);
+        //获取当前会员等级
+        if ($level === false)
+            $grade = 0;
+        else
+            $grade = UserLevel::getUserLevelInfo($level, 'grade');
+        //查询高于当前会员的所有会员等级
+        $systemLevelList = SystemUserLevel::merSet($this->merId)->where('grade', '>', $grade)->where(['is_show' => 1, 'is_del' => 0])->field(['name', 'id'])->select();
+        $field[] = Form::select('level_id', '会员等级')->setOptions(function () use ($systemLevelList) {
+            $menus = [];
+            foreach ($systemLevelList as $menu) {
+                $menus[] = ['value' => $menu['id'], 'label' => $menu['name']];
+            }
+            return $menus;
+        })->filterable(1);
+        return $this->makePostForm('赠送会员', $field, Url::buildUrl('/user/save_give_level/' . $id), 'PUT');
+    }
+
+    /**
+     * 执行赠送会员等级
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function save_give_level($id)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        if (!UserModel::be(['uid' => $id, 'mer_id' => $this->merId])) {
+            return $this->fail('参数错误');
+        }
+        list($level_id) = Util::postMore([
+            ['level_id', 0],
+        ], $this->request, true);
+        //查询当前选择的会员等级
+        $systemLevel = SystemUserLevel::merSet($this->merId)->where(['is_show' => 1, 'is_del' => 0, 'id' => $level_id])->find();
+        if (!$systemLevel) return $this->fail('您选择赠送的会员等级不存在!');
+        //检查是否拥有此会员等级
+        $level = UserLevel::where(['uid' => $id, 'level_id' => $level_id, 'is_del' => 0])->field('valid_time,is_forever')->find();
+        if ($level) {
+            if ($level['is_forever']) {
+                return $this->fail('此用户已有该会员等级,无法再次赠送');
+            } else {
+                if (time() < $level['valid_time'])
+                    return $this->fail('此用户已有该会员等级,无法再次赠送');
+            }
+        }
+
+        //设置会员过期时间
+        $add_valid_time = (int)$systemLevel->valid_date * 86400;
+        UserModel::commitTrans();
+        try {
+            //保存会员信息
+            $res = UserLevel::setUserLevel($id, $level_id, $this->merId);
+            //提取等级任务并记录完成情况
+            $levelIds = [$level_id];
+            $lowGradeLevelIds = SystemUserLevel::merSet($this->merId)->where('grade', '<', $systemLevel->grade)->where(['is_show' => 1, 'is_del' => 0])->column('id', 'id');
+            if (count($lowGradeLevelIds)) $levelIds = array_merge($levelIds, $lowGradeLevelIds);
+            $taskIds = SystemUserTask::where('level_id', 'in', $levelIds)->column('id', 'id');
+            $inserValue = [];
+            foreach ($taskIds as $taskId) {
+                $inserValue[] = ['uid' => $id, 'task_id' => $taskId, 'status' => 1, 'add_time' => time()];
+            }
+            $res = $res && UserTaskFinish::insertAll($inserValue);
+            if ($res) {
+                UserModel::commitTrans();
+                return $this->success('赠送成功');
+            } else {
+                UserModel::rollbackTrans();
+                return $this->success('赠送失败');
+            }
+        } catch (Exception $e) {
+            UserModel::rollbackTrans();
+            return $this->fail('赠送失败');
+        }
+    }
+
+    /**
+     * 清除会员等级
+     * @param $id
+     * @return Response
+     */
+    public function del_level($id)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        if (!UserModel::be(['uid' => $id, 'mer_id' => $this->merId])) {
+            return $this->fail('参数错误');
+        }
+        if (UserLevel::cleanUpLevel($id))
+            return $this->success('清除成功');
+        else
+            return $this->fail('清除失败');
+    }
+
+    /**
+     * 设置会员分组
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function set_group()
+    {
+        list($uids) = Util::postMore([
+            ['uids', []],
+        ], $this->request, true);
+        $uid = implode(',', $uids);
+        if (!$uid) return $this->fail('缺少参数');
+        if (UserModel::where(['uid' => $uids, 'mer_id' => $this->merId])->count() != count($uids)) {
+            return $this->fail('参数错误');
+        }
+        $userGroup = UserGroup::select();
+        if (count($uids) == 1) {
+            $user = UserModel::where('uid', $uids[0])->find();
+            $field[] = Form::select('group_id', '会员分组', (string)$user->getData('group_id'))->setOptions(function () use ($userGroup) {
+                $menus = [];
+                foreach ($userGroup as $menu) {
+                    $menus[] = ['value' => $menu['id'], 'label' => $menu['group_name']];
+                }
+                return $menus;
+            })->filterable(1);
+        } else {
+            $field[] = Form::select('group_id', '会员分组')->setOptions(function () use ($userGroup) {
+                $menus = [];
+                foreach ($userGroup as $menu) {
+                    $menus[] = ['value' => $menu['id'], 'label' => $menu['group_name']];
+                }
+                return $menus;
+            })->filterable(1);
+        }
+        $field[] = Form::hidden('uids', $uid);
+        return $this->makePostForm('设置会员分组', $field, Url::buildUrl('/user/save_set_group'), 'PUT');
+    }
+
+    /**
+     * 保存会员分组
+     * @param $id
+     * @return mixed
+     * @throws Exception
+     */
+    public function save_set_group()
+    {
+        list($group_id, $uids) = Util::postMore([
+            ['group_id', 0],
+            ['uids', ''],
+        ], $this->request, true);
+        if (!$uids) return $this->fail('缺少参数');
+        $uids = explode(',', $uids);
+        if (UserModel::where(['uid' => $uids, 'mer_id' => $this->merId])->count() != count($uids)) {
+            return $this->fail('参数错误');
+        }
+        $res = UserModel::whereIn('uid', $uids)->update(['group_id' => $group_id]);
+        if ($res) {
+            return $this->success('设置分组成功');
+        } else {
+            return $this->fail('设置分组失败或无改动');
+        }
+    }
+
+    /**
+     * 设置用户标签
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function set_label()
+    {
+        list($uids) = Util::postMore([
+            ['uids', []],
+        ], $this->request, true);
+        $uid = implode(',', $uids);
+        if (!$uid) return $this->fail('缺少参数');
+        if (UserModel::where(['uid' => $uids, 'mer_id' => $this->merId])->count() != count($uids)) {
+            return $this->fail('参数错误');
+        }
+        $userGroup = UserLabel::select();
+        if (count($uids) == 1) {
+            $lids = UserLabelRelation::where('uid', $uids[0])->column('label_id');
+            $field[] = Form::select('label_id', '会员标签', $lids)->setOptions(function () use ($userGroup) {
+                $menus = [];
+                foreach ($userGroup as $menu) {
+                    $menus[] = ['value' => $menu['id'], 'label' => $menu['label_name']];
+                }
+                return $menus;
+            })->filterable(1)->multiple(1);
+        } else {
+            $field[] = Form::select('label_id', '会员标签')->setOptions(function () use ($userGroup) {
+                $menus = [];
+                foreach ($userGroup as $menu) {
+                    $menus[] = ['value' => $menu['id'], 'label' => $menu['label_name']];
+                }
+                return $menus;
+            })->filterable(1)->multiple(1);
+        }
+        $field[] = Form::hidden('uids', $uid);
+        return $this->makePostForm('设置会员标签', $field, Url::buildUrl('/user/save_set_label'), 'PUT');
+    }
+
+    /**
+     * 保存用户标签
+     * @return mixed
+     * @throws Exception
+     */
+    public function save_set_label()
+    {
+        list($lables, $uids) = Util::postMore([
+            ['label_id', ''],
+            ['uids', ''],
+        ], $this->request, true);
+        if (!$uids) return $this->fail('缺少参数');
+        $uids = explode(',', $uids);
+        if (UserModel::where([['uid', 'in', $uids], ['mer_id', '=', $this->merId]])->count() != count($uids)) {
+            return $this->fail('参数错误');
+        }
+        $res = UserLabelRelation::saveUserLabel($uids, $lables);
+        if ($res) {
+            return $this->success('设置标签成功');
+        } else {
+            return $this->fail('设置标签失败');
+        }
+    }
+
+    /**
+     * 编辑其他
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws FormBuilderException
+     * @throws ModelNotFoundException
+     */
+    public function edit_other($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $user = UserModel::merSet($this->merId)->where('uid', $id)->find();
+        if (!$user) return $this->fail('数据不存在!');
+        $f = array();
+        $f[] = Form::radio('money_status', '修改余额', 1)->options([['value' => 1, 'label' => '增加'], ['value' => 2, 'label' => '减少']]);
+        $f[] = Form::number('money', '余额', 0)->min(0);
+        $f[] = Form::radio('integration_status', '修改积分', 1)->options([['value' => 1, 'label' => '增加'], ['value' => 2, 'label' => '减少']]);
+        $f[] = Form::number('integration', '积分', 0)->min(0);
+        return $this->makePostForm('修改其他', $f, Url::buildUrl('/user/update_other/' . $id), 'PUT');
+    }
+
+    /**
+     * 执行编辑其他
+     * @param int $id
+     * @return mixed
+     * @throws Exception
+     */
+    public function update_other($id)
+    {
+        $data = Util::postMore([
+            ['money_status', 0],
+            ['money', 0],
+            ['integration_status', 0],
+            ['integration', 0],
+        ], $this->request);
+        if (!$id) return $this->fail('数据不存在');
+        $user = UserModel::merSet($this->merId)->where('uid', $id)->find();
+        if (!$user) return $this->fail('数据不存在!');
+        BaseModel::beginTrans();
+        $res1 = false;
+        $res2 = false;
+        $edit = array();
+        if ($data['money_status'] && $data['money']) {//余额增加或者减少
+            if ($data['money_status'] == 1) {//增加
+                $edit['now_money'] = bcadd($user['now_money'], $data['money'], 2);
+                $res1 = UserBillAdmin::income('系统增加余额', $user['uid'], 'now_money', 'system_add', $data['money'], $this->adminId, $edit['now_money'], '系统增加了' . floatval($data['money']) . '余额');
+                try {
+                    UserRepository::adminAddMoney($user, $data['money']);
+                } catch (Exception $e) {
+                    BaseModel::rollbackTrans();
+                    return $this->fail($e->getMessage());
+                }
+            } else if ($data['money_status'] == 2) {//减少
+                $edit['now_money'] = bcsub($user['now_money'], $data['money'], 2);
+                $res1 = UserBillAdmin::expend('系统减少余额', $user['uid'], 'now_money', 'system_sub', $data['money'], $this->adminId, $edit['now_money'], '系统扣除了' . floatval($data['money']) . '余额');
+                try {
+                    UserRepository::adminSubMoney($user, $data['money']);
+                } catch (Exception $e) {
+                    BaseModel::rollbackTrans();
+                    return $this->fail($e->getMessage());
+                }
+            }
+        } else {
+            $res1 = true;
+        }
+        if ($data['integration_status'] && $data['integration']) {//积分增加或者减少
+            if ($data['integration_status'] == 1) {//增加
+                $edit['integral'] = bcadd($user['integral'], $data['integration'], 2);
+                $res2 = UserBillAdmin::income('系统增加积分', $user['uid'], 'integral', 'system_add', $data['integration'], $this->adminId, $edit['integral'], '系统增加了' . floatval($data['integration']) . '积分');
+                try {
+                    UserRepository::adminAddIntegral($user, $data['integration']);
+                } catch (Exception $e) {
+                    BaseModel::rollbackTrans();
+                    return $this->fail($e->getMessage());
+                }
+            } else if ($data['integration_status'] == 2) {//减少
+                $edit['integral'] = bcsub($user['integral'], $data['integration'], 2);
+                $res2 = UserBillAdmin::expend('系统减少积分', $user['uid'], 'integral', 'system_sub', $data['integration'], $this->adminId, $edit['integral'], '系统扣除了' . floatval($data['integration']) . '积分');
+                try {
+                    UserRepository::adminSubIntegral($user, $data['integration']);
+                } catch (Exception $e) {
+                    BaseModel::rollbackTrans();
+                    return $this->fail($e->getMessage());
+                }
+            }
+        } else {
+            $res2 = true;
+        }
+        if ($edit) $res3 = UserModel::edit($edit, $id);
+        else $res3 = true;
+        if ($res1 && $res2 && $res3) $res = true;
+        else $res = false;
+        BaseModel::checkTrans($res);
+        if ($res) return $this->success('修改成功!');
+        else return $this->fail('修改失败');
+    }
+
+    /**
+     * 修改user表状态
+     *
+     * @return array
+     */
+    public function set_status($status, $id)
+    {
+        if ($status == '' || $id == 0) return $this->fail('参数错误');
+        UserModel::merSet($this->merId)->where(['uid' => $id])->update(['status' => $status]);
+
+        return $this->success($status == 0 ? '禁用成功' : '解禁成功');
+    }
+
+    /**
+     * 编辑会员信息
+     * @param $id
+     * @return mixed|Json|void
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws FormBuilderException
+     * @throws ModelNotFoundException
+     */
+    public function edit($id)
+    {
+        if (!$id) return $this->fail('数据不存在');
+        $user = UserModel::merSet($this->merId)->where('uid', $id)->find();
+        if (!$user) return $this->fail('数据不存在!');
+        $f = array();
+        $f[] = Form::input('uid', '用户编号', $user->getData('uid'))->disabled(1);
+        $f[] = Form::input('real_name', '真实姓名', $user->getData('real_name'));
+        $f[] = Form::input('phone', '手机号码', $user->getData('phone'));
+        $f[] = Form::date('birthday', '生日', $user->getData('birthday') ? date('Y-m-d', $user->getData('birthday')) : '');
+        $f[] = Form::input('card_id', '身份证号', $user->getData('card_id'));
+        $f[] = Form::input('addres', '用户地址', $user->getData('addres'));
+        $f[] = Form::textarea('mark', '用户备注', $user->getData('mark'));
+        $level = UserLevel::getUserLevel($id);
+        //获取当前会员等级
+        if ($level === false)
+            $grade = 0;
+        else
+            $grade = UserLevel::getUserLevelInfo($level, 'grade');
+        //查询高于当前会员的所有会员等级
+        $systemLevelList = SystemUserLevel::merSet($this->merId)->where('grade', '>', $grade)->where(['is_show' => 1, 'is_del' => 0])->field(['name', 'id'])->select();
+        $f[] = Form::select('level', '会员等级', (string)$user->getData('level'))->setOptions(function () use ($systemLevelList) {
+            $menus = [];
+            foreach ($systemLevelList as $menu) {
+                $menus[] = ['value' => $menu['id'], 'label' => $menu['name']];
+            }
+            return $menus;
+        })->filterable(1);
+        $systemGroupList = UserGroup::select();
+        $f[] = Form::select('group_id', '会员分组', (string)$user->getData('group_id'))->setOptions(function () use ($systemGroupList) {
+            $menus = [];
+            foreach ($systemGroupList as $menu) {
+                $menus[] = ['value' => $menu['id'], 'label' => $menu['group_name']];
+            }
+            return $menus;
+        })->filterable(1);
+        $systemLabelList = UserLabel::select();
+        $labels = UserLabelRelation::where('uid', $user->getData('uid'))->column('label_id');
+        $f[] = Form::select('label_id', '会员标签', $labels)->setOptions(function () use ($systemLabelList) {
+            $menus = [];
+            foreach ($systemLabelList as $menu) {
+                $menus[] = ['value' => $menu['id'], 'label' => $menu['label_name']];
+            }
+            return $menus;
+        })->filterable(1)->multiple(1);
+        $f[] = Form::radio('is_promoter', '推广员', $user->getData('is_promoter'))->options([['value' => 1, 'label' => '开启'], ['value' => 0, 'label' => '关闭']]);
+        $f[] = Form::radio('status', '状态', $user->getData('status'))->options([['value' => 1, 'label' => '开启'], ['value' => 0, 'label' => '锁定']]);
+        return $this->makePostForm('编辑', $f, Url::buildUrl('/user/user/' . $id), 'PUT');
+    }
+
+    /**
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function update($id)
+    {
+        $data = Util::postMore([
+            ['money_status', 0],
+            ['is_promoter', 1],
+            ['real_name', ''],
+            ['card_id', ''],
+            ['birthday', ''],
+            ['mark', ''],
+            ['money', 0],
+            ['integration_status', 0],
+            ['integration', 0],
+            ['status', 0],
+            ['level', 0],
+            ['phone', 0],
+            ['addres', ''],
+            ['label_id', ''],
+            ['group_id', 0]
+        ]);
+        if ($data['phone']) {
+            if (!preg_match("/^1[3456789]\d{9}$/", $data['phone'])) return $this->fail('手机号码格式不正确');
+        }
+        if ($data['card_id']) {
+            if (!preg_match('/^[1-9]{1}\d{5}[1-9]{2}\d{9}[Xx0-9]{1}$/', $data['card_id'])) return $this->fail('请输入正确的身份证');
+        }
+
+        if (!$id) return $this->fail('数据不存在');
+        $user = UserModel::merSet($this->merId)->where('uid', $id)->find();
+        if (!$user) return $this->fail('数据不存在!');
+        BaseModel::beginTrans();
+        $res1 = false;
+        $res2 = false;
+        $edit = array();
+        if ($data['money_status'] && $data['money']) {//余额增加或者减少
+            if ($data['money_status'] == 1) {//增加
+                $edit['now_money'] = bcadd($user['now_money'], $data['money'], 2);
+                $res1 = UserBillAdmin::income('系统增加余额', $user['uid'], 'now_money', 'system_add', $data['money'], $this->adminId, $edit['now_money'], '系统增加了' . floatval($data['money']) . '余额');
+                try {
+                    UserRepository::adminAddMoney($user, $data['money']);
+                } catch (Exception $e) {
+                    BaseModel::rollbackTrans();
+                    return $this->fail($e->getMessage());
+                }
+            } else if ($data['money_status'] == 2) {//减少
+                $edit['now_money'] = bcsub($user['now_money'], $data['money'], 2);
+                $res1 = UserBillAdmin::expend('系统减少余额', $user['uid'], 'now_money', 'system_sub', $data['money'], $this->adminId, $edit['now_money'], '系统扣除了' . floatval($data['money']) . '余额');
+                try {
+                    UserRepository::adminSubMoney($user, $data['money']);
+                } catch (Exception $e) {
+                    BaseModel::rollbackTrans();
+                    return $this->fail($e->getMessage());
+                }
+            }
+        } else {
+            $res1 = true;
+        }
+        if ($data['integration_status'] && $data['integration']) {//积分增加或者减少
+            if ($data['integration_status'] == 1) {//增加
+                $edit['integral'] = bcadd($user['integral'], $data['integration'], 2);
+                $res2 = UserBillAdmin::income('系统增加积分', $user['uid'], 'integral', 'system_add', $data['integration'], $this->adminId, $edit['integral'], '系统增加了' . floatval($data['integration']) . '积分');
+                try {
+                    UserRepository::adminAddIntegral($user, $data['integration']);
+                } catch (Exception $e) {
+                    BaseModel::rollbackTrans();
+                    return $this->fail($e->getMessage());
+                }
+            } else if ($data['integration_status'] == 2) {//减少
+                $edit['integral'] = bcsub($user['integral'], $data['integration'], 2);
+                $res2 = UserBillAdmin::expend('系统减少积分', $user['uid'], 'integral', 'system_sub', $data['integration'], $this->adminId, $edit['integral'], '系统扣除了' . floatval($data['integration']) . '积分');
+                try {
+                    UserRepository::adminSubIntegral($user, $data['integration']);
+                } catch (Exception $e) {
+                    BaseModel::rollbackTrans();
+                    return $this->fail($e->getMessage());
+                }
+            }
+        } else {
+            $res2 = true;
+        }
+        UserLabelRelation::saveUserLabel([$id], $data['label_id']);
+        $edit['status'] = $data['status'];
+        $edit['real_name'] = $data['real_name'];
+        $edit['card_id'] = $data['card_id'];
+        $edit['birthday'] = strtotime($data['birthday']);
+        $edit['mark'] = $data['mark'];
+        $edit['is_promoter'] = $data['is_promoter'];
+        $edit['level'] = $data['level'];
+        $edit['phone'] = $data['phone'];
+        $edit['addres'] = $data['addres'];
+        $edit['group_id'] = $data['group_id'];
+        if ($edit) $res3 = UserModel::edit($edit, $id);
+        else $res3 = true;
+        if ($res1 && $res2 && $res3) $res = true;
+        else $res = false;
+        BaseModel::checkTrans($res);
+        if ($res) return $this->success('修改成功!');
+        else return $this->fail('修改失败');
+    }
+
+    /**
+     * TODO:需统一到数据统计模块 start
+     * 用户图表
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     * @throws Exception
+     */
+    public function user_analysis()
+    {
+        $where = Util::getMore([
+            ['nickname', ''],
+            ['status', ''],
+            ['is_promoter', ''],
+            ['date', ''],
+            ['user_type', ''],
+            ['export', 0]
+        ], $this->request);
+        $user_count = UserModel::consume($where, '', true, $this->merId);
+        //头部信息
+        $header = [
+            [
+                'name' => '新增用户',
+                'class' => 'fa-line-chart',
+                'value' => $user_count,
+                'color' => 'red'
+            ],
+            [
+                'name' => '用户留存',
+                'class' => 'fa-area-chart',
+                'value' => $this->gethreaderValue(UserModel::consume($where, '', true, $this->merId), $where) . '%',
+                'color' => 'lazur'
+            ],
+            [
+                'name' => '新增用户总消费',
+                'class' => 'fa-bar-chart',
+                'value' => '¥' . UserModel::consume($where, '', '', $this->merId),
+                'color' => 'navy'
+            ],
+            [
+                'name' => '用户活跃度',
+                'class' => 'fa-pie-chart',
+                'value' => $this->gethreaderValue(UserModel::consume($where, '', true, $this->merId)) . '%',
+                'color' => 'yellow'
+            ],
+        ];
+        $name = ['新增用户', '用户消费'];
+        $dates = $this->get_user_index($where, $name);
+        $user_index = ['name' => json_encode($name), 'date' => json_encode($dates['time']), 'series' => json_encode($dates['series'])];
+        //用户浏览分析
+        $view = StoreVisit::getVisit($where['date'], ['', 'warning', 'info', 'danger'], $this->merId);
+        $view_v1 = WechatMessage::getViweList($where['date'], ['', 'warning', 'info', 'danger'], $this->merId);
+        $view = array_merge($view, $view_v1);
+        $view_v2 = [];
+        foreach ($view as $val) {
+            $view_v2['color'][] = '#' . rand(100000, 339899);
+            $view_v2['name'][] = $val['name'];
+            $view_v2['value'][] = $val['value'];
+        }
+        $view = $view_v2;
+        //消费会员排行用户分析
+        $user_null = UserModel::getUserSpend($where['date'], '', $this->merId);
+        //消费数据
+        $now_number = UserModel::getUserSpend($where['date'], true, $this->merId);
+        list($paren_number, $title) = UserModel::getPostNumber($where['date'], false, 'A.add_time', '消费', $this->merId);
+        if ($paren_number == 0) {
+            $rightTitle = [
+                'number' => $now_number > 0 ? $now_number : 0,
+                'icon' => 'fa-level-up',
+                'title' => $title
+            ];
+        } else {
+            $number = (float)bcsub((string)$now_number, $paren_number, 4);
+            if ($now_number == 0) {
+                $icon = 'fa-level-down';
+            } else {
+                $icon = $now_number > $paren_number ? 'fa-level-up' : 'fa-level-down';
+            }
+            $rightTitle = ['number' => $number, 'icon' => $icon, 'title' => $title];
+        }
+        unset($title, $paren_number, $now_number);
+        list($paren_user_count, $title) = UserModel::getPostNumber($where['date'], true, 'add_time', '', $this->merId);
+        if ($paren_user_count == 0) {
+            $count = $user_count == 0 ? 0 : $user_count;
+            $icon = $user_count == 0 ? 'fa-level-down' : 'fa-level-up';
+        } else {
+            $count = (float)bcsub($user_count, $paren_user_count, 4);
+            $icon = $user_count < $paren_user_count ? 'fa-level-down' : 'fa-level-up';
+        }
+        $leftTitle = [
+            'count' => $count,
+            'icon' => $icon,
+            'title' => $title
+        ];
+        unset($count, $icon, $title);
+        $consume = [
+            'title' => '消费金额为¥' . UserModel::consume($where, '', '', $this->merId),
+            'series' => UserModel::consume($where, 'xiaofei', '', $this->merId),
+            'rightTitle' => $rightTitle,
+            'leftTitle' => $leftTitle,
+        ];
+        $form = UserModel::consume($where, 'form', '', $this->merId);
+        $grouping = UserModel::consume($where, 'grouping', '', $this->merId);
+        return $this->success(compact('header', 'user_index', 'view', 'user_null', 'consume', 'form', 'grouping', 'where'));
+    }
+
+    /**
+     * @param $chart
+     * @param array $where
+     * @return float|int
+     */
+    public function gethreaderValue($chart, $where = [])
+    {
+        if ($where) {
+            switch ($where['date']) {
+                case null:
+                case 'today':
+                case 'week':
+                case 'year':
+                    if ($where['date'] == null) {
+                        $where['date'] = 'month';
+                    }
+                    $sum_user = UserModel::merSet($this->merId)->whereTime('add_time', $where['date'])->count();
+                    if ($sum_user == 0) return 0;
+                    $counts = bcdiv($chart, $sum_user, 4) * 100;
+                    return $counts;
+                    break;
+                case 'quarter':
+                    $quarter = UserModel::getMonth('n');
+                    $quarter[0] = strtotime($quarter[0]);
+                    $quarter[1] = strtotime($quarter[1]);
+                    $sum_user = UserModel::merSet($this->merId)->where('add_time', 'between', $quarter)->count();
+                    if ($sum_user == 0) return 0;
+                    $counts = bcdiv($chart, $sum_user, 4) * 100;
+                    return $counts;
+                default:
+                    //自定义时间
+                    $quarter = explode('-', $where['date']);
+                    $quarter[0] = strtotime($quarter[0]);
+                    $quarter[1] = strtotime($quarter[1]);
+                    $sum_user = UserModel::merSet($this->merId)->where('add_time', 'between', $quarter)->count();
+                    if ($sum_user == 0) return 0;
+                    $counts = bcdiv($chart, $sum_user, 4) * 100;
+                    return $counts;
+                    break;
+            }
+        } else {
+            $num = UserModel::merSet($this->merId)->count();
+            $chart = $num != 0 ? bcdiv($chart, $num, 5) * 100 : 0;
+            return $chart;
+        }
+    }
+
+    /**
+     * @param $where
+     * @param $name
+     * @return array
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function get_user_index($where, $name)
+    {
+        switch ($where['date']) {
+            case null:
+                $days = date("t", strtotime(date('Y-m', time())));
+                $dates = [];
+                $series = [];
+                $times_list = [];
+                foreach ($name as $key => $val) {
+                    for ($i = 1; $i <= $days; $i++) {
+                        if (!in_array($i . '号', $times_list)) {
+                            array_push($times_list, $i . '号');
+                        }
+                        $time = $this->gettime(date("Y-m", time()) . '-' . $i);
+                        if ($key == 0) {
+                            $dates['data'][] = UserModel::merSet($this->merId)->where('add_time', 'between', $time)->count();
+                        } else if ($key == 1) {
+                            $dates['data'][] = UserModel::consume(true, $time, '', $this->merId);
+                        }
+                    }
+                    $dates['name'] = $val;
+                    $dates['type'] = 'line';
+                    $series[] = $dates;
+                    unset($dates);
+                }
+                return ['time' => $times_list, 'series' => $series];
+            case 'today':
+                $dates = [];
+                $series = [];
+                $times_list = [];
+                foreach ($name as $key => $val) {
+                    for ($i = 0; $i <= 24; $i++) {
+                        $strtitle = $i . '点';
+                        if (!in_array($strtitle, $times_list)) {
+                            array_push($times_list, $strtitle);
+                        }
+                        $time = $this->gettime(date("Y-m-d ", time()) . $i);
+                        if ($key == 0) {
+                            $dates['data'][] = UserModel::merSet($this->merId)->where('add_time', 'between', $time)->count();
+                        } else if ($key == 1) {
+                            $dates['data'][] = UserModel::consume(true, $time, '', $this->merId);
+                        }
+                    }
+                    $dates['name'] = $val;
+                    $dates['type'] = 'line';
+                    $series[] = $dates;
+                    unset($dates);
+                }
+                return ['time' => $times_list, 'series' => $series];
+            case "week":
+                $dates = [];
+                $series = [];
+                $times_list = [];
+                foreach ($name as $key => $val) {
+                    for ($i = 0; $i <= 6; $i++) {
+                        if (!in_array('星期' . ($i + 1), $times_list)) {
+                            array_push($times_list, '星期' . ($i + 1));
+                        }
+                        $time = UserModel::getMonth('h', $i);
+                        if ($key == 0) {
+                            $dates['data'][] = UserModel::merSet($this->merId)->where('add_time', 'between', [strtotime($time[0]), strtotime($time[1])])->count();
+                        } else if ($key == 1) {
+                            $dates['data'][] = UserModel::consume(true, [strtotime($time[0]), strtotime($time[1])], '', $this->merId);
+                        }
+                    }
+                    $dates['name'] = $val;
+                    $dates['type'] = 'line';
+                    $series[] = $dates;
+                    unset($dates);
+                }
+                return ['time' => $times_list, 'series' => $series];
+            case 'year':
+                $dates = [];
+                $series = [];
+                $times_list = [];
+                $year = date('Y');
+                foreach ($name as $key => $val) {
+                    for ($i = 1; $i <= 12; $i++) {
+                        if (!in_array($i . '月', $times_list)) {
+                            array_push($times_list, $i . '月');
+                        }
+                        $t = strtotime($year . '-' . $i . '-01');
+                        $arr = explode('/', date('Y-m-01', $t) . '/' . date('Y-m-', $t) . date('t', $t));
+                        if ($key == 0) {
+                            $dates['data'][] = UserModel::merSet($this->merId)->where('add_time', 'between', [strtotime($arr[0]), strtotime($arr[1])])->count();
+                        } else if ($key == 1) {
+                            $dates['data'][] = UserModel::consume(true, [strtotime($arr[0]), strtotime($arr[1])], '', $this->merId);
+                        }
+                    }
+                    $dates['name'] = $val;
+                    $dates['type'] = 'line';
+                    $series[] = $dates;
+                    unset($dates);
+                }
+                return ['time' => $times_list, 'series' => $series];
+            case 'quarter':
+                $dates = [];
+                $series = [];
+                $times_list = [];
+                foreach ($name as $key => $val) {
+                    for ($i = 1; $i <= 4; $i++) {
+                        $arr = $this->gettime('quarter', $i);
+                        if (!in_array(implode('--', $arr) . '季度', $times_list)) {
+                            array_push($times_list, implode('--', $arr) . '季度');
+                        }
+                        if ($key == 0) {
+                            $dates['data'][] = UserModel::merSet($this->merId)->where('add_time', 'between', [strtotime($arr[0]), strtotime($arr[1])])->count();
+                        } else if ($key == 1) {
+                            $dates['data'][] = UserModel::consume(true, [strtotime($arr[0]), strtotime($arr[1])], '', $this->merId);
+                        }
+                    }
+                    $dates['name'] = $val;
+                    $dates['type'] = 'line';
+                    $series[] = $dates;
+                    unset($dates);
+                }
+                return ['time' => $times_list, 'series' => $series];
+            default:
+                $list = UserModel::consume($where, 'default', '', $this->merId);
+                $dates = [];
+                $series = [];
+                $times_list = [];
+                foreach ($name as $k => $v) {
+                    foreach ($list as $val) {
+                        $date = $val['add_time'];
+                        if (!in_array($date, $times_list)) {
+                            array_push($times_list, $date);
+                        }
+                        if ($k == 0) {
+                            $dates['data'][] = $val['num'];
+                        } else if ($k == 1) {
+                            $dates['data'][] = UserBillAdmin::where(['uid' => $val['uid'], 'type' => 'pay_product'])->sum('number');
+                        }
+                    }
+                    $dates['name'] = $v;
+                    $dates['type'] = 'line';
+                    $series[] = $dates;
+                    unset($dates);
+                }
+                return ['time' => $times_list, 'series' => $series];
+        }
+    }
+
+    public function gettime($time = '', $season = '')
+    {
+        if (!empty($time) && empty($season)) {
+            $timestamp0 = strtotime($time);
+            $timestamp24 = strtotime($time) + 86400;
+            return [$timestamp0, $timestamp24];
+        } else if (!empty($time) && !empty($season)) {
+            $firstday = date('Y-m-01', mktime(0, 0, 0, ($season - 1) * 3 + 1, 1, date('Y')));
+            $lastday = date('Y-m-t', mktime(0, 0, 0, $season * 3, 1, date('Y')));
+            return [$firstday, $lastday];
+        }
+    }
+
+    /**
+     * TODO:需统一到数据统计模块 end
+     */
+
+    /**
+     * 获取单个用户信息
+     * @param int $id 用户id
+     * @return mixed
+     * @throws Exception
+     */
+    public function oneUserInfo($id)
+    {
+        $data = Util::getMore([
+            ['type', ''],
+            ['page', 1],
+            ['limit', 20]
+        ]);
+        if (!$id || $data['type'] == '') return $this->fail('缺少参数');
+        if (!UserModel::be(['uid' => $id, 'mer_id' => $this->merId])) {
+            return $this->fail('参数错误');
+        }
+        switch ($data['type']) {
+            case 'spread':
+                return $this->getSpreadList($id, $data['page'], $data['limit'], $this->merId);
+                break;
+            case 'order':
+                return $this->getOneOrderList($id, $data['page'], $data['limit']);
+                break;
+            case 'integral':
+                return $this->getOneIntegralList($id, $data['page'], $data['limit']);
+                break;
+            case 'sign':
+                return $this->getOneSignList($id, $data['page'], $data['limit']);
+                break;
+            case 'coupon':
+                return $this->getOneCouponsList($id, $data['page'], $data['limit']);
+                break;
+            case 'balance_change':
+                return $this->getOneBalanceChangList($id, $data['page'], $data['limit']);
+                break;
+            default:
+                return $this->fail('type参数错误');
+        }
+    }
+
+    /**
+     * 获取某个用户的推广下线
+     * @param $id
+     * @param $page
+     * @param $limit
+     * @param string $mer_id
+     * @return Response
+     */
+    public function getSpreadList($id, $page, $limit, $mer_id = '')
+    {
+        return $this->success(UserModel::getSpreadList($id, (int)$page, (int)$limit, $mer_id));
+    }
+
+    /**
+     * 获取某用户的订单列表
+     * @param $id
+     * @param $page
+     * @param $limit
+     * @param string $mer_id
+     * @return Response
+     */
+    public function getOneOrderList($id, $page, $limit)
+    {
+        return $this->success(StoreOrder::getOneorderList(compact('id', 'page', 'limit')));
+    }
+
+    /**
+     * 获取某用户的积分列表
+     * @param $id
+     * @param int $page
+     * @param int $limit
+     * @return
+     */
+    public function getOneIntegralList($id, $page = 1, $limit = 20)
+    {
+        return $this->success(UserBillAdmin::getOneIntegralList(compact('id', 'page', 'limit')));
+    }
+
+    /**
+     * 获取某用户的签到明细
+     * @param $id
+     * @param int $page
+     * @param int $limit
+     * @return
+     */
+    public function getOneSignList($id, $page = 1, $limit = 20)
+    {
+        return $this->success(UserBillAdmin::getOneSignList(compact('id', 'page', 'limit')));
+    }
+
+    /**
+     * 获取某用户的持有优惠劵
+     * @param $id
+     * @param int $page
+     * @param int $limit
+     * @return
+     */
+    public function getOneCouponsList($id, $page = 1, $limit = 20)
+    {
+        return $this->success(StoreCouponUser::getOneCouponsList(compact('id', 'page', 'limit')));
+    }
+
+    /**
+     * 获取某用户的余额变动记录
+     * @param $id
+     * @param int $page
+     * @param int $limit
+     * @return
+     */
+    public function getOneBalanceChangList($id, $page = 1, $limit = 20)
+    {
+        return $this->success(UserBillAdmin::getOneBalanceChangList(compact('id', 'page', 'limit')));
+    }
+}

+ 100 - 0
app/adminapi/controller/v1/user/UserGroup.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace app\adminapi\controller\v1\user;
+
+use app\adminapi\controller\AuthController;
+use app\models\user\UserGroup as GroupModel;
+use crmeb\services\{FormBuilder as Form, UtilService as Util};
+use think\facade\Route as Url;
+use crmeb\traits\CurdControllerTrait;
+
+/**
+ * 会员设置
+ * Class UserLevel
+ * @package app\admin\controller\user
+ */
+class UserGroup extends AuthController
+{
+    use CurdControllerTrait;
+
+    /**
+     * 分组列表
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+        ]);
+        return $this->success(GroupModel::getList($where));
+    }
+
+    /**
+     * 添加/修改分组页面
+     * @param int $id
+     * @return string
+     */
+    public function add()
+    {
+        $data = Util::getMore([
+            ['id', 0],
+        ]);
+        $group = GroupModel::get($data['id']);
+        $field = array();
+        if (!$group) {
+            $title = '添加分组';
+            $field[] = Form::input('group_name', '分组名称', '');
+        } else {
+            $title = '修改分组';
+            $field[] = Form::hidden('id', $group->getData('id'));
+            $field[] = Form::input('group_name', '分组名称', $group->getData('group_name'));
+        }
+        return $this->makePostForm($title, $field, Url::buildUrl('/user/user_group/save'), 'POST');
+    }
+
+    /**
+     *
+     * @param int $id
+     * @return mixed
+     */
+    public function save()
+    {
+        $data = Util::postMore([
+            ['id', 0],
+            ['group_name', ''],
+        ]);
+        if ($data['id'] != 0) {
+            if (GroupModel::where('id', $data['id'])->update($data)) {
+                return $this->success('修改成功');
+            } else {
+                return $this->fail('修改失败或者您没有修改什么!');
+            }
+        } else {
+            unset($data['id']);
+            if ($res = GroupModel::create($data)) {
+                return $this->success('添加成功');
+            } else {
+                return $this->fail('添加失败!');
+            }
+        }
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @throws \Exception
+     */
+    public function delete()
+    {
+        $data = Util::getMore([
+            ['id', 0],
+        ]);
+        if (!$data['id']) return $this->fail('数据不存在');
+        if (!GroupModel::be(['id' => $data['id']])) return $this->fail('分组数据不存在');
+        if (!GroupModel::where('id', $data['id'])->delete()) {
+            return $this->fail(GroupModel::getErrorInfo('删除失败,请稍候再试!'));
+        } else {
+            return $this->success('删除成功!');
+        }
+    }
+}

+ 102 - 0
app/adminapi/controller/v1/user/UserLabel.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace app\adminapi\controller\v1\user;
+
+use app\adminapi\controller\AuthController;
+use app\models\user\UserLabel as LabelModel;
+use crmeb\services\{FormBuilder as Form, UtilService as Util};
+use think\facade\Route as Url;
+use crmeb\traits\CurdControllerTrait;
+
+/**
+ * 用户标签控制器
+ * Class UserLabel
+ * @package app\adminapi\controller\v1\user
+ */
+class UserLabel extends AuthController
+{
+    use CurdControllerTrait;
+
+    /**
+     * 标签列表
+     * @return mixed
+     */
+    public function index()
+    {
+        $where = Util::getMore([
+            ['page', 1],
+            ['limit', 20],
+        ]);
+        return $this->success(LabelModel::getList($where));
+    }
+
+    /**
+     * 添加修改标签表单
+     * @return mixed
+     * @throws \FormBuilder\exception\FormBuilderException
+     */
+    public function add()
+    {
+        list($id) = Util::getMore([
+            ['id', 0],
+        ], $this->request, true);
+        $label = LabelModel::get($id);
+        $field = array();
+        if (!$label) {
+            $title = '添加标签';
+            $field[] = Form::input('label_name', '标签名称', '');
+        } else {
+            $title = '修改标签';
+            $field[] = Form::hidden('id', $label->getData('id'));
+            $field[] = Form::input('label_name', '标签名称', $label->getData('label_name'))->required('请填写标签名称');
+        }
+        return $this->makePostForm($title, $field, Url::buildUrl('/user/user_label/save'), 'POST');
+    }
+
+    /**
+     * 保存标签表单数据
+     * @param int $id
+     * @return mixed
+     */
+    public function save()
+    {
+        $data = Util::postMore([
+            ['id', 0],
+            ['label_name', ''],
+        ]);
+        if (!$data['label_name'] = trim($data['label_name'])) return $this->fail('会员标签不能为空!');
+        if ($data['id'] != 0) {
+            if (LabelModel::where('id', $data['id'])->update($data)) {
+                return $this->success('修改成功');
+            } else {
+                return $this->fail('修改失败或者您没有修改什么!');
+            }
+        } else {
+            unset($data['id']);
+            if ($res = LabelModel::create($data)) {
+                return $this->success('添加成功');
+            } else {
+                return $this->fail('添加失败!');
+            }
+        }
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @throws \Exception
+     */
+    public function delete()
+    {
+        list($id) = Util::getMore([
+            ['id', 0],
+        ], $this->request, true);
+        if (!$id) return $this->fail('数据不存在');
+        if (!LabelModel::be(['id' => $id])) return $this->fail('分组数据不存在');
+        if (!LabelModel::where('id', $id)->delete()) {
+            return $this->fail(LabelModel::getErrorInfo('删除失败,请稍候再试!'));
+        } else {
+            return $this->success('删除成功!');
+        }
+    }
+}

+ 408 - 0
app/adminapi/controller/v1/user/UserLevel.php

@@ -0,0 +1,408 @@
+<?php
+
+namespace app\adminapi\controller\v1\user;
+
+use app\adminapi\controller\AuthController;
+use Exception;
+use FormBuilder\exception\FormBuilderException;
+use app\models\system\{SystemUserLevel, SystemUserTask};
+use crmeb\services\{FormBuilder as Form, UtilService};
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\facade\Route as Url;
+use crmeb\traits\CurdControllerTrait;
+
+/**
+ * 会员设置
+ * Class UserLevel
+ * @package app\adminapi\controller\v1\user
+ */
+class UserLevel extends AuthController
+{
+    use CurdControllerTrait;
+
+    /**
+     * 会员详情
+     * @param $id
+     * @return mixed
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function read($id)
+    {
+        $info = SystemUserLevel::merSet($this->merId)->where('id', $id)->find();
+        return $this->success(compact('info'));
+    }
+
+    /**
+     * 获取添加资源表单
+     *
+     * @throws FormBuilderException
+     * @throws Exception
+     */
+    public function create()
+    {
+        $where = UtilService::getMore(
+            ['id', 0]
+        );
+        if ($where['id']) {
+            $vipinfo = SystemUserLevel::merSet($this->merId)->where('id', $where['id'])->find();
+            if (!$vipinfo) {
+                return $this->fail('不存在等级');
+            }
+            $msg = '编辑会员等级';
+        } else {
+            $msg = '添加会员等级';
+        }
+        $field[] = Form::input('name', '等级名称', isset($vipinfo) ? $vipinfo->name : '')->col(Form::col(24));
+        $field[] = Form::radio('is_forever', '是否为永久', isset($vipinfo) ? $vipinfo->is_forever : 0)->options([['label' => '永久', 'value' => 1], ['label' => '非永久', 'value' => 0]])->col(24);
+//        $field[] = Form::number('money', '等级价格', isset($vipinfo) ? $vipinfo->money : 0)->min(0)->col(24);
+//        $field[] = Form::radio('is_pay', '是否需要购买', isset($vipinfo) ? $vipinfo->is_pay : 0)->options([['label' => '需要', 'value' => 1], ['label' => '免费', 'value' => 0]])->col(24);
+        $field[] = Form::number('valid_date', '有效时间(天)', isset($vipinfo) ? $vipinfo->valid_date : 0)->min(0)->col(8);
+        $field[] = Form::number('grade', '等级', isset($vipinfo) ? $vipinfo->grade : 0)->min(0)->col(8);
+        $field[] = Form::number('discount', '享受折扣', isset($vipinfo) ? $vipinfo->discount : 0)->min(0)->col(8);
+        $field[] = Form::frameImageOne('icon', '图标', Url::buildUrl('admin/widget.images/index', array('fodder' => 'icon')), isset($vipinfo) ? $vipinfo->icon : '')->icon('ios-add')->width('60%')->height('435px');
+        $field[] = Form::frameImageOne('image', '会员背景', Url::buildUrl('admin/widget.images/index', array('fodder' => 'image')), isset($vipinfo) ? $vipinfo->image : '')->icon('ios-add')->width('60%')->height('435px');
+        $field[] = Form::radio('is_show', '是否显示', isset($vipinfo) ? $vipinfo->is_show : 0)->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]])->col(24);
+        $field[] = Form::radio('show_commission', '是否显示佣金', isset($vipinfo) ? $vipinfo->show_commission : 1)->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]])->col(24);
+        $field[] = Form::textarea('explain', '等级说明', isset($vipinfo) ? $vipinfo->explain : '');
+        if ($where['id']) $field[] = Form::hidden('id', $where['id']);
+        return $this->makePostForm($msg, $field, Url::buildUrl('/user/user_level'), 'POST');
+    }
+
+    /**
+     * 会员等级添加或者修改
+     * @return mixed
+     *
+     * @throws Exception
+     */
+    public function save()
+    {
+        $data = UtilService::postMore([
+            ['id', 0],
+            ['name', ''],
+            ['is_forever', 0],
+            ['money', 0],
+            ['is_pay', 0],
+            ['valid_date', 0],
+            ['grade', 0],
+            ['discount', 0],
+            ['icon', ''],
+            ['image', ''],
+            ['is_show', ''],
+            ['show_commission', 1],
+            ['explain', ''],
+        ]);
+        $id = $data['id'];
+        $data['mer_id'] = $this->merId ?: '';
+        if (!$data['name']) return $this->fail('请输入等级名称');
+        if (!$data['grade']) return $this->fail('请输入等级');
+        if (!$data['explain']) return $this->fail('请输入等级说明');
+        if ($data['is_forever'] == 0 && !$data['valid_date']) return $this->fail('请输入有效时间(天)');
+//        if ($data['is_pay']) return $this->fail('会员等级购买功能正在开发中,暂时关闭可购买功能!');
+//        if ($data['is_pay'] && !$data['money']) return $this->fail('请输入购买金额');
+        if (!$data['icon']) return $this->fail('请上传等级图标');
+        if (!$data['image']) return $this->fail('请上传等级背景图标');
+        if (!$id && SystemUserLevel::be(['mer_id' => $data['mer_id'], 'is_del' => 0, 'grade' => $data['grade']])) return $this->fail('已检测到您设置过的会员等级,此等级不可重复');
+        SystemUserLevel::beginTrans();
+        try {
+            //修改
+            if ($id) {
+                if (SystemUserLevel::merSet($this->merId)->where('id', $id)->find())
+                    if (SystemUserLevel::edit($data, $id)) {
+                        SystemUserLevel::commitTrans();
+                        return $this->success('修改成功');
+                    } else {
+                        SystemUserLevel::rollbackTrans();
+                        return $this->fail('修改失败');
+                    }
+                else return $this->fail('不存在等级');
+            } else {
+                //新增
+                $data['add_time'] = time();
+                if (SystemUserLevel::create($data)) {
+                    SystemUserLevel::commitTrans();
+                    return $this->success('添加成功');
+                } else {
+                    SystemUserLevel::rollbackTrans();
+                    return $this->fail('添加失败');
+                }
+            }
+        } catch (Exception $e) {
+            SystemUserLevel::rollbackTrans();
+            return $this->fail($e->getMessage());
+        }
+    }
+
+    /**
+     * 获取系统设置的vip列表
+     * @param int page
+     * @param int limit
+     *
+     * @return mixed
+     * @throws Exception
+     */
+    public function get_system_vip_list()
+    {
+        $where = UtilService::getMore([
+            ['page', 0],
+            ['limit', 10],
+            ['title', ''],
+            ['is_show', ''],
+        ]);
+        $where['mer_id'] = $this->merId;
+        return $this->success(SystemUserLevel::getSytemList($where));
+    }
+
+    /**
+     * 删除会员等级
+     * @param int $id
+     *
+     * @return
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public function delete($id)
+    {
+        if (!$id || !SystemUserLevel::merSet($this->merId)->where('id', $id)->find()) {
+            return $this->fail('参数错误');
+        }
+        if (SystemUserLevel::edit(['is_del' => 1], $id))
+            return $this->success('删除成功');
+        else
+            return $this->fail('删除失败');
+    }
+
+    /**
+     * 设置会员等级显示|隐藏
+     *
+     * @param string $is_show
+     * @param string $id
+     * @return mixed
+     */
+    public function set_show($is_show = '', $id = '')
+    {
+        if ($is_show == '' || $id == '') return $this->fail('缺少参数');
+        $res = SystemUserLevel::merSet($this->merId)->where(['id' => $id])->update(['is_show' => (int)$is_show]);
+        if ($res) {
+            return $this->success($is_show == 1 ? '显示成功' : '隐藏成功');
+        } else {
+            return $this->fail($is_show == 1 ? '显示失败' : '隐藏失败');
+        }
+    }
+
+    /**
+     * 设置会员等级显示返佣|隐藏
+     */
+    public function set_show_commission($show_commission = '', $id = '')
+    {
+        if ($show_commission == '' || $id == '') return $this->fail('缺少参数');
+        $res = SystemUserLevel::merSet($this->merId)->where(['id' => $id])->update(['show_commission' => (int)$show_commission]);
+        if ($res) {
+            return $this->success($show_commission == 1 ? '显示成功' : '隐藏成功');
+        } else {
+            return $this->fail($show_commission == 1 ? '显示失败' : '隐藏失败');
+        }
+    }
+
+    /**
+     * 等级列表快速编辑
+     * field:value name:钻石会员/grade:8/discount:92.00
+     * @return mixed
+     * @throws Exception
+     */
+    public function set_value($id)
+    {
+        $data = UtilService::postMore([
+            ['field', ''],
+            ['value', '']
+        ], $this->request);
+        if ($data['field'] == '' || $data['value'] == '') return $this->fail('缺少参数');
+        if (SystemUserLevel::merSet($this->merId)->where(['id' => $id])->update([$data['field'] => $data['value']]))
+            return $this->success('保存成功');
+        else
+            return $this->fail('保存失败');
+    }
+
+    /**
+     * 异步获取等级任务列表
+     * @param int $level_id 会员id
+     * @return mixed
+     *
+     * @throws Exception
+     */
+    public function get_task_list($level_id)
+    {
+        $where = UtilService::getMore([
+            ['page', 1],
+            ['limit', 10],
+            ['name', ''],
+            ['is_show', '']
+        ], $this->request);
+        if (!$level_id || !SystemUserLevel::merSet($this->merId)->where('id', $level_id)->find()) {
+            return $this->fail('参数错误');
+        }
+        return $this->success(SystemUserTask::taskList($level_id, $where));
+    }
+
+    /**
+     * 快速编辑等级任务 目前仅排序 sort:2
+     * $id 等级任务id
+     * @param $id
+     * @return mixed
+     * @throws Exception
+     */
+    public function set_task_value($id)
+    {
+        $data = UtilService::postMore([
+            ['field', ''],
+            ['value', '']
+        ]);
+        if (!$id || !in_array(SystemUserTask::where('id', $id)->value('level_id'), SystemUserLevel::merSet($this->merId)->column('id'))) {
+            return $this->fail('不存在任务');
+        }
+        if ($data['field'] == '' || $data['value'] == '') return $this->fail('缺少参数');
+        if (SystemUserTask::where(['id' => $id])->update([$data['field'] => $data['value']]))
+            return $this->success('保存成功');
+        else
+            return $this->fail('保存失败');
+    }
+
+    /**
+     * 设置等级任务显示|隐藏
+     *
+     * @param string $is_show
+     * @param string $id
+     * @return mixed
+     */
+    public function set_task_show($is_show = '', $id = '')
+    {
+        ($is_show == '' || $id == '') && $this->fail('缺少参数');
+        if (!$id || !in_array(SystemUserTask::where('id', $id)->value('level_id'), SystemUserLevel::merSet($this->merId)->column('id'))) {
+            return $this->fail('不存在任务');
+        }
+        $res = SystemUserTask::where(['id' => $id])->update(['is_show' => (int)$is_show]);
+        if ($res) {
+            return $this->success($is_show == 1 ? '显示成功' : '隐藏成功');
+        } else {
+            return $this->fail($is_show == 1 ? '显示失败' : '隐藏失败');
+        }
+    }
+
+    /**
+     * 设置任务是否务必达成
+     *
+     * @param string $is_must
+     * @param string $id
+     * @return mixed
+     */
+    public function set_task_must($is_must = '', $id = '')
+    {
+        ($is_must == '' || $id == '') && $this->fail('缺少参数');
+        if (!$id || !in_array(SystemUserTask::where('id', $id)->value('level_id'), SystemUserLevel::merSet($this->merId)->column('id'))) {
+            return $this->fail('不存在任务');
+        }
+        $res = SystemUserTask::where(['id' => $id])->update(['is_must' => (int)$is_must]);
+        if ($res) {
+            return $this->success('设置成功');
+        } else {
+            return $this->fail('设置失败');
+        }
+    }
+
+    /**
+     * 生成任务表单
+     * @return Form
+     *
+     * @throws FormBuilderException
+     * @throws Exception
+     */
+    public function create_task()
+    {
+        $where = UtilService::getMore([
+            ['id', 0],
+            ['level_id', 0]
+        ]);
+        if ($where['id']) $tash = SystemUserTask::get($where['id']);
+        $field[] = Form::select('task_type', '任务类型', isset($tash) ? $tash->task_type : '')->setOptions(function () {
+            $list = SystemUserTask::getTaskTypeAll();
+            $menus = [];
+            foreach ($list as $menu) {
+                $menus[] = ['value' => $menu['type'], 'label' => $menu['name'] . '----单位[' . $menu['unit'] . ']'];
+            }
+            return $menus;
+        })->filterable(1);
+        if ($where['id']) $field[] = Form::hidden('id', $where['id']);
+        $field[] = Form::hidden('level_id', $where['level_id']);
+        $field[] = Form::number('number', '限定数量', isset($tash) ? $tash->number : 0)->min(0)->col(24);
+        $field[] = Form::number('sort', '排序', isset($tash) ? $tash->sort : 0)->min(0)->col(24);
+        $field[] = Form::radio('is_show', '是否显示', isset($tash) ? $tash->is_show : 1)->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]])->col(24);
+        $field[] = Form::radio('is_must', '是否务必达成', isset($tash) ? $tash->is_must : 1)->options([['label' => '务必达成', 'value' => 1], ['label' => '完成其一', 'value' => 0]])->col(24);
+        $field[] = Form::textarea('illustrate', '任务说明', isset($tash) ? $tash->illustrate : '');
+        return $this->makePostForm('添加任务', $field, Url::buildUrl('/user/user_level/save_task'), 'POST');
+    }
+
+    /**
+     * 保存或者修改任务
+     * @return mixed
+     * @throws Exception
+     */
+    public function save_task()
+    {
+        $data = UtilService::postMore([
+            ['id', 0],
+            ['level_id', 0],
+            ['task_type', ''],
+            ['number', 0],
+            ['is_show', 0],
+            ['sort', 0],
+            ['is_must', 0],
+            ['illustrate', ''],
+        ]);
+        if (!$data['level_id'] || !SystemUserLevel::merSet($this->merId)->where('id', $data['level_id'])->find()) {
+            return $this->fail('缺少参数');
+        }
+        if (!$data['task_type']) return $this->fail('请选择任务类型');
+        if ($data['number'] < 1) return $this->fail('请输入限定数量,数量不能小于1');
+        $tash = SystemUserTask::getTaskType($data['task_type']);
+        if ($tash['max_number'] != 0 && $data['number'] > $tash['max_number']) return $this->fail('您设置的限定数量超出最大限制,最大限制为:' . $tash['max_number']);
+        $data['name'] = SystemUserTask::setTaskName($data['task_type'], $data['number']);
+        try {
+            if ($data['id']) {
+                SystemUserTask::edit($data, $data['id']);
+                return $this->success('修改成功');
+            } else {
+                $data['add_time'] = time();
+                $data['real_name'] = $tash['real_name'];
+                if (SystemUserTask::create($data))
+                    return $this->success('添加成功');
+                else
+                    return $this->fail('添加失败');
+            }
+        } catch (Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+    }
+
+    /**
+     * 删除任务
+     * @param int 任务id
+     *
+     * @return
+     */
+    public function delete_task($id)
+    {
+        if (!$id) return $this->fail('缺少参数');
+        if (!$id || !in_array(SystemUserTask::where('id', $id)->value('level_id'), SystemUserLevel::merSet($this->merId)->column('id'))) {
+            return $this->fail('不存在任务');
+        }
+        if (SystemUserTask::del($id))
+            return $this->success('删除成功');
+        else
+            return $this->fail('删除失败');
+    }
+
+}

+ 24 - 0
app/adminapi/event.php

@@ -0,0 +1,24 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// 事件定义文件
+return [
+    'listen'    => [
+        'AppInit'  => [],
+        'HttpRun'  => [],
+        'HttpEnd'  => [],
+        'LogLevel' => [],
+        'LogWrite' => [],
+        'UserLogin'=>[
+             \crmeb\listeners\user\UserLogin::class
+        ]
+    ]
+];

+ 1 - 0
app/adminapi/lang/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/lang

+ 55 - 0
app/adminapi/lang/zh-cn.php

@@ -0,0 +1,55 @@
+<?php
+
+return [
+    'SUCCESS' => '操作成功',
+    'Successful operation' => '操作成功',
+    'ERROR' => '操作失败',
+    'Landing overdue' => '登陆过期',
+    'Modified success' => '修改成功',
+    'Modification failed' => '修改失败',
+    'You do not have permission to access for the time being' => '您暂无权限访问',
+    'Interface is not authorized, you cannot access' => '接口未授权,您无法访问',
+    'Failed to get administrator ID' => '管理员id获取失败',
+    'Token is invalid, please login' => 'token失效,请登录',
+    'Please login' => '请登录',
+    'The login status is incorrect. Please login again.' => '登录状态有误,请重新登录',
+
+    //订单错误提示语言
+    'Data does not exist!' => '数据不存在',
+    'Write off successfully' => '核销成功',
+    'Write off failure' => '核销失败',
+    'Order written off' => '订单已核销',
+    'Write off order does not exist' => '核销订单不存在',
+    'Lack of write-off code' => '缺少核销码',
+    'Missing order ID' => '缺少订单id',
+    'Please enter the total price' => '请输入商品总价',
+    'Please enter the actual payment amount' => '请输入实际支付金额',
+    'Failed to write off the group order' => '拼团订单暂未成功无法核销',
+    'Parent classification error' => '父级分类错误',
+    'There are attachments under the category. Please delete them first' => '分类下面有附件,请先删除附件',
+
+    //上传配置错误提示语言
+    'Please configure accessKey and secretKey'=>'请设置上传配置的accessKey和secretKey',
+    'Upload failure'=>'上传失败',
+    'Upload file does not exist'=>'上传文件不存在',
+    'COS bucket cannot be null'=>'腾讯云bucket不能为空',
+    'COS allowPrefix cannot be null'=>'腾讯云允许前缀不能为空',
+    'durationSeconds must be a int type'=>'durationSeconds必须是int类型',
+    'get cam failed'=>'获取cam失败',
+    'Failed to generate upload directory, please check the permission!'=>'生成上传目录失败,请检查权限!',
+
+    //易联云
+    'request was aborted'=>'请求已中止',
+    'Accesstoken has expired'=>'访问窗口已过期',
+
+    //云信短信
+    'Mobile number cannot be empty'=>'手机号码不能为空',
+    'Account does not exist'=>'帐户不存在',
+    'Access token does not exist'=>'访问令牌不存在',
+    'Missing template number'=>'缺少模板号',
+
+    //订阅消息&模板消息
+    'Template number does not exist'=>'模板号不存在',
+    'Template ID does not exist'=>'模板ID不存在',
+    'Openid does not exist'=>'Openid不存在',
+];

+ 1 - 0
app/adminapi/middleware/.WeDrive

@@ -0,0 +1 @@
+/Users/huangjianfeng/WeDrive/六牛科技/源码/CRMEB_PRO_v1.0.0(9)/app/adminapi/middleware

Some files were not shown because too many files changed in this diff