zhangguidong %!s(int64=2) %!d(string=hai) anos
achega
fde36501ce
Modificáronse 100 ficheiros con 12885 adicións e 0 borrados
  1. 11 0
      .gitignore
  2. 21 0
      LICENSE
  3. 133 0
      README.md
  4. 180 0
      application/admin/controller/Auth.php
  5. 130 0
      application/admin/controller/Config.php
  6. 172 0
      application/admin/controller/Index.php
  7. 118 0
      application/admin/controller/Login.php
  8. 160 0
      application/admin/controller/Menu.php
  9. 94 0
      application/admin/controller/Oplog.php
  10. 144 0
      application/admin/controller/Queue.php
  11. 173 0
      application/admin/controller/User.php
  12. 130 0
      application/admin/controller/api/Plugs.php
  13. 65 0
      application/admin/controller/api/Update.php
  14. 159 0
      application/admin/sys.php
  15. 3659 0
      application/admin/view/api/plugs/icon.html
  16. 116 0
      application/admin/view/auth/apply.html
  17. 30 0
      application/admin/view/auth/form.html
  18. 80 0
      application/admin/view/auth/index.html
  19. 42 0
      application/admin/view/auth/index_search.html
  20. 80 0
      application/admin/view/config/info.html
  21. 20 0
      application/admin/view/config/storage-local.html
  22. 80 0
      application/admin/view/config/storage-oss.html
  23. 80 0
      application/admin/view/config/storage-qiniu.html
  24. 57 0
      application/admin/view/config/system-config.html
  25. 128 0
      application/admin/view/index/index.html
  26. 207 0
      application/admin/view/index/main.html
  27. 57 0
      application/admin/view/login/index.html
  28. 11 0
      application/admin/view/main.html
  29. 89 0
      application/admin/view/menu/form.html
  30. 80 0
      application/admin/view/menu/index.html
  31. 68 0
      application/admin/view/oplog/index.html
  32. 56 0
      application/admin/view/oplog/index_search.html
  33. 94 0
      application/admin/view/queue/index.html
  34. 70 0
      application/admin/view/queue/index_search.html
  35. 73 0
      application/admin/view/user/form.html
  36. 77 0
      application/admin/view/user/index.html
  37. 57 0
      application/admin/view/user/index_search.html
  38. 57 0
      application/admin/view/user/pass.html
  39. 103 0
      application/company/command/Subversion.php
  40. 101 0
      application/company/controller/Auth.php
  41. 49 0
      application/company/controller/Clock.php
  42. 131 0
      application/company/controller/User.php
  43. 97 0
      application/company/controller/api/Push.php
  44. 40 0
      application/company/service/DataService.php
  45. 21 0
      application/company/sys.php
  46. 35 0
      application/company/view/auth/form.html
  47. 78 0
      application/company/view/auth/index.html
  48. 50 0
      application/company/view/auth/index_search.html
  49. 41 0
      application/company/view/clock/index.html
  50. 28 0
      application/company/view/clock/index_search.html
  51. 89 0
      application/company/view/user/form.html
  52. 83 0
      application/company/view/user/index.html
  53. 50 0
      application/company/view/user/index_search.html
  54. 34 0
      application/index/controller/Index.php
  55. 63 0
      application/service/controller/Config.php
  56. 158 0
      application/service/controller/Fans.php
  57. 166 0
      application/service/controller/Index.php
  58. 174 0
      application/service/controller/api/Client.php
  59. 184 0
      application/service/controller/api/Push.php
  60. 69 0
      application/service/handler/PublishHandler.php
  61. 69 0
      application/service/handler/ReceiveHandler.php
  62. 161 0
      application/service/handler/WechatHandler.php
  63. 88 0
      application/service/queue/WechatQueue.php
  64. 53 0
      application/service/service/BuildService.php
  65. 83 0
      application/service/service/MichatService.php
  66. 158 0
      application/service/service/WechatService.php
  67. 44 0
      application/service/view/config/form.html
  68. 113 0
      application/service/view/config/index.html
  69. 98 0
      application/service/view/fans/index.html
  70. 76 0
      application/service/view/fans/index_search.html
  71. 84 0
      application/service/view/index/index.html
  72. 76 0
      application/service/view/index/index_search.html
  73. 1 0
      application/service/view/not-auth.html
  74. 196 0
      application/store/command/AutoRun.php
  75. 60 0
      application/store/controller/Config.php
  76. 128 0
      application/store/controller/ExpressCompany.php
  77. 85 0
      application/store/controller/ExpressTemplate.php
  78. 215 0
      application/store/controller/Goods.php
  79. 113 0
      application/store/controller/GoodsCate.php
  80. 50 0
      application/store/controller/Member.php
  81. 61 0
      application/store/controller/Message.php
  82. 126 0
      application/store/controller/Order.php
  83. 52 0
      application/store/controller/Slider.php
  84. 38 0
      application/store/controller/api/Data.php
  85. 38 0
      application/store/controller/api/Express.php
  86. 122 0
      application/store/controller/api/Goods.php
  87. 80 0
      application/store/controller/api/Member.php
  88. 74 0
      application/store/controller/api/Notify.php
  89. 92 0
      application/store/controller/api/Wechat.php
  90. 128 0
      application/store/controller/api/member/Address.php
  91. 122 0
      application/store/controller/api/member/Center.php
  92. 313 0
      application/store/controller/api/member/Order.php
  93. 84 0
      application/store/service/ExpressService.php
  94. 423 0
      application/store/service/ExtendService.php
  95. 71 0
      application/store/service/GoodsService.php
  96. 67 0
      application/store/service/OrderService.php
  97. 19 0
      application/store/sys.php
  98. 32 0
      application/store/view/config/index-store-config.html
  99. 45 0
      application/store/view/config/index-store-message1.html
  100. 45 0
      application/store/view/config/index-store-message2.html

+ 11 - 0
.gitignore

@@ -0,0 +1,11 @@
+/.git
+/.svn
+/.idea
+/runtime
+/safefile
+/nbproject
+/config/cert
+/composer.lock
+/public/upload
+public/static/theme/css/console.*.css
+public/static/theme/css/console.*.css.map

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 邹景立
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 133 - 0
README.md

@@ -0,0 +1,133 @@
+## 大道至简 · 原生框架
+
+ThinkAdmin V5 是一个基于 ThinkPHP 5.1 开发的后台管理系统。
+
+我们致力于二次开发底层框架,提供完整的组件及API,基于此框架可以快速开发应用。
+
+另外项目安装及二次开发可以参考 ThinkPHP 官方文档,数据库文件摆放在项目根目录下。
+
+ThinkAdmin 非常适用快速二次开发,默认集成 微信开发组件,支持微信服务号、微信支付、支付宝支付、阿里云OSS存储、七牛云存储、本地服务器存储等。
+后台UI基于最新版本的 LayUI 及 RequireJs 加载第三方插件(建议自行了解 LayUI 及 RequireJs)。
+
+#### 注意事项 
+* 项目测试需要自行搭建环境导入数据库( admin_v5.sql )并修改配置( config/database.php );
+* 若操作提示“演示系统禁止操作”等字样,需要删除演示路由配置( route/demo.php )或清空路由文件;
+* 当前版本使用 ThinkPHP 5.1.x,对 PHP 版本标注不低于 PHP 5.6,具体请阅读 ThinkPHP 官方文档;
+* 环境需开启 PATHINFO,不再支持 ThinkPHP 的 URL 兼容模式运行(源于如何优雅的展示);
+
+## 技术支持
+
+开发文档:http://doc.thinkadmin.top/thinkadmin-v5
+
+开发前请认真阅读 ThinkPHP 官方文档会对您有帮助哦!
+
+本地开发命令`php think run`,使用`http://127.0.0.1:8000`访问项目。
+
+PHP 开发技术交流( QQ 群 513350915)
+
+[![PHP微信开发群 (SDK)](http://pub.idqqimg.com/wpa/images/group.png)](http://shang.qq.com/wpa/qunwpa?idkey=ae25cf789dafbef62e50a980ffc31242f150bc61a61164458216dd98c411832a) 
+
+
+## 注解权限
+
+注解权限是指通过方法注释来实现后台RBAC授权管理,用注解来管理功能节点。
+
+开发人员只需要写好注释,RBAC的节点会自动生成,只需要配置角色及用户就可以使用RBAC权限。
+
+* 此版本的权限使用注解实现
+* 注释必需使用标准的块注释,如下案例
+* 其中`@auth true`表示访问需要权限验证
+* 其中`@menu true`显示在菜单编辑的节点可选项
+```php
+/**
+* 操作的名称
+* @auth true  # 表示需要验证权限
+* @menu true  # 在菜单编辑的节点可选项
+*/
+public function index(){
+   // @todo
+}
+```
+
+## 代码仓库
+
+ ThinkAdmin 为 MIT 协议开源项目,安装使用或二次开发不受约束,欢迎 fork 项目。
+ 
+ 部分代码来自互联网,若有异议可以联系作者进行删除。
+ 
+ * 在线体验地址:https://demo.thinkadmin.top (账号和密码都是 admin )
+ * Gitee仓库地址:https://gitee.com/zoujingli/ThinkAdmin
+ * GitHub仓库地址:https://github.com/zoujingli/ThinkAdmin
+ 
+## 框架指令
+
+* 执行 `build.cmd` 可更新 `Composer` 插件,会删除并替换 `vendor` 目录
+* 执行 `php think run` 启用本地开发环境,访问 `http://127.0.0.1:8000`
+
+#### 1. 线上代码更新
+* 执行 `php think xsync:admin` 从线上服务更新 `admin` 模块的所有文件(注意文件安全)
+* 执行 `php think xsync:wechat` 从线上服务更新 `wechat` 模块的所有文件(注意文件安全)
+* 执行 `php think xsync:service` 从线上服务更新 `service` 模块的所有文件(注意文件安全)
+* 执行 `php think xsync:plugs` 从线上服务更新 `plugs` 静态插件的部分文件(注意文件安全)
+* 执行 `php think xsync:config` 从线上服务更新 `config` 项目配置的部分文件(注意文件安全)
+
+#### 2. 微信资料管理
+* 执行 `php think xfans:all` 更新已经对接的公众号全部列表
+* 执行 `php think xfans:list` 更新已经对接的公众号粉丝列表
+* 执行 `php think xfans:tags` 更新已经对接的公众号标签列表
+* 执行 `php think xfans:black` 更新已经对接的公众号黑名单列表
+
+#### 3. 守护进程管理
+* 执行 `php think xtask:listen` 启动异步任务监听守护主进程
+* 执行 `php think xtask:query` 查询正在执行的所有任务进程
+* 执行 `php think xtask:start` 创建异步任务监听守护主进程
+* 执行 `php think xtask:state` 查看异步任务监听主进程状态
+* 执行 `php think xtask:stop` 平滑停止异步任务所有的进程
+
+#### 4. 其它自定工具
+* 执行 `php think xclean:session` 清理无效的会话SESSION文件
+* 执行 `php think xclean:store` 清理无效的订单信息及定时任务
+ 
+## 特别感谢
+
+|名称|描述|
+|---|---|
+|layui|后台基础UI组件库|
+|ckeditor|后台富文本编辑器|
+|awesome|后台扩展字体图标库|
+|pluploader|后台文件上传工具|
+|ThinkPHP|PHP基础支持框架|
+|ThinkLibrary|ThinkPHP扩展组件|
+|WeChatDeveloper|微信开放工具组件|
+|WeOpenDeveloper|微信开放平台组件|
+
+## 赞助打赏
+![赞助](http://static.thinkadmin.top/pay.png)
+
+## 项目版本
+体验账号及密码都是`admin`
+
+#### ThinkAdmin v1 基于 ThinkPHP 5.0 开发
+* 在线体验地址:https://v1.thinkadmin.top
+* Gitee 代码地址:https://gitee.com/zoujingli/ThinkAdmin/tree/v1
+* Github 代码地址:https://github.com/zoujingli/ThinkAdmin/tree/v1
+
+#### ThinkAdmin v2 基于 ThinkPHP 5.0 开发
+* 在线体验地址:https://v2.thinkadmin.top
+* Gitee 代码地址:https://gitee.com/zoujingli/ThinkAdmin/tree/v2
+* Github 代码地址:https://github.com/zoujingli/ThinkAdmin/tree/v2
+
+#### ThinkAdmin v3 基于 ThinkPHP 5.1 开发
+* 在线体验地址:https://v3.thinkadmin.top
+* Gitee 代码地址:https://gitee.com/zoujingli/ThinkAdmin/tree/v3
+* Github 代码地址:https://github.com/zoujingli/ThinkAdmin/tree/v3
+
+#### ThinkAdmin v4 基于 ThinkPHP 5.1 开发
+* 在线体验地址:https://v4.thinkadmin.top
+* Gitee 代码地址:https://gitee.com/zoujingli/ThinkAdmin/tree/v4
+* Github 代码地址:https://github.com/zoujingli/ThinkAdmin/tree/v4
+
+#### ThinkAdmin v5 基于 ThinkPHP 5.1 开发(后台权限基于注解实现)
+* 在线体验地址:https://v5.thinkadmin.top
+* Gitee 代码地址:https://gitee.com/zoujingli/ThinkAdmin/tree/v5
+* Github 代码地址:https://github.com/zoujingli/ThinkAdmin/tree/v5

+ 180 - 0
application/admin/controller/Auth.php

@@ -0,0 +1,180 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller;
+
+use library\Controller;
+use library\service\AdminService;
+use think\Db;
+
+/**
+ * 系统权限管理
+ * Class Auth
+ * @package app\admin\controller
+ */
+class Auth extends Controller
+{
+    /**
+     * 默认数据模型
+     * @var string
+     */
+    public $table = 'SystemAuth';
+
+    /**
+     * 系统权限管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '系统权限管理';
+        $query = $this->_query($this->table)->dateBetween('create_at');
+        $query->like('title,desc')->equal('status')->order('sort desc,id desc')->page();
+    }
+
+    /**
+     * 权限配置节点
+     * @auth true
+     * @throws \ReflectionException
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function apply()
+    {
+        $map = ['auth' => input('id', '0')];
+        $action = strtolower(input('action', ''));
+        if ($action === 'get') {
+            $checkeds = Db::name('SystemAuthNode')->where($map)->column('node');
+            $this->success('获取权限节点成功!', AdminService::instance()->getTree($checkeds));
+        } elseif ($action === 'save') {
+            list($post, $data) = [$this->request->post(), []];
+            foreach (isset($post['nodes']) ? $post['nodes'] : [] as $node) {
+                $data[] = ['auth' => $map['auth'], 'node' => $node];
+            }
+            Db::name('SystemAuthNode')->where($map)->delete();
+            Db::name('SystemAuthNode')->insertAll($data);
+            AdminService::instance()->apply(true);
+            $this->success('权限授权更新成功!', 'javascript:history.back()');
+        } else {
+            $this->title = '权限配置节点';
+            $this->_form($this->table, 'apply');
+        }
+    }
+
+    /**
+     * 添加系统权限
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function add()
+    {
+        $this->applyCsrfToken();
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 编辑系统权限
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function edit()
+    {
+        $this->applyCsrfToken();
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 刷新系统权限
+     * @auth true
+     */
+    public function refresh()
+    {
+        try {
+            AdminService::instance()->apply(true);
+            $this->success('刷新系统授权成功!');
+        } catch (\think\exception\HttpResponseException $exception) {
+            throw  $exception;
+        } catch (\Exception $e) {
+            $this->error("刷新系统授权失败<br>{$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 禁用系统权限
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function forbid()
+    {
+        $this->applyCsrfToken();
+        $this->_save($this->table, ['status' => '0']);
+    }
+
+    /**
+     * 启用系统权限
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function resume()
+    {
+        $this->applyCsrfToken();
+        $this->_save($this->table, ['status' => '1']);
+    }
+
+    /**
+     * 删除系统权限
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->applyCsrfToken();
+        $this->_delete($this->table);
+    }
+
+    /**
+     * 删除结果处理
+     * @param boolean $result
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    protected function _remove_delete_result($result)
+    {
+        if ($result) {
+            $map = ['auth' => $this->request->post('id')];
+            Db::name('SystemAuthNode')->where($map)->delete();
+            $this->success("权限删除成功!", '');
+        } else {
+            $this->error("权限删除失败,请稍候再试!");
+        }
+    }
+
+}

+ 130 - 0
application/admin/controller/Config.php

@@ -0,0 +1,130 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller;
+
+use library\Controller;
+use think\exception\HttpResponseException;
+use think\facade\Request;
+
+/**
+ * 系统参数配置
+ * Class Config
+ * @package app\admin\controller
+ */
+class Config extends Controller
+{
+    /**
+     * 默认数据模型
+     * @var string
+     */
+    protected $table = 'SystemConfig';
+
+    /**
+     * 阿里云OSS上传点
+     * @var array
+     */
+    protected $ossPoints = [
+        'oss-cn-hangzhou.aliyuncs.com'    => '华东 1 杭州',
+        'oss-cn-shanghai.aliyuncs.com'    => '华东 2 上海',
+        'oss-cn-qingdao.aliyuncs.com'     => '华北 1 青岛',
+        'oss-cn-beijing.aliyuncs.com'     => '华北 2 北京',
+        'oss-cn-zhangjiakou.aliyuncs.com' => '华北 3 张家口',
+        'oss-cn-huhehaote.aliyuncs.com'   => '华北 5 呼和浩特',
+        'oss-cn-shenzhen.aliyuncs.com'    => '华南 1 深圳',
+        'oss-cn-hongkong.aliyuncs.com'    => '香港 1',
+        'oss-us-west-1.aliyuncs.com'      => '美国西部 1 硅谷',
+        'oss-us-east-1.aliyuncs.com'      => '美国东部 1 弗吉尼亚',
+        'oss-ap-southeast-1.aliyuncs.com' => '亚太东南 1 新加坡',
+        'oss-ap-southeast-2.aliyuncs.com' => '亚太东南 2 悉尼',
+        'oss-ap-southeast-3.aliyuncs.com' => '亚太东南 3 吉隆坡',
+        'oss-ap-southeast-5.aliyuncs.com' => '亚太东南 5 雅加达',
+        'oss-ap-northeast-1.aliyuncs.com' => '亚太东北 1 日本',
+        'oss-ap-south-1.aliyuncs.com'     => '亚太南部 1 孟买',
+        'oss-eu-central-1.aliyuncs.com'   => '欧洲中部 1 法兰克福',
+        'oss-eu-west-1.aliyuncs.com'      => '英国 1 伦敦',
+        'oss-me-east-1.aliyuncs.com'      => '中东东部 1 迪拜',
+    ];
+
+    /**
+     * 系统参数配置
+     * @auth true
+     * @menu true
+     */
+    public function info()
+    {
+        $this->title = '系统参数配置';
+        $this->fetch();
+    }
+
+    /**
+     * 修改系统能数配置
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function config()
+    {
+        $this->applyCsrfToken();
+        if (Request::isGet()) {
+            $this->fetch('system-config');
+        }
+        foreach (Request::post() as $key => $value) {
+            sysconf($key, $value);
+        }
+        $this->success('系统参数配置成功!');
+    }
+
+    /**
+     * 文件存储引擎
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function file()
+    {
+        $this->applyCsrfToken();
+        if (Request::isGet()) {
+            $this->type = input('type', 'local');
+            $this->fetch("storage-{$this->type}");
+        }
+        $post = Request::post();
+        if (isset($post['storage_type']) && isset($post['storage_local_exts'])) {
+            $exts = array_unique(explode(',', strtolower($post['storage_local_exts'])));
+            sort($exts);
+            if (in_array('php', $exts)) $this->error('禁止上传可执行文件到本地服务器!');
+            $post['storage_local_exts'] = join(',', $exts);
+        }
+        foreach ($post as $key => $value) sysconf($key, $value);
+        if (isset($post['storage_type']) && $post['storage_type'] === 'oss') {
+            try {
+                $local = sysconf('storage_oss_domain');
+                $bucket = $this->request->post('storage_oss_bucket');
+                $domain = \library\File::instance('oss')->setBucket($bucket);
+                if (empty($local) || stripos($local, '.aliyuncs.com') !== false) {
+                    sysconf('storage_oss_domain', $domain);
+                }
+                $this->success('阿里云OSS存储配置成功!');
+            } catch (HttpResponseException $exception) {
+                throw $exception;
+            } catch (\Exception $e) {
+                $this->error("阿里云OSS存储配置失效,{$e->getMessage()}");
+            }
+        } else {
+            $this->success('文件存储配置成功!');
+        }
+    }
+
+}

+ 172 - 0
application/admin/controller/Index.php

@@ -0,0 +1,172 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller;
+
+use library\Controller;
+use library\service\AdminService;
+use library\service\MenuService;
+use library\tools\Data;
+use think\Console;
+use think\Db;
+use think\exception\HttpResponseException;
+
+/**
+ * 系统公共操作
+ * Class Index
+ * @package app\admin\controller
+ */
+class Index extends Controller
+{
+
+    /**
+     * 显示后台首页
+     * @throws \ReflectionException
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $this->title = '系统管理后台';
+        $auth = AdminService::instance()->apply(true);
+        $this->menus = MenuService::instance()->getTree();
+        if (empty($this->menus) && !$auth->isLogin()) {
+            $this->redirect('@admin/login');
+        } else {
+            $this->fetch();
+        }
+    }
+
+    /**
+     * 后台环境信息
+     */
+    public function main()
+    {
+        $this->think_ver = \think\App::VERSION;
+        $this->mysql_ver = Db::query('select version() as ver')[0]['ver'];
+        $this->fetch();
+    }
+
+    /**
+     * 修改密码
+     * @login true
+     * @param integer $id
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function pass($id)
+    {
+        $this->applyCsrfToken();
+        if (intval($id) !== intval(session('user.id'))) {
+            $this->error('只能修改当前用户的密码!');
+        }
+        if (!AdminService::instance()->isLogin()) {
+            $this->error('需要登录才能操作哦!');
+        }
+        if ($this->request->isGet()) {
+            $this->verify = true;
+            $this->_form('SystemUser', 'admin@user/pass', 'id', [], ['id' => $id]);
+        } else {
+            $data = $this->_input([
+                'password'    => $this->request->post('password'),
+                'repassword'  => $this->request->post('repassword'),
+                'oldpassword' => $this->request->post('oldpassword'),
+            ], [
+                'oldpassword' => 'require',
+                'password'    => 'require|min:4',
+                'repassword'  => 'require|confirm:password',
+            ], [
+                'oldpassword.require' => '旧密码不能为空!',
+                'password.require'    => '登录密码不能为空!',
+                'password.min'        => '登录密码长度不能少于4位有效字符!',
+                'repassword.require'  => '重复密码不能为空!',
+                'repassword.confirm'  => '重复密码与登录密码不匹配,请重新输入!',
+            ]);
+            $user = Db::name('SystemUser')->where(['id' => $id])->find();
+            if (md5($data['oldpassword']) !== $user['password']) {
+                $this->error('旧密码验证失败,请重新输入!');
+            }
+            if (Data::save('SystemUser', ['id' => $user['id'], 'password' => md5($data['password'])])) {
+                $this->success('密码修改成功,下次请使用新密码登录!', '');
+            } else {
+                $this->error('密码修改失败,请稍候再试!');
+            }
+        }
+    }
+
+    /**
+     * 修改用户资料
+     * @login true
+     * @param integer $id 会员ID
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function info($id = 0)
+    {
+        if (!AdminService::instance()->isLogin()) {
+            $this->error('需要登录才能操作哦!');
+        }
+        $this->applyCsrfToken();
+        if (intval($id) === intval(session('user.id'))) {
+            $this->_form('SystemUser', 'admin@user/form', 'id', [], ['id' => $id]);
+        } else {
+            $this->error('只能修改登录用户的资料!');
+        }
+    }
+
+    /**
+     * 清理运行缓存
+     * @auth true
+     */
+    public function clearRuntime()
+    {
+        try {
+            Console::call('clear');
+            Console::call('xclean:session');
+            $this->success('清理运行缓存成功!');
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            $this->error("清理运行缓存失败,{$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 压缩发布系统
+     * @auth true
+     */
+    public function buildOptimize()
+    {
+        try {
+            Console::call('optimize:route');
+            Console::call('optimize:schema');
+            Console::call('optimize:autoload');
+            Console::call('optimize:config');
+            $this->success('压缩发布成功!');
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            $this->error("压缩发布失败,{$e->getMessage()}");
+        }
+    }
+
+}

+ 118 - 0
application/admin/controller/Login.php

@@ -0,0 +1,118 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller;
+
+use library\Controller;
+use library\service\AdminService;
+use library\service\CaptchaService;
+use library\service\SystemService;
+use library\tools\Data;
+use think\Db;
+use think\facade\Request;
+
+/**
+ * 用户登录管理
+ * Class Login
+ * @package app\admin\controller
+ */
+class Login extends Controller
+{
+
+    /**
+     * 后台登录入口
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        if (Request::isGet()) {
+            if (AdminService::instance()->isLogin()) {
+                $this->redirect('@admin');
+            } else {
+                $this->title = '系统登录';
+                $this->captcha_type = 'login_captcha';
+                $this->captcha_token = Data::uniqidDateCode(18);
+                $this->app->session->set($this->captcha_type, $this->captcha_token);
+                $this->devmode = SystemService::instance()->checkRunMode('dev');
+                $this->fetch();
+            }
+        } else {
+            $data = $this->_vali([
+                'username.require' => '登录账号不能为空!',
+                'username.min:4'   => '登录账号长度不能少于4位有效字符!',
+                'password.require' => '登录密码不能为空!',
+                'password.min:4'   => '登录密码长度不能少于4位有效字符!',
+                'verify.require'   => '图形验证码不能为空!',
+                'uniqid.require'   => '图形验证标识不能为空!',
+            ]);
+            if (!CaptchaService::instance()->check($data['verify'], $data['uniqid'])) {
+                $this->error('图形验证码验证失败,请重新输入!');
+            }
+            // 用户信息验证
+            $map = ['is_deleted' => '0', 'username' => $data['username']];
+            $user = Db::name('SystemUser')->where($map)->order('id desc')->find();
+            if (empty($user)) {
+                $this->error('登录账号或密码错误,请重新输入1!');
+            }
+            if (md5("{$user['password']}{$data['uniqid']}") !== $data['password']) {
+                $this->error('登录账号或密码错误,请重新输入2!');
+            }
+            if (empty($user['status'])) {
+                $this->error('账号已经被禁用,请联系管理员!');
+            }
+            Db::name('SystemUser')->where(['id' => $user['id']])->update([
+                'login_ip'  => Request::ip(),
+                'login_at'  => Db::raw('now()'),
+                'login_num' => Db::raw('login_num+1'),
+            ]);
+            $this->app->session->set('user', $user);
+            AdminService::instance()->apply(true);
+            sysoplog('系统管理', '用户登录系统后台成功');
+            $this->success('登录成功', url('@admin'));
+        }
+    }
+
+    /**
+     * 生成验证码
+     * 需要指定类型及令牌
+     */
+    public function captcha()
+    {
+        $image = CaptchaService::instance();
+        $this->type = input('type', 'captcha-type');
+        $this->token = input('token', 'captcha-token');
+        $captcha = ['image' => $image->getData(), 'uniqid' => $image->getUniqid()];
+        if ($this->app->session->get($this->type) === $this->token) {
+            $captcha['code'] = $image->getCode();
+            $this->app->session->delete($this->type);
+        }
+        $this->success('生成验证码成功', $captcha);
+    }
+
+    /**
+     * 退出登录
+     */
+    public function out()
+    {
+        $this->app->session->clear();
+        $this->app->session->destroy();
+        $this->success('退出登录成功!', url('@admin/login'));
+    }
+
+}

+ 160 - 0
application/admin/controller/Menu.php

@@ -0,0 +1,160 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller;
+
+use library\Controller;
+use library\service\MenuService;
+use library\tools\Data;
+use think\Db;
+
+/**
+ * 系统菜单管理
+ * Class Menu
+ * @package app\admin\controller
+ */
+class Menu extends Controller
+{
+
+    /**
+     * 当前操作数据库
+     * @var string
+     */
+    protected $table = 'SystemMenu';
+
+    /**
+     * 系统菜单管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '系统菜单管理';
+        $this->_page($this->table, false);
+    }
+
+    /**
+     * 列表数据处理
+     * @param array $data
+     */
+    protected function _index_page_filter(&$data)
+    {
+        foreach ($data as &$vo) {
+            if ($vo['url'] !== '#') {
+                $vo['url'] = trim(url($vo['url']) . (empty($vo['params']) ? '' : "?{$vo['params']}"), '/\\');
+            }
+            $vo['ids'] = join(',', Data::getArrSubIds($data, $vo['id']));
+        }
+        $data = Data::arr2table($data);
+    }
+
+    /**
+     * 添加系统菜单
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function add()
+    {
+        $this->applyCsrfToken();
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 编辑系统菜单
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function edit()
+    {
+        $this->applyCsrfToken();
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 表单数据处理
+     * @param array $vo
+     * @throws \ReflectionException
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    protected function _form_filter(&$vo)
+    {
+        if ($this->request->isGet()) {
+            // 读取系统功能节点
+            $this->nodes = MenuService::instance()->getList();
+            // 选择自己的上级菜单
+            if (empty($vo['pid']) && $this->request->get('pid', '0')) $vo['pid'] = $this->request->get('pid', '0');
+            // 列出可选上级菜单
+            $menus = Db::name($this->table)->where(['status' => '1'])->order('sort desc,id asc')->column('id,pid,url,title');
+            $this->menus = Data::arr2table(array_merge($menus, [['id' => '0', 'pid' => '-1', 'url' => '#', 'title' => '顶部菜单']]));
+            if (isset($vo['id'])) foreach ($this->menus as $key => $menu) if ($menu['id'] === $vo['id']) $vo = $menu;
+            foreach ($this->menus as $key => &$menu) {
+                if ($menu['spt'] >= 3 || $menu['url'] !== '#') unset($this->menus[$key]);
+                if (isset($vo['spt']) && $vo['spt'] <= $menu['spt']) unset($this->menus[$key]);
+            }
+        }
+    }
+
+    /**
+     * 启用系统菜单
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function resume()
+    {
+        $this->applyCsrfToken();
+        $this->_save($this->table, ['status' => '1']);
+    }
+
+    /**
+     * 禁用系统菜单
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function forbid()
+    {
+        $this->applyCsrfToken();
+        $this->_save($this->table, ['status' => '0']);
+    }
+
+    /**
+     * 删除系统菜单
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->applyCsrfToken();
+        $this->_delete($this->table);
+    }
+
+}

+ 94 - 0
application/admin/controller/Oplog.php

@@ -0,0 +1,94 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller;
+
+use library\Controller;
+use think\Db;
+
+/**
+ * 系统日志管理
+ * Class Oplog
+ * @package app\admin\controller
+ */
+class Oplog extends Controller
+{
+
+    /**
+     * 指定当前数据表
+     * @var string
+     */
+    public $table = 'SystemLog';
+
+    /**
+     * 系统操作日志
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $this->title = '系统操作日志';
+        $query = $this->_query($this->table)->like('action,node,content,username,geoip');
+        $query->dateBetween('create_at')->order('id desc')->page();
+    }
+
+    /**
+     * 列表数据处理
+     * @auth true
+     * @param array $data
+     * @throws \Exception
+     */
+    protected function _index_page_filter(&$data)
+    {
+        $ip = new \Ip2Region();
+        foreach ($data as &$vo) {
+            $result = $ip->btreeSearch($vo['geoip']);
+            $vo['isp'] = isset($result['region']) ? $result['region'] : '';
+            $vo['isp'] = str_replace(['内网IP', '0', '|'], '', $vo['isp']);
+        }
+    }
+
+    /**
+     * 清理系统日志
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function clear()
+    {
+        if (Db::name($this->table)->whereRaw('1=1')->delete() !== false) {
+            $this->success('日志清理成功!');
+        } else {
+            $this->error('日志清理失败,请稍候再试!');
+        }
+    }
+
+    /**
+     * 删除系统日志
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->applyCsrfToken();
+        $this->_delete($this->table);
+    }
+
+}

+ 144 - 0
application/admin/controller/Queue.php

@@ -0,0 +1,144 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller;
+
+use library\Controller;
+use library\service\ProcessService;
+use think\Console;
+use think\Db;
+use think\exception\HttpResponseException;
+
+/**
+ * 系统系统任务
+ * Class Queue
+ * @package app\admin\controller
+ */
+class Queue extends Controller
+{
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    protected $table = 'SystemQueue';
+
+    /**
+     * 系统系统任务
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        if (session('user.username') === 'admin') try {
+            $this->message = Console::call('xtask:state')->fetch();
+            $this->command = ProcessService::instance()->think('xtask:start');
+            $this->listen = preg_match('/process.*?\d+.*?running/', $this->message, $attr);
+        } catch (\Exception $exception) {
+            $this->listen = false;
+            $this->message = $exception->getMessage();
+        }
+        $this->title = '系统任务管理';
+        $this->iswin = ProcessService::instance()->iswin();
+        $query = $this->_query($this->table)->dateBetween('create_at,start_at,end_at');
+        $query->like('title,preload')->equal('status')->order('id desc')->page();
+    }
+
+    /**
+     * 重启系统任务
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function redo()
+    {
+        $this->_save($this->table, ['status' => '1']);
+    }
+
+    /**
+     * WIN开始监听任务
+     * @auth true
+     */
+    public function start()
+    {
+        try {
+            $message = nl2br(Console::call('xtask:start')->fetch());
+            if (preg_match('/process.*?\d+/', $message, $attr)) {
+                $this->success('任务监听主进程启动成功!');
+            } else {
+                $this->error($message);
+            }
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            $this->error($e->getMessage());
+        }
+    }
+
+    /**
+     * WIN停止监听任务
+     * @auth true
+     */
+    public function stop()
+    {
+        try {
+            $message = nl2br(Console::call('xtask:stop')->fetch());
+            if (stripos($message, 'succeeded')) {
+                $this->success('停止任务监听主进程成功!');
+            } elseif (stripos($message, 'finish')) {
+                $this->success('没有找到需要停止的进程!');
+            } else {
+                $this->error($message);
+            }
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            $this->error($e->getMessage());
+        }
+    }
+
+    /**
+     * 清理3天前的记录
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function clear()
+    {
+        $map = [['time', '<', strtotime('-3days')]];
+        $result = Db::name($this->table)->where($map)->delete();
+        if ($result !== false) {
+            $this->success('成功清理3天前的任务记录!');
+        } else {
+            $this->error('清理3天前的任务记录失败!');
+        }
+    }
+
+    /**
+     * 删除系统任务
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->_delete($this->table);
+    }
+
+}

+ 173 - 0
application/admin/controller/User.php

@@ -0,0 +1,173 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller;
+
+use library\Controller;
+use library\tools\Data;
+use think\Db;
+
+/**
+ * 系统用户管理
+ * Class User
+ * @package app\admin\controller
+ */
+class User extends Controller
+{
+
+    /**
+     * 指定当前数据表
+     * @var string
+     */
+    public $table = 'SystemUser';
+
+    /**
+     * 系统用户管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '系统用户管理';
+        $query = $this->_query($this->table)->like('username,phone,mail')->equal('status');
+        $query->dateBetween('login_at,create_at')->where(['is_deleted' => '0'])->order('id desc')->page();
+    }
+
+    /**
+     * 添加系统用户
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function add()
+    {
+        $this->applyCsrfToken();
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 编辑系统用户
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function edit()
+    {
+        $this->applyCsrfToken();
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 修改用户密码
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function pass()
+    {
+        $this->applyCsrfToken();
+        if ($this->request->isGet()) {
+            $this->verify = false;
+            $this->_form($this->table, 'pass');
+        } else {
+            $post = $this->request->post();
+            if ($post['password'] !== $post['repassword']) {
+                $this->error('两次输入的密码不一致!');
+            }
+            if (Data::save($this->table, ['id' => $post['id'], 'password' => md5($post['password'])], 'id')) {
+                $this->success('密码修改成功,下次请使用新密码登录!', '');
+            } else {
+                $this->error('密码修改失败,请稍候再试!');
+            }
+        }
+    }
+
+    /**
+     * 表单数据处理
+     * @param array $data
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function _form_filter(&$data)
+    {
+        if ($this->request->isPost()) {
+            // 用户权限处理
+            $data['authorize'] = (isset($data['authorize']) && is_array($data['authorize'])) ? join(',', $data['authorize']) : '';
+            // 用户账号重复检查
+            if (isset($data['id'])) unset($data['username']);
+            elseif (Db::name($this->table)->where(['username' => $data['username'], 'is_deleted' => '0'])->count() > 0) {
+                $this->error("账号{$data['username']}已经存在,请使用其它账号!");
+            }
+        } else {
+            $data['authorize'] = explode(',', isset($data['authorize']) ? $data['authorize'] : '');
+            $this->authorizes = Db::name('SystemAuth')->where(['status' => '1'])->order('sort desc,id desc')->select();
+        }
+    }
+
+    /**
+     * 禁用系统用户
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function forbid()
+    {
+        if (in_array('10000', explode(',', $this->request->post('id')))) {
+            $this->error('系统超级账号禁止操作!');
+        }
+        $this->applyCsrfToken();
+        $this->_save($this->table, ['status' => '0']);
+    }
+
+    /**
+     * 启用系统用户
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function resume()
+    {
+        $this->applyCsrfToken();
+        $this->_save($this->table, ['status' => '1']);
+    }
+
+    /**
+     * 删除系统用户
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        if (in_array('10000', explode(',', $this->request->post('id')))) {
+            $this->error('系统超级账号禁止删除!');
+        }
+        $this->applyCsrfToken();
+        $this->_delete($this->table);
+    }
+
+}

+ 130 - 0
application/admin/controller/api/Plugs.php

@@ -0,0 +1,130 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller\api;
+
+use library\Controller;
+use library\File;
+
+/**
+ * 后台插件管理
+ * Class Plugs
+ * @package app\admin\controller\api
+ */
+class Plugs extends Controller
+{
+
+    /**
+     * 系统图标选择器
+     */
+    public function icon()
+    {
+        $this->title = '图标选择器';
+        $this->field = input('field', 'icon');
+        $this->fetch();
+    }
+
+    /**
+     * 获取文件上传参数
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function check()
+    {
+        $diff1 = explode(',', strtolower(input('exts', '')));
+        $diff2 = explode(',', strtolower(sysconf('storage_local_exts')));
+        $exts = array_intersect($diff1, $diff2);
+        $this->success('获取文件上传参数', [
+            'exts' => join('|', $exts),
+            'mime' => File::mine($exts),
+            'type' => $this->getUploadType(),
+            'data' => $this->getUploadData(),
+        ]);
+    }
+
+    /**
+     * 后台通用文件上传
+     * @login true
+     * @return \think\response\Json
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function upload()
+    {
+        if (!($file = $this->getUploadFile()) || empty($file)) {
+            return json(['uploaded' => false, 'error' => ['message' => '文件上传异常,文件可能过大或未上传']]);
+        }
+        if (!$file->checkExt(strtolower(sysconf('storage_local_exts')))) {
+            return json(['uploaded' => false, 'error' => ['message' => '文件上传类型受限,请在后台配置']]);
+        }
+        if ($file->checkExt('php,sh')) {
+            return json(['uploaded' => false, 'error' => ['message' => '可执行文件禁止上传到本地服务器']]);
+        }
+        $this->safe = boolval(input('safe'));
+        $this->uptype = $this->getUploadType();
+        $this->extend = pathinfo($file->getInfo('name'), PATHINFO_EXTENSION);
+        $name = File::name($file->getPathname(), $this->extend, '', 'md5_file');
+        $info = File::instance($this->uptype)->save($name, file_get_contents($file->getRealPath()), $this->safe);
+        if (is_array($info) && isset($info['url'])) {
+            return json(['uploaded' => true, 'filename' => $name, 'url' => $this->safe ? $name : $info['url']]);
+        } else {
+            return json(['uploaded' => false, 'error' => ['message' => '文件处理失败,请稍候再试!']]);
+        }
+    }
+
+    /**
+     * 生成文件上传参数
+     * @return array
+     * @throws \think\Exception
+     */
+    private function getUploadData()
+    {
+        if ($this->getUploadType() === 'qiniu') {
+            $file = File::instance('qiniu');
+            return ['url' => $file->upload(true), 'token' => $file->buildUploadToken(), 'uptype' => $this->getUploadType()];
+        } else {
+            return ['url' => '?s=admin/api.plugs/upload', 'token' => uniqid('local_upload_'), 'uptype' => $this->getUploadType()];
+        }
+    }
+
+    /**
+     * 获取文件上传方式
+     * @return string
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    private function getUploadType()
+    {
+        $this->uptype = input('uptype');
+        if (!in_array($this->uptype, ['local', 'oss', 'qiniu'])) {
+            $this->uptype = sysconf('storage_type');
+        }
+        return $this->uptype;
+    }
+
+    /**
+     * 获取本地文件对象
+     * @return \think\File
+     */
+    private function getUploadFile()
+    {
+        try {
+            return $this->request->file('file');
+        } catch (\Exception $e) {
+            $this->error(lang($e->getMessage()));
+        }
+    }
+
+}

+ 65 - 0
application/admin/controller/api/Update.php

@@ -0,0 +1,65 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\admin\controller\api;
+
+use library\command\Sync;
+use library\Controller;
+
+/**
+ * 系统更新接口
+ * Class Update
+ * @package app\admin\controller\api
+ */
+class Update extends Controller
+{
+    /**
+     * 访问权限
+     */
+    protected function initialize()
+    {
+        $domain = $this->app->request->host(true);
+        $isDemo = is_numeric(stripos($domain, 'thinkadmin.top'));
+        $isLocal = in_array($domain, ['127.0.0.1', 'localhost']);
+        if (!$isLocal && !$isDemo) $this->error('只允许访问本地或官方代码!');
+    }
+
+    /**
+     * 获取文件列表
+     */
+    public function tree()
+    {
+        $sync = new Sync('Update');
+        $this->success('获取当前文件列表成功!', $sync->build());
+    }
+
+    /**
+     * 读取线上文件数据
+     * @param string $encode
+     */
+    public function read($encode)
+    {
+        $this->file = env('root_path') . decode($encode);
+        if (file_exists($this->file)) {
+            $this->success('读取文件成功!', [
+                'format'  => 'base64',
+                'content' => base64_encode(file_get_contents($this->file)),
+            ]);
+        } else {
+            $this->error('获取文件内容失败!');
+        }
+    }
+
+}

+ 159 - 0
application/admin/sys.php

@@ -0,0 +1,159 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+use library\File;
+use library\service\AdminService;
+use library\service\CaptchaService;
+use library\service\SystemService;
+use think\Db;
+use think\facade\Middleware;
+use think\facade\Route;
+use think\Request;
+
+if (!function_exists('auth')) {
+    /**
+     * 节点访问权限检查
+     * @param string $node 需要检查的节点
+     * @return boolean
+     * @throws ReflectionException
+     */
+    function auth($node)
+    {
+        return AdminService::instance()->check($node);
+    }
+}
+
+if (!function_exists('sysdata')) {
+    /**
+     * JSON 数据读取与存储
+     * @param string $name 数据名称
+     * @param mixed $value 数据内容
+     * @return mixed
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    function sysdata($name, $value = null)
+    {
+        if (is_null($value)) {
+            return SystemService::instance()->getData($name);
+        } else {
+            return SystemService::instance()->setData($name, $value);
+        }
+    }
+}
+
+if (!function_exists('sysoplog')) {
+    /**
+     * 写入系统日志
+     * @param string $action 日志行为
+     * @param string $content 日志内容
+     * @return boolean
+     */
+    function sysoplog($action, $content)
+    {
+        return SystemService::instance()->setOplog($action, $content);
+    }
+}
+
+if (!function_exists('sysqueue')) {
+    /**
+     * 创建异步处理任务
+     * @param string $title 任务名称
+     * @param string $loade 执行内容
+     * @param integer $later 延时执行时间
+     * @param array $data 任务附加数据
+     * @param integer $double 任务多开
+     * @return boolean
+     * @throws \think\Exception
+     */
+    function sysqueue($title, $loade, $later = 0, $data = [], $double = 1)
+    {
+        $map = [['title', 'eq', $title], ['status', 'in', [1, 2]]];
+        if (empty($double) && Db::name('SystemQueue')->where($map)->count() > 0) {
+            throw new \think\Exception('该任务已经创建,请耐心等待处理完成!');
+        }
+        $result = Db::name('SystemQueue')->insert([
+            'title'  => $title, 'preload' => $loade,
+            'data'   => json_encode($data, JSON_UNESCAPED_UNICODE),
+            'time'   => $later > 0 ? time() + $later : time(),
+            'double' => intval($double), 'create_at' => date('Y-m-d H:i:s'),
+        ]);
+        return $result !== false;
+    }
+}
+
+if (!function_exists('local_image')) {
+    /**
+     * 下载远程文件到本地
+     * @param string $url 远程图片地址
+     * @param boolean $force 是否强制重新下载
+     * @param integer $expire 强制本地存储时间
+     * @return string
+     */
+    function local_image($url, $force = false, $expire = 0)
+    {
+        $result = File::down($url, $force, $expire);
+        if (isset($result['url'])) {
+            return $result['url'];
+        } else {
+            return $url;
+        }
+    }
+}
+
+if (!function_exists('base64_image')) {
+    /**
+     * base64 图片上传接口
+     * @param string $content 图片base64内容
+     * @param string $dirname 图片存储目录
+     * @return string
+     */
+    function base64_image($content, $dirname = 'base64/')
+    {
+        try {
+            if (preg_match('|^data:image/(.*?);base64,|i', $content)) {
+                list($ext, $base) = explode('|||', preg_replace('|^data:image/(.*?);base64,|i', '$1|||', $content));
+                $info = File::save($dirname . md5($base) . '.' . (empty($ext) ? 'tmp' : $ext), base64_decode($base));
+                return $info['url'];
+            } else {
+                return $content;
+            }
+        } catch (\Exception $e) {
+            return $content;
+        }
+    }
+}
+
+// 访问权限检查中间键
+Middleware::add(function (Request $request, \Closure $next) {
+    if (AdminService::instance()->check()) {
+        return $next($request);
+    } elseif (AdminService::instance()->isLogin()) {
+        return json(['code' => 0, 'msg' => '抱歉,没有访问该操作的权限!']);
+    } else {
+        return json(['code' => 0, 'msg' => '抱歉,需要登录获取访问权限!', 'url' => url('@admin/login')]);
+    }
+});
+
+// ThinkAdmin 图形验证码
+Route::get('/think/admin/captcha', function () {
+    $image = CaptchaService::instance();
+    return json(['code' => '1', 'info' => '生成验证码', 'data' => [
+        'uniqid' => $image->getUniqid(), 'image' => $image->getData()
+    ]]);
+});

+ 3659 - 0
application/admin/view/api/plugs/icon.html

@@ -0,0 +1,3659 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <title>{block name="title"}{$title|default=''}{if !empty($title)} · {/if}{:sysconf('site_name')}{/block}</title>
+    <meta name="renderer" content="webkit">
+    <meta name="format-detection" content="telephone=no">
+    <meta name="apple-mobile-web-app-capable" content="yes">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="apple-mobile-web-app-status-bar-style" content="black">
+    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
+    <link href="__ROOT__/static/plugs/awesome/fonts.css?v={:date('ymd')}" rel="stylesheet">
+    <link href="__ROOT__/static/plugs/layui/css/layui.css?v={:date('ymd')}" rel="stylesheet">
+
+    <style>
+        ::-webkit-input-placeholder {
+            color: #aaa
+        }
+
+        ::-webkit-scrollbar {
+            width: 3px;
+            height: 3px
+        }
+
+        ::-webkit-scrollbar-track {
+            background: #ccc
+        }
+
+        ::-webkit-scrollbar-thumb {
+            background-color: #666
+        }
+
+        ::selection {
+            background-color: #ec494e;
+            color: #fff
+        }
+
+        ::-moz-selection {
+            background-color: #ec494e;
+            color: #fff
+        }
+
+        :-webkit-autofill {
+            -webkit-box-shadow: 0 0 0 1000px white inset;
+            -webkit-text-fill-color: #333
+        }
+
+        ul li {
+            width: 20%;
+            height: 65px;
+            display: block;
+            float: left;
+            margin-right: -1px;
+            margin-left: -2px;
+            margin-bottom: -2px;
+            cursor: pointer;
+            font-size: 14px;
+            overflow: hidden;
+            text-align: center;
+            padding: 15px 0 10px 0;
+            border: 1px solid #e2e2e2;
+            background-color: #efefef;
+            user-select: none;
+            -ms-user-select: none;
+            -moz-user-select: none;
+            -webkit-user-select: none;
+            transition: all .2s linear;
+            -o-transition: all .2s linear;
+            -moz-transition: all .2s linear;
+            -webkit-transition: all .2s linear
+        }
+
+        ul li:hover {
+            color: #fff;
+            background-color: #563d7c
+        }
+
+        ul li:hover i, ul li:hover div {
+            color: #fff;
+        }
+
+        ul li i {
+            color: #333;
+            display: inline-block;
+            font-size: 30px !important
+        }
+
+        ul li div {
+            color: #333;
+            height: 35px;
+            font-size: 13px;
+            line-height: 35px;
+            white-space: nowrap
+        }
+    </style>
+</head>
+<body>
+<ul class="site-doc-icon">
+    <li>
+        <i class="layui-icon layui-icon-rate-half"></i>
+        <div class="icon-title">layui-icon-rate-half</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-rate"></i>
+        <div class="icon-title">layui-icon-rate</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-rate-solid"></i>
+        <div class="icon-title">layui-icon-rate-solid</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-cellphone"></i>
+        <div class="icon-title">layui-icon-cellphone</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-vercode"></i>
+        <div class="icon-title">layui-icon-vercode</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-login-wechat"></i>
+        <div class="icon-title">layui-icon-login-wechat</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-login-qq"></i>
+        <div class="icon-title">layui-icon-login-qq</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-login-weibo"></i>
+        <div class="icon-title">layui-icon-login-weibo</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-password"></i>
+        <div class="icon-title">layui-icon-password</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-username"></i>
+        <div class="icon-title">layui-icon-username</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-refresh-3"></i>
+        <div class="icon-title">layui-icon-refresh-3</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-auz"></i>
+        <div class="icon-title">layui-icon-auz</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-spread-left"></i>
+        <div class="icon-title">layui-icon-spread-left</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-shrink-right"></i>
+        <div class="icon-title">layui-icon-shrink-right</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-snowflake"></i>
+        <div class="icon-title">layui-icon-snowflake</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-tips"></i>
+        <div class="icon-title">layui-icon-tips</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-note"></i>
+        <div class="icon-title">layui-icon-note</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-home"></i>
+        <div class="icon-title">layui-icon-home</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-senior"></i>
+        <div class="icon-title">layui-icon-senior</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-refresh"></i>
+        <div class="icon-title">layui-icon-refresh</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-refresh-1"></i>
+        <div class="icon-title">layui-icon-refresh-1</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-flag"></i>
+        <div class="icon-title">layui-icon-flag</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-theme"></i>
+        <div class="icon-title">layui-icon-theme</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-notice"></i>
+        <div class="icon-title">layui-icon-notice</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-website"></i>
+        <div class="icon-title">layui-icon-website</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-console"></i>
+        <div class="icon-title">layui-icon-console</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-face-surprised"></i>
+        <div class="icon-title">layui-icon-face-surprised</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-set"></i>
+        <div class="icon-title">layui-icon-set</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-template-1"></i>
+        <div class="icon-title">layui-icon-template-1</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-app"></i>
+        <div class="icon-title">layui-icon-app</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-template"></i>
+        <div class="icon-title">layui-icon-template</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-praise"></i>
+        <div class="icon-title">layui-icon-praise</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-tread"></i>
+        <div class="icon-title">layui-icon-tread</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-male"></i>
+        <div class="icon-title">layui-icon-male</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-female"></i>
+        <div class="icon-title">layui-icon-female</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-camera"></i>
+        <div class="icon-title">layui-icon-camera</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-camera-fill"></i>
+        <div class="icon-title">layui-icon-camera-fill</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-more"></i>
+        <div class="icon-title">layui-icon-more</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-more-vertical"></i>
+        <div class="icon-title">layui-icon-more-vertical</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-rmb"></i>
+        <div class="icon-title">layui-icon-rmb</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-dollar"></i>
+        <div class="icon-title">layui-icon-dollar</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-diamond"></i>
+        <div class="icon-title">layui-icon-diamond</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-fire"></i>
+        <div class="icon-title">layui-icon-fire</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-return"></i>
+        <div class="icon-title">layui-icon-return</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-location"></i>
+        <div class="icon-title">layui-icon-location</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-read"></i>
+        <div class="icon-title">layui-icon-read</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-survey"></i>
+        <div class="icon-title">layui-icon-survey</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-face-smile"></i>
+        <div class="icon-title">layui-icon-face-smile</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-face-cry"></i>
+        <div class="icon-title">layui-icon-face-cry</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-cart-simple"></i>
+        <div class="icon-title">layui-icon-cart-simple</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-cart"></i>
+        <div class="icon-title">layui-icon-cart</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-next"></i>
+        <div class="icon-title">layui-icon-next</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-prev"></i>
+        <div class="icon-title">layui-icon-prev</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-upload-drag"></i>
+        <div class="icon-title">layui-icon-upload-drag</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-upload"></i>
+        <div class="icon-title">layui-icon-upload</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-download-circle"></i>
+        <div class="icon-title">layui-icon-download-circle</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-component"></i>
+        <div class="icon-title">layui-icon-component</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-file-b"></i>
+        <div class="icon-title">layui-icon-file-b</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-user"></i>
+        <div class="icon-title">layui-icon-user</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-find-fill"></i>
+        <div class="icon-title">layui-icon-find-fill</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-loading layui-icon layui-anim layui-anim-rotate layui-anim-loop"></i>
+        <div class="icon-title">layui-icon-loading</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-loading-1 layui-icon layui-anim layui-anim-rotate layui-anim-loop"></i>
+        <div class="icon-title">layui-icon-loading-1</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-add-1"></i>
+        <div class="icon-title">layui-icon-add-1</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-play"></i>
+        <div class="icon-title">layui-icon-play</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-pause"></i>
+        <div class="icon-title">layui-icon-pause</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-headset"></i>
+        <div class="icon-title">layui-icon-headset</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-video"></i>
+        <div class="icon-title">layui-icon-video</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-voice"></i>
+        <div class="icon-title">layui-icon-voice</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-speaker"></i>
+        <div class="icon-title">layui-icon-speaker</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-fonts-del"></i>
+        <div class="icon-title">layui-icon-fonts-del</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-fonts-code"></i>
+        <div class="icon-title">layui-icon-fonts-code</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-fonts-html"></i>
+        <div class="icon-title">layui-icon-fonts-html</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-fonts-strong"></i>
+        <div class="icon-title">layui-icon-fonts-strong</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-unlink"></i>
+        <div class="icon-title">layui-icon-unlink</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-picture"></i>
+        <div class="icon-title">layui-icon-picture</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-link"></i>
+        <div class="icon-title">layui-icon-link</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-face-smile-b"></i>
+        <div class="icon-title">layui-icon-face-smile-b</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-align-left"></i>
+        <div class="icon-title">layui-icon-align-left</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-align-right"></i>
+        <div class="icon-title">layui-icon-align-right</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-align-center"></i>
+        <div class="icon-title">layui-icon-align-center</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-fonts-u"></i>
+        <div class="icon-title">layui-icon-fonts-u</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-fonts-i"></i>
+        <div class="icon-title">layui-icon-fonts-i</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-tabs"></i>
+        <div class="icon-title">layui-icon-tabs</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-radio"></i>
+        <div class="icon-title">layui-icon-radio</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-circle"></i>
+        <div class="icon-title">layui-icon-circle</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-edit"></i>
+        <div class="icon-title">layui-icon-edit</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-share"></i>
+        <div class="icon-title">layui-icon-share</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-delete"></i>
+        <div class="icon-title">layui-icon-delete</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-form"></i>
+        <div class="icon-title">layui-icon-form</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-cellphone-fine"></i>
+        <div class="icon-title">layui-icon-cellphone-fine</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-dialogue"></i>
+        <div class="icon-title">layui-icon-dialogue</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-fonts-clear"></i>
+        <div class="icon-title">layui-icon-fonts-clear</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-layer"></i>
+        <div class="icon-title">layui-icon-layer</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-date"></i>
+        <div class="icon-title">layui-icon-date</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-water"></i>
+        <div class="icon-title">layui-icon-water</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-code-circle"></i>
+        <div class="icon-title">layui-icon-code-circle</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-carousel"></i>
+        <div class="icon-title">layui-icon-carousel</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-prev-circle"></i>
+        <div class="icon-title">layui-icon-prev-circle</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-layouts"></i>
+        <div class="icon-title">layui-icon-layouts</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-util"></i>
+        <div class="icon-title">layui-icon-util</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-templeate-1"></i>
+        <div class="icon-title">layui-icon-templeate-1</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-upload-circle"></i>
+        <div class="icon-title">layui-icon-upload-circle</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-tree"></i>
+        <div class="icon-title">layui-icon-tree</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-table"></i>
+        <div class="icon-title">layui-icon-table</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-chart"></i>
+        <div class="icon-title">layui-icon-chart</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-chart-screen"></i>
+        <div class="icon-title">layui-icon-chart-screen</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-engine"></i>
+        <div class="icon-title">layui-icon-engine</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-triangle-d"></i>
+        <div class="icon-title">layui-icon-triangle-d</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-triangle-r"></i>
+        <div class="icon-title">layui-icon-triangle-r</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-file"></i>
+        <div class="icon-title">layui-icon-file</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-set-sm"></i>
+        <div class="icon-title">layui-icon-set-sm</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-add-circle"></i>
+        <div class="icon-title">layui-icon-add-circle</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-404"></i>
+        <div class="icon-title">layui-icon-404</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-about"></i>
+        <div class="icon-title">layui-icon-about</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-up"></i>
+        <div class="icon-title">layui-icon-up</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-down"></i>
+        <div class="icon-title">layui-icon-down</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-left"></i>
+        <div class="icon-title">layui-icon-left</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-right"></i>
+        <div class="icon-title">layui-icon-right</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-circle-dot"></i>
+        <div class="icon-title">layui-icon-circle-dot</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-search"></i>
+        <div class="icon-title">layui-icon-search</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-set-fill"></i>
+        <div class="icon-title">layui-icon-set-fill</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-group"></i>
+        <div class="icon-title">layui-icon-group</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-friends"></i>
+        <div class="icon-title">layui-icon-friends</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-reply-fill"></i>
+        <div class="icon-title">layui-icon-reply-fill</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-menu-fill"></i>
+        <div class="icon-title">layui-icon-menu-fill</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-log"></i>
+        <div class="icon-title">layui-icon-log</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-picture-fine"></i>
+        <div class="icon-title">layui-icon-picture-fine</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-face-smile-fine"></i>
+        <div class="icon-title">layui-icon-face-smile-fine</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-list"></i>
+        <div class="icon-title">layui-icon-list</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-release"></i>
+        <div class="icon-title">layui-icon-release</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-ok"></i>
+        <div class="icon-title">layui-icon-ok</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-help"></i>
+        <div class="icon-title">layui-icon-help</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-chat"></i>
+        <div class="icon-title">layui-icon-chat</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-top"></i>
+        <div class="icon-title">layui-icon-top</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-star"></i>
+        <div class="icon-title">layui-icon-star</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-star-fill"></i>
+        <div class="icon-title">layui-icon-star-fill</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-close-fill"></i>
+        <div class="icon-title">layui-icon-close-fill</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-close"></i>
+        <div class="icon-title">layui-icon-close</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-ok-circle"></i>
+        <div class="icon-title">layui-icon-ok-circle</div>
+    </li>
+    <li>
+        <i class="layui-icon layui-icon-add-circle-fine"></i>
+        <div class="icon-title">layui-icon-add-circle-fine</div>
+    </li>
+</ul>
+<ul>
+    <li>
+        <i class="fa fa-address-book"></i>
+        <div class="icon-title">fa-address-book</div>
+    </li>
+    <li>
+        <i class="fa fa-address-book-o"></i>
+        <div class="icon-title">fa-address-book-o</div>
+    </li>
+    <li>
+        <i class="fa fa-address-card"></i>
+        <div class="icon-title">fa-address-card</div>
+    </li>
+    <li>
+        <i class="fa fa-address-card-o"></i>
+        <div class="icon-title">fa-address-card-o</div>
+    </li>
+    <li>
+        <i class="fa fa-adjust"></i>
+        <div class="icon-title">fa-adjust</div>
+    </li>
+    <li>
+        <i class="fa fa-american-sign-language-interpreting"></i>
+        <div class="icon-title">fa-american-sign-language-interpreting</div>
+    </li>
+    <li>
+        <i class="fa fa-anchor"></i>
+        <div class="icon-title">fa-anchor</div>
+    </li>
+    <li>
+        <i class="fa fa-archive"></i>
+        <div class="icon-title">fa-archive</div>
+    </li>
+    <li>
+        <i class="fa fa-area-chart"></i>
+        <div class="icon-title">fa-area-chart</div>
+    </li>
+    <li>
+        <i class="fa fa-arrows"></i>
+        <div class="icon-title">fa-arrows</div>
+    </li>
+    <li>
+        <i class="fa fa-arrows-h"></i>
+        <div class="icon-title">fa-arrows-h</div>
+    </li>
+    <li>
+        <i class="fa fa-arrows-v"></i>
+        <div class="icon-title">fa-arrows-v</div>
+    </li>
+    <li>
+        <i class="fa fa-asl-interpreting"></i>
+        <div class="icon-title">fa-asl-interpreting</div>
+    </li>
+    <li>
+        <i class="fa fa-assistive-listening-systems"></i>
+        <div class="icon-title">fa-assistive-listening-systems</div>
+    </li>
+    <li>
+        <i class="fa fa-asterisk"></i>
+        <div class="icon-title">fa-asterisk</div>
+    </li>
+    <li>
+        <i class="fa fa-at"></i>
+        <div class="icon-title">fa-at</div>
+    </li>
+    <li>
+        <i class="fa fa-audio-description"></i>
+        <div class="icon-title">fa-audio-description</div>
+    </li>
+    <li>
+        <i class="fa fa-automobile"></i>
+        <div class="icon-title">fa-automobile</div>
+    </li>
+    <li>
+        <i class="fa fa-balance-scale"></i>
+        <div class="icon-title">fa-balance-scale</div>
+    </li>
+    <li>
+        <i class="fa fa-ban"></i>
+        <div class="icon-title">fa-ban</div>
+    </li>
+    <li>
+        <i class="fa fa-bank"></i>
+        <div class="icon-title">fa-bank</div>
+    </li>
+    <li>
+        <i class="fa fa-bar-chart"></i>
+        <div class="icon-title">fa-bar-chart</div>
+    </li>
+    <li>
+        <i class="fa fa-bar-chart-o"></i>
+        <div class="icon-title">fa-bar-chart-o</div>
+    </li>
+    <li>
+        <i class="fa fa-barcode"></i>
+        <div class="icon-title">fa-barcode</div>
+    </li>
+    <li>
+        <i class="fa fa-bars"></i>
+        <div class="icon-title">fa-bars</div>
+    </li>
+    <li>
+        <i class="fa fa-bath"></i>
+        <div class="icon-title">fa-bath</div>
+    </li>
+    <li>
+        <i class="fa fa-bathtub"></i>
+        <div class="icon-title">fa-bathtub</div>
+    </li>
+    <li>
+        <i class="fa fa-battery"></i>
+        <div class="icon-title">fa-battery</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-0"></i>
+        <div class="icon-title">fa-battery-0</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-1"></i>
+        <div class="icon-title">fa-battery-1</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-2"></i>
+        <div class="icon-title">fa-battery-2</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-3"></i>
+        <div class="icon-title">fa-battery-3</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-4"></i>
+        <div class="icon-title">fa-battery-4</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-empty"></i>
+        <div class="icon-title">fa-battery-empty</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-full"></i>
+        <div class="icon-title">fa-battery-full</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-half"></i>
+        <div class="icon-title">fa-battery-half</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-quarter"></i>
+        <div class="icon-title">fa-battery-quarter</div>
+    </li>
+    <li>
+        <i class="fa fa-battery-three-quarters"></i>
+        <div class="icon-title">fa-battery-three-quarters</div>
+    </li>
+    <li>
+        <i class="fa fa-bed"></i>
+        <div class="icon-title">fa-bed</div>
+    </li>
+    <li>
+        <i class="fa fa-beer"></i>
+        <div class="icon-title">fa-beer</div>
+    </li>
+    <li>
+        <i class="fa fa-bell"></i>
+        <div class="icon-title">fa-bell</div>
+    </li>
+    <li>
+        <i class="fa fa-bell-o"></i>
+        <div class="icon-title">fa-bell-o</div>
+    </li>
+    <li>
+        <i class="fa fa-bell-slash"></i>
+        <div class="icon-title">fa-bell-slash</div>
+    </li>
+    <li>
+        <i class="fa fa-bell-slash-o"></i>
+        <div class="icon-title">fa-bell-slash-o</div>
+    </li>
+    <li>
+        <i class="fa fa-bicycle"></i>
+        <div class="icon-title">fa-bicycle</div>
+    </li>
+    <li>
+        <i class="fa fa-binoculars"></i>
+        <div class="icon-title">fa-binoculars</div>
+    </li>
+    <li>
+        <i class="fa fa-birthday-cake"></i>
+        <div class="icon-title">fa-birthday-cake</div>
+    </li>
+    <li>
+        <i class="fa fa-blind"></i>
+        <div class="icon-title">fa-blind</div>
+    </li>
+    <li>
+        <i class="fa fa-bluetooth"></i>
+        <div class="icon-title">fa-bluetooth</div>
+    </li>
+    <li>
+        <i class="fa fa-bluetooth-b"></i>
+        <div class="icon-title">fa-bluetooth-b</div>
+    </li>
+    <li>
+        <i class="fa fa-bolt"></i>
+        <div class="icon-title">fa-bolt</div>
+    </li>
+    <li>
+        <i class="fa fa-bomb"></i>
+        <div class="icon-title">fa-bomb</div>
+    </li>
+    <li>
+        <i class="fa fa-book"></i>
+        <div class="icon-title">fa-book</div>
+    </li>
+    <li>
+        <i class="fa fa-bookmark"></i>
+        <div class="icon-title">fa-bookmark</div>
+    </li>
+    <li>
+        <i class="fa fa-bookmark-o"></i>
+        <div class="icon-title">fa-bookmark-o</div>
+    </li>
+    <li>
+        <i class="fa fa-braille"></i>
+        <div class="icon-title">fa-braille</div>
+    </li>
+    <li>
+        <i class="fa fa-briefcase"></i>
+        <div class="icon-title">fa-briefcase</div>
+    </li>
+    <li>
+        <i class="fa fa-bug"></i>
+        <div class="icon-title">fa-bug</div>
+    </li>
+    <li>
+        <i class="fa fa-building"></i>
+        <div class="icon-title">fa-building</div>
+    </li>
+    <li>
+        <i class="fa fa-building-o"></i>
+        <div class="icon-title">fa-building-o</div>
+    </li>
+    <li>
+        <i class="fa fa-bullhorn"></i>
+        <div class="icon-title">fa-bullhorn</div>
+    </li>
+    <li>
+        <i class="fa fa-bullseye"></i>
+        <div class="icon-title">fa-bullseye</div>
+    </li>
+    <li>
+        <i class="fa fa-bus"></i>
+        <div class="icon-title">fa-bus</div>
+    </li>
+    <li>
+        <i class="fa fa-cab"></i>
+        <div class="icon-title">fa-cab</div>
+    </li>
+    <li>
+        <i class="fa fa-calculator"></i>
+        <div class="icon-title">fa-calculator</div>
+    </li>
+    <li>
+        <i class="fa fa-calendar"></i>
+        <div class="icon-title">fa-calendar</div>
+    </li>
+    <li>
+        <i class="fa fa-calendar-check-o"></i>
+        <div class="icon-title">fa-calendar-check-o</div>
+    </li>
+    <li>
+        <i class="fa fa-calendar-minus-o"></i>
+        <div class="icon-title">fa-calendar-minus-o</div>
+    </li>
+    <li>
+        <i class="fa fa-calendar-o"></i>
+        <div class="icon-title">fa-calendar-o</div>
+    </li>
+    <li>
+        <i class="fa fa-calendar-plus-o"></i>
+        <div class="icon-title">fa-calendar-plus-o</div>
+    </li>
+    <li>
+        <i class="fa fa-calendar-times-o"></i>
+        <div class="icon-title">fa-calendar-times-o</div>
+    </li>
+    <li>
+        <i class="fa fa-camera"></i>
+        <div class="icon-title">fa-camera</div>
+    </li>
+    <li>
+        <i class="fa fa-camera-retro"></i>
+        <div class="icon-title">fa-camera-retro</div>
+    </li>
+    <li>
+        <i class="fa fa-car"></i>
+        <div class="icon-title">fa-car</div>
+    </li>
+    <li>
+        <i class="fa fa-caret-square-o-down"></i>
+        <div class="icon-title">fa-caret-square-o-down</div>
+    </li>
+    <li>
+        <i class="fa fa-caret-square-o-left"></i>
+        <div class="icon-title">fa-caret-square-o-left</div>
+    </li>
+    <li>
+        <i class="fa fa-caret-square-o-right"></i>
+        <div class="icon-title">fa-caret-square-o-right</div>
+    </li>
+    <li>
+        <i class="fa fa-caret-square-o-up"></i>
+        <div class="icon-title">fa-caret-square-o-up</div>
+    </li>
+    <li>
+        <i class="fa fa-cart-arrow-down"></i>
+        <div class="icon-title">fa-cart-arrow-down</div>
+    </li>
+    <li>
+        <i class="fa fa-cart-plus"></i>
+        <div class="icon-title">fa-cart-plus</div>
+    </li>
+    <li>
+        <i class="fa fa-cc"></i>
+        <div class="icon-title">fa-cc</div>
+    </li>
+    <li>
+        <i class="fa fa-certificate"></i>
+        <div class="icon-title">fa-certificate</div>
+    </li>
+    <li>
+        <i class="fa fa-check"></i>
+        <div class="icon-title">fa-check</div>
+    </li>
+    <li>
+        <i class="fa fa-check-circle"></i>
+        <div class="icon-title">fa-check-circle</div>
+    </li>
+    <li>
+        <i class="fa fa-check-circle-o"></i>
+        <div class="icon-title">fa-check-circle-o</div>
+    </li>
+    <li>
+        <i class="fa fa-check-square"></i>
+        <div class="icon-title">fa-check-square</div>
+    </li>
+    <li>
+        <i class="fa fa-check-square-o"></i>
+        <div class="icon-title">fa-check-square-o</div>
+    </li>
+    <li>
+        <i class="fa fa-child"></i>
+        <div class="icon-title">fa-child</div>
+    </li>
+    <li>
+        <i class="fa fa-circle"></i>
+        <div class="icon-title">fa-circle</div>
+    </li>
+    <li>
+        <i class="fa fa-circle-o"></i>
+        <div class="icon-title">fa-circle-o</div>
+    </li>
+    <li>
+        <i class="fa fa-circle-o-notch"></i>
+        <div class="icon-title">fa-circle-o-notch</div>
+    </li>
+    <li>
+        <i class="fa fa-circle-thin"></i>
+        <div class="icon-title">fa-circle-thin</div>
+    </li>
+    <li>
+        <i class="fa fa-clock-o"></i>
+        <div class="icon-title">fa-clock-o</div>
+    </li>
+    <li>
+        <i class="fa fa-clone"></i>
+        <div class="icon-title">fa-clone</div>
+    </li>
+    <li>
+        <i class="fa fa-close"></i>
+        <div class="icon-title">fa-close</div>
+    </li>
+    <li>
+        <i class="fa fa-cloud"></i>
+        <div class="icon-title">fa-cloud</div>
+    </li>
+    <li>
+        <i class="fa fa-cloud-download"></i>
+        <div class="icon-title">fa-cloud-download</div>
+    </li>
+    <li>
+        <i class="fa fa-cloud-upload"></i>
+        <div class="icon-title">fa-cloud-upload</div>
+    </li>
+    <li>
+        <i class="fa fa-code"></i>
+        <div class="icon-title">fa-code</div>
+    </li>
+    <li>
+        <i class="fa fa-code-fork"></i>
+        <div class="icon-title">fa-code-fork</div>
+    </li>
+    <li>
+        <i class="fa fa-coffee"></i>
+        <div class="icon-title">fa-coffee</div>
+    </li>
+    <li>
+        <i class="fa fa-cog"></i>
+        <div class="icon-title">fa-cog</div>
+    </li>
+    <li>
+        <i class="fa fa-cogs"></i>
+        <div class="icon-title">fa-cogs</div>
+    </li>
+    <li>
+        <i class="fa fa-comment"></i>
+        <div class="icon-title">fa-comment</div>
+    </li>
+    <li>
+        <i class="fa fa-comment-o"></i>
+        <div class="icon-title">fa-comment-o</div>
+    </li>
+    <li>
+        <i class="fa fa-commenting"></i>
+        <div class="icon-title">fa-commenting</div>
+    </li>
+    <li>
+        <i class="fa fa-commenting-o"></i>
+        <div class="icon-title">fa-commenting-o</div>
+    </li>
+    <li>
+        <i class="fa fa-comments"></i>
+        <div class="icon-title">fa-comments</div>
+    </li>
+    <li>
+        <i class="fa fa-comments-o"></i>
+        <div class="icon-title">fa-comments-o</div>
+    </li>
+    <li>
+        <i class="fa fa-compass"></i>
+        <div class="icon-title">fa-compass</div>
+    </li>
+    <li>
+        <i class="fa fa-copyright"></i>
+        <div class="icon-title">fa-copyright</div>
+    </li>
+    <li>
+        <i class="fa fa-creative-commons"></i>
+        <div class="icon-title">fa-creative-commons</div>
+    </li>
+    <li>
+        <i class="fa fa-credit-card"></i>
+        <div class="icon-title">fa-credit-card</div>
+    </li>
+    <li><i class="fa fa-credit-card-alt"></i>
+        <div class="icon-title">fa-credit-card-alt</div>
+    </li>
+    <li><i class="fa fa-crop"></i>
+        <div class="icon-title">fa-crop</div>
+    </li>
+    <li><i class="fa fa-crosshairs"></i>
+        <div class="icon-title">fa-crosshairs</div>
+    </li>
+    <li><i class="fa fa-cube"></i>
+        <div class="icon-title">fa-cube</div>
+    </li>
+    <li><i class="fa fa-cubes"></i>
+        <div class="icon-title">fa-cubes</div>
+    </li>
+    <li><i class="fa fa-cutlery"></i>
+        <div class="icon-title">fa-cutlery</div>
+    </li>
+    <li><i class="fa fa-dashboard"></i>
+        <div class="icon-title">fa-dashboard</div>
+    </li>
+    <li><i class="fa fa-database"></i>
+        <div class="icon-title">fa-database</div>
+    </li>
+    <li><i class="fa fa-deaf"></i>
+        <div class="icon-title">fa-deaf</div>
+    </li>
+    <li><i class="fa fa-deafness"></i>
+        <div class="icon-title">fa-deafness</div>
+    </li>
+    <li><i class="fa fa-desktop"></i>
+        <div class="icon-title">fa-desktop</div>
+    </li>
+    <li><i class="fa fa-diamond"></i>
+        <div class="icon-title">fa-diamond</div>
+    </li>
+    <li><i class="fa fa-dot-circle-o"></i>
+        <div class="icon-title">fa-dot-circle-o</div>
+    </li>
+    <li><i class="fa fa-download"></i>
+        <div class="icon-title">fa-download</div>
+    </li>
+    <li><i class="fa fa-drivers-license"></i>
+        <div class="icon-title">fa-drivers-license</div>
+    </li>
+    <li><i class="fa fa-drivers-license-o"></i>
+        <div class="icon-title">fa-drivers-license-o</div>
+    </li>
+    <li><i class="fa fa-edit"></i>
+        <div class="icon-title">fa-edit</div>
+    </li>
+    <li><i class="fa fa-ellipsis-h"></i>
+        <div class="icon-title">fa-ellipsis-h</div>
+    </li>
+    <li><i class="fa fa-ellipsis-v"></i>
+        <div class="icon-title">fa-ellipsis-v</div>
+    </li>
+    <li><i class="fa fa-envelope"></i>
+        <div class="icon-title">fa-envelope</div>
+    </li>
+    <li><i class="fa fa-envelope-o"></i>
+        <div class="icon-title">fa-envelope-o</div>
+    </li>
+    <li><i class="fa fa-envelope-open"></i>
+        <div class="icon-title">fa-envelope-open</div>
+    </li>
+    <li><i class="fa fa-envelope-open-o"></i>
+        <div class="icon-title">fa-envelope-open-o</div>
+    </li>
+    <li><i class="fa fa-envelope-square"></i>
+        <div class="icon-title">fa-envelope-square</div>
+    </li>
+    <li><i class="fa fa-eraser"></i>
+        <div class="icon-title">fa-eraser</div>
+    </li>
+    <li><i class="fa fa-exchange"></i>
+        <div class="icon-title">fa-exchange</div>
+    </li>
+    <li><i class="fa fa-exclamation"></i>
+        <div class="icon-title">fa-exclamation</div>
+    </li>
+    <li><i class="fa fa-exclamation-circle"></i>
+        <div class="icon-title">fa-exclamation-circle</div>
+    </li>
+    <li><i class="fa fa-exclamation-triangle"></i>
+        <div class="icon-title">fa-exclamation-triangle</div>
+    </li>
+    <li><i class="fa fa-external-link"></i>
+        <div class="icon-title">fa-external-link</div>
+    </li>
+    <li><i class="fa fa-external-link-square"></i>
+        <div class="icon-title">fa-external-link-square</div>
+    </li>
+    <li><i class="fa fa-eye"></i>
+        <div class="icon-title">fa-eye</div>
+    </li>
+    <li><i class="fa fa-eye-slash"></i>
+        <div class="icon-title">fa-eye-slash</div>
+    </li>
+    <li><i class="fa fa-eyedropper"></i>
+        <div class="icon-title">fa-eyedropper</div>
+    </li>
+    <li><i class="fa fa-fax"></i>
+        <div class="icon-title">fa-fax</div>
+    </li>
+    <li><i class="fa fa-feed"></i>
+        <div class="icon-title">fa-feed</div>
+    </li>
+    <li><i class="fa fa-female"></i>
+        <div class="icon-title">fa-female</div>
+    </li>
+    <li><i class="fa fa-fighter-jet"></i>
+        <div class="icon-title">fa-fighter-jet</div>
+    </li>
+    <li><i class="fa fa-file-archive-o"></i>
+        <div class="icon-title">fa-file-archive-o</div>
+    </li>
+    <li><i class="fa fa-file-audio-o"></i>
+        <div class="icon-title">fa-file-audio-o</div>
+    </li>
+    <li><i class="fa fa-file-code-o"></i>
+        <div class="icon-title">fa-file-code-o</div>
+    </li>
+    <li><i class="fa fa-file-excel-o"></i>
+        <div class="icon-title">fa-file-excel-o</div>
+    </li>
+    <li><i class="fa fa-file-image-o"></i>
+        <div class="icon-title">fa-file-image-o</div>
+    </li>
+    <li><i class="fa fa-file-movie-o"></i>
+        <div class="icon-title">fa-file-movie-o</div>
+    </li>
+    <li><i class="fa fa-file-pdf-o"></i>
+        <div class="icon-title">fa-file-pdf-o</div>
+    </li>
+    <li><i class="fa fa-file-photo-o"></i>
+        <div class="icon-title">fa-file-photo-o</div>
+    </li>
+    <li><i class="fa fa-file-picture-o"></i>
+        <div class="icon-title">fa-file-picture-o</div>
+    </li>
+    <li><i class="fa fa-file-powerpoint-o"></i>
+        <div class="icon-title">fa-file-powerpoint-o</div>
+    </li>
+    <li><i class="fa fa-file-sound-o"></i>
+        <div class="icon-title">fa-file-sound-o</div>
+    </li>
+    <li><i class="fa fa-file-video-o"></i>
+        <div class="icon-title">fa-file-video-o</div>
+    </li>
+    <li><i class="fa fa-file-word-o"></i>
+        <div class="icon-title">fa-file-word-o</div>
+    </li>
+    <li><i class="fa fa-file-zip-o"></i>
+        <div class="icon-title">fa-file-zip-o</div>
+    </li>
+    <li><i class="fa fa-film"></i>
+        <div class="icon-title">fa-film</div>
+    </li>
+    <li><i class="fa fa-filter"></i>
+        <div class="icon-title">fa-filter</div>
+    </li>
+    <li><i class="fa fa-fire"></i>
+        <div class="icon-title">fa-fire</div>
+    </li>
+    <li><i class="fa fa-fire-extinguisher"></i>
+        <div class="icon-title">fa-fire-extinguisher</div>
+    </li>
+    <li><i class="fa fa-flag"></i>
+        <div class="icon-title">fa-flag</div>
+    </li>
+    <li><i class="fa fa-flag-checkered"></i>
+        <div class="icon-title">fa-flag-checkered</div>
+    </li>
+    <li><i class="fa fa-flag-o"></i>
+        <div class="icon-title">fa-flag-o</div>
+    </li>
+    <li><i class="fa fa-flash"></i>
+        <div class="icon-title">fa-flash</div>
+    </li>
+    <li><i class="fa fa-flask"></i>
+        <div class="icon-title">fa-flask</div>
+    </li>
+    <li><i class="fa fa-folder"></i>
+        <div class="icon-title">fa-folder</div>
+    </li>
+    <li><i class="fa fa-folder-o"></i>
+        <div class="icon-title">fa-folder-o</div>
+    </li>
+    <li><i class="fa fa-folder-open"></i>
+        <div class="icon-title">fa-folder-open</div>
+    </li>
+    <li><i class="fa fa-folder-open-o"></i>
+        <div class="icon-title">fa-folder-open-o</div>
+    </li>
+    <li><i class="fa fa-frown-o"></i>
+        <div class="icon-title">fa-frown-o</div>
+    </li>
+    <li><i class="fa fa-futbol-o"></i>
+        <div class="icon-title">fa-futbol-o</div>
+    </li>
+    <li><i class="fa fa-gamepad"></i>
+        <div class="icon-title">fa-gamepad</div>
+    </li>
+    <li><i class="fa fa-gavel"></i>
+        <div class="icon-title">fa-gavel</div>
+    </li>
+    <li><i class="fa fa-gear"></i>
+        <div class="icon-title">fa-gear</div>
+    </li>
+    <li><i class="fa fa-gears"></i>
+        <div class="icon-title">fa-gears</div>
+    </li>
+    <li><i class="fa fa-gift"></i>
+        <div class="icon-title">fa-gift</div>
+    </li>
+    <li><i class="fa fa-glass"></i>
+        <div class="icon-title">fa-glass</div>
+    </li>
+    <li><i class="fa fa-globe"></i>
+        <div class="icon-title">fa-globe</div>
+    </li>
+    <li><i class="fa fa-graduation-cap"></i>
+        <div class="icon-title">fa-graduation-cap</div>
+    </li>
+    <li><i class="fa fa-group"></i>
+        <div class="icon-title">fa-group</div>
+    </li>
+    <li><i class="fa fa-hand-grab-o"></i>
+        <div class="icon-title">fa-hand-grab-o</div>
+    </li>
+    <li><i class="fa fa-hand-lizard-o"></i>
+        <div class="icon-title">fa-hand-lizard-o</div>
+    </li>
+    <li><i class="fa fa-hand-paper-o"></i>
+        <div class="icon-title">fa-hand-paper-o</div>
+    </li>
+    <li><i class="fa fa-hand-peace-o"></i>
+        <div class="icon-title">fa-hand-peace-o</div>
+    </li>
+    <li><i class="fa fa-hand-pointer-o"></i>
+        <div class="icon-title">fa-hand-pointer-o</div>
+    </li>
+    <li><i class="fa fa-hand-rock-o"></i>
+        <div class="icon-title">fa-hand-rock-o</div>
+    </li>
+    <li><i class="fa fa-hand-scissors-o"></i>
+        <div class="icon-title">fa-hand-scissors-o</div>
+    </li>
+    <li><i class="fa fa-hand-spock-o"></i>
+        <div class="icon-title">fa-hand-spock-o</div>
+    </li>
+    <li><i class="fa fa-hand-stop-o"></i>
+        <div class="icon-title">fa-hand-stop-o</div>
+    </li>
+    <li><i class="fa fa-handshake-o"></i>
+        <div class="icon-title">fa-handshake-o</div>
+    </li>
+    <li><i class="fa fa-hard-of-hearing"></i>
+        <div class="icon-title">fa-hard-of-hearing</div>
+    </li>
+    <li><i class="fa fa-hashtag"></i>
+        <div class="icon-title">fa-hashtag</div>
+    </li>
+    <li><i class="fa fa-hdd-o"></i>
+        <div class="icon-title">fa-hdd-o</div>
+    </li>
+    <li><i class="fa fa-headphones"></i>
+        <div class="icon-title">fa-headphones</div>
+    </li>
+    <li><i class="fa fa-heart"></i>
+        <div class="icon-title">fa-heart</div>
+    </li>
+    <li><i class="fa fa-heart-o"></i>
+        <div class="icon-title">fa-heart-o</div>
+    </li>
+    <li><i class="fa fa-heartbeat"></i>
+        <div class="icon-title">fa-heartbeat</div>
+    </li>
+    <li><i class="fa fa-history"></i>
+        <div class="icon-title">fa-history</div>
+    </li>
+    <li><i class="fa fa-home"></i>
+        <div class="icon-title">fa-home</div>
+    </li>
+    <li><i class="fa fa-hotel"></i>
+        <div class="icon-title">fa-hotel</div>
+    </li>
+    <li><i class="fa fa-hourglass"></i>
+        <div class="icon-title">fa-hourglass</div>
+    </li>
+    <li><i class="fa fa-hourglass-1"></i>
+        <div class="icon-title">fa-hourglass-1</div>
+    </li>
+    <li><i class="fa fa-hourglass-2"></i>
+        <div class="icon-title">fa-hourglass-2</div>
+    </li>
+    <li><i class="fa fa-hourglass-3"></i>
+        <div class="icon-title">fa-hourglass-3</div>
+    </li>
+    <li><i class="fa fa-hourglass-end"></i>
+        <div class="icon-title">fa-hourglass-end</div>
+    </li>
+    <li><i class="fa fa-hourglass-half"></i>
+        <div class="icon-title">fa-hourglass-half</div>
+    </li>
+    <li><i class="fa fa-hourglass-o"></i>
+        <div class="icon-title">fa-hourglass-o</div>
+    </li>
+    <li><i class="fa fa-hourglass-start"></i>
+        <div class="icon-title">fa-hourglass-start</div>
+    </li>
+    <li><i class="fa fa-i-cursor"></i>
+        <div class="icon-title">fa-i-cursor</div>
+    </li>
+    <li><i class="fa fa-id-badge"></i>
+        <div class="icon-title">fa-id-badge</div>
+    </li>
+    <li><i class="fa fa-id-card"></i>
+        <div class="icon-title">fa-id-card</div>
+    </li>
+    <li><i class="fa fa-id-card-o"></i>
+        <div class="icon-title">fa-id-card-o</div>
+    </li>
+    <li><i class="fa fa-image"></i>
+        <div class="icon-title">fa-image</div>
+    </li>
+    <li><i class="fa fa-inbox"></i>
+        <div class="icon-title">fa-inbox</div>
+    </li>
+    <li><i class="fa fa-industry"></i>
+        <div class="icon-title">fa-industry</div>
+    </li>
+    <li><i class="fa fa-info"></i>
+        <div class="icon-title">fa-info</div>
+    </li>
+    <li><i class="fa fa-info-circle"></i>
+        <div class="icon-title">fa-info-circle</div>
+    </li>
+    <li><i class="fa fa-institution"></i>
+        <div class="icon-title">fa-institution</div>
+    </li>
+    <li><i class="fa fa-key"></i>
+        <div class="icon-title">fa-key</div>
+    </li>
+    <li><i class="fa fa-keyboard-o"></i>
+        <div class="icon-title">fa-keyboard-o</div>
+    </li>
+    <li><i class="fa fa-language"></i>
+        <div class="icon-title">fa-language</div>
+    </li>
+    <li><i class="fa fa-laptop"></i>
+        <div class="icon-title">fa-laptop</div>
+    </li>
+    <li><i class="fa fa-leaf"></i>
+        <div class="icon-title">fa-leaf</div>
+    </li>
+    <li><i class="fa fa-legal"></i>
+        <div class="icon-title">fa-legal</div>
+    </li>
+    <li><i class="fa fa-lemon-o"></i>
+        <div class="icon-title">fa-lemon-o</div>
+    </li>
+    <li><i class="fa fa-level-down"></i>
+        <div class="icon-title">fa-level-down</div>
+    </li>
+    <li><i class="fa fa-level-up"></i>
+        <div class="icon-title">fa-level-up</div>
+    </li>
+    <li><i class="fa fa-life-bouy"></i>
+        <div class="icon-title">fa-life-bouy</div>
+    </li>
+    <li><i class="fa fa-life-buoy"></i>
+        <div class="icon-title">fa-life-buoy</div>
+    </li>
+    <li><i class="fa fa-life-ring"></i>
+        <div class="icon-title">fa-life-ring</div>
+    </li>
+    <li><i class="fa fa-life-saver"></i>
+        <div class="icon-title">fa-life-saver</div>
+    </li>
+    <li><i class="fa fa-lightbulb-o"></i>
+        <div class="icon-title">fa-lightbulb-o</div>
+    </li>
+    <li><i class="fa fa-line-chart"></i>
+        <div class="icon-title">fa-line-chart</div>
+    </li>
+    <li><i class="fa fa-location-arrow"></i>
+        <div class="icon-title">fa-location-arrow</div>
+    </li>
+    <li><i class="fa fa-lock"></i>
+        <div class="icon-title">fa-lock</div>
+    </li>
+    <li><i class="fa fa-low-vision"></i>
+        <div class="icon-title">fa-low-vision</div>
+    </li>
+    <li><i class="fa fa-magic"></i>
+        <div class="icon-title">fa-magic</div>
+    </li>
+    <li><i class="fa fa-magnet"></i>
+        <div class="icon-title">fa-magnet</div>
+    </li>
+    <li><i class="fa fa-mail-forward"></i>
+        <div class="icon-title">fa-mail-forward</div>
+    </li>
+    <li><i class="fa fa-mail-reply"></i>
+        <div class="icon-title">fa-mail-reply</div>
+    </li>
+    <li><i class="fa fa-mail-reply-all"></i>
+        <div class="icon-title">fa-mail-reply-all</div>
+    </li>
+    <li><i class="fa fa-male"></i>
+        <div class="icon-title">fa-male</div>
+    </li>
+    <li><i class="fa fa-map"></i>
+        <div class="icon-title">fa-map</div>
+    </li>
+    <li><i class="fa fa-map-marker"></i>
+        <div class="icon-title">fa-map-marker</div>
+    </li>
+    <li><i class="fa fa-map-o"></i>
+        <div class="icon-title">fa-map-o</div>
+    </li>
+    <li><i class="fa fa-map-pin"></i>
+        <div class="icon-title">fa-map-pin</div>
+    </li>
+    <li><i class="fa fa-map-signs"></i>
+        <div class="icon-title">fa-map-signs</div>
+    </li>
+    <li><i class="fa fa-meh-o"></i>
+        <div class="icon-title">fa-meh-o</div>
+    </li>
+    <li><i class="fa fa-microchip"></i>
+        <div class="icon-title">fa-microchip</div>
+    </li>
+    <li><i class="fa fa-microphone"></i>
+        <div class="icon-title">fa-microphone</div>
+    </li>
+    <li><i class="fa fa-microphone-slash"></i>
+        <div class="icon-title">fa-microphone-slash</div>
+    </li>
+    <li><i class="fa fa-minus"></i>
+        <div class="icon-title">fa-minus</div>
+    </li>
+    <li><i class="fa fa-minus-circle"></i>
+        <div class="icon-title">fa-minus-circle</div>
+    </li>
+    <li><i class="fa fa-minus-square"></i>
+        <div class="icon-title">fa-minus-square</div>
+    </li>
+    <li><i class="fa fa-minus-square-o"></i>
+        <div class="icon-title">fa-minus-square-o</div>
+    </li>
+    <li><i class="fa fa-mobile"></i>
+        <div class="icon-title">fa-mobile</div>
+    </li>
+    <li><i class="fa fa-mobile-phone"></i>
+        <div class="icon-title">fa-mobile-phone</div>
+    </li>
+    <li><i class="fa fa-money"></i>
+        <div class="icon-title">fa-money</div>
+    </li>
+    <li><i class="fa fa-moon-o"></i>
+        <div class="icon-title">fa-moon-o</div>
+    </li>
+    <li><i class="fa fa-mortar-board"></i>
+        <div class="icon-title">fa-mortar-board</div>
+    </li>
+    <li><i class="fa fa-motorcycle"></i>
+        <div class="icon-title">fa-motorcycle</div>
+    </li>
+    <li><i class="fa fa-mouse-pointer"></i>
+        <div class="icon-title">fa-mouse-pointer</div>
+    </li>
+    <li><i class="fa fa-music"></i>
+        <div class="icon-title">fa-music</div>
+    </li>
+    <li><i class="fa fa-navicon"></i>
+        <div class="icon-title">fa-navicon</div>
+    </li>
+    <li><i class="fa fa-newspaper-o"></i>
+        <div class="icon-title">fa-newspaper-o</div>
+    </li>
+    <li><i class="fa fa-object-group"></i>
+        <div class="icon-title">fa-object-group</div>
+    </li>
+    <li><i class="fa fa-object-ungroup"></i>
+        <div class="icon-title">fa-object-ungroup</div>
+    </li>
+    <li><i class="fa fa-paint-brush"></i>
+        <div class="icon-title">fa-paint-brush</div>
+    </li>
+    <li><i class="fa fa-paper-plane"></i>
+        <div class="icon-title">fa-paper-plane</div>
+    </li>
+    <li><i class="fa fa-paper-plane-o"></i>
+        <div class="icon-title">fa-paper-plane-o</div>
+    </li>
+    <li><i class="fa fa-paw"></i>
+        <div class="icon-title">fa-paw</div>
+    </li>
+    <li><i class="fa fa-pencil"></i>
+        <div class="icon-title">fa-pencil</div>
+    </li>
+    <li><i class="fa fa-pencil-square"></i>
+        <div class="icon-title">fa-pencil-square</div>
+    </li>
+    <li><i class="fa fa-pencil-square-o"></i>
+        <div class="icon-title">fa-pencil-square-o</div>
+    </li>
+    <li><i class="fa fa-percent"></i>
+        <div class="icon-title">fa-percent</div>
+    </li>
+    <li><i class="fa fa-phone"></i>
+        <div class="icon-title">fa-phone</div>
+    </li>
+    <li><i class="fa fa-phone-square"></i>
+        <div class="icon-title">fa-phone-square</div>
+    </li>
+    <li><i class="fa fa-photo"></i>
+        <div class="icon-title">fa-photo</div>
+    </li>
+    <li><i class="fa fa-picture-o"></i>
+        <div class="icon-title">fa-picture-o</div>
+    </li>
+    <li><i class="fa fa-pie-chart"></i>
+        <div class="icon-title">fa-pie-chart</div>
+    </li>
+    <li><i class="fa fa-plane"></i>
+        <div class="icon-title">fa-plane</div>
+    </li>
+    <li><i class="fa fa-plug"></i>
+        <div class="icon-title">fa-plug</div>
+    </li>
+    <li><i class="fa fa-plus"></i>
+        <div class="icon-title">fa-plus</div>
+    </li>
+    <li><i class="fa fa-plus-circle"></i>
+        <div class="icon-title">fa-plus-circle</div>
+    </li>
+    <li><i class="fa fa-plus-square"></i>
+        <div class="icon-title">fa-plus-square</div>
+    </li>
+    <li><i class="fa fa-plus-square-o"></i>
+        <div class="icon-title">fa-plus-square-o</div>
+    </li>
+    <li><i class="fa fa-podcast"></i>
+        <div class="icon-title">fa-podcast</div>
+    </li>
+    <li><i class="fa fa-power-off"></i>
+        <div class="icon-title">fa-power-off</div>
+    </li>
+    <li><i class="fa fa-print"></i>
+        <div class="icon-title">fa-print</div>
+    </li>
+    <li><i class="fa fa-puzzle-piece"></i>
+        <div class="icon-title">fa-puzzle-piece</div>
+    </li>
+    <li><i class="fa fa-qrcode"></i>
+        <div class="icon-title">fa-qrcode</div>
+    </li>
+    <li><i class="fa fa-question"></i>
+        <div class="icon-title">fa-question</div>
+    </li>
+    <li><i class="fa fa-question-circle"></i>
+        <div class="icon-title">fa-question-circle</div>
+    </li>
+    <li><i class="fa fa-question-circle-o"></i>
+        <div class="icon-title">fa-question-circle-o</div>
+    </li>
+    <li><i class="fa fa-quote-left"></i>
+        <div class="icon-title">fa-quote-left</div>
+    </li>
+    <li><i class="fa fa-quote-right"></i>
+        <div class="icon-title">fa-quote-right</div>
+    </li>
+    <li><i class="fa fa-random"></i>
+        <div class="icon-title">fa-random</div>
+    </li>
+    <li><i class="fa fa-recycle"></i>
+        <div class="icon-title">fa-recycle</div>
+    </li>
+    <li><i class="fa fa-refresh"></i>
+        <div class="icon-title">fa-refresh</div>
+    </li>
+    <li><i class="fa fa-registered"></i>
+        <div class="icon-title">fa-registered</div>
+    </li>
+    <li><i class="fa fa-remove"></i>
+        <div class="icon-title">fa-remove</div>
+    </li>
+    <li><i class="fa fa-reorder"></i>
+        <div class="icon-title">fa-reorder</div>
+    </li>
+    <li><i class="fa fa-reply"></i>
+        <div class="icon-title">fa-reply</div>
+    </li>
+    <li><i class="fa fa-reply-all"></i>
+        <div class="icon-title">fa-reply-all</div>
+    </li>
+    <li><i class="fa fa-retweet"></i>
+        <div class="icon-title">fa-retweet</div>
+    </li>
+    <li><i class="fa fa-road"></i>
+        <div class="icon-title">fa-road</div>
+    </li>
+    <li><i class="fa fa-rocket"></i>
+        <div class="icon-title">fa-rocket</div>
+    </li>
+    <li><i class="fa fa-rss"></i>
+        <div class="icon-title">fa-rss</div>
+    </li>
+    <li><i class="fa fa-rss-square"></i>
+        <div class="icon-title">fa-rss-square</div>
+    </li>
+    <li><i class="fa fa-s15"></i>
+        <div class="icon-title">fa-s15</div>
+    </li>
+    <li><i class="fa fa-search"></i>
+        <div class="icon-title">fa-search</div>
+    </li>
+    <li><i class="fa fa-search-minus"></i>
+        <div class="icon-title">fa-search-minus</div>
+    </li>
+    <li><i class="fa fa-search-plus"></i>
+        <div class="icon-title">fa-search-plus</div>
+    </li>
+    <li><i class="fa fa-send"></i>
+        <div class="icon-title">fa-send</div>
+    </li>
+    <li><i class="fa fa-send-o"></i>
+        <div class="icon-title">fa-send-o</div>
+    </li>
+    <li><i class="fa fa-server"></i>
+        <div class="icon-title">fa-server</div>
+    </li>
+    <li><i class="fa fa-share"></i>
+        <div class="icon-title">fa-share</div>
+    </li>
+    <li><i class="fa fa-share-alt"></i>
+        <div class="icon-title">fa-share-alt</div>
+    </li>
+    <li><i class="fa fa-share-alt-square"></i>
+        <div class="icon-title">fa-share-alt-square</div>
+    </li>
+    <li><i class="fa fa-share-square"></i>
+        <div class="icon-title">fa-share-square</div>
+    </li>
+    <li><i class="fa fa-share-square-o"></i>
+        <div class="icon-title">fa-share-square-o</div>
+    </li>
+    <li><i class="fa fa-shield"></i>
+        <div class="icon-title">fa-shield</div>
+    </li>
+    <li><i class="fa fa-ship"></i>
+        <div class="icon-title">fa-ship</div>
+    </li>
+    <li><i class="fa fa-shopping-bag"></i>
+        <div class="icon-title">fa-shopping-bag</div>
+    </li>
+    <li><i class="fa fa-shopping-basket"></i>
+        <div class="icon-title">fa-shopping-basket</div>
+    </li>
+    <li><i class="fa fa-shopping-cart"></i>
+        <div class="icon-title">fa-shopping-cart</div>
+    </li>
+    <li><i class="fa fa-shower"></i>
+        <div class="icon-title">fa-shower</div>
+    </li>
+    <li><i class="fa fa-sign-in"></i>
+        <div class="icon-title">fa-sign-in</div>
+    </li>
+    <li><i class="fa fa-sign-language"></i>
+        <div class="icon-title">fa-sign-language</div>
+    </li>
+    <li><i class="fa fa-sign-out"></i>
+        <div class="icon-title">fa-sign-out</div>
+    </li>
+    <li><i class="fa fa-signal"></i>
+        <div class="icon-title">fa-signal</div>
+    </li>
+    <li><i class="fa fa-signing"></i>
+        <div class="icon-title">fa-signing</div>
+    </li>
+    <li><i class="fa fa-sitemap"></i>
+        <div class="icon-title">fa-sitemap</div>
+    </li>
+    <li><i class="fa fa-sliders"></i>
+        <div class="icon-title">fa-sliders</div>
+    </li>
+    <li><i class="fa fa-smile-o"></i>
+        <div class="icon-title">fa-smile-o</div>
+    </li>
+    <li><i class="fa fa-snowflake-o"></i>
+        <div class="icon-title">fa-snowflake-o</div>
+    </li>
+    <li><i class="fa fa-soccer-ball-o"></i>
+        <div class="icon-title">fa-soccer-ball-o</div>
+    </li>
+    <li><i class="fa fa-sort"></i>
+        <div class="icon-title">fa-sort</div>
+    </li>
+    <li><i class="fa fa-sort-alpha-asc"></i>
+        <div class="icon-title">fa-sort-alpha-asc</div>
+    </li>
+    <li><i class="fa fa-sort-alpha-desc"></i>
+        <div class="icon-title">fa-sort-alpha-desc</div>
+    </li>
+    <li><i class="fa fa-sort-amount-asc"></i>
+        <div class="icon-title">fa-sort-amount-asc</div>
+    </li>
+    <li><i class="fa fa-sort-amount-desc"></i>
+        <div class="icon-title">fa-sort-amount-desc</div>
+    </li>
+    <li><i class="fa fa-sort-asc"></i>
+        <div class="icon-title">fa-sort-asc</div>
+    </li>
+    <li><i class="fa fa-sort-desc"></i>
+        <div class="icon-title">fa-sort-desc</div>
+    </li>
+    <li><i class="fa fa-sort-down"></i>
+        <div class="icon-title">fa-sort-down</div>
+    </li>
+    <li><i class="fa fa-sort-numeric-asc"></i>
+        <div class="icon-title">fa-sort-numeric-asc</div>
+    </li>
+    <li><i class="fa fa-sort-numeric-desc"></i>
+        <div class="icon-title">fa-sort-numeric-desc</div>
+    </li>
+    <li><i class="fa fa-sort-up"></i>
+        <div class="icon-title">fa-sort-up</div>
+    </li>
+    <li><i class="fa fa-space-shuttle"></i>
+        <div class="icon-title">fa-space-shuttle</div>
+    </li>
+    <li><i class="fa fa-spinner"></i>
+        <div class="icon-title">fa-spinner</div>
+    </li>
+    <li><i class="fa fa-spoon"></i>
+        <div class="icon-title">fa-spoon</div>
+    </li>
+    <li><i class="fa fa-square"></i>
+        <div class="icon-title">fa-square</div>
+    </li>
+    <li><i class="fa fa-square-o"></i>
+        <div class="icon-title">fa-square-o</div>
+    </li>
+    <li><i class="fa fa-star"></i>
+        <div class="icon-title">fa-star</div>
+    </li>
+    <li><i class="fa fa-star-half"></i>
+        <div class="icon-title">fa-star-half</div>
+    </li>
+    <li><i class="fa fa-star-half-empty"></i>
+        <div class="icon-title">fa-star-half-empty</div>
+    </li>
+    <li><i class="fa fa-star-half-full"></i>
+        <div class="icon-title">fa-star-half-full</div>
+    </li>
+    <li><i class="fa fa-star-half-o"></i>
+        <div class="icon-title">fa-star-half-o</div>
+    </li>
+    <li><i class="fa fa-star-o"></i>
+        <div class="icon-title">fa-star-o</div>
+    </li>
+    <li><i class="fa fa-sticky-note"></i>
+        <div class="icon-title">fa-sticky-note</div>
+    </li>
+    <li><i class="fa fa-sticky-note-o"></i>
+        <div class="icon-title">fa-sticky-note-o</div>
+    </li>
+    <li><i class="fa fa-street-view"></i>
+        <div class="icon-title">fa-street-view</div>
+    </li>
+    <li><i class="fa fa-suitcase"></i>
+        <div class="icon-title">fa-suitcase</div>
+    </li>
+    <li><i class="fa fa-sun-o"></i>
+        <div class="icon-title">fa-sun-o</div>
+    </li>
+    <li><i class="fa fa-support"></i>
+        <div class="icon-title">fa-support</div>
+    </li>
+    <li><i class="fa fa-tablet"></i>
+        <div class="icon-title">fa-tablet</div>
+    </li>
+    <li><i class="fa fa-tachometer"></i>
+        <div class="icon-title">fa-tachometer</div>
+    </li>
+    <li><i class="fa fa-tag"></i>
+        <div class="icon-title">fa-tag</div>
+    </li>
+    <li><i class="fa fa-tags"></i>
+        <div class="icon-title">fa-tags</div>
+    </li>
+    <li><i class="fa fa-tasks"></i>
+        <div class="icon-title">fa-tasks</div>
+    </li>
+    <li><i class="fa fa-taxi"></i>
+        <div class="icon-title">fa-taxi</div>
+    </li>
+    <li><i class="fa fa-television"></i>
+        <div class="icon-title">fa-television</div>
+    </li>
+    <li><i class="fa fa-terminal"></i>
+        <div class="icon-title">fa-terminal</div>
+    </li>
+    <li><i class="fa fa-thermometer"></i>
+        <div class="icon-title">fa-thermometer</div>
+    </li>
+    <li><i class="fa fa-thermometer-0"></i>
+        <div class="icon-title">fa-thermometer-0</div>
+    </li>
+    <li><i class="fa fa-thermometer-1"></i>
+        <div class="icon-title">fa-thermometer-1</div>
+    </li>
+    <li><i class="fa fa-thermometer-2"></i>
+        <div class="icon-title">fa-thermometer-2</div>
+    </li>
+    <li><i class="fa fa-thermometer-3"></i>
+        <div class="icon-title">fa-thermometer-3</div>
+    </li>
+    <li><i class="fa fa-thermometer-4"></i>
+        <div class="icon-title">fa-thermometer-4</div>
+    </li>
+    <li><i class="fa fa-thermometer-empty"></i>
+        <div class="icon-title">fa-thermometer-empty</div>
+    </li>
+    <li><i class="fa fa-thermometer-full"></i>
+        <div class="icon-title">fa-thermometer-full</div>
+    </li>
+    <li><i class="fa fa-thermometer-half"></i>
+        <div class="icon-title">fa-thermometer-half</div>
+    </li>
+    <li><i class="fa fa-thermometer-quarter"></i>
+        <div class="icon-title">fa-thermometer-quarter</div>
+    </li>
+    <li><i class="fa fa-thermometer-three-quarters"></i>
+        <div class="icon-title">fa-thermometer-three-quarters</div>
+    </li>
+    <li><i class="fa fa-thumb-tack"></i>
+        <div class="icon-title">fa-thumb-tack</div>
+    </li>
+    <li><i class="fa fa-thumbs-down"></i>
+        <div class="icon-title">fa-thumbs-down</div>
+    </li>
+    <li><i class="fa fa-thumbs-o-down"></i>
+        <div class="icon-title">fa-thumbs-o-down</div>
+    </li>
+    <li><i class="fa fa-thumbs-o-up"></i>
+        <div class="icon-title">fa-thumbs-o-up</div>
+    </li>
+    <li><i class="fa fa-thumbs-up"></i>
+        <div class="icon-title">fa-thumbs-up</div>
+    </li>
+    <li><i class="fa fa-ticket"></i>
+        <div class="icon-title">fa-ticket</div>
+    </li>
+    <li><i class="fa fa-times"></i>
+        <div class="icon-title">fa-times</div>
+    </li>
+    <li><i class="fa fa-times-circle"></i>
+        <div class="icon-title">fa-times-circle</div>
+    </li>
+    <li><i class="fa fa-times-circle-o"></i>
+        <div class="icon-title">fa-times-circle-o</div>
+    </li>
+    <li><i class="fa fa-times-rectangle"></i>
+        <div class="icon-title">fa-times-rectangle</div>
+    </li>
+    <li><i class="fa fa-times-rectangle-o"></i>
+        <div class="icon-title">fa-times-rectangle-o</div>
+    </li>
+    <li><i class="fa fa-tint"></i>
+        <div class="icon-title">fa-tint</div>
+    </li>
+    <li><i class="fa fa-toggle-down"></i>
+        <div class="icon-title">fa-toggle-down</div>
+    </li>
+    <li><i class="fa fa-toggle-left"></i>
+        <div class="icon-title">fa-toggle-left</div>
+    </li>
+    <li><i class="fa fa-toggle-off"></i>
+        <div class="icon-title">fa-toggle-off</div>
+    </li>
+    <li><i class="fa fa-toggle-on"></i>
+        <div class="icon-title">fa-toggle-on</div>
+    </li>
+    <li><i class="fa fa-toggle-right"></i>
+        <div class="icon-title">fa-toggle-right</div>
+    </li>
+    <li><i class="fa fa-toggle-up"></i>
+        <div class="icon-title">fa-toggle-up</div>
+    </li>
+    <li><i class="fa fa-trademark"></i>
+        <div class="icon-title">fa-trademark</div>
+    </li>
+    <li><i class="fa fa-trash"></i>
+        <div class="icon-title">fa-trash</div>
+    </li>
+    <li><i class="fa fa-trash-o"></i>
+        <div class="icon-title">fa-trash-o</div>
+    </li>
+    <li><i class="fa fa-tree"></i>
+        <div class="icon-title">fa-tree</div>
+    </li>
+    <li><i class="fa fa-trophy"></i>
+        <div class="icon-title">fa-trophy</div>
+    </li>
+    <li><i class="fa fa-truck"></i>
+        <div class="icon-title">fa-truck</div>
+    </li>
+    <li><i class="fa fa-tty"></i>
+        <div class="icon-title">fa-tty</div>
+    </li>
+    <li><i class="fa fa-tv"></i>
+        <div class="icon-title">fa-tv</div>
+    </li>
+    <li><i class="fa fa-umbrella"></i>
+        <div class="icon-title">fa-umbrella</div>
+    </li>
+    <li><i class="fa fa-universal-access"></i>
+        <div class="icon-title">fa-universal-access</div>
+    </li>
+    <li><i class="fa fa-university"></i>
+        <div class="icon-title">fa-university</div>
+    </li>
+    <li><i class="fa fa-unlock"></i>
+        <div class="icon-title">fa-unlock</div>
+    </li>
+    <li><i class="fa fa-unlock-alt"></i>
+        <div class="icon-title">fa-unlock-alt</div>
+    </li>
+    <li><i class="fa fa-unsorted"></i>
+        <div class="icon-title">fa-unsorted</div>
+    </li>
+    <li><i class="fa fa-upload"></i>
+        <div class="icon-title">fa-upload</div>
+    </li>
+    <li><i class="fa fa-user"></i>
+        <div class="icon-title">fa-user</div>
+    </li>
+    <li><i class="fa fa-user-circle"></i>
+        <div class="icon-title">fa-user-circle</div>
+    </li>
+    <li><i class="fa fa-user-circle-o"></i>
+        <div class="icon-title">fa-user-circle-o</div>
+    </li>
+    <li><i class="fa fa-user-o"></i>
+        <div class="icon-title">fa-user-o</div>
+    </li>
+    <li><i class="fa fa-user-plus"></i>
+        <div class="icon-title">fa-user-plus</div>
+    </li>
+    <li><i class="fa fa-user-secret"></i>
+        <div class="icon-title">fa-user-secret</div>
+    </li>
+    <li><i class="fa fa-user-times"></i>
+        <div class="icon-title">fa-user-times</div>
+    </li>
+    <li><i class="fa fa-users"></i>
+        <div class="icon-title">fa-users</div>
+    </li>
+    <li><i class="fa fa-vcard"></i>
+        <div class="icon-title">fa-vcard</div>
+    </li>
+    <li><i class="fa fa-vcard-o"></i>
+        <div class="icon-title">fa-vcard-o</div>
+    </li>
+    <li><i class="fa fa-video-camera"></i>
+        <div class="icon-title">fa-video-camera</div>
+    </li>
+    <li><i class="fa fa-volume-control-phone"></i>
+        <div class="icon-title">fa-volume-control-phone</div>
+    </li>
+    <li><i class="fa fa-volume-down"></i>
+        <div class="icon-title">fa-volume-down</div>
+    </li>
+    <li><i class="fa fa-volume-off"></i>
+        <div class="icon-title">fa-volume-off</div>
+    </li>
+    <li><i class="fa fa-volume-up"></i>
+        <div class="icon-title">fa-volume-up</div>
+    </li>
+    <li><i class="fa fa-warning"></i>
+        <div class="icon-title">fa-warning</div>
+    </li>
+    <li><i class="fa fa-wheelchair"></i>
+        <div class="icon-title">fa-wheelchair</div>
+    </li>
+    <li><i class="fa fa-wheelchair-alt"></i>
+        <div class="icon-title">fa-wheelchair-alt</div>
+    </li>
+    <li><i class="fa fa-wifi"></i>
+        <div class="icon-title">fa-wifi</div>
+    </li>
+    <li><i class="fa fa-window-close"></i>
+        <div class="icon-title">fa-window-close</div>
+    </li>
+    <li><i class="fa fa-window-close-o"></i>
+        <div class="icon-title">fa-window-close-o</div>
+    </li>
+    <li><i class="fa fa-window-maximize"></i>
+        <div class="icon-title">fa-window-maximize</div>
+    </li>
+    <li><i class="fa fa-window-minimize"></i>
+        <div class="icon-title">fa-window-minimize</div>
+    </li>
+    <li><i class="fa fa-window-restore"></i>
+        <div class="icon-title">fa-window-restore</div>
+    </li>
+    <li><i class="fa fa-wrench"></i>
+        <div class="icon-title">fa-wrench</div>
+    </li>
+    <li><i class="fa fa-american-sign-language-interpreting"></i>
+        <div class="icon-title">fa-american-sign-language-interpreting</div>
+    </li>
+    <li><i class="fa fa-asl-interpreting"></i>
+        <div class="icon-title">fa-asl-interpreting</div>
+    </li>
+    <li><i class="fa fa-assistive-listening-systems"></i>
+        <div class="icon-title">fa-assistive-listening-systems</div>
+    </li>
+    <li><i class="fa fa-audio-description"></i>
+        <div class="icon-title">fa-audio-description</div>
+    </li>
+    <li><i class="fa fa-blind"></i>
+        <div class="icon-title">fa-blind</div>
+    </li>
+    <li><i class="fa fa-braille"></i>
+        <div class="icon-title">fa-braille</div>
+    </li>
+    <li><i class="fa fa-cc"></i>
+        <div class="icon-title">fa-cc</div>
+    </li>
+    <li><i class="fa fa-deaf"></i>
+        <div class="icon-title">fa-deaf</div>
+    </li>
+    <li><i class="fa fa-deafness"></i>
+        <div class="icon-title">fa-deafness</div>
+    </li>
+    <li><i class="fa fa-hard-of-hearing"></i>
+        <div class="icon-title">fa-hard-of-hearing</div>
+    </li>
+    <li><i class="fa fa-low-vision"></i>
+        <div class="icon-title">fa-low-vision</div>
+    </li>
+    <li><i class="fa fa-question-circle-o"></i>
+        <div class="icon-title">fa-question-circle-o</div>
+    </li>
+    <li><i class="fa fa-sign-language"></i>
+        <div class="icon-title">fa-sign-language</div>
+    </li>
+    <li><i class="fa fa-signing"></i>
+        <div class="icon-title">fa-signing</div>
+    </li>
+    <li><i class="fa fa-tty"></i>
+        <div class="icon-title">fa-tty</div>
+    </li>
+    <li><i class="fa fa-universal-access"></i>
+        <div class="icon-title">fa-universal-access</div>
+    </li>
+    <li><i class="fa fa-volume-control-phone"></i>
+        <div class="icon-title">fa-volume-control-phone</div>
+    </li>
+    <li><i class="fa fa-wheelchair"></i>
+        <div class="icon-title">fa-wheelchair</div>
+    </li>
+    <li><i class="fa fa-wheelchair-alt"></i>
+        <div class="icon-title">fa-wheelchair-alt</div>
+    </li>
+    <li><i class="fa fa-hand-grab-o"></i>
+        <div class="icon-title">fa-hand-grab-o</div>
+    </li>
+    <li><i class="fa fa-hand-lizard-o"></i>
+        <div class="icon-title">fa-hand-lizard-o</div>
+    </li>
+    <li><i class="fa fa-hand-o-down"></i>
+        <div class="icon-title">fa-hand-o-down</div>
+    </li>
+    <li><i class="fa fa-hand-o-left"></i>
+        <div class="icon-title">fa-hand-o-left</div>
+    </li>
+    <li><i class="fa fa-hand-o-right"></i>
+        <div class="icon-title">fa-hand-o-right</div>
+    </li>
+    <li><i class="fa fa-hand-o-up"></i>
+        <div class="icon-title">fa-hand-o-up</div>
+    </li>
+    <li><i class="fa fa-hand-paper-o"></i>
+        <div class="icon-title">fa-hand-paper-o</div>
+    </li>
+    <li><i class="fa fa-hand-peace-o"></i>
+        <div class="icon-title">fa-hand-peace-o</div>
+    </li>
+    <li><i class="fa fa-hand-pointer-o"></i>
+        <div class="icon-title">fa-hand-pointer-o</div>
+    </li>
+    <li><i class="fa fa-hand-rock-o"></i>
+        <div class="icon-title">fa-hand-rock-o</div>
+    </li>
+    <li><i class="fa fa-hand-scissors-o"></i>
+        <div class="icon-title">fa-hand-scissors-o</div>
+    </li>
+    <li><i class="fa fa-hand-spock-o"></i>
+        <div class="icon-title">fa-hand-spock-o</div>
+    </li>
+    <li><i class="fa fa-hand-stop-o"></i>
+        <div class="icon-title">fa-hand-stop-o</div>
+    </li>
+    <li><i class="fa fa-thumbs-down"></i>
+        <div class="icon-title">fa-thumbs-down</div>
+    </li>
+    <li><i class="fa fa-thumbs-o-down"></i>
+        <div class="icon-title">fa-thumbs-o-down</div>
+    </li>
+    <li><i class="fa fa-thumbs-o-up"></i>
+        <div class="icon-title">fa-thumbs-o-up</div>
+    </li>
+    <li><i class="fa fa-thumbs-up"></i>
+        <div class="icon-title">fa-thumbs-up</div>
+    </li>
+    <li><i class="fa fa-ambulance"></i>
+        <div class="icon-title">fa-ambulance</div>
+    </li>
+    <li><i class="fa fa-automobile"></i>
+        <div class="icon-title">fa-automobile</div>
+    </li>
+    <li><i class="fa fa-bicycle"></i>
+        <div class="icon-title">fa-bicycle</div>
+    </li>
+    <li><i class="fa fa-bus"></i>
+        <div class="icon-title">fa-bus</div>
+    </li>
+    <li><i class="fa fa-cab"></i>
+        <div class="icon-title">fa-cab</div>
+    </li>
+    <li><i class="fa fa-car"></i>
+        <div class="icon-title">fa-car</div>
+    </li>
+    <li><i class="fa fa-fighter-jet"></i>
+        <div class="icon-title">fa-fighter-jet</div>
+    </li>
+    <li><i class="fa fa-motorcycle"></i>
+        <div class="icon-title">fa-motorcycle</div>
+    </li>
+    <li><i class="fa fa-plane"></i>
+        <div class="icon-title">fa-plane</div>
+    </li>
+    <li><i class="fa fa-rocket"></i>
+        <div class="icon-title">fa-rocket</div>
+    </li>
+    <li><i class="fa fa-ship"></i>
+        <div class="icon-title">fa-ship</div>
+    </li>
+    <li><i class="fa fa-space-shuttle"></i>
+        <div class="icon-title">fa-space-shuttle</div>
+    </li>
+    <li><i class="fa fa-subway"></i>
+        <div class="icon-title">fa-subway</div>
+    </li>
+    <li><i class="fa fa-taxi"></i>
+        <div class="icon-title">fa-taxi</div>
+    </li>
+    <li><i class="fa fa-train"></i>
+        <div class="icon-title">fa-train</div>
+    </li>
+    <li><i class="fa fa-truck"></i>
+        <div class="icon-title">fa-truck</div>
+    </li>
+    <li><i class="fa fa-wheelchair"></i>
+        <div class="icon-title">fa-wheelchair</div>
+    </li>
+    <li><i class="fa fa-wheelchair-alt"></i>
+        <div class="icon-title">fa-wheelchair-alt</div>
+    </li>
+    <li><i class="fa fa-genderless"></i>
+        <div class="icon-title">fa-genderless</div>
+    </li>
+    <li><i class="fa fa-intersex"></i>
+        <div class="icon-title">fa-intersex</div>
+    </li>
+    <li><i class="fa fa-mars"></i>
+        <div class="icon-title">fa-mars</div>
+    </li>
+    <li><i class="fa fa-mars-double"></i>
+        <div class="icon-title">fa-mars-double</div>
+    </li>
+    <li><i class="fa fa-mars-stroke"></i>
+        <div class="icon-title">fa-mars-stroke</div>
+    </li>
+    <li><i class="fa fa-mars-stroke-h"></i>
+        <div class="icon-title">fa-mars-stroke-h</div>
+    </li>
+    <li><i class="fa fa-mars-stroke-v"></i>
+        <div class="icon-title">fa-mars-stroke-v</div>
+    </li>
+    <li><i class="fa fa-mercury"></i>
+        <div class="icon-title">fa-mercury</div>
+    </li>
+    <li><i class="fa fa-neuter"></i>
+        <div class="icon-title">fa-neuter</div>
+    </li>
+    <li><i class="fa fa-transgender"></i>
+        <div class="icon-title">fa-transgender</div>
+    </li>
+    <li><i class="fa fa-transgender-alt"></i>
+        <div class="icon-title">fa-transgender-alt</div>
+    </li>
+    <li><i class="fa fa-venus"></i>
+        <div class="icon-title">fa-venus</div>
+    </li>
+    <li><i class="fa fa-venus-double"></i>
+        <div class="icon-title">fa-venus-double</div>
+    </li>
+    <li><i class="fa fa-venus-mars"></i>
+        <div class="icon-title">fa-venus-mars</div>
+    </li>
+    <li><i class="fa fa-file"></i>
+        <div class="icon-title">fa-file</div>
+    </li>
+    <li><i class="fa fa-file-archive-o"></i>
+        <div class="icon-title">fa-file-archive-o</div>
+    </li>
+    <li><i class="fa fa-file-audio-o"></i>
+        <div class="icon-title">fa-file-audio-o</div>
+    </li>
+    <li><i class="fa fa-file-code-o"></i>
+        <div class="icon-title">fa-file-code-o</div>
+    </li>
+    <li><i class="fa fa-file-excel-o"></i>
+        <div class="icon-title">fa-file-excel-o</div>
+    </li>
+    <li><i class="fa fa-file-image-o"></i>
+        <div class="icon-title">fa-file-image-o</div>
+    </li>
+    <li><i class="fa fa-file-movie-o"></i>
+        <div class="icon-title">fa-file-movie-o</div>
+    </li>
+    <li><i class="fa fa-file-o"></i>
+        <div class="icon-title">fa-file-o</div>
+    </li>
+    <li><i class="fa fa-file-pdf-o"></i>
+        <div class="icon-title">fa-file-pdf-o</div>
+    </li>
+    <li><i class="fa fa-file-photo-o"></i>
+        <div class="icon-title">fa-file-photo-o</div>
+    </li>
+    <li><i class="fa fa-file-picture-o"></i>
+        <div class="icon-title">fa-file-picture-o</div>
+    </li>
+    <li><i class="fa fa-file-powerpoint-o"></i>
+        <div class="icon-title">fa-file-powerpoint-o</div>
+    </li>
+    <li><i class="fa fa-file-sound-o"></i>
+        <div class="icon-title">fa-file-sound-o</div>
+    </li>
+    <li><i class="fa fa-file-text"></i>
+        <div class="icon-title">fa-file-text</div>
+    </li>
+    <li><i class="fa fa-file-text-o"></i>
+        <div class="icon-title">fa-file-text-o</div>
+    </li>
+    <li><i class="fa fa-file-video-o"></i>
+        <div class="icon-title">fa-file-video-o</div>
+    </li>
+    <li><i class="fa fa-file-word-o"></i>
+        <div class="icon-title">fa-file-word-o</div>
+    </li>
+    <li><i class="fa fa-file-zip-o"></i>
+        <div class="icon-title">fa-file-zip-o</div>
+    </li>
+    <li><i class="fa fa-circle-o-notch"></i>
+        <div class="icon-title">fa-circle-o-notch</div>
+    </li>
+    <li><i class="fa fa-cog"></i>
+        <div class="icon-title">fa-cog</div>
+    </li>
+    <li><i class="fa fa-gear"></i>
+        <div class="icon-title">fa-gear</div>
+    </li>
+    <li><i class="fa fa-refresh"></i>
+        <div class="icon-title">fa-refresh</div>
+    </li>
+    <li><i class="fa fa-spinner"></i>
+        <div class="icon-title">fa-spinner</div>
+    </li>
+    <li><i class="fa fa-check-square"></i>
+        <div class="icon-title">fa-check-square</div>
+    </li>
+    <li><i class="fa fa-check-square-o"></i>
+        <div class="icon-title">fa-check-square-o</div>
+    </li>
+    <li><i class="fa fa-circle"></i>
+        <div class="icon-title">fa-circle</div>
+    </li>
+    <li><i class="fa fa-circle-o"></i>
+        <div class="icon-title">fa-circle-o</div>
+    </li>
+    <li><i class="fa fa-dot-circle-o"></i>
+        <div class="icon-title">fa-dot-circle-o</div>
+    </li>
+    <li><i class="fa fa-minus-square"></i>
+        <div class="icon-title">fa-minus-square</div>
+    </li>
+    <li><i class="fa fa-minus-square-o"></i>
+        <div class="icon-title">fa-minus-square-o</div>
+    </li>
+    <li><i class="fa fa-plus-square"></i>
+        <div class="icon-title">fa-plus-square</div>
+    </li>
+    <li><i class="fa fa-plus-square-o"></i>
+        <div class="icon-title">fa-plus-square-o</div>
+    </li>
+    <li><i class="fa fa-square"></i>
+        <div class="icon-title">fa-square</div>
+    </li>
+    <li><i class="fa fa-square-o"></i>
+        <div class="icon-title">fa-square-o</div>
+    </li>
+    <li><i class="fa fa-cc-amex"></i>
+        <div class="icon-title">fa-cc-amex</div>
+    </li>
+    <li><i class="fa fa-cc-diners-club"></i>
+        <div class="icon-title">fa-cc-diners-club</div>
+    </li>
+    <li><i class="fa fa-cc-discover"></i>
+        <div class="icon-title">fa-cc-discover</div>
+    </li>
+    <li><i class="fa fa-cc-jcb"></i>
+        <div class="icon-title">fa-cc-jcb</div>
+    </li>
+    <li><i class="fa fa-cc-mastercard"></i>
+        <div class="icon-title">fa-cc-mastercard</div>
+    </li>
+    <li><i class="fa fa-cc-paypal"></i>
+        <div class="icon-title">fa-cc-paypal</div>
+    </li>
+    <li><i class="fa fa-cc-stripe"></i>
+        <div class="icon-title">fa-cc-stripe</div>
+    </li>
+    <li><i class="fa fa-cc-visa"></i>
+        <div class="icon-title">fa-cc-visa</div>
+    </li>
+    <li><i class="fa fa-credit-card"></i>
+        <div class="icon-title">fa-credit-card</div>
+    </li>
+    <li><i class="fa fa-credit-card-alt"></i>
+        <div class="icon-title">fa-credit-card-alt</div>
+    </li>
+    <li><i class="fa fa-google-wallet"></i>
+        <div class="icon-title">fa-google-wallet</div>
+    </li>
+    <li><i class="fa fa-paypal"></i>
+        <div class="icon-title">fa-paypal</div>
+    </li>
+    <li><i class="fa fa-area-chart"></i>
+        <div class="icon-title">fa-area-chart</div>
+    </li>
+    <li><i class="fa fa-bar-chart"></i>
+        <div class="icon-title">fa-bar-chart</div>
+    </li>
+    <li><i class="fa fa-bar-chart-o"></i>
+        <div class="icon-title">fa-bar-chart-o</div>
+    </li>
+    <li><i class="fa fa-line-chart"></i>
+        <div class="icon-title">fa-line-chart</div>
+    </li>
+    <li><i class="fa fa-pie-chart"></i>
+        <div class="icon-title">fa-pie-chart</div>
+    </li>
+    <li><i class="fa fa-bitcoin"></i>
+        <div class="icon-title">fa-bitcoin</div>
+    </li>
+    <li><i class="fa fa-btc"></i>
+        <div class="icon-title">fa-btc</div>
+    </li>
+    <li><i class="fa fa-cny"></i>
+        <div class="icon-title">fa-cny</div>
+    </li>
+    <li><i class="fa fa-dollar"></i>
+        <div class="icon-title">fa-dollar</div>
+    </li>
+    <li><i class="fa fa-eur"></i>
+        <div class="icon-title">fa-eur</div>
+    </li>
+    <li><i class="fa fa-euro"></i>
+        <div class="icon-title">fa-euro</div>
+    </li>
+    <li><i class="fa fa-gbp"></i>
+        <div class="icon-title">fa-gbp</div>
+    </li>
+    <li><i class="fa fa-gg"></i>
+        <div class="icon-title">fa-gg</div>
+    </li>
+    <li><i class="fa fa-gg-circle"></i>
+        <div class="icon-title">fa-gg-circle</div>
+    </li>
+    <li><i class="fa fa-ils"></i>
+        <div class="icon-title">fa-ils</div>
+    </li>
+    <li><i class="fa fa-inr"></i>
+        <div class="icon-title">fa-inr</div>
+    </li>
+    <li><i class="fa fa-jpy"></i>
+        <div class="icon-title">fa-jpy</div>
+    </li>
+    <li><i class="fa fa-krw"></i>
+        <div class="icon-title">fa-krw</div>
+    </li>
+    <li><i class="fa fa-money"></i>
+        <div class="icon-title">fa-money</div>
+    </li>
+    <li><i class="fa fa-rmb"></i>
+        <div class="icon-title">fa-rmb</div>
+    </li>
+    <li><i class="fa fa-rouble"></i>
+        <div class="icon-title">fa-rouble</div>
+    </li>
+    <li><i class="fa fa-rub"></i>
+        <div class="icon-title">fa-rub</div>
+    </li>
+    <li><i class="fa fa-ruble"></i>
+        <div class="icon-title">fa-ruble</div>
+    </li>
+    <li><i class="fa fa-rupee"></i>
+        <div class="icon-title">fa-rupee</div>
+    </li>
+    <li><i class="fa fa-shekel"></i>
+        <div class="icon-title">fa-shekel</div>
+    </li>
+    <li><i class="fa fa-sheqel"></i>
+        <div class="icon-title">fa-sheqel</div>
+    </li>
+    <li><i class="fa fa-try"></i>
+        <div class="icon-title">fa-try</div>
+    </li>
+    <li><i class="fa fa-turkish-lira"></i>
+        <div class="icon-title">fa-turkish-lira</div>
+    </li>
+    <li><i class="fa fa-usd"></i>
+        <div class="icon-title">fa-usd</div>
+    </li>
+    <li><i class="fa fa-won"></i>
+        <div class="icon-title">fa-won</div>
+    </li>
+    <li><i class="fa fa-yen"></i>
+        <div class="icon-title">fa-yen</div>
+    </li>
+    <li><i class="fa fa-align-center"></i>
+        <div class="icon-title">fa-align-center</div>
+    </li>
+    <li><i class="fa fa-align-justify"></i>
+        <div class="icon-title">fa-align-justify</div>
+    </li>
+    <li><i class="fa fa-align-left"></i>
+        <div class="icon-title">fa-align-left</div>
+    </li>
+    <li><i class="fa fa-align-right"></i>
+        <div class="icon-title">fa-align-right</div>
+    </li>
+    <li><i class="fa fa-bold"></i>
+        <div class="icon-title">fa-bold</div>
+    </li>
+    <li><i class="fa fa-chain"></i>
+        <div class="icon-title">fa-chain</div>
+    </li>
+    <li><i class="fa fa-chain-broken"></i>
+        <div class="icon-title">fa-chain-broken</div>
+    </li>
+    <li><i class="fa fa-clipboard"></i>
+        <div class="icon-title">fa-clipboard</div>
+    </li>
+    <li><i class="fa fa-columns"></i>
+        <div class="icon-title">fa-columns</div>
+    </li>
+    <li><i class="fa fa-copy"></i>
+        <div class="icon-title">fa-copy</div>
+    </li>
+    <li><i class="fa fa-cut"></i>
+        <div class="icon-title">fa-cut</div>
+    </li>
+    <li><i class="fa fa-dedent"></i>
+        <div class="icon-title">fa-dedent</div>
+    </li>
+    <li><i class="fa fa-eraser"></i>
+        <div class="icon-title">fa-eraser</div>
+    </li>
+    <li><i class="fa fa-file"></i>
+        <div class="icon-title">fa-file</div>
+    </li>
+    <li><i class="fa fa-file-o"></i>
+        <div class="icon-title">fa-file-o</div>
+    </li>
+    <li><i class="fa fa-file-text"></i>
+        <div class="icon-title">fa-file-text</div>
+    </li>
+    <li><i class="fa fa-file-text-o"></i>
+        <div class="icon-title">fa-file-text-o</div>
+    </li>
+    <li><i class="fa fa-files-o"></i>
+        <div class="icon-title">fa-files-o</div>
+    </li>
+    <li><i class="fa fa-floppy-o"></i>
+        <div class="icon-title">fa-floppy-o</div>
+    </li>
+    <li><i class="fa fa-font"></i>
+        <div class="icon-title">fa-font</div>
+    </li>
+    <li><i class="fa fa-header"></i>
+        <div class="icon-title">fa-header</div>
+    </li>
+    <li><i class="fa fa-indent"></i>
+        <div class="icon-title">fa-indent</div>
+    </li>
+    <li><i class="fa fa-italic"></i>
+        <div class="icon-title">fa-italic</div>
+    </li>
+    <li><i class="fa fa-link"></i>
+        <div class="icon-title">fa-link</div>
+    </li>
+    <li><i class="fa fa-list"></i>
+        <div class="icon-title">fa-list</div>
+    </li>
+    <li><i class="fa fa-list-alt"></i>
+        <div class="icon-title">fa-list-alt</div>
+    </li>
+    <li><i class="fa fa-list-ol"></i>
+        <div class="icon-title">fa-list-ol</div>
+    </li>
+    <li><i class="fa fa-list-ul"></i>
+        <div class="icon-title">fa-list-ul</div>
+    </li>
+    <li><i class="fa fa-outdent"></i>
+        <div class="icon-title">fa-outdent</div>
+    </li>
+    <li><i class="fa fa-paperclip"></i>
+        <div class="icon-title">fa-paperclip</div>
+    </li>
+    <li><i class="fa fa-paragraph"></i>
+        <div class="icon-title">fa-paragraph</div>
+    </li>
+    <li><i class="fa fa-paste"></i>
+        <div class="icon-title">fa-paste</div>
+    </li>
+    <li><i class="fa fa-repeat"></i>
+        <div class="icon-title">fa-repeat</div>
+    </li>
+    <li><i class="fa fa-rotate-left"></i>
+        <div class="icon-title">fa-rotate-left</div>
+    </li>
+    <li><i class="fa fa-rotate-right"></i>
+        <div class="icon-title">fa-rotate-right</div>
+    </li>
+    <li><i class="fa fa-save"></i>
+        <div class="icon-title">fa-save</div>
+    </li>
+    <li><i class="fa fa-scissors"></i>
+        <div class="icon-title">fa-scissors</div>
+    </li>
+    <li><i class="fa fa-strikethrough"></i>
+        <div class="icon-title">fa-strikethrough</div>
+    </li>
+    <li><i class="fa fa-subscript"></i>
+        <div class="icon-title">fa-subscript</div>
+    </li>
+    <li><i class="fa fa-superscript"></i>
+        <div class="icon-title">fa-superscript</div>
+    </li>
+    <li><i class="fa fa-table"></i>
+        <div class="icon-title">fa-table</div>
+    </li>
+    <li><i class="fa fa-text-height"></i>
+        <div class="icon-title">fa-text-height</div>
+    </li>
+    <li><i class="fa fa-text-width"></i>
+        <div class="icon-title">fa-text-width</div>
+    </li>
+    <li><i class="fa fa-th"></i>
+        <div class="icon-title">fa-th</div>
+    </li>
+    <li><i class="fa fa-th-large"></i>
+        <div class="icon-title">fa-th-large</div>
+    </li>
+    <li><i class="fa fa-th-list"></i>
+        <div class="icon-title">fa-th-list</div>
+    </li>
+    <li><i class="fa fa-underline"></i>
+        <div class="icon-title">fa-underline</div>
+    </li>
+    <li><i class="fa fa-undo"></i>
+        <div class="icon-title">fa-undo</div>
+    </li>
+    <li><i class="fa fa-unlink"></i>
+        <div class="icon-title">fa-unlink</div>
+    </li>
+    <li><i class="fa fa-angle-double-down"></i>
+        <div class="icon-title">fa-angle-double-down</div>
+    </li>
+    <li><i class="fa fa-angle-double-left"></i>
+        <div class="icon-title">fa-angle-double-left</div>
+    </li>
+    <li><i class="fa fa-angle-double-right"></i>
+        <div class="icon-title">fa-angle-double-right</div>
+    </li>
+    <li><i class="fa fa-angle-double-up"></i>
+        <div class="icon-title">fa-angle-double-up</div>
+    </li>
+    <li><i class="fa fa-angle-down"></i>
+        <div class="icon-title">fa-angle-down</div>
+    </li>
+    <li><i class="fa fa-angle-left"></i>
+        <div class="icon-title">fa-angle-left</div>
+    </li>
+    <li><i class="fa fa-angle-right"></i>
+        <div class="icon-title">fa-angle-right</div>
+    </li>
+    <li><i class="fa fa-angle-up"></i>
+        <div class="icon-title">fa-angle-up</div>
+    </li>
+    <li><i class="fa fa-arrow-circle-down"></i>
+        <div class="icon-title">fa-arrow-circle-down</div>
+    </li>
+    <li><i class="fa fa-arrow-circle-left"></i>
+        <div class="icon-title">fa-arrow-circle-left</div>
+    </li>
+    <li><i class="fa fa-arrow-circle-o-down"></i>
+        <div class="icon-title">fa-arrow-circle-o-down</div>
+    </li>
+    <li><i class="fa fa-arrow-circle-o-left"></i>
+        <div class="icon-title">fa-arrow-circle-o-left</div>
+    </li>
+    <li><i class="fa fa-arrow-circle-o-right"></i>
+        <div class="icon-title">fa-arrow-circle-o-right</div>
+    </li>
+    <li><i class="fa fa-arrow-circle-o-up"></i>
+        <div class="icon-title">fa-arrow-circle-o-up</div>
+    </li>
+    <li><i class="fa fa-arrow-circle-right"></i>
+        <div class="icon-title">fa-arrow-circle-right</div>
+    </li>
+    <li><i class="fa fa-arrow-circle-up"></i>
+        <div class="icon-title">fa-arrow-circle-up</div>
+    </li>
+    <li><i class="fa fa-arrow-down"></i>
+        <div class="icon-title">fa-arrow-down</div>
+    </li>
+    <li><i class="fa fa-arrow-left"></i>
+        <div class="icon-title">fa-arrow-left</div>
+    </li>
+    <li><i class="fa fa-arrow-right"></i>
+        <div class="icon-title">fa-arrow-right</div>
+    </li>
+    <li><i class="fa fa-arrow-up"></i>
+        <div class="icon-title">fa-arrow-up</div>
+    </li>
+    <li><i class="fa fa-arrows"></i>
+        <div class="icon-title">fa-arrows</div>
+    </li>
+    <li><i class="fa fa-arrows-alt"></i>
+        <div class="icon-title">fa-arrows-alt</div>
+    </li>
+    <li><i class="fa fa-arrows-h"></i>
+        <div class="icon-title">fa-arrows-h</div>
+    </li>
+    <li><i class="fa fa-arrows-v"></i>
+        <div class="icon-title">fa-arrows-v</div>
+    </li>
+    <li><i class="fa fa-caret-down"></i>
+        <div class="icon-title">fa-caret-down</div>
+    </li>
+    <li><i class="fa fa-caret-left"></i>
+        <div class="icon-title">fa-caret-left</div>
+    </li>
+    <li><i class="fa fa-caret-right"></i>
+        <div class="icon-title">fa-caret-right</div>
+    </li>
+    <li><i class="fa fa-caret-square-o-down"></i>
+        <div class="icon-title">fa-caret-square-o-down</div>
+    </li>
+    <li><i class="fa fa-caret-square-o-left"></i>
+        <div class="icon-title">fa-caret-square-o-left</div>
+    </li>
+    <li><i class="fa fa-caret-square-o-right"></i>
+        <div class="icon-title">fa-caret-square-o-right</div>
+    </li>
+    <li><i class="fa fa-caret-square-o-up"></i>
+        <div class="icon-title">fa-caret-square-o-up</div>
+    </li>
+    <li><i class="fa fa-caret-up"></i>
+        <div class="icon-title">fa-caret-up</div>
+    </li>
+    <li><i class="fa fa-chevron-circle-down"></i>
+        <div class="icon-title">fa-chevron-circle-down</div>
+    </li>
+    <li><i class="fa fa-chevron-circle-left"></i>
+        <div class="icon-title">fa-chevron-circle-left</div>
+    </li>
+    <li><i class="fa fa-chevron-circle-right"></i>
+        <div class="icon-title">fa-chevron-circle-right</div>
+    </li>
+    <li><i class="fa fa-chevron-circle-up"></i>
+        <div class="icon-title">fa-chevron-circle-up</div>
+    </li>
+    <li><i class="fa fa-chevron-down"></i>
+        <div class="icon-title">fa-chevron-down</div>
+    </li>
+    <li><i class="fa fa-chevron-left"></i>
+        <div class="icon-title">fa-chevron-left</div>
+    </li>
+    <li><i class="fa fa-chevron-right"></i>
+        <div class="icon-title">fa-chevron-right</div>
+    </li>
+    <li><i class="fa fa-chevron-up"></i>
+        <div class="icon-title">fa-chevron-up</div>
+    </li>
+    <li><i class="fa fa-exchange"></i>
+        <div class="icon-title">fa-exchange</div>
+    </li>
+    <li><i class="fa fa-hand-o-down"></i>
+        <div class="icon-title">fa-hand-o-down</div>
+    </li>
+    <li><i class="fa fa-hand-o-left"></i>
+        <div class="icon-title">fa-hand-o-left</div>
+    </li>
+    <li><i class="fa fa-hand-o-right"></i>
+        <div class="icon-title">fa-hand-o-right</div>
+    </li>
+    <li><i class="fa fa-hand-o-up"></i>
+        <div class="icon-title">fa-hand-o-up</div>
+    </li>
+    <li><i class="fa fa-long-arrow-down"></i>
+        <div class="icon-title">fa-long-arrow-down</div>
+    </li>
+    <li><i class="fa fa-long-arrow-left"></i>
+        <div class="icon-title">fa-long-arrow-left</div>
+    </li>
+    <li><i class="fa fa-long-arrow-right"></i>
+        <div class="icon-title">fa-long-arrow-right</div>
+    </li>
+    <li><i class="fa fa-long-arrow-up"></i>
+        <div class="icon-title">fa-long-arrow-up</div>
+    </li>
+    <li><i class="fa fa-toggle-down"></i>
+        <div class="icon-title">fa-toggle-down</div>
+    </li>
+    <li><i class="fa fa-toggle-left"></i>
+        <div class="icon-title">fa-toggle-left</div>
+    </li>
+    <li><i class="fa fa-toggle-right"></i>
+        <div class="icon-title">fa-toggle-right</div>
+    </li>
+    <li><i class="fa fa-toggle-up"></i>
+        <div class="icon-title">fa-toggle-up</div>
+    </li>
+    <li><i class="fa fa-arrows-alt"></i>
+        <div class="icon-title">fa-arrows-alt</div>
+    </li>
+    <li><i class="fa fa-backward"></i>
+        <div class="icon-title">fa-backward</div>
+    </li>
+    <li><i class="fa fa-compress"></i>
+        <div class="icon-title">fa-compress</div>
+    </li>
+    <li><i class="fa fa-eject"></i>
+        <div class="icon-title">fa-eject</div>
+    </li>
+    <li><i class="fa fa-expand"></i>
+        <div class="icon-title">fa-expand</div>
+    </li>
+    <li><i class="fa fa-fast-backward"></i>
+        <div class="icon-title">fa-fast-backward</div>
+    </li>
+    <li><i class="fa fa-fast-forward"></i>
+        <div class="icon-title">fa-fast-forward</div>
+    </li>
+    <li><i class="fa fa-forward"></i>
+        <div class="icon-title">fa-forward</div>
+    </li>
+    <li><i class="fa fa-pause"></i>
+        <div class="icon-title">fa-pause</div>
+    </li>
+    <li><i class="fa fa-pause-circle"></i>
+        <div class="icon-title">fa-pause-circle</div>
+    </li>
+    <li><i class="fa fa-pause-circle-o"></i>
+        <div class="icon-title">fa-pause-circle-o</div>
+    </li>
+    <li><i class="fa fa-play"></i>
+        <div class="icon-title">fa-play</div>
+    </li>
+    <li><i class="fa fa-play-circle"></i>
+        <div class="icon-title">fa-play-circle</div>
+    </li>
+    <li><i class="fa fa-play-circle-o"></i>
+        <div class="icon-title">fa-play-circle-o</div>
+    </li>
+    <li><i class="fa fa-random"></i>
+        <div class="icon-title">fa-random</div>
+    </li>
+    <li><i class="fa fa-step-backward"></i>
+        <div class="icon-title">fa-step-backward</div>
+    </li>
+    <li><i class="fa fa-step-forward"></i>
+        <div class="icon-title">fa-step-forward</div>
+    </li>
+    <li><i class="fa fa-stop"></i>
+        <div class="icon-title">fa-stop</div>
+    </li>
+    <li><i class="fa fa-stop-circle"></i>
+        <div class="icon-title">fa-stop-circle</div>
+    </li>
+    <li><i class="fa fa-stop-circle-o"></i>
+        <div class="icon-title">fa-stop-circle-o</div>
+    </li>
+    <li><i class="fa fa-youtube-play"></i>
+        <div class="icon-title">fa-youtube-play</div>
+    </li>
+    <li><i class="fa fa-500px"></i>
+        <div class="icon-title">fa-500px</div>
+    </li>
+    <li><i class="fa fa-adn"></i>
+        <div class="icon-title">fa-adn</div>
+    </li>
+    <li><i class="fa fa-amazon"></i>
+        <div class="icon-title">fa-amazon</div>
+    </li>
+    <li><i class="fa fa-android"></i>
+        <div class="icon-title">fa-android</div>
+    </li>
+    <li><i class="fa fa-angellist"></i>
+        <div class="icon-title">fa-angellist</div>
+    </li>
+    <li><i class="fa fa-apple"></i>
+        <div class="icon-title">fa-apple</div>
+    </li>
+    <li><i class="fa fa-bandcamp"></i>
+        <div class="icon-title">fa-bandcamp</div>
+    </li>
+    <li><i class="fa fa-behance"></i>
+        <div class="icon-title">fa-behance</div>
+    </li>
+    <li><i class="fa fa-behance-square"></i>
+        <div class="icon-title">fa-behance-square</div>
+    </li>
+    <li><i class="fa fa-bitbucket"></i>
+        <div class="icon-title">fa-bitbucket</div>
+    </li>
+    <li><i class="fa fa-bitbucket-square"></i>
+        <div class="icon-title">fa-bitbucket-square</div>
+    </li>
+    <li><i class="fa fa-bitcoin"></i>
+        <div class="icon-title">fa-bitcoin</div>
+    </li>
+    <li><i class="fa fa-black-tie"></i>
+        <div class="icon-title">fa-black-tie</div>
+    </li>
+    <li><i class="fa fa-bluetooth"></i>
+        <div class="icon-title">fa-bluetooth</div>
+    </li>
+    <li><i class="fa fa-bluetooth-b"></i>
+        <div class="icon-title">fa-bluetooth-b</div>
+    </li>
+    <li><i class="fa fa-btc"></i>
+        <div class="icon-title">fa-btc</div>
+    </li>
+    <li><i class="fa fa-buysellads"></i>
+        <div class="icon-title">fa-buysellads</div>
+    </li>
+    <li><i class="fa fa-cc-amex"></i>
+        <div class="icon-title">fa-cc-amex</div>
+    </li>
+    <li><i class="fa fa-cc-diners-club"></i>
+        <div class="icon-title">fa-cc-diners-club</div>
+    </li>
+    <li><i class="fa fa-cc-discover"></i>
+        <div class="icon-title">fa-cc-discover</div>
+    </li>
+    <li><i class="fa fa-cc-jcb"></i>
+        <div class="icon-title">fa-cc-jcb</div>
+    </li>
+    <li><i class="fa fa-cc-mastercard"></i>
+        <div class="icon-title">fa-cc-mastercard</div>
+    </li>
+    <li><i class="fa fa-cc-paypal"></i>
+        <div class="icon-title">fa-cc-paypal</div>
+    </li>
+    <li><i class="fa fa-cc-stripe"></i>
+        <div class="icon-title">fa-cc-stripe</div>
+    </li>
+    <li><i class="fa fa-cc-visa"></i>
+        <div class="icon-title">fa-cc-visa</div>
+    </li>
+    <li><i class="fa fa-chrome"></i>
+        <div class="icon-title">fa-chrome</div>
+    </li>
+    <li><i class="fa fa-codepen"></i>
+        <div class="icon-title">fa-codepen</div>
+    </li>
+    <li><i class="fa fa-codiepie"></i>
+        <div class="icon-title">fa-codiepie</div>
+    </li>
+    <li><i class="fa fa-connectdevelop"></i>
+        <div class="icon-title">fa-connectdevelop</div>
+    </li>
+    <li><i class="fa fa-contao"></i>
+        <div class="icon-title">fa-contao</div>
+    </li>
+    <li><i class="fa fa-css3"></i>
+        <div class="icon-title">fa-css3</div>
+    </li>
+    <li><i class="fa fa-dashcube"></i>
+        <div class="icon-title">fa-dashcube</div>
+    </li>
+    <li><i class="fa fa-delicious"></i>
+        <div class="icon-title">fa-delicious</div>
+    </li>
+    <li><i class="fa fa-deviantart"></i>
+        <div class="icon-title">fa-deviantart</div>
+    </li>
+    <li><i class="fa fa-digg"></i>
+        <div class="icon-title">fa-digg</div>
+    </li>
+    <li><i class="fa fa-dribbble"></i>
+        <div class="icon-title">fa-dribbble</div>
+    </li>
+    <li><i class="fa fa-dropbox"></i>
+        <div class="icon-title">fa-dropbox</div>
+    </li>
+    <li><i class="fa fa-drupal"></i>
+        <div class="icon-title">fa-drupal</div>
+    </li>
+    <li><i class="fa fa-edge"></i>
+        <div class="icon-title">fa-edge</div>
+    </li>
+    <li><i class="fa fa-eercast"></i>
+        <div class="icon-title">fa-eercast</div>
+    </li>
+    <li><i class="fa fa-empire"></i>
+        <div class="icon-title">fa-empire</div>
+    </li>
+    <li><i class="fa fa-envira"></i>
+        <div class="icon-title">fa-envira</div>
+    </li>
+    <li><i class="fa fa-etsy"></i>
+        <div class="icon-title">fa-etsy</div>
+    </li>
+    <li><i class="fa fa-expeditedssl"></i>
+        <div class="icon-title">fa-expeditedssl</div>
+    </li>
+    <li><i class="fa fa-fa"></i>
+        <div class="icon-title">fa-fa</div>
+    </li>
+    <li><i class="fa fa-facebook"></i>
+        <div class="icon-title">fa-facebook</div>
+    </li>
+    <li><i class="fa fa-facebook-f"></i>
+        <div class="icon-title">fa-facebook-f</div>
+    </li>
+    <li><i class="fa fa-facebook-official"></i>
+        <div class="icon-title">fa-facebook-official</div>
+    </li>
+    <li><i class="fa fa-facebook-square"></i>
+        <div class="icon-title">fa-facebook-square</div>
+    </li>
+    <li><i class="fa fa-firefox"></i>
+        <div class="icon-title">fa-firefox</div>
+    </li>
+    <li><i class="fa fa-first-order"></i>
+        <div class="icon-title">fa-first-order</div>
+    </li>
+    <li><i class="fa fa-flickr"></i>
+        <div class="icon-title">fa-flickr</div>
+    </li>
+    <li><i class="fa fa-font-awesome"></i>
+        <div class="icon-title">fa-font-awesome</div>
+    </li>
+    <li><i class="fa fa-fonticons"></i>
+        <div class="icon-title">fa-fonticons</div>
+    </li>
+    <li><i class="fa fa-fort-awesome"></i>
+        <div class="icon-title">fa-fort-awesome</div>
+    </li>
+    <li><i class="fa fa-forumbee"></i>
+        <div class="icon-title">fa-forumbee</div>
+    </li>
+    <li><i class="fa fa-foursquare"></i>
+        <div class="icon-title">fa-foursquare</div>
+    </li>
+    <li><i class="fa fa-free-code-camp"></i>
+        <div class="icon-title">fa-free-code-camp</div>
+    </li>
+    <li><i class="fa fa-ge"></i>
+        <div class="icon-title">fa-ge</div>
+    </li>
+    <li><i class="fa fa-get-pocket"></i>
+        <div class="icon-title">fa-get-pocket</div>
+    </li>
+    <li><i class="fa fa-gg"></i>
+        <div class="icon-title">fa-gg</div>
+    </li>
+    <li><i class="fa fa-gg-circle"></i>
+        <div class="icon-title">fa-gg-circle</div>
+    </li>
+    <li><i class="fa fa-git"></i>
+        <div class="icon-title">fa-git</div>
+    </li>
+    <li><i class="fa fa-git-square"></i>
+        <div class="icon-title">fa-git-square</div>
+    </li>
+    <li><i class="fa fa-github"></i>
+        <div class="icon-title">fa-github</div>
+    </li>
+    <li><i class="fa fa-github-alt"></i>
+        <div class="icon-title">fa-github-alt</div>
+    </li>
+    <li><i class="fa fa-github-square"></i>
+        <div class="icon-title">fa-github-square</div>
+    </li>
+    <li><i class="fa fa-gitlab"></i>
+        <div class="icon-title">fa-gitlab</div>
+    </li>
+    <li><i class="fa fa-gittip"></i>
+        <div class="icon-title">fa-gittip</div>
+    </li>
+    <li><i class="fa fa-glide"></i>
+        <div class="icon-title">fa-glide</div>
+    </li>
+    <li><i class="fa fa-glide-g"></i>
+        <div class="icon-title">fa-glide-g</div>
+    </li>
+    <li><i class="fa fa-google"></i>
+        <div class="icon-title">fa-google</div>
+    </li>
+    <li><i class="fa fa-google-plus"></i>
+        <div class="icon-title">fa-google-plus</div>
+    </li>
+    <li><i class="fa fa-google-plus-circle"></i>
+        <div class="icon-title">fa-google-plus-circle</div>
+    </li>
+    <li><i class="fa fa-google-plus-official"></i>
+        <div class="icon-title">fa-google-plus-official</div>
+    </li>
+    <li><i class="fa fa-google-plus-square"></i>
+        <div class="icon-title">fa-google-plus-square</div>
+    </li>
+    <li><i class="fa fa-google-wallet"></i>
+        <div class="icon-title">fa-google-wallet</div>
+    </li>
+    <li><i class="fa fa-gratipay"></i>
+        <div class="icon-title">fa-gratipay</div>
+    </li>
+    <li><i class="fa fa-grav"></i>
+        <div class="icon-title">fa-grav</div>
+    </li>
+    <li><i class="fa fa-hacker-news"></i>
+        <div class="icon-title">fa-hacker-news</div>
+    </li>
+    <li><i class="fa fa-houzz"></i>
+        <div class="icon-title">fa-houzz</div>
+    </li>
+    <li><i class="fa fa-html5"></i>
+        <div class="icon-title">fa-html5</div>
+    </li>
+    <li><i class="fa fa-imdb"></i>
+        <div class="icon-title">fa-imdb</div>
+    </li>
+    <li><i class="fa fa-instagram"></i>
+        <div class="icon-title">fa-instagram</div>
+    </li>
+    <li><i class="fa fa-internet-explorer"></i>
+        <div class="icon-title">fa-internet-explorer</div>
+    </li>
+    <li><i class="fa fa-ioxhost"></i>
+        <div class="icon-title">fa-ioxhost</div>
+    </li>
+    <li><i class="fa fa-joomla"></i>
+        <div class="icon-title">fa-joomla</div>
+    </li>
+    <li><i class="fa fa-jsfiddle"></i>
+        <div class="icon-title">fa-jsfiddle</div>
+    </li>
+    <li><i class="fa fa-lastfm"></i>
+        <div class="icon-title">fa-lastfm</div>
+    </li>
+    <li><i class="fa fa-lastfm-square"></i>
+        <div class="icon-title">fa-lastfm-square</div>
+    </li>
+    <li><i class="fa fa-leanpub"></i>
+        <div class="icon-title">fa-leanpub</div>
+    </li>
+    <li><i class="fa fa-linkedin"></i>
+        <div class="icon-title">fa-linkedin</div>
+    </li>
+    <li>
+        <i class="fa fa-linkedin-square"></i>
+        <div class="icon-title">fa-linkedin-square</div>
+    </li>
+    <li>
+        <i class="fa fa-linode"></i>
+        <div class="icon-title">fa-linode</div>
+    </li>
+    <li>
+        <i class="fa fa-linux"></i>
+        <div class="icon-title">fa-linux</div>
+    </li>
+    <li>
+        <i class="fa fa-maxcdn"></i>
+        <div class="icon-title">fa-maxcdn</div>
+    </li>
+    <li>
+        <i class="fa fa-meanpath"></i>
+        <div class="icon-title">fa-meanpath</div>
+    </li>
+    <li>
+        <i class="fa fa-medium"></i>
+        <div class="icon-title">fa-medium</div>
+    </li>
+    <li>
+        <i class="fa fa-meetup"></i>
+        <div class="icon-title">fa-meetup</div>
+    </li>
+    <li>
+        <i class="fa fa-mixcloud"></i>
+        <div class="icon-title">fa-mixcloud</div>
+    </li>
+    <li>
+        <i class="fa fa-modx"></i>
+        <div class="icon-title">fa-modx</div>
+    </li>
+    <li>
+        <i class="fa fa-odnoklassniki"></i>
+        <div class="icon-title">fa-odnoklassniki</div>
+    </li>
+    <li>
+        <i class="fa fa-odnoklassniki-square"></i>
+        <div class="icon-title">fa-odnoklassniki-square</div>
+    </li>
+    <li>
+        <i class="fa fa-opencart"></i>
+        <div class="icon-title">fa-opencart</div>
+    </li>
+    <li>
+        <i class="fa fa-openid"></i>
+        <div class="icon-title">fa-openid</div>
+    </li>
+    <li>
+        <i class="fa fa-opera"></i>
+        <div class="icon-title">fa-opera</div>
+    </li>
+    <li>
+        <i class="fa fa-optin-monster"></i>
+        <div class="icon-title">fa-optin-monster</div>
+    </li>
+    <li>
+        <i class="fa fa-pagelines"></i>
+        <div class="icon-title">fa-pagelines</div>
+    </li>
+    <li>
+        <i class="fa fa-paypal"></i>
+        <div class="icon-title">fa-paypal</div>
+    </li>
+    <li>
+        <i class="fa fa-pied-piper"></i>
+        <div class="icon-title">fa-pied-piper</div>
+    </li>
+    <li>
+        <i class="fa fa-pied-piper-alt"></i>
+        <div class="icon-title">fa-pied-piper-alt</div>
+    </li>
+    <li>
+        <i class="fa fa-pied-piper-pp"></i>
+        <div class="icon-title">fa-pied-piper-pp</div>
+    </li>
+    <li>
+        <i class="fa fa-pinterest"></i>
+        <div class="icon-title">fa-pinterest</div>
+    </li>
+    <li>
+        <i class="fa fa-pinterest-p"></i>
+        <div class="icon-title">fa-pinterest-p</div>
+    </li>
+    <li>
+        <i class="fa fa-pinterest-square"></i>
+        <div class="icon-title">fa-pinterest-square</div>
+    </li>
+    <li>
+        <i class="fa fa-product-hunt"></i>
+        <div class="icon-title">fa-product-hunt</div>
+    </li>
+    <li>
+        <i class="fa fa-qq"></i>
+        <div class="icon-title">fa-qq</div>
+    </li>
+    <li>
+        <i class="fa fa-quora"></i>
+        <div class="icon-title">fa-quora</div>
+    </li>
+    <li>
+        <i class="fa fa-ra"></i>
+        <div class="icon-title">fa-ra</div>
+    </li>
+    <li>
+        <i class="fa fa-ravelry"></i>
+        <div class="icon-title">fa-ravelry</div>
+    </li>
+    <li>
+        <i class="fa fa-rebel"></i>
+        <div class="icon-title">fa-rebel</div>
+    </li>
+    <li>
+        <i class="fa fa-reddit"></i>
+        <div class="icon-title">fa-reddit</div>
+    </li>
+    <li>
+        <i class="fa fa-reddit-alien"></i>
+        <div class="icon-title">fa-reddit-alien</div>
+    </li>
+    <li>
+        <i class="fa fa-reddit-square"></i>
+        <div class="icon-title">fa-reddit-square</div>
+    </li>
+    <li>
+        <i class="fa fa-renren"></i>
+        <div class="icon-title">fa-renren</div>
+    </li>
+    <li>
+        <i class="fa fa-resistance"></i>
+        <div class="icon-title">fa-resistance</div>
+    </li>
+    <li>
+        <i class="fa fa-safari"></i>
+        <div class="icon-title">fa-safari</div>
+    </li>
+    <li>
+        <i class="fa fa-scribd"></i>
+        <div class="icon-title">fa-scribd</div>
+    </li>
+    <li>
+        <i class="fa fa-sellsy"></i>
+        <div class="icon-title">fa-sellsy</div>
+    </li>
+    <li>
+        <i class="fa fa-share-alt"></i>
+        <div class="icon-title">fa-share-alt</div>
+    </li>
+    <li>
+        <i class="fa fa-share-alt-square"></i>
+        <div class="icon-title">fa-share-alt-square</div>
+    </li>
+    <li>
+        <i class="fa fa-shirtsinbulk"></i>
+        <div class="icon-title">fa-shirtsinbulk</div>
+    </li>
+    <li>
+        <i class="fa fa-simplybuilt"></i>
+        <div class="icon-title">fa-simplybuilt</div>
+    </li>
+    <li>
+        <i class="fa fa-skyatlas"></i>
+        <div class="icon-title">fa-skyatlas</div>
+    </li>
+    <li>
+        <i class="fa fa-skype"></i>
+        <div class="icon-title">fa-skype</div>
+    </li>
+    <li>
+        <i class="fa fa-slack"></i>
+        <div class="icon-title">fa-slack</div>
+    </li>
+    <li>
+        <i class="fa fa-slideshare"></i>
+        <div class="icon-title">fa-slideshare</div>
+    </li>
+    <li>
+        <i class="fa fa-snapchat"></i>
+        <div class="icon-title">fa-snapchat</div>
+    </li>
+    <li>
+        <i class="fa fa-snapchat-ghost"></i>
+        <div class="icon-title">fa-snapchat-ghost</div>
+    </li>
+    <li>
+        <i class="fa fa-snapchat-square"></i>
+        <div class="icon-title">fa-snapchat-square</div>
+    </li>
+    <li>
+        <i class="fa fa-soundcloud"></i>
+        <div class="icon-title">fa-soundcloud</div>
+    </li>
+    <li>
+        <i class="fa fa-spotify"></i>
+        <div class="icon-title">fa-spotify</div>
+    </li>
+    <li>
+        <i class="fa fa-stack-exchange"></i>
+        <div class="icon-title">fa-stack-exchange</div>
+    </li>
+    <li>
+        <i class="fa fa-stack-overflow"></i>
+        <div class="icon-title">fa-stack-overflow</div>
+    </li>
+    <li>
+        <i class="fa fa-steam"></i>
+        <div class="icon-title">fa-steam</div>
+    </li>
+    <li>
+        <i class="fa fa-steam-square"></i>
+        <div class="icon-title">fa-steam-square</div>
+    </li>
+    <li>
+        <i class="fa fa-stumbleupon"></i>
+        <div class="icon-title">fa-stumbleupon</div>
+    </li>
+    <li>
+        <i class="fa fa-stumbleupon-circle"></i>
+        <div class="icon-title">fa-stumbleupon-circle</div>
+    </li>
+    <li>
+        <i class="fa fa-superpowers"></i>
+        <div class="icon-title">fa-superpowers</div>
+    </li>
+    <li>
+        <i class="fa fa-telegram"></i>
+        <div class="icon-title">fa-telegram</div>
+    </li>
+    <li>
+        <i class="fa fa-tencent-weibo"></i>
+        <div class="icon-title">fa-tencent-weibo</div>
+    </li>
+    <li>
+        <i class="fa fa-themeisle"></i>
+        <div class="icon-title">fa-themeisle</div>
+    </li>
+    <li>
+        <i class="fa fa-trello"></i>
+        <div class="icon-title">fa-trello</div>
+    </li>
+    <li>
+        <i class="fa fa-tripadvisor"></i>
+        <div class="icon-title">fa-tripadvisor</div>
+    </li>
+    <li>
+        <i class="fa fa-tumblr"></i>
+        <div class="icon-title">fa-tumblr</div>
+    </li>
+    <li>
+        <i class="fa fa-tumblr-square"></i>
+        <div class="icon-title">fa-tumblr-square</div>
+    </li>
+    <li>
+        <i class="fa fa-twitch"></i>
+        <div class="icon-title">fa-twitch</div>
+    </li>
+    <li>
+        <i class="fa fa-twitter"></i>
+        <div class="icon-title">fa-twitter</div>
+    </li>
+    <li>
+        <i class="fa fa-twitter-square"></i>
+        <div class="icon-title">fa-twitter-square</div>
+    </li>
+    <li>
+        <i class="fa fa-usb"></i>
+        <div class="icon-title">fa-usb</div>
+    </li>
+    <li>
+        <i class="fa fa-viacoin"></i>
+        <div class="icon-title">fa-viacoin</div>
+    </li>
+    <li>
+        <i class="fa fa-viadeo"></i>
+        <div class="icon-title">fa-viadeo</div>
+    </li>
+    <li>
+        <i class="fa fa-viadeo-square"></i>
+        <div class="icon-title">fa-viadeo-square</div>
+    </li>
+    <li>
+        <i class="fa fa-vimeo"></i>
+        <div class="icon-title">fa-vimeo</div>
+    </li>
+    <li>
+        <i class="fa fa-vimeo-square"></i>
+        <div class="icon-title">fa-vimeo-square</div>
+    </li>
+    <li>
+        <i class="fa fa-vine"></i>
+        <div class="icon-title">fa-vine</div>
+    </li>
+    <li>
+        <i class="fa fa-vk"></i>
+        <div class="icon-title">fa-vk</div>
+    </li>
+    <li>
+        <i class="fa fa-wechat"></i>
+        <div class="icon-title">fa-wechat</div>
+    </li>
+    <li>
+        <i class="fa fa-weibo"></i>
+        <div class="icon-title">fa-weibo</div>
+    </li>
+    <li>
+        <i class="fa fa-weixin"></i>
+        <div class="icon-title">fa-weixin</div>
+    </li>
+    <li>
+        <i class="fa fa-whatsapp"></i>
+        <div class="icon-title">fa-whatsapp</div>
+    </li>
+    <li>
+        <i class="fa fa-wikipedia-w"></i>
+        <div class="icon-title">fa-wikipedia-w</div>
+    </li>
+    <li>
+        <i class="fa fa-windows"></i>
+        <div class="icon-title">fa-windows</div>
+    </li>
+    <li>
+        <i class="fa fa-wordpress"></i>
+        <div class="icon-title">fa-wordpress</div>
+    </li>
+    <li>
+        <i class="fa fa-wpbeginner"></i>
+        <div class="icon-title">fa-wpbeginner</div>
+    </li>
+    <li>
+        <i class="fa fa-wpexplorer"></i>
+        <div class="icon-title">fa-wpexplorer</div>
+    </li>
+    <li>
+        <i class="fa fa-wpforms"></i>
+        <div class="icon-title">fa-wpforms</div>
+    </li>
+    <li>
+        <i class="fa fa-xing"></i>
+        <div class="icon-title">fa-xing</div>
+    </li>
+    <li>
+        <i class="fa fa-xing-square"></i>
+        <div class="icon-title">fa-xing-square</div>
+    </li>
+    <li>
+        <i class="fa fa-y-combinator"></i>
+        <div class="icon-title">fa-y-combinator</div>
+    </li>
+    <li>
+        <i class="fa fa-y-combinator-square"></i>
+        <div class="icon-title">fa-y-combinator-square</div>
+    </li>
+    <li>
+        <i class="fa fa-yahoo"></i>
+        <div class="icon-title">fa-yahoo</div>
+    </li>
+    <li>
+        <i class="fa fa-yc"></i>
+        <div class="icon-title">fa-yc</div>
+    </li>
+    <li>
+        <i class="fa fa-yc-square"></i>
+        <div class="icon-title">fa-yc-square</div>
+    </li>
+    <li>
+        <i class="fa fa-yelp"></i>
+        <div class="icon-title">fa-yelp</div>
+    </li>
+    <li>
+        <i class="fa fa-yoast"></i>
+        <div class="icon-title">fa-yoast</div>
+    </li>
+    <li>
+        <i class="fa fa-youtube"></i>
+        <div class="icon-title">fa-youtube</div>
+    </li>
+    <li>
+        <i class="fa fa-youtube-play"></i>
+        <div class="icon-title">fa-youtube-play</div>
+    </li>
+    <li>
+        <i class="fa fa-youtube-square"></i>
+        <div class="icon-title">fa-youtube-square</div>
+    </li>
+    <li>
+        <i class="fa fa-ambulance"></i>
+        <div class="icon-title">fa-ambulance</div>
+    </li>
+    <li>
+        <i class="fa fa-h-square"></i>
+        <div class="icon-title">fa-h-square</div>
+    </li>
+    <li>
+        <i class="fa fa-heart"></i>
+        <div class="icon-title">fa-heart</div>
+    </li>
+    <li>
+        <i class="fa fa-heart-o"></i>
+        <div class="icon-title">fa-heart-o</div>
+    </li>
+    <li>
+        <i class="fa fa-heartbeat"></i>
+        <div class="icon-title">fa-heartbeat</div>
+    </li>
+    <li>
+        <i class="fa fa-hospital-o"></i>
+        <div class="icon-title">fa-hospital-o</div>
+    </li>
+    <li>
+        <i class="fa fa-medkit"></i>
+        <div class="icon-title">fa-medkit</div>
+    </li>
+    <li>
+        <i class="fa fa-plus-square"></i>
+        <div class="icon-title">fa-plus-square</div>
+    </li>
+    <li>
+        <i class="fa fa-stethoscope"></i>
+        <div class="icon-title">fa-stethoscope</div>
+    </li>
+    <li>
+        <i class="fa fa-user-md"></i>
+        <div class="icon-title">fa-user-md</div>
+    </li>
+    <li>
+        <i class="fa fa-wheelchair"></i>
+        <div class="icon-title">fa-wheelchair</div>
+    </li>
+    <li>
+        <i class="fa fa-wheelchair-alt"></i>
+        <div class="icon-title">fa-wheelchair-alt</div>
+    </li>
+</ul>
+
+<script src="__ROOT__/static/plugs/jquery/jquery.min.js" type="text/javascript"></script>
+<script>
+    $(function () {
+        $('li').on('click', function (className) {
+            className = $(this).find('i').get(0).className;
+            if (className) {
+                top.$('[name="{$field}"]').val(className).trigger('change');
+                top.layer.close(top.layer.getFrameIndex(window.name));
+            }
+        });
+    });
+</script>
+
+</body>
+</html>

+ 116 - 0
application/admin/view/auth/apply.html

@@ -0,0 +1,116 @@
+{extend name='main'}
+
+{block name="button"}
+
+{if auth("admin/auth/refresh")}
+<button data-load='{:url("refresh")}' class='layui-btn layui-btn-sm layui-btn-primary'>刷新权限</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+<div class="think-box-shadow">
+    <ul id="zTree" class="ztree notselect"></ul>
+    <div class="hr-line-dashed"></div>
+    <div class="layui-form-item text-center">
+        <button class="layui-btn" data-submit-role type='button'>保存数据</button>
+        <button class="layui-btn layui-btn-danger" type='button' onclick="window.history.back()">取消编辑</button>
+    </div>
+</div>
+{/block}
+
+{block name="script"}
+<script>
+    window.RoleAction = new function () {
+        this.data = {};
+        this.ztree = null;
+        this.setting = {
+            view: {showLine: false, showIcon: false, dblClickExpand: false},
+            check: {enable: true, nocheck: false, chkboxType: {"Y": "ps", "N": "ps"}},
+            callback: {
+                beforeClick: function (id, node) {
+                    node.children.length < 1 ? RoleAction.ztree.checkNode(node, !node.checked, null, true) : RoleAction.ztree.expandNode(node);
+                    return false;
+                }
+            }
+        };
+        this.renderChildren = function (list, level) {
+            var childrens = [];
+            for (var i in list) childrens.push({
+                open: true, node: list[i].node, name: list[i].title || list[i].node,
+                checked: list[i].checked || false, children: this.renderChildren(list[i]._sub_, level + 1)
+            });
+            return childrens;
+        };
+        this.getData = function (that) {
+            var index = $.msg.loading();
+            $.form.load('{:url()}', {id: '{$vo.id}', action: 'get'}, 'post', function (ret) {
+                that.data = that.renderChildren(ret.data, 1);
+                return $.msg.close(index), that.showTree(), false;
+            });
+        };
+        this.showTree = function () {
+            this.ztree = $.fn.zTree.init($("#zTree"), this.setting, this.data);
+            while (true) {
+                var nodes = this.ztree.getNodesByFilter(function (node) {
+                    return (!node.node && node.children.length < 1);
+                });
+                if (nodes.length < 1) break;
+                for (var i in nodes) this.ztree.removeNode(nodes[i]);
+            }
+        };
+        this.submit = function () {
+            var nodes = [], data = this.ztree.getCheckedNodes(true);
+            for (var i in data) if (data[i].node) nodes.push(data[i].node);
+            $.form.load('{:url()}', {id: '{$vo.id}', action: 'save', nodes: nodes}, 'post');
+        };
+        // 刷新数据
+        this.getData(this);
+        // 提交表单
+        $('[data-submit-role]').on('click', function () {
+            RoleAction.submit();
+        });
+    };
+</script>
+{/block}
+
+{block name="style"}
+<link href="__ROOT__/static/plugs/ztree/zTreeStyle/zTreeStyle.css" rel="stylesheet">
+<script src="__ROOT__/static/plugs/ztree/ztree.all.min.js"></script>
+<style>
+    ul.ztree li {
+        white-space: normal !important;
+    }
+
+    ul.ztree li span.button.switch {
+        margin-right: 5px;
+    }
+
+    ul.ztree ul ul li {
+        display: inline-block;
+        white-space: normal;
+    }
+
+    ul.ztree > li {
+        padding: 15px 25px 15px 15px;
+    }
+
+    ul.ztree > li > ul {
+        margin-top: 12px;
+        border-top: 1px solid rgba(0, 0, 0, .1);
+    }
+
+    ul.ztree > li > ul > li {
+        padding: 5px;
+    }
+
+    ul.ztree > li > a > span {
+        font-weight: 700;
+        font-size: 15px;
+    }
+
+    ul.ztree .level2 .button.level2 {
+        background: 0 0;
+    }
+</style>
+{/block}

+ 30 - 0
application/admin/view/auth/form.html

@@ -0,0 +1,30 @@
+<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post" autocomplete="off">
+
+    <div class="layui-card-body padding-left-40">
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">权限名称</span>
+                <span class="color-desc margin-left-5">Title</span>
+                <input type="text" name="title" value='{$vo.title|default=""}' required placeholder="请输入权限名称" class="layui-input">
+            </label>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">权限描述</span>
+                <span class="color-desc margin-left-5">Authorization</span>
+                <textarea placeholder="请输入权限描述" required class="layui-textarea" name="desc">{$vo.desc|default=""}</textarea>
+            </label>
+        </div>
+
+    </div>
+
+    <div class="hr-line-dashed"></div>
+    {notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
+
+    <div class="layui-form-item text-center">
+        <button class="layui-btn" type='submit'>保存数据</button>
+        <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
+    </div>
+</form>

+ 80 - 0
application/admin/view/auth/index.html

@@ -0,0 +1,80 @@
+{extend name='main'}
+
+{block name="button"}
+
+{if auth("admin/auth/add")}
+<button data-modal='{:url("add")}' data-title="添加权限" class='layui-btn layui-btn-sm layui-btn-primary'>添加权限</button>
+{/if}
+
+{if auth("admin/auth/remove")}
+<button data-action='{:url("remove")}' data-rule="id#{key}" data-csrf="{:systoken('admin/auth/remove')}" data-confirm="确定要删除这些权限吗?" class='layui-btn layui-btn-sm layui-btn-primary'>删除权限</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+
+<div class="think-box-shadow">
+    {include file='auth/index_search'}
+    <table class="layui-table margin-top-10" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'>
+                <label><input data-auto-none data-check-target='.list-check-box' type='checkbox'></label>
+            </th>
+            <th class='text-left nowrap'>权限信息</th>
+            <th class='text-left nowrap'>创建时间</th>
+            <th class="text-center nowrap">使用状态</th>
+            <th></th>
+        </tr>
+        </thead>
+        {/notempty}
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr data-dbclick>
+            <td class='list-table-check-td think-checkbox'>
+                <label><input class="list-check-box" value='{$vo.id}' type='checkbox'></label>
+            </td>
+            <td class='text-left'>
+                权限名称:{$vo.title|default='-'}<br>
+                <p class="color-desc">权限描述:{$vo.desc|default="没有写描述哦!"}</p>
+            </td>
+            <td class="text-left nowrap">
+                日期:{$vo.create_at|format_datetime|str_replace=' ','<br><span class="color-desc">时间:',###|raw}</span>
+            </td>
+            <td class='text-center nowrap'>
+                {eq name='vo.status' value='0'}<span class="color-red">已禁用</span>{else}<span class="color-green">使用中</span>{/eq}
+            </td>
+            <td class='text-center nowrap'>
+
+                {if auth("admin/auth/edit")}
+                <span class="text-explode">|</span>
+                <a data-dbclick class="layui-btn layui-btn-sm" data-title="编辑权限" data-modal='{:url("admin/auth/edit")}?id={$vo.id}'>编 辑</a>
+                {/if}
+
+                {if auth("admin/auth/apply")}
+                <a class="layui-btn layui-btn-normal layui-btn-sm" data-open='{:url("admin/auth/apply")}?id={$vo.id}'>授 权</a>
+                {/if}
+
+                {if $vo.status eq 1 and auth("admin/auth/forbid")}
+                <a class="layui-btn layui-btn-warm layui-btn-sm" data-action="{:url('forbid')}" data-value="id#{$vo.id};status#0" data-csrf="{:systoken('admin/auth/forbid')}">禁 用</a>
+                {elseif $vo.status eq 0 and auth("admin/auth/resume")}
+                <a class="layui-btn layui-btn-warm layui-btn-sm" data-action="{:url('resume')}" data-value="id#{$vo.id};status#1" data-csrf="{:systoken('admin/auth/resume')}">启 用</a>
+                {/if}
+
+                {if auth("admin/auth/remove")}
+                <a class="layui-btn layui-btn-danger layui-btn-sm" data-confirm="确定要删除数据吗?" data-action="{:url('remove')}" data-value="id#{$vo.id}" data-csrf="{:systoken('admin/auth/remove')}">删 除</a>
+                {/if}
+
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+
+</div>
+
+{/block}

+ 42 - 0
application/admin/view/auth/index_search.html

@@ -0,0 +1,42 @@
+<fieldset>
+    <legend>条件搜索</legend>
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">权限名称</label>
+            <div class="layui-input-inline">
+                <input name="title" value="{$Think.get.title|default=''}" placeholder="请输入权限名称" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">权限描述</label>
+            <div class="layui-input-inline">
+                <input name="desc" value="{$Think.get.desc|default=''}" placeholder="请输入权限描述" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">使用状态</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="status">
+                    {foreach [''=>'-- 全部状态 --','0'=>'已禁用的权限','1'=>'使用中的权限'] as $k=>$v}
+                    {eq name='Think.get.status' value='$k.""'}
+                    <option selected value="{$k}">{$v}</option>
+                    {else}
+                    <option value="{$k}">{$v}</option>
+                    {/eq}
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">创建时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="create_at" value="{$Think.get.create_at|default=''}" placeholder="请选择创建时间" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+    </form>
+</fieldset>
+
+<script>form.render()</script>

+ 80 - 0
application/admin/view/config/info.html

@@ -0,0 +1,80 @@
+{extend name="main"}
+
+{block name="button"}
+
+{if auth('config')}
+<a class="layui-btn layui-btn-sm layui-btn-primary" data-modal="{:url('config')}">修改系统配置</a>
+{/if}
+
+{/block}
+
+{block name="content"}
+
+<div class="think-box-shadow margin-bottom-15">
+    <span class="color-green font-w7 text-middle">文件存储引擎:</span>
+    {foreach ['local'=>'本地服务器存储','qiniu'=>'七牛云对象存储','oss'=>'阿里云OSS存储'] as $k=>$v}
+    {if sysconf('storage_type') eq $k}
+    {if auth('file')}<a data-modal="{:url('file')}?type={$k}" class="layui-btn layui-btn-sm">{$v}</a>{else}<a class="layui-btn layui-btn-sm">{$v}</a>{/if}
+    {elseif auth('file')}<a data-modal="{:url('file')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
+    {/foreach}
+</div>
+
+<div class="think-box-shadow padding-40">
+    <div class="layui-form-item">
+        <span class="color-green font-w7">网站名称 Website</span>
+        <label class="relative block">
+            <input readonly value="{:sysconf('site_name')}" class="layui-input layui-bg-gray">
+            <a data-copy="{:sysconf('site_name')}" class="fa fa-copy right-btn"></a>
+        </label>
+        <p class="help-block">网站名称及网站图标,将显示在浏览器的标签上</p>
+    </div>
+    <div class="layui-form-item">
+        <span class="color-green font-w7">管理程序名称 Name</span>
+        <label class="relative block">
+            <input readonly placeholder="请输入程序名称" value="{:sysconf('app_name')}" class="layui-input layui-bg-gray">
+            <a data-copy="{:sysconf('app_name')}" class="fa fa-copy right-btn"></a>
+        </label>
+        <p class="help-block">管理程序名称,将显示在后台左上角标题</p>
+    </div>
+    <div class="layui-form-item">
+        <span class="color-green font-w7">管理程序版本 Version</span>
+        <label class="relative block">
+            <input readonly value="{:sysconf('app_version')}" class="layui-input layui-bg-gray">
+            <a data-copy="{:sysconf('app_version')}" class="fa fa-copy right-btn"></a>
+        </label>
+        <p class="help-block">管理程序版本,将显示在后台左上角标题</p>
+    </div>
+
+    <div class="layui-form-item">
+        <span class="color-green font-w7">网站备案号 Miitbeian</span>
+        <label class="relative block">
+            <input readonly value="{:sysconf('miitbeian')}" class="layui-input layui-bg-gray">
+            <a data-copy="{:sysconf('miitbeian')}" class="fa fa-copy right-btn"></a>
+        </label>
+        <p class="help-block">网站备案号,可以在<a target="_blank" href="http://beian.miit.gov.cn">备案管理中心</a>查询获取,将显示在登录页面下面</p>
+    </div>
+    <div class="layui-form-item">
+        <span class="color-green font-w7">网站版权信息 Copyright</span>
+        <label class="relative block">
+            <input readonly value="{:sysconf('site_copy')}" class="layui-input layui-bg-gray">
+            <a data-copy="{:sysconf('site_copy')}" class="fa fa-copy right-btn"></a>
+        </label>
+        <p class="help-block">网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统</p>
+    </div>
+</div>
+{/block}
+
+{block name='style'}
+<style>
+    .right-btn {
+        top: 0;
+        right: 0;
+        width: 38px;
+        height: 38px;
+        display: inline-block;
+        position: absolute;
+        text-align: center;
+        line-height: 38px;
+    }
+</style>
+{/block}

+ 20 - 0
application/admin/view/config/storage-local.html

@@ -0,0 +1,20 @@
+<form onsubmit="return false;" data-auto="true" action="{:url()}" method="post" class='layui-form layui-card' autocomplete="off">
+    <div class="layui-card-body">
+        <div class="color-text margin-left-40 margin-bottom-20 layui-code" style="border-left-width:1px;background:none">
+            <p>文件将存储在本地服务器,需确保服务器的 public/upload 目录有写入权限,有足够的存储空间!</p>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">AllowExts<br><span class="nowrap color-desc">允许类型</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_local_exts" required value="{:sysconf('storage_local_exts')}" placeholder="请输入系统文件上传后缀" class="layui-input">
+                <p class="help-block">设置系统允许上传文件的后缀,多个以英文逗号隔开。如:png,jpg,rar,doc</p>
+            </div>
+        </div>
+        <div class="hr-line-dashed margin-left-40"></div>
+        <div class="layui-form-item text-center padding-left-40">
+            <input type="hidden" name="storage_type" value="local">
+            <button class="layui-btn" type="submit">保存配置</button>
+            <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
+        </div>
+    </div>
+</form>

+ 80 - 0
application/admin/view/config/storage-oss.html

@@ -0,0 +1,80 @@
+<form onsubmit="return false;" data-auto="true" action="{:url()}" method="post" class='layui-form layui-card' autocomplete="off">
+    <div class="layui-card-body">
+        <div class="color-text margin-left-40 margin-bottom-20 layui-code" style="border-left-width:1px;background:none">
+            <p class="margin-bottom-5 font-w7">文件将上传到阿里云OSS空间,需要配置OSS公开访问及跨域策略!</p>
+            目前已实现自动创建空间及配置访问策略!<a target="_blank" href="https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=08p1pddw">我要免费申请</a>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">AllowExts<br><span class="nowrap color-desc">允许类型</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_local_exts" required value="{:sysconf('storage_local_exts')}" placeholder="请输入系统文件上传后缀" class="layui-input">
+                <p class="help-block">设置系统允许上传文件的后缀,多个以英文逗号隔开。如:png,jpg,rar,doc</p>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">Protocol<br><span class="nowrap color-desc">访问协议</span></label>
+            <div class="layui-input-block">
+                {foreach ['http','https','auto'] as $pro}
+                <label class="think-radio">
+                    {if sysconf('storage_oss_is_https') eq $pro}
+                    <input checked type="radio" name="storage_oss_is_https" value="{$pro}" lay-ignore> {$pro}
+                    {else}
+                    <input type="radio" name="storage_oss_is_https" value="{$pro}" lay-ignore> {$pro}
+                    {/if}
+                </label>
+                {/foreach}
+                <p class="help-block">阿里云对象存储访问协议(http、https、auto),其中 https 需要配置证书才能使用,auto 为相对协议自动根据域名切换http与https。</p>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">Bucket<br><span class="nowrap color-desc">空间名称</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_oss_bucket" required value="{:sysconf('storage_oss_bucket')}" placeholder="请输入OSS Bucket (空间名称)" class="layui-input">
+                <p class="help-block">填写OSS存储空间名称,如:think-admin-oss(需要是全区唯一的值,不存在时会自动创建)</p>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">EndPoint<br><span class="nowrap color-desc">数据中心</span></label>
+            <div class="layui-input-block">
+                <select required name="storage_oss_endpoint" class="layui-select" lay-search>
+                    {foreach $ossPoints as $k=>$p}
+                    {if sysconf('storage_oss_endpoint') eq $k}
+                    <option selected value="{$k}">{$p} <span class="font-s10 color-desc">({$k})</span></option>
+                    {else}
+                    <option value="{$k}">{$p} <span class="font-s10 color-desc">({$k})</span></option>
+                    {/if}
+                    {/foreach}
+                </select>
+                <p class="help-block">请选择OSS数据中心访问节点,有效值如:oss-cn-shenzhen.aliyuncs.com</p>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">AccessKey<br><span class="nowrap color-desc">访问密钥</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_oss_keyid" required value="{:sysconf('storage_oss_keyid')}" maxlength="16" placeholder="请输入OSS AccessKey (访问密钥)" class="layui-input">
+                <p class="help-block">可以在 [ 阿里云 > 个人中心 ] 设置并获取到访问密钥。</p>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">SecretKey<br><span class="nowrap color-desc">安全密钥</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_oss_secret" required value="{:sysconf('storage_oss_secret')}" maxlength="30" placeholder="请输入OSS SecretKey (安全密钥)" class="layui-input">
+                <p class="help-block">可以在 [ 阿里云 > 个人中心 ] 设置并获取到安全密钥。</p>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">Domain<br><span class="nowrap color-desc">访问域名</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_oss_domain" value="{:sysconf('storage_oss_domain')}" placeholder="请输入OSS存储 Domain (访问域名)" class="layui-input">
+                <p class="help-block">填写OSS存储外部访问域名,如:think-admin-oss.oss-cn-shenzhen.aliyuncs.com(正常情况下是自动获取的)</p>
+            </div>
+        </div>
+        <div class="hr-line-dashed margin-left-40"></div>
+        <div class="layui-form-item text-center padding-left-40">
+            <input type="hidden" name="storage_type" value="oss">
+            <button class="layui-btn" type="submit">保存配置</button>
+            <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
+        </div>
+    </div>
+    <script>form.render()</script>
+</form>

+ 80 - 0
application/admin/view/config/storage-qiniu.html

@@ -0,0 +1,80 @@
+<form onsubmit="return false" data-auto="true" action="{:url()}" method="post" class='layui-form layui-card' autocomplete="off">
+    <div class="layui-card-body">
+        <div class="color-text margin-left-40 margin-bottom-20 layui-code" style="border-left-width:1px;background:none">
+            <p class="margin-bottom-5 font-w7">文件将上传到七牛云存储,对象存储需要配置为公开访问的Bucket空间!</p>
+            完成实名认证后可获得10G免费存储空间哦!<a target="_blank" href="https://portal.qiniu.com/signup?code=1hefnmobzees2">我要免费申请</a>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">AllowExts<br><span class="nowrap color-desc">允许类型</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_local_exts" required value="{:sysconf('storage_local_exts')}" placeholder="请输入系统文件上传后缀" class="layui-input">
+                <p class="help-block">设置系统允许上传文件的后缀,多个以英文逗号隔开。如:png,jpg,rar,doc</p>
+            </div>
+        </div>
+        <div class="layui-form-item" data-storage-type="qiniu">
+            <label class="layui-form-label label-required">Protocol<br><span class="nowrap color-desc">访问协议</span></label>
+            <div class="layui-input-block">
+                {foreach ['http','https','auto'] as $pro}
+                <label class="think-radio">
+                    {if sysconf('storage_qiniu_is_https') eq $pro}
+                    <input checked type="radio" name="storage_qiniu_is_https" value="{$pro}" lay-ignore> {$pro}
+                    {else}
+                    <input type="radio" name="storage_qiniu_is_https" value="{$pro}" lay-ignore> {$pro}
+                    {/if}
+                </label>
+                {/foreach}
+                <p class="help-block">七牛云存储访问协议,其中 https 需要配置证书才能使用,auto 为相对协议。</p>
+            </div>
+        </div>
+        <div class="layui-form-item" data-storage-type="qiniu">
+            <label class="layui-form-label">Region<br><span class="nowrap color-desc label-required">存储区域</span></label>
+            <div class="layui-input-block">
+                {foreach ['华东','华北','华南','北美'] as $area}
+                <label class="think-radio">
+                    {if sysconf('storage_qiniu_region') eq $area}
+                    <input checked type="radio" name="storage_qiniu_region" value="{$area}" lay-ignore>
+                    {else}
+                    <input type="radio" name="storage_qiniu_region" value="{$area}" lay-ignore>
+                    {/if}
+                    {$area}
+                </label>
+                {/foreach}
+                <p class="help-block">七牛云存储空间所在区域,需要严格对应储存所在区域才能上传文件。</p>
+            </div>
+        </div>
+        <div class="layui-form-item" data-storage-type="qiniu">
+            <label class="layui-form-label">Bucket<br><span class="nowrap color-desc">空间名称</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_qiniu_bucket" required value="{:sysconf('storage_qiniu_bucket')}" placeholder="请输入七牛云存储 Bucket (空间名称)" class="layui-input">
+                <p class="help-block">填写七牛云存储空间名称,如:static</p>
+            </div>
+        </div>
+        <div class="layui-form-item" data-storage-type="qiniu">
+            <label class="layui-form-label">Domain<br><span class="nowrap color-desc">访问域名</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_qiniu_domain" required value="{:sysconf('storage_qiniu_domain')}" placeholder="请输入七牛云存储 Domain (访问域名)" class="layui-input">
+                <p class="help-block">填写七牛云存储访问域名,如:static.ctolog.cc</p>
+            </div>
+        </div>
+        <div class="layui-form-item" data-storage-type="qiniu">
+            <label class="layui-form-label">AccessKey<br><span class="nowrap color-desc">访问密钥</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_qiniu_access_key" required value="{:sysconf('storage_qiniu_access_key')}" placeholder="请输入七牛云 AccessKey (访问密钥)" class="layui-input">
+                <p class="help-block">可以在 [ 七牛云 > 个人中心 ] 设置并获取到访问密钥。</p>
+            </div>
+        </div>
+        <div class="layui-form-item" data-storage-type="qiniu">
+            <label class="layui-form-label">SecretKey<br><span class="nowrap color-desc">安全密钥</span></label>
+            <div class="layui-input-block">
+                <input type="text" name="storage_qiniu_secret_key" required value="{:sysconf('storage_qiniu_secret_key')}" maxlength="43" placeholder="请输入七牛云 SecretKey (安全密钥)" class="layui-input">
+                <p class="help-block">可以在 [ 七牛云 > 个人中心 ] 设置并获取到安全密钥。</p>
+            </div>
+        </div>
+        <div class="hr-line-dashed margin-left-40"></div>
+        <div class="layui-form-item text-center padding-left-40">
+            <input type="hidden" name="storage_type" value="qiniu">
+            <button class="layui-btn" type="submit">保存配置</button>
+            <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
+        </div>
+    </div>
+</form>

+ 57 - 0
application/admin/view/config/system-config.html

@@ -0,0 +1,57 @@
+<form onsubmit="return false" data-auto="true" action="{:url()}" method="post" class='layui-form layui-card' autocomplete="off">
+    <div class="layui-card-body padding-left-40">
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">网站名称 Website</span>
+                <input name="site_name" required placeholder="请输入网站名称" value="{:sysconf('site_name')}" class="layui-input">
+            </label>
+            <p class="help-block">网站名称,将在浏览器的标签上显示名称</p>
+        </div>
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">管理程序名称 Name</span>
+                <input name="app_name" required placeholder="请输入程序名称" value="{:sysconf('app_name')}" class="layui-input">
+            </label>
+            <p class="help-block">管理程序名称,在后台左上角标题处理显示</p>
+        </div>
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">管理程序版本 Version</span>
+                <input name="app_version" placeholder="请输入程序版本" value="{:sysconf('app_version')}" class="layui-input">
+            </label>
+            <p class="help-block">管理程序版本,在后台左上角标题处理显示</p>
+        </div>
+        <div class="layui-form-item">
+            <span class="color-green font-w7">浏览器图标 Browser</span>
+            <div class="relative block">
+                <img alt="icon" data-tips-image style="height:auto;max-height:42px;min-width:42px" src="{:sysconf('site_icon')}"/>
+                <input type="hidden" name="site_icon" onchange="$(this).prev('img').attr('src', this.value)" value="{:sysconf('site_icon')}" class="layui-input">
+                <a class="margin-left-10" data-file="btn" data-type="ico,png" data-field="site_icon">上传图片</a>
+                <p class="help-block">建议上传 ICO 图标的尺寸为 128x128 <a href="http://www.favicon-icon-generator.com" target="_blank">ICON在线制作</a></p>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">网站备案号 Miitbeian</span>
+                <input name="miitbeian" required placeholder="请输入网站备案号" value="{:sysconf('miitbeian')}" class="layui-input">
+            </label>
+            <p class="help-block">网站备案号,可以在<a target="_blank" href="http://beian.miit.gov.cn">备案管理中心</a>查询获取,将在登录页面下面显示</p>
+        </div>
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">网站版权信息 Copyright</span>
+                <input name="site_copy" required placeholder="请输入版权信息" value="{:sysconf('site_copy')}" class="layui-input">
+            </label>
+            <p class="help-block">网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统</p>
+        </div>
+
+        <div class="hr-line-dashed"></div>
+
+        <div class="layui-form-item text-center">
+            <button class="layui-btn" type="submit">保存配置</button>
+            <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
+        </div>
+    </div>
+
+</form>

+ 128 - 0
application/admin/view/index/index.html

@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html lang="zh">
+
+<head>
+    <title>{block name="title"}{$title|default=''}{if !empty($title)} · {/if}{:sysconf('site_name')}{/block}</title>
+    <meta charset="utf-8">
+    <meta name="renderer" content="webkit">
+    <meta name="format-detection" content="telephone=no">
+    <meta name="apple-mobile-web-app-capable" content="yes">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="apple-mobile-web-app-status-bar-style" content="black">
+    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=0.4">
+    <link rel="shortcut icon" href="{:sysconf('site_icon')}">
+    <link rel="stylesheet" href="__ROOT__/static/plugs/awesome/fonts.css?at={:date('md')}">
+    <link rel="stylesheet" href="__ROOT__/static/plugs/layui/css/layui.css?at={:date('md')}">
+    <link rel="stylesheet" href="__ROOT__/static/theme/css/console.css?at={:date('md')}">
+    {block name="style"}{/block}
+    <script>window.ROOT_URL = '__ROOT__';</script>
+    <script src="__ROOT__/static/plugs/jquery/pace.min.js"></script>
+</head>
+
+<body class="layui-layout-body">
+{block name='body'}
+<div class="layui-layout layui-layout-admin layui-layout-left-hide">
+
+    <!-- 顶部菜单 开始 -->
+    <div class="layui-header notselect">
+        <a href="{:url('@')}" class="layui-logo layui-elip">
+            {:sysconf('app_name')} {if sysconf('app_version')}<sup class="padding-left-5">{:sysconf('app_version')}</sup>{/if}
+        </a>
+        <ul class="layui-nav layui-layout-left">
+            <li class="layui-nav-item" lay-unselect>
+                <a class="text-center" data-target-menu-type>
+                    <i class="layui-icon layui-icon-spread-left"></i>
+                </a>
+            </li>
+            {foreach $menus as $oneMenu}
+            <li class="layui-nav-item">
+                <a data-menu-node="m-{$oneMenu.id}" data-open="{$oneMenu.url}">
+                    {notempty name='oneMenu.icon'}<span class='{$oneMenu.icon} padding-right-5'></span>{/notempty}
+                    <span>{$oneMenu.title|default=''}</span>
+                </a>
+            </li>
+            {/foreach}
+        </ul>
+        <ul class="layui-nav layui-layout-right">
+            <li lay-unselect class="layui-nav-item"><a data-reload><i class="layui-icon layui-icon-refresh-3"></i></a></li>
+            {notempty name='Think.session.user.username'}
+            <li class="layui-nav-item">
+                <dl class="layui-nav-child">
+                    <dd lay-unselect><a data-modal="{:url('admin/index/info',['id'=>session('user.id')])}"><i class="layui-icon layui-icon-set-fill margin-right-5"></i>基本资料</a></dd>
+                    <dd lay-unselect><a data-modal="{:url('admin/index/pass',['id'=>session('user.id')])}"><i class="layui-icon layui-icon-component margin-right-5"></i>安全设置</a></dd>
+                    {if auth('admin/index/buildoptimize')}
+                    <dd lay-unselect><a data-modal="{:url('admin/index/buildOptimize')}"><i class="layui-icon layui-icon-template-1 margin-right-5"></i>压缩发布</a></dd>
+                    {/if}
+                    {if auth('admin/index/clearruntime')}
+                    <dd lay-unselect><a data-modal="{:url('admin/index/clearRuntime')}"><i class="layui-icon layui-icon-fonts-clear margin-right-5"></i>清理缓存</a></dd>
+                    {/if}
+                    {notempty name='GLOBALS.AdminUserRightOption'}{foreach $GLOBALS.AdminUserRightOption as $option}{if auth($option.node)}
+                    <dd lay-unselect><a data-{$option.type}="{$option.action}"><i class="{$option.icon} margin-right-5"></i>{$option.title}</a></dd>
+                    {/if}{/foreach}{/notempty}
+                    <dd lay-unselect><a data-confirm="确定要退出登录吗?" data-load="{:url('admin/login/out')}"><i class="layui-icon layui-icon-release margin-right-5"></i>退出登录</a></dd>
+                </dl>
+                <a><span><i class="layui-icon layui-icon-username margin-right-5"></i> {:session('user.username')}</span></a>
+            </li>
+            {else}
+            <li class="layui-nav-item">
+                <a data-href="{:url('@admin/login')}"><i class="layui-icon layui-icon-username"></i> 立即登录</a>
+            </li>
+            {/notempty}
+        </ul>
+    </div>
+    <!-- 顶部菜单 结束 -->
+
+    <!-- 左则菜单 开始 -->
+    <div class="layui-side layui-bg-black notselect">
+        <div class="layui-side-scroll">
+
+            {foreach $menus as $oneMenu}
+            {notempty name='oneMenu.sub'}
+            <ul class="layui-nav layui-nav-tree layui-hide" data-menu-layout="m-{$oneMenu.id}">
+                {foreach $oneMenu.sub as $twoMenu}
+                {empty name='twoMenu.sub'}
+                <li class="layui-nav-item">
+                    <a data-target-tips="{$twoMenu.title}" data-menu-node="m-{$oneMenu.id}-{$twoMenu.id}" data-open="{$twoMenu.url}">
+                        <span class='{$twoMenu.icon|default="layui-icon layui-icon-link"}'></span>
+                        <span class="nav-text padding-left-5">{$twoMenu.title}</span>
+                    </a>
+                </li>
+                {else}
+                <li class="layui-nav-item" data-submenu-layout='m-{$oneMenu.id}-{$twoMenu.id}'>
+                    <a data-target-tips="{$twoMenu.title}" style="background:#393D49">
+                        <span class='nav-icon layui-hide {$twoMenu.icon|default="layui-icon layui-icon-triangle-d"}'></span>
+                        <span class="nav-text padding-left-5">{$twoMenu.title}</span>
+                    </a>
+                    <dl class="layui-nav-child">
+                        {foreach $twoMenu.sub as $thrMenu}
+                        <dd>
+                            <a data-target-tips="{$thrMenu.title}" data-open="{$thrMenu.url}" data-menu-node="m-{$oneMenu.id}-{$twoMenu.id}-{$thrMenu.id}">
+                                <span class='nav-icon padding-left-5 {$thrMenu.icon|default="layui-icon layui-icon-link"}'></span>
+                                <span class="nav-text padding-left-5">{$thrMenu.title}</span>
+                            </a>
+                        </dd>
+                        {/foreach}
+                    </dl>
+                </li>
+                {/empty}
+                {/foreach}
+            </ul>
+            {/notempty}
+            {/foreach}
+        </div>
+    </div>
+    <!-- 左则菜单 结束 -->
+
+    <!-- 主体内容 开始 -->
+    <div class="layui-body layui-bg-gray">{block name='content'}{/block}</div>
+    <!-- 主体内容 结束 -->
+
+</div>
+{/block}
+<script src="__ROOT__/static/plugs/layui/layui.all.js"></script>
+<script src="__ROOT__/static/plugs/require/require.js"></script>
+<script src="__ROOT__/static/admin.js"></script>
+{block name='script'}{/block}
+</body>
+
+</html>

+ 207 - 0
application/admin/view/index/main.html

@@ -0,0 +1,207 @@
+{extend name='main'}
+
+{block name='content'}
+
+<style>
+
+    .store-total-container {
+        font-size: 14px;
+        margin-bottom: 20px;
+        letter-spacing: 1px;
+    }
+
+    .store-total-container .store-total-icon {
+        top: 45%;
+        right: 8%;
+        font-size: 65px;
+        position: absolute;
+        color: rgba(255, 255, 255, 0.4);
+    }
+
+    .store-total-container .store-total-item {
+        color: #fff;
+        line-height: 4em;
+        padding: 15px 25px;
+        position: relative;
+    }
+
+    .store-total-container .store-total-item > div:nth-child(2) {
+        font-size: 46px;
+        line-height: 46px;
+    }
+
+</style>
+
+<div class="think-box-shadow store-total-container notselect">
+    <div class="margin-bottom-15">商城统计</div>
+    <div class="layui-row layui-col-space15">
+        <div class="layui-col-sm6 layui-col-md3">
+            <div class="store-total-item nowrap" style="background:linear-gradient(-125deg,#57bdbf,#2f9de2)">
+                <div>商品总量</div>
+                <div>63</div>
+                <div>当前商品总数量</div>
+            </div>
+            <i class="store-total-icon layui-icon layui-icon-template-1"></i>
+        </div>
+        <div class="layui-col-sm6 layui-col-md3">
+            <div class="store-total-item nowrap" style="background:linear-gradient(-125deg,#ff7d7d,#fb2c95)">
+                <div>用户总量</div>
+                <div>1,634</div>
+                <div>当前用户总数量</div>
+            </div>
+            <i class="store-total-icon layui-icon layui-icon-user"></i>
+        </div>
+        <div class="layui-col-sm6 layui-col-md3">
+            <div class="store-total-item nowrap" style="background:linear-gradient(-113deg,#c543d8,#925cc3)">
+                <div>订单总量</div>
+                <div>148</div>
+                <div>已付款订单总数量</div>
+            </div>
+            <i class="store-total-icon layui-icon layui-icon-read"></i>
+        </div>
+        <div class="layui-col-sm6 layui-col-md3">
+            <div class="store-total-item nowrap" style="background:linear-gradient(-141deg,#ecca1b,#f39526)">
+                <div>评价总量</div>
+                <div>0</div>
+                <div>订单评价总数量</div>
+            </div>
+            <i class="store-total-icon layui-icon layui-icon-survey"></i>
+        </div>
+    </div>
+</div>
+
+<div class="think-box-shadow store-total-container">
+    <div class="margin-bottom-15">实时概况</div>
+    <div class="layui-row layui-col-space15">
+        <div class="layui-col-md6 margin-bottom-15">
+            <div class="layui-row">
+                <div class="layui-col-xs3 text-center">
+                    <i class="layui-icon color-blue" style="font-size:60px;line-height:72px">&#xe65e;</i>
+                </div>
+                <div class="layui-col-xs4">
+                    <div class="font-s14">销售额(元)</div>
+                    <div class="font-s16">1351.00</div>
+                    <div class="font-s12 color-desc">昨日:974.00</div>
+                </div>
+                <div class="layui-col-xs5">
+                    <div class="font-s14">支付订单数</div>
+                    <div class="font-s16">106</div>
+                    <div class="font-s12 color-desc">昨日:76</div>
+                </div>
+            </div>
+        </div>
+        <div class="layui-col-md6 margin-bottom-15">
+            <div class="layui-row">
+                <div class="layui-col-xs3 text-center">
+                    <i class="layui-icon color-blue" style="font-size:60px;line-height:72px">&#xe663;</i>
+                </div>
+                <div class="layui-col-xs4">
+                    <div class="font-s14">新增用户数</div>
+                    <div class="font-s16">327</div>
+                    <div class="font-s12 color-desc">昨日:238</div>
+                </div>
+                <div class="layui-col-xs5">
+                    <div class="font-s14">下单用户数</div>
+                    <div class="font-s16">69</div>
+                    <div class="font-s12 color-desc">昨日:34</div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="layui-row layui-col-space15">
+    <div class="layui-col-md6">
+        <div class="think-box-shadow">
+            <table class="layui-table" lay-skin="line" lay-even>
+                <caption class="text-left margin-bottom-15 font-s14">系统信息</caption>
+                <colgroup>
+                    <col width="30%">
+                </colgroup>
+                <tbody>
+                <tr>
+                    <td>当前程序版本</td>
+                    <td>{:sysconf('app_version')}</td>
+                </tr>
+                <tr>
+                    <td>运行PHP版本</td>
+                    <td>{$Think.PHP_VERSION}</td>
+                </tr>
+                <tr>
+                    <td>ThinkPHP版本</td>
+                    <td>{$think_ver}</td>
+                </tr>
+                <tr>
+                    <td>MySQL数据库版本</td>
+                    <td>{$mysql_ver}</td>
+                </tr>
+                <tr>
+                    <td>服务器操作系统</td>
+                    <td>{:php_uname('s')}</td>
+                </tr>
+                <tr>
+                    <td>WEB运行环境</td>
+                    <td>{:php_sapi_name()}</td>
+                </tr>
+                <tr>
+                    <td>上传大小限制</td>
+                    <td>{:ini_get('upload_max_filesize')}</td>
+                </tr>
+                <tr>
+                    <td>POST大小限制</td>
+                    <td>{:ini_get('post_max_size')}</td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+    </div>
+    <div class="layui-col-md6">
+        <div class="think-box-shadow">
+            <table class="layui-table" lay-skin="line" lay-even>
+                <caption class="text-left margin-bottom-15 font-s14">产品团队</caption>
+                <colgroup>
+                    <col width="30%">
+                </colgroup>
+                <tbody>
+                <tr>
+                    <td>产品名称</td>
+                    <td>ThinkAdmin</td>
+                </tr>
+                <tr>
+                    <td>在线体验</td>
+                    <td><a target="_blank" href="http://demo.thinkadmin.top">http://demo.thinkadmin.top</a></td>
+                </tr>
+                <tr>
+                    <td>官方QQ群</td>
+                    <td>
+                        <a href="http://shang.qq.com/wpa/qunwpa?idkey=ae25cf789dafbef62e50a980ffc31242f150bc61a61164458216dd98c411832a">
+                            <img src="//pub.idqqimg.com/wpa/images/group.png" style="height:18px;width:auto" target="_blank">
+                        </a>
+                    </td>
+                </tr>
+                <tr>
+                    <td>项目地址</td>
+                    <td><a target="_blank" href="https://github.com/zoujingli/ThinkAdmin">https://github.com/zoujingli/ThinkAdmin</a></td>
+                </tr>
+                <tr>
+                    <td>BUG反馈</td>
+                    <td><a target="_blank" href="https://github.com/zoujingli/ThinkAdmin/issues">https://github.com/zoujingli/ThinkAdmin/issues</a></td>
+                </tr>
+                <tr>
+                    <td>开发团队</td>
+                    <td><a href="http://www.cuci.cc" target="_blank">广州楚才信息科技有限公司</a></td>
+                </tr>
+                <tr>
+                    <td>团队官网</td>
+                    <td><a target="_blank" href="http://www.cuci.cc">http://www.cuci.cc</a></td>
+                </tr>
+                <tr>
+                    <td>办公地址</td>
+                    <td>广州市天河区东圃一横路东泷商贸中心G02</td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+    </div>
+</div>
+{/block}

+ 57 - 0
application/admin/view/login/index.html

@@ -0,0 +1,57 @@
+{extend name="index/index"}
+
+{block name="body"}
+<div class="login-container" data-supersized="__ROOT__/static/theme/img/login/bg1.jpg,__ROOT__/static/theme/img/login/bg2.jpg">
+    <div class="header notselect layui-hide-xs">
+        <a href="{:url('@')}" class="title">{:sysconf('app_name')}<span class="padding-left-5 font-s10">{:sysconf('app_version')}</span></a>
+        {notempty name='devmode'}
+        <a class="pull-right layui-anim layui-anim-fadein" href='https://gitee.com/zoujingli/ThinkAdmin'>
+            <img src='https://gitee.com/zoujingli/ThinkAdmin/widgets/widget_1.svg' alt='Fork me on Gitee'>
+        </a>
+        {/notempty}
+    </div>
+    <form data-login-form onsubmit="return false" method="post" class="layui-anim layui-anim-upbit" autocomplete="off">
+        <h2 class="notselect">系统管理</h2>
+        <ul>
+            <li class="username">
+                <label>
+                    <i class="layui-icon layui-icon-username"></i>
+                    <input class="layui-input" required pattern="^\S{4,}$" name="username" autofocus autocomplete="off" placeholder="登录账号" title="请输入登录账号">
+                </label>
+            </li>
+            <li class="password">
+                <label>
+                    <i class="layui-icon layui-icon-password"></i>
+                    <input class="layui-input" required pattern="^\S{4,}$" name="password" maxlength="32" type="password" autocomplete="off" placeholder="登录密码" title="请输入登录密码">
+                </label>
+            </li>
+            <li class="verify layui-hide">
+                <label class="inline-block relative">
+                    <i class="layui-icon layui-icon-picture-fine"></i>
+                    <input class="layui-input" required pattern="^\S{4,}$" name="verify" maxlength="4" autocomplete="off" placeholder="验证码" title="请输入验证码">
+                </label>
+                <label data-captcha="{:url('admin/login/captcha',[],false)}" data-field-verify="verify" data-field-uniqid="uniqid" data-captcha-type="{$captcha_type}" data-captcha-token="{$captcha_token}"></label>
+            </li>
+            <li class="text-center padding-top-20">
+                <button type="submit" class="layui-btn layui-disabled full-width" data-form-loaded="立即登入">正在载入</button>
+            </li>
+        </ul>
+    </form>
+    <div class="footer notselect">
+        <p class="layui-hide-xs"><a target="_blank" href="https://www.google.cn/chrome">推荐使用谷歌浏览器</a></p>
+        {:sysconf('site_copy')}
+        {if sysconf('miitbeian')}<span class="padding-5">|</span><a target="_blank" href="http://beian.miit.gov.cn">{:sysconf('miitbeian')}</a>{/if}
+    </div>
+</div>
+{/block}
+
+{block name='style'}
+<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
+<script>if (location.href.indexOf('#') > -1) location.replace(location.href.split('#')[0])</script>
+<link rel="stylesheet" href="__ROOT__/static/theme/css/login.css">
+{/block}
+
+{block name='script'}
+<script src="__ROOT__/static/login.js"></script>
+<script src="__ROOT__/static/plugs/supersized/supersized.3.2.7.min.js"></script>
+{/block}

+ 11 - 0
application/admin/view/main.html

@@ -0,0 +1,11 @@
+<div class="layui-card layui-bg-gray">
+    {block name='style'}{/block}
+    {notempty name='title'}
+    <div class="layui-card-header layui-anim layui-anim-fadein notselect">
+        <span class="layui-icon layui-icon-next font-s10 color-desc margin-right-5"></span>{$title|default=''}
+        <div class="pull-right">{block name='button'}{/block}</div>
+    </div>
+    {/notempty}
+    <div class="layui-card-body layui-anim layui-anim-upbit">{block name='content'}{/block}</div>
+    {block name='script'}{/block}
+</div>

+ 89 - 0
application/admin/view/menu/form.html

@@ -0,0 +1,89 @@
+<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post" autocomplete="off">
+
+    <div class="layui-card-body">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">上级菜单</label>
+            <div class="layui-input-block">
+                <select name='pid' class='layui-select' lay-search>
+                    <!--{foreach $menus as $menu}-->
+                    <!--{eq name='menu.id' value='$vo.pid|default=0'}-->
+                    <option selected value='{$menu.id}'>{$menu.spl|raw}{$menu.title}</option>
+                    <!--{else}-->
+                    <option value='{$menu.id}'>{$menu.spl|raw}{$menu.title}</option>
+                    <!--{/eq}-->
+                    <!--{/foreach}-->
+                </select>
+                <p class="help-block">必选,请选择上级菜单或顶级菜单(目前最多支持三级菜单)</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">菜单名称</label>
+            <div class="layui-input-block">
+                <input name="title" value='{$vo.title|default=""}' required placeholder="请输入菜单名称" class="layui-input">
+                <p class="help-block">必填,请填写菜单名称(如:系统管理),建议字符不要太长,一般4-6个汉字</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">菜单链接</label>
+            <div class="layui-input-block">
+                <input onblur="this.value=this.value === ''?'#':this.value" name="url" required placeholder="请输入菜单链接" value="{$vo.url|default='#'}" class="layui-input">
+                <p class="help-block">
+                    必填,请填写系统节点(如:admin/user/index),节点加入权限管理时菜单才会自动隐藏,非规则内的不会隐藏;
+                    <br>正常情况下,在输入的时候会有自动提示。如果是上级菜单时,请填写"#"符号,不要填写地址或节点地址
+                </p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">链接参数</label>
+            <div class="layui-input-block">
+                <input name="params" placeholder="请输入链接参数" value="{$vo.params|default=''}" class="layui-input">
+                <p class="help-block">可选,设置菜单链接的GET访问参数(如:name=1&age=3)</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">菜单图标</label>
+            <div class="layui-input-block">
+                <div class="layui-input-inline">
+                    <input placeholder="请输入或选择图标" name="icon" value='{$vo.icon|default=""}' class="layui-input">
+                </div>
+                <span style="padding:0 12px;min-width:45px" class='layui-btn layui-btn-primary'>
+                    <i style="font-size:1.2em;margin:0" class='{$vo.icon|default=""}'></i>
+                </span>
+                <button data-icon='icon' type='button' class='layui-btn layui-btn-primary'>选择图标</button>
+                <p class="help-block">可选,设置菜单选项前置图标,目前只支持 Font Awesome 5.2.0 字体图标</p>
+            </div>
+        </div>
+
+    </div>
+
+    <div class="hr-line-dashed"></div>
+    {notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
+
+    <div class="layui-form-item text-center">
+        <button class="layui-btn" type='submit'>保存数据</button>
+        <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
+    </div>
+
+</form>
+
+{block name='script'}
+<script>
+    require(['jquery.autocompleter'], function () {
+        form.render();
+        $('[name="icon"]').on('change', function () {
+            $(this).parent().next().find('i').get(0).className = this.value
+        });
+        $('input[name=url]').autocompleter({
+            limit: 6, highlightMatches: true, template: '{{ label }} <span> {{ title }} </span>', source: (function (subjects, data) {
+                for (var i in subjects) data.push({value: subjects[i].node, label: subjects[i].node, title: subjects[i].title});
+                return data;
+            })(JSON.parse('{$nodes|raw|json_encode}'), [])
+        });
+    });
+</script>
+{/block}

+ 80 - 0
application/admin/view/menu/index.html

@@ -0,0 +1,80 @@
+{extend name='main'}
+
+{block name="button"}
+
+{if auth("add")}
+<button data-modal='{:url("add")}' data-title="添加菜单" class='layui-btn layui-btn-sm layui-btn-primary'>添加菜单</button>
+{/if}
+
+{if auth("remove")}
+<button data-action='{:url("remove")}' data-csrf="{:systoken('remove')}" data-rule="id#{key}" class='layui-btn layui-btn-sm layui-btn-primary'>删除菜单</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+<div class="think-box-shadow">
+    {empty name='list'}
+    <blockquote class="layui-elem-quote">没 有 记 录 哦!</blockquote>
+    {else}
+    <table class="layui-table" lay-skin="line">
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'>
+                <input data-auto-none data-check-target='.list-check-box' type='checkbox'>
+            </th>
+            <th class='list-table-sort-td'>
+                <button type="button" data-reload class="layui-btn layui-btn-xs">刷 新</button>
+            </th>
+            <th class='text-center' style="width:30px"></th>
+            <th style="width:180px"></th>
+            <th class='layui-hide-xs' style="width:180px"></th>
+            <th colspan="2"></th>
+        </tr>
+        </thead>
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr data-dbclick>
+            <td class='list-table-check-td think-checkbox'>
+                <input class="list-check-box" value='{$vo.ids}' type='checkbox'>
+            </td>
+            <td class='list-table-sort-td'>
+                <input data-action-blur="{:request()->url()}" data-value="id#{$vo.id};action#sort;sort#{value}" data-loading="false" value="{$vo.sort}" class="list-sort-input">
+            </td>
+            <td class='text-center'><i class="{$vo.icon} font-s18"></i></td>
+            <td class="nowrap"><span class="color-desc">{$vo.spl|raw}</span>{$vo.title}</td>
+            <td class='layui-hide-xs'>{$vo.url}</td>
+            <td class='text-center nowrap'>{eq name='vo.status' value='0'}<span class="color-red">已禁用</span>{else}<span class="color-green">使用中</span>{/eq}</td>
+            <td class='text-center nowrap notselect'>
+
+                {if auth("add")}
+                <span class="text-explode">|</span>
+                <!--{if $vo.spt < 2}-->
+                <a class="layui-btn layui-btn-xs layui-btn-primary" data-title="添加子菜单" data-modal='{:url("add")}?pid={$vo.id}'>添 加</a>
+                <!--{else}-->
+                <a class="layui-btn layui-btn-xs layui-btn-disabled">添 加</a>
+                <!--{/if}-->
+                {/if}
+
+                {if auth("edit")}
+                <a data-dbclick class="layui-btn layui-btn-xs" data-title="编辑菜单" data-modal='{:url("edit")}?id={$vo.id}'>编 辑</a>
+                {/if}
+
+                {if $vo.status eq 1 and auth("forbid")}
+                <a class="layui-btn layui-btn-warm layui-btn-xs" data-confirm="确定要禁用菜单吗?" data-action="{:url('forbid')}" data-value="id#{$vo.ids};status#0" data-csrf="{:systoken('forbid')}">禁 用</a>
+                {elseif auth("resume")}
+                <a class="layui-btn layui-btn-warm layui-btn-xs" data-action="{:url('resume')}" data-value="id#{$vo.ids};status#1" data-csrf="{:systoken('resume')}">启 用</a>
+                {/if}
+
+                {if auth("remove")}
+                <a class="layui-btn layui-btn-danger layui-btn-xs" data-confirm="确定要删除数据吗?" data-action="{:url('remove')}" data-value="id#{$vo.ids}" data-csrf="{:systoken('remove')}">删 除</a>
+                {/if}
+
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+    {/empty}
+</div>
+{/block}

+ 68 - 0
application/admin/view/oplog/index.html

@@ -0,0 +1,68 @@
+{extend name='main'}
+
+{block name="button"}
+
+{if auth("clear")}
+<button data-load='{:url("clear")}' data-confirm="确定要消除所有日志吗?" class='layui-btn layui-btn-sm layui-btn-primary'>清理日志</button>
+{/if}
+
+{if auth("remove")}
+<button data-action='{:url("remove")}' data-rule="id#{key}" data-csrf="{:systoken('remove')}" data-confirm="确定要删除日志吗?" class='layui-btn layui-btn-sm layui-btn-primary'>删除日志</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+<div class="think-box-shadow">
+    {include file='oplog/index_search'}
+    <table class="layui-table margin-top-10" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'>
+                <input data-auto-none data-check-target='.list-check-box' type='checkbox'>
+            </th>
+            <th class='text-left nowrap'>操作权限</th>
+            <th class='text-left nowrap'>操作行为</th>
+            <th class='text-left nowrap'>地理位置</th>
+            <th class='text-left nowrap'>操作时间</th>
+            <th></th>
+        </tr>
+        </thead>
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr data-dbclick>
+            <td class='list-table-check-td think-checkbox'>
+                <input class="list-check-box" value='{$vo.id}' type='checkbox'>
+            </td>
+            <td class="text-left nowrap">
+                <span class="font-w7">{$vo.username|default='-'}</span><br>
+                <span class="color-desc">{$vo.node|default='-'}</span>
+            </td>
+            <td class='text-left nowrap'>
+                <p class="color-text">{$vo.action|default='-'}</p>
+                <p class="color-desc">{$vo.content|default='-'}</p>
+            </td>
+            <td class='text-left nowrap'>
+                <p class="color-text">{$vo.geoip|default='-'}</p>
+                <p class="color-desc">{$vo.isp|default='-'}</p>
+            </td>
+            <td class='text-left nowrap'>
+                日期:{$vo.create_at|format_datetime|str_replace=' ','<br><span class="color-desc">时间:',###|raw}</span>
+            </td>
+            <td class='text-left nowrap'>
+                {if auth("remove")}
+                <a data-dbclick class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该日志吗?" data-action="{:url('remove')}" data-value="id#{$vo.id}" data-csrf="{:systoken('remove')}">删 除</a>
+                {/if}
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+        {/notempty}
+    </table>
+
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+
+</div>
+
+{/block}

+ 56 - 0
application/admin/view/oplog/index_search.html

@@ -0,0 +1,56 @@
+<fieldset>
+
+    <legend>条件搜索</legend>
+
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">操作账号</label>
+            <div class="layui-input-inline">
+                <input name="username" value="{$Think.get.username|default=''}" placeholder="请输入操作账号" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">操作节点</label>
+            <div class="layui-input-inline">
+                <input name="node" value="{$Think.get.node|default=''}" placeholder="请输入操作节点" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">操作行为</label>
+            <div class="layui-input-inline">
+                <input name="action" value="{$Think.get.action|default=''}" placeholder="请输入操作行为" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">操作描述</label>
+            <div class="layui-input-inline">
+                <input name="content" value="{$Think.get.content|default=''}" placeholder="请输入操作内容" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">位置地址</label>
+            <div class="layui-input-inline">
+                <input name="geoip" value="{$Think.get.geoip|default=''}" placeholder="请输入位置地址" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">操作时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="create_at" value="{$Think.get.create_at|default=''}" placeholder="请选择操作时间" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+    </form>
+
+</fieldset>
+
+<script>form.render()</script>

+ 94 - 0
application/admin/view/queue/index.html

@@ -0,0 +1,94 @@
+{extend name='main'}
+
+{block name="button"}
+
+{if auth("start") && $iswin}
+<button data-load='{:url("start")}' class='layui-btn layui-btn-sm layui-btn-primary'>开始监听</button>
+{/if}
+
+{if auth("stop") && $iswin}
+<button data-load='{:url("stop")}' data-confirm="确定要停止主进程监听吗?" class='layui-btn layui-btn-sm layui-btn-primary'>停止监听</button>
+{/if}
+
+{if auth("remove")}
+<button data-action='{:url("remove")}' data-rule="id#{key}" data-confirm="确定批量删除任务吗?" class='layui-btn layui-btn-sm layui-btn-primary'>批量删除</button>
+{/if}
+
+{if auth("clear")}
+<button data-load='{:url("clear")}' data-confirm="确定要清理3天前的任务记录吗?" class='layui-btn layui-btn-sm layui-btn-primary'>清理记录</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+
+<div class="think-box-shadow">
+    {include file='queue/index_search'}
+    <table class="layui-table margin-top-15" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'>
+                <input data-auto-none data-check-target='.list-check-box' type='checkbox'>
+            </th>
+            <th class='text-left nowrap'>任务信息</th>
+            <th class='text-left nowrap'>任务时间</th>
+            <th class='text-left nowrap'>任务状态</th>
+        </tr>
+        </thead>
+        {/notempty}
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr>
+            <td class='list-table-check-td think-checkbox'>
+                <input class="list-check-box" value='{$vo.id}' type='checkbox'>
+            </td>
+            <td class='text-left nowrap'>
+                任务名称:{$vo.title}<br>
+                <span class="color-desc">任务指令:{$vo.preload}</span>
+            </td>
+            <td class='text-left nowrap'>
+                计划时间:{$vo.time|format_datetime} {if isset($vo.exec_pid) and $vo.exec_pid>0}( 进程 <b class="color-blue">{$vo.exec_pid|default='-'}</b> ){/if}<br>
+                {if $vo.status eq 3 or $vo.status eq 4}
+                执行时间:{$vo.start_at|format_datetime}( 耗时 <b class="color-blue">{:sprintf("%.4f",strtotime($vo.end_at)-strtotime($vo.start_at))}</b> 秒 )
+                {elseif $vo.status eq 2} 开始时间:{$vo.start_at|format_datetime}
+                {else} 创建时间:{$vo.create_at|format_datetime} {/if}
+            </td>
+            <td class='text-left nowrap'>
+                <div>
+                    {eq name='vo.double' value='1'}
+                    <span class="layui-badge layui-bg-green margin-right-5">复</span>
+                    {else}
+                    <span class="layui-badge layui-bg-blue margin-right-5">单</span>
+                    {/eq}
+
+                    {eq name='vo.status' value='1'}
+                    <span class="layui-badge layui-bg-black">待处理</span>
+                    {elseif $vo.status eq 2}
+                    <span class="layui-badge layui-bg-green">处理中</span>
+                    {elseif $vo.status eq 3}
+                    <span class="layui-badge layui-bg-blue">处理完成</span>
+                    {elseif $vo.status eq 4}
+                    <span class="layui-badge layui-bg-red margin-right-5">处理失败</span>
+                    <a class="layui-badge layui-bg-green" data-tips-text="重置该任务" data-action="{:url('redo')}" data-value="id#{$vo.id}">
+                        <i class="layui-icon font-s12">&#xe669;</i>
+                    </a>
+                    {/eq}
+
+                    {if auth("admin/queue/remove") and in_array($vo.status,[1,3,4])}
+                    <a data-action='{:url("remove")}' data-confirm="确定要删除该任务吗?" data-value="id#{$vo.id}" data-tips-text="删除该任务" class='layui-badge layui-bg-red margin-left-5'>
+                        <i class="layui-icon">&#xe640;</i>
+                    </a>
+                    {/if}
+                </div>
+                <div class="color-desc">{$vo.desc|raw|default="没有获取到状态描述"}</div>
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+
+</div>
+{/block}

+ 70 - 0
application/admin/view/queue/index_search.html

@@ -0,0 +1,70 @@
+{if session('user.username') eq 'admin'}
+<fieldset class="margin-bottom-15">
+    <legend class="notselect">守护状态</legend>
+    <div class="layui-code border-0 margin-top-0">
+        <h4 class="color-desc margin-top-10 notselect">守护进程运行状态</h4>
+        {if $listen}
+        <span class="color-green">{$message|raw|default='--'}</span>
+        {else}
+        <span class="color-red">{$message|raw|default='--'}</span>
+        {/if}
+        <h4 class="color-desc margin-top-10 notselect">配置定时任务来检查并启动进程(建议每分钟执行)</h4>
+        <p>{$command|default='--'}</p>
+    </div>
+</fieldset>
+{/if}
+
+<fieldset>
+    <legend>条件搜索</legend>
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">任务名称</label>
+            <div class="layui-input-inline">
+                <input name="title" value="{$Think.get.title|default=''}" placeholder="请输入任务名称" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">任务指令</label>
+            <div class="layui-input-inline">
+                <input name="preload" value="{$Think.get.preload|default=''}" placeholder="请输入任务指令" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">任务状态</label>
+            <div class="layui-input-inline">
+                <select name="status" class="layui-select">
+                    {foreach [''=>'-- 全部状态 --','1'=>'待处理','2'=>'处理中','3'=>'处理完成','4'=>'处理失败'] as $k=>$v}
+                    <!--{if $Think.get.status eq $k}-->
+                    <option selected value="{$k}">{$v}</option>
+                    <!--{else}-->
+                    <option value="{$k}">{$v}</option>
+                    <!--{/if}-->
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">创建时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="create_at" value="{$Think.get.create_at|default=''}" placeholder="请选择创建时间" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">开始时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="start_at" value="{$Think.get.start_at|default=''}" placeholder="请选择开始时间" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">结束时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="end_at" value="{$Think.get.end_at|default=''}" placeholder="请选择结束时间" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item layui-inline">
+            <button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+    </form>
+</fieldset>
+
+<script>form.render()</script>

+ 73 - 0
application/admin/view/user/form.html

@@ -0,0 +1,73 @@
+<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post" autocomplete="off">
+    <div class="layui-card-body padding-left-40">
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">登录用户账号</span>
+                <span class="color-desc margin-left-5">Username</span>
+                {if isset($vo) and isset($vo.username)}
+                <input disabled value='{$vo.username|default=""}' class="layui-input layui-bg-gray">
+                {else}
+                <input name="username" value='{$vo.username|default=""}' required pattern="^.{4,}$" placeholder="请输入4位及以上字符登录用户账号" class="layui-input">
+                {/if}
+            </label>
+            <p class="help-block">登录用户账号创建后,不允许再次修改。</p>
+        </div>
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">用户联系手机</span>
+                <span class="color-desc margin-left-5">Phone</span>
+                <input type="tel" maxlength="11" autocomplete="off" autofocus name="phone" value='{$vo.phone|default=""}' pattern="^1[3-9][0-9]{9}$" placeholder="请输入用户联系手机" class="layui-input">
+            </label>
+            <p class="color-desc">可选,用户联系手机号码,需要填写正确的格式</p>
+        </div>
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">联系电子邮箱</span>
+                <span class="color-desc margin-left-5">Email</span>
+                <input name="mail" autocomplete="off" pattern="^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$" value='{$vo.mail|default=""}' placeholder="请输入联系电子邮箱" class="layui-input">
+            </label>
+            <p class="color-desc">可选,用户联系电子邮箱,需要填写正确的格式</p>
+        </div>
+
+        {notempty name='authorizes'}
+        <div class="layui-form-item relative">
+            <span class="color-green font-w7">角色访问授权</span>
+            <span class="color-desc margin-left-5">Authorization</span>
+            <div class="layui-textarea">
+                {if isset($vo.username) and $vo.username eq 'admin'}
+                <span class="color-desc">超级用户不需要配置权限</span>
+                {elseif empty($authorizes)}
+                <span class="color-desc">未配置权限</span>
+                {else}
+                {foreach $authorizes as $authorize}
+                <label class="think-checkbox layui-unselect">
+                    {if in_array($authorize.id, $vo.authorize)}
+                    <input type="checkbox" checked name="authorize[]" value="{$authorize.id}" lay-ignore> {$authorize.title}
+                    {else}
+                    <input type="checkbox" name="authorize[]" value="{$authorize.id}" lay-ignore> {$authorize.title}
+                    {/if}
+                </label>
+                {/foreach}
+                {/if}
+            </div>
+        </div>
+        {/notempty}
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">用户身份描述</span>
+                <span class="color-desc margin-left-5">Description</span>
+                <textarea placeholder="请输入用户身份描述" class="layui-textarea" name="desc">{$vo.desc|default=""}</textarea>
+            </label>
+        </div>
+    </div>
+
+    <div class="hr-line-dashed"></div>
+    {notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
+
+    <div class="layui-form-item text-center">
+        <button class="layui-btn" type='submit'>保存数据</button>
+        <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
+    </div>
+
+</form>

+ 77 - 0
application/admin/view/user/index.html

@@ -0,0 +1,77 @@
+{extend name='main'}
+
+{block name="button"}
+
+{if auth("add")}
+<button data-modal='{:url("add")}' data-title="添加用户" class='layui-btn layui-btn-sm layui-btn-primary'>添加用户</button>
+{/if}
+
+{if auth("remove")}
+<button data-action='{:url("remove")}' data-rule="id#{key}" data-csrf="{:systoken('remove')}" data-confirm="确定要删除这些用户吗?" class='layui-btn layui-btn-sm layui-btn-primary'>删除用户</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+<div class="think-box-shadow">
+    {include file='user/index_search'}
+    <table class="layui-table margin-top-10" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'>
+                <label><input data-auto-none data-check-target='.list-check-box' type='checkbox'></label>
+            </th>
+            <th class='text-left nowrap'>用户账号</th>
+            <th class='text-left nowrap'>联系手机</th>
+            <th class='text-center nowrap'>登录次数</th>
+            <th class='text-center nowrap'>使用状态</th>
+            <th class='text-left nowrap'>创建时间</th>
+            <th class='text-left nowrap'>登录时间</th>
+            <th class='text-left nowrap'></th>
+        </tr>
+        </thead>
+        {/notempty}
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr data-dbclick>
+            <td class='list-table-check-td think-checkbox'>
+                <label><input class="list-check-box" value='{$vo.id}' type='checkbox'></label>
+            </td>
+            <td class='text-left nowrap'>{$vo.username|default=''}</td>
+            <td class='text-left nowrap'>{$vo.phone|default='-'}</td>
+            <td class='text-center nowrap'>{$vo.login_num|default=0}</td>
+            <td class='text-center nowrap'>{eq name='vo.status' value='0'}<span class="color-red">已禁用</span>{else}<span class="color-green">使用中</span>{/eq}</td>
+            <td class='text-left nowrap'>{$vo.create_at|format_datetime}</td>
+            <td class='text-left nowrap'>{if $vo.login_num>0}{$vo.login_at|format_datetime}{else} - {/if}</td>
+            <td class='text-left nowrap'>
+
+                {if auth("pass")}
+                <a class="layui-btn layui-btn-normal layui-btn-sm" data-title="设置密码" data-modal='{:url("pass")}?id={$vo.id}'>密 码</a>
+                {/if}
+
+                {if auth("edit")}
+                <a data-dbclick class="layui-btn layui-btn-sm" data-title="编辑用户" data-modal='{:url("edit")}?id={$vo.id}'>编 辑</a>
+                {/if}
+
+                {if $vo.status eq 1 and auth("forbid")}
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('forbid')}" data-value="id#{$vo.id};status#0" data-csrf="{:systoken('forbid')}">禁 用</a>
+                {elseif $vo.status eq 0 and auth("resume")}
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('resume')}" data-value="id#{$vo.id};status#1" data-csrf="{:systoken('resume')}">启 用</a>
+                {/if}
+
+                {if auth("remove")}
+                <a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该用户吗?" data-action="{:url('remove')}" data-value="id#{$vo.id}" data-csrf="{:systoken('remove')}">删 除</a>
+                {/if}
+
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+
+</div>
+
+{/block}

+ 57 - 0
application/admin/view/user/index_search.html

@@ -0,0 +1,57 @@
+<fieldset>
+
+    <legend>条件搜索</legend>
+
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">用户账号</label>
+            <div class="layui-input-inline">
+                <input name="username" value="{$Think.get.username|default=''}" placeholder="请输入用户名" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">联系手机</label>
+            <div class="layui-input-inline">
+                <input name="phone" value="{$Think.get.phone|default=''}" placeholder="请输入联系手机" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">联系邮箱</label>
+            <div class="layui-input-inline">
+                <input name="mail" value="{$Think.get.mail|default=''}" placeholder="请输入联系邮箱" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">使用状态</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="status">
+                    {foreach [''=>'-- 全部状态 --','0'=>'已禁用的账号','1'=>'使用中的账号'] as $k=>$v}
+                    {eq name='Think.get.status' value='$k.""'}
+                    <option selected value="{$k}">{$v}</option>
+                    {else}
+                    <option value="{$k}">{$v}</option>
+                    {/eq}
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">登录时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="login_at" value="{$Think.get.login_at|default=''}" placeholder="请选择登录时间" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+    </form>
+
+</fieldset>
+
+<script>form.render()</script>

+ 57 - 0
application/admin/view/user/pass.html

@@ -0,0 +1,57 @@
+<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post" autocomplete="off">
+
+    <div class="layui-card-body padding-left-40">
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">登录用户账号</span>
+                <span class="color-desc margin-left-5">Username</span>
+                <!--{if isset($vo) and isset($vo.username)}-->
+                <input disabled value='{$vo.username|default=""}' class="layui-input layui-bg-gray">
+                <!--{else}-->
+                <input name="username" value='{$vo.username|default=""}' required pattern="^.{4,}$" placeholder="请输入4位及以上字符登录用户账号" class="layui-input">
+                <!--{/if}-->
+            </label>
+            <p class="help-block">登录用户账号创建后,不允许再次修改。</p>
+        </div>
+
+        <!--{if $verify}-->
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">旧的登录密码</span>
+                <span class="color-desc margin-left-5">Old Password</span>
+                <input type="password" autofocus name="oldpassword" value='' pattern="^\S{1,}$" required placeholder="请输入旧的登录密码" class="layui-input">
+            </label>
+            <p class="color-desc">请输入旧密码来验证修改权限,旧密码不限制格式。</p>
+        </div>
+        <!--{/if}-->
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">新的登录密码</span>
+                <span class="color-desc margin-left-5">New Password</span>
+                <input type="password" name="password" maxlength="32" pattern="^(?![\d]+$)(?![a-zA-Z]+$)(?![^\da-zA-Z]+$).{6,32}$" required placeholder="请输入新的登录密码" class="layui-input">
+            </label>
+            <p class="color-desc">密码必需包含大小写字母、数字、符号的任意两者组合。</p>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">重复登录密码</span>
+                <span class="color-desc margin-left-5">Retype Password</span>
+                <input type="password" name="repassword" maxlength="32" pattern="^(?![\d]+$)(?![a-zA-Z]+$)(?![^\da-zA-Z]+$).{6,32}$" required placeholder="请输入重复登录密码" class="layui-input">
+            </label>
+            <p class="color-desc">密码必需包含大小写字母、数字、符号的任意两者组合。</p>
+        </div>
+
+    </div>
+
+    <div class="hr-line-dashed"></div>
+    {notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
+
+    <div class="layui-form-item text-center">
+        <button class="layui-btn" type='submit'>保存数据</button>
+        <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
+    </div>
+
+</form>

+ 103 - 0
application/company/command/Subversion.php

@@ -0,0 +1,103 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\company\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+use think\Db;
+
+/**
+ * SVN 版本指令
+ * Class Subversion
+ * @package app\company\command
+ */
+class Subversion extends Command
+{
+    /**
+     * 账号授权文件位置
+     * @var string
+     */
+    protected $authzFile = 'php://output';
+
+    /**
+     * 账号管理文件位置
+     * @var string
+     */
+    protected $passwdFile = 'php://output';
+
+    /**
+     * 配置指令配置
+     */
+    protected function configure()
+    {
+        $this->setName('xsubversion:config')->setDescription('从数据库的配置同步到SVN配置文件');
+    }
+
+    /**
+     * @param Input $input
+     * @param Output $output
+     * @return int|void|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        $paths = ['/' => [0]];
+        $where = ['status' => '1', 'is_deleted' => '0'];
+        // 取得可用的用户账号
+        $users = Db::name('CompanyUser')->field('svn_username,svn_password,svn_authorize')->where($where)->select();
+        $authids = array_unique(explode(',', join(',', array_column($users, 'svn_authorize'))));
+        // 取得可用的权限配置
+        $userAuths = Db::name('CompanyUserAuth')->field('id,path')->where($where)->whereIn('id', $authids)->order('sort desc,id desc')->select();
+        foreach ($userAuths as $item) foreach (explode("\n", preg_replace('/\s+/i', "\n", trim($item['path']))) as $path) {
+            $paths[$path][] = $item['id'];
+        }
+        $this->writeAuth($users, $paths);
+    }
+
+    /**
+     * 写入 SVN 配置文件
+     * @param array $users
+     * @param array $paths
+     */
+    protected function writeAuth($users, $paths)
+    {
+        $output = [];
+        // Passwd 用户账号处理
+        foreach ($users as $user) $output[] = "{$user['svn_username']}={$user['svn_password']}";
+        file_put_contents($this->passwdFile, join(PHP_EOL, $output));
+        // Authz 授权配置处理
+        $groups = ['_0' => []];
+        foreach ($users as $user) {
+            $ids = array_unique(explode(',', $user['svn_authorize']));
+            foreach ($ids as $id) $groups["_{$id}"][] = $user['svn_username'];
+        }
+        $output = [];
+        $output[] = '[groups]';
+        foreach ($groups as $key => $group) $output[] = "group{$key}=" . join(',', $group);
+        $output[] = '';
+        foreach ($paths as $path => $ids) {
+            $output[] = "[{$path}]";
+            $output[] = "* =";
+            $output[] = '@group_0 = rw';
+            foreach ($ids as $id) if ($id > 0) $output[] = "@group_{$id} = rw";
+            $output[] = '';
+        }
+        file_put_contents($this->authzFile, join(PHP_EOL, $output));
+    }
+}

+ 101 - 0
application/company/controller/Auth.php

@@ -0,0 +1,101 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\company\controller;
+
+use library\Controller;
+
+/**
+ * 仓库权限管理
+ * Class Auth
+ * @package app\company\controller
+ */
+class Auth extends Controller
+{
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    protected $table = 'CompanyUserAuth';
+
+    /**
+     * 仓库权限管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '仓库权限管理';
+        $query = $this->_query($this->table)->like('title,path')->equal('status');
+        $query->dateBetween('create_at')->where(['is_deleted' => '0'])->page();
+    }
+
+    /**
+     * 添加SVN权限
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function add()
+    {
+        $this->title = '添加SVN权限';
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 编辑SVN权限
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function edit()
+    {
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 修改SVN权限状态
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function state()
+    {
+        $this->_save($this->table, ['status' => input('status')]);
+    }
+
+    /**
+     * 删除SVN权限
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->_delete($this->table);
+    }
+
+}

+ 49 - 0
application/company/controller/Clock.php

@@ -0,0 +1,49 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\company\controller;
+
+use library\Controller;
+
+/**
+ * 网络打卡管理
+ * Class Clock
+ * @package app\company\controller
+ */
+class Clock extends Controller
+{
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    protected $table = 'CompanyUserClock';
+
+    /**
+     * 网络打卡管理
+     * @auth true
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '网络打卡管理';
+        $this->_query($this->table)->like('name')->equal('date')->order('id asc')->page();
+    }
+
+}

+ 131 - 0
application/company/controller/User.php

@@ -0,0 +1,131 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\company\controller;
+
+use app\company\service\DataService;
+use library\Controller;
+use think\Db;
+
+/**
+ * 企业员工管理
+ * Class User
+ * @package app\worker\controller
+ */
+class User extends Controller
+{
+
+    /**
+     * 绑定当前数据表
+     * @var string
+     */
+    protected $table = 'CompanyUser';
+
+    /**
+     * 企业员工管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '仓库权限管理';
+        $this->_query($this->table)->like('nickname,svn_username')->equal('status')->page();
+    }
+
+    /**
+     * 添加企业员工
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function add()
+    {
+        $this->_form($this->table, 'form');
+    }
+
+
+    /**
+     * 修改企业员工
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function edit()
+    {
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 权限表单数据处理
+     * @param array $data 表单数据
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    protected function _form_filter(&$data)
+    {
+        if ($this->request->isGet()) {
+            $where = ['status' => '1', 'is_deleted' => '0'];
+            $this->auths = Db::name('company_user_auth')->where($where)->order('sort desc,id desc')->select();
+            array_unshift($this->auths, ['id' => '0', 'title' => '所有权限', 'path' => '/']);
+            $data['svn_authorize'] = isset($data['svn_authorize']) ? explode(',', $data['svn_authorize']) : [];
+        } else {
+            if (isset($data['svn_authorize']) && is_array($data['svn_authorize'])) {
+                $data['svn_authorize'] = join(',', $data['svn_authorize']);
+            } else {
+                $data['svn_authorize'] = '';
+            }
+            $macs = [];
+            foreach (explode(PHP_EOL, preg_replace("/\s+/", PHP_EOL, trim($data['mobile_macs']))) as $mac) {
+                if (DataService::applyMacValue($mac)) $macs[] = $mac;
+            }
+            $data['mobile_macs'] = join(PHP_EOL, array_unique($macs));
+        }
+    }
+
+    /**
+     * 更改企业员工状态
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function state()
+    {
+        $this->_save($this->table, ['status' => input('status', '0')]);
+    }
+
+    /**
+     * 删除企业员工
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->_delete($this->table);
+    }
+
+}

+ 97 - 0
application/company/controller/api/Push.php

@@ -0,0 +1,97 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\company\controller\api;
+
+use app\company\service\DataService;
+use library\Controller;
+use think\Db;
+
+/**
+ * ARP-SAN 推送内容接收
+ * Class Push
+ * @package app\company\controller\api
+ */
+class Push extends Controller
+{
+    /**
+     * ARP-SAN 推送内容接收
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        // 数据输入检查
+        $content = file_get_contents('php://input');
+        if (empty($content)) $this->error('没有接收到数据');
+        // 企业员工检查
+        $where = ['is_deleted' => '0', 'status' => '1'];
+        $users = Db::name('CompanyUser')->cache(10)->where($where)->column('id uid,nickname name,mobile_macs mac');
+        if (empty($users)) $this->error('没有需要打卡的用户');
+        // 企业员工检查
+        $macs = [];
+        foreach ($users as $user) foreach (explode("\n", preg_replace('/\s+/', "\n", $user['mac'])) as $mac) {
+            if (DataService::applyMacValue($mac)) $macs[$mac] = ['uid' => $user['uid'], 'name' => $user['name']];
+        }
+        // 数据内容解析
+        list($s, $e) = [0, 0];
+        foreach (explode("\n", $content) as $line) {
+            list($ip, $mac, $dsc) = explode(' ', preg_replace('/\s+/', ' ', trim($line)) . '  ');
+            if (preg_match('/^(\d+\.?){4}$/', $ip) && DataService::applyMacValue($mac)) {
+                if (isset($macs[$mac])) {
+                    $s++;
+                    $this->writeUser($ip, $mac, strtoupper($dsc), $macs);
+                } else {
+                    $e++;
+                    $this->writeNone($ip, $mac, strtoupper($dsc));
+                }
+            }
+        }
+        return "接收到{$s}个已知设备推送,{$e}个未知设备推送。" . PHP_EOL . PHP_EOL;
+    }
+
+    /**
+     * 已知设备打卡记录
+     * @param string $ip 内网地址
+     * @param string $mac 设备地址
+     * @param string $desc 设备描述
+     * @param array $macs 用户MAC列表
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    private function writeUser($ip, $mac, $desc, $macs)
+    {
+        if (isset($macs[$mac])) {
+            $data = $macs[$mac];
+            $data['ip'] = $ip;
+            $data['mac'] = $mac;
+            $data['desc'] = $desc;
+            $data['date'] = date('Y-m-d');
+            $data['end_at'] = date('Y-m-d H:i:s');
+            data_save('CompanyUserClock', $data, 'uid', ['date' => $data['date']]);
+        }
+    }
+
+    /**
+     * 未知设备额外标识
+     * @param string $ip 内网地址
+     * @param string $mac 设备地址
+     * @param string $desc 设备描述
+     */
+    private function writeNone($ip, $mac, $desc)
+    {
+        // @todo 记录未匹配成功的设备标识
+    }
+}

+ 40 - 0
application/company/service/DataService.php

@@ -0,0 +1,40 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\company\service;
+
+/**
+ * 企业数据处理服务
+ * Class DataService
+ * @package app\company\service
+ */
+class DataService
+{
+    /**
+     * 格式化MAC地址信息
+     * @param string $mac
+     * @return string
+     */
+    public static function applyMacValue(&$mac)
+    {
+        $mac = strtoupper(str_replace('-', ':', $mac));
+        if (preg_match('/([A-F0-9]{2}:){5}[A-F0-9]{2}/', $mac)) {
+            return $mac;
+        } else {
+            return false;
+        }
+    }
+
+}

+ 21 - 0
application/company/sys.php

@@ -0,0 +1,21 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+// 注册系统指令
+// use think\Console;
+
+// Console::addDefaultCommands([
+//    'app\company\command\Subversion',
+// ]);

+ 35 - 0
application/company/view/auth/form.html

@@ -0,0 +1,35 @@
+<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post" autocomplete="off">
+    <div class="layui-card-body">
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">权限名称</label>
+            <div class="layui-input-block">
+                <input name="title" value='{$vo.title|default=""}' required placeholder="请输入权限名称" class="layui-input">
+                <p class="help-block">权限名称将出现在员工SVN授权处,请根据需求准确填写权限名称。</p>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">权限路径</label>
+            <div class="layui-input-block">
+                <textarea class="layui-textarea" name="path" placeholder="请输入权限路径">{$vo.path|default=''}</textarea>
+                <p class="help-block">权限路径将配置到SVN权限分组上,务必要保证路径与SVN文件路径一致。</p>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">权限描述</label>
+            <div class="layui-input-block">
+                <textarea class="layui-textarea" name="desc" placeholder="请输入权限描述">{$vo.desc|default=''}</textarea>
+            </div>
+        </div>
+    </div>
+
+    <div class="hr-line-dashed"></div>
+    {notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
+
+    <div class="layui-form-item text-center">
+        <button class="layui-btn" type='submit'>保存数据</button>
+        <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
+    </div>
+
+    <script>window.form.render();</script>
+
+</form>

+ 78 - 0
application/company/view/auth/index.html

@@ -0,0 +1,78 @@
+{extend name='admin@main'}
+
+{block name="button"}
+
+{if auth("add")}
+<button data-modal='{:url("add")}' data-title="添加仓库权限" class='layui-btn layui-btn-sm layui-btn-primary'>添加仓库权限</button>
+{/if}
+
+{if auth("remove")}
+<button data-action='{:url("remove")}' data-rule="id#{key}" data-csrf="{:systoken('remove')}" data-confirm="确定要删除这些权限吗?" class='layui-btn layui-btn-sm layui-btn-primary'>添加仓库权限</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+<div class="think-box-shadow">
+    {include file='auth/index_search'}
+    <table class="layui-table" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'>
+                <label><input data-auto-none data-check-target='.list-check-box' type='checkbox'></label>
+            </th>
+            <th class='list-table-sort-td'>
+                <button type="button" data-reload class="layui-btn layui-btn-xs">刷 新</button>
+            </th>
+            <th class='text-left nowrap'>权限名称</th>
+            <th class='text-left nowrap'>权限路径</th>
+            <th class='text-center nowrap'>使用状态</th>
+            <th class='text-left nowrap'>登录时间</th>
+            <th class='text-left nowrap'></th>
+        </tr>
+        </thead>
+        {/notempty}
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr data-dbclick>
+            <td class='list-table-check-td think-checkbox'>
+                <label><input class="list-check-box" value='{$vo.id}' type='checkbox'></label>
+            </td>
+            <td class='list-table-sort-td'>
+                <label><input data-action-blur="{:request()->url()}" data-value="id#{$vo.id};action#sort;sort#{value}" data-loading="false" value="{$vo.sort}" class="list-sort-input"></label>
+            </td>
+            <td class='text-left nowrap'>{$vo.title|default=''}</td>
+            <td class='text-left nowrap'>{$vo.path|nl2br|raw}</td>
+            <td class='text-center nowrap'>
+                {eq name='vo.status' value='0'}<span class="color-red">已禁用</span>{/eq}
+                {eq name='vo.status' value='1'}<span class="color-green">使用中</span>{/eq}
+            </td>
+            <td class='text-left nowrap'>{$vo.create_at|format_datetime}</td>
+            <td class='text-left nowrap'>
+
+                {if auth("edit")}
+                <a data-dbclick class="layui-btn layui-btn-sm" data-title="编辑仓库权限" data-modal='{:url("edit")}?id={$vo.id}'>编 辑</a>
+                {/if}
+
+                {if auth("state") and $vo.status neq 0}
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('state')}" data-value="id#{$vo.id};status#0" data-csrf="{:systoken('state')}">禁 用</a>
+                {elseif auth("state") and $vo.status neq 1 }
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('state')}" data-value="id#{$vo.id};status#1" data-csrf="{:systoken('state')}">启 用</a>
+                {/if}
+
+                {if auth("remove")}
+                <a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该权限吗?" data-action="{:url('remove')}" data-value="id#{$vo.id}" data-csrf="{:systoken('remove')}">删 除</a>
+                {/if}
+
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+
+</div>
+
+{/block}

+ 50 - 0
application/company/view/auth/index_search.html

@@ -0,0 +1,50 @@
+<fieldset>
+
+    <legend>条件搜索</legend>
+
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">权限名称</label>
+            <div class="layui-input-inline">
+                <input name="title" value="{$Think.get.title|default=''}" placeholder="请输入权限名称" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">权限路径</label>
+            <div class="layui-input-inline">
+                <input name="path" value="{$Think.get.path|default=''}" placeholder="请输入权限路径" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">使用状态</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="status">
+                    {foreach [''=>'-- 全部状态 --','0'=>'已禁用的权限','1'=>'使用中的权限'] as $k=>$v}
+                    {eq name='Think.get.status' value='$k.""'}
+                    <option selected value="{$k}">{$v}</option>
+                    {else}
+                    <option value="{$k}">{$v}</option>
+                    {/eq}
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">创建时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="create_at" value="{$Think.get.create_at|default=''}" placeholder="请选择创建时间" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+    </form>
+
+</fieldset>
+
+<script>form.render()</script>

+ 41 - 0
application/company/view/clock/index.html

@@ -0,0 +1,41 @@
+{extend name='admin@main'}
+
+{block name="content"}
+<div class="think-box-shadow">
+    {include file='clock/index_search'}
+    <table class="layui-table" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'>
+                <label><input data-auto-none data-check-target='.list-check-box' type='checkbox'></label>
+            </th>
+            <th class='text-left nowrap'>员工姓名</th>
+            <th class='text-left nowrap'>打卡日期</th>
+            <th class='text-left nowrap'>内网地址</th>
+            <th class='text-left nowrap'>首次打卡</th>
+            <th class='text-left nowrap'>最后打卡</th>
+        </tr>
+        </thead>
+        {/notempty}
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr data-dbclick>
+            <td class='list-table-check-td think-checkbox'>
+                <label><input class="list-check-box" value='{$vo.id}' type='checkbox'></label>
+            </td>
+            <td class='text-left nowrap'>{$vo.name|default=''}</td>
+            <td class='text-left nowrap'>{$vo.date|default='-'}</td>
+            <td class='text-left nowrap'>{$vo.ip|default='-'}</td>
+            <td class='text-left nowrap'>{$vo.start_at|format_datetime}</td>
+            <td class='text-left nowrap'>{$vo.end_at|format_datetime}</td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+
+</div>
+
+{/block}

+ 28 - 0
application/company/view/clock/index_search.html

@@ -0,0 +1,28 @@
+<fieldset>
+
+    <legend>条件搜索</legend>
+
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">员工姓名</label>
+            <div class="layui-input-inline">
+                <input name="name" value="{$Think.get.name|default=''}" placeholder="请输入员工姓名" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">打卡日期</label>
+            <div class="layui-input-inline">
+                <input data-date-input="date" name="date" value="{$Think.get.date|default=''}" placeholder="请输入打卡日期" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+    </form>
+
+</fieldset>
+
+<script>form.render()</script>

+ 89 - 0
application/company/view/user/form.html

@@ -0,0 +1,89 @@
+<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post" autocomplete="off">
+    <div class="layui-card-body">
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">员工姓名</label>
+            <div class="layui-input-block">
+                <input name="nickname" value='{$vo.nickname|default=""}' required placeholder="请输入公司员工姓名" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">员工身份</label>
+            <div class="layui-input-block">
+                <input name="user_type" value='{$vo.user_type|default=""}' required placeholder="请输入公司员工身份" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">仓库账号</label>
+            <div class="layui-input-block">
+                <input name="svn_username" value='{$vo.svn_username|default=""}' required placeholder="请输入SVN授权账号" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">仓库密码</label>
+            <div class="layui-input-block">
+                <input name="svn_password" value='{$vo.svn_password|default=""}' required placeholder="请输入SVN授权密码" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">仓库权限</label>
+            <div class="layui-input-block">
+                <div class="layui-textarea padding-bottom-10" style="min-height:auto">
+                    {foreach $auths as $auth}
+                    <label class="think-checkbox layui-unselect">
+                        {if isset($vo.svn_authorize) and in_array($auth.id, $vo.svn_authorize)}
+                        <input type="checkbox" name="svn_authorize[]" value="{$auth.id}" checked lay-ignore> {$auth.title}
+                        {else}
+                        <input type="checkbox" name="svn_authorize[]" value="{$auth.id}" lay-ignore> {$auth.title}
+                        {/if}
+                    </label>
+                    {/foreach}
+                </div>
+            </div>
+        </div>
+        <div class="layui-form-item nowrap">
+            <label class="layui-form-label">入职日期</label>
+            <div class="layui-input-inline margin-right-0" style="width:164px">
+                <input type="text" data-date-input="date" name="entry_date" value='{$vo.entry_date|default=""}' placeholder="请输入入职日期" class="layui-input">
+            </div>
+            <label class="layui-form-label" style="width:auto">转正日期</label>
+            <div class="layui-input-inline margin-right-0" style="width:164px">
+                <input type="text" data-date-input="date" name="become_date" value='{$vo.become_date|default=""}' placeholder="请输入转正日期" class="layui-input">
+            </div>
+            <label class="layui-form-label" style="width:auto">离职日期</label>
+            <div class="layui-input-inline margin-right-0" style="width:164px">
+                <input type="text" data-date-input="date" name="leave_date" value='{$vo.leave_date|default=""}' placeholder="请输入离职日期" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">联系QQ</label>
+            <div class="layui-input-inline margin-right-0" style="width:164px">
+                <input type="tel" autocomplete="off" name="contact_qq" value='{$vo.contact_qq|default=""}' pattern="^\d{6,}$" placeholder="请输入联系QQ号码" class="layui-input">
+            </div>
+            <label class="layui-form-label" style="width:auto">联系手机</label>
+            <div class="layui-input-inline margin-right-0" style="width:164px">
+                <input type="tel" maxlength="11" autocomplete="off" name="contact_phone" value='{$vo.contact_phone|default=""}' pattern="^1[3-9][0-9]{9}$" placeholder="请输入联系手机" class="layui-input">
+            </div>
+            <label class="layui-form-label" style="width:auto">联系邮箱</label>
+            <div class="layui-input-inline margin-right-0" style="width:164px">
+                <input name="contact_mail" autocomplete="off" pattern="^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$" value='{$vo.contact_mail|default=""}' placeholder="请输入联系邮箱" class="layui-input">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">打卡绑定</label>
+            <div class="layui-input-block">
+                <textarea placeholder="请输入绑定手机" class="layui-textarea" name="mobile_macs">{$vo.mobile_macs|default=""}</textarea>
+            </div>
+        </div>
+    </div>
+
+    <div class="hr-line-dashed"></div>
+    {notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
+
+    <div class="layui-form-item text-center">
+        <button class="layui-btn" type='submit'>保存数据</button>
+        <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消编辑吗?" data-close>取消编辑</button>
+    </div>
+
+    <script>window.form.render();</script>
+
+</form>

+ 83 - 0
application/company/view/user/index.html

@@ -0,0 +1,83 @@
+{extend name='admin@main'}
+
+{block name="button"}
+
+{if auth("add")}
+<button data-modal='{:url("add")}' data-title="添加企业员工" class='layui-btn layui-btn-sm layui-btn-primary'>添加企业员工</button>
+{/if}
+
+{if auth("remove")}
+<button data-action='{:url("remove")}' data-rule="id#{key}" data-csrf="{:systoken('remove')}" data-confirm="确定要删除这些员工吗?" class='layui-btn layui-btn-sm layui-btn-primary'>删除企业员工</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+<div class="think-box-shadow">
+    {include file='user/index_search'}
+    <table class="layui-table" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'>
+                <label><input data-auto-none data-check-target='.list-check-box' type='checkbox'></label>
+            </th>
+            <th class='list-table-sort-td'>
+                <button type="button" data-reload class="layui-btn layui-btn-xs">刷 新</button>
+            </th>
+            <th class='text-left nowrap'>员工姓名</th>
+            <th class='text-left nowrap'>员工身份</th>
+            <th class='text-left nowrap'>库存账号</th>
+            <th class='text-center nowrap'>使用状态</th>
+            <th class='text-left nowrap'>添加时间</th>
+            <th class='text-left nowrap'></th>
+        </tr>
+        </thead>
+        {/notempty}
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr data-dbclick>
+            <td class='list-table-check-td think-checkbox'>
+                <label><input class="list-check-box" value='{$vo.id}' type='checkbox'></label>
+            </td>
+            <td class='list-table-sort-td'>
+                <label><input data-action-blur="{:request()->url()}" data-value="id#{$vo.id};action#sort;sort#{value}" data-loading="false" value="{$vo.sort}" class="list-sort-input"></label>
+            </td>
+            <td class='text-left nowrap'>{$vo.nickname|default=''}</td>
+            <td class='text-left nowrap'>{$vo.user_type|default='-'}</td>
+            <td class='text-left nowrap'>{$vo.svn_username|default='-'}</td>
+            <td class='text-center nowrap'>
+                {eq name='vo.status' value='0'}<span class="color-red">已禁用</span>{/eq}
+                {eq name='vo.status' value='1'}<span class="color-green">在职中</span>{/eq}
+                {eq name='vo.status' value='2'}<span class="color-desc">已离职</span>{/eq}
+            </td>
+            <td class='text-left nowrap'>
+                {$vo.create_at|format_datetime}
+            </td>
+            <td class='text-left nowrap'>
+
+                {if auth("edit")}
+                <a data-dbclick class="layui-btn layui-btn-sm" data-title="编辑员工信息" data-modal='{:url("edit")}?id={$vo.id}'>编 辑</a>
+                {/if}
+
+                {if auth("state") and $vo.status neq 0}
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('state')}" data-value="id#{$vo.id};status#0" data-csrf="{:systoken('state')}">禁 用</a>
+                {elseif auth("state") and $vo.status neq 1 }
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('state')}" data-value="id#{$vo.id};status#1" data-csrf="{:systoken('state')}">启 用</a>
+                {/if}
+
+                {if auth("remove")}
+                <a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该员工吗?" data-action="{:url('remove')}" data-value="id#{$vo.id}" data-csrf="{:systoken('remove')}">删 除</a>
+                {/if}
+
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+
+</div>
+
+{/block}

+ 50 - 0
application/company/view/user/index_search.html

@@ -0,0 +1,50 @@
+<fieldset>
+
+    <legend>条件搜索</legend>
+
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">员工姓名</label>
+            <div class="layui-input-inline">
+                <input name="nickname" value="{$Think.get.nickname|default=''}" placeholder="请输入员工姓名" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">仓库账号</label>
+            <div class="layui-input-inline">
+                <input name="svn_username" value="{$Think.get.svn_username|default=''}" placeholder="请输入仓库账号" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">员工状态</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="status">
+                    {foreach [''=>'-- 全部状态 --','0'=>'禁用的员工','1'=>'正常的员工','2'=>'离职的员工'] as $k=>$v}
+                    {eq name='Think.get.status' value='$k.""'}
+                    <option selected value="{$k}">{$v}</option>
+                    {else}
+                    <option value="{$k}">{$v}</option>
+                    {/eq}
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">添加时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="create_at" value="{$Think.get.create_at|default=''}" placeholder="请选择添加时间" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+    </form>
+
+</fieldset>
+
+<script>form.render()</script>

+ 34 - 0
application/index/controller/Index.php

@@ -0,0 +1,34 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\index\controller;
+
+use library\Controller;
+
+/**
+ * 应用入口
+ * Class Index
+ * @package app\index\controller
+ */
+class Index extends Controller
+{
+    /**
+     * 入口跳转链接
+     */
+    public function index()
+    {
+        $this->redirect('@admin/login');
+    }
+}

+ 63 - 0
application/service/controller/Config.php

@@ -0,0 +1,63 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\controller;
+
+use library\Controller;
+
+/**
+ * 开放平台参数配置
+ * Class Config
+ * @package app\service\controller
+ */
+class Config extends Controller
+{
+
+    /**
+     * 定义当前操作表名
+     * @var string
+     */
+    public $table = 'WechatServiceConfig';
+
+    /**
+     * 显示参数配置
+     * @auth true
+     * @menu true
+     */
+    public function index()
+    {
+        $this->title = '开放平台参数配置';
+        $this->fetch();
+    }
+
+    /**
+     * 修改参数配置
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function edit()
+    {
+        $this->applyCsrfToken();
+        if ($this->request->isGet()) {
+            $this->fetch('form');
+        } else {
+            $post = $this->request->post();
+            foreach ($post as $k => $v) sysconf($k, $v);
+            $this->success('参数修改成功!');
+        }
+    }
+
+}

+ 158 - 0
application/service/controller/Fans.php

@@ -0,0 +1,158 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\controller;
+
+use app\service\queue\WechatQueue;
+use app\service\service\WechatService;
+use library\Controller;
+use think\Db;
+use think\exception\HttpResponseException;
+
+/**
+ * 微信粉丝管理
+ * Class Fans
+ * @package app\wechat\controller
+ */
+class Fans extends Controller
+{
+
+    protected $appid = '';
+
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    protected $table = 'WechatFans';
+
+    /**
+     * 初始化函数
+     * @return Controller|void
+     */
+    protected function initialize()
+    {
+        $this->appid = input('appid', session('current_appid'));
+        if (empty($this->appid)) {
+            $this->where = ['status' => '1', 'service_type' => '2', 'is_deleted' => '0', 'verify_type' => '0'];
+            $this->appid = Db::name('WechatServiceConfig')->where($this->where)->value('authorizer_appid');
+        }
+        if (empty($this->appid)) {
+            $this->fetch('/not-auth');
+        } else {
+            session('current_appid', $this->appid);
+        }
+        if ($this->request->isGet()) {
+            $this->where = ['status' => '1', 'service_type' => '2', 'is_deleted' => '0', 'verify_type' => '0'];
+            $this->wechats = Db::name('WechatServiceConfig')->where($this->where)->order('id desc')->column('authorizer_appid,nick_name');
+        }
+    }
+
+    /**
+     * 微信粉丝管理
+     * @auth true
+     * @menu true
+     */
+    public function index()
+    {
+        $this->title = '微信粉丝管理';
+        $query = $this->_query($this->table)->like('nickname')->equal('subscribe,is_black');
+        $query->dateBetween('subscribe_at')->where(['appid' => $this->appid])->order('subscribe_time desc')->page();
+    }
+
+    /**
+     * 列表数据处理
+     * @param array $data
+     */
+    protected function _index_page_filter(array &$data)
+    {
+        $tags = Db::name('WechatFansTags')->column('id,name');
+        foreach ($data as &$user) {
+            $user['tags'] = [];
+            foreach (explode(',', $user['tagid_list']) as $tagid) {
+                if (isset($tags[$tagid])) $user['tags'][] = $tags[$tagid];
+            }
+        }
+    }
+
+    /**
+     * 批量拉黑粉丝
+     * @auth true
+     */
+    public function setBlack()
+    {
+        try {
+            $this->applyCsrfToken();
+            foreach (array_chunk(explode(',', $this->request->post('openid')), 20) as $openids) {
+                WechatService::WeChatUser($this->appid)->batchBlackList($openids);
+                Db::name('WechatFans')->where(['appid' => $this->appid])->whereIn('openid', $openids)->update(['is_black' => '1']);
+            }
+            $this->success('拉黑粉丝信息成功!');
+        } catch (HttpResponseException $exception) {
+            throw  $exception;
+        } catch (\Exception $e) {
+            $this->error("拉黑粉丝信息失败,请稍候再试!{$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 取消拉黑粉丝
+     * @auth true
+     */
+    public function delBlack()
+    {
+        try {
+            $this->applyCsrfToken();
+            foreach (array_chunk(explode(',', $this->request->post('openid')), 20) as $openids) {
+                WechatService::WeChatUser($this->appid)->batchUnblackList($openids);
+                Db::name('WechatFans')->where(['appid' => $this->appid])->whereIn('openid', $openids)->update(['is_black' => '0']);
+            }
+            $this->success('取消拉黑粉丝信息成功!');
+        } catch (HttpResponseException $exception) {
+            throw  $exception;
+        } catch (\Exception $e) {
+            $this->error("取消拉黑粉丝信息失败,请稍候再试!{$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 同步粉丝列表
+     * @auth true
+     */
+    public function sync()
+    {
+        try {
+            sysoplog('微信管理', "创建微信[{$this->appid}]粉丝同步任务");
+            sysqueue("同步[{$this->appid}]粉丝列表", WechatQueue::URI, 0, ['appid' => $this->appid], 0);
+            $this->success('创建同步粉丝任务成功,需要时间来完成。<br>请到 系统管理 > 任务管理 查看执行进度!');
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            $this->error("创建同步粉丝任务失败,请稍候再试!<br> {$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 删除粉丝信息
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->applyCsrfToken();
+        $this->_delete($this->table);
+    }
+
+}

+ 166 - 0
application/service/controller/Index.php

@@ -0,0 +1,166 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\controller;
+
+use app\service\service\BuildService;
+use app\service\service\WechatService;
+use library\Controller;
+use think\Db;
+use think\exception\HttpResponseException;
+
+/**
+ * 公众授权管理
+ * @package app\service\controller
+ */
+class Index extends Controller
+{
+
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    public $table = 'WechatServiceConfig';
+
+    /**
+     * 公众授权管理
+     * @auth true
+     * @menu true
+     * @return string
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $this->applyCsrfToken();
+        $this->title = '公众授权管理';
+        $query = $this->_query($this->table)->like('authorizer_appid,nick_name,principal_name');
+        $query = $query->equal('service_type,status')->dateBetween('create_at');
+        $query->where(['is_deleted' => '0'])->order('id desc')->page();
+    }
+
+    /**
+     * 清理调用次数
+     * @auth true
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     */
+    public function clearQuota()
+    {
+        $appid = input('appid');
+        $result = WechatService::WeChatLimit($appid)->clearQuota();
+        if (empty($result['errcode']) && $result['errmsg'] === 'ok') {
+            $this->success('接口调用次数清零成功!');
+        } elseif (isset($result['errmsg'])) {
+            $this->error('接口调用次数清零失败,请稍候再试!' . $result['errmsg']);
+        } else {
+            $this->error('接口调用次数清零失败,请稍候再试!');
+        }
+    }
+
+    /**
+     * 同步指定授权公众号
+     * @auth true
+     */
+    public function sync()
+    {
+        try {
+            $appid = $this->request->get('appid');
+            $where = ['authorizer_appid' => $appid, 'is_deleted' => '0', 'status' => '1'];
+            $author = Db::name('WechatServiceConfig')->where($where)->find();
+            if (empty($author)) $this->error('无效的授权信息,请同步其它公众号!');
+            $data = BuildService::filter(WechatService::service()->getAuthorizerInfo($appid));
+            $data['authorizer_appid'] = $appid;
+            $where = ['authorizer_appid' => $data['authorizer_appid']];
+            $appkey = Db::name('WechatServiceConfig')->where($where)->value('appkey');
+            if (empty($appkey)) $data['appkey'] = md5(uniqid('', true));
+            if (data_save('WechatServiceConfig', $data, 'authorizer_appid')) {
+                $this->success('更新公众号授权成功!', '');
+            }
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            $this->error("获取授权信息失败,请稍候再试!<br>{$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 同步所有授权公众号
+     * @auth true
+     */
+    public function syncall()
+    {
+        try {
+            $wechat = WechatService::service();
+            $result = $wechat->getAuthorizerList();
+            foreach ($result['list'] as $item) if (!empty($item['refresh_token']) && !empty($item['auth_time'])) {
+                $data = BuildService::filter($wechat->getAuthorizerInfo($item['authorizer_appid']));
+                $data['is_deleted'] = '0';
+                $data['authorizer_appid'] = $item['authorizer_appid'];
+                $data['authorizer_refresh_token'] = $item['refresh_token'];
+                $data['create_at'] = date('Y-m-d H:i:s', $item['auth_time']);
+                $where = ['authorizer_appid' => $data['authorizer_appid']];
+                $appkey = Db::name('WechatServiceConfig')->where($where)->value('appkey');
+                if (empty($appkey)) $data['appkey'] = md5(uniqid('', true));
+                if (!data_save('WechatServiceConfig', $data, 'authorizer_appid')) {
+                    $this->error('获取授权信息失败,请稍候再试!', '');
+                }
+            }
+            $this->success('同步所有授权信息成功!', '');
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            $this->error("同步授权失败,请稍候再试!<br>{$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 删除公众号授权
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->applyCsrfToken();
+        $this->_delete($this->table);
+    }
+
+    /**
+     * 禁用公众号授权
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function forbid()
+    {
+        $this->applyCsrfToken();
+        $this->_save($this->table, ['status' => '0']);
+    }
+
+    /**
+     * 启用公众号授权
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function resume()
+    {
+        $this->applyCsrfToken();
+        $this->_save($this->table, ['status' => '1']);
+    }
+}

+ 174 - 0
application/service/controller/api/Client.php

@@ -0,0 +1,174 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\controller\api;
+
+use app\service\service\WechatService;
+use library\tools\JsonRpcServer;
+use think\Controller;
+use think\Db;
+use think\exception\HttpResponseException;
+
+/**
+ * 获取微信SDK实例对象
+ * Class Instance
+ * @package app\wechat\controller
+ */
+class Client extends Controller
+{
+
+    /**
+     * 当前配置
+     * @var string
+     */
+    protected $config = [];
+
+    /**
+     * 当前APPID
+     * @var string
+     */
+    protected $appid = '';
+
+    /**
+     * 接口实例名称
+     * @var string
+     */
+    protected $name = '';
+
+    /**
+     * 接口类型
+     * @var string
+     */
+    protected $type = '';
+
+    /**
+     * 错误消息
+     * @var string
+     */
+    protected $message = '';
+
+    /**
+     * 启动Yar接口服务
+     * @param string $param AppName-AppId-AppKey
+     * @return string
+     */
+    public function yar($param)
+    {
+        try {
+            $instance = $this->create($param);
+            $service = new \Yar_Server(empty($instance) ? $this : $instance);
+            $service->handle();
+        } catch (\Exception $e) {
+            return $e->getMessage();
+        }
+    }
+
+    /**
+     * 启动SOAP接口服务
+     * @param string $param AppName-AppId-AppKey
+     * @return string
+     */
+    public function soap($param)
+    {
+        try {
+            $instance = $this->create($param);
+            $service = new \SoapServer(null, ['uri' => strtolower($this->name)]);
+            $service->setObject(empty($instance) ? $this : $instance);
+            $service->handle();
+        } catch (\Exception $e) {
+            return $e->getMessage();
+        }
+    }
+
+    /**
+     * JsonRpc 接口标准
+     * @param string $param
+     * @return string
+     */
+    public function jsonrpc($param)
+    {
+        try {
+            $instance = $this->create($param);
+            JsonRpcServer::instance()->handle(empty($instance) ? $this : $instance);
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            return $e->getMessage();
+        }
+    }
+
+    /**
+     * 创建接口服务
+     * @param string $token
+     * @return \WeChat\Oauth|\WeChat\Pay|\WeChat\Receive|\WeChat\Script|\WeChat\User|\WeOpen\Service
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\Exception
+     */
+    private function create($token)
+    {
+        if ($this->auth($token)) {
+            $weminiClassName = 'Account,Basic,Code,Domain,Tester,User,Crypt,Plugs,Poi,Qrcode,Template,Total,Delivery,Image,Logistics,Message,Ocr,Security,Soter';
+            $wechatClassName = 'Card,Custom,Limit,Media,Menu,Oauth,Pay,Product,Qrcode,Receive,Scan,Script,Shake,Tags,Template,User,Wifi';
+            if ($this->type === 'wechat' && stripos($wechatClassName, $this->name) !== false) {
+                $instance = WechatService::instance($this->name, $this->appid, 'WeChat');
+            } elseif ($this->type === 'wemini' && stripos($weminiClassName, $this->name) !== false) {
+                $instance = WechatService::instance($this->name, $this->appid, 'WeMini');
+            } elseif (stripos('Service,MiniApp', $this->name) !== false) {
+                $instance = WechatService::instance($this->name, $this->appid, 'WeOpen');
+            } elseif (stripos('Wechat,Config,Handler', $this->name) !== false) {
+                $className = "\\app\\service\\handler\\WechatHandler";
+                $instance = new $className($this->config);
+            }
+            if (!empty($instance)) return $instance;
+        }
+        throw new \think\Exception($this->message);
+    }
+
+    /**
+     * 加载微信实例对象
+     * @param string $token 数据格式 name|appid|appkey
+     * @return bool
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    private function auth($token = '')
+    {
+        list($this->name, $this->appid, $appkey, $this->type) = explode('-', $token . '---');
+        if (empty($this->name) || empty($this->appid) || empty($appkey)) {
+            $this->message = '缺少必要的参数AppId或AppKey';
+            return false;
+        }
+        $where = ['authorizer_appid' => $this->appid, 'status' => '1', 'is_deleted' => '0'];
+        $this->config = Db::name('WechatServiceConfig')->where($where)->find();
+        if (empty($this->config)) {
+            $this->message = '无效的微信绑定对象';
+            return false;
+        }
+        if (strtolower($this->config['appkey']) !== strtolower($appkey)) {
+            $this->message = '授权AppId与AppKey不匹配';
+            return false;
+        }
+        $this->message = '';
+        $this->name = ucfirst(strtolower($this->name));
+        $this->type = strtolower(empty($this->type) ? 'WeChat' : $this->type);
+        Db::name('WechatServiceConfig')->where($where)->setInc('total', 1);
+        return true;
+    }
+
+}

+ 184 - 0
application/service/controller/api/Push.php

@@ -0,0 +1,184 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\controller\api;
+
+use app\service\service\BuildService;
+use app\service\service\WechatService;
+use library\Controller;
+use think\Db;
+
+/**
+ * 微信推送事件处理
+ *
+ * @author Anyon <zoujingli@qq.com>
+ * @date 2016/10/18 12:38
+ */
+class Push extends Controller
+{
+
+    /**
+     * 微信API推送事件处理
+     * @param string $appid
+     * @return string
+     * @throws \WeChat\Exceptions\InvalidDecryptException
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function notify($appid)
+    {
+        if (in_array($appid, ['wx570bc396a51b8ff8', 'wxd101a85aa106f53e'])) {
+            # 全网发布接口测试
+            return \app\service\handler\PublishHandler::handler($appid);
+        } else {
+            # 接口类正常服务
+            return \app\service\handler\ReceiveHandler::handler($appid);
+        }
+    }
+
+    /**
+     * 一、处理服务推送Ticket
+     * 二、处理取消公众号授权
+     * @return string
+     * @throws \think\Exception
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     * @throws \think\exception\PDOException
+     */
+    public function ticket()
+    {
+        try {
+            $server = WechatService::service();
+            if (!($data = $server->getComonentTicket())) {
+                return "Ticket event handling failed.";
+            }
+        } catch (\Exception $e) {
+            return "Ticket event handling failed, {$e->getMessage()}";
+        }
+        if (!empty($data['AuthorizerAppid']) && isset($data['InfoType'])) {
+            # 授权成功通知
+            if ($data['InfoType'] === 'authorized') {
+                Db::name('WechatServiceConfig')->where(['authorizer_appid' => $data['AuthorizerAppid']])->update(['is_deleted' => '0']);
+            }
+            # 接收取消授权服务事件
+            if ($data['InfoType'] === 'unauthorized') {
+                Db::name('WechatServiceConfig')->where(['authorizer_appid' => $data['AuthorizerAppid']])->update(['is_deleted' => '1']);
+            }
+            # 授权更新通知
+            if ($data['InfoType'] === 'updateauthorized') {
+                $_GET['auth_code'] = $data['PreAuthCode'];
+                $this->applyAuth($server);
+            }
+        }
+        return 'success';
+    }
+
+    /**
+     * 网页授权
+     * @throws \think\Exception
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     */
+    public function oauth()
+    {
+        list($mode, $appid, $enurl, $sessid) = [
+            $this->request->get('mode'), $this->request->get('state'),
+            $this->request->get('enurl'), $this->request->get('sessid'),
+        ];
+        $service = WechatService::service();
+        $result = $service->getOauthAccessToken($appid);
+        if (empty($result['openid'])) throw new \think\Exception('网页授权失败, 无法进一步操作!');
+        cache("{$appid}_{$sessid}_openid", $result['openid'], 3600);
+        if (!empty($mode)) {
+            $wechat = new \WeChat\Oauth($service->getConfig($appid));
+            $fans = $wechat->getUserInfo($result['access_token'], $result['openid']);
+            if (empty($fans)) throw new \think\Exception('网页授权信息获取失败, 无法进一步操作!');
+            cache("{$appid}_{$sessid}_fans", $fans, 3600);
+        }
+        redirect(decode($enurl), [], 301)->send();
+    }
+
+    /**
+     * 跳转到微信服务授权页面
+     * @param string $redirect
+     * @return string
+     * @throws \think\Exception
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     * @throws \think\exception\PDOException
+     */
+    public function auth($redirect = '')
+    {
+        $fromRedirect = decode($redirect);
+        if (empty($redirect) || empty($fromRedirect)) {
+            return '请传入回跳Redirect参数 ( 请使用ENCODE加密 )';
+        }
+        # 预授权码不为空,则表示可以进行授权处理
+        $service = WechatService::service();
+        if (($auth_code = $this->request->get('auth_code'))) {
+            return $this->applyAuth($service, $fromRedirect);
+        }
+        # 生成微信授权链接,使用刷新跳转到授权网页
+        $url = url("@service/api.push/auth/{$redirect}", false, true, true);
+        if (($redirect = $service->getAuthRedirect($url))) {
+            ob_clean();
+            header("Refresh:0;url={$redirect}");
+            return "<script>window.location.href='{$redirect}';</script><a href='{$redirect}'>跳转中...</a>";
+        }
+        # 生成微信授权链接失败
+        return "<h2>Failed to create authorization. Please return to try again.</h2>";
+    }
+
+    /**
+     * 公众号授权绑定数据处理
+     * @param \WeOpen\Service $service
+     * @param string|null $redirect 授权成功回跳地址
+     * @return string
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    private function applyAuth($service, $redirect = null)
+    {
+        // 通过授权code换取公众号信息
+        $result = $service->getQueryAuthorizerInfo();
+        if (empty($result['authorizer_appid'])) {
+            return "接收微信第三方平台授权失败! ";
+        }
+        // 重新通过接口查询公众号参数
+        if (!($update = array_merge($result, $service->getAuthorizerInfo($result['authorizer_appid'])))) {
+            return '获取授权数据失败, 请稍候再试!';
+        }
+        // 生成公众号授权参数
+        $update = array_merge(BuildService::filter($update), [
+            'status' => '1', 'is_deleted' => '0', 'expires_in' => time() + 7000, 'create_at' => date('y-m-d H:i:s'),
+        ]);
+        // 微信接口APPKEY处理与更新
+        $config = Db::name('WechatServiceConfig')->where(['authorizer_appid' => $result['authorizer_appid']])->find();
+        $update['appkey'] = empty($config['appkey']) ? md5(uniqid('', true)) : $config['appkey'];
+        data_save('WechatServiceConfig', $update, 'authorizer_appid');
+        if (!empty($redirect)) { // 带上appid与appkey跳转到应用
+            $split = stripos($redirect, '?') > 0 ? '&' : '?';
+            $realurl = preg_replace(['/appid=\w+/i', '/appkey=\w+/i', '/(\?\&)$/i'], ['', '', ''], $redirect);
+            return redirect("{$realurl}{$split}appid={$update['authorizer_appid']}&appkey={$update['appkey']}");
+        }
+    }
+
+}

+ 69 - 0
application/service/handler/PublishHandler.php

@@ -0,0 +1,69 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\handler;
+
+use app\service\service\WechatService;
+
+/**
+ * 第三方平台测试上线
+ *
+ * @author Anyon <zoujingli@qq.com>
+ * @date 2016/10/27 14:14
+ */
+class PublishHandler
+{
+
+    /**
+     * 当前微信APPID
+     * @var string
+     */
+    protected static $appid;
+
+    /**
+     * 事件初始化
+     * @param string $appid
+     * @return string
+     * @throws \WeChat\Exceptions\InvalidDecryptException
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     */
+    public static function handler($appid)
+    {
+        try {
+            $wechat = WechatService::WeChatReceive($appid);
+        } catch (\Exception $e) {
+            return "Wechat message handling failed, {$e->getMessage()}";
+        }
+        /* 分别执行对应类型的操作 */
+        switch (strtolower($wechat->getMsgType())) {
+            case 'text':
+                $receive = $wechat->getReceive();
+                if ($receive['Content'] === 'TESTCOMPONENT_MSG_TYPE_TEXT') {
+                    return $wechat->text('TESTCOMPONENT_MSG_TYPE_TEXT_callback')->reply([], true);
+                } else {
+                    $key = str_replace("QUERY_AUTH_CODE:", '', $receive['Content']);
+                    WechatService::service()->getQueryAuthorizerInfo($key);
+                    return $wechat->text("{$key}_from_api")->reply([], true);
+                }
+            case 'event':
+                $receive = $wechat->getReceive();
+                return $wechat->text("{$receive['Event']}from_callback")->reply([], true);
+            default:
+                return 'success';
+        }
+    }
+
+}

+ 69 - 0
application/service/handler/ReceiveHandler.php

@@ -0,0 +1,69 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\handler;
+
+use app\service\service\WechatService;
+use think\Db;
+
+/**
+ * 微信推送消息处理
+ *
+ * @author Anyon <zoujingli@qq.com>
+ * @date 2016/10/27 14:14
+ */
+class ReceiveHandler
+{
+
+    /**
+     * 事件初始化
+     * @param string $appid
+     * @return string
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\Exception
+     */
+    public static function handler($appid)
+    {
+        try {
+            $wechat = WechatService::WeChatReceive($appid);
+        } catch (\Exception $e) {
+            return "Wechat message handling failed, {$e->getMessage()}";
+        }
+        // 验证微信配置信息
+        $config = Db::name('WechatServiceConfig')->where(['authorizer_appid' => $appid])->find();
+        if (empty($config) || empty($config['appuri'])) {
+            \think\facade\Log::error(($message = "微信{$appid}授权配置验证无效"));
+            return $message;
+        }
+        try {
+            list($data, $openid) = [$wechat->getReceive(), $wechat->getOpenid()];
+            if (isset($data['EventKey']) && is_object($data['EventKey'])) $data['EventKey'] = (array)$data['EventKey'];
+            $input = ['openid' => $openid, 'appid' => $appid, 'receive' => serialize($data), 'encrypt' => intval($wechat->isEncrypt())];
+            if (is_string($result = http_post($config['appuri'], $input, ['timeout' => 30]))) {
+                if (is_array($json = json_decode($result, true))) {
+                    return $wechat->reply($json, true, $wechat->isEncrypt());
+                } else {
+                    return $result;
+                }
+            }
+        } catch (\Exception $e) {
+            \think\facade\Log::error("微信{$appid}接口调用异常,{$e->getMessage()}");
+        }
+        return 'success';
+    }
+
+}

+ 161 - 0
application/service/handler/WechatHandler.php

@@ -0,0 +1,161 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\handler;
+
+use app\service\service\WechatService as WechatLogic;
+use think\Db;
+
+/**
+ * 微信网页授权接口
+ * Class WechatHandler
+ * @package app\wechat\handler
+ * @author Anyon <zoujingli@qq.com>
+ */
+class WechatHandler
+{
+    /**
+     * 当前微信APPID
+     * @var string
+     */
+    protected $appid;
+
+    /**
+     * 当前微信配置
+     * @var array
+     */
+    protected $config;
+
+    /**
+     * 错误消息
+     * @var string
+     */
+    protected $message;
+
+    /**
+     * Wechat constructor.
+     * @param array $config
+     */
+    public function __construct($config = [])
+    {
+        $this->config = $config;
+        $this->appid = isset($config['authorizer_appid']) ? $config['authorizer_appid'] : '';
+    }
+
+    /**
+     * 检查微信配置服务初始化状态
+     * @return boolean
+     * @throws \think\Exception
+     */
+    private function checkInit()
+    {
+        if (!empty($this->config)) return true;
+        throw new \think\Exception('Wechat Please bind Wechat first');
+    }
+
+    /**
+     * 获取当前公众号配置
+     * @return array|boolean
+     * @throws \think\Exception
+     */
+    public function getConfig()
+    {
+        $this->checkInit();
+        $info = Db::name('WechatServiceConfig')->where(['authorizer_appid' => $this->appid])->find();
+        if (empty($info)) return false;
+        if (isset($info['id'])) unset($info['id']);
+        return $info;
+    }
+
+    /**
+     * 设置微信接口通知URL地址
+     * @param string $notifyUri 接口通知URL地址
+     * @return boolean
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function setApiNotifyUri($notifyUri)
+    {
+        $this->checkInit();
+        if (empty($notifyUri)) throw new \think\Exception('请传入微信通知URL');
+        list($where, $data) = [['authorizer_appid' => $this->appid], ['appuri' => $notifyUri]];
+        return Db::name('WechatServiceConfig')->where($where)->update($data) !== false;
+    }
+
+    /**
+     * 更新接口Appkey(成功返回新的Appkey)
+     * @return bool|string
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function updateApiAppkey()
+    {
+        $this->checkInit();
+        $data = ['appkey' => md5(uniqid())];
+        Db::name('WechatServiceConfig')->where(['authorizer_appid' => $this->appid])->update($data);
+        return $data['appkey'];
+    }
+
+    /**
+     * 获取公众号的配置参数
+     * @param string $name 参数名称
+     * @return array|string
+     * @throws \think\Exception
+     */
+    public function config($name = null)
+    {
+        $this->checkInit();
+        return WechatLogic::WeChatScript($this->appid)->config->get($name);
+    }
+
+    /**
+     * 微信网页授权
+     * @param string $sessid 当前会话id(可用session_id()获取)
+     * @param string $selfUrl 当前会话URL地址(需包含域名的完整URL地址)
+     * @param int $fullMode 网页授权模式(0静默模式,1高级授权)
+     * @return array|bool
+     * @throws \think\Exception
+     */
+    public function oauth($sessid, $selfUrl, $fullMode = 0)
+    {
+        $this->checkInit();
+        $fans = cache("{$this->appid}_{$sessid}_fans");
+        $openid = cache("{$this->appid}_{$sessid}_openid");
+        if (!empty($openid) && (empty($fullMode) || !empty($fans))) {
+            return ['openid' => $openid, 'fans' => $fans, 'url' => ''];
+        }
+        $service = WechatLogic::service();
+        $mode = empty($fullMode) ? 'snsapi_base' : 'snsapi_userinfo';
+        $url = url('@service/api.push/oauth', '', true, true);
+        $params = ['mode' => $fullMode, 'sessid' => $sessid, 'enurl' => encode($selfUrl)];
+        $authurl = $service->getOauthRedirect($this->appid, $url . '?' . http_build_query($params), $mode);
+        return ['openid' => $openid, 'fans' => $fans, 'url' => $authurl];
+    }
+
+    /**
+     * 微信网页JS签名
+     * @param string $url 当前会话URL地址(需包含域名的完整URL地址)
+     * @return array|boolean
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     * @throws \think\Exception
+     */
+    public function jsSign($url)
+    {
+        $this->checkInit();
+        return WechatLogic::WeChatScript($this->appid)->getJsSign($url);
+    }
+
+}

+ 88 - 0
application/service/queue/WechatQueue.php

@@ -0,0 +1,88 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\queue;
+
+use app\service\service\WechatService;
+use app\wechat\service\FansService;
+use think\console\Input;
+use think\console\Output;
+use think\Db;
+
+/**
+ * Class Jobs
+ * @package app\wechat
+ */
+class WechatQueue
+{
+    /**
+     * 当前类名
+     * @var string
+     */
+    const URI = self::class;
+
+    /**
+     * 当前操作APPID
+     * @var string
+     */
+    protected $appid;
+
+    /**
+     * 执行任务
+     * @param Input $input
+     * @param Output $output
+     * @param array $data
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function execute(Input $input, Output $output, array $data = [])
+    {
+        $this->appid = $data['appid'];
+        $wechat = WechatService::WeChatUser($this->appid);
+        // 获取远程粉丝
+        list($next, $done) = ['', 0];
+        $output->writeln('Start synchronizing fans from the Wechat server');
+        while (!is_null($next) && is_array($result = $wechat->getUserList($next)) && !empty($result['data']['openid'])) {
+            $done += $result['count'];
+            foreach (array_chunk($result['data']['openid'], 100) as $chunk) {
+                if (is_array($list = $wechat->getBatchUserInfo($chunk)) && !empty($list['user_info_list'])) {
+                    foreach ($list['user_info_list'] as $user) FansService::set($user, $this->appid);
+                }
+            }
+            $next = $result['total'] > $done ? $result['next_openid'] : null;
+        }
+        // 同步粉丝黑名单
+        list($next, $done) = ['', 0];
+        $output->writeln('Start synchronizing black from the Wechat server');
+        while (!is_null($next) && is_array($result = $wechat->getBlackList($next)) && !empty($result['data']['openid'])) {
+            $done += $result['count'];
+            foreach (array_chunk($result['data']['openid'], 100) as $chunk) {
+                $where = [['is_black', 'eq', '0'], ['openid', 'in', $chunk]];
+                Db::name('WechatFans')->where($where)->update(['is_black' => '1']);
+            }
+            $next = $result['total'] > $done ? $result['next_openid'] : null;
+        }
+        // 同步粉丝标签列表
+        $output->writeln('Start synchronizing tags from the Wechat server');
+        if (is_array($list = WechatService::WeChatTags($this->appid)->getTags()) && !empty($list['tags'])) {
+            foreach ($list['tags'] as &$tag) $tag['appid'] = $this->appid;
+            Db::name('WechatFansTags')->where('1=1')->delete();
+            Db::name('WechatFansTags')->insertAll($list['tags']);
+        }
+    }
+
+}

+ 53 - 0
application/service/service/BuildService.php

@@ -0,0 +1,53 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\service;
+
+/**
+ * 授权数据处理
+ * Class Build
+ * @package app\service\service
+ */
+class BuildService
+{
+
+    /**
+     * 授权数据过滤转换处理
+     * @param array $info
+     * @return mixed
+     */
+    public static function filter(array $info)
+    {
+        if (isset($info['func_info'])) $info['func_info'] = join(',', array_map(function ($tmp) {
+            return $tmp['funcscope_category']['id'];
+        }, $info['func_info']));
+        $info['verify_type_info'] = join(',', $info['verify_type_info']);
+        $info['service_type_info'] = join(',', $info['service_type_info']);
+        $info['business_info'] = json_encode($info['business_info'], JSON_UNESCAPED_UNICODE);
+        // 微信类型:  0 代表订阅号, 2 代表服务号, 3 代表小程序
+        $info['service_type'] = intval($info['service_type_info']) === 2 ? 2 : 0;
+        if (!empty($info['MiniProgramInfo'])) {
+            // 微信类型:  0 代表订阅号, 2 代表服务号, 3 代表小程序
+            $info['service_type'] = 3;
+            // 小程序信息
+            $info['miniprograminfo'] = json_encode($info['MiniProgramInfo'], JSON_UNESCAPED_UNICODE);
+        }
+        unset($info['MiniProgramInfo']);
+        // 微信认证: -1 代表未认证, 0 代表微信认证
+        $info['verify_type'] = intval($info['verify_type_info']) !== 0 ? -1 : 0;
+        return $info;
+    }
+
+}

+ 83 - 0
application/service/service/MichatService.php

@@ -0,0 +1,83 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\service;
+
+use library\tools\Http;
+use think\Exception;
+
+/**
+ * 小米消息服务
+ * Class MichatService
+ * @package app\service\service
+ */
+class MichatService
+{
+    const URI = 'https://mimc.chat.xiaomi.net';
+
+    const BIZ_TYPE_PING = 'PING';
+    const BIZ_TYPE_POND = 'PONG';
+    const BIZ_TYPE_TEXT = 'TEXT';
+    const BIZ_TYPE_PIC_FILE = 'PIC_FILE';
+    const BIZ_TYPE_BIN_FILE = 'BIN_FILE';
+    const BIZ_TYPE_AUDIO_FILE = 'AUDIO_FILE';
+    const MSG_TYPE_BASE64 = 'base64';
+
+    /**
+     * 给指定账号推送消息内容
+     * @param string $from 消息来源
+     * @param string $to 消息目标
+     * @param string $message 消息内容
+     * @return bool|string
+     * @throws Exception
+     * @throws \think\exception\PDOException
+     */
+    public static function push($from, $to, $message)
+    {
+        return self::post('/api/push/p2p/', [
+            'appId'        => sysconf('michat_appid'),
+            'appKey'       => sysconf('michat_appkey'),
+            'appSecret'    => sysconf('michat_appsecert'),
+            'fromAccount'  => $from,
+            'fromResource' => $from,
+            'toAccount'    => $to,
+            'msg'          => base64_encode($message),
+            'msgType'      => 'base64',
+            'bizType'      => '',
+            'isStore'      => false,
+        ]);
+    }
+
+    /**
+     * POST提交消息数据
+     * @param string $api 接口地址
+     * @param array $data 接口数据
+     * @return bool|string
+     * @throws Exception
+     */
+    private static function post($api, array $data)
+    {
+        $result = json_decode(Http::request('post', self::URI . $api, [
+            'data'    => json_encode($data, JSON_UNESCAPED_UNICODE),
+            'headers' => ['Content-Type: application/json'],
+        ]), true);
+        if (isset($result['code']) && intval($result['code']) === 200) {
+            return $result['data'];
+        } else {
+            throw new Exception($result['message'], $result['code']);
+        }
+    }
+
+}

+ 158 - 0
application/service/service/WechatService.php

@@ -0,0 +1,158 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\service\service;
+
+use think\Db;
+
+/**
+ * 微信数据服务
+ * Class WechatService
+ * @package app\service\service
+ * @method \WeChat\Card WeChatCard($appid) static 微信卡券管理
+ * @method \WeChat\Custom WeChatCustom($appid) static 微信客服消息
+ * @method \WeChat\Limit WeChatLimit($appid) static 接口调用频次限制
+ * @method \WeChat\Media WeChatMedia($appid) static 微信素材管理
+ * @method \WeChat\Menu WeChatMenu($appid) static 微信菜单管理
+ * @method \WeChat\Oauth WeChatOauth($appid) static 微信网页授权
+ * @method \WeChat\Pay WeChatPay($appid) static 微信支付商户
+ * @method \WeChat\Product WeChatProduct($appid) static 微信商店管理
+ * @method \WeChat\Qrcode WeChatQrcode($appid) static 微信二维码管理
+ * @method \WeChat\Receive WeChatReceive($appid) static 微信推送管理
+ * @method \WeChat\Scan WeChatScan($appid) static 微信扫一扫接入管理
+ * @method \WeChat\Script WeChatScript($appid) static 微信前端支持
+ * @method \WeChat\Shake WeChatShake($appid) static 微信揺一揺周边
+ * @method \WeChat\Tags WeChatTags($appid) static 微信用户标签管理
+ * @method \WeChat\Template WeChatTemplate($appid) static 微信模板消息
+ * @method \WeChat\User WeChatUser($appid) static 微信粉丝管理
+ * @method \WeChat\Wifi WeChatWifi($appid) static 微信门店WIFI管理
+ *
+ * ----- WeMini -----
+ * @method \WeMini\Account WeMiniAccount($appid) static 小程序账号管理
+ * @method \WeMini\Basic WeMiniBasic($appid) static 小程序基础信息设置
+ * @method \WeMini\Code WeMiniCode($appid) static 小程序代码管理
+ * @method \WeMini\Domain WeMiniDomain($appid) static 小程序域名管理
+ * @method \WeMini\Tester WeMinitester($appid) static 小程序成员管理
+ * @method \WeMini\User WeMiniUser($appid) static 小程序帐号管理
+ * --------------------
+ * @method \WeMini\Crypt WeMiniCrypt($options = []) static 小程序数据加密处理
+ * @method \WeMini\Delivery WeMiniDelivery($options = []) static 小程序即时配送
+ * @method \WeMini\Image WeMiniImage($options = []) static 小程序图像处理
+ * @method \WeMini\Logistics WeMiniLogistics($options = []) static 小程序物流助手
+ * @method \WeMini\Message WeMiniMessage($options = []) static 小程序动态消息
+ * @method \WeMini\Ocr WeMiniOcr($options = []) static 小程序ORC服务
+ * @method \WeMini\Plugs WeMiniPlugs($options = []) static 小程序插件管理
+ * @method \WeMini\Poi WeMiniPoi($options = []) static 小程序地址管理
+ * @method \WeMini\Qrcode WeMiniQrcode($options = []) static 小程序二维码管理
+ * @method \WeMini\Security WeMiniSecurity($options = []) static 小程序内容安全
+ * @method \WeMini\Soter WeMiniSoter($options = []) static 小程序生物认证
+ * @method \WeMini\Template WeMiniTemplate($options = []) static 小程序模板消息支持
+ * @method \WeMini\Total WeMiniTotal($options = []) static 小程序数据接口
+ *
+ * ----- WePay -----
+ * @method \WePay\Bill WePayBill($appid) static 微信商户账单及评论
+ * @method \WePay\Order WePayOrder($appid) static 微信商户订单
+ * @method \WePay\Refund WePayRefund($appid) static 微信商户退款
+ * @method \WePay\Coupon WePayCoupon($appid) static 微信商户代金券
+ * @method \WePay\Redpack WePayRedpack($appid) static 微信红包支持
+ * @method \WePay\Transfers WePayTransfers($appid) static 微信商户打款到零钱
+ * @method \WePay\TransfersBank WePayTransfersBank($appid) static 微信商户打款到银行卡
+ *
+ * ----- WeOpen -----
+ * @method \WeOpen\Login login() static 第三方微信登录
+ * @method \WeOpen\Service service() static 第三方服务
+ *
+ * ----- ThinkService -----
+ * @method mixed wechat() static 第三方微信工具
+ * @method mixed config() static 第三方配置工具
+ */
+class WechatService
+{
+
+    /**
+     * 接口类型模式
+     * @var string
+     */
+    private static $type = 'WeChat';
+
+    /**
+     * 实例微信对象
+     * @param string $name
+     * @param string $appid 授权公众号APPID
+     * @param string $type 将要获取SDK类型
+     * @return mixed
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public static function instance($name, $appid = '', $type = null)
+    {
+        $config = [
+            'cache_path'               => env('runtime_path') . 'wechat',
+            'component_appid'          => sysconf('component_appid'),
+            'component_token'          => sysconf('component_token'),
+            'component_appsecret'      => sysconf('component_appsecret'),
+            'component_encodingaeskey' => sysconf('component_encodingaeskey'),
+        ];
+        // 注册授权公众号 AccessToken 处理
+        $config['GetAccessTokenCallback'] = function ($authorizerAppid) use ($config) {
+            $where = ['authorizer_appid' => $authorizerAppid];
+            if (!($refreshToken = Db::name('WechatServiceConfig')->where($where)->value('authorizer_refresh_token'))) {
+                throw new \think\Exception('The WeChat information is not configured.', '404');
+            }
+            $open = new \WeOpen\MiniApp($config);
+            $result = $open->refreshAccessToken($authorizerAppid, $refreshToken);
+            if (empty($result['authorizer_access_token']) || empty($result['authorizer_refresh_token'])) {
+                throw new \think\Exception($result['errmsg'], '0');
+            }
+            Db::name('WechatServiceConfig')->where($where)->update([
+                'authorizer_access_token'  => $result['authorizer_access_token'],
+                'authorizer_refresh_token' => $result['authorizer_refresh_token'],
+            ]);
+            return $result['authorizer_access_token'];
+        };
+        $app = new \WeOpen\MiniApp($config);
+        if (in_array(strtolower($name), ['service', 'miniapp'])) {
+            return $app;
+        }
+        if (!in_array($type, ['WeChat', 'WeMini'])) {
+            $type = self::$type;
+        }
+        return $app->instance($name, $appid, $type);
+    }
+
+    /**
+     * 静态初始化对象
+     * @param string $name
+     * @param array $arguments
+     * @return mixed
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public static function __callStatic($name, $arguments)
+    {
+        if (substr($name, 0, 6) === 'WeMini') {
+            self::$type = 'WeMini';
+            $name = substr($name, 6);
+        } elseif (substr($name, 0, 6) === 'WeChat') {
+            self::$type = 'WeChat';
+            $name = substr($name, 6);
+        } elseif (substr($name, 0, 5) === 'WePay') {
+            self::$type = 'WePay';
+            $name = substr($name, 5);
+        }
+        return self::instance($name, isset($arguments[0]) ? $arguments[0] : '', self::$type);
+    }
+
+}

+ 44 - 0
application/service/view/config/form.html

@@ -0,0 +1,44 @@
+<form onsubmit="return false;" action="{:request()->url()}" data-auto="true" method="post" class='layui-form layui-card' autocomplete="off">
+
+    <div class="layui-card-body padding-left-40">
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">开放平台服务 AppID</span>
+                <input name="component_appid" required pattern="^.{18}$" maxlength="18" placeholder="请输入18位开放平台服务AppID" value="{:sysconf('component_appid')}" class="layui-input">
+            </label>
+            <p class="help-block">开放平台服务 AppID,需要在微信开放平台获取</p>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">开放平台服务 AppSecret</span>
+                <input name="component_appsecret" required pattern="^.{32}$" maxlength="32" placeholder="请输入32位开放平台服务AppSecret" value="{:sysconf('component_appsecret')}" class="layui-input">
+            </label>
+            <p class="help-block">开放平台服务 AppSecret,需要在微信开放平台获取</p>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">开放平台消息校验 Token</span>
+                <input name="component_token" required placeholder="请输入微信消息校验Token" value="{:sysconf('component_token')}" class="layui-input">
+            </label>
+            <p class="help-block">开发者在代替微信接收到消息时,用此 Token 来校验消息</p>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="relative block">
+                <span class="color-green font-w7">开放平台消息加解密 AesKey</span>
+                <input name="component_encodingaeskey" required pattern="^.{43}$" maxlength="43" placeholder="请输入43位微信消息加解密Key" value="{:sysconf('component_encodingaeskey')}" class="layui-input">
+            </label>
+            <p class="help-block">在代替微信收发消息时使用,必须是长度为43位字母和数字组合的字符串</p>
+        </div>
+
+        <div class="hr-line-dashed"></div>
+
+        <div class="text-center padding-bottom-15 margin-bottom-20">
+            <button class="layui-btn" type="submit">保存配置</button>
+        </div>
+
+    </div>
+</form>

+ 113 - 0
application/service/view/config/index.html

@@ -0,0 +1,113 @@
+{extend name="admin@main"}
+
+{block name="button"}
+
+{if auth("edit")}
+<button data-modal="{:url('edit')}" class='layui-btn layui-btn-sm layui-btn-primary'>配置平台参数</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+<div class="relative">
+
+    <div class="think-box-shadow border-0 margin-bottom-20">
+        强烈建议安装 YAR 扩展来实现接口通信,SOAP 不能正常显示接口的异常信息
+    </div>
+
+    <div class="think-box-shadow padding-right-40 padding-left-40">
+        <div class="layui-row layui-col-space18">
+            <div class="layui-col-md6">
+                <div class="layui-form-item">
+                    <span class="color-green font-w7">开放平台服务</span>
+                    <span class="color-desc margin-left-5">AppID</span>
+                    <label class="relative block">
+                        <input disabled class="layui-input layui-bg-gray" value="{:sysconf('component_appid')}">
+                        <a data-copy="{:sysconf('component_appid')}" class="fa fa-copy right-btn"></a>
+                    </label>
+                    <p class="help-block">开放平台服务 AppID,需要在微信开放平台获取</p>
+                </div>
+                <div class="layui-form-item">
+                    <span class="color-green font-w7">开放平台服务</span>
+                    <span class="color-desc margin-left-5">AppSecret</span>
+                    <label class="relative block">
+                        <input disabled class="layui-input layui-bg-gray" value="{:sysconf('component_appsecret')}">
+                        <a data-copy="{:sysconf('component_appsecret')}" class="fa fa-copy right-btn"></a>
+                    </label>
+                    <p class="help-block">开放平台服务 AppSecret,需要在微信开放平台获取</p>
+                </div>
+                <div class="layui-form-item">
+                    <span class="color-green font-w7">开放平台消息校验</span>
+                    <span class="color-desc margin-left-5">Token</span>
+                    <label class="relative block">
+                        <input disabled class="layui-input layui-bg-gray" value="{:sysconf('component_token')}">
+                        <a data-copy="{:sysconf('component_token')}" class="fa fa-copy right-btn"></a>
+                    </label>
+                    <p class="help-block">开发者在代替微信接收到消息时,用此 Token 来校验消息</p>
+                </div>
+                <div class="layui-form-item">
+                    <span class="color-green font-w7">开放平台消息加解密</span>
+                    <span class="color-desc margin-left-5">AesKey</span>
+                    <label class="relative block">
+                        <input disabled class="layui-input layui-bg-gray" value="{:sysconf('component_encodingaeskey')}">
+                        <a data-copy="{:sysconf('component_encodingaeskey')}" class="fa fa-copy right-btn"></a>
+                    </label>
+                    <p class="help-block">在代替微信收发消息时使用,必须是长度为43位字母和数字组合的字符串</p>
+                </div>
+            </div>
+
+            <div class="layui-col-md6">
+                <div class="layui-form-item">
+                    <p class="color-green font-w7">授权发起页域名</p>
+                    <label class="relative block">
+                        <input disabled class="layui-input layui-bg-gray" value="{:request()->host()}">
+                        <a data-copy="{:request()->host()}" class="fa fa-copy right-btn"></a>
+                    </label>
+                    <p class="help-block">从本域名跳转到登录授权页才可以完成登录授权,无需填写域名协议前缀</p>
+                </div>
+                <div class="layui-form-item">
+                    <span class="color-green font-w7">授权事件接收地址</span>
+                    <label class="relative block">
+                        <input disabled class="layui-input layui-bg-gray" value="{:url('@service/api.push/ticket','',false,true)}">
+                        <a data-copy="{:url('@service/api.push/ticket','',false,true)}" class="fa fa-copy right-btn"></a>
+                    </label>
+                    <p class="help-block">用于接收取消授权通知、授权成功通知、授权更新通知、接收 TICKET 凭据</p>
+                </div>
+                <div class="layui-form-item">
+                    <span class="color-green font-w7">微信消息与事件接收</span>
+                    <label class="relative block">
+                        <input disabled class="layui-input layui-bg-gray" value="{:url('@service/api.push/notify/\$APPID\$','',false,true)}">
+                        <a data-copy="{:url('@service/api.push/notify/\$APPID\$','',false,true)}" class="fa fa-copy right-btn"></a>
+                    </label>
+                    <p class="help-block">通过该 URL 接收微信消息和事件推送,$APPID$ 将被替换为微信 AppId</p>
+                </div>
+                <div class="layui-form-item">
+                    <p class="color-green font-w7">客户端系统 Yar 模块接口</p>
+                    <label class="relative block"><input disabled class="layui-input layui-bg-gray" value="{:url('@service/api.client/yar/PARAM','',false,true)}"></label>
+                    <p class="help-block">客户端 Yar 接口,PARAM 规则 AppName-AppId-AppKey-AppType</p>
+                </div>
+                <div class="layui-form-item">
+                    <p class="color-green font-w7">客户端系统 Soap 模块接口</p>
+                    <label class="relative block"><input disabled class="layui-input layui-bg-gray" value="{:url('@service/api.client/soap/PARAM','',false,true)}"></label>
+                    <p class="help-block">客户端 Soap 接口,PARAM 规则 AppName-AppId-AppKey-AppType</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+{/block}
+
+{block name='style'}
+<style>
+    .right-btn {
+        top: 0;
+        right: 0;
+        width: 38px;
+        height: 38px;
+        display: inline-block;
+        position: absolute;
+        text-align: center;
+        line-height: 38px;
+    }
+</style>
+{/block}

+ 98 - 0
application/service/view/fans/index.html

@@ -0,0 +1,98 @@
+{extend name='admin@main'}
+
+{block name="button"}
+
+{if auth("service/fans/setblack")}
+<button data-action='{:url("setblack")}' data-rule="openid#{key}" data-csrf="{:systoken('service/fans/setblack')}" class='layui-btn layui-btn-sm layui-btn-primary'>批量拉黑</button>
+{/if}
+
+{if auth("service/fans/delblack")}
+<button data-action='{:url("delblack")}' data-rule="openid#{key}" data-csrf="{:systoken('service/fans/delblack')}" class='layui-btn layui-btn-sm layui-btn-primary'>取消拉黑</button>
+{/if}
+
+{if auth("service/fans/sync")}
+<button data-load='{:url("sync")}' class='layui-btn layui-btn-sm layui-btn-primary'>同步粉丝</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+<div class="think-box-shadow">
+    {include file='fans/index_search'}
+    <table class="layui-table margin-top-10" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='list-table-check-td think-checkbox'><input data-auto-none data-check-target='.list-check-box' type='checkbox'></th>
+            <th width="180px" class='text-left nowrap'>微信昵称</th>
+            <th width="180px" class="text-left nowrap">粉丝标签</th>
+            <th width="150px" class='text-left nowrap'>性别语言</th>
+            <th width="180px" class='text-left nowrap'>关注时间</th>
+            <th width="80px"></th>
+            <th></th>
+        </tr>
+        </thead>
+        {/notempty}
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr>
+            <td class='list-table-check-td think-checkbox'>
+                <input class="list-check-box" value='{$vo.openid}' type='checkbox'>
+            </td>
+            <td class='text-left nowrap relative layui-elip'>
+                <img src="{$vo.headimgurl|default=''}" onerror="$(this).remove()" data-tips-image class="inline-block" style="width:40px;height:40px;vertical-align:top;margin-right:5px">
+                <div class="inline-block">
+                    昵称:{$vo.nickname|default='--'}
+                    <br>
+                    区域:{$vo.country|default='--'} {$vo.province} {$vo.city}
+                </div>
+            </td>
+            <td class="text-left padding-0">
+                <div style="max-height:60px;overflow:auto">{foreach $vo.tags as $t}<p><span class="layui-badge layui-bg-cyan margin-right-5">{$t|default='--'}</span></p>{/foreach}</div>
+            </td>
+            <td class='text-left nowrap'>
+                性别:{switch name='vo.sex'}{case value='1'}男{/case}{case value='2'}女{/case}{default}未知{/switch}
+                <br>
+                语言:{$vo.language|raw}
+            </td>
+            <td class='text-left nowrap'>
+                日期:{$vo.subscribe_at|format_datetime|str_replace=' ','<br>时间:',###|raw}
+            </td>
+            <td class='text-center nowrap'>
+                {eq name='vo.subscribe' value='0'}
+                <span class="layui-badge">未关注</span>
+                {else}
+                <span class="layui-badge layui-bg-green">已关注</span>
+                {/eq}
+                <br>
+                {eq name='vo.is_black' value='0'}
+                <span class="layui-badge layui-bg-green">未拉黑</span>
+                {else}
+                <span class="layui-badge">已拉黑</span>
+                {/eq}
+            </td>
+            <td class="nowrap">
+
+                {eq name='vo.is_black' value='0'}
+                <!--{if auth("service/fans/setblack")}-->
+                <a class="margin-left-10 layui-btn layui-btn-normal layui-btn-sm" data-action="{:url('setblack')}" data-value="openid#{$vo.openid}" data-csrf="{:systoken('service/fans/setblack')}">拉 黑</a>
+                <!--{/if}-->
+                {else}
+                <!--{if auth("service/fans/delblack")}-->
+                <a class="margin-left-10 layui-btn layui-btn-normal layui-btn-sm" data-action="{:url('delblack')}" data-value="openid#{$vo.openid}" data-csrf="{:systoken('service/fans/delblack')}">拉 白</a>
+                <!--{/if}-->
+                {/eq}
+
+                {if auth("service/fans/remove")}
+                <a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该粉丝吗?" data-action="{:url('remove')}" data-value="id#{$vo.id}" data-csrf="{:systoken('service/fans/remove')}">删 除</a>
+                {/if}
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+
+</div>
+{/block}

+ 76 - 0
application/service/view/fans/index_search.html

@@ -0,0 +1,76 @@
+<fieldset>
+    <legend>条件搜索</legend>
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">当前微信</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="appid" lay-filter="appid" lay-search>
+                    <!--{foreach $wechats as $k=>$v}-->
+                    <!--{if $k eq $appid}-->
+                    <option selected value="{$k}">{$v}</option>
+                    <!--{else}-->
+                    <option value="{$k}">{$v}</option>
+                    <!--{/if}-->
+                    <!--{/foreach}-->
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">微信昵称</label>
+            <div class="layui-input-inline">
+                <input name="nickname" value="{$Think.get.nickname|default=''}" placeholder="请输入微信昵称" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">关注状态</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="subscribe">
+                    {foreach [''=>'-- 全部 --','0'=>'显示未关注的粉丝','1'=>'显示已关注的粉丝'] as $k=>$v}
+                    {eq name='Think.get.subscribe' value='$k.""'}
+                    <option selected value="{$k}">{$v}</option>
+                    {else}
+                    <option value="{$k}">{$v}</option>
+                    {/eq}
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">拉黑状态</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="is_black">
+                    {foreach [''=>'-- 全部 --','0'=>'显示未拉黑的粉丝','1'=>'显示已拉黑的粉丝'] as $k=>$v}
+                    {eq name='Think.get.is_black' value='$k.""'}
+                    <option selected value="{$k}">{$v}</option>
+                    {else}
+                    <option value="{$k}">{$v}</option>
+                    {/eq}
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">关注时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="subscribe_at" value="{$Think.get.subscribe_at|default=''}" placeholder="请选择关注时间" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <button id="SearchButton" class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+    </form>
+
+</fieldset>
+
+<script>
+    form.render();
+    form.on('select(appid)', function () {
+        $('#SearchButton').trigger('click');
+    });
+</script>

+ 84 - 0
application/service/view/index/index.html

@@ -0,0 +1,84 @@
+{extend name="admin@main"}
+
+{block name="button"}
+
+{if auth("service/index/syncall")}
+<button data-load="{:url('syncall')}" class='layui-btn layui-btn-sm layui-btn-primary'>同步所有授权</button>
+{/if}
+
+{/block}
+
+{block name="content"}
+<div class="think-box-shadow">
+    {include file='index/index_search'}
+    <table class="layui-table margin-top-10" lay-skin="line">
+        {notempty name='list'}
+        <thead>
+        <tr>
+            <th class='text-left nowrap' style="width:390px">接口信息</th>
+            <th class='text-left nowrap' style="width:120px">服务号信息</th>
+            <th class='text-left nowrap'></th>
+            <th class='text-left nowrap'></th>
+        </tr>
+        </thead>
+        {/notempty}
+        <tbody>
+        {foreach $list as $key=>$vo}
+        <tr>
+            <td class='text-left nowrap'>
+                <div class="inline-block text-top margin-right-5">
+                    <img onerror="this.src='__ROOT__/static/theme/img/404_icon.png'" style="width:60px;height:60px" data-tips-text="微信头像" data-tips-image src="{$vo.head_img|default=''}">
+                </div>
+                <div class="inline-block">
+                    公众号APPID:{$vo.authorizer_appid}&nbsp;&nbsp;&nbsp;&nbsp;调用次数:{$vo.total}<br>
+                    接口授权密钥:{$vo.appkey|default='<span class="color-desc">未生成接口服务密码, 请稍候授权绑定</span>'|raw}<br>
+                    消息推送接口:{$vo.appuri|default='<span class="color-desc">未配置消息推送接口</span>'|raw}
+                </div>
+            </td>
+            <td class='text-left nowrap'>
+                <div class="inline-block text-top margin-right-5">
+                    <img onerror="this.src='__ROOT__/static/theme/img/404_icon.png'" style="width:60px;height:60px" data-tips-text="微信二维码" data-tips-image src="{$vo.qrcode_url|local_image}">
+                </div>
+                <div class="inline-block">
+                    昵称:{$vo.nick_name|default='<span class="color-desc">未获取到公众号昵称</span>'|raw}<br>
+                    公司:{$vo.principal_name|default='<span class="color-desc">未获取到公司名字</span>'|raw}<br>
+                    状态:{if $vo.service_type eq 2}服务号{elseif $vo.service_type eq 3}小程序{else}订阅号{/if} /
+                    {$vo.verify_type == -1 ? '<span class="color-red">未认证</span>' : '<span class="color-green">已认证</span>'} /
+                    {if $vo.status eq 0}<span class="color-red">已禁用</span>{elseif $vo.status eq 1}<span class="color-green">使用中</span>{/if}
+                </div>
+            </td>
+            <td class="text-left nowrap">
+                账号:{$vo.user_name|default='--'}<br>
+                日期:{$vo.create_at|format_datetime|str_replace=' ','<br>时间:',###|raw}
+            </td>
+            <td class='text-left nowrap'>
+
+                {if $vo.status eq 1 and auth("servce/index/forbid")}
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('forbid')}" data-csrf="{:systoken('service/index/forbid')}" data-value="id#{$vo.id};status#0">禁 用</a>
+                {elseif auth("servce/index/resume")}
+                <a class="layui-btn layui-btn-sm layui-btn-warm" data-action="{:url('resume')}" data-csrf="{:systoken('service/index/resume')}" data-value="id#{$vo.id};status#1">启 用</a>
+                {/if}
+
+                {if auth("service/index/sync")}
+                <a class="layui-btn layui-btn-sm" data-load='{:url("service/index/sync")}?appid={$vo.authorizer_appid}'>同 步</a>
+                {/if}
+
+                {if auth("service/index/remove")}
+                <a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该公众号吗?" data-action="{:url('remove')}" data-csrf="{:systoken('service/index/remove')}" data-value="id#{$vo.id}">删 除</a>
+                {/if}
+
+                {if auth("service/index/clearquota")}
+                <a class="layui-btn layui-btn-sm layui-btn-primary" data-confirm="每个公众号每个月有10次清零机会,请谨慎使用!" data-load='{:url("service/index/clearquota")}?appid={$vo.authorizer_appid}'>清 零</a>
+                {/if}
+
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+    </table>
+
+    {empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
+
+</div>
+
+{/block}

+ 76 - 0
application/service/view/index/index_search.html

@@ -0,0 +1,76 @@
+<fieldset>
+
+    <legend>条件搜索</legend>
+
+    <form class="layui-form layui-form-pane form-search" action="{:request()->url()}" onsubmit="return false" method="get" autocomplete="off">
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">微信标识</label>
+            <div class="layui-input-inline">
+                <input name="authorizer_appid" value="{$Think.get.authorizer_appid|default=''}" placeholder="请输入微信APPID" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">微信名称</label>
+            <div class="layui-input-inline">
+                <input name="nick_name" value="{$Think.get.nick_name|default=''}" placeholder="请输入微信名称" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">类型过滤</label>
+            <div class="layui-input-inline">
+                <select name="service_type" class="layui-select">
+                    <option value="">- 全部 -</option>
+                    {foreach ['0'=>'显示订阅号类型','2'=>'显示服务号类型','3'=>'显示小程序类型'] as $k=>$v}
+                    <!--{if $k.'' eq $Think.get.service_type}-->
+                    <option selected value="{$k}">{$v}</option>
+                    <!--{else}-->
+                    <option value="{$k}">{$v}</option>
+                    <!--{/if}-->
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">使用状态</label>
+            <div class="layui-input-inline">
+                <select class="layui-select" name="status">
+                    {foreach [''=>'- 全部 -','0'=>'已禁用的账号','1'=>'使用中的账号'] as $k=>$v}
+                    {eq name='Think.get.status' value='$k.""'}
+                    <option selected value="{$k}">{$v}</option>
+                    {else}
+                    <option value="{$k}">{$v}</option>
+                    {/eq}
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">注册公司</label>
+            <div class="layui-input-inline">
+                <input name="principal_name" value="{$Think.get.principal_name|default=''}" placeholder="请输入注册公司" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <label class="layui-form-label">授权时间</label>
+            <div class="layui-input-inline">
+                <input data-date-range name="create_at" value="{$Think.get.create_at|default=''}" placeholder="请选择授权时间" class="layui-input">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-inline">
+            <button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
+        </div>
+
+    </form>
+
+</fieldset>
+
+<script>
+    window.form.render();
+</script>

+ 1 - 0
application/service/view/not-auth.html

@@ -0,0 +1 @@
+还没有授权,请授权公众号

+ 196 - 0
application/store/command/AutoRun.php

@@ -0,0 +1,196 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+use think\Db;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\ModelNotFoundException;
+use think\Exception;
+use think\exception\DbException;
+use think\exception\PDOException;
+use We;
+
+/**
+ * 商城数据处理指令
+ * Class AutoRun
+ * @package app\store\command
+ */
+class AutoRun extends Command
+{
+
+    /**
+     * 配置指令信息
+     */
+    protected function configure()
+    {
+        $this->setName('xclean:store')->setDescription('[清理]检查并处理商城任务');
+    }
+
+    /**
+     * 业务指令执行
+     * @param Input $input
+     * @param Output $output
+     * @throws Exception
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws DbException
+     * @throws PDOException
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        // 自动取消30分钟未支付的订单
+        $this->autoCancelOrder();
+        // 清理一天前未支付的订单
+        $this->autoRemoveOrder();
+        // 订单自动退款处理
+        // $this->autoRefundOrder();
+        // 提现自动打款处理
+        // $this->autoTransfer();
+    }
+
+    /**
+     * 自动取消30分钟未支付的订单
+     * @throws Exception
+     * @throws PDOException
+     */
+    private function autoCancelOrder()
+    {
+        $datetime = $this->getDatetime('store_order_wait_time');
+        $where = [['status', 'in', ['1', '2']], ['pay_state', 'eq', '0'], ['create_at', '<', $datetime]];
+        $count = Db::name('StoreOrder')->where($where)->update([
+            'status'       => '0',
+            'cancel_state' => '1',
+            'cancel_at'    => date('Y-m-d H:i:s'),
+            'cancel_desc'  => '30分钟未完成支付自动取消订单',
+        ]);
+        if ($count > 0) {
+            $this->output->info("共计自动取消了30分钟未支付的{$count}笔订单!");
+        } else {
+            $this->output->comment('没有需要自动取消30分钟未支付的订单记录!');
+        }
+    }
+
+    /**
+     * 清理一天前未支付的订单
+     * @throws Exception
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws DbException
+     * @throws PDOException
+     */
+    private function autoRemoveOrder()
+    {
+        $datetime = $this->getDatetime('store_order_clear_time');
+        $where = [['status', 'eq', '0'], ['pay_state', 'eq', '0'], ['create_at', '<', $datetime]];
+        $list = Db::name('StoreOrder')->where($where)->limit(20)->select();
+        if (count($orderNos = array_unique(array_column($list, 'order_no'))) > 0) {
+            $this->output->info("自动删除前一天已经取消的订单:" . PHP_EOL . join(',' . PHP_EOL, $orderNos));
+            Db::name('StoreOrder')->whereIn('order_no', $orderNos)->delete();
+            Db::name('StoreOrderList')->whereIn('order_no', $orderNos)->delete();
+        } else {
+            $this->output->comment('没有需要自动删除前一天已经取消的订单!');
+        }
+    }
+
+    /**
+     * 订单自动退款操作
+     * @throws Exception
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws DbException
+     * @throws PDOException
+     */
+    private function autoRefundOrder()
+    {
+        // 未完成退款的订单,执行微信退款操作
+        foreach (Db::name('StoreOrder')->where(['refund_state' => '1'])->select() as $order) try {
+            $this->output->writeln("正在为 {$order['order_no']} 执行退款操作...");
+            $result = We::WePayRefund(config('wechat.wxpay'))->create([
+                'transaction_id' => $order['pay_no'],
+                'out_refund_no'  => $order['refund_no'],
+                'total_fee'      => $order['price_total'] * 100,
+                'refund_fee'     => $order['pay_price'] * 100,
+                'refund_account' => 'REFUND_SOURCE_UNSETTLED_FUNDS',
+            ]);
+            if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') {
+                Db::name('StoreOrder')->where(['order_no' => $order['order_no']])->update([
+                    'refund_state' => '2', 'refund_desc' => '自动退款成功!',
+                ]);
+            } else {
+                Db::name('StoreOrder')->where(['order_no' => $order['order_no']])->update([
+                    'refund_desc' => isset($result['err_code_des']) ? $result['err_code_des'] : '自动退款失败',
+                ]);
+            }
+        } catch (\Exception $e) {
+            $this->output->writeln("订单 {$order['order_no']} 执行退款失败,{$e->getMessage()}!");
+            Db::name('StoreOrder')->where(['order_no' => $order['order_no']])->update(['refund_desc' => $e->getMessage()]);
+        }
+        $this->output->writeln('自动检测退款订单执行完成!');
+    }
+
+    /**
+     * 自动企业打款操作
+     * @throws Exception
+     * @throws DataNotFoundException
+     * @throws ModelNotFoundException
+     * @throws DbException
+     * @throws PDOException
+     */
+    private function autoTransfer()
+    {
+        # 批量企业打款
+        foreach (Db::name('StoreProfitUsed')->where(['status' => '1'])->select() as $vo) try {
+            $wechat = We::WePayTransfers(config('wechat.wxpay'));
+            $result = $wechat->create([
+                'partner_trade_no' => $vo['trs_no'],
+                'openid'           => $vo['openid'],
+                'check_name'       => 'NO_CHECK',
+                'amount'           => $vo['pay_price'] * 100,
+                'desc'             => '营销活动拥金提现',
+                'spbill_create_ip' => '127.0.0.1',
+            ]);
+            if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') {
+                Db::name('StoreProfitUsed')->where(['trs_no' => $vo['trs_no']])->update([
+                    'status' => '2', 'pay_desc' => '拥金提现成功!', 'pay_no' => $result['payment_no'], 'pay_at' => date('Y-m-d H:i:s'),
+                ]);
+            } else {
+                Db::name('StoreProfitUsed')->where(['trs_no' => $vo['trs_no']])->update([
+                    'pay_desc' => isset($result['err_code_des']) ? $result['err_code_des'] : '自动打款失败', 'last_at' => date('Y-m-d H:i:s'),
+                ]);
+            }
+        } catch (\Exception $e) {
+            $this->output->writeln("订单 {$vo['trs_no']} 执行提现失败,{$e->getMessage()}!");
+            Db::name('StoreProfitUsed')->where(['trs_no' => $vo['trs_no']])->update(['pay_desc' => $e->getMessage()]);
+        }
+    }
+
+    /**
+     * 获取配置时间
+     * @param string $code
+     * @return string
+     * @throws Exception
+     * @throws PDOException
+     */
+    private function getDatetime($code)
+    {
+        $minutes = intval(sysconf($code) * 60);
+        return date('Y-m-d H:i:s', strtotime("-{$minutes} minutes"));
+    }
+
+}

+ 60 - 0
application/store/controller/Config.php

@@ -0,0 +1,60 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller;
+
+use app\store\service\ExtendService;
+use library\Controller;
+
+/**
+ * 商城参数配置
+ * Class Config
+ * @package app\store\controller
+ */
+class Config extends Controller
+{
+
+    /**
+     * 商城参数配置
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '商城参数配置';
+        $this->applyCsrfToken('save');
+        $this->query = ExtendService::queryChinaSmsBalance();
+        $this->query2 = ExtendService::queryGlobeSmsBalance();
+        $this->fetch();
+    }
+
+    /**
+     * 保存商城参数
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function save()
+    {
+        if ($this->request->isPost()) {
+            $this->applyCsrfToken('save');
+            foreach ($this->request->post() as $k => $v) sysconf($k, $v);
+            $this->success('商城短信配置保存成功!');
+        }
+    }
+
+}

+ 128 - 0
application/store/controller/ExpressCompany.php

@@ -0,0 +1,128 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller;
+
+use library\Controller;
+use think\Db;
+
+/**
+ * 快递公司管理
+ * Class Express
+ * @package app\store\controller
+ */
+class ExpressCompany extends Controller
+{
+    /**
+     * 指定数据表
+     * @var string
+     */
+    protected $table = 'StoreExpressCompany';
+
+    /**
+     * 快递公司管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '快递公司管理';
+        $query = $this->_query($this->table)->equal('status')->like('express_title,express_code');
+        $query->dateBetween('create_at')->order('status desc,sort desc,id desc')->where(['is_deleted' => '0'])->page();
+    }
+
+    /**
+     * 添加快递公司
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function add()
+    {
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 编辑快递公司
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function edit()
+    {
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 表单数据处理
+     * @param array $data
+     * @auth true
+     */
+    protected function _form_filter(array $data)
+    {
+        if ($this->request->isPost()) {
+            $where = [['express_code', 'eq', $data['express_code']], ['is_deleted', 'eq', '0']];
+            if (!empty($data['id'])) $where[] = ['id ', 'neq', $data['id']];
+            if (Db::name($this->table)->where($where)->count() > 0) {
+                $this->error('该快递编码已经存在,请使用其它编码!');
+            }
+        }
+    }
+
+    /**
+     * 禁用快递公司
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function forbid()
+    {
+        $this->_save($this->table, ['status' => '0']);
+    }
+
+    /**
+     * 启用快递公司
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function resume()
+    {
+        $this->_save($this->table, ['status' => '1']);
+    }
+
+    /**
+     * 删除快递公司
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->_delete($this->table);
+    }
+
+}

+ 85 - 0
application/store/controller/ExpressTemplate.php

@@ -0,0 +1,85 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller;
+
+use library\Controller;
+use think\Db;
+
+/**
+ * 邮费模板管理
+ * Class ExpressTemplate
+ * @package app\store\controller
+ */
+class ExpressTemplate extends Controller
+{
+    /**
+     * 指定数据表
+     * @var string
+     */
+    protected $table = 'StoreExpressTemplate';
+
+    /**
+     * 邮费模板管理
+     * @auth true
+     * @menu true
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $this->title = '邮费模板管理';
+        $filename = env('root_path') . 'public/static/plugs/jquery/area/area.json';
+        $this->provinces = array_column(json_decode(file_get_contents($filename), true), 'name');
+        $this->list = Db::name($this->table)->where(['is_default' => '0'])->select();
+        foreach ($this->list as &$item) $item['rule'] = explode(',', $item['rule']);
+        $this->default = Db::name($this->table)->where(['is_default' => '1'])->find();
+        $this->fetch();
+    }
+
+    /**
+     * 保存邮费模板
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function save()
+    {
+        list($list, $idxs, $post) = [[], [], $this->request->post()];
+        foreach (array_keys($post) as $key) if (stripos($key, 'order_reduction_state_') !== false) {
+            $idxs[] = str_replace('order_reduction_state_', '', $key);
+        }
+        foreach (array_unique($idxs) as $index) if (!empty($post["rule_{$index}"])) $list[] = [
+            'rule'                  => join(',', $post["rule_{$index}"]),
+            // 订单满减配置
+            'order_reduction_state' => $post["order_reduction_state_{$index}"],
+            'order_reduction_price' => $post["order_reduction_price_{$index}"],
+            // 首件邮费配置
+            'first_number'          => $post["first_number_{$index}"],
+            'first_price'           => $post["first_price_{$index}"],
+            // 首件邮费配置
+            'next_number'           => $post["next_number_{$index}"],
+            'next_price'            => $post["next_price_{$index}"],
+            // 默认邮费规则
+            'is_default'            => $post["is_default_{$index}"],
+        ];
+        if (empty($list)) $this->error('请配置有效的邮费规则');
+        Db::name($this->table)->where('1=1')->delete();
+        Db::name($this->table)->insertAll($list);
+        $this->success('邮费规则配置成功!');
+    }
+
+}

+ 215 - 0
application/store/controller/Goods.php

@@ -0,0 +1,215 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller;
+
+use library\Controller;
+use library\tools\Data;
+use think\Db;
+
+/**
+ * 商品信息管理
+ * Class Goods
+ * @package app\store\controller
+ */
+class Goods extends Controller
+{
+    /**
+     * 指定数据表
+     * @var string
+     */
+    protected $table = 'StoreGoods';
+
+    /**
+     * 商品信息管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '商品信息管理';
+        $query = $this->_query($this->table)->equal('status,cate_id')->like('title');
+        $query->where(['is_deleted' => '0'])->order('sort desc,id desc')->page();
+    }
+
+    /**
+     * 数据列表处理
+     * @param array $data
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    protected function _index_page_filter(&$data)
+    {
+        $this->clist = Db::name('StoreGoodsCate')->where(['is_deleted' => '0', 'status' => '1'])->select();
+        $list = Db::name('StoreGoodsList')->where('status', '1')->whereIn('goods_id', array_unique(array_column($data, 'id')))->select();
+        foreach ($data as &$vo) {
+            list($vo['list'], $vo['cate']) = [[], []];
+            foreach ($list as $goods) if ($goods['goods_id'] === $vo['id']) array_push($vo['list'], $goods);
+            foreach ($this->clist as $cate) if ($cate['id'] === $vo['cate_id']) $vo['cate'] = $cate;
+        }
+    }
+
+    /**
+     * 商品库存入库
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function stock()
+    {
+        if ($this->request->isGet()) {
+            $GoodsId = $this->request->get('id');
+            $goods = Db::name('StoreGoods')->where(['id' => $GoodsId])->find();
+            empty($goods) && $this->error('无效的商品信息,请稍候再试!');
+            $goods['list'] = Db::name('StoreGoodsList')->where(['goods_id' => $GoodsId])->select();
+            $this->fetch('', ['vo' => $goods]);
+        } else {
+            list($post, $data) = [$this->request->post(), []];
+            if (isset($post['id']) && isset($post['goods_id']) && is_array($post['goods_id'])) {
+                foreach (array_keys($post['goods_id']) as $key) if ($post['goods_number'][$key] > 0) array_push($data, [
+                    'goods_id'     => $post['goods_id'][$key],
+                    'goods_spec'   => $post['goods_spec'][$key],
+                    'number_stock' => $post['goods_number'][$key],
+                ]);
+                if (!empty($data)) {
+                    Db::name('StoreGoodsStock')->insertAll($data);
+                    \app\store\service\GoodsService::syncStock($post['id']);
+                    $this->success('商品信息入库成功!');
+                }
+            }
+            $this->error('没有需要商品入库的数据!');
+        }
+    }
+
+    /**
+     * 添加商品信息
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function add()
+    {
+        $this->title = '添加商品信息';
+        $this->isAddMode = '1';
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 编辑商品信息
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function edit()
+    {
+        $this->title = '编辑商品信息';
+        $this->isAddMode = '0';
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 表单数据处理
+     * @param array $data
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    protected function _form_filter(&$data)
+    {
+        // 生成商品ID
+        if (empty($data['id'])) $data['id'] = Data::uniqidNumberCode(14);
+        if ($this->request->isGet()) {
+            $fields = 'goods_spec,goods_id,status,price_market market,price_selling selling,number_virtual `virtual`,number_express express';
+            $defaultValues = Db::name('StoreGoodsList')->where(['goods_id' => $data['id']])->column($fields);
+            $this->defaultValues = json_encode($defaultValues, JSON_UNESCAPED_UNICODE);
+            $this->cates = Db::name('StoreGoodsCate')->where(['is_deleted' => '0', 'status' => '1'])->order('sort desc,id desc')->select();
+        } elseif ($this->request->isPost()) {
+            if (empty($data['logo'])) $this->error('商品LOGO不能为空,请上传图片');
+            if (empty($data['image'])) $this->error('商品展示图片不能为空,请上传图片');
+            Db::name('StoreGoodsList')->where(['goods_id' => $data['id']])->update(['status' => '0']);
+            foreach (json_decode($data['lists'], true) as $vo) Data::save('StoreGoodsList', [
+                'goods_id'       => $data['id'],
+                'goods_spec'     => $vo[0]['key'],
+                'price_market'   => $vo[0]['market'],
+                'price_selling'  => $vo[0]['selling'],
+                'number_virtual' => $vo[0]['virtual'],
+                'number_express' => $vo[0]['express'],
+                'status'         => $vo[0]['status'] ? 1 : 0,
+            ], 'goods_spec', ['goods_id' => $data['id']]);
+        }
+    }
+
+    /**
+     * 表单结果处理
+     * @param boolean $result
+     */
+    protected function _form_result($result)
+    {
+        if ($result && $this->request->isPost()) {
+            $this->success('商品编辑成功!', 'javascript:history.back()');
+        }
+    }
+
+    /**
+     * 禁用商品信息
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function forbid()
+    {
+        $this->_save($this->table, ['status' => '0']);
+    }
+
+    /**
+     * 启用商品信息
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function resume()
+    {
+        $this->_save($this->table, ['status' => '1']);
+    }
+
+    /**
+     * 删除商品信息
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->_delete($this->table);
+    }
+
+}

+ 113 - 0
application/store/controller/GoodsCate.php

@@ -0,0 +1,113 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller;
+
+use library\Controller;
+
+/**
+ * 商品分类管理
+ * Class GoodsCate
+ * @package app\store\controller
+ */
+class GoodsCate extends Controller
+{
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    protected $table = 'StoreGoodsCate';
+
+    /**
+     * 商品分类管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '商品分类管理';
+        $query = $this->_query($this->table)->like('title')->equal('status');
+        $query->where(['is_deleted' => '0'])->order('sort desc,id desc')->page();
+    }
+
+    /**
+     * 添加商品分类
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function add()
+    {
+        $this->title = '添加商品分类';
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 编辑商品分类
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function edit()
+    {
+        $this->title = '编辑商品分类';
+        $this->_form($this->table, 'form');
+    }
+
+    /**
+     * 禁用商品分类
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function forbid()
+    {
+        $this->_save($this->table, ['status' => '0']);
+    }
+
+    /**
+     * 启用商品分类
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function resume()
+    {
+        $this->_save($this->table, ['status' => '1']);
+    }
+
+    /**
+     * 删除商品分类
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->_delete($this->table);
+    }
+
+}

+ 50 - 0
application/store/controller/Member.php

@@ -0,0 +1,50 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller;
+
+use library\Controller;
+
+/**
+ * 会员信息管理
+ * Class Member
+ * @package app\store\controller
+ */
+class Member extends Controller
+{
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    protected $table = 'StoreMember';
+
+    /**
+     * 会员信息管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '会员信息管理';
+        $query = $this->_query($this->table)->like('nickname,phone')->equal('vip_level');
+        $query->dateBetween('create_at')->order('id desc')->page();
+    }
+
+}

+ 61 - 0
application/store/controller/Message.php

@@ -0,0 +1,61 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller;
+
+use library\Controller;
+
+/**
+ * 短信发送管理
+ * Class Message
+ * @package app\store\controller
+ */
+class Message extends Controller
+{
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    protected $table = 'StoreMemberSmsHistory';
+
+    /**
+     * 短信发送管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '短信发送管理';
+        $query = $this->_query($this->table)->like('phone,content,result');
+        $query->dateBetween('create_at')->order('id desc')->page();
+    }
+
+    /**
+     * 删除短信记录
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function remove()
+    {
+        $this->_delete($this->table);
+    }
+
+}

+ 126 - 0
application/store/controller/Order.php

@@ -0,0 +1,126 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller;
+
+use library\Controller;
+use library\tools\Express;
+use think\Db;
+
+/**
+ * 订单记录管理
+ * Class Order
+ * @package app\store\controller
+ */
+class Order extends Controller
+{
+    /**
+     * 绑定数据表
+     * @var string
+     */
+    protected $table = 'StoreOrder';
+
+    /**
+     * 订单记录管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function index()
+    {
+        $this->title = '订单记录管理';
+        $this->_query($this->table)->order('id desc')->page();
+    }
+
+    /**
+     * 订单列表处理
+     * @param array $data
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    protected function _index_page_filter(array &$data)
+    {
+        $goodsList = Db::name('StoreOrderList')->whereIn('order_no', array_unique(array_column($data, 'order_no')))->select();
+        $mids = array_unique(array_merge(array_column($data, 'mid'), array_column($data, 'from_mid')));
+        $memberList = Db::name('StoreMember')->whereIn('id', $mids)->select();
+        foreach ($data as &$vo) {
+            list($vo['member'], $vo['from_member'], $vo['list']) = [[], [], []];
+            foreach ($goodsList as $goods) if ($goods['order_no'] === $vo['order_no']) {
+                $vo['list'][] = $goods;
+            }
+            foreach ($memberList as $member) if ($member['id'] === $vo['mid']) {
+                $vo['member'] = $member;
+            }
+        }
+    }
+
+    /**
+     * 修改快递管理
+     * @auth true
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function express()
+    {
+        if ($this->request->isGet()) {
+            $where = ['is_deleted' => '0', 'status' => '1'];
+            $this->expressList = Db::name('StoreExpress')->where($where)->order('sort desc,id desc')->select();
+        }
+        $this->_form($this->table);
+    }
+
+    /**
+     * 快递追踪查询
+     * @auth true
+     */
+    public function expressQuery()
+    {
+        list($code, $no) = [input('code', ''), input('no', '')];
+        if (empty($no)) $this->error('快递编号不能为空!');
+        if (empty($code)) $this->error('快递公司编码不能为空!');
+        $this->result = Express::query($code, $no);
+        $this->fetch();
+    }
+
+    /**
+     * 快递表单处理
+     * @param array $vo
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    protected function _express_form_filter(&$vo)
+    {
+        if ($this->request->isPost()) {
+            $order = Db::name($this->table)->where(['id' => $vo['id']])->find();
+            if (empty($order)) $this->error('订单查询异常,请稍候再试!');
+            $express = Db::name('StoreExpress')->where(['express_code' => $vo['express_company_code']])->find();
+            if (empty($express)) $this->error('发货快递公司异常,请重新选择快递公司!');
+            $vo['express_company_title'] = $express['express_title'];
+            $vo['express_send_at'] = empty($order['express_send_at']) ? date('Y-m-d H:i:s') : $order['express_send_at'];
+            $vo['express_state'] = '1';
+            $vo['status'] = '4';
+        }
+    }
+
+}

+ 52 - 0
application/store/controller/Slider.php

@@ -0,0 +1,52 @@
+<?php
+
+
+namespace app\store\controller;
+
+
+use library\Controller;
+
+/**
+ * 轮播图片管理
+ * Class Slider
+ * @package app\store\controller
+ */
+class Slider extends Controller
+{
+    /**
+     * 轮播图片管理
+     * @auth true
+     * @menu true
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function home()
+    {
+        $this->size = '600 * 350';
+        $this->keys = 'slider_home';
+        $this->title = '轮播图片管理';
+        $this->desc = "建议上传图片尺寸 {$this->size}";
+        $this->_apply('index');
+    }
+
+    /**
+     * 显示与管理
+     * @param string $tpl 模板名称
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    private function _apply($tpl)
+    {
+        if ($this->request->isGet()) {
+            $this->list = sysdata($this->keys);
+            $this->fetch($tpl);
+        } else {
+            if (sysdata($this->keys, json_decode($this->request->post('data'), true))) {
+                $this->success('数据保存成功!', '');
+            } else {
+                $this->error('数据保存失败,请稍候再试!');
+            }
+        }
+    }
+
+}

+ 38 - 0
application/store/controller/api/Data.php

@@ -0,0 +1,38 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller\api;
+
+use library\Controller;
+
+/**
+ * 数据商城基础数据
+ * Class Data
+ * @package app\store\controller\api
+ */
+class Data extends Controller
+{
+    /**
+     * 获取轮播图片数据
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function getSlider()
+    {
+        $this->keys = input('keys', 'slider_home');
+        $this->success('获取轮播图片数据!', sysdata($this->keys));
+    }
+
+}

+ 38 - 0
application/store/controller/api/Express.php

@@ -0,0 +1,38 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller\api;
+
+use library\Controller;
+
+/**
+ * 快递查询接口
+ * Class Express
+ * @package app\store\controller\api
+ */
+class Express extends Controller
+{
+    /**
+     * 物流查询结果
+     */
+    public function query()
+    {
+        $this->expressNo = input('express_no', '');
+        $this->expressCode = input('express_code', '');
+        $result = \library\tools\Express::query($this->expressCode, $this->expressNo);
+        $this->success('获取物流查询结果!', $result);
+    }
+
+}

+ 122 - 0
application/store/controller/api/Goods.php

@@ -0,0 +1,122 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller\api;
+
+use library\Controller;
+use think\Db;
+
+/**
+ * 商品管理接口
+ * Class Goods
+ * @package app\store\controller\api
+ */
+class Goods extends Controller
+{
+
+    /**
+     * 获取商品列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function gets()
+    {
+        $where = [['status', 'eq', '1'], ['is_deleted', 'eq', '0']];
+        $this->success('获取商品列表成功!', ['list' => $this->_getGoodsList($where)]);
+    }
+
+    /**
+     * 获取礼包商品列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function vips()
+    {
+        $where = [['status', 'eq', '1'], ['is_deleted', 'eq', '0']];
+        $this->success('获取礼包列表成功!', ['list' => $this->_getGoodsList($where)]);
+    }
+
+    /**
+     * 获取商品列表
+     * @param array $where
+     * @return array
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    private function _getGoodsList($where = [])
+    {
+        if ($this->request->has('title', 'post', true)) {
+            $where[] = ['title', 'like', "%{$this->request->post('title')}%"];
+        }
+        if ($this->request->has('cate_id', 'post', true)) {
+            $where[] = ['cate_id', 'eq', $this->request->post('cate_id')];
+        }
+        $field = 'id,title,logo,cate_id,image,number_sales,number_stock,content,specs,lists';
+        $list = Db::name('StoreGoods')->field($field)->where($where)->order('sort desc,id desc')->select();
+        $goodsList = Db::name('StoreGoodsList')->whereIn('goods_id', array_unique(array_column($list, 'id')))->select();
+        foreach ($list as &$vo) {
+            $vo['list'] = [];
+            $vo['image'] = explode('|', $vo['image']);
+            $vo['specs'] = json_decode($vo['specs'], true);
+            $vo['lists'] = json_decode($vo['lists'], true);
+            foreach ($goodsList as $goods) if ($goods['goods_id'] === $vo['id']) {
+                array_push($vo['list'], $goods);
+            }
+        }
+        return $list;
+    }
+
+    /**
+     * 获取单个商品信息
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function get()
+    {
+        $goods_id = input('goods_id');
+        $where = ['is_deleted' => '0', 'status' => '1', 'id' => $goods_id];
+        $field = 'id,title,logo,cate_id,image,number_sales,number_stock,content,specs,lists';
+        $goods = Db::name('StoreGoods')->field($field)->where($where)->find();
+        if (empty($goods)) $this->error('指定商品不存在,请更换商品ID重试!');
+        $goods['image'] = explode('|', $goods['image']);
+        $goods['specs'] = json_decode($goods['specs'], true);
+        $goods['lists'] = json_decode($goods['lists'], true);
+        $goods['list'] = Db::name('StoreGoodsList')->where(['goods_id' => $goods_id])->select();
+        if (empty($goods['list'])) {
+            $this->error('指定商品规格不存在,请更换商品ID重试!');
+        } else {
+            $this->success('获取商品信息成功!', $goods);
+        }
+    }
+
+    /**
+     * 获取商品分类信息
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function cate()
+    {
+        $where = ['is_deleted' => '0', 'status' => '1'];
+        $field = 'id cate_id,logo cate_logo,title cate_title';
+        $list = Db::name('StoreGoodsCate')->field($field)->where($where)->order('sort desc,id desc')->select();
+        $this->success('获取商品分类成功!', ['list' => $list]);
+    }
+
+}

+ 80 - 0
application/store/controller/api/Member.php

@@ -0,0 +1,80 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller\api;
+
+use library\Controller;
+use think\Db;
+
+/**
+ * 会员管理基类
+ * Class Member
+ * @package app\store\controller\api
+ */
+class Member extends Controller
+{
+    /**
+     * 当前会员ID
+     * @var integer
+     */
+    protected $mid;
+
+    /**
+     * 当前会员数据
+     * @var array
+     */
+    protected $member;
+
+    /**
+     * 当前公众号OPENID
+     * @var string
+     */
+    protected $openid;
+
+    /**
+     * Member constructor.
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function __construct()
+    {
+        parent::__construct();
+        // 会员信息检查
+        $this->mid = $this->request->post('mid');
+        $this->openid = $this->request->post('openid');
+        if (empty($this->mid)) $this->error('无效的会员ID参数!');
+        if (empty($this->openid)) $this->error('无效的会员绑定OPENID!');
+        $this->getMember();
+    }
+
+    /**
+     * 获取会员信息
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    protected function getMember()
+    {
+        $where = ['id' => $this->mid, 'openid' => $this->openid];
+        $this->member = Db::name('StoreMember')->where($where)->find();
+        if (empty($this->member)) $this->error('无效的会员信息,请重新登录授权!');
+        // 会员当前已经领取次数
+        $where = [['mid', 'eq', $this->mid], ['status', 'in', ['2', '3', '4', '5']]];
+        $this->member['times_used'] = Db::name('StoreOrder')->where($where)->count();
+        return $this->member;
+    }
+
+}

+ 74 - 0
application/store/controller/api/Notify.php

@@ -0,0 +1,74 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller\api;
+
+use app\store\service\OrderService;
+use think\Db;
+
+/**
+ * 支付通知处理
+ * Class Notify
+ * @package app\store\controller\api
+ */
+class Notify
+{
+    /**
+     * 微信支付通知处理
+     * @return string
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function wxpay()
+    {
+        $wechat = \We::WePayOrder(config('wechat.miniapp'));
+        $notify = $wechat->getNotify();
+        if ($notify['result_code'] == 'SUCCESS' && $notify['return_code'] == 'SUCCESS') {
+            if ($this->update($notify['out_trade_no'], $notify['transaction_id'], $notify['cash_fee'] / 100, 'wechat')) {
+                return $wechat->getNotifySuccessReply();
+            }
+        } else {
+            return $wechat->getNotifySuccessReply();
+        }
+    }
+
+    /**
+     * 订单状态更新
+     * @param string $order_no 订单号
+     * @param string $pay_no 交易号
+     * @param string $pay_price 交易金额
+     * @param string $type 支付类型
+     * @return boolean
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    private function update($order_no, $pay_no, $pay_price, $type = 'wechat')
+    {
+        // 检查订单支付状态
+        $where = ['order_no' => $order_no, 'pay_state' => '0', 'status' => '2'];
+        $order = Db::name('StoreOrder')->where($where)->find();
+        if (empty($order)) return false;
+        // 更新订单支付状态
+        $result = Db::name('StoreOrder')->where($where)->update([
+            'pay_type'  => $type, 'pay_no' => $pay_no, 'status' => '3',
+            'pay_price' => $pay_price, 'pay_state' => '1', 'pay_at' => date('Y-m-d H:i:s'),
+        ]);
+        // 调用会员升级机制
+        OrderService::update($order['order_no']);
+        return $result !== false;
+    }
+
+}

+ 92 - 0
application/store/controller/api/Wechat.php

@@ -0,0 +1,92 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller\api;
+
+use library\Controller;
+use think\Db;
+use think\exception\HttpResponseException;
+
+/**
+ * Class Wechat
+ * @package app\store\controller\api
+ */
+class Wechat extends Controller
+{
+    /**
+     * 获取小程序配置
+     * @return array
+     */
+    private function config()
+    {
+        return config('wechat.miniapp');
+    }
+
+    /**
+     * Code信息换取
+     */
+    public function session()
+    {
+        try {
+            $code = $this->request->post('code');
+            $result = \We::WeMiniCrypt($this->config())->session($code);
+            if (isset($result['openid'])) {
+                data_save('StoreMember', ['openid' => $result['openid']], 'openid');
+                $result['member'] = Db::name('StoreMember')->where(['openid' => $result['openid']])->find();
+                $this->success('授权CODE信息换取成功!', $result);
+            } else {
+                $this->error("[{$result['errcode']}] {$result['errmsg']}");
+            }
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $exception) {
+            $this->error("授权CODE信息换取失败,{$exception->getMessage()}");
+        }
+    }
+
+    /**
+     * 小程序数据解密
+     */
+    public function decode()
+    {
+        try {
+            $iv = $this->request->post('iv');
+            $session = $this->request->post('session');
+            $content = $this->request->post('encrypted');
+            if (empty($session)) {
+                $code = $this->request->post('code');
+                $result = \We::WeMiniCrypt($this->config())->session($code);
+                $session = isset($result['session_key']) ? $result['session_key'] : '';
+            }
+            $result = \We::WeMiniCrypt($this->config())->decode($iv, $session, $content);
+            if ($result !== false && isset($result['openId'])) {
+                data_save('StoreMember', [
+                    'openid'   => $result['openId'],
+                    'headimg'  => $result['avatarUrl'],
+                    'nickname' => $result['nickName'],
+                ], 'openid');
+                $result['member'] = Db::name('StoreMember')->where(['openid' => $result['openId']])->find();
+                $this->success('小程序加密数据解密成功!', $result);
+            } else {
+                $this->error('小程序加密数据解密失败,请稍候再试!');
+            }
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            $this->error("小程序加密数据解密失败,{$e->getMessage()}");
+        }
+    }
+
+}

+ 128 - 0
application/store/controller/api/member/Address.php

@@ -0,0 +1,128 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller\api\member;
+
+use app\store\controller\api\Member;
+use think\Db;
+
+/**
+ * 会员收货地址管理
+ * Class Address
+ * @package app\store\controller\api\member
+ */
+class Address extends Member
+{
+
+    /**
+     * 获取会员收货地址信息
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function gets()
+    {
+        $this->success('获取会员收货地址成功!', [
+            'list' => Db::name('StoreMemberAddress')
+                ->where(['mid' => $this->member['id']])
+                ->order('is_default desc,id desc')
+                ->select(),
+        ]);
+    }
+
+    /**
+     * 更新收货地址
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function set()
+    {
+        $data = $this->_input([
+            'mid'        => $this->request->post('mid'),
+            'openid'     => $this->request->post('openid'),
+            'name'       => $this->request->post('name'),
+            'phone'      => $this->request->post('phone'),
+            'province'   => $this->request->post('province'),
+            'city'       => $this->request->post('city'),
+            'area'       => $this->request->post('area'),
+            'address'    => $this->request->post('address'),
+            'is_default' => $this->request->post('is_default'),
+        ], [
+            'name'     => 'require',
+            'phone'    => 'require|mobile',
+            'province' => 'require',
+            'city'     => 'require',
+            'area'     => 'require',
+            'address'  => 'require',
+        ], [
+            'name.require'     => '收货人姓名不能为空!',
+            'phone.require'    => '收货人联系手机不能为空!',
+            'phone.mobile'     => '收货人联系手机格式不对!',
+            'province.require' => '收货地址省份不能为空!',
+            'city.require'     => '收货地址城市不能为空!',
+            'area.require'     => '收货地址区域不能为空!',
+            'address.require'  => '收货详情地址不能为空!',
+        ]);
+        if (!empty($data['is_default'])) {
+            Db::name('StoreMemberAddress')->where(['mid' => $this->member['id']])->setField('is_default', '0');
+        }
+        if ($this->request->has('id', 'post', true)) {
+            $data['id'] = $this->request->post('id');
+        }
+        if (data_save('StoreMemberAddress', $data, 'id')) {
+            $this->success('收货地址更新成功!');
+        }
+        $this->error('收货地址更新失败,请稍候再试!');
+    }
+
+    /**
+     * 删除收货地址
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function del()
+    {
+        $id = $this->request->post('address_id');
+        if (empty($id)) $this->error('待处理的收货地址ID不能为空!');
+        $where = ['id' => $id, 'mid' => $this->member['id']];
+        if (Db::name('StoreMemberAddress')->where($where)->delete() !== false) {
+            $this->success('删除收货地址成功!');
+        }
+        $this->error('删除收货地址失败,请稍候再试!');
+    }
+
+    /**
+     * 设置默认收货地址
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function setDefault()
+    {
+        $id = $this->request->post('address_id');
+        if (empty($id)) $this->error('待处理的收货地址ID不存在!');
+        $where = ['id' => $id, 'mid' => $this->member['id']];
+        $address = Db::name('StoreMemberAddress')->where($where)->find();
+        if (empty($address)) $this->error('待处理的收货地址获取失败,请稍候再试!');
+        Db::name('StoreMemberAddress')->where(['mid' => $this->member['id']])->update(['is_default' => '0']);
+        if (Db::name('StoreMemberAddress')->where($where)->update(['is_default' => '1']) !== false) {
+            $this->success('设置默认收货地址成功!');
+        }
+        $this->error('设置默认收货地址失败!');
+    }
+
+}

+ 122 - 0
application/store/controller/api/member/Center.php

@@ -0,0 +1,122 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller\api\member;
+
+use app\store\controller\api\Member;
+use app\store\service\ExtendService;
+use think\Db;
+
+/**
+ * 商品会员中心
+ * Class Center
+ * @package app\store\controller\api\member
+ */
+class Center extends Member
+{
+    /**
+     * 修改会员资料
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function info()
+    {
+        $data = [];
+        if ($this->request->has('headimg', 'post', true)) {
+            $data['headimg'] = $this->request->post('headimg');
+        }
+        if ($this->request->has('nickname', 'post', true)) {
+            $data['nickname'] = $this->request->post('nickname');
+        }
+        if ($this->request->has('username', 'post', true)) {
+            $data['username'] = $this->request->post('username');
+        }
+        if (empty($data)) $this->error('没有需要修改的数据哦!');
+        if (data_save('StoreMember', array_merge($data, ['id' => $this->mid]), 'id') !== false) {
+            $this->success('会员资料更新成功!', $this->getMember());
+        } else {
+            $this->error('会员资料更新失败,请稍候再试!');
+        }
+    }
+
+    /**
+     * 发送短信验证码
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function sendsms()
+    {
+        $phone = $this->request->post('phone');
+        if ($this->request->post('secure') !== sysconf('sms_secure')) {
+            $this->error('短信发送安全码不正确,请使用正确的安全码!');
+        }
+        $member = Db::name('StoreMember')->where(['phone' => $phone])->find();
+        if (!empty($member)) $this->error('该手机号已经注册了,请使用其它手机号!');
+        $cache = cache($cachekey = "send_register_sms_{$phone}");
+        if (is_array($cache) && isset($cache['time']) && $cache['time'] > time() - 120) {
+            $dtime = ($cache['time'] + 120 < time()) ? 0 : (120 - time() + $cache['time']);
+            $this->success('短信验证码已经发送!', ['time' => $dtime]);
+        }
+        list($code, $content) = [rand(1000, 9999), sysconf('sms_reg_template')];
+        if (empty($content) || stripos($content, '{code}') === false) {
+            $content = '您的验证码为{code},请在十分钟内完成操作!';
+        }
+        cache($cachekey, ['phone' => $phone, 'captcha' => $code, 'time' => time()], 600);
+        if (empty($content) || strpos($content, '{code}') === false) {
+            $this->error('获取短信模板失败,联系管理员配置!');
+        }
+        $cache = cache($cachekey);
+        if (ExtendService::sendChinaSms($this->mid, $phone, str_replace('{code}', $code, $content))) {
+            $dtime = ($cache['time'] + 120 < time()) ? 0 : (120 - time() + $cache['time']);
+            $this->success('短信验证码发送成功!', ['time' => $dtime]);
+        } else {
+            $this->error('短信发送失败,请稍候再试!');
+        }
+    }
+
+    /**
+     * 会员登录绑定
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function bind()
+    {
+        $code = $this->request->post('code');
+        $phone = $this->request->post('phone');
+        $cache = cache($cachekey = "send_register_sms_{$phone}");
+        if (is_array($cache) && isset($cache['captcha']) && $cache['captcha'] == $code) {
+            $where = ['id' => $this->member['id']];
+            if (Db::name('StoreMember')->where($where)->update(['phone' => $phone]) !== false) {
+                $this->success('手机绑定登录成功!');
+            } else {
+                $this->error('手机绑定登录失败,请稍候再试!');
+            }
+        } else {
+            $this->error('短信验证码验证失败!');
+        }
+    }
+
+    /**
+     * 获取会员资源成功
+     */
+    public function member()
+    {
+        $this->success('获取会员资料成功!', $this->member);
+    }
+
+}

+ 313 - 0
application/store/controller/api/member/Order.php

@@ -0,0 +1,313 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\controller\api\member;
+
+use app\store\controller\api\Member;
+use app\store\service\GoodsService;
+use app\store\service\OrderService;
+use library\tools\Data;
+use think\Db;
+use think\exception\HttpResponseException;
+
+/**
+ * 订单接口管理
+ * Class Order
+ * @package app\store\controller\api\member
+ */
+class Order extends Member
+{
+
+    /**
+     * 创建商城订单
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * 商品ID1@商品规格1@商品数量1||商品ID2@商品规格2@商品数量2
+     */
+    public function set()
+    {
+        // 商品规则
+        $rule = $this->request->post('rule', '');
+        if (empty($rule)) $this->error('下单商品规则不能为空!');
+        // 订单处理
+        list($orderList, $order) = [[], [
+            'status'   => '1', 'mid' => $this->mid,
+            'order_no' => Data::uniqidNumberCode(12),
+            'from_mid' => $this->request->post('from_mid', '0'),
+        ]];
+        // 推荐人处理
+        if (intval($order['from_mid']) === intval($this->mid)) {
+            $order['from_mid'] = '0';
+        } elseif ($order['from_mid'] > 0) {
+            if (Db::name('StoreMember')->where(['id' => $order['from_mid']])->count() < 1) {
+                $this->error('无效的推荐会员ID,稍候再试!');
+            }
+        }
+        foreach (explode('||', $rule) as $item) {
+            list($goods_id, $goods_spec, $number) = explode('@', $item);
+            // 商品信息检查
+            $goods = Db::name('StoreGoods')->where(['id' => $goods_id, 'status' => '1', 'is_deleted' => '0'])->find();
+            if (empty($goods)) $this->error('查询商品主体信息失败,请稍候再试!');
+            $spec = Db::name('StoreGoodsList')->where(['goods_id' => $goods_id, 'goods_spec' => $goods_spec])->find();
+            if (empty($spec)) $this->error('查询商品规则信息失败,请稍候再试!');
+            // 商品库存检查
+            if ($spec['number_sales'] + $number > $spec['number_stock']) {
+                $this->error('商品库存不足,请购买其它商品!');
+            }
+            // 订单详情处理
+            array_push($orderList, [
+                'mid'               => $order['mid'],
+                'from_mid'          => $order['from_mid'],
+                'order_no'          => $order['order_no'],
+                // 商品信息字段管理
+                'goods_id'          => $goods_id,
+                'goods_spec'        => $goods_spec,
+                'goods_logo'        => $goods['logo'],
+                'goods_title'       => $goods['title'],
+                'number_goods'      => $number,
+                'number_express'    => $spec['number_express'],
+                // 费用字段处理
+                'price_market'      => $spec['price_market'],
+                'price_selling'     => $spec['price_selling'],
+                'price_real'        => $spec['price_selling'] * $number,
+                'price_express'     => $goods['price_express'],
+                // 返利字段处理
+                'price_rate'        => $goods['price_rate'],
+                'price_rate_amount' => $spec['price_selling'] * $number * $goods['price_rate'] / 100,
+            ]);
+        }
+        $order['price_goods'] = array_sum(array_column($orderList, 'price_real')) + 0;
+        $order['price_express'] = max(array_column($orderList, 'price_express')) + 0;
+        $order['price_total'] = $order['price_goods'] + $order['price_express'];
+        $order['price_rate_amount'] = array_sum(array_column($orderList, 'price_rate_amount')) + 0;
+        try {
+            // 订单数据写入
+            Db::name('StoreOrder')->insert($order);
+            Db::name('StoreOrderList')->insertAll($orderList);
+            // 同步商品库存及销量
+            foreach (array_unique(array_column($orderList, 'goods_id')) as $goodsId) GoodsService::syncStock($goodsId);
+            $this->success('订单创建成功,请补全收货信息后支付!', ['order' => $order]);
+        } catch (HttpResponseException $exception) {
+            throw $exception;
+        } catch (\Exception $e) {
+            $this->error("创建订单失败,请稍候再试!{$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 订单信息完成
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function perfect()
+    {
+        $data = $this->_input([
+            'order_no'   => $this->request->post('order_no'),
+            'address_id' => $this->request->post('address_id'),
+        ], [
+            'order_no'   => 'require',
+            'address_id' => 'require',
+        ], [
+            'order_no.require'   => '订单号不能为空!',
+            'address_id.require' => '收货地址ID不能为空(0自提可以为空)',
+        ]);
+        $map = ['order_no' => $data['order_no'], 'mid' => $this->member['id']];
+        $order = Db::name('StoreOrder')->whereIn('status', ['1', '2'])->where($map)->find();
+        if (empty($order)) $this->error('订单异常,请返回商品重新下单!');
+        $update = ['status' => '2'];
+        $where = ['id' => $data['address_id'], 'mid' => $this->member['id']];
+        $address = Db::name('StoreMemberAddress')->where($where)->find();
+        if (empty($address)) $this->error('会员收货地址异常,请刷新页面重试!');
+        $update['express_address_id'] = $data['address_id'];
+        $update['express_name'] = $address['name'];
+        $update['express_phone'] = $address['phone'];
+        $update['express_province'] = $address['province'];
+        $update['express_city'] = $address['city'];
+        $update['express_area'] = $address['area'];
+        $update['express_address'] = $address['address'];
+        if (Db::name('StoreOrder')->where($map)->update($update) !== false) {
+            $params = $this->getPayParams($order['order_no'], $order['price_total']);
+            $this->success('更新订单会员信息成功!', $params);
+        } else {
+            $this->error('更新订单会员信息失败,请稍候再试!');
+        }
+    }
+
+    /**
+     * 获取订单支付状态
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function pay()
+    {
+        $order_no = $this->request->post('order_no');
+        $order = Db::name('StoreOrder')->where(['order_no' => $order_no])->find();
+        if (empty($order_no)) $this->error('获取订单信息异常,请稍候再试!');
+        if ($order['pay_state']) $this->error('订单已经完成支付,不需要再次支付!');
+        if ($order['status'] <> 2) $this->error('该订单不能发起支付哦!');
+        try {
+            $param = $this->getPayParams($order['order_no'], $order['price_total']);
+            $this->success('获取订单支付参数成功!', $param);
+        } catch (HttpResponseException $exception) {
+            throw  $exception;
+        } catch (\Exception $e) {
+            $this->error("获取订单支付参数失败,{$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 获取订单支付参数
+     * @param string $order_no
+     * @param string $pay_price
+     * @return array
+     */
+    private function getPayParams($order_no, $pay_price)
+    {
+        $options = [
+            'body'             => '商城订单支付',
+            'openid'           => $this->openid,
+            'out_trade_no'     => $order_no,
+            // 'total_fee'        => '1',
+            'total_fee'        => $pay_price * 100,
+            'trade_type'       => 'JSAPI',
+            'notify_url'       => url('@store/api.notify/wxpay', '', false, true),
+            'spbill_create_ip' => $this->request->ip(),
+        ];
+        try {
+            $pay = \We::WePayOrder(config('wechat.miniapp'));
+            $info = $pay->create($options);
+            if ($info['return_code'] === 'SUCCESS' && $info['result_code'] === 'SUCCESS') {
+                return $pay->jsapiParams($info['prepay_id']);
+            }
+            if (isset($info['err_code_des'])) {
+                throw new \think\Exception($info['err_code_des']);
+            }
+        } catch (\Exception $e) {
+            $this->error("创建订单失败参数失败,{$e->getMessage()}");
+        }
+    }
+
+    /**
+     * 获取订单列表
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function gets()
+    {
+        $where = [['mid', 'eq', $this->mid]];
+        if ($this->request->has('order_no', 'post', true)) {
+            $where[] = ['order_no', 'eq', $this->request->post('order_no')];
+        } else {
+            $where[] = ['status', 'in', ['0', '2', '3', '4', '5']];
+        }
+        if ($this->request->has('status', 'post', true)) {
+            $where[] = ['status', 'eq', $this->request->post('status')];
+        }
+        $result = $this->_query('StoreOrder')->where($where)->order('id desc')->page(true, false, false, 20);
+        $glist = Db::name('StoreOrderList')->whereIn('order_no', array_unique(array_column($result['list'], 'order_no')))->select();
+        foreach ($result['list'] as &$vo) {
+            list($vo['goods_count'], $vo['list']) = [0, []];
+            foreach ($glist as $goods) if ($vo['order_no'] === $goods['order_no']) {
+                $vo['list'][] = $goods;
+                $vo['goods_count'] += $goods['number_goods'];
+            }
+        }
+        $this->success('获取订单列表成功!', $result);
+    }
+
+    /**
+     * 订单取消
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function cancel()
+    {
+        $where = [
+            'mid'      => $this->member['id'],
+            'order_no' => $this->request->post('order_no'),
+        ];
+        $order = Db::name('StoreOrder')->where($where)->find();
+        if (empty($order)) $this->error('待取消的订单不存在,请稍候再试!');
+        if (in_array($order['status'], ['1', '2'])) {
+            $result = Db::name('StoreOrder')->where($where)->update([
+                'status'       => '0',
+                'cancel_state' => '1',
+                'cancel_at'    => date('Y-m-d H:i:s'),
+                'cancel_desc'  => '用户主动取消订单!',
+            ]);
+            if ($result !== false && OrderService::syncStock($order['order_no'])) {
+                $this->success('订单取消成功!');
+            } else {
+                $this->error('订单取消失败,请稍候再试!');
+            }
+        } else {
+            $this->error('该订单状态不允许取消哦~');
+        }
+    }
+
+    /**
+     * 订单确认收货
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function confirm()
+    {
+        $where = [
+            'mid'      => $this->member['id'],
+            'order_no' => $this->request->post('order_no'),
+        ];
+        $order = Db::name('StoreOrder')->where($where)->find();
+        if (empty($order)) $this->error('待确认的订单不存在,请稍候再试!');
+        if (in_array($order['status'], ['4'])) {
+            if (Db::name('StoreOrder')->where($where)->update(['status' => '5']) !== false) {
+                $this->success('订单确认成功!');
+            } else {
+                $this->error('订单取确认失败,请稍候再试!');
+            }
+        } else {
+            $this->error('该订单状态不允许确认哦~');
+        }
+    }
+
+    /**
+     * 订单状态统计
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function total()
+    {
+        $result = Db::name('StoreOrder')
+            ->fieldRaw('mid,status,count(1) count')
+            ->where(['mid' => $this->mid])
+            ->group('status')
+            ->select();
+        $this->success('获取订单统计记录!', $result);
+    }
+}

+ 84 - 0
application/store/service/ExpressService.php

@@ -0,0 +1,84 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\service;
+
+use think\Db;
+
+/**
+ * 商城邮费服务
+ * Class ExpressService
+ * @package app\store\service
+ */
+class ExpressService
+{
+
+    /**
+     * 订单邮费计算
+     * @param string $province 配送省份
+     * @param string $number 计费数量
+     * @param string $amount 订单金额
+     * @return array
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public static function price($province, $number, $amount)
+    {
+        // 读取对应的模板规则
+        $map = [['is_default', 'eq', '0'], ['rule', 'like', "%{$province}%"]];
+        $rule = Db::name('StoreExpressTemplate')->where($map)->find();
+        if (!empty($rule)) return self::buildData($rule, '普通模板', $number, $amount);
+        $rule = Db::name('StoreExpressTemplate')->where(['is_default' => '1'])->find();
+        return self::buildData($rule, '默认模板', $number, $amount);
+    }
+
+    /**
+     * 生成邮费数据
+     * @param array $rule 模板规则
+     * @param string $type 模板类型
+     * @param integer $number 计费件数
+     * @param double $amount 订单金额
+     * @return array
+     */
+    protected static function buildData($rule, $type, $number, $amount)
+    {
+        // 异常规则
+        if (empty($rule)) return [
+            'express_price' => 0.00, 'express_type' => '未知模板', 'express_desc' => '未匹配到邮费模板',
+        ];
+        // 满减免邮
+        if ($rule['order_reduction_state'] && $amount >= $rule['order_reduction_price']) {
+            return [
+                'express_price' => 0.00, 'express_type' => $type,
+                'express_desc'  => "订单总金额满{$rule['order_reduction_price']}元减免全部邮费",
+            ];
+        }
+        // 首重计费
+        if ($number <= $rule['first_number']) return [
+            'express_price' => $rule['first_price'], 'express_type' => $type,
+            'express_desc'  => "首件计费,{$rule['first_number']}件及{$rule['first_number']}以内计费{$rule['first_price']}元",
+        ];
+        // 续重计费
+        list($price1, $price2) = [$rule['first_price'], 0];
+        if ($rule['next_number'] > 0 && $rule['next_price'] > 0) {
+            $price2 = $rule['next_price'] * ceil(($number - $rule['first_number']) / $rule['next_number']);
+        }
+        return [
+            'express_price' => $price1 + $price2, 'express_type' => $type,
+            'express_desc'  => "续件计费,超出{$rule['first_number']}件,首件费用{$rule['first_price']}元 + 续件费用{$price2}元",
+        ];
+    }
+}

+ 423 - 0
application/store/service/ExtendService.php

@@ -0,0 +1,423 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\service;
+
+use library\tools\Http;
+use think\Db;
+
+/**
+ * 短信业务扩展服务
+ * Class ExtendService
+ * @package app\store\service
+ * =================================
+ * =================================
+ * 发送国内短信需要给产品码 [productid]
+ * --- 验证短信的产品码为:676767
+ * --- 营销短信的产品码为:333333
+ * ---------------------------------
+ * ---------------------------------
+ * 发送国际短信需要给国家代码 [code]
+ * --- 国家代码见 getGlobeRegionMap
+ * ---------------------------------
+ * ---------------------------------
+ * 需要开通短信账号请联系客服
+ * --- 客服电话:18122377655
+ * =================================
+ */
+class ExtendService
+{
+
+    /**
+     * 发送国内短信验证码
+     * @param string $mid 会员ID
+     * @param string $phone 手机号
+     * @param string $content 短信内容
+     * @param string $productid 短信通道ID
+     * @return boolean
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public static function sendChinaSms($mid, $phone, $content, $productid = '676767')
+    {
+        $tkey = date("YmdHis");
+        $result = Http::post('http://www.ztsms.cn/sendNSms.do', [
+            'tkey'     => $tkey, 'mobile' => $phone, 'content' => $content,
+            'username' => sysconf('sms_zt_username'), 'productid' => $productid,
+            'password' => md5(md5(sysconf('sms_zt_password')) . $tkey),
+        ]);
+        list($code, $msg) = explode(',', $result . ',');
+        Db::name('StoreMemberSmsHistory')->insert([
+            'mid' => $mid, 'phone' => $phone, 'content' => $content, 'result' => $result
+        ]);
+        return intval($code) === 1;
+    }
+
+    /**
+     * 发送国内短信验证码
+     * @param string $mid 会员ID
+     * @param string $phone 目标手机
+     * @param integer $wait 等待时间
+     * @param string $type 短信模板
+     * @return array
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public static function sendChinaSmsByCode($mid, $phone, $wait = 120, $type = 'sms_reg_template')
+    {
+        $cache = cache($ckey = "{$type}_{$phone}");
+        if (is_array($cache) && isset($cache['time']) && $cache['time'] > time() - $wait) {
+            $dtime = ($cache['time'] + $wait < time()) ? 0 : ($wait - time() + $cache['time']);
+            return [1, '短信验证码已经发送!', ['time' => $dtime]];
+        }
+        list($code, $content) = [rand(1000, 9999), sysconf($type)];
+        if (empty($content) || stripos($content, '{code}') === false) {
+            $content = '您的验证码为{code},请在十分钟内完成操作!';
+        }
+        cache($ckey, $cache = ['phone' => $phone, 'code' => $code, 'time' => time()], 600);
+        if (self::sendChinaSms($mid, $phone, str_replace('{code}', $code, $content))) {
+            $dtime = ($cache['time'] + $wait < time()) ? 0 : ($wait - time() + $cache['time']);
+            return [1, '短信验证码发送成功!', ['time' => $dtime]];
+        } else {
+            return [0, '短信发送失败,请稍候再试!', []];
+        }
+    }
+
+    /**
+     * 验证手机短信验证码
+     * @param string $phone 目标手机
+     * @param string $code 短信验证码
+     * @param string $type 短信模板
+     * @return boolean
+     */
+    public static function checkChinaSmsByCode($phone, $code, $type = 'sms_reg_template')
+    {
+        $cache = cache($cachekey = "{$type}_{$phone}");
+        return is_array($cache) && isset($cache['code']) && $cache['code'] == $code;
+    }
+
+    /**
+     * 查询国内短信余额
+     * @return array
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public static function queryChinaSmsBalance()
+    {
+        $tkey = date("YmdHis");
+        $result = Http::post('http://www.ztsms.cn/balanceN.do', [
+            'username' => sysconf('sms_zt_username'), 'tkey' => $tkey,
+            'password' => md5(md5(sysconf('sms_zt_password')) . $tkey),
+        ]);
+        if ($result > -1) {
+            return ['code' => 1, 'num' => $result, 'msg' => '获取短信剩余条数成功!'];
+        } elseif ($result > -2) {
+            return ['code' => 0, 'num' => '0', 'msg' => '用户名或者密码不正确!'];
+        } elseif ($result > -3) {
+            return ['code' => 0, 'num' => '0', 'msg' => 'tkey不正确!'];
+        } elseif ($result > -4) {
+            return ['code' => 0, 'num' => '0', 'msg' => '用户不存在或用户停用!'];
+        }
+    }
+
+    /**
+     * 错误消息处理
+     * @var array
+     */
+    private static $globeMessageMap = [
+        2  => '用户账号为空',
+        3  => '用户账号错误',
+        4  => '授权密码为空',
+        5  => '授权密码错误',
+        6  => '当前时间为空',
+        7  => '当前时间错误',
+        8  => '用户类型错误',
+        9  => '用户鉴权错误',
+        10 => '请求IP已被列入黑名单',
+    ];
+
+    /**
+     * 发送国际短信内容
+     * @param string $mid 会员编号
+     * @param string $code 国家代码
+     * @param string $mobile 手机号码
+     * @param string $content 发送内容
+     * @return boolean
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public static function sendGlobeSms($mid, $code, $mobile, $content)
+    {
+        $tkey = date("YmdHis");
+        $result = Http::post('http://intl.zthysms.com/intSendSms.do', [
+            'tkey'     => $tkey, 'code' => $code, 'mobile' => $mobile,
+            'content'  => $content, 'username' => sysconf('sms_zt_username2'),
+            'password' => md5(md5(sysconf('sms_zt_password2')) . $tkey),
+        ]);
+        Db::name('StoreMemberSmsHistory')->insert([
+            'mid' => $mid, 'region' => $code, 'phone' => $mobile, 'content' => $content, 'result' => $result,
+        ]);
+        return intval($result) === 1;
+    }
+
+    /**
+     * 查询国际短信余额
+     * @return array
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public static function queryGlobeSmsBalance()
+    {
+        $tkey = date("YmdHis");
+        $result = Http::post('http://intl.zthysms.com/intBalance.do', [
+            'username' => sysconf('sms_zt_username2'), 'tkey' => $tkey,
+            'password' => md5(md5(sysconf('sms_zt_password2')) . $tkey),
+        ]);
+        if (!is_numeric($result) && ($state = intval($result)) && isset(self::$globeMessageMap[$state])) {
+            return ['code' => 0, 'num' => 0, 'msg' => self::$globeMessageMap[$state]];
+        } else {
+            return ['code' => 1, 'num' => $result, 'msg' => '查询成功'];
+        }
+    }
+
+    /**
+     * 获取国际地域编号
+     * @return array
+     */
+    public static function getGlobeRegionMap()
+    {
+        return [
+            ['title' => '中国 台湾', 'english' => 'Taiwan', 'code' => 886],
+            ['title' => '东帝汶民主共和国', 'english' => 'DEMOCRATIC REPUBLIC OF TIMORLESTE', 'code' => 670],
+            ['title' => '中非共和国', 'english' => 'Central African Republic', 'code' => 236],
+            ['title' => '丹麦', 'english' => 'Denmark', 'code' => 45],
+            ['title' => '乌克兰', 'english' => 'Ukraine', 'code' => 380],
+            ['title' => '乌兹别克斯坦', 'english' => 'Uzbekistan', 'code' => 998],
+            ['title' => '乌干达', 'english' => 'Uganda', 'code' => 256],
+            ['title' => '乌拉圭', 'english' => 'Uruguay', 'code' => 598],
+            ['title' => '乍得', 'english' => 'Chad', 'code' => 235],
+            ['title' => '也门', 'english' => 'Yemen', 'code' => 967],
+            ['title' => '亚美尼亚', 'english' => 'Armenia', 'code' => 374],
+            ['title' => '以色列', 'english' => 'Israel', 'code' => 972],
+            ['title' => '伊拉克', 'english' => 'Iraq', 'code' => 964],
+            ['title' => '伊朗', 'english' => 'Iran', 'code' => 98],
+            ['title' => '伯利兹', 'english' => 'Belize', 'code' => 501],
+            ['title' => '佛得角', 'english' => 'Cape Verde', 'code' => 238],
+            ['title' => '俄罗斯', 'english' => 'Russia', 'code' => 7],
+            ['title' => '保加利亚', 'english' => 'Bulgaria', 'code' => 359],
+            ['title' => '克罗地亚', 'english' => 'Croatia', 'code' => 385],
+            ['title' => '关岛', 'english' => 'Guam', 'code' => 1671],
+            ['title' => '冈比亚', 'english' => 'The Gambia', 'code' => 220],
+            ['title' => '冰岛', 'english' => 'Iceland', 'code' => 354],
+            ['title' => '几内亚', 'english' => 'Guinea', 'code' => 224],
+            ['title' => '几内亚比绍', 'english' => 'Guinea - Bissau', 'code' => 245],
+            ['title' => '列支敦士登', 'english' => 'Liechtenstein', 'code' => 423],
+            ['title' => '刚果共和国', 'english' => 'The Republic of Congo', 'code' => 242],
+            ['title' => '刚果民主共和国', 'english' => 'Democratic Republic of the Congo', 'code' => 243],
+            ['title' => '利比亚', 'english' => 'Libya', 'code' => 218],
+            ['title' => '利比里亚', 'english' => 'Liberia', 'code' => 231],
+            ['title' => '加拿大', 'english' => 'Canada', 'code' => 1],
+            ['title' => '加纳', 'english' => 'Ghana', 'code' => 233],
+            ['title' => '加蓬', 'english' => 'Gabon', 'code' => 241],
+            ['title' => '匈牙利', 'english' => 'Hungary', 'code' => 36],
+            ['title' => '南非', 'english' => 'South Africa', 'code' => 27],
+            ['title' => '博茨瓦纳', 'english' => 'Botswana', 'code' => 267],
+            ['title' => '卡塔尔', 'english' => 'Qatar', 'code' => 974],
+            ['title' => '卢旺达', 'english' => 'Rwanda', 'code' => 250],
+            ['title' => '卢森堡', 'english' => 'Luxembourg', 'code' => 352],
+            ['title' => '印尼', 'english' => 'Indonesia', 'code' => 62],
+            ['title' => '印度', 'english' => 'India', 'code' => 91918919],
+            ['title' => '危地马拉', 'english' => 'Guatemala', 'code' => 502],
+            ['title' => '厄瓜多尔', 'english' => 'Ecuador', 'code' => 593],
+            ['title' => '厄立特里亚', 'english' => 'Eritrea', 'code' => 291],
+            ['title' => '叙利亚', 'english' => 'Syria', 'code' => 963],
+            ['title' => '古巴', 'english' => 'Cuba', 'code' => 53],
+            ['title' => '吉尔吉斯斯坦', 'english' => 'Kyrgyzstan', 'code' => 996],
+            ['title' => '吉布提', 'english' => 'Djibouti', 'code' => 253],
+            ['title' => '哥伦比亚', 'english' => 'Colombia', 'code' => 57],
+            ['title' => '哥斯达黎加', 'english' => 'Costa Rica', 'code' => 506],
+            ['title' => '喀麦隆', 'english' => 'Cameroon', 'code' => 237],
+            ['title' => '图瓦卢', 'english' => 'Tuvalu', 'code' => 688],
+            ['title' => '土库曼斯坦', 'english' => 'Turkmenistan', 'code' => 993],
+            ['title' => '土耳其', 'english' => 'Turkey', 'code' => 90],
+            ['title' => '圣卢西亚', 'english' => 'Saint Lucia', 'code' => 1758],
+            ['title' => '圣基茨和尼维斯', 'english' => 'Saint Kitts and Nevis', 'code' => 1869],
+            ['title' => '圣多美和普林西比', 'english' => 'Sao Tome and Principe', 'code' => 239],
+            ['title' => '圣文森特和格林纳丁斯', 'english' => 'Saint Vincent and the Grenadines', 'code' => 1784],
+            ['title' => '圣皮埃尔和密克隆群岛', 'english' => 'Saint Pierre and Miquelon', 'code' => 508],
+            ['title' => '圣赫勒拿岛', 'english' => 'Saint Helena', 'code' => 290],
+            ['title' => '圣马力诺', 'english' => 'San Marino', 'code' => 378],
+            ['title' => '圭亚那', 'english' => 'Guyana', 'code' => 592],
+            ['title' => '坦桑尼亚', 'english' => 'Tanzania', 'code' => 255],
+            ['title' => '埃及', 'english' => 'Egypt', 'code' => 20],
+            ['title' => '埃塞俄比亚', 'english' => 'Ethiopia', 'code' => 251],
+            ['title' => '基里巴斯', 'english' => 'Kiribati', 'code' => 686],
+            ['title' => '塔吉克斯坦', 'english' => 'Tajikistan', 'code' => 992],
+            ['title' => '塞内加尔', 'english' => 'Senegal', 'code' => 221],
+            ['title' => '塞尔维亚', 'english' => 'Serbia and Montenegro', 'code' => 381],
+            ['title' => '塞拉利昂', 'english' => 'Sierra Leone', 'code' => 232],
+            ['title' => '塞浦路斯', 'english' => 'Cyprus', 'code' => 357],
+            ['title' => '塞舌尔', 'english' => 'Seychelles', 'code' => 248],
+            ['title' => '墨西哥', 'english' => 'Mexico', 'code' => 52],
+            ['title' => '多哥', 'english' => 'Togo', 'code' => 228],
+            ['title' => '多米尼克', 'english' => 'Dominica', 'code' => 1767],
+            ['title' => '奥地利', 'english' => 'Austria', 'code' => 43],
+            ['title' => '委内瑞拉', 'english' => 'Venezuela', 'code' => 58],
+            ['title' => '孟加拉', 'english' => 'Bangladesh', 'code' => 880],
+            ['title' => '安哥拉', 'english' => 'Angola', 'code' => 244],
+            ['title' => '安圭拉岛', 'english' => 'Anguilla', 'code' => 1264],
+            ['title' => '安道尔', 'english' => 'Andorra', 'code' => 376],
+            ['title' => '密克罗尼西亚', 'english' => 'Federated States of Micronesia', 'code' => 691],
+            ['title' => '尼加拉瓜', 'english' => 'Nicaragua', 'code' => 505],
+            ['title' => '尼日利亚', 'english' => 'Nigeria', 'code' => 234],
+            ['title' => '尼日尔', 'english' => 'Niger', 'code' => 227],
+            ['title' => '尼泊尔', 'english' => 'Nepal', 'code' => 977],
+            ['title' => '巴勒斯坦', 'english' => 'Palestine', 'code' => 970],
+            ['title' => '巴哈马', 'english' => 'The Bahamas', 'code' => 1242],
+            ['title' => '巴基斯坦', 'english' => 'Pakistan', 'code' => 92],
+            ['title' => '巴巴多斯', 'english' => 'Barbados', 'code' => 1246],
+            ['title' => '巴布亚新几内亚', 'english' => 'Papua New Guinea', 'code' => 675],
+            ['title' => '巴拉圭', 'english' => 'Paraguay', 'code' => 595],
+            ['title' => '巴拿马', 'english' => 'Panama', 'code' => 507],
+            ['title' => '巴林', 'english' => 'Bahrain', 'code' => 973],
+            ['title' => '巴西', 'english' => 'Brazil', 'code' => 55],
+            ['title' => '布基纳法索', 'english' => ' Burkina Faso', 'code' => 226],
+            ['title' => '布隆迪', 'english' => 'Burundi', 'code' => 257],
+            ['title' => '希腊', 'english' => ' Greece', 'code' => 30],
+            ['title' => '帕劳', 'english' => 'Palau', 'code' => 680],
+            ['title' => '库克群岛', 'english' => ' Cook Islands', 'code' => 682],
+            ['title' => '开曼群岛', 'english' => 'Cayman Islands', 'code' => 1345],
+            ['title' => '德国', 'english' => ' Germany', 'code' => 49],
+            ['title' => '意大利', 'english' => 'Italy', 'code' => 39],
+            ['title' => '所罗门群岛', 'english' => ' Solomon Islands', 'code' => 677],
+            ['title' => '托克劳', 'english' => 'Tokelau', 'code' => 690],
+            ['title' => '拉脱维亚', 'english' => 'Latvia', 'code' => 371],
+            ['title' => '挪威', 'english' => 'Norway', 'code' => 47],
+            ['title' => '捷克共和国', 'english' => 'Czech Republic', 'code' => 420],
+            ['title' => '摩尔多瓦', 'english' => 'Moldova', 'code' => 373],
+            ['title' => '摩洛哥', 'english' => 'Morocco', 'code' => 212],
+            ['title' => '摩纳哥', 'english' => 'Monaco', 'code' => 377],
+            ['title' => '文莱', 'english' => 'Brunei Darussalam', 'code' => 673],
+            ['title' => '斐济', 'english' => 'Fiji', 'code' => 679],
+            ['title' => '斯威士兰王国', 'english' => 'The Kingdom of Swaziland', 'code' => 268],
+            ['title' => '斯洛伐克', 'english' => 'Slovakia', 'code' => 421],
+            ['title' => '斯洛文尼亚', 'english' => 'Slovenia', 'code' => 386],
+            ['title' => '斯里兰卡', 'english' => 'Sri Lanka', 'code' => 94],
+            ['title' => '新加坡', 'english' => 'Singapore ', 'code' => 65],
+            ['title' => '新喀里多尼亚', 'english' => 'New Caledonia', 'code' => 687],
+            ['title' => '新西兰', 'english' => 'New Zealand', 'code' => 64],
+            ['title' => '日本', 'english' => 'Japan', 'code' => 81],
+            ['title' => '智利', 'english' => 'Chile', 'code' => 56],
+            ['title' => '朝鲜', 'english' => 'Korea, North', 'code' => 850],
+            ['title' => '柬埔寨 ', 'english' => 'Cambodia', 'code' => 855],
+            ['title' => '格林纳达', 'english' => 'Grenada', 'code' => 1473],
+            ['title' => '格陵兰', 'english' => 'Greenland', 'code' => 299],
+            ['title' => '格鲁吉亚', 'english' => 'Georgia', 'code' => 995],
+            ['title' => '比利时', 'english' => 'Belgium', 'code' => 32],
+            ['title' => '毛里塔尼亚', 'english' => 'Mauritania', 'code' => 222],
+            ['title' => '毛里求斯', 'english' => 'Mauritius', 'code' => 230],
+            ['title' => '汤加', 'english' => 'Tonga', 'code' => 676],
+            ['title' => '沙特阿拉伯', 'english' => 'Saudi Arabia', 'code' => 966],
+            ['title' => '法国', 'english' => 'France', 'code' => 33],
+            ['title' => '法属圭亚那', 'english' => 'French Guiana', 'code' => 594],
+            ['title' => '法属波利尼西亚', 'english' => 'French Polynesia', 'code' => 689],
+            ['title' => '法属西印度群岛', 'english' => 'french west indies', 'code' => 596],
+            ['title' => '法罗群岛', 'english' => 'Faroe Islands', 'code' => 298],
+            ['title' => '波兰', 'english' => 'Poland', 'code' => 48],
+            ['title' => '波多黎各', 'english' => 'The Commonwealth of Puerto Rico', 'code' => 17871939],
+            ['title' => '波黑', 'english' => 'Bosnia and Herzegovina ', 'code' => 387],
+            ['title' => '泰国', 'english' => 'Thailand', 'code' => 66],
+            ['title' => '津巴布韦', 'english' => 'Zimbabwe', 'code' => 263],
+            ['title' => '洪都拉斯', 'english' => 'Honduras', 'code' => 504],
+            ['title' => '海地', 'english' => 'Haiti', 'code' => 509],
+            ['title' => '澳大利亚', 'english' => 'Australia', 'code' => 61],
+            ['title' => '澳门', 'english' => 'Macao', 'code' => 853],
+            ['title' => '爱尔兰', 'english' => 'Ireland', 'code' => 353],
+            ['title' => '爱沙尼亚', 'english' => 'Estonia', 'code' => 372],
+            ['title' => '牙买加 ', 'english' => 'Jamaica', 'code' => 1876],
+            ['title' => '特克斯和凯科斯群岛', 'english' => 'Turks and Caicos Islands', 'code' => 1649],
+            ['title' => '特立尼达和多巴哥', 'english' => 'Trinidad and Tobago', 'code' => 1868],
+            ['title' => '玻利维亚', 'english' => 'Bolivia', 'code' => 591],
+            ['title' => '瑙鲁', 'english' => 'Nauru', 'code' => 674],
+            ['title' => '瑞典', 'english' => 'Sweden', 'code' => 46],
+            ['title' => '瑞士', 'english' => 'Switzerland', 'code' => 41],
+            ['title' => '瓜德罗普', 'english' => 'Guadeloupe', 'code' => 590],
+            ['title' => '瓦利斯和富图纳群岛', 'english' => 'Wallis et Futuna', 'code' => 681],
+            ['title' => '瓦努阿图', 'english' => 'Vanuatu', 'code' => 678],
+            ['title' => '留尼汪 ', 'english' => 'Reunion', 'code' => 262],
+            ['title' => '白俄罗斯', 'english' => 'Belarus', 'code' => 375],
+            ['title' => '百慕大', 'english' => 'Bermuda', 'code' => 1441],
+            ['title' => '直布罗陀', 'english' => 'Gibraltar', 'code' => 350],
+            ['title' => '福克兰群岛', 'english' => 'Falkland', 'code' => 500],
+            ['title' => '科威特', 'english' => 'Kuwait', 'code' => 965],
+            ['title' => '科摩罗和马约特', 'english' => 'Comoros', 'code' => 269],
+            ['title' => '科特迪瓦', 'english' => 'Cote d’Ivoire', 'code' => 225],
+            ['title' => '秘鲁', 'english' => 'Peru', 'code' => 51],
+            ['title' => '突尼斯', 'english' => 'Tunisia', 'code' => 216],
+            ['title' => '立陶宛', 'english' => 'Lithuania', 'code' => 370],
+            ['title' => '索马里', 'english' => 'Somalia', 'code' => 252],
+            ['title' => '约旦', 'english' => 'Jordan', 'code' => 962],
+            ['title' => '纳米比亚', 'english' => 'Namibia', 'code' => 264],
+            ['title' => '纽埃岛', 'english' => 'Island of Niue', 'code' => 683],
+            ['title' => '缅甸  ', 'english' => 'Burma', 'code' => 95],
+            ['title' => '罗马尼亚', 'english' => 'Romania', 'code' => 40],
+            ['title' => '美国', 'english' => 'United States of America', 'code' => 1],
+            ['title' => '美属维京群岛', 'english' => 'Virgin Islands', 'code' => 1340],
+            ['title' => '美属萨摩亚', 'english' => 'American Samoa', 'code' => 1684],
+            ['title' => '老挝', 'english' => 'Laos', 'code' => 856],
+            ['title' => '肯尼亚', 'english' => 'Kenya', 'code' => 254],
+            ['title' => '芬兰', 'english' => 'Finland', 'code' => 358],
+            ['title' => '苏丹', 'english' => 'Sudan', 'code' => 249],
+            ['title' => '苏里南', 'english' => 'Suriname', 'code' => 597],
+            ['title' => '英国', 'english' => 'United Kingdom', 'code' => 44],
+            ['title' => '英属维京群岛', 'english' => 'British Virgin Islands', 'code' => 1284],
+            ['title' => '荷兰', 'english' => 'Netherlands', 'code' => 31],
+            ['title' => '荷属安的列斯', 'english' => 'Netherlands Antilles', 'code' => 599],
+            ['title' => '莫桑比克', 'english' => 'Mozambique', 'code' => 258],
+            ['title' => '莱索托', 'english' => 'Lesotho', 'code' => 266],
+            ['title' => '菲律宾', 'english' => 'Philippines', 'code' => 63],
+            ['title' => '萨尔瓦多', 'english' => 'El Salvador', 'code' => 503],
+            ['title' => '萨摩亚', 'english' => 'Samoa', 'code' => 685],
+            ['title' => '葡萄牙', 'english' => 'Portugal', 'code' => 351],
+            ['title' => '蒙古', 'english' => 'Mongolia', 'code' => 976],
+            ['title' => '西班牙', 'english' => 'Spain', 'code' => 34],
+            ['title' => '贝宁', 'english' => 'Benin', 'code' => 229],
+            ['title' => '赞比亚', 'english' => 'Zambia', 'code' => 260],
+            ['title' => '赤道几内亚', 'english' => 'Equatorial Guinea', 'code' => 240],
+            ['title' => '越南', 'english' => 'Vietnam', 'code' => 84],
+            ['title' => '阿塞拜疆', 'english' => 'Azerbaijan', 'code' => 994],
+            ['title' => '阿富汗', 'english' => 'Afghanistan', 'code' => 93],
+            ['title' => '阿尔及利亚', 'english' => 'Algeria', 'code' => 213],
+            ['title' => '阿尔巴尼亚', 'english' => 'Albania', 'code' => 355],
+            ['title' => '阿拉伯联合酋长国', 'english' => 'United Arab Emirates', 'code' => 971],
+            ['title' => '阿曼', 'english' => 'Oman', 'code' => 968],
+            ['title' => '阿根廷', 'english' => 'Argentina', 'code' => 54],
+            ['title' => '阿鲁巴', 'english' => 'Aruba', 'code' => 297],
+            ['title' => '韩国', 'english' => 'Korea, South)', 'code' => 82],
+            ['title' => '香港', 'english' => 'Hong Kong(SAR)', 'code' => 852],
+            ['title' => '马其顿', 'english' => 'Macedonia', 'code' => 389],
+            ['title' => '马尔代夫', 'english' => 'Maldives  ', 'code' => 960],
+            ['title' => '马拉维', 'english' => ' Malawi', 'code' => 265],
+            ['title' => '马来西亚', 'english' => 'Malaysia', 'code' => 60],
+            ['title' => '马绍尔群岛', 'english' => 'Marshall Islands', 'code' => 692],
+            ['title' => '马耳他', 'english' => 'Malta', 'code' => 356],
+            ['title' => '马达加斯加', 'english' => 'Madagascar', 'code' => 261],
+            ['title' => '马里', 'english' => 'Mali', 'code' => 223],
+            ['title' => '黎巴嫩', 'english' => 'Lebanon', 'code' => 961],
+            ['title' => '黑山共和国', 'english' => 'The Republic of Montenegro', 'code' => 382],
+        ];
+    }
+
+}

+ 71 - 0
application/store/service/GoodsService.php

@@ -0,0 +1,71 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\service;
+
+use think\Db;
+
+/**
+ * 商品数据管理
+ * Class GoodsService
+ * @package app\store\logic
+ */
+class GoodsService
+{
+    /**
+     * 同步商品库存信息
+     * @param integer $goodsId
+     * @return boolean
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public static function syncStock($goodsId)
+    {
+        // 商品入库统计
+        $fields = "goods_id,goods_spec,ifnull(sum(number_stock),0) number_stock";
+        $stockList = Db::name('StoreGoodsStock')->field($fields)->where(['goods_id' => $goodsId])->group('goods_id,goods_spec')->select();
+        // 商品销量统计
+        $where = [['b.goods_id', 'eq', $goodsId], ['a.status', 'in', ['1', '2', '3', '4', '5']]];
+        $fields = 'b.goods_id,b.goods_spec,ifnull(sum(b.number_goods),0) number_sales';
+        $salesList = Db::table('store_order a')->field($fields)->leftJoin('store_order_list b', 'a.order_no=b.order_no')->where($where)->group('b.goods_id,b.goods_spec')->select();
+        // 组装更新数据
+        $dataList = [];
+        foreach (array_merge($stockList, $salesList) as $vo) {
+            $key = "{$vo['goods_id']}@@{$vo['goods_spec']}";
+            $dataList[$key] = isset($dataList[$key]) ? array_merge($dataList[$key], $vo) : $vo;
+            if (empty($dataList[$key]['number_sales'])) $dataList[$key]['number_sales'] = '0';
+            if (empty($dataList[$key]['number_stock'])) $dataList[$key]['number_stock'] = '0';
+        }
+        unset($salesList, $stockList);
+        // 更新商品规格销量及库存
+        foreach ($dataList as $vo) Db::name('StoreGoodsList')->where([
+            'goods_id'   => $goodsId,
+            'goods_spec' => $vo['goods_spec'],
+        ])->update([
+            'number_stock' => $vo['number_stock'],
+            'number_sales' => $vo['number_sales'],
+        ]);
+        // 更新商品主体销量及库存
+        Db::name('StoreGoods')->where(['id' => $goodsId])->update([
+            'number_stock' => intval(array_sum(array_column($dataList, 'number_stock'))),
+            'number_sales' => intval(array_sum(array_column($dataList, 'number_sales'))),
+        ]);
+        return true;
+    }
+
+}

+ 67 - 0
application/store/service/OrderService.php

@@ -0,0 +1,67 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+namespace app\store\service;
+
+use think\Db;
+
+/**
+ * 订单服务管理器
+ * Class OrderService
+ * @package app\store\service
+ */
+class OrderService
+{
+    /**
+     * 根据订单号升级会员等级
+     * @param string $order_no 订单单号
+     * @return boolean
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public static function update($order_no)
+    {
+        // @todo 更新订单状态
+    }
+
+    /**
+     * 根据订单同步库存销量
+     * @param string $order_no
+     * @return boolean
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public static function syncStock($order_no)
+    {
+        $map = ['order_no' => $order_no];
+        $goodsIds = Db::name('StoreOrderList')->where($map)->column('goods_id');
+        foreach (array_unique($goodsIds) as $goodsId) if (!GoodsService::syncStock($goodsId)) return false;
+        return true;
+    }
+
+    /**
+     * 订单利润计算
+     * @param string $order_no
+     * @return boolean
+     */
+    public static function profit($order_no = '')
+    {
+        // @todo 计算订单返佣
+    }
+}

+ 19 - 0
application/store/sys.php

@@ -0,0 +1,19 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
+// | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
+// +----------------------------------------------------------------------
+
+// 注册系统指令
+\think\Console::addDefaultCommands([
+    'app\store\command\AutoRun',
+]);

+ 32 - 0
application/store/view/config/index-store-config.html

@@ -0,0 +1,32 @@
+<form onsubmit="return false;" data-auto="true" action="{:url('save')}" method="post" class='layui-form layui-card' autocomplete="off">
+
+    <div class="layui-card-body padding-40">
+        <label class="layui-form-item block relative">
+            <span class="color-green margin-right-10">商城展示名称</span><span class="nowrap color-desc">StoreTitle</span>
+            <input name="store_title" required placeholder="请输入商城展示名称" value="{:sysconf('store_title')}" class="layui-input">
+            <p class="help-block">订单支付等待时长,新下订单未在此时间内容完成支付将会被自动取消</p>
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">未订单的支付等待时长(小时)</span><span class="nowrap color-desc">OrderWaitTime</span>
+            <input onblur="this.value=Math.abs(parseFloat(this.value)||0.00).toFixed(2)" name="store_order_wait_time" required placeholder="请输入订单支付等待时长" value="{:sysconf('store_order_wait_time')}" class="layui-input">
+            <p class="help-block">订单支付等待时长,新下订单未在此时间内容完成支付将会被自动取消</p>
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">已取消的订单清理时长(小时)</span><span class="nowrap color-desc">OrderClearTime</span>
+            <input onblur="this.value=Math.abs(parseFloat(this.value)||0.00).toFixed(2)" name="store_order_clear_time" required placeholder="请输入已取消订单清理时长" value="{:sysconf('store_order_clear_time')}" class="layui-input">
+            <p class="help-block">已取消订单清理时长,已取消未支付订单将在此时间点将被自动清理掉</p>
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">已发货订单自动确认收货时长(小时)</span><span class="nowrap color-desc">OrderConfirmTime</span>
+            <input onblur="this.value=Math.abs(parseFloat(this.value)||0.00).toFixed(2)" name="store_order_confirm_time" required placeholder="请输入已发货订单自动确认收货时长" value="{:sysconf('store_order_confirm_time')}" class="layui-input">
+            <p class="help-block">已取消订单清理时长,已取消未支付订单将在此时间点将被自动清理掉</p>
+        </label>
+
+        <div class="layui-form-item text-center margin-top-20">
+            <button class="layui-btn" type="submit">保存配置</button>
+        </div>
+    </div>
+</form>

+ 45 - 0
application/store/view/config/index-store-message1.html

@@ -0,0 +1,45 @@
+<form onsubmit="return false;" data-auto="true" action="{:url('save')}" method="post" class='layui-form layui-card' autocomplete="off">
+
+    <div class="layui-card-body padding-40">
+
+        <label class="layui-form-item block relative">
+            <span class="color-green margin-right-10">接口查询状态</span><span class="nowrap color-desc">UserState</span>
+            {if empty($query.code)}
+            <p class="layui-input layui-bg-gray color-red">{$query.msg|default=''}</p>
+            {elseif $query.code eq 1}
+            <p class="layui-input layui-bg-gray color-green">平台剩余 {$query.num|default=0} 条可用短信,{$query.msg|default=''}</p>
+            {else}
+            <p class="layui-input layui-bg-gray color-text">没有结果</p>
+            {/if}
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">短信平台账号</span><span class="nowrap color-desc">Username</span>
+            <input name="sms_zt_username" required placeholder="请输入助通短信平台账号" value="{:sysconf('sms_zt_username')}" class="layui-input">
+            <p class="help-block">短信平台账号,可以联系18122377655获取账号与密码</p>
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">短信平台密码</span><span class="nowrap color-desc">Password</span>
+            <input name="sms_zt_password" required placeholder="请输入助通短信平台密码" value="{:sysconf('sms_zt_password')}" class="layui-input">
+            <p class="help-block">短信平台密码,可以联系18122377655获取账号与密码</p>
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">短信发送安全码</span><span class="nowrap color-desc">Secure</span>
+            <input name="sms_secure" required placeholder="请输入短信发送安全码" value="{:sysconf('sms_secure')}" class="layui-input">
+            <p class="help-block">短信发送安全码,调用接口发短信时需要传入此参数</p>
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">注册短信模板</span><span class="nowrap color-desc">Template</span>
+            <textarea class="layui-textarea" required name="sms_reg_template" placeholder="请输入助通短信平台密码">{:sysconf('sms_reg_template')}</textarea>
+            <p class="help-block">会员注册短信模板,验证码变量使用 {code} 代替</p>
+        </label>
+
+        <div class="layui-form-item text-center margin-top-20">
+            <button class="layui-btn" type="submit">保存配置</button>
+        </div>
+
+    </div>
+</form>

+ 45 - 0
application/store/view/config/index-store-message2.html

@@ -0,0 +1,45 @@
+<form onsubmit="return false;" data-auto="true" action="{:url('save')}" method="post" class='layui-form layui-card' autocomplete="off">
+
+    <div class="layui-card-body padding-40">
+
+        <label class="layui-form-item block relative">
+            <span class="color-green margin-right-10">接口查询状态</span><span class="nowrap color-desc">UserState</span>
+            {if empty($query2.code)}
+            <p class="layui-input layui-bg-gray color-red">{$query2.msg|default=''}</p>
+            {elseif $query2.code eq 1}
+            <p class="layui-input layui-bg-gray color-green">平台剩余 {$query2.num|default=0} 元可用余额,{$query2.msg|default=''}</p>
+            {else}
+            <p class="layui-input layui-bg-gray color-text">没有结果</p>
+            {/if}
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">短信平台账号</span><span class="nowrap color-desc">Username</span>
+            <input name="sms_zt_username2" required placeholder="请输入短信平台账号" value="{:sysconf('sms_zt_username2')}" class="layui-input">
+            <p class="help-block">短信平台账号,可以联系18122377655获取账号与密码</p>
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">短信平台密码</span><span class="nowrap color-desc">Password</span>
+            <input name="sms_zt_password2" required placeholder="请输入短信平台密码" value="{:sysconf('sms_zt_password2')}" class="layui-input">
+            <p class="help-block">短信平台密码,可以联系18122377655获取账号与密码</p>
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">短信发送安全码</span><span class="nowrap color-desc">Secure</span>
+            <input name="sms_secure2" required placeholder="请输入短信发送安全码" value="{:sysconf('sms_secure2')}" class="layui-input">
+            <p class="help-block">短信发送安全码,调用接口发短信时需要传入此参数</p>
+        </label>
+
+        <label class="layui-form-item margin-top-20 block relative">
+            <span class="color-green margin-right-10">注册短信模板</span><span class="nowrap color-desc">Template</span>
+            <textarea class="layui-textarea" required name="sms_reg_template2" placeholder="请输入短信平台密码">{:sysconf('sms_reg_template2')}</textarea>
+            <p class="help-block">会员注册短信模板,验证码变量使用 {code} 代替</p>
+        </label>
+
+        <div class="layui-form-item text-center margin-top-20">
+            <button class="layui-btn" type="submit">保存配置</button>
+        </div>
+
+    </div>
+</form>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio