zzb 2 жил өмнө
commit
fbff3d1b82
100 өөрчлөгдсөн 14970 нэмэгдсэн , 0 устгасан
  1. 12 0
      .example.env
  2. 13 0
      .gitignore
  3. 42 0
      .travis.yml
  4. 32 0
      LICENSE.txt
  5. 41 0
      README.md
  6. 1 0
      app/.htaccess
  7. 31 0
      app/admin/common.php
  8. 25 0
      app/admin/config.php
  9. 112 0
      app/admin/controller/Address.php
  10. 230 0
      app/admin/controller/Admin.php
  11. 172 0
      app/admin/controller/AdminCate.php
  12. 79 0
      app/admin/controller/AdminLog.php
  13. 91 0
      app/admin/controller/ApiGeneration.php
  14. 327 0
      app/admin/controller/Article.php
  15. 241 0
      app/admin/controller/Articlecate.php
  16. 301 0
      app/admin/controller/Attachment.php
  17. 402 0
      app/admin/controller/Catalog.php
  18. 203 0
      app/admin/controller/CodeGeneration.php
  19. 118 0
      app/admin/controller/Common.php
  20. 172 0
      app/admin/controller/Config.php
  21. 180 0
      app/admin/controller/ConfigOption.php
  22. 120 0
      app/admin/controller/ConfigTab.php
  23. 234 0
      app/admin/controller/Databackup.php
  24. 88 0
      app/admin/controller/Emailconfig.php
  25. 291 0
      app/admin/controller/FileManage.php
  26. 101 0
      app/admin/controller/Index.php
  27. 141 0
      app/admin/controller/Main.php
  28. 196 0
      app/admin/controller/Menu.php
  29. 76 0
      app/admin/controller/PointLog.php
  30. 82 0
      app/admin/controller/Smsconfig.php
  31. 135 0
      app/admin/controller/Templet.php
  32. 206 0
      app/admin/controller/Tomessages.php
  33. 116 0
      app/admin/controller/Urlsconfig.php
  34. 234 0
      app/admin/controller/User.php
  35. 69 0
      app/admin/controller/Webconfig.php
  36. 98 0
      app/admin/controller/base/Permissions.php
  37. 44 0
      app/admin/model/Admin.php
  38. 24 0
      app/admin/model/AdminCate.php
  39. 31 0
      app/admin/model/AdminLog.php
  40. 37 0
      app/admin/model/AdminMenu.php
  41. 53 0
      app/admin/model/Urlconfig.php
  42. 12 0
      app/admin/tags.php
  43. 171 0
      app/admin/view/address/index.html
  44. 116 0
      app/admin/view/address/publish.html
  45. 90 0
      app/admin/view/admin/edit_password.html
  46. 193 0
      app/admin/view/admin/index.html
  47. 121 0
      app/admin/view/admin/personal.html
  48. 157 0
      app/admin/view/admin/publish.html
  49. 143 0
      app/admin/view/admin_cate/index.html
  50. 74 0
      app/admin/view/admin_cate/preview.html
  51. 157 0
      app/admin/view/admin_cate/publish.html
  52. 136 0
      app/admin/view/admin_log/index.html
  53. 117 0
      app/admin/view/api_generation/index.html
  54. 295 0
      app/admin/view/article/index.html
  55. 239 0
      app/admin/view/article/publish.html
  56. 71 0
      app/admin/view/article/publish_markdown.html
  57. 63 0
      app/admin/view/article/publish_tinymce.html
  58. 16 0
      app/admin/view/article/publish_ueditor.html
  59. 203 0
      app/admin/view/article/publish_wangEditor.html
  60. 196 0
      app/admin/view/articlecate/index.html
  61. 188 0
      app/admin/view/articlecate/publish.html
  62. 332 0
      app/admin/view/attachment/index.html
  63. 133 0
      app/admin/view/attachment/select_image.html
  64. 237 0
      app/admin/view/catalog/index.html
  65. 254 0
      app/admin/view/catalog/publish.html
  66. 227 0
      app/admin/view/code_generation/index.html
  67. 250 0
      app/admin/view/code_generation/tpl/AdminController.php.tp
  68. 165 0
      app/admin/view/code_generation/tpl/ApiController.php.tp
  69. 145 0
      app/admin/view/code_generation/tpl/ApiDoc.md.tp
  70. 40 0
      app/admin/view/code_generation/tpl/Model.php.tp
  71. 365 0
      app/admin/view/code_generation/tpl/index.html.tp
  72. 324 0
      app/admin/view/code_generation/tpl/publish.html.tp
  73. 81 0
      app/admin/view/common/login.html
  74. 214 0
      app/admin/view/config/index.html
  75. 101 0
      app/admin/view/config/index2.html
  76. 141 0
      app/admin/view/config/publish.html
  77. 242 0
      app/admin/view/config_option/index.html
  78. 150 0
      app/admin/view/config_option/publish.html
  79. 168 0
      app/admin/view/config_tab/index.html
  80. 84 0
      app/admin/view/config_tab/publish.html
  81. 133 0
      app/admin/view/databackup/importlist.html
  82. 204 0
      app/admin/view/databackup/index.html
  83. 166 0
      app/admin/view/emailconfig/index.html
  84. 63 0
      app/admin/view/emailconfig/publish_tinymce.html
  85. 367 0
      app/admin/view/file_manage/index.html
  86. 149 0
      app/admin/view/file_manage/publish.html
  87. 10 0
      app/admin/view/index/index.html
  88. 401 0
      app/admin/view/main/index.html
  89. 222 0
      app/admin/view/menu/index.html
  90. 164 0
      app/admin/view/menu/publish.html
  91. 129 0
      app/admin/view/point_log/index.html
  92. 134 0
      app/admin/view/public/foot.html
  93. 178 0
      app/admin/view/public/footer.html
  94. 48 0
      app/admin/view/public/header.html
  95. 29 0
      app/admin/view/public/left.html
  96. 164 0
      app/admin/view/smsconfig/index.html
  97. 208 0
      app/admin/view/templet/index.html
  98. 149 0
      app/admin/view/templet/publish.html
  99. 346 0
      app/admin/view/tomessages/index.html
  100. 94 0
      app/admin/view/tomessages/publish.html

+ 12 - 0
.example.env

@@ -0,0 +1,12 @@
+app_debug = true
+app_trace = false
+
+db_host = 127.0.0.1
+db_port = 3306
+db_name = tplay
+db_username = tplay
+db_password = 123456
+db_prefix = tplay_
+
+# 开放用户模块
+open_user_module = false

+ 13 - 0
.gitignore

@@ -0,0 +1,13 @@
+~$*
+**/.well-known/*
+.idea/*
+/.vscode
+public/.user.ini
+.env
+*.log
+install.lock
+runtime/*
+public/uploads/*
+public/*.xml
+app/common/view/template/*
+app/install/data/*_demo/*

+ 42 - 0
.travis.yml

@@ -0,0 +1,42 @@
+sudo: false
+
+language: php
+
+branches:
+  only:
+    - stable
+
+cache:
+  directories:
+    - $HOME/.composer/cache
+
+before_install:
+  - composer self-update
+
+install:
+  - composer install --no-dev --no-interaction --ignore-platform-reqs
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
+  - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
+  - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
+
+script:
+  - php think unit
+
+deploy:
+  provider: releases
+  api_key:
+    secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
+  file:
+    - ThinkPHP_Core.zip
+    - ThinkPHP_Full.zip
+  skip_cleanup: true
+  on:
+    tags: true

+ 32 - 0
LICENSE.txt

@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件: 
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。 
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.

+ 41 - 0
README.md

@@ -0,0 +1,41 @@
+
+![预览](https://images.gitee.com/uploads/images/2020/1209/110523_b9207c4c_396298.jpeg "tplaylogin.jpg")
+![预览](https://images.gitee.com/uploads/images/2020/1209/110503_e5481600_396298.jpeg "tplayindex.jpg")
+![预览](https://images.gitee.com/uploads/images/2020/1209/110535_c00e2385_396298.jpeg "tplaymenu.jpg")
+
+## 简介
+ 
+Tplay是一款基于TP5 + layui + Mysql开发的后台管理框架,致力于快速建站。
+
+集成了一般应用所必须的基础性功能,为开发者减少重复性的工作,提升开发速度,规范团队开发模式。
+
+本人基于Tplay 1.3.4二次开发,修复了许多bug,添加了许多功能,让开发更有效率。
+
+
+
+## 安装使用
+
+### [查看在线文档](https://gitee.com/pps/tplay/wikis/pages?sort_id=4562118&doc_id=480370)
+> 离线文档已加入到源码中,文档目录 tplay/app/install/doc/
+
+## 版本信息
+
+php适用版本:7 ~ 7.2
+
+mysql适用版本:5.5 ~ 5.7
+
+基于layui-2.5.6
+
+基于thinkphp-5.0.24
+
+## 版权信息
+
+遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2017 by Tplay
+
+All rights reserved
+
+

+ 1 - 0
app/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 31 - 0
app/admin/common.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+// +----------------------------------------------------------------------
+// 如果只需要给admin模块添加函数,在此文件中定义函数即可,系统会自动加载
+// +----------------------------------------------------------------------
+
+function addlog($operation_id = 0, $remark = '')
+{
+    //兼容2021/05/24之前的旧版代码
+    return true;
+}
+
+/**
+ * 清除temp目录里的模板缓存
+ * @param string $path
+ * @return bool
+ */
+function clear_temp_cache($path = TEMP_PATH)
+{
+    if (array_map('unlink', glob($path . '*.php'))) {
+        return true;
+    } else {
+        return false;
+    }
+}

+ 25 - 0
app/admin/config.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2020/2/4
+ * Time: 12:47
+ */
+
+//配置文件
+return [
+    //全局替换
+    'view_replace_str' => [
+        '__PUBLIC__' => '/static/public',
+        '__CSS__' => '/static/admin/css',
+        '__JS__' => '/static/admin/js',
+        '__IMG__' => '/static/admin/images',
+        '__EXTEND_JS__' => '/static/public/layui/extend'
+    ],
+    //session设置
+    'session' => [
+        'prefix' => 'admin',
+        'type' => '',
+        'auto_start' => true,
+    ],
+];

+ 112 - 0
app/admin/controller/Address.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use think\Db;
+
+class Address extends Permissions
+{
+    private function getModel()
+    {
+        return new \app\common\model\Address();
+    }
+
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [];
+            if (isset($post['ids']) and !empty($post['ids'])) {
+                $where['id'] = ['in', $post['ids']];
+            }
+            if (!empty($post["title"])) {
+                $where["title"] = ['like', '%' . $post["title"] . '%'];
+            }
+            if (!empty($post["address"])) {
+                $where["address"] = ['like', '%' . $post["address"] . '%'];
+            }
+
+            $model = $this->getModel();
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order('id desc')->select();
+
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            return $this->fetch();
+        }
+    }
+
+
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = $this->getModel();
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+
+
+            //验证
+            $validate = new \think\Validate([
+                ['title|标题', 'max:50'],
+                ['address|地址', 'max:500'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        }
+
+        if ($id > 0) {
+            //修改
+            $data = $model->where('id', $id)->find();
+            if (empty($data)) {
+                $this->error('id不正确');
+            }
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改成功');
+                }
+            } else {
+                $this->assign('data', $data);
+                return $this->fetch();
+            }
+        } else {
+            //新增
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加失败');
+                } else {
+                    $this->success('添加成功', 'index');
+                }
+            } else {
+                return $this->fetch();
+            }
+        }
+    }
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            if (false == $this->getModel()->where('id', $id)->delete()) {
+                $this->error('删除失败');
+            } else {
+                $this->success('删除成功', 'index');
+            }
+        }
+    }
+
+    public function deletes()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+            if ($this->getModel()->where('id', 'in', $ids)->delete()) {
+                $this->success('删除成功');
+            }
+        }
+    }
+
+}

+ 230 - 0
app/admin/controller/Admin.php

@@ -0,0 +1,230 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2023/02/05
+ * Time: 20:33
+ */
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\admin\model\Admin as adminModel;
+use think\Db;
+use think\Session;
+
+
+class Admin extends Permissions
+{
+
+    /**
+     * 管理员列表
+     * @return mixed
+     */
+    public function index()
+    {
+        $model = new adminModel();
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [];
+            if (isset($post['keywords']) and !empty($post['keywords'])) {
+                $where['nickname'] = ['like', '%' . $post['keywords'] . '%'];
+            }
+            if (isset($post['admin_cate_id']) and $post['admin_cate_id'] > 0) {
+                $where['admin_cate_id'] = $post['admin_cate_id'];
+            }
+            if (isset($post['create_time']) and !empty($post['create_time'])) {
+                $min_time = strtotime($post['create_time']);
+                $max_time = $min_time + 24 * 60 * 60;
+                $where['create_time'] = [['>=', $min_time], ['<=', $max_time]];
+            }
+
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order('admin_cate_id desc')->select();
+
+            foreach ($data as $k => $v) {
+                $v['cate_name'] = $v->admincate->name;
+                $v['head_pic'] = geturl($v->thumb, '/static/public/images/tx.jpg');
+                $data[$k] = $v;
+            }
+
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            $this->assign('cate', Db::name('admin_cate')->select());
+            return $this->fetch();
+        }
+    }
+
+    /**
+     * 管理员的添加及修改
+     * @return mixed
+     */
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = new adminModel();
+        if ($id > 0) {
+            if ($this->request->isPost()) {
+                $post = $this->request->post();
+                $validate = new \think\Validate([
+                    ['admin_cate_id', 'require', '请选择管理员分组'],
+                ]);
+                if (!$validate->check($post)) {
+                    $this->error('提交失败:' . $validate->getError());
+                }
+                //验证昵称是否存在
+                $nickname = $model->where(['nickname' => $post['nickname'], 'id' => ['neq', $post['id']]])->select();
+                if (!empty($nickname)) {
+                    $this->error('提交失败:该昵称已被占用');
+                }
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改管理员信息成功', 'admin/admin/index');
+                }
+            } else {
+                $info['admin'] = $model->where('id', $id)->find();
+                $info['admin_cate'] = Db::name('admin_cate')->select();
+                $this->assign('info', $info);
+                return $this->fetch();
+            }
+        } else {
+            //是新增操作
+            if ($this->request->isPost()) {
+                $post = $this->request->post();
+                $validate = new \think\Validate([
+                    ['name|账号', 'require|alphaDash'],
+                    ['password', 'require|confirm', '密码不能为空|两次密码不一致'],
+                    ['password_confirm', 'require', '重复密码不能为空'],
+                    ['admin_cate_id', 'require', '请选择管理员分组'],
+                ]);
+                if (!$validate->check($post)) {
+                    $this->error('提交失败:' . $validate->getError());
+                }
+                //验证用户名是否存在
+                $name = $model->where('name', $post['name'])->select();
+                if (!empty($name)) {
+                    $this->error('提交失败:该用户名已被注册');
+                }
+                //验证昵称是否存在
+                $nickname = $model->where('nickname', $post['nickname'])->select();
+                if (!empty($nickname)) {
+                    $this->error('提交失败:该昵称已被占用');
+                }
+                //密码处理
+                $post['password'] = password($post['password']);
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加管理员失败');
+                } else {
+                    $this->success('添加管理员成功', 'admin/admin/index');
+                }
+            } else {
+                $info['admin_cate'] = Db::name('admin_cate')->select();
+                $this->assign('info', $info);
+                return $this->fetch();
+            }
+        }
+    }
+
+
+    /**
+     * 管理员删除
+     */
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->has('id') ? $this->request->param('id', 0, 'intval') : 0;
+            if ($id == 1) {
+                $this->error('网站所有者不能被删除');
+            }
+            if ($id == Session::get(self::ADMIN_ID)) {
+                $this->error('自己不能删除自己');
+            }
+            if (false == Db::name('admin')->where('id', $id)->delete()) {
+                $this->error('删除失败');
+            } else {
+                $this->success('删除成功', 'admin/admin/index');
+            }
+        }
+    }
+
+    //重置密码
+    public function resetpass()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->has('id') ? $this->request->param('id', 0, 'intval') : 0;
+            if ($id == 1) {
+                $this->error('admin不能被重置');
+            }
+            if ($id == Session::get(self::ADMIN_ID)) {
+                $this->error('不能重置自己账号');
+            }
+            if (false == Db::name('admin')->where('id', $id)->update(['password' => password(123456)])) {
+                $this->error('重置失败');
+            } else {
+                $this->success('重置成功', 'admin/admin/index');
+            }
+        }
+    }
+
+    /**
+     * 管理员个人资料修改
+     * @return mixed
+     */
+    public function personal()
+    {
+        $id = Session::get(self::ADMIN_ID);
+        $model = new adminModel();
+        if ($id > 0) {
+            $admin = $model->where('id', $id)->find();
+            if ($this->request->isPost()) {
+                $thumb = $this->request->post('thumb');
+                $nickname = $this->request->post('nickname');
+                if (false == $admin->save(['thumb' => $thumb, 'nickname' => $nickname])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改个人信息成功', 'admin/admin/personal');
+                }
+            } else {
+                $this->assign('info', $admin);
+                return $this->fetch();
+            }
+        } else {
+            $this->error('id不正确');
+        }
+    }
+
+    /**
+     * 修改密码
+     * @return mixed
+     */
+    public function editPassword()
+    {
+        if ($this->request->isPost()) {
+            $id = Session::get(self::ADMIN_ID);
+            $post = $this->request->post();
+            $validate = new \think\Validate([
+                ['password', 'require', '原密码不能为空'],
+                ['password', 'require|confirm', '新密码不能为空|确认密码不一致'],
+                ['password_confirm', 'require', '确认密码不能为空'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+            $admin = Db::name('admin')->where('id', $id)->find();
+            if (password($post['password_old']) == $admin['password']) {
+                if (false == Db::name('admin')->where('id', $id)->update(['password' => password($post['password'])])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改成功', 'admin/main/index');
+                }
+            } else {
+                $this->error('原密码错误');
+            }
+        } else {
+            return $this->fetch();
+        }
+    }
+
+}

+ 172 - 0
app/admin/controller/AdminCate.php

@@ -0,0 +1,172 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2023/02/05
+ * Time: 20:33
+ */
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use think\Db;
+
+
+class AdminCate extends Permissions
+{
+    /**
+     * 管理员权限分组列表
+     * @return mixed
+     */
+    public function index()
+    {
+        $model = new \app\admin\model\AdminCate;
+        $post = $this->request->param();
+        $where = [];
+        if (isset($post['keywords']) and !empty($post['keywords'])) {
+            $where['name'] = ['like', '%' . $post['keywords'] . '%'];
+        }
+        if (isset($post['create_time']) and !empty($post['create_time'])) {
+            $min_time = strtotime($post['create_time']);
+            $max_time = $min_time + 24 * 60 * 60;
+            $where['create_time'] = [['>=', $min_time], ['<=', $max_time]];
+        }
+        $cate = $model->where($where)->order('create_time desc')->paginate(15, false, ['query' => $this->request->param()]);
+        $this->assign('cate', $cate);
+        return $this->fetch();
+    }
+
+    /**
+     * 管理员角色添加和修改操作
+     * @return mixed
+     */
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = new \app\admin\model\AdminCate();
+        if ($id > 0) {
+            if ($this->request->isPost()) {
+                $post = $this->request->post();
+                $validate = new \think\Validate([
+                    ['name', 'require', '角色名称不能为空'],
+                ]);
+                if (!$validate->check($post)) {
+                    $this->error('提交失败:' . $validate->getError());
+                }
+                $name = $model->where(['name' => $post['name'], 'id' => ['neq', $post['id']]])->select();
+                if (!empty($name)) {
+                    $this->error('提交失败:该角色名已存在');
+                }
+                //处理选中的权限菜单id,转为字符串
+                if (!empty($post['admin_menu_id'])) {
+                    $post['permissions'] = implode(',', $post['admin_menu_id']);
+                } else {
+                    $post['permissions'] = '0';
+                }
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改角色信息成功', 'index');
+                }
+            } else {
+                $info['cate'] = $model->where('id', $id)->find();
+                if (!empty($info['cate']['permissions'])) {
+                    //将菜单id字符串拆分成数组
+                    $info['cate']['permissions'] = explode(',', $info['cate']['permissions']);
+                }
+                $menus = Db::name('admin_menu')->where('type', 1)->order('orders asc')->select();
+                $info['menu'] = $this->menulist($menus);
+                $this->assign('info', $info);
+                return $this->fetch();
+            }
+        } else {
+            if ($this->request->isPost()) {
+                $post = $this->request->post();
+                $validate = new \think\Validate([
+                    ['name', 'require', '角色名称不能为空'],
+                ]);
+                if (!$validate->check($post)) {
+                    $this->error('提交失败:' . $validate->getError());
+                }
+                $name = $model->where('name', $post['name'])->find();
+                if (!empty($name)) {
+                    $this->error('提交失败:该角色名已存在');
+                }
+                //处理选中的权限菜单id,转为字符串
+                if (!empty($post['admin_menu_id'])) {
+                    $post['permissions'] = implode(',', $post['admin_menu_id']);
+                } else {
+                    $post['permissions'] = '0';
+                }
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加角色失败');
+                } else {
+                    $this->success('添加角色成功', 'index');
+                }
+            } else {
+                $menus = Db::name('admin_menu')->where('type', 1)->order('orders asc')->select();
+                $info['menu'] = $this->menulist($menus);
+                $this->assign('info', $info);
+                return $this->fetch();
+            }
+        }
+    }
+
+    public function preview()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = new \app\admin\model\AdminCate();
+        $info['cate'] = $model->where('id', $id)->find();
+        if (!empty($info['cate']['permissions'])) {
+            //将菜单id字符串拆分成数组
+            $info['cate']['permissions'] = explode(',', $info['cate']['permissions']);
+        }
+        $menus = Db::name('admin_menu')->where('type', 1)->order('orders asc')->select();
+        $info['menu'] = $this->menulist($menus);
+        $this->assign('info', $info);
+        return $this->fetch();
+    }
+
+    protected function menulist($menu, $id = 0, $level = 0)
+    {
+        static $menus = array();
+        $size = count($menus) - 1;
+        foreach ($menu as $value) {
+            if ($value['pid'] == $id) {
+                $value['level'] = $level + 1;
+                $value['str'] = $level == 0 ? "" : str_repeat('&emsp;&emsp;', $level) . '└ ';
+                if ($level == 0) {
+                    $menus[] = $value;
+                } else {
+                    $menus[$size]['list'][] = $value;
+                }
+                $this->menulist($menu, $value['id'], $value['level']);
+            }
+        }
+        return $menus;
+    }
+
+    /**
+     * 管理员角色删除
+     */
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->has('id') ? $this->request->param('id', 0, 'intval') : 0;
+            if ($id > 0) {
+                if ($id == 1) {
+                    $this->error('超级管理员角色不能删除');
+                }
+                if (false == Db::name('admin_cate')->where('id', $id)->delete()) {
+                    $this->error('删除失败');
+                } else {
+                    $this->success('删除成功', 'index');
+                }
+            } else {
+                $this->error('id不正确');
+            }
+        }
+    }
+
+}

+ 79 - 0
app/admin/controller/AdminLog.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2023/02/05
+ * Time: 20:33
+ */
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\admin\model\AdminMenu;
+use think\Db;
+use think\Hook;
+
+
+class AdminLog extends Permissions
+{
+    /**
+     * 管理员操作记录
+     * @return mixed
+     */
+    public function index()
+    {
+        $model = new \app\admin\model\AdminLog();
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [];
+            if (isset($post['admin_menu_id']) and $post['admin_menu_id'] > 0) {
+                $where['admin_menu_id'] = $post['admin_menu_id'];
+            }
+            if (isset($post['admin_id']) and $post['admin_id'] > 0) {
+                $where['admin_id'] = $post['admin_id'];
+            }
+            if (isset($post['create_time']) and !empty($post['create_time'])) {
+                $timerang = explode(' - ', $post['create_time']);
+                $min_time = strtotime($timerang[0]);
+                $max_time = strtotime($timerang[1]);
+                $where['create_time'] = [['>=', $min_time], ['<=', $max_time]];
+            }
+
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order('id desc')->select();
+
+            $admin_cates = Db::name('admin_cate')->column('name', 'id');
+            foreach ($data as $k => $v) {
+                $v['title'] = !empty($v->menu) ? $v->menu->name : 'no route';
+                $v['params'] = htmlspecialchars($v->params);
+                $v['person'] = $v->admin->nickname;
+                if (!empty($v->admin_id)) {
+                    $v['person'] .= '<' . $admin_cates[$v->admin->admin_cate_id] . '>';
+                }
+                $data[$k] = $v;
+            }
+
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            //菜单
+            $model = new AdminMenu();
+            $menu = $model->order('orders asc')->select();
+            $menus = $model->menulist($menu);
+            $this->assign('menus', $menus);
+            //管理员
+            $this->assign('adminer', Db::name('admin')->select());
+            return $this->fetch();
+        }
+    }
+
+    //清空log
+    public function clearLog()
+    {
+        $prefix = \think\Env::get("db_prefix", "tplay_");
+        Db::execute("TRUNCATE {$prefix}admin_log");
+        //
+        $result = Hook::exec('app\\common\\behavior\\AdminLogBehavior', 'run', $params);
+        $this->success("执行成功");
+    }
+}

+ 91 - 0
app/admin/controller/ApiGeneration.php

@@ -0,0 +1,91 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2023/02/05
+ * Time: 20:33
+ */
+
+namespace app\admin\controller;
+
+use file\FileHelper;
+
+/**
+ * 演示代码生成,只生成接口
+ * Class ApiGeneration
+ * @package app\admin\controller
+ */
+class ApiGeneration extends CodeGeneration
+{
+
+    public function generation()
+    {
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['table', 'require|max:50', '请选择数据库表'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        }
+
+        $nameAndComment = explode('|', $post['table']);
+        $tableName = $nameAndComment[0]??'';
+        $menuName = $nameAndComment[1] ?: $tableName;
+        $humpName = $this->getHumpName($tableName);
+        $underLineName = $this->getUnderLineName($tableName);
+        $fieldsInfo = $this->getFieldsInfoByTableName($tableName);
+
+        $tpData = [
+            'fieldsInfo' => $fieldsInfo,
+            'humpName' => $humpName,
+            'underLineName' => $underLineName,
+            'menuName' => $menuName,
+            'tableName' => $tableName,
+            'crud' => $post['crud']??[],
+        ];
+
+
+        //模板文件目录
+        $tpdir = APP_PATH . 'admin' . DS . 'view' . DS . 'code_generation' . DS . 'tpl' . DS;
+        //生成文件路径
+        $ControllerPath = APP_PATH . 'api' . DS . 'controller' . DS . $humpName . '.php';
+        $ModelPath = APP_PATH . 'common' . DS . 'model' . DS . $humpName . '.php';
+        $ApiDocPath = APP_PATH . 'api' . DS . 'controller' . DS . $humpName . '.md';
+
+        if (!$this->request->has('cover')) {
+            //检查文件是否已存在
+            $checkMsg = "";
+            if (file_exists($ControllerPath)) {
+                $checkMsg .= str_replace(APP_PATH, '', $ControllerPath) . " 已存在" . '</br>';
+            }
+            if (file_exists($ModelPath)) {
+                $checkMsg .= str_replace(APP_PATH, '', $ModelPath) . " 已存在" . '</br>';
+            }
+            if (file_exists($ApiDocPath)) {
+                $checkMsg .= str_replace(APP_PATH, '', $ApiDocPath) . " 已存在" . '</br>';
+            }
+            if (!empty($checkMsg)) {
+                $checkMsg .= "<span style='color:red;'>确认生成并覆盖?</span>";
+                $this->error($checkMsg);
+            }
+        }
+
+
+        //生成controller
+        $content = $this->fetch($tpdir . 'ApiController.php.tp', $tpData);
+        FileHelper::save($ControllerPath, "<?php" . PHP_EOL . $content);
+
+        //生成model
+        $content = $this->fetch($tpdir . 'Model.php.tp', $tpData);
+        FileHelper::save($ModelPath, "<?php" . PHP_EOL . $content);
+
+        //生成api doc
+        $content = $this->fetch($tpdir . 'ApiDoc.md.tp', $tpData);
+        FileHelper::save($ApiDocPath, $content);
+
+        $this->success("success");
+    }
+
+}

+ 327 - 0
app/admin/controller/Article.php

@@ -0,0 +1,327 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2022/11/25
+ * Time: 15:10
+ */
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\Article as articleModel;
+use app\common\model\ArticleCate as articleCateModel;
+use app\common\model\ArticleCate as cateModel;
+use app\common\model\Attachment as attachmentModel;
+use app\common\model\Catalog as cataLogModel;
+use app\common\model\Templet as templetModel;
+use app\common\service\CmsService;
+use file\FileHelper;
+use think\Db;
+use think\Exception;
+use think\Log;
+use think\Session;
+
+class Article extends Permissions
+{
+    public function index()
+    {
+        $cateModel = new cateModel();
+        $catalogModel = new cataLogModel();
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [];
+            if (isset($post['id']) and !empty($post['id'])) {
+                $where['id'] = $post['id'];
+            }
+            if (isset($post['keywords']) and !empty($post['keywords'])) {
+                $where['title'] = ['like', '%' . $post['keywords'] . '%'];
+            }
+            if (isset($post['article_cate_id']) and $post['article_cate_id'] != 0) {
+                if ($post['article_cate_id'] == -1) {
+                    $where['article_cate_id'] = 0;
+                } else {
+                    $where['article_cate_id'] = ['in', $cateModel->getChildsIdByPid($post['article_cate_id'])];
+                }
+            }
+            if (isset($post['catalog_id']) and $post['catalog_id'] > 0) {
+                $where['catalog_id'] = $post['catalog_id'];
+            }
+            if (isset($post['admin_id']) and $post['admin_id'] > 0) {
+                $where['admin_id'] = $post['admin_id'];
+            }
+            if (isset($post['status']) and ($post['status'] == 1 or $post['status'] === '0')) {
+                $where['status'] = $post['status'];
+            }
+            if (isset($post['istop_time']) and ($post['istop_time'] == 1 or $post['istop_time'] === '0')) {
+                $where['istop_time'] = $post['istop_time'] == 1 ? ['<>', 0] : 0;
+            }
+            if (isset($post['create_time']) and !empty($post['create_time'])) {
+                $timerang = explode(' - ', $post['create_time']);
+                $min_time = strtotime($timerang[0]);
+                $max_time = strtotime($timerang[1]);
+                $where['create_time'] = [['>=', $min_time], ['<=', $max_time]];
+            }
+
+            $model = new articleModel();
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order('create_time desc')->select();
+            $cateIdAndName = $cateModel->column('title', 'id');
+            $cateLogIdAndName = $catalogModel->column('title', 'id');
+            $adminModel = new \app\admin\model\Admin();
+            $adminIdAndName = $adminModel->column('nickname', 'id');
+
+            foreach ($data as $key => $value) {
+                $value['editor_name'] = $adminIdAndName[$value['edit_admin_id']]??"";
+                $value['admin_name'] = $adminIdAndName[$value['admin_id']]??"";
+                $value['article_cate'] = $cateIdAndName[$value['article_cate_id']]??"";
+                $value['catalog'] = $cateLogIdAndName[$value['catalog_id']]??"";
+                $value['thumb_url'] = geturl($value['thumb']);
+                $data[$key] = $value;
+            }
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            //创建人
+            $this->assign('admins', Db::name('admin')->select());
+            //分类
+            $this->assign('cates', $cateModel->treelist2());
+            //栏目
+            $this->assign('catalogs', $catalogModel->treelistForArticle());
+            return $this->fetch();
+        }
+    }
+
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = new articleModel();
+        $cateModel = new cateModel();
+        $catalogModel = new cataLogModel();
+        $web_config = Db::name('webconfig')->where('id', 1)->find();
+        $post = $this->request->post();
+
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['title', 'require|max:50', '标题不能为空'],
+//                ['article_cate_id', 'require', '请选择分类'],
+//                ['content', 'require', '文章内容不能为空'],
+                ['seo_title|SEO标题', 'max:50'],
+                ['seo_keyword|SEO关键词', 'max:255'],
+                ['seo_description|SEO描述', 'max:255'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+            if (!empty($post['article_cate_id']) && !empty($post['catalog_id'])) {
+                $this->error('不能同时发布到,栏目和分类');
+            }
+            $post['uploadip'] = $this->request->ip();
+        } else {
+            $this->assign('web_config', $web_config);
+            //分类
+            $this->assign('cates', $cateModel->treelist2());
+            //栏目
+            $this->assign('catalogs', $catalogModel->treelistForArticle());
+        }
+
+        if ($id > 0) {
+            $article = $model->where('id', $id)->find();
+            if (empty($article)) {
+                $this->error('id不正确');
+            }
+            if ($this->request->isPost()) {
+                $post['edit_admin_id'] = Session::get(self::ADMIN_ID);
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改成功');
+                }
+            } else {
+                $this->assign('article', $article);
+                $this->assign('thumb', $article->thumb);
+                //切换编辑器
+                $article_editor = $article->article_editor ?: $web_config['article_editor'];
+                $this->switchEditor($article_editor, $id);
+                return $this->fetch();
+            }
+        } else {
+            //新增
+            if ($this->request->isPost()) {
+                $post['admin_id'] = Session::get(self::ADMIN_ID);
+                $post['edit_admin_id'] = $post['admin_id'];
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加失败');
+                } else {
+                    $this->success('添加成功', 'index');
+                }
+            } else {
+                //随机获取封面图
+                $attachment = new attachmentModel();
+                $imgid = $attachment->where(['use' => ['like', '%article%'], 'fileext' => ['in', ['jpg', 'png', 'gif']], 'status' => attachmentModel::STATUS_OPEN])->orderRaw('rand()')->limit(1)->value('id');
+                $this->assign('thumb', $imgid);
+                //切换编辑器
+                $article_editor = $web_config['article_editor'];
+                $this->switchEditor($article_editor, 0);
+                return $this->fetch();
+            }
+        }
+    }
+
+    private function switchEditor($article_editor, $id = 0)
+    {
+        if ($this->request->has('switchEditor')) {
+            $switchEditor = $this->request->param("switchEditor");
+            $editors = ["wangEditor", "tinymce", "ueditor", "markdown"];
+            $index = array_search($switchEditor, $editors);//获取索引
+            $next = ($index == count($editors) - 1) ? 0 : $index + 1;
+            $article_editor = $editors[$next];
+        }
+        $this->assign('article_editor', $article_editor);
+        $this->assign('switch_editor_url', "/admin/article/publish?switchEditor={$article_editor}&id={$id}");
+    }
+
+    private function deleteFile($id)
+    {
+        try {
+            //先删除html文件
+            $article = articleModel::get($id);
+            if ($article->catalog_id) {
+                $catalog = $article->catalog;
+                $filepath = $article->getRulePath($catalog->getRealPath(), $catalog->article_rule);
+                if (file_exists($filepath)) {
+                    unlink($filepath);//删除
+                }
+            } else {
+                $catalogs = $article->catalogs();
+                foreach ($catalogs as $catalog) {
+                    $filepath = $article->getRulePath($catalog->getRealPath(), $catalog->article_rule);
+                    if (file_exists($filepath)) {
+                        unlink($filepath);//删除
+                    }
+                }
+            }
+        } catch (Exception $e) {
+        }
+    }
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            $this->deleteFile($id);
+            if (false == Db::name('article')->where('id', $id)->delete()) {
+                $this->error('删除失败');
+            } else {
+                $this->success('删除成功', 'index');
+            }
+        }
+    }
+
+    public function deletes()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+            foreach ($ids as $id) {
+                $this->deleteFile($id);
+            }
+            $model = new articleModel();
+            if ($model->where('id', 'in', $ids)->delete()) {
+                Log::log("批量删除文章:" . implode(',', $ids));
+                $this->success('删除成功');
+            }
+        }
+    }
+
+    public function istop_time()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            $status = $post['status'] == 1 ? time() : 0;
+            if (false == Db::name('article')->where('id', $post['id'])->update(['istop_time' => $status])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+
+    public function status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            if (false == Db::name('article')->where('id', $post['id'])->update(['status' => $post['status']])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+
+    /**
+     * 预览
+     * @return mixed
+     */
+    public function perview()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        if ($id) {
+            $article = articleModel::get($id);
+            return (new CmsService())->perviewArticle($article);
+        }
+        $this->error('预览失败:id is null');
+    }
+
+    /**
+     * 重新生成html
+     */
+    public function createFile()
+    {
+        $post = $this->request->param();
+        $ids = $post['ids'];
+        if ($ids) {
+            $articles = (new articleModel())->where('id', 'in', $ids)->select();
+            if (empty($articles)) {
+                $this->error('articles is null');
+            }
+            $service = new CmsService();
+            foreach ($articles as $article) {
+                try {
+                    //全局对象
+                    $this->assign([
+                        'cataLogModel' => new cataLogModel(),
+                        'articleCateModel' => new articleCateModel(),
+                        'articleModel' => new articleModel()
+                    ]);
+                    /** @var articleModel $article */
+                    /** @var cataLogModel $catalog */
+                    if ($article->catalog_id) {
+                        $catalog = $article->catalog;
+                        $filepath = $article->getRulePath($catalog->getRealPath(), $catalog->article_rule);
+                        $content = $this->fetch($catalog->getArticleTemplet()->getRealPath(), ['catalog' => $catalog, 'article' => $article, 'articleCate' => null]);
+                        if (ifContain($filepath, templetModel::getRootDir())) {
+                            $service->info("           ├─ 文章:" . $filepath);
+                            FileHelper::save($filepath, $content);
+                        }
+                    } else {
+                        $catalogs = $article->catalogs();
+                        $cate = $article->cate;
+                        foreach ($catalogs as $catalog) {
+                            $filepath = $article->getRulePath($catalog->getRealPath(), $catalog->article_rule);
+                            $content = $this->fetch($catalog->getArticleTemplet()->getRealPath(), ['catalog' => $catalog, 'article' => $article, 'articleCate' => $cate]);
+                            if (ifContain($filepath, templetModel::getRootDir())) {
+                                $service->info("           ├─ 文章:" . $filepath);
+                                FileHelper::save($filepath, $content);
+                            }
+                        }
+                    }
+                } catch (\Exception $e) {
+                    $this->error('生成失败:' . $e->getMessage());
+                }
+            }
+            $this->success('执行成功');
+        }
+        $this->error('ids is null');
+    }
+}

+ 241 - 0
app/admin/controller/Articlecate.php

@@ -0,0 +1,241 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2022/11/25
+ * Time: 15:10
+ */
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\ArticleCate as cateModel;
+use app\common\model\Catalog as cataLogModel;
+use app\common\service\CmsService;
+use think\Db;
+
+class Articlecate extends Permissions
+{
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $data = (new cateModel())->order('sort desc')->select();
+            foreach ($data as $k => $v) {
+                /**@var cateModel $v */
+                $v['catalog'] = $v->getCatalogValues();
+                $v['article_count'] = $v->article_count ?: '';
+                try {
+                    $v['uri'] = $v->getUri();
+                } catch (\Exception $e) {
+                    $v['uri'] = '';
+                }
+                $data[$k] = $v;
+            }
+            return $data;
+        } else {
+            return $this->fetch();
+        }
+    }
+
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = new cateModel();
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['title|分类名称', 'require'],
+                ['pid', 'require', '请选择上级分类'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        } else {
+            //栏目
+            $cataLogModel = new cataLogModel();
+            $catalogs = $cataLogModel->where('type', cataLogModel::TYPE_ARTICLE_LIST)->select();
+            $this->assign('catalogs', $catalogs);
+            //分类
+            $catelist = $model->order('sort desc')->select();
+            $cates = $model->treelist($catelist);
+            $this->assign('cates', $cates);
+        }
+
+        if ($id > 0) {
+            //修改
+            $cate = $model->where('id', $id)->find();
+            if (empty($cate)) {
+                $this->error('id不正确');
+            }
+            if ($this->request->isPost()) {
+                if ($id == $post['pid']) {
+                    $this->error('上级节点不能选自己');
+                }
+                //保存中间表
+                $res1 = $this->updateCatalog($post, $cate);
+                //更新tree_path
+                $post = $this->updatePath($post, $id);
+                if (!$res1 && false == $cate->allowField(true)->save($post)) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改成功', 'index');
+                }
+            } else {
+                $this->assign('cate', $cate);
+                return $this->fetch();
+            }
+        } else {
+            //新增
+            if ($this->request->isPost()) {
+                $post['status'] = $model::STATUS_OPEN;
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加失败');
+                } else {
+                    $cate = $model;
+                    //更新tree_path
+                    $post = $this->updatePath($post, $model->id);
+                    if (false == $cate->save(['tree_path' => $post['tree_path']])) {
+                        $this->error('更新tree_path失败');
+                    }
+                    //保存中间表
+                    $this->updateCatalog($post, $cate);
+                    $this->success('添加成功', 'index');
+                }
+            } else {
+                $pid = $this->request->param('pid', null, 'intval');
+                if (!empty($pid)) {
+                    $this->assign('pid', $pid);
+                }
+                return $this->fetch();
+            }
+        }
+    }
+
+    /**
+     * 保存中间表
+     * @param $post
+     * @param $cate cateModel
+     * @return bool [是否有修改]
+     */
+    private function updateCatalog($post, $cate)
+    {
+        if (isset($post['catalog_ids'])) {
+            $catalog_ids = explode(',', $post['catalog_ids']);
+            $old_catalog_ids = $cate->getCatalogValues('id');
+            if ($old_catalog_ids == $post['catalog_ids']) {
+                return false;
+            }
+            foreach ($cate->catalogs as $catalog) {
+                if (!in_array($catalog->id, $catalog_ids)) {
+                    $cate->catalogs()->detach($catalog->id);// 删除中间表数据
+                }
+            }
+            if (false == $cate->catalogs()->attach($catalog_ids)) {//插入数据
+                $this->error('保存中间表失败');
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 更新tree_path
+     * @param $post
+     * @param $id
+     * @return mixed
+     */
+    private function updatePath($post, $id)
+    {
+        if ($post['pid']) {
+            $pdoc = (new cateModel())->find($post['pid']);
+            if (!$pdoc) {
+                $this->error('该上级节点不存在,请重新选择');
+            }
+            $post['tree_path'] = $pdoc->tree_path . '-' . $id;
+        } else {
+            $post['tree_path'] = $id;
+        }
+        return $post;
+    }
+
+    /**
+     * 删除
+     */
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->has('id') ? $this->request->param('id', 0, 'intval') : 0;
+            if (Db::name('article_cate')->where('pid', $id)->count() == 0) {
+                if (false == Db::name('article_cate')->where('id', $id)->delete()) {
+                    $this->error('删除失败');
+                } else {
+                    $this->success('删除成功', 'index');
+                }
+            } else {
+                $this->error('请先删除子节点');
+            }
+        }
+    }
+
+    /**
+     * 审核
+     */
+    public function status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            if (false == Db::name('article_cate')->where('id', $post['id'])->update(['status' => $post['status']])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+
+    /**
+     * 排序
+     */
+    public function sort()
+    {
+        if ($this->request->isPost() && $this->request->has('ids')) {
+            $post = $this->request->post();
+            $i = 0;
+            foreach ($post['ids'] as $k => $id) {
+                $sort = Db::name('article_cate')->where('id', $id)->value('sort');
+                $newsort = $post['sorts'][$k]??$sort;
+                if ($sort != $newsort) {
+                    if (false == Db::name('article_cate')->where('id', $id)->update(['sort' => $newsort])) {
+                        $this->error('更新失败');
+                    } else {
+                        $i++;
+                    }
+                }
+            }
+            $this->success('成功更新' . $i . '个数据', 'index');
+        } else {
+            $this->error('无数据更新', 'index');
+        }
+    }
+
+    /**
+     * 更新所有节点tree_path
+     * admin/Articlecate/updateTreePath
+     */
+    public function updateTreePath()
+    {
+        (new cateModel())->updateTreePath();
+        return $this->success();
+    }
+
+    //预览
+    public function perview()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        if ($id) {
+            $articleCate = (new cateModel())->where('id', $id)->find();
+            return (new CmsService())->perviewArticleCate($articleCate);
+        }
+        $this->error('预览失败:id is null');
+    }
+}

+ 301 - 0
app/admin/controller/Attachment.php

@@ -0,0 +1,301 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\behavior\AdminLogBehavior;
+use app\common\model\Attachment as model;
+use file\FileHelper;
+use file\PathHelper;
+use think\Db;
+use think\Log;
+use think\Session;
+use time\Timestamp;
+
+class Attachment extends Permissions
+{
+    public function index()
+    {
+        $model = new model();
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [];
+            if (isset($post['filename']) and !empty($post['filename'])) {
+                $where['filename'] = ['like', '%' . $post['filename'] . '%'];
+            }
+            if (isset($post['filepath']) and !empty($post['filepath'])) {
+                $where['filepath'] = ['like', '%' . $post['filepath'] . '%'];
+            }
+            if (isset($post['status']) and ($post['status'] == 1 or $post['status'] === '0' or $post['status'] == -1)) {
+                $where['status'] = $post['status'];
+            }
+            if (isset($post['type'])) {
+                if ($post['type'] == 1) {
+                    $where['filepath'] = ['like', '%http%'];
+                }
+                if ($post['type'] == -1) {
+                    $where['filepath'] = ['not like', '%http%'];
+                }
+            }
+            if (isset($post['create_time']) and !empty($post['create_time'])) {
+                $min_time = strtotime($post['create_time']);
+                $max_time = $min_time + 24 * 60 * 60;
+                $where['create_time'] = [['>=', $min_time], ['<=', $max_time]];
+            }
+
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order('create_time desc')->select();
+
+            foreach ($data as $k => $v) {
+                $v['thumb_url'] = $v['filepath'];
+                $v['status_text'] = $v->status_text;
+                $data[$k] = $v;
+            }
+
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            return $this->fetch();
+        }
+    }
+
+    /**
+     * 选择图片
+     * @return mixed
+     */
+    public function selectImage()
+    {
+        $model = new model();
+        $where = [
+            'fileext' => ['in', ['jpg', 'png', 'gif', 'jpeg']],
+            'status' => model::STATUS_OPEN
+        ];
+        $attachment = $model->where($where)->order('create_time desc')->paginate(24);
+        $this->assign('attachment', $attachment);
+        $this->assign('showUpload', in_array(43, $this->getPermission()));
+        return $this->fetch();
+    }
+
+    /**
+     * 审核
+     */
+    public function status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            $admin_id = \think\Session::get(self::ADMIN_ID);
+            if (false == Db::name('attachment')->where('id', $post['id'])->update(['status' => $post['status'], 'admin_id' => $admin_id, 'audit_time' => time()])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+
+    /**
+     * 删除本地文件
+     */
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            $attachment = Db::name('attachment')->where('id', $id)->value('filepath');
+            if (file_exists(ROOT_PATH . 'public' . $attachment)) {
+                //删除本地文件
+                if (unlink(ROOT_PATH . 'public' . $attachment)) {
+                    if (false == Db::name('attachment')->where('id', $id)->delete()) {
+                        $this->error('删除失败');
+                    } else {
+                        $this->success('删除成功', 'admin/attachment/index');
+                    }
+                } else {
+                    $this->error('删除失败');
+                }
+            } else {
+                if (false == Db::name('attachment')->where('id', $id)->delete()) {
+                    $this->error('删除失败');
+                } else {
+                    $this->success('删除成功', 'admin/attachment/index');
+                }
+            }
+        }
+    }
+
+    /**
+     * 批量删除本地文件
+     */
+    public function deletes()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+            $attachments = (new model())->where('id', 'in', $ids)->select();
+            foreach ($attachments as $attachment) {
+                if (file_exists(ROOT_PATH . 'public' . $attachment->filepath)) {
+                    //删除本地文件
+                    if (unlink(ROOT_PATH . 'public' . $attachment->filepath)) {
+                        if (false == Db::name('attachment')->where('id', $attachment->id)->delete()) {
+                            $this->error('删除失败');
+                        }
+                    } else {
+                        $this->error('删除失败');
+                    }
+                } else {
+                    if (false == Db::name('attachment')->where('id', $attachment->id)->delete()) {
+                        $this->error('删除失败');
+                    }
+                }
+            }
+            Log::log("批量删除附件:" . implode(',', $ids));
+            $this->success('删除成功');
+        }
+    }
+
+
+    /**
+     * 下载
+     * @return \think\response\Json
+     */
+    public function download()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            if ($id > 0) {
+                //获取下载链接
+                $data = Db::name('attachment')->where('id', $id)->find();
+                $res['data'] = $data['filepath'];
+                $res['name'] = $data['filename'];
+                //增加下载量
+                Db::name('attachment')->where('id', $id)->setInc('download', 1);
+                $res['code'] = 1;
+                return json($res);
+            } else {
+                $this->error('错误请求');
+            }
+        }
+    }
+
+    /**
+     * 网络文件本地化
+     */
+    public function imgPersistence()
+    {
+        $attachments = (new model())->where(['filepath' => ['like', '%http%']])->order('id desc')->select();
+        if (count($attachments) > 50) {
+            $this->error('网络文件过多,请使用命令行操作');
+        }
+        $savePath = DS . 'uploads' . DS . 'admin' . DS . 'attachment' . DS . Timestamp::dayStart(time()) . DS;
+        foreach ($attachments as $attachment) {
+            $saveName = PathHelper::getFilename($attachment->filepath);
+            FileHelper::fetchDownFile($attachment->filepath, ROOT_PATH . 'public' . $savePath, $saveName);
+            $attachment->save(['filepath' => $savePath . $saveName]);
+        }
+        $msg = '执行完成,本地化' . count($attachments) . '个文件';
+        if ($this->request->isCli()) {
+            echo $msg . PHP_EOL;
+        } else {
+            $this->success($msg);
+        }
+    }
+
+    /**
+     * 添加网络图片
+     */
+    public function create()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->post();
+            $validate = new \think\Validate([
+                ['filepath', 'require|max:200'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+            $module = $this->request->param('module', 'admin');
+            $use = $this->request->param('use', 'attachment');
+            if (ifContain($use, '/')) {
+                $use = deleteStartDS(replaceDS($use));
+            }
+            if (ifContain($use, '.')) {
+                $use = explode('.', $use)[0];
+            }
+            $filepath = $post['filepath'];
+            $pathinfo = pathinfo($filepath);
+            $data = [
+                'module' => $module,//模块
+                'use' => $use,//来源
+                'filename' => $pathinfo['basename'],//文件名
+                'filepath' => $filepath,//文件路径
+                'fileext' => $pathinfo['extension']??'',//文件后缀
+                'filesize' => 0,//文件大小
+                'create_time' => time(),//时间
+                'uploadip' => $this->request->ip(),//IP
+                'user_id' => Session::has(self::ADMIN_ID) ? Session::get(self::ADMIN_ID) : 0,
+                'status' => model::STATUS_OPEN,
+                'admin_id' => 0,
+                'audit_time' => 0,
+            ];
+            $res['id'] = Db::name('attachment')->insertGetId($data);
+            $res['src'] = $filepath;
+            $this->success('完成', '', $res);
+        }
+    }
+
+    /**
+     * 上传附件
+     * @return \think\response\Json
+     */
+    public function upload()
+    {
+        $module = $this->request->param('module', 'admin');
+        $use = $this->request->param('use', 'attachment');
+        if (ifContain($use, '/')) {
+            $use = deleteStartDS(replaceDS($use));
+        }
+        if (ifContain($use, '.')) {
+            $use = explode('.', $use)[0];
+        }
+
+        if ($this->request->file('file')) {
+            $file = $this->request->file('file');
+        } else {
+            $res['code'] = 1;
+            $res['msg'] = '没有上传文件';
+            return json($res);
+        }
+        $web_config = Db::name('webconfig')->where('id', 1)->find();
+        $info = $file->validate(['size' => $web_config['file_size'] * 1024, 'ext' => $web_config['file_type']])->rule('date')->move(ROOT_PATH . 'public' . DS . 'uploads' . DS . $module . DS . $use);
+        if ($info) {
+            //写入到附件表
+            $data = [];
+            $data['module'] = $module;//模块
+            $data['use'] = $use;//来源
+            $data['filename'] = $info->getFilename();
+            $data['filepath'] = DS . 'uploads' . DS . $module . DS . $use . DS . $info->getSaveName();
+            $data['fileext'] = $info->getExtension();
+            $data['filesize'] = $info->getSize();
+            $data['create_time'] = time();
+            $data['uploadip'] = $this->request->ip();
+            $data['user_id'] = Session::has(self::ADMIN_ID) ? Session::get(self::ADMIN_ID) : 0;
+            if ($data['module'] = 'admin') {
+                //通过后台上传的文件直接审核通过
+                $data['status'] = model::STATUS_OPEN;
+                $data['admin_id'] = $data['user_id'];
+                $data['audit_time'] = time();
+            }
+            $res['id'] = Db::name('attachment')->insertGetId($data);
+            $res['src'] = replaceUrlDS($data['filepath']);
+            (new AdminLogBehavior())->updateLastLog('上传附件:' . $data['filepath']);
+            $this->success('上传完成', 'admin/attachment/index', $res);
+        } else {
+            $this->error('上传失败:' . $file->getError());
+        }
+    }
+
+}

+ 402 - 0
app/admin/controller/Catalog.php

@@ -0,0 +1,402 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\Article as articleModel;
+use app\common\model\ArticleCate as articleCateModel;
+use app\common\model\Catalog as cataLogModel;
+use app\common\model\Templet as templetModel;
+use app\common\service\CmsService;
+use file\PathHelper;
+use paginate\Bootstrap;
+use think\Db;
+
+class Catalog extends Permissions
+{
+    /**
+     * index2 使用新的树形表格组件
+     * @return array|mixed
+     */
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $model = new cataLogModel();
+            $data = $model->order('pid,sort desc,id asc')->select();
+            foreach ($data as $k => $v) {
+                /**@var cataLogModel $v */
+                $v['type_text'] = $v->type_text;
+                $v['article_count'] = $v->article_count ?: '';
+                $v['tpath'] = $v->tpath();
+                $data[$k] = $v;
+            }
+            return array('code' => 0, 'count' => count($data), 'data' => $data, "tip" => "操作成功!");
+        } else {
+            return $this->fetch();
+        }
+    }
+
+
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $post = $this->request->post();
+        $model = new cataLogModel();
+
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['title|栏目名称', 'require'],
+                ['catalog_templet|栏目模板', 'requireIf:type,0|requireIf:type,1'],
+                ['article_templet|文章模板', 'requireIf:type,1'],
+                ['path|栏目路径', 'requireIf:type,0|requireIf:type,1'],
+                ['article_rule|文章路径', 'requireIf:type,1'],
+                ['articlelist_rule|列表页路径', 'requireIf:type,1'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+            $post['path'] = trim($post['path']);
+            $post['path'] = empty($post['path']) ? "" : appendStartDS(trim($post['path']), '/');
+            $post['catalog_templet'] = trim($post['catalog_templet']);
+            $post['article_templet'] = trim($post['article_templet']);
+            $post['article_rule'] = trim($post['article_rule']);
+            $post['articlelist_rule'] = trim($post['articlelist_rule']);
+            $post['articlelist_rows'] = empty($post['articlelist_rows']) ? 10 : $post['articlelist_rows'];
+        } else {
+            $pid = $this->request->param('pid', 0, 'intval');
+            $this->assign('pid', $pid);
+            $this->assign("templets", templetModel::getTemplets());
+            $this->assign('cates', $model->treelistForCatalog());
+            $this->assign('types', cataLogModel::TYPES);
+        }
+
+        if ($id > 0) {
+            //修改
+            $item = $model->where('id', $id)->find();
+            if (empty($item)) {
+                $this->error('id不存在');
+            }
+            if ($this->request->isPost()) {
+                if ($id == $post['pid']) {
+                    $this->error('上级节点不能选自己');
+                }
+                //更新tree_path
+                $post = $this->updatePath($post, $id);
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改成功', 'index');
+                }
+            } else {
+                $this->assign('item', $item);
+                return $this->fetch();
+            }
+        } else {
+            //新增
+            if ($this->request->isPost()) {
+                $post['status'] = $model::STATUS_OPEN;
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加失败');
+                } else {
+                    $cate = $model;
+                    //更新tree_path
+                    $post = $this->updatePath($post, $model->id);
+                    if (false == $cate->save(['tree_path' => $post['tree_path']])) {
+                        $this->error('更新tree_path失败');
+                    }
+                    $this->success('添加成功', 'index');
+                }
+            } else {
+                return $this->fetch();
+            }
+        }
+    }
+
+    /**
+     * 更新tree_path
+     * @param $post
+     * @param $id
+     * @return mixed
+     */
+    private function updatePath($post, $id)
+    {
+        if ($post['pid']) {
+            $pdoc = (new cataLogModel())->find($post['pid']);
+            if (!$pdoc) {
+                $this->error('该上级节点不存在,请重新选择');
+            }
+            $post['tree_path'] = $pdoc->tree_path . '-' . $id;
+        } else {
+            $post['tree_path'] = $id;
+        }
+        return $post;
+    }
+
+    /**
+     * 删除
+     */
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            $exits = cataLogModel::get(['pid' => $id]);
+            if ($exits) {
+                $this->error('请先删除子节点');
+            }
+            if (false == Db::name('catalog')->where('id', $id)->delete()) {
+                $this->error('删除失败');
+            } else {
+                $this->success('删除成功', 'index');
+            }
+        }
+    }
+
+    /**
+     * 审核
+     */
+    public function status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            if (false == Db::name('catalog')->where('id', $post['id'])->update(['status' => $post['status']])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+
+    /**
+     * 排序
+     */
+    public function sort()
+    {
+        if ($this->request->isPost() && $this->request->has('ids')) {
+            $post = $this->request->post();
+            $i = 0;
+            foreach ($post['ids'] as $k => $id) {
+                $sort = Db::name('catalog')->where('id', $id)->value('sort');
+                $newsort = $post['sorts'][$k]??$sort;
+                if ($sort != $newsort) {
+                    if (false == Db::name('catalog')->where('id', $id)->update(['sort' => $newsort])) {
+                        $this->error('更新失败');
+                    } else {
+                        $i++;
+                    }
+                }
+            }
+            if (empty($i)) {
+                $this->error('没有更新排序', 'index');
+            }
+            $this->success('成功更新' . $i . '个排序', 'index');
+        } else {
+            $this->error('无数据更新', 'index');
+        }
+    }
+
+    /**
+     * 更新所有节点tree_path
+     * admin/catalog/updateTreePath
+     */
+    public function updateTreePath()
+    {
+        (new cataLogModel())->updateTreePath();
+        return $this->success();
+    }
+
+    //预览
+    public function perview()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        if ($id) {
+            /** @var cataLogModel $catalog */
+            $catalog = (new cataLogModel())->where('id', $id)->find();
+            try {
+                $cate = $catalog->getFirstArticleCate();
+            } catch (\Exception $e) {
+                $cate = null;
+            }
+            return (new CmsService())->perviewCatalog($catalog, $cate);
+        }
+        $this->error('预览失败:id is null');
+    }
+
+
+    /**
+     * @param $where array
+     * @param $catalog cataLogModel
+     * @param $tid int
+     * @param $cate articleCateModel
+     */
+    private function exportArticle($where, $catalog, $tid, $cate)
+    {
+        $replace = CmsService::getArticleReplace();
+        $service = new CmsService();
+        $articleModel = new articleModel();
+        $catalogPath = $catalog->getRealPath();
+        $total = $articleModel->where($where)->where($catalog->articlelist_where)->count();
+        $listRows = $catalog->articlelist_rows??10;//分页
+        $orderby = $catalog->articlelist_sort??'create_time desc';//排序
+
+        if ($total) {
+            $lastPage = (int)ceil($total / $listRows);
+            //分页
+            for ($page = 1; $page <= $lastPage; $page++) {
+
+                $articleCatePath = articleCateModel::getRulePath($catalogPath, $catalog->articlelist_rule, $catalog->id, $tid, $page);
+                $articleList = $articleModel->where($where)->where($catalog->articlelist_where)->page($page, $listRows)->order($orderby)->select();
+                $prefix = isset($replace['_ROOT_']) ? $replace['_ROOT_'] : '_ROOT_';
+                $paginate = new Bootstrap($articleList, $listRows, $page, $total, false, ["path" => $prefix . CmsService::getPaginatePath($catalog, $tid)]);
+
+                $service->info("       ├─ 列表页:" . $articleCatePath);
+                $content = $this->fetch($catalog->getCatalogTemplet()->getRealPath(), ['catalog' => $catalog, 'articleCate' => $cate, 'paginate' => $paginate], $replace);
+                $service->createFile($articleCatePath, $content);
+
+                foreach ($articleList as $article) {
+                    /**@var articleModel $article */
+                    $articlePath = $article->getRulePath($catalogPath, $catalog->article_rule);
+                    $content = $this->fetch($catalog->getArticleTemplet()->getRealPath(), ['catalog' => $catalog, 'article' => $article, 'articleCate' => $cate], $replace);
+                    $service->info("           ├─ 文章:" . $articlePath);
+                    $service->createFile($articlePath, $content);
+                }
+            }
+        }
+
+        $articleCatePath = articleCateModel::getRulePath($catalogPath, $catalog->articlelist_rule, $catalog->id, $tid);
+        $content = file_exists($articleCatePath) ? $this->fetch($articleCatePath, [], $replace) : CmsService::errorHtml($articleCatePath);
+        $dir = PathHelper::getDir($articleCatePath);
+        $service->info("       ├─ 列表默认页:" . $dir . DS . "index.html");
+        $service->createFile($dir . DS . "index.html", $content);
+    }
+
+    /**
+     * 生成文章列表&文章页
+     * @param $catalog cataLogModel
+     * @param $cate
+     */
+    private function exportArticleList($catalog, $cate)
+    {
+        $where = [
+            'article_cate_id' => $cate->id,
+            'status' => articleModel::STATUS_OPEN,
+        ];
+        $this->exportArticle($where, $catalog, $cate->id, $cate);
+    }
+
+
+    /**
+     * 生成栏目文章页
+     * @param $catalog cataLogModel
+     */
+    private function exportCatalogArticle($catalog)
+    {
+        $where = [
+            'catalog_id' => $catalog->id,
+            'status' => articleModel::STATUS_OPEN,
+        ];
+        $this->exportArticle($where, $catalog, 0, null);
+    }
+
+
+    /**
+     * 生成静态文件
+     */
+    public function exportHtml()
+    {
+        if (!$this->request->isAjax() && !$this->request->isCli()) {
+            return;
+        }
+
+        if ($this->request->isCli()) {
+            $ids = (new cataLogModel())->column('id');
+        } else {
+            $post = $this->request->param();
+            if (!isset($post['ids']) || empty($post['ids'])) {
+                $this->error('请选择栏目');
+            }
+            $ids = $post['ids'];
+        }
+
+        clear_temp_cache();//预览模式和生成模式切换时会缓存
+
+        //全局对象
+        $cataLogModel = new cataLogModel();
+        $articleModel = new articleModel();
+        $articleCateModel = new articleCateModel();
+        $service = new CmsService();
+        $this->assign([
+            'cataLogModel' => $cataLogModel,
+            'articleCateModel' => $articleCateModel,
+            'articleModel' => $articleModel,
+        ]);
+
+        $catalogs = $cataLogModel->where(['id' => ['in', $ids], 'status' => cataLogModel::STATUS_OPEN])->order('sort asc')->select();
+        $replace = CmsService::getArticleReplace();
+
+        try {
+            /**@var cataLogModel $catalog */
+            foreach ($catalogs as $catalog) {
+
+                $service->info("栏目:" . $catalog->title);
+                if ($catalog->type == cataLogModel::TYPE_DIRECTORY) {
+                    $service->info("  ├─ 类型:目录");
+                    continue;
+                }
+
+                $catalogPath = $catalog->getRealPath();
+
+                if ($catalog->type == cataLogModel::TYPE_CATALOG) {
+                    $service->info("  ├─ 类型:栏目页:" . $catalogPath);
+                    $content = $this->fetch($catalog->getCatalogTemplet()->getRealPath(), ['catalog' => $catalog], $replace);
+                    $service->createFile($catalogPath, $content);
+                } else {
+                    $articleCates = $catalog->articleCates;
+                    if (count($articleCates) > 0) {
+                        $service->info("  ├─ 类型:文章列表+分类");
+                        foreach ($articleCates as $cate) {
+                            $service->info("    ├─ 分类:" . $cate->title);
+                            $this->exportArticleList($catalog, $cate);
+                        }
+                        $service->info("      ├─ 栏目默认页:" . $catalogPath);
+                        $articleCatePath = articleCateModel::getRulePath($catalogPath, $catalog->articlelist_rule, $catalog->id, $articleCates[0]->id);
+                        $content = file_exists($articleCatePath) ? $this->fetch($articleCatePath, [], $replace) : CmsService::errorHtml($articleCatePath);
+                        $service->createFile($catalogPath, $content);
+                    } else {
+                        $service->info("  ├─ 类型:文章列表+栏目");
+                        if ($catalog->article_count > 0) {
+                            $this->exportCatalogArticle($catalog);
+                        } else {
+                            $service->info("      ├─ 栏目默认页:" . $catalogPath);
+                            $content = $this->fetch($catalog->getCatalogTemplet()->getRealPath(), ['catalog' => $catalog], $replace);
+                            $service->createFile($catalogPath, $content);
+                        }
+                    }
+                }
+            }
+
+            if (!$this->request->has('delete')) {
+                $service->copyUploads();
+            }
+
+        } catch (\Exception $e) {
+            CmsService::errorlog($e);
+            if ($this->request->isCli()) {
+                return $service->info('执行失败 ' . $e->getMessage());
+            } else {
+                $this->error('执行失败 ' . $e->getMessage());
+            }
+        }
+
+        if ($this->request->isCli()) {
+            $service->info('执行成功');
+        } else {
+            $this->success('执行成功', 'index');
+        }
+    }
+}

+ 203 - 0
app/admin/controller/CodeGeneration.php

@@ -0,0 +1,203 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use file\FileHelper;
+use think\Db;
+
+class CodeGeneration extends Permissions
+{
+    /**
+     * @return mixed
+     */
+    public function index()
+    {
+        $dbName = \think\Env::get("db_name", "");
+        $prefix = \think\Env::get("db_prefix", "");
+        //排除核心表
+        $coreTables = ['admin', 'admin_cate', 'admin_log', 'admin_menu', 'urlconfig', 'webconfig', 'smsconfig', 'emailconfig', 'config', 'config_option', 'config_tab', 'catalog', 'cate_catalog'];
+        $filters = [];
+        foreach ($coreTables as $table) {
+            $filters[] = "'{$prefix}{$table}'";
+        }
+        $filter = implode(',', $filters);
+
+        $tables = Db::query("SELECT TABLE_NAME,TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME NOT IN ($filter)", [$dbName]);
+        $this->assign('tables', $tables);
+        return $this->fetch();
+    }
+
+    /**
+     * 获取字段信息
+     * @return array
+     */
+    public function getFieldsInfo()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $validate = new \think\Validate([
+                ['table', 'require|max:50', '请选择数据库表'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+            $nameAndComment = explode('|', $post['table']);
+            $tableName = $nameAndComment[0]??'';
+            $data = $this->getFieldsInfoByTableName($tableName);
+            return array('code' => 0, 'count' => count($data), 'data' => $data);
+        }
+    }
+
+    /**
+     * 生成
+     */
+    public function generation()
+    {
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['table', 'require|max:50', '请选择数据库表'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        }
+
+        $nameAndComment = explode('|', $post['table']);
+        $tableName = $nameAndComment[0]??'';
+        $menuName = $nameAndComment[1] ?: $tableName;
+        $humpName = $this->getHumpName($tableName);
+        $underLineName = $this->getUnderLineName($tableName);
+
+        $fieldsInfo = $this->getFieldsInfoByTableName($tableName);
+        foreach ($fieldsInfo as $k => $item) {
+            $field = $item['Field'];
+            $fieldsInfo[$k]["ShowList"] = $post[$field . "_ShowList"]??'';
+            $fieldsInfo[$k]["ShowSearch"] = $post[$field . "_ShowSearch"]??'';
+            $fieldsInfo[$k]["ShowEdit"] = $post[$field . "_ShowEdit"]??'';
+            $fieldsInfo[$k]["Component"] = $post[$field . "_Component"];
+        }
+
+        $tpData = [
+            'fieldsInfo' => $fieldsInfo,
+            'humpName' => $humpName,
+            'underLineName' => $underLineName,
+            'menuName' => $menuName,
+            'tableName' => $tableName,
+            'crud' => $post['crud']??[],
+        ];
+
+
+        //模板文件目录
+        $tpdir = APP_PATH . 'admin' . DS . 'view' . DS . 'code_generation' . DS . 'tpl' . DS;
+        //生成文件路径
+        $ControllerPath = APP_PATH . 'admin' . DS . 'controller' . DS . $humpName . '.php';
+        $ModelPath = APP_PATH . 'common' . DS . 'model' . DS . $humpName . '.php';
+        $IndexPath = APP_PATH . 'admin' . DS . 'view' . DS . $underLineName . DS . 'index.html';
+        $PublishPaht = APP_PATH . 'admin' . DS . 'view' . DS . $underLineName . DS . 'publish.html';
+
+        if (!$this->request->has('cover')) {
+            //检查文件是否已存在
+            $checkMsg = "";
+            if (file_exists($ControllerPath)) {
+                $checkMsg .= str_replace(APP_PATH, '', $ControllerPath) . " 已存在" . '</br>';
+            }
+            if (file_exists($ModelPath)) {
+                $checkMsg .= str_replace(APP_PATH, '', $ModelPath) . " 已存在" . '</br>';
+            }
+            if (file_exists($IndexPath)) {
+                $checkMsg .= str_replace(APP_PATH, '', $IndexPath) . " 已存在" . '</br>';
+            }
+            if (array_key_exists('update', $tpData['crud']) or array_key_exists('create', $tpData['crud'])) {
+                if (file_exists($PublishPaht)) {
+                    $checkMsg .= str_replace(APP_PATH, '', $PublishPaht) . " 已存在" . '</br>';
+                }
+            }
+            if (!empty($checkMsg)) {
+                $checkMsg .= "<span style='color:red;'>确认生成并覆盖?</span>";
+                $this->error($checkMsg);
+            }
+        }
+
+
+        //生成controller
+        $content = $this->fetch($tpdir . 'AdminController.php.tp', $tpData);
+        FileHelper::save($ControllerPath, "<?php" . PHP_EOL . $content);
+
+        //生成model
+        $content = $this->fetch($tpdir . 'Model.php.tp', $tpData);
+        FileHelper::save($ModelPath, "<?php" . PHP_EOL . $content);
+
+        //生成index.html
+        $content = $this->fetch($tpdir . 'index.html.tp', $tpData);
+        FileHelper::save($IndexPath, $content);
+
+        //生成publish.html
+        if (array_key_exists('update', $tpData['crud']) or array_key_exists('create', $tpData['crud'])) {
+            $content = $this->fetch($tpdir . 'publish.html.tp', $tpData);
+            FileHelper::save($PublishPaht, $content);
+        }
+
+        $this->success("success");
+    }
+
+    /**
+     * 表名 转 驼峰命名
+     * @param $tableName
+     * @return string
+     */
+    protected function getHumpName($tableName)
+    {
+        $db_prefix = \think\Env::get("db_prefix", "");
+        //去除表前缀
+        $tableName = str_replace($db_prefix, '', $tableName);
+        //拆分,首字母大写
+        $trems = explode('_', $tableName);
+        $controllerName = "";
+        foreach ($trems as $trem) {
+            $controllerName .= ucfirst($trem);
+        }
+        return $controllerName ?: 'DefaultController';
+    }
+
+    /**
+     * 表名 转 下划线命名
+     * @param $tableName
+     * @return mixed
+     */
+    protected function getUnderLineName($tableName)
+    {
+        //去除表前缀即可
+        $db_prefix = \think\Env::get("db_prefix", "");
+        return str_replace($db_prefix, '', $tableName);
+    }
+
+    /**
+     * 获取字段信息
+     * @param $tableName
+     * @return array
+     */
+    protected function getFieldsInfoByTableName($tableName)
+    {
+        $dbName = \think\Env::get("db_name", "");
+        //读取字段信息
+        $infos = Db::query("desc " . $tableName);
+
+        //读取字段注释
+        $comments = Db::query("SELECT COLUMN_NAME,column_comment FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME=? AND table_schema=?", [$tableName, $dbName]);
+
+        $data = [];
+        foreach ($infos as $k => $info) {
+            $data[$info['Field']] = $info;
+            $data[$info['Field']]['Comment'] = $comments[$k]['column_comment'];//这里不能保证两个数组顺序一致,后期有问题再修改
+        }
+        return $data;
+    }
+}

+ 118 - 0
app/admin/controller/Common.php

@@ -0,0 +1,118 @@
+<?php
+// +----------------------------------------------------------------------
+// | Tplay [ WE ONLY DO WHAT IS NECESSARY ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017 http://tplay.pengyichen.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 听雨 < 389625819@qq.com >
+// +----------------------------------------------------------------------
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\behavior\AdminLogBehavior;
+use think\Cache;
+use think\Controller;
+use think\Cookie;
+use think\Db;
+use think\Hook;
+use think\Session;
+
+class Common extends Controller
+{
+    const USE_REMEMBER = 'usermember';
+
+    protected function _initialize()
+    {
+        stopCC();
+
+        if (\think\Env::get('online_status', 'open') == 'close') {
+            exit("网站后台维护中 ...");
+        }
+        Hook::listen('admin_log');
+    }
+
+    /**
+     * 登录
+     * //不适用表单令牌,验证码输错时,token不会变,导致要刷新页面并重新输入
+     * @return mixed
+     */
+    public function login()
+    {
+        if (Session::has(Permissions::ADMIN_ID) == false) {
+            if ($this->request->isPost()) {
+                $post = $this->request->post();
+                $validate = new \think\Validate([
+                    ['name|账号', 'require|alphaDash|max:30'],
+                    ['password|密码', 'require|length:6,32'],
+                    ['captcha', 'require|captcha', '验证码不能为空|验证码不正确'],
+                ]);
+                if (!$validate->check($post)) {
+                    $this->error('提交失败:' . $validate->getError());
+                }
+
+                //连续错误5次账号暂停
+                $error_count = Cache::get('error_count' . $post['name']);
+                if ($error_count >= 5) {
+                    (new AdminLogBehavior())->updateLastLog("登入频繁,请休息10分钟", false);
+                    $this->error('登入频繁,请休息10分钟');
+                }
+
+                $name = Db::name('admin')->where('name', $post['name'])->find();
+                if (empty($name)) {
+                    //不存在该用户名
+                    (new AdminLogBehavior())->updateLastLog("账号不存在", false);
+                    $this->error('账号不存在');
+                } else {
+                    //验证密码
+                    $post['password'] = password($post['password']);
+                    if ($name['password'] != $post['password']) {
+                        //记录次数
+                        if (empty($error_count)) {
+                            Cache::set('error_count' . $post['name'], 1, 600);
+                        } else {
+                            Cache::set('error_count' . $post['name'], ++$error_count, 600);
+                        }
+                        (new AdminLogBehavior())->updateLastLog("密码错误", false);
+                        $this->error('密码错误');
+                    } else {
+                        //是否记住账号
+                        if (!empty($post['remember']) and $post['remember'] == 1) {
+                            //检查当前有没有记住的账号
+                            if (Cookie::has(self::USE_REMEMBER)) {
+                                Cookie::delete(self::USE_REMEMBER);
+                            }
+                            //保存新的
+                            Cookie::forever(self::USE_REMEMBER, $post['name']);
+                        } else {
+                            //未选择记住账号,或取消操作
+                            if (Cookie::has(self::USE_REMEMBER)) {
+                                Cookie::delete(self::USE_REMEMBER);
+                            }
+                        }
+                        Session::set(Permissions::ADMIN_ID, $name['id']); //保存admin_id
+                        Session::set(Permissions::ADMIN_NAME, $name['nickname']); //保存admin_name
+                        Session::set(Permissions::ADMIN_CATE_ID, $name['admin_cate_id']); //保存admin_cate_id
+                        //记录登录时间和ip
+                        Db::name('admin')->where('id', $name['id'])->update(['login_ip' => $this->request->ip(), 'login_time' => time()]);
+                        //清空次数
+                        Cache::rm('error_count' . $post['name']);
+                        (new AdminLogBehavior())->updateLastLog("登录成功", false);
+                        $this->success('登录成功,正在跳转...', 'admin/index/index');
+                    }
+                }
+            } else {
+                if (Cookie::has(self::USE_REMEMBER)) {
+                    $this->assign(self::USE_REMEMBER, Cookie::get(self::USE_REMEMBER));
+                }
+                return $this->fetch();
+            }
+        } else {
+            $this->redirect('admin/index/index');
+        }
+    }
+
+}

+ 172 - 0
app/admin/controller/Config.php

@@ -0,0 +1,172 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\Config as cateModel;
+use app\common\model\ConfigTab as tabModel;
+use think\Db;
+
+class Config extends Permissions
+{
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [
+                'hide' => cateModel::STATUS_SHOW,
+            ];
+            if (isset($post['tab_id']) && !empty($post['tab_id'])) {
+                $where['tab_id'] = $post['tab_id'];
+            }
+            if (isset($post['keywords']) and !empty($post['keywords'])) {
+                $where['name'] = ['like', '%' . $post['keywords'] . '%'];
+            }
+            $model = new cateModel();
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??0)->order('sort desc')->select();
+            foreach ($data as $k => $v) {
+                $data[$k]['tab_text'] = $v->tab_text;
+                $data[$k]['type_text'] = $v->type_text;
+            }
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            $grouptabs = (new tabModel())->order('sort desc')->select();
+            $this->assign('tabs', $grouptabs);
+            return $this->fetch();
+        }
+    }
+
+    public function index2()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [
+                'hide' => cateModel::STATUS_SHOW,
+                'status' => cateModel::STATUS_OPEN
+            ];
+            if (isset($post['tab_id']) && !empty($post['tab_id'])) {
+                $where['tab_id'] = $post['tab_id'];
+            }
+            if (isset($post['keywords']) and !empty($post['keywords'])) {
+                $where['name'] = ['like', '%' . $post['keywords'] . '%'];
+            }
+            $model = new cateModel();
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??0)->order('sort desc')->select();
+            foreach ($data as $k => $v) {
+                $data[$k]['value_text'] = $v->value_text;
+            }
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            $grouptabs = (new tabModel())->order('sort desc')->select();
+            $this->assign('tabs', $grouptabs);
+            return $this->fetch();
+        }
+    }
+
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = new cateModel();
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['name', 'require', '名称不能为空'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        } else {
+            $grouptabs = (new tabModel())->order('sort desc')->select();
+            $this->assign('tabs', $grouptabs);
+            $this->assign('types', $model::TYPES);
+        }
+
+        if ($id > 0) {
+            $cate = $model->where('id', $id)->find();
+            if (empty($cate)) {
+                $this->error('id不正确');
+            }
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改成功', 'index');
+                }
+            } else {
+                $this->assign('cate', $cate);
+                $this->assign('type', $cate->type);
+                return $this->fetch();
+            }
+        } else {
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加失败');
+                } else {
+                    $this->success('添加成功', 'index');
+                }
+            } else {
+                $this->assign('type', 0);
+                return $this->fetch();
+            }
+        }
+    }
+
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            if (Db::name('config_option')->where('pid', $id)->select() == null) {
+                if (false == Db::name('config')->where('id', $id)->delete()) {
+                    $this->error('删除失败');
+                } else {
+                    $this->success('删除成功', 'index');
+                }
+            } else {
+                $this->error('请先删除子配置');
+            }
+        }
+    }
+
+    public function sort()
+    {
+        if ($this->request->isPost() && $this->request->has('ids')) {
+            $post = $this->request->post();
+            $i = 0;
+            foreach ($post['ids'] as $k => $id) {
+                $sort = Db::name('config')->where('id', $id)->value('sort');
+                $newsort = $post['sorts'][$k]??$sort;
+                if ($sort != $newsort) {
+                    if (false == Db::name('config')->where('id', $id)->update(['sort' => $newsort])) {
+                        $this->error('更新失败');
+                    } else {
+                        $i++;
+                    }
+                }
+            }
+            $this->success('成功更新' . $i . '个数据', 'index');
+        } else {
+            $this->error('无数据更新', 'index');
+        }
+    }
+
+    public function status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            if (false == Db::name('config')->where('id', $post['id'])->update(['status' => $post['status']])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+}

+ 180 - 0
app/admin/controller/ConfigOption.php

@@ -0,0 +1,180 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\ConfigOption as optionModel;
+use think\Db;
+use think\Log;
+
+class ConfigOption extends Permissions
+{
+    public function index()
+    {
+        $pid = $this->request->param('pid', 0, 'intval');
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [
+                'pid' => $pid,
+                'hide' => optionModel::STATUS_SHOW
+            ];
+            if (isset($post['keywords']) and !empty($post['keywords'])) {
+                $where['name'] = ['like', '%' . $post['keywords'] . '%'];
+            }
+            $model = new optionModel();
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order('sort desc')->select();
+
+            foreach ($data as $k => $v) {
+                $v['cate_name'] = $v->config['name'];
+                $v['thumb_url'] = geturl($v['image']);
+                $v['group_type'] = $v->config['type'];
+                $data[$k] = $v;
+            }
+
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            $this->assign('json_config', \app\common\model\Config::get($pid)->getJsonConfig());
+            return $this->fetch();
+        }
+    }
+
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $pid = $this->request->param('pid', 0, 'intval');
+        $this->assign('pid', $pid);
+        $model = new optionModel();
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['name', 'require', '标题不能为空'],
+                ['pid', 'require', '请选择分类'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        } else {
+            $this->assign('json_config', \app\common\model\Config::get($pid)->getJsonConfig());
+        }
+        if ($id > 0) {
+            //是修改操作
+            $link = $model->where('id', $id)->find();
+            if (empty($link)) {
+                $this->error('id不正确');
+            }
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改成功', 'index', ['pid' => $pid]);
+                }
+            } else {
+                $this->assign('link', $link);
+                return $this->fetch();
+            }
+        } else {
+            //是新增操作
+            if ($this->request->isPost()) {
+
+                if ($model->where('pid', $pid)->count() == 0) {
+                    $config = \app\common\model\Config::get($pid);
+                    if (!$config) {
+                        $this->error('pid异常');
+                    }
+                    if ($config->type == \app\common\model\Config::TYPE_RADIO) {
+                        $post['single_status'] = 1;
+                    }
+                }
+
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加失败');
+                } else {
+                    $this->success('添加成功', 'index', ['pid' => $pid]);
+                }
+            } else {
+                return $this->fetch();
+            }
+        }
+    }
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            if (false == Db::name('config_option')->where('id', $id)->delete()) {
+                $this->error('删除失败');
+            } else {
+                $this->success('删除成功', 'index');
+            }
+        }
+    }
+
+    public function deletes()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+            $model = new optionModel();
+            if ($model->where('id', 'in', $ids)->delete()) {
+                Log::log("批量删除链接:" . implode(',', $ids));
+                $this->success('删除成功');
+            }
+        }
+    }
+
+    //排序
+    public function sort()
+    {
+        if ($this->request->isPost() && $this->request->has('ids')) {
+            $post = $this->request->post();
+            $i = 0;
+            foreach ($post['ids'] as $k => $id) {
+                $sort = Db::name('config_option')->where('id', $id)->value('sort');
+                $newsort = $post['sorts'][$k]??$sort;
+                if ($sort != $newsort) {
+                    if (false == Db::name('config_option')->where('id', $id)->update(['sort' => $newsort])) {
+                        $this->error('更新失败');
+                    } else {
+                        $i++;
+                    }
+                }
+            }
+            $this->success('成功更新' . $i . '个数据', 'index');
+        } else {
+            $this->error('无数据更新', 'index');
+        }
+    }
+
+    public function status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            if (false == Db::name('config_option')->where('id', $post['id'])->update(['status' => $post['status']])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+
+    //单选状态
+    public function single_status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            Db::name('config_option')->where('pid', $post['pid'])->update(['single_status' => 0]);
+            if (false == Db::name('config_option')->where('id', $post['id'])->update(['single_status' => $post['status']])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+}

+ 120 - 0
app/admin/controller/ConfigTab.php

@@ -0,0 +1,120 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\ConfigTab as tabModel;
+use think\Db;
+
+class ConfigTab extends Permissions
+{
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $model = new tabModel();
+            $data = $model->page($post['page']??0, $post['limit']??15)->order('sort desc')->select();
+            return array('code' => 0, 'count' => count($data), 'data' => $data);
+        } else {
+            return $this->fetch();
+        }
+    }
+
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = new tabModel();
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['name', 'require', '分类名称不能为空'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        }
+
+        if ($id > 0) {
+            $cate = $model->where('id', $id)->find();
+            if (empty($cate)) {
+                $this->error('id不正确');
+            }
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改成功', 'index');
+                }
+            } else {
+                $this->assign('cate', $cate);
+                return $this->fetch();
+            }
+        } else {
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加失败');
+                } else {
+                    $this->success('添加成功', 'index');
+                }
+            } else {
+                return $this->fetch();
+            }
+        }
+    }
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            if (Db::name('config')->where('tab_id', $id)->select() == null) {
+                if (false == Db::name('config_tab')->where('id', $id)->delete()) {
+                    $this->error('删除失败');
+                } else {
+                    $this->success('删除成功', 'index');
+                }
+            } else {
+                $this->error('该标签已经绑定了链接组,请先删除关联');
+            }
+        }
+    }
+
+    public function sort()
+    {
+        if ($this->request->isPost() && $this->request->has('ids')) {
+            $post = $this->request->post();
+            $i = 0;
+            foreach ($post['ids'] as $k => $id) {
+                $sort = Db::name('config_tab')->where('id', $id)->value('sort');
+                $newsort = $post['sorts'][$k]??$sort;
+                if ($sort != $newsort) {
+                    if (false == Db::name('config_tab')->where('id', $id)->update(['sort' => $newsort])) {
+                        $this->error('更新失败');
+                    } else {
+                        $i++;
+                    }
+                }
+            }
+            $this->success('成功更新' . $i . '个数据', 'index');
+        } else {
+            $this->error('无数据更新', 'index');
+        }
+    }
+
+    public function status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            if (false == Db::name('config_tab')->where('id', $post['id'])->update(['status' => $post['status']])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+}

+ 234 - 0
app/admin/controller/Databackup.php

@@ -0,0 +1,234 @@
+<?php
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use databackup\src\Backup;
+use think\Request;
+use think\Session;
+
+/**
+ * 备份大数据,还原时间太长,效率太差
+ * Class Databackup
+ * @package app\admin\controller
+ */
+class Databackup extends Permissions
+{
+    /** @var Backup */
+    private static $sdk;
+
+    const DEFAULT_PATH = RUNTIME_PATH . 'backup' . DS;
+
+    protected function _initialize()
+    {
+        parent::_initialize();
+
+        $path = \app\common\model\Webconfig::getValue('data_backup_path');
+        $path = empty($path) ? self::DEFAULT_PATH : (endWith($path, DS) ? $path : $path . DS);
+
+        self::$sdk = new Backup([
+            'path' => $path
+        ]);
+    }
+
+    /**
+     * table列表
+     * @return mixed
+     */
+    public function index()
+    {
+        return $this->fetch('index', ['list' => self::$sdk->dataList()]);
+    }
+
+    /**
+     * 备份文件列表
+     * @return mixed
+     */
+    public function importlist()
+    {
+        return $this->fetch('importlist', ['list' => self::$sdk->fileList()]);
+    }
+
+    /**
+     * 还原数据
+     * @param int $time
+     * @param null $part
+     * @param null $start
+     */
+    public function import($time = 0, $part = null, $start = null)
+    {
+        if (is_numeric($time) && is_null($part) && is_null($start)) {
+            $list = self::$sdk->getFile('timeverif', $time);
+            if (is_array($list)) {
+                session::set('backup_list', $list);
+                $this->success('初始化完成!', '', array('part' => 1, 'start' => 0));
+            } else {
+                $this->error('备份文件可能已经损坏,请检查!');
+            }
+        } else if (is_numeric($part) && is_numeric($start)) {
+
+            $list = session::get('backup_list');
+            $start = self::$sdk->setFile($list)->import($start);
+
+            if (false === $start) {
+                $this->error('还原数据出错!');
+            } elseif (0 === $start) {
+                if (isset($list[++$part])) {
+                    $data = array('part' => $part, 'start' => 0);
+                    $this->success("正在还原...#{$part}", '', $data);
+                } else {
+                    session::delete('backup_list');
+                    $this->success('还原完成!');
+                }
+            } else {
+                $data = array('part' => $part, 'start' => $start[0]);
+                if ($start[1]) {
+                    $rate = floor(100 * ($start[0] / $start[1]));
+                    $this->success("正在还原...#{$part} ({$rate}%)", '', $data);
+                } else {
+                    $data['gz'] = 1;
+                    $this->success("正在还原...#{$part}", '', $data);
+                }
+                $this->success("正在还原...#{$part}", '');
+            }
+        } else {
+            $this->error('参数错误!');
+        }
+    }
+
+    /**
+     * 删除备份文件
+     */
+    public function del($time = 0)
+    {
+        if (self::$sdk->delFile($time)) {
+            $this->success("备份文件删除成功!", 'admin/databackup/importlist');
+        } else {
+            $this->error("备份文件删除失败,请检查权限!");
+        }
+    }
+
+    /**
+     * 备份表 - 命令行
+     */
+    public function backup()
+    {
+        $tables = $this->request->param('tables');
+        if ($tables && $this->request->isCli()) {
+            if ("all" === $tables) {
+                $tables = [];
+                foreach (self::$sdk->dataList() as $item) {
+                    $tables[] = $item['name'];
+                }
+            } else {
+                $tables = explode(',', $tables);
+            }
+            //lock
+            $fileinfo = self::$sdk->getFile();
+            $lock = "{$fileinfo['filepath']}backup.lock";
+            !is_file($lock) || $this->error('检测到有一个备份任务正在执行,请稍后再试!');
+            is_writeable($fileinfo['filepath']) || $this->error('备份目录不存在或不可写,请检查后重试!');
+            file_put_contents($lock, time());
+            //
+            $file = $fileinfo['file'];
+            self::$sdk->Backup_Init() || $this->error('初始化失败,备份文件创建失败!');
+            foreach ($tables as $tablename) {
+                $start = self::$sdk->setFile($file)->backup($tablename, 0);
+                if (false === $start) {
+                    $this->error($tablename . '备份出错!');
+                } else {
+                    while (is_array($start)) {
+                        $start = self::$sdk->setFile($file)->backup($tablename, $start[0]);
+                    }
+                }
+            }
+            unlink($lock);
+            $this->success('备份完成!');
+        }
+        $this->error('参数错误!');
+    }
+
+    /**
+     * 备份表
+     */
+    public function export()
+    {
+        if (Request::instance()->isPost()) {
+            $input = input('post.');
+            $fileinfo = self::$sdk->getFile();
+            $lock = "{$fileinfo['filepath']}backup.lock";
+            !is_file($lock) || $this->error('检测到有一个备份任务正在执行,请稍后再试!');
+            is_writeable($fileinfo['filepath']) || $this->error('备份目录不存在或不可写,请检查后重试!');
+            file_put_contents($lock, time());
+            //缓存锁文件
+            session::set('lock', $lock);
+            //缓存备份文件信息
+            session::set('backup_file', $fileinfo['file']);
+            //缓存要备份的表
+            session::set('backup_tables', $input['tables']);
+            //创建备份文件
+            if (false !== self::$sdk->Backup_Init()) {
+                $first_table_name = $input['tables'][0]??'null';
+                $this->success('初始化成功!', '', ['tab' => ['id' => 0, 'start' => 0, 'tablename' => $first_table_name]]);
+            } else {
+                $this->error('初始化失败,备份文件创建失败!');
+            }
+        } else if (Request::instance()->isGet()) {
+            $tables = session::get('backup_tables');
+            $file = session::get('backup_file');
+            $id = input('id');
+            $start = input('start');
+            $tablename = $tables[$id];
+            $start = self::$sdk->setFile($file)->backup($tablename, $start);
+            if (false === $start) {
+                $this->error($tablename . '备份出错!');
+            } else {
+                while (is_array($start)) {
+                    $start = self::$sdk->setFile($file)->backup($tablename, $start[0]);
+                }
+                if (0 === $start) {
+                    if (isset($tables[++$id])) {
+                        $tab = ['id' => $id, 'start' => 0, 'tablename' => $tables[$id]];//下一个递归信息
+                        $this->success($tablename . '备份完成!', '', array('tab' => $tab));
+                    } else {
+                        //备份完成,清空缓存
+                        unlink(session::get('lock'));
+                        Session::delete('backup_tables');
+                        Session::delete('backup_file');
+                        $this->success($tablename . '备份完成!');
+                    }
+                } else {
+                    $this->error($tablename . '备份出错!');
+                }
+            }
+        } else {
+            $this->error('参数错误!');
+        }
+    }
+
+    /**
+     * 修复表
+     * @param null $tables
+     */
+    public function repair($tables = null)
+    {
+        if (self::$sdk->repair($tables)) {
+            $this->success("数据表修复完成!");
+        } else {
+            $this->error("数据表修复出错请重试");
+        }
+    }
+
+    /**
+     * 优化表
+     * @param null $tables
+     */
+    public function optimize($tables = null)
+    {
+        if (self::$sdk->optimize($tables)) {
+            $this->success("数据表优化完成!");
+        } else {
+            $this->error("数据表优化出错请重试!");
+        }
+    }
+}

+ 88 - 0
app/admin/controller/Emailconfig.php

@@ -0,0 +1,88 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use think\Db;
+
+class Emailconfig extends Permissions
+{
+    public function index()
+    {
+        $data = \app\common\model\Emailconfig::get(1);
+        $this->assign('data', $data);
+        $grouptabs = (new \app\common\model\ConfigTab())->order('sort desc')->select();
+        $this->assign('tabs', $grouptabs);
+        return $this->fetch();
+    }
+
+    public function publish()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            $validate = new \think\Validate([
+                ['from_email', 'require|email', '发件箱不能为空|发件箱格式不正确'],
+                ['from_name', 'require', '发件人不能为空'],
+                ['smtp', 'require', '邮箱smtp服务器不能为空'],
+                ['username', 'require|email', '登录账户不能为空|登录账户应为邮箱格式'],
+                ['password', 'require', '登录密码不能为空'],
+                ['content', 'require', '邮件模板不能为空'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+
+            if (false == Db::name('emailconfig')->where('email', 'email')->update($post)) {
+                $this->error('提交失败');
+            } else {
+                $this->success('提交成功', 'admin/emailconfig/index');
+            }
+        } else {
+            $this->error('非法请求');
+        }
+    }
+
+    //测试发送
+    public function mailto()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            $validate = new \think\Validate([
+                ['email', 'require|email', '收件箱不能为空|收件箱格式不正确'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+
+            $to_email = $post['email'];
+            $data = \app\common\model\Emailconfig::get(1);
+            $title = $data['title'];
+            $content = str_replace(['{nickname}', '{code}', '{passport}', '{email}', '{time}'], ['tplay1', '1234', 'tplay2', 'tplay@test.com', date('Y-m-d H:i:s', time())], $data['content']);
+            $from_email = $data['from_email'];
+            $from_name = $data['from_name'];
+            $smtp = $data['smtp'];
+            $username = $data['username'];
+            $password = $data['password'];
+
+            if (!$title || !$content || !$from_email || !$from_name || !$smtp || !$username || !$password) {
+                $this->error('请先配置发件邮箱信息');
+            }
+
+            $mailto = SendMail($to_email, $title, $content, $from_email, $from_name, $smtp, $username, $password);
+            if (false == $mailto) {
+                $this->error('发送失败');
+            } else {
+                $this->success('邮件发送成功');
+            }
+        } else {
+            return $this->fetch();
+        }
+    }
+}

+ 291 - 0
app/admin/controller/FileManage.php

@@ -0,0 +1,291 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\FileManageModel;
+use app\common\service\UploadService;
+use file\DirHelper;
+use file\FileHelper;
+use file\ImgHelper;
+use file\PathHelper;
+use file\ZipHelper;
+use network\FtpHelper;
+use think\Exception;
+use think\Log;
+
+class FileManage extends Permissions
+{
+    public function index()
+    {
+        $relativePath = $this->request->param('relative_path', '', 'replaceDS,deleteEndDS');
+        $this->assign('relative_path', $relativePath);
+
+        if ($this->request->isAjax()) {
+            $keyword = $this->request->param('keyword', '', 'trim');
+
+            if (ifContain($keyword, DS)) {
+                if (ifContain($keyword, FileManageModel::getRootDir())) {
+                    $keyword = str_replace(FileManageModel::getRootDir(), '', $keyword);
+                } else {
+                    $keyword = deleteStartDS($keyword);
+                }
+
+                if (file_exists(FileManageModel::getRootDir() . $keyword)) {
+                    if (is_dir(FileManageModel::getRootDir() . $keyword)) {
+                        $relativePath2 = $keyword;
+                    } else {
+                        $relativePath2 = PathHelper::getParentDir($keyword);
+                    }
+                    if ($relativePath != $relativePath2) {
+                        $this->success($relativePath2, 'index');
+                    } else {
+                        $keyword = PathHelper::getFilename($keyword);
+                    }
+                }
+            }
+
+            $data = FileManageModel::getFileList(trim($keyword), $relativePath);
+            return array('code' => 0, 'data' => $data);
+        } else {
+            $this->assign('fullPath', appendEndDS(appendEndDS(FileManageModel::getRootDir()) . $relativePath));
+            $arr = explode(DS, $relativePath);
+            $parentPath = end($arr) == $relativePath ? "" : str_replace(DS . end($arr), '', $relativePath);
+            $this->assign('parentPath', $parentPath);
+            return $this->fetch();
+        }
+    }
+
+    public function publish()
+    {
+        $rootpath = FileManageModel::getRootDir();
+        $relativePath = $this->request->param('relative_path', '', 'replaceDS');
+        $this->assign('relative_path', $relativePath);
+
+        $post = $this->request->post();
+        $fullname = $this->request->param('fullname');//文件名
+        $name = $this->request->param('name');//新增模板名
+
+        if ($fullname) {
+            //修改操作
+            $filePath = $rootpath . DS . $relativePath . DS . $fullname;
+            if ($this->request->isPost()) {
+                $getfilemtime = $this->request->param('filemtime');
+                if ($getfilemtime != filemtime($filePath)) {
+                    $this->error('文件版本不正确,请刷新后重新提交');
+                }
+                try {
+                    FileHelper::save($filePath, $post['content']);
+                } catch (\Exception $e) {
+                    $this->error('修改失败.' . $e->getMessage());
+                }
+                $this->success('修改成功', '', filemtime($filePath));
+            } else {
+                if (file_exists($filePath)) {
+                    if (is_dir($filePath)) {
+                        $this->error('不可以编辑目录');
+                    }
+                    if (ImgHelper::isImage($filePath)) {
+                        $this->error('不可以编辑图片');
+                    }
+                    $this->assign('fullname', $fullname);
+                    $this->assign('filemtime', filemtime($filePath));
+                    $this->assign('content', FileHelper::read($filePath));
+                    return $this->fetch();
+                } else {
+                    $this->error('文件不存在');
+                }
+            }
+        } else {
+            //新增操作
+            if ($this->request->isPost()) {
+                $validate = new \think\Validate([
+                    ['name|模板名称', 'require'],
+                ]);
+                if (!$validate->check($post)) {
+                    $this->error('提交失败:' . $validate->getError());
+                }
+                $filePath = $rootpath . DS . $relativePath . DS . $name;
+                if (file_exists($filePath)) {
+                    $this->error('该文件已存在,请修改名称');
+                } else {
+                    try {
+                        FileHelper::save($filePath, $post['content']);
+                    } catch (\Exception $e) {
+                        $this->error('添加失败.' . $e->getMessage());
+                    }
+                }
+                $this->success('添加成功', 'index', ['relative_path' => $relativePath]);
+            } else {
+                return $this->fetch();
+            }
+        }
+    }
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $rootpath = FileManageModel::getRootDir();
+            $fullname = $this->request->param('fullname');
+            $relativePath = $this->request->param('relative_path', '', 'replaceDS');
+            $fullpath = $rootpath . DS . $relativePath . DS . $fullname;
+            if (!file_exists($fullpath)) {
+                $this->error('文件不存在');
+            }
+            if (is_dir($fullpath)) {
+                DirHelper::delDir($fullpath);
+                if (file_exists($fullpath)) {
+                    $this->error("删除失败,请检查目录权限");
+                }
+                $this->success('删除成功', 'index');
+            } else {
+                if (false == unlink($fullpath)) {
+                    $this->error('删除失败');
+                } else {
+                    $this->success('删除成功', 'index');
+                }
+            }
+        }
+    }
+
+    public function rename()
+    {
+        if ($this->request->isAjax()) {
+            $relativePath = $this->request->param('relative_path', '', 'replaceDS');
+            $oldname = $this->request->param('oldname');
+            $newname = $this->request->param('newname');
+            $rootpath = FileManageModel::getRootDir();
+            if (file_exists($rootpath . DS . $newname)) {
+                $this->error('文件已存在');
+            }
+            if (FileHelper::rename($rootpath . DS . $relativePath . DS . $oldname, $rootpath . DS . $relativePath . DS . $newname)) {
+                $this->success('重命名成功', 'index');
+            } else {
+                $this->error('重命名失败');
+            }
+        }
+    }
+
+    public function upload()
+    {
+        //html目录
+        $rootpath = FileManageModel::getRootDir();
+        //相对路径
+        $relativePath = $this->request->param('relative_path', '', 'replaceDS');
+        (new UploadService())->upload2path(appendEndDS(appendEndDS($rootpath) . $relativePath));
+    }
+
+    public function createDir()
+    {
+        $name = $this->request->param('name');
+        //根目录
+        $rootpath = FileManageModel::getRootDir();
+        //根目录下的相对路径
+        $relativePath = $this->request->param('relative_path', '', 'replaceDS');
+        if (true === DirHelper::makeDir($rootpath . DS . $relativePath . DS . $name)) {
+            $this->success('执行成功');
+        } else {
+            $this->error('执行失败');
+        }
+    }
+
+    public function unzip()
+    {
+        $post = $this->request->param();
+        $file_list = $post['file_list'];
+        $relativePath = $this->request->param('relative_path', '', 'replaceDS');
+
+        $rootPath = FileManageModel::getRootDir();
+        try {
+            foreach ($file_list as $filename) {
+                $source = $rootPath . DS . ($relativePath ? $relativePath . DS . $filename : $filename);
+                $dest = $rootPath . DS . $relativePath;
+                if (ifContain($source, '.zip')) {
+                    if (false === (new ZipHelper())->unzip_gbk($source, $dest)) {
+                        throw new \Exception('解压失败');
+                    }
+                }
+            }
+        } catch (Exception $e) {
+            $this->error($e->getMessage());
+        }
+        $this->success('执行成功');
+    }
+
+    /**
+     * 上传到ftp ,要防止数据量过多
+     */
+    public function ftpCover()
+    {
+        $post = $this->request->param();
+        $file_list = $post['file_list'];
+        $relativePath = $this->request->param('relative_path', '', 'replaceDS');
+        //
+        $rootPath = FileManageModel::getRootDir();
+        $config = \app\common\model\Webconfig::getFtpConfig();
+        $ftpHost = $config['host']??'';
+        if (empty($ftpHost)) {
+            $this->error('请先配置ftp');
+        }
+        try {
+            $ftp = new FtpHelper();
+            $ftp->connect($ftpHost, $config['user']??'', $config['pwd']??'', $config['port']??21);
+            //设置根目录
+            if ($config['webRoot']??'') {
+                $ftp->cd($config['webRoot']??'');
+            }
+            foreach ($file_list as $filename) {
+                $upDownPath = $rootPath . DS . ($relativePath ? $relativePath . DS . $filename : $filename);
+                if (is_file($upDownPath)) {
+                    Log::log($upDownPath);
+                    $ftp->mkdir($relativePath);//新建远程目录
+                    $ftp->upload($relativePath . DS . $filename, $upDownPath);
+                } else {
+                    $this->ftpUpload($upDownPath, $rootPath, $ftp);
+                }
+            }
+        } catch (Exception $e) {
+            $this->error($e->getMessage());
+        }
+        $this->success('执行成功');
+    }
+
+    /**
+     * 递归上传ftp
+     * @param $dir [要上传的文件或目录的物理路径]
+     * @param $rootPath [本地根目录]
+     * @param $ftp FtpHelper
+     */
+    private function ftpUpload($dir, $rootPath, $ftp)
+    {
+        Log::log($dir);
+        if (file_exists($dir)) {
+            $ftpDir = replaceUrlDS(str_replace($rootPath, '', $dir));
+            if ($ftpDir) {
+                $ftp->mkdir($ftpDir);//新建远程目录
+            }
+            $mydir = dir($dir);
+            while (false !== ($file = $mydir->read())) {
+                if ($file != "." && $file != "..") {
+                    $path = $dir . DS . $file;
+                    if (is_dir($path)) {
+                        $this->ftpUpload($path, $rootPath, $ftp);
+                    } else {
+//                        if (time() - filemtime($path) <= 3600 * 24) {//修改时间是否超过24小时
+                        Log::log($path);
+                        $ftp->upload($ftpDir . DS . $file, $path);
+//                        }
+                    }
+                }
+            }
+            $mydir->close();
+        }
+    }
+
+}

+ 101 - 0
app/admin/controller/Index.php

@@ -0,0 +1,101 @@
+<?php
+// +----------------------------------------------------------------------
+// | Tplay [ WE ONLY DO WHAT IS NECESSARY ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017 http://tplay.pengyichen.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 听雨 < 389625819@qq.com >
+// +----------------------------------------------------------------------
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use think\Db;
+use think\Session;
+
+class Index extends Permissions
+{
+    public function index()
+    {
+        $menu = Db::name('admin_menu')->where(['is_display' => 1])->order('orders asc')->select();
+
+        //删除不在当前角色权限里的菜单,实现隐藏
+        $admin_cate = Session::get(self::ADMIN_CATE_ID);
+        $permissions = Db::name('admin_cate')->where(['id' => $admin_cate])->value('permissions');
+        $permissions = explode(',', $permissions);
+
+        foreach ($menu as $k => $val) {
+            if ($val['type'] == 1 and $val['is_display'] == 1 and !in_array($val['id'], $permissions)) {
+                unset($menu[$k]);
+            }
+        }
+
+        //添加url
+        foreach ($menu as $key => $value) {
+            if (empty($value['parameter'])) {
+                $url = url($value['module'] . '/' . $value['controller'] . '/' . $value['function']);
+            } else {
+                $url = url($value['module'] . '/' . $value['controller'] . '/' . $value['function'], $value['parameter']);
+            }
+            $menu[$key]['url'] = $url;
+        }
+        $menus = $this->menulist($menu);
+        $this->assign('menus', $menus);
+
+        $admin = Db::name('admin')->where('id', Session::get(self::ADMIN_ID))->find();
+        $this->assign('admin', $admin);
+
+        return $this->fetch();
+    }
+
+
+    protected function menulist($menu)
+    {
+        $menus = array();
+        //先找出顶级菜单
+        foreach ($menu as $k => $val) {
+            if ($val['pid'] == 0) {
+                $menus[$k] = $val;
+            }
+        }
+        //通过顶级菜单找到下属的子菜单
+        foreach ($menus as $k => $val) {
+            foreach ($menu as $key => $value) {
+                if ($value['pid'] == $val['id']) {
+                    $menus[$k]['list'][] = $value;
+                }
+            }
+        }
+        //三级菜单
+        foreach ($menus as $k => $val) {
+            if (isset($val['list'])) {
+                foreach ($val['list'] as $ks => $vals) {
+                    foreach ($menu as $key => $value) {
+                        if ($value['pid'] == $vals['id']) {
+                            $menus[$k]['list'][$ks]['list'][] = $value;
+                        }
+                    }
+                }
+            }
+        }
+        return $menus;
+    }
+
+    /**
+     * 管理员退出,清除session
+     */
+    public function logout()
+    {
+        Session::delete(self::ADMIN_ID);
+        Session::delete(self::ADMIN_NAME);
+        Session::delete(self::ADMIN_CATE_ID);
+        if (Session::has(self::ADMIN_ID) or Session::has(self::ADMIN_CATE_ID)) {
+            $this->error('退出失败');
+        } else {
+            $this->success('正在退出...', 'admin/common/login');
+        }
+    }
+}

+ 141 - 0
app/admin/controller/Main.php

@@ -0,0 +1,141 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\admin\model\AdminLog;
+use app\admin\model\Urlconfig;
+use think\Cache;
+use think\Db;
+
+class Main extends Permissions
+{
+    public function index()
+    {
+        //tp版本号
+        $info['tp'] = THINK_VERSION;
+        //php版本
+        $info['php'] = PHP_VERSION;
+        //操作系统
+        $info['win'] = PHP_OS;
+        //最大上传限制
+        $info['upload_max_filesize'] = ini_get('upload_max_filesize');//不能使用ini_set修改
+        $info['post_max_size'] = ini_get('post_max_size');//不能使用ini_set修改
+        $info['memory_limit'] = ini_get('memory_limit');
+        $info['tplay_filesize'] = \app\common\model\Webconfig::getValue('file_size') * 1024;
+        //脚本执行时间限制
+        $info['max_execution_time'] = ini_get('max_execution_time') . 'S';
+        //运行环境
+//        $sapi = php_sapi_name();
+//        if ($sapi == 'apache2handler') {
+//            $info['environment'] = 'apache';
+//        } elseif ($sapi == 'cgi-fcgi') {
+//            $info['environment'] = 'cgi';
+//        } elseif ($sapi == 'cli') {
+//            $info['environment'] = 'cli';
+//        } else {
+//            $info['environment'] = $sapi;
+//        }
+        $info['environment'] = $_SERVER['SERVER_SOFTWARE'];
+        try {
+            //剩余空间大小,服务器没有权限会报错
+            $info['disk'] = format_bytes(disk_free_space("/"));
+        } catch (\Exception $e) {
+        }
+
+        $this->assign('info', $info);
+
+        //==============网站数据=============================
+
+        //会员
+        $web['user_num'] = Db::name('user')->where('status', \app\common\model\User::STATUS_PASS)->count();
+        $web['user_num_wait'] = Db::name('user')->where('status', \app\common\model\User::STATUS_WAIT)->count();
+        //文章
+        $web['article_num'] = Db::name('article')->count();
+        $web['status_article'] = Db::name('article')->where('status', 0)->count();
+        //附件
+        $web['file_num'] = Db::name('attachment')->count();
+        $web['status_file'] = Db::name('attachment')->where('status', 0)->count();
+        //消息
+        $web['message_num'] = Db::name('messages')->count();
+        $web['look_message'] = Db::name('messages')->where('is_look', 0)->count();
+
+        //==============管理员操作记录===========================
+
+        $today = date('Y-m-d');
+        //取当前时间的前十天
+        $date = [];
+        $date_string = '';
+        for ($i = 9; $i > 0; $i--) {
+            $date[] = date("Y-m-d", strtotime("-{$i} day"));
+            $date_string .= date("Y-m-d", strtotime("-{$i} day")) . ',';
+        }
+        $date[] = $today;
+        $date_string .= $today;
+        $web['date_string'] = $date_string;
+
+        $login_sum = '';
+        foreach ($date as $k => $val) {
+            $min_time = strtotime($val);
+            $max_time = $min_time + 60 * 60 * 24;
+            $where['create_time'] = [['>=', $min_time], ['<=', $max_time]];
+            $login_sum .= Db::name('admin_log')->where('params', '<>', '')->where($where)->count() . ',';
+        }
+        $web['login_sum'] = $login_sum;
+        $this->assign('web', $web);
+
+        //==============风险提示=============================
+        $safe = true;
+
+        if (file_exists(APP_PATH . 'install')) {
+            $safe = false;
+            //提示删除install目录
+            $this->assign('delete_install', true);
+        }
+
+        //提示改密码
+        $weekpass = Db::name('admin')->where('id', session(self::ADMIN_ID))->where('password', password('123456'))->count();
+        if ($weekpass > 0) {
+            $safe = false;
+            $this->assign('weekpass', true);
+            if (!in_array($this->request->ip(), ['0.0.0.0', '127.0.0.1'])) {
+                $this->assign('waring', ['msg' => "当前密码过于简单,是否立即修改?", "url" => url('admin/admin/editpassword')]);
+            }
+        }
+
+        //提示设置安全入口
+        if ((new Urlconfig())->isWeekBackend()) {
+            $safe = false;
+            $this->assign('week_backend', true);
+        }
+
+        if (session(self::ADMIN_CATE_ID) == \app\admin\model\Admin::SUPER_ADMIN_ID) {
+            //登录请求过多,防止被爆破,提示管理员修改后台地址
+            if ((new AdminLog())->where(['admin_menu_id' => '50', 'params' => ['<>', '']])->whereTime('create_time', 'today')->count() > 100) {
+                $this->assign('waring', ['msg' => "今日发现多次登录请求,是否立即修改后台地址?", "url" => url('admin/urlsconfig/index')]);
+            }
+        }
+
+        $this->assign('safe', $safe);
+        return $this->fetch();
+    }
+
+    /**
+     * 清除全部缓存
+     */
+    public function clear()
+    {
+        if (false == Cache::clear()) {
+            $this->error('清除缓存失败');
+        } else {
+            $this->success('清除缓存成功');
+        }
+    }
+}

+ 196 - 0
app/admin/controller/Menu.php

@@ -0,0 +1,196 @@
+<?php
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\admin\model\AdminMenu as menuModel;
+use think\Db;
+
+class Menu extends Permissions
+{
+    public function index()
+    {
+        $model = new menuModel();
+        if ($this->request->isAjax()) {
+            $data = $model->order('orders asc')->select();
+            foreach ($data as $k => $v) {
+                $v['type_text'] = $v->type == 1 ? "权限节点" : "普通节点";
+                $v['is_display_text'] = $v->is_display == 1 ? "显示在左侧菜单" : "操作节点";
+                $data[$k] = $v;
+            }
+            return $data;
+        } else {
+            $menus = $model->order('orders asc')->select();
+            $menus_all = $model->menulist($menus);
+            $this->assign('menus', $menus_all);
+            $this->assign('noInsertRoutes', $this->noInsertRoutes());
+            return $this->fetch();
+        }
+    }
+
+    public function publish()
+    {
+        $id = $this->request->has('id') ? $this->request->param('id', 0, 'intval') : 0;
+        $model = new menuModel();
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['name', 'require', '菜单名称不能为空'],
+                ['pid', 'require', '请选择上级菜单'],
+                // ['module', 'require', '请填写模块名称'],
+                // ['controller', 'require', '请填写控制器名称'],
+                // ['function', 'require', '请填写方法名称'],
+                ['type', 'require', '请选择菜单类型'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        }
+
+        if ($id > 0) {
+            if ($this->request->isPost()) {
+                if ($id == $post['pid']) {
+                    $this->error('不能选自己当上级分类');
+                }
+                $menu = $model->where('id', $id)->find();
+                if (empty($menu)) {
+                    $this->error('id不正确');
+                }
+                //如果关闭默认展开,给默认值0
+                if (empty($post['is_open'])) {
+                    $post['is_open'] = 0;
+                }
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改菜单信息成功', 'admin/menu/index');
+                }
+            } else {
+                $menu = $model->where('id', $id)->find();
+                $menus = $model->order('orders asc')->select();
+                $menus_all = $model->menulist($menus);
+                $this->assign('menus', $menus_all);
+                if (!empty($menu)) {
+                    $this->assign('menu', $menu);
+                    return $this->fetch();
+                } else {
+                    $this->error('id不正确');
+                }
+            }
+        } else {
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加菜单失败');
+                } else {
+                    $this->success('添加菜单成功', 'admin/menu/index');
+                }
+            } else {
+                $pid = $this->request->has('pid') ? $this->request->param('pid', null, 'intval') : null;
+                if (!empty($pid)) {
+                    $this->assign('pid', $pid);
+                }
+                $menu = $model->order('orders asc')->select();
+                $menus = $model->menulist($menu);
+                $this->assign('menus', $menus);
+                return $this->fetch();
+            }
+        }
+    }
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->has('id') ? $this->request->param('id', 0, 'intval') : 0;
+            if (Db::name('admin_menu')->where('pid', $id)->select() == null) {
+                if (false == Db::name('admin_menu')->where('id', $id)->delete()) {
+                    $this->error('删除失败');
+                } else {
+                    $this->success('删除成功', 'admin/menu/index');
+                }
+            } else {
+                $this->error('该菜单下还有子菜单,不能删除');
+            }
+        }
+    }
+
+    public function orders()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            $i = 0;
+            foreach ($post['id'] as $k => $val) {
+                $order = Db::name('admin_menu')->where('id', $val)->value('orders');
+                if ($order != $post['orders'][$k]) {
+                    if (false == Db::name('admin_menu')->where('id', $val)->update(['orders' => $post['orders'][$k]])) {
+                        $this->error('更新失败');
+                    } else {
+                        $i++;
+                    }
+                }
+            }
+            $this->success('成功更新' . $i . '个数据', 'admin/menu/index');
+        }
+    }
+
+    /**
+     * 未添加的路由
+     * @return array
+     */
+    private function noInsertRoutes()
+    {
+        $allRoutes = [];
+        $controllers = getFilenameList(APP_PATH . 'admin' . DS . 'controller' . DS, '*.php');
+        foreach ($controllers as $controller) {
+            if ($controller != "Permissions") {
+                $actions = getActions("\app\admin\controller\\" . $controller, $base = '\app\admin\controller\base\Permissions');
+                foreach ($actions as $action) {
+                    $allRoutes[] = "admin-$controller-$action";
+                }
+            }
+        }
+
+        $allMenus = (new menuModel())->field("concat(module,'-',controller,'-',function) as route")->where('module', '<>', '')->select();
+        $allMenusLowCase = [];
+        foreach ($allMenus as $menu) {
+            $allMenusLowCase[] = strtolower($menu['route']);
+        }
+
+        $noInsertRoutes = [];
+        foreach ($allRoutes as $route) {
+            if (!in_array(strtolower($route), $allMenusLowCase)) {
+                $noInsertRoutes[] = explode('-', $route);
+            }
+        }
+        return $noInsertRoutes;
+    }
+
+    /**
+     * 批量添加
+     */
+    public function batchAdd()
+    {
+        $post = $this->request->param();
+        if (isset($post['name']) && is_array($post['name'])) {
+            $data = [];
+            foreach ($post['name'] as $key => $name) {
+                $data[] = [
+                    'name' => $name,
+                    'module' => $post['module'][$key],
+                    'controller' => $post['controller'][$key],
+                    'function' => $post['function'][$key],
+                    'is_display' => $post['is_display'][$key],
+                    'type' => $post['type'][$key],
+                    'pid' => $post['pid'][$key],
+                    'icon' => 'fa-tag',
+                ];
+            }
+            $model = new menuModel();
+            if (false == $model->allowField(true)->saveAll($data)) {
+                $this->error('添加菜单失败');
+            } else {
+                $this->success('添加菜单成功', 'admin/menu/index');
+            }
+        }
+        $this->error('当前没有路由可添加');
+    }
+}

+ 76 - 0
app/admin/controller/PointLog.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\PointLog as pointLogModel;
+use think\Db;
+
+class PointLog extends Permissions
+{
+
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+
+            $post = $this->request->param();
+
+            $where = [];
+            if (isset($post['user_id']) and !empty($post['user_id'])) {
+                $where['user_id'] = $post['user_id'];
+            }
+            if (isset($post['type']) and !empty($post['type'])) {
+                $where['type'] = $post['type'];
+            }
+            if (isset($post['create_time']) and !empty($post['create_time'])) {
+                $timerang = explode(' - ', $post['create_time']);
+                $min_time = strtotime($timerang[0]);
+                $max_time = strtotime($timerang[1]);
+                $where['create_time'] = [['>=', $min_time], ['<=', $max_time]];
+            }
+
+            $model = new pointLogModel();
+            $count = $model->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order('create_time desc')->select();
+
+            foreach ($data as $k => $v) {
+                $v['type_text'] = $v->type_text;
+                $data[$k] = $v;
+            }
+
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            $this->assign("types", pointLogModel::TYPES);
+            return $this->fetch();
+        }
+    }
+
+    public function deletes()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+
+            // 启动事务
+            Db::startTrans();
+            try {
+                $model = new pointLogModel();
+                if ($model->where('id', 'in', $ids)->delete()) {
+                }
+                // 提交事务
+                Db::commit();
+            } catch (\Exception $e) {
+                // 回滚事务
+                Db::rollback();
+                $this->error('删除失败');
+            }
+            $this->success('删除成功');
+        }
+    }
+}

+ 82 - 0
app/admin/controller/Smsconfig.php

@@ -0,0 +1,82 @@
+<?php
+// +----------------------------------------------------------------------
+// | Tplay [ WE ONLY DO WHAT IS NECESSARY ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017 http://tplay.pengyichen.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 听雨 < 389625819@qq.com >
+// +----------------------------------------------------------------------
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use think\Db;
+
+class Smsconfig extends Permissions
+{
+    public function index()
+    {
+        $data = Db::name('smsconfig')->where('sms', 'sms')->find();
+        $this->assign('data', $data);
+        $grouptabs = (new \app\common\model\ConfigTab())->order('sort desc')->select();
+        $this->assign('tabs', $grouptabs);
+        return $this->fetch();
+    }
+
+
+    public function publish()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            $validate = new \think\Validate([
+                ['appkey', 'require', 'AppKey不能为空'],
+                ['secretkey', 'require', 'SecretKey不能为空'],
+                ['name', 'require', '短信签名不能为空'],
+                ['code', 'require', '短信模板ID不能为空'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+
+            //$post['content'] = htmlentities($post['content']);
+            if (false == Db::name('smsconfig')->where('sms', 'sms')->update($post)) {
+                $this->error('提交失败');
+            } else {
+                $this->success('提交成功', 'admin/smsconfig/index');
+            }
+        } else {
+            $this->error('非法请求');
+        }
+    }
+
+
+    public function smsto()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            $validate = new \think\Validate([
+                ['phone', 'require|length:11,11|number', '手机号码不能为空|手机号码格式不正确|手机号码格式不正确'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+
+            $phone = (string)$post['phone'];
+
+            $param = '{"name":"Tplay用户"}';
+
+            $smsto = SendSms($param, $phone);
+
+            if (!empty($smsto)) {
+                $this->error('发送失败');
+            } else {
+                $this->success('短信发送成功');
+            }
+        } else {
+            return $this->fetch();
+        }
+    }
+}

+ 135 - 0
app/admin/controller/Templet.php

@@ -0,0 +1,135 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\Templet as tpModel;
+use app\common\service\UploadService;
+use file\FileHelper;
+use paginate\PaginateHelper;
+
+class Templet extends Permissions
+{
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $name = $this->request->param('name');
+            $data = tpModel::getTemplets(trim($name));
+            $total = count($data);
+            //分页
+            $helper = new PaginateHelper($total, $this->request->param('limit'), $this->request->param('page'));
+            $start = $helper->start() - 1;
+            $end = $helper->end() - 1;
+            foreach ($data as $k => $v) {
+                if ($k > $end || $k < $start)
+                    unset($data[$k]);
+            }
+            return array('code' => 0, 'data' => $data, 'count' => $total);
+        } else {
+            $this->assign('tpPath', tpModel::getTempletDir());
+            return $this->fetch();
+        }
+    }
+
+    public function publish()
+    {
+        $rootpath = tpModel::getTempletDir();
+        $post = $this->request->post();
+        $filename = trim($this->request->param('filename'));//文件名
+        $name = $this->request->param('name');//新增模板名
+
+        if ($filename) {
+            //修改操作
+            $filePath = $rootpath . $filename . ".html";
+            if ($this->request->isPost()) {
+                $getfilemtime = $this->request->param('filemtime');
+                if ($getfilemtime != filemtime($filePath)) {
+                    $this->error('文件版本不正确,请刷新后重新提交');
+                }
+                try {
+                    FileHelper::save($filePath, $post['content']);
+                } catch (\Exception $e) {
+                    $this->error('修改失败.' . $e->getMessage());
+                }
+                $this->success('修改成功', '', filemtime($filePath));
+            } else {
+                if (file_exists($filePath)) {
+                    $this->assign('filename', $filename);
+                    $this->assign('filemtime', filemtime($filePath));
+                    $this->assign('content', FileHelper::read($filePath));
+                    return $this->fetch();
+                } else {
+                    $this->error('模板格式不正确');
+                }
+            }
+        } else {
+            //新增操作
+            if ($this->request->isPost()) {
+                $validate = new \think\Validate([
+                    ['name|模板名称', 'require|alphaDash'],
+                ]);
+                if (!$validate->check($post)) {
+                    $this->error('提交失败:' . $validate->getError());
+                }
+                $filePath = $rootpath . $name . ".html";
+                if (file_exists($filePath)) {
+                    $this->error('该文件已存在,请修改名称');
+                } else {
+                    try {
+                        FileHelper::save($filePath, $post['content']);
+                    } catch (\Exception $e) {
+                        $this->error('添加失败.' . $e->getMessage());
+                    }
+                }
+                $this->success('添加成功', 'index');
+            } else {
+                return $this->fetch();
+            }
+        }
+    }
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $fullname = $this->request->param('fullname');
+            $rootpath = tpModel::getTempletDir();
+
+            if (false == unlink($rootpath . $fullname)) {
+                $this->error('删除失败');
+            } else {
+                $this->success('删除成功', 'index');
+            }
+        }
+    }
+
+    public function rename()
+    {
+        if ($this->request->isAjax()) {
+            $oldname = $this->request->param('oldname');
+            $newname = $this->request->param('newname');
+            $rootpath = tpModel::getTempletDir();
+            if (file_exists($rootpath . $newname)) {
+                $this->error('文件已存在');
+            }
+            if (FileHelper::rename($rootpath . $oldname, $rootpath . $newname)) {
+                (new \app\common\model\Catalog())->save(['catalog_templet' => $newname], ['catalog_templet' => $oldname]);
+                (new \app\common\model\Catalog())->save(['article_templet' => $newname], ['article_templet' => $oldname]);
+                $this->success('重命名成功', 'index');
+            } else {
+                $this->error('重命名失败');
+            }
+        }
+    }
+
+    public function upload()
+    {
+        $rootpath = tpModel::getTempletDir();
+        (new UploadService())->upload2path($rootpath);
+    }
+}

+ 206 - 0
app/admin/controller/Tomessages.php

@@ -0,0 +1,206 @@
+<?php
+// +----------------------------------------------------------------------
+// | Tplay [ WE ONLY DO WHAT IS NECESSARY ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017 http://tplay.pengyichen.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 听雨 < 389625819@qq.com >
+// +----------------------------------------------------------------------
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\Messages;
+use think\Db;
+use think\Log;
+
+class Tomessages extends Permissions
+{
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+
+            $post = $this->request->param();
+            $where = [];
+            if (isset($post['id']) and $post['id'] != '') {
+                $where['id'] = $post['id'];
+            }
+            if (isset($post['from_user_id']) and $post['from_user_id'] != '') {
+                $where['from_user_id'] = $post['from_user_id'];
+            }
+            if (isset($post['to_user_id']) and $post['to_user_id'] != '') {
+                $where['to_user_id'] = $post['to_user_id'];
+            }
+            if (isset($post['keywords']) and !empty($post['keywords'])) {
+                $where['message'] = ['like', '%' . $post['keywords'] . '%'];
+            }
+            if (isset($post['ip']) and $post['ip'] != '') {
+                $where['ip'] = $post['ip'];
+            }
+            if (isset($post['is_look']) and ($post['is_look'] == 1 or $post['is_look'] === '0')) {
+                $where['is_look'] = $post['is_look'];
+            }
+            if (isset($post['status']) and in_array($post['status'], ["0", "1", "-1"], true)) {
+                $where['status'] = $post['status'];
+            }
+            if (isset($post['create_time']) and !empty($post['create_time'])) {
+                $timerang = explode(' - ', $post['create_time']);
+                $min_time = strtotime($timerang[0]);
+                $max_time = strtotime($timerang[1]);
+                $where['create_time'] = [['>=', $min_time], ['<=', $max_time]];
+            }
+
+            $model = new Messages();
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order('create_time desc')->select();
+
+            foreach ($data as $k => $v) {
+                $v['from_user'] = $v->fromUser->nickname??($v->from_user_id ?: "系统");
+                $v['to_user'] = $v->toUser->nickname??($v->to_user_id ?: '');
+                $v['status_text'] = $v->status_text;
+                $data[$k] = $v;
+            }
+
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            return $this->fetch();
+        }
+    }
+
+
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['from_user_id|发送者id', 'number'],
+                ['to_user_id|接收者id', 'number'],
+                ['message', 'require', '消息不能为空'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        }
+
+        $model = new Messages();
+        if ($id > 0) {
+            //是修改操作
+            $link = $model->where('id', $id)->find();
+            if (empty($link)) {
+                $this->error('记录id不存在');
+            }
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    $this->success('修改成功', 'index');
+                }
+            } else {
+                $this->assign('link', $link);
+                return $this->fetch();
+            }
+        } else {
+            //是新增操作
+            if ($this->request->isPost()) {
+                $post['ip'] = $this->request->ip();
+                $post['status'] = Messages::STATUS_PASS;
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('失败');
+                } else {
+                    $this->success('发送成功,请查看消息管理', 'index');
+                }
+            } else {
+                return $this->fetch();
+            }
+        }
+    }
+
+    //批量回复
+    public function sysmessage()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+            $model = new Messages();
+            $list = $model->where('id', 'in', $ids)->select();
+            foreach ($list as $item) {
+                $model = new Messages();
+                $data = [
+                    'from_user_id' => 0,
+                    'to_user_id' => $item->from_user_id,
+                    'to_msg_id' => $item->id,
+                    'ip' => $this->request->ip(),
+                    'message' => $post['message'],
+                    'status' => Messages::STATUS_PASS,
+                ];
+                if (false == $model->allowField(true)->save($data)) {
+                    Log::log("回复失败:" . $item->from_user_id);
+                }
+            }
+            $this->success('回复成功');
+        }
+    }
+
+    public function look()
+    {
+        if ($this->request->isPost()) {
+            $id = $this->request->param('id', 0, 'intval');
+            $model = new Messages();
+            if ($id > 0) {
+                $post = $this->request->post();
+                //验证
+                $message = $model->where('id', $id)->find();
+                if (empty($message)) {
+                    $this->error('id不正确');
+                }
+                $post['is_look'] = $post['status'];
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('提交失败');
+                } else {
+                    $this->success('提交成功', 'admin/tomessages/index');
+                }
+            }
+        }
+    }
+
+    public function status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            if (false == Db::name('messages')->where('id', $post['id'])->update(['status' => $post['status']])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            if (false == Db::name('messages')->where('id', $id)->delete()) {
+                $this->error('删除失败');
+            } else {
+                $this->success('删除成功', 'index');
+            }
+        }
+    }
+
+    public function deletes()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+            $model = new Messages();
+            if ($model->where('id', 'in', $ids)->delete()) {
+                $this->success('删除成功');
+            }
+        }
+    }
+}

+ 116 - 0
app/admin/controller/Urlsconfig.php

@@ -0,0 +1,116 @@
+<?php
+// +----------------------------------------------------------------------
+// | Tplay [ WE ONLY DO WHAT IS NECESSARY ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017 http://tplay.pengyichen.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 听雨 < 389625819@qq.com >
+// +----------------------------------------------------------------------
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\admin\model\Urlconfig;
+use think\Cache;
+use think\Db;
+
+class Urlsconfig extends Permissions
+{
+    public function index()
+    {
+        $model = new Urlconfig();
+        $urlconfig = $model->order('create_time desc')->paginate(20);
+        $this->assign('urlconfig', $urlconfig);
+        return $this->fetch();
+    }
+
+
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = new Urlconfig();
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+            $validate = new \think\Validate([
+                ['url|需要美化的url', 'require'],
+                ['aliases|美化后的url', 'require|regex:^[a-zA-Z_]+\S*$'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        }
+        if ($id > 0) {
+            //是修改操作
+            if ($this->request->isPost()) {
+                $urlconfig = $model->where('id', $id)->find();
+                if (empty($urlconfig)) {
+                    $this->error('id不正确');
+                }
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败');
+                } else {
+                    Cache::clear();
+                    $this->success('修改成功,并清理缓存', 'admin/urlsconfig/index');
+                }
+            } else {
+                $urlconfig = $model->where('id', $id)->find();
+                if (!empty($urlconfig)) {
+                    $this->assign('urlconfig', $urlconfig);
+                    return $this->fetch();
+                } else {
+                    $this->error('id不正确');
+                }
+            }
+        } else {
+            //是新增操作
+            if ($this->request->isPost()) {
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加失败');
+                } else {
+                    Cache::clear();
+                    $this->success('添加成功,并清理缓存', 'admin/urlsconfig/index');
+                }
+            } else {
+                return $this->fetch();
+            }
+        }
+    }
+
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            if (false == Db::name('urlconfig')->where('id', $id)->delete()) {
+                $this->error('删除失败');
+            } else {
+                Cache::clear();
+                $this->success('删除成功,并清理缓存', 'admin/urlsconfig/index');
+            }
+        }
+    }
+
+
+    public function status()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        if ($id > 0) {
+            if ($this->request->isPost()) {
+                //是提交操作
+                $post = $this->request->post();
+                $status = $post['status'];
+                if (false == Db::name('urlconfig')->where('id', $id)->update(['status' => $status])) {
+                    $this->error('设置失败');
+                } else {
+                    Cache::clear();
+                    $this->success('设置成功,并清理缓存', 'admin/urlsconfig/index');
+                }
+            }
+        } else {
+            $this->error('id不正确');
+        }
+    }
+}

+ 234 - 0
app/admin/controller/User.php

@@ -0,0 +1,234 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\common\model\PointLog as pointLogModel;
+use app\common\model\User as userModel;
+use think\Db;
+
+class User extends Permissions
+{
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [];
+            if (isset($post['ids']) and !empty($post['ids'])) {
+                $where['id'] = ['in', $post['ids']];
+            }
+            if (isset($post['pid']) and !empty($post['pid'])) {
+                $where['pid'] = intval($post['pid']);
+            }
+            if (isset($post["level"]) and "" != $post["level"]) {
+                $where["level"] = $post["level"];
+            }
+            if (isset($post['nickname']) and !empty($post['nickname'])) {
+                $where['nickname'] = ['like', '%' . $post['nickname'] . '%'];
+            }
+            if (isset($post['user_type']) and "" != $post['user_type']) {
+                $where['user_type'] = $post['user_type'];
+            }
+            if (isset($post['user_cate']) and "" != $post['user_cate']) {
+                $where['user_cate'] = $post['user_cate'];
+            }
+            if (isset($post['ip']) and !empty($post['ip'])) {
+                $where['ip'] = $post['ip'];
+            }
+            if (isset($post['create_time']) and !empty($post['create_time'])) {
+                $timerang = explode(' - ', $post['create_time']);
+                $min_time = strtotime($timerang[0]);
+                $max_time = strtotime($timerang[1]);
+                $where['create_time'] = [['>=', $min_time], ['<=', $max_time]];
+            }
+
+            $model = new userModel();
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order('id desc')->select();
+
+            foreach ($data as $k => $v) {
+                $v['user_cate_text'] = $v->user_cate_text;
+                $v['user_type_text'] = $v->user_type_text;
+                $v['invite_code'] = $v->invite_code;
+                $v['level_text'] = $v->level_text;
+                $v['status_text'] = $v->status_text;
+                $v['parent_name'] = $v->parent ? $v->parent->nickname : '';
+                $v['child_count'] = $v->child_count;
+                $v['stop_time_text'] = $v->stop_time_text;
+                $data[$k] = $v;
+            }
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            $this->assign('user_cates', userModel::USER_CATES);
+            $this->assign('user_types', userModel::USER_TYPES);
+            $this->assign('vips', \app\common\model\User::GRADE);
+            return $this->fetch();
+        }
+    }
+
+    public function publish()
+    {
+        $id = $this->request->has('id') ? $this->request->param('id', 0, 'intval') : 0;
+        $model = new userModel();
+
+        $post = $this->request->post();
+        if (!$this->request->isPost()) {
+            $this->assign('user_cates', userModel::USER_CATES);
+            $this->assign('user_types', userModel::USER_TYPES);
+        } else {
+            $validate = new \think\Validate([
+                ['passport', 'require', '账号不能为空'],
+                ['passport', 'max:50', '账号长度要小于50'],
+                ['passport', 'unique:user', '该账号已经存在'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        }
+
+        if ($id > 0) {
+            //修改操作
+            if ($this->request->isPost()) {
+                $user = $model->where('id', $id)->find();
+                if (empty($user)) {
+                    $this->error('id不正确');
+                }
+                unset($post['passport']);
+                if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                    $this->error('修改失败' . $model->getError());
+                } else {
+                    $this->success('修改成功', 'index');
+                }
+            } else {
+                $user = $model->where('id', $id)->find();
+                if (!empty($user)) {
+                    $this->assign('user', $user);
+                    return $this->fetch();
+                } else {
+                    $this->error('id不正确');
+                }
+            }
+        } else {
+            //新增操作
+            if ($this->request->isPost()) {
+                $post = $this->request->post();
+                $post['ip'] = $this->request->ip();
+                $post['create_time'] = time();
+                $post['login_time'] = time();
+                if (false == $model->allowField(true)->save($post)) {
+                    $this->error('添加失败');
+                } else {
+                    $this->success('添加成功', 'index');
+                }
+            } else {
+                return $this->fetch();
+            }
+        }
+    }
+
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->has('id') ? $this->request->param('id', 0, 'intval') : 0;
+            if (false == Db::name('user')->where('id', $id)->delete()) {
+                $this->error('删除失败');
+            } else {
+                $this->success('删除成功', 'index');
+            }
+        }
+    }
+
+    public function status()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            if (false == Db::name('user')->where('id', $post['id'])->update(['status' => $post['status']])) {
+                $this->error('设置失败');
+            } else {
+                $this->success('设置成功', 'index');
+            }
+        }
+    }
+
+    public function setPoint()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+            $value = abs($this->request->param('value', 0, 'intval'));
+            if (empty($value)) {
+                $this->error("请输入积分数量");
+            }
+            $type = $post['type']??pointLogModel::TYPE_ADMIN_UPDATE;
+            $action = $post['action']??1;
+            $model = new userModel();
+            $users = $model->where('id', 'in', $ids)->select();
+            // 启动事务
+            Db::startTrans();
+            try {
+                foreach ($users as $user) {
+                    $pointLog = new pointLogModel();
+                    $final = $action == 1 ? $user->point + $value : $user->point - $value;
+                    $log = [
+                        "user_id" => $user->id,
+                        "before" => $user->point,
+                        "symbol" => $action == 1 ? '+' : '-',
+                        "change" => $value,
+                        "final" => $final,
+                        "type" => $type,
+                        "remark" => "管理员" . session(self::ADMIN_ID) . ':' . session(self::ADMIN_NAME) . "操作",
+                    ];
+                    if (!$pointLog->save($log)) {
+                        throw new \Exception("insert point_log fail");
+                    }
+                    $user->point = $final;
+                    $user->save();
+                }
+                // 提交事务
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+                $this->error("修改失败" . $e->getMessage());
+            }
+            $this->success('修改成功');
+        }
+    }
+
+    public function setVips()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+            $vip_id = $this->request->param("vip", 0);
+            $model = new userModel();
+            $users = $model->where('id', 'in', $ids)->select();
+            /** @var userModel $user */
+            foreach ($users as $user) {
+                $user->save([
+                    'level' => $vip_id,
+                    'stop_time' => $vip_id ? $user->createStopTime($vip_id) : 0
+                ]);
+            }
+            $this->success('修改成功');
+        }
+    }
+
+    //重置密码
+    public function resetpass()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            if (false == Db::name('user')->where('id', $id)->update(['password' => password(123456)])) {
+                $this->error('重置失败');
+            } else {
+                $this->success('重置成功', 'index');
+            }
+        }
+    }
+}

+ 69 - 0
app/admin/controller/Webconfig.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use app\admin\model\AdminLog;
+use app\admin\model\Urlconfig;
+use think\Cache;
+use think\Db;
+
+class Webconfig extends Permissions
+{
+    public function index()
+    {
+        $this->assign('web_config', Db::name('webconfig')->where('id', 1)->find());
+        $this->assign('is_close_site_key', (new Urlconfig())->getCloseSiteKey());
+        $this->assign('admin_log_num', (new AdminLog())->count());
+        $this->assign('backend_pass', (new Urlconfig())->getBackendPass());
+        $this->assign('default_static_path', \app\common\model\Templet::DEFAULT_STATIC_PATH);
+        $this->assign('default_templet_path', \app\common\model\Templet::DEFAULT_TEMPLET_PATH);
+        $this->assign('default_backup_path', Databackup::DEFAULT_PATH);
+        $grouptabs = (new \app\common\model\ConfigTab())->order('sort desc')->select();
+        $this->assign('tabs', $grouptabs);
+        return $this->fetch();
+    }
+
+    public function publish()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            $validate = new \think\Validate([
+                ['file_type', 'requireWith:name', '上传类型不能为空'],
+                ['file_size', 'requireWith:name', '上传大小不能为空'],
+                ['host', 'url'],
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+            if (isset($post['article_ftp_config']) && !empty($post['article_ftp_config'])) {
+                $json = json_decode($post['article_ftp_config'], true);
+                if (!is_array($json)) {
+                    $this->error('ftp配置格式错误');
+                } else {
+                    $post['article_ftp_config'] = json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
+                }
+            }
+            if (!isset($post['is_log'])) {
+                $post['is_log'] = 0;
+            }
+            if (!isset($post['is_close_site'])) {
+                $post['is_close_site'] = 0;
+            }
+            $model = (new \app\common\model\Webconfig())->where('id', 1)->find();
+            if (false == $model->allowField(true)->save($post)) {
+                $this->error('提交失败');
+            } else {
+                Cache::clear();
+                $this->success('提交成功', 'admin/webconfig/index');
+            }
+        }
+    }
+}

+ 98 - 0
app/admin/controller/base/Permissions.php

@@ -0,0 +1,98 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+namespace app\admin\controller\base;
+
+use app\admin\model\Urlconfig;
+use app\common\behavior\AdminLogBehavior;
+use app\common\service\WebService;
+use think\Controller;
+use think\Db;
+use think\Hook;
+use think\Session;
+
+class Permissions extends Controller
+{
+    const ADMIN_ID = 'admin';
+    const ADMIN_NAME = 'admin_name';
+    const ADMIN_CATE_ID = 'admin_cate_id';
+
+    protected function _initialize()
+    {
+        (new WebService())->checkInstalled();
+
+        Hook::listen('admin_log');
+
+        if ($this->request->isCli()) {
+            return;
+        }
+
+        //检查ip黑名单
+        $black_ip = \app\common\model\Webconfig::getValue('black_ip', 3600);
+        if (!empty($black_ip)) {
+            $black_ip = explode(',', $black_ip);
+            $ip = $this->request->ip();
+            if (in_array($ip, $black_ip)) {
+                //退出登录
+                if (Session::has(self::ADMIN_ID)) {
+                    Session::delete(self::ADMIN_ID);
+                }
+                $this->error('你已被封禁!', 'admin/common/login');
+            }
+        }
+
+        //检查是否登录
+        if (!Session::has(self::ADMIN_ID)) {
+            if ((new Urlconfig())->isWeekBackend()) {
+                $this->redirect('admin/common/login');
+            } else {
+                abort(404, '404 not found');
+            }
+        }
+
+        //检查访问的url是否再用户的权限范围内,mysql查询自动忽略了大小写
+        $where['module'] = $this->request->module();
+        $where['controller'] = $this->request->controller();
+        $where['function'] = $this->request->action();
+        $where['type'] = 1;//权限节点
+
+        //用户的权限菜单id
+        $menus = Db::name('admin_cate')->where('id', Session::get(self::ADMIN_CATE_ID))->value('permissions');
+        $menus = explode(',', $menus);
+
+        $string = $this->request->query();
+        $param_menu = Db::name('admin_menu')->where($where)->where('parameter', $string)->find();
+        if ($param_menu) {
+            if (false == in_array($param_menu['id'], $menus)) {
+                (new AdminLogBehavior())->updateLastLog("缺少权限");
+                $this->error('缺少权限');
+            }
+        } else if ($string) {
+            $menu = Db::name('admin_menu')->where($where)->where('parameter', '')->find();
+            if ($menu && !in_array($menu['id'], $menus)) {
+                (new AdminLogBehavior())->updateLastLog("缺少权限");
+                $this->error('缺少权限');
+            }
+        }
+        if ($this->request->has('check_permission')) {
+            $this->success('has permission');
+        }
+    }
+
+    /**
+     * 查询当前用户权限
+     * @return array|mixed 菜单id数组
+     */
+    public function getPermission()
+    {
+        //用户的权限菜单id
+        $menus = Db::name('admin_cate')->where('id', Session::get(self::ADMIN_CATE_ID))->value('permissions');
+        $menus = explode(',', $menus);
+        return $menus;
+    }
+}

+ 44 - 0
app/admin/model/Admin.php

@@ -0,0 +1,44 @@
+<?php
+// +----------------------------------------------------------------------
+// | Tplay [ WE ONLY DO WHAT IS NECESSARY ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017 http://tplay.pengyichen.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 听雨 < 389625819@qq.com >
+// +----------------------------------------------------------------------
+
+
+namespace app\admin\model;
+
+use think\Model;
+
+class Admin extends Model
+{
+    const SUPER_ADMIN_ID = 1;//默认超级管理员id
+
+	public function admincate()
+    {
+        //关联角色表
+        return $this->belongsTo('AdminCate');
+    }
+
+    public function article()
+    {
+        //关联文章表
+        return $this->hasOne('Article');
+    }
+
+    public function log()
+    {
+        //关联日志表
+        return $this->hasOne('AdminLog');
+    }
+
+    public function attachment()
+    {
+        //关联附件表
+        return $this->hasOne('Attachment');
+    }
+}

+ 24 - 0
app/admin/model/AdminCate.php

@@ -0,0 +1,24 @@
+<?php
+// +----------------------------------------------------------------------
+// | Tplay [ WE ONLY DO WHAT IS NECESSARY ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017 http://tplay.pengyichen.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 听雨 < 389625819@qq.com >
+// +----------------------------------------------------------------------
+
+
+namespace app\admin\model;
+
+use think\Model;
+
+class AdminCate extends Model
+{
+    public function admin()
+    {
+        //关联管理员表
+        return $this->hasOne('Admin');
+    }
+}

+ 31 - 0
app/admin/model/AdminLog.php

@@ -0,0 +1,31 @@
+<?php
+// +----------------------------------------------------------------------
+// | Tplay [ WE ONLY DO WHAT IS NECESSARY ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017 http://tplay.pengyichen.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 听雨 < 389625819@qq.com >
+// +----------------------------------------------------------------------
+
+
+namespace app\admin\model;
+
+use think\Model;
+
+class AdminLog extends Model
+{
+    // 关闭自动写入update_time字段
+    protected $updateTime = false;
+
+	public function admin()
+    {
+        return $this->belongsTo('Admin');
+    }
+
+    public function menu()
+    {
+        return $this->belongsTo('AdminMenu');
+    }
+}

+ 37 - 0
app/admin/model/AdminMenu.php

@@ -0,0 +1,37 @@
+<?php
+// +----------------------------------------------------------------------
+// | Tplay [ WE ONLY DO WHAT IS NECESSARY ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017 http://tplay.pengyichen.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 听雨 < 389625819@qq.com >
+// +----------------------------------------------------------------------
+
+
+namespace app\admin\model;
+
+use think\Model;
+
+class AdminMenu extends Model
+{
+    public function menulist($cate, $id = 0, $level = 0)
+    {
+        static $cates = array();
+        foreach ($cate as $value) {
+            if ($value['pid'] == $id) {
+                $value['level'] = $level + 1;
+                $value['str'] = $level == 0 ? "" : str_repeat('&emsp;&emsp;', $level) . '└ ';
+                $cates[] = $value;
+                $this->menulist($cate, $value['id'], $value['level']);
+            }
+        }
+        return $cates;
+    }
+
+    public function log()
+    {
+        return $this->hasOne('AdminLog');
+    }
+}

+ 53 - 0
app/admin/model/Urlconfig.php

@@ -0,0 +1,53 @@
+<?php
+
+
+namespace app\admin\model;
+
+use \think\Model;
+
+class Urlconfig extends Model
+{
+    const STATUS_OPEN = 1;
+
+    /**
+     * 获取登录地址(美化后的)
+     * @return string
+     */
+    public function getLoginUrl()
+    {
+        return url('admin/common/login', '', true, true);
+    }
+
+    /**
+     * 是否了开启安全入口
+     */
+    public function isWeekBackend()
+    {
+        $has = $this->where('url', 'admin/common/login')->where('aliases', 'admin_login')->count();
+        return $has > 0;
+    }
+
+    /**
+     * 获取安全入口key
+     * @return mixed|string
+     */
+    public function getBackendPass()
+    {
+        $backendurl = url('admin/common/login', '', false);//有可能被缓存影响
+        if ($backendurl == "/admin_login" || $backendurl == "/admin/common/login") {
+            return "";
+        } else {
+            return str_replace('/admin_login/', '', $backendurl);
+        }
+    }
+
+    /**
+     * 获取维护入口key
+     * @return mixed|string
+     */
+    public function getCloseSiteKey()
+    {
+        $pass = $this->getBackendPass();
+        return empty($pass) ? "1464674022" : $pass;
+    }
+}

+ 12 - 0
app/admin/tags.php

@@ -0,0 +1,12 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 中闽 < 1464674022@qq.com >
+ * Date: 2019/12/5
+ * Time: 17:44
+ */
+
+// 应用行为扩展定义文件
+return [
+    'admin_log' => ['app\\common\\behavior\\AdminLogBehavior'],
+];

+ 171 - 0
app/admin/view/address/index.html

@@ -0,0 +1,171 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="/static/public/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="/static/public/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="/static/admin/css/admin.css" media="all">
+    <style type="text/css">
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+        .tooltip > img{
+            width: 20px;
+            height: 20px;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+        <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">列表</li>
+            <li><a href="{:url('publish')}" class="a_menu">新增</a></li>
+        </ul>
+    </div>
+    
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+                        <button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="deletes">批量删除</button>
+                    </div>
+    </script>
+
+    <form class="layui-form serch" action="index" method="post">
+        <div class="layui-form-item" style="float: left;">
+
+            <div class="layui-input-inline">
+                                <input type="text" name="ids" autocomplete="off" placeholder="请输入ID,多个id逗号分隔" class="layui-input layui-btn-sm">
+                            </div><div class="layui-input-inline">
+                            <input type="text" name="title" autocomplete="off" placeholder="标题(模糊搜索)" class="layui-input layui-btn-sm">
+                        </div><div class="layui-input-inline">
+                            <input type="text" name="address" autocomplete="off" placeholder="地址(模糊搜索)" class="layui-input layui-btn-sm">
+                        </div>
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+
+        <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+                        <button class="layui-btn layui-btn-xs a_menu" lay-event="edit"><i class="layui-icon" style="margin-right: 0;"></i></button>
+                        <button class="layui-btn layui-btn-xs delete" lay-event="del"><i class="layui-icon" style="margin-right: 0;"></i></button>
+                    </div>
+    </script>
+    
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+        layui.use(['table', 'layer', 'form','laydate'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            var laydate = layui.laydate;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                                , toolbar: '#toolbarDemo'
+                                , limit: 15
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: "{:url('index')}" //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {type: 'checkbox'},
+
+                                                {field: 'id', title: 'ID', width: 60},
+                                                        {field: "title", title: '标题'},
+                                                        {field: "address", title: '地址'},
+                                                    {field: 'action', title: '操作', toolbar: '#barDemo', fixed: 'right'}
+                                    ]],
+                done: function () {
+                    if (isExitsFunction('showThumb')) {
+                        showThumb()
+                    }
+                                    }
+            });
+
+            
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+                        table.on('tool(table)', function (obj) {
+                                if (obj.event == 'edit') {
+                    window.parent.tab.tabAdd({
+                        icon: "fa-bookmark",
+                        id: "tplay_address" + obj.data.id,
+                        title: obj.data.title == null ? "地址" + obj.data.id : obj.data.title,
+                        url: "/admin/address/publish?id=" + obj.data.id
+                    });
+                }
+                                else if (obj.event == 'del') {
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            dataType: 'json',
+                            data: {id: obj.data.id},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }
+                            });
+                        //监听事件
+            table.on('toolbar(table)', function (obj) {
+                if (obj.event == 'deletes') {
+                    var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                    var data = checkStatus.data;
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            ids[key] = item.id;
+                        })
+                        layer.confirm('是否删除?', function (index, layero) {
+                            $.ajax({
+                                url: "{:url('deletes')}",
+                                dataType: 'json',
+                                data: {"ids": ids},
+                                type: 'post',
+                                success: function (res) {
+                                    layer.msg(res.msg);
+                                    if (res.code == 1) {
+                                        table.reload('table');
+                                    }
+                                }
+                            })
+                            layer.close(index)
+                        });
+                    } else {
+                        layer.msg('请先勾选需要操作的记录');
+                    }
+                }
+            });
+                    });
+    </script>
+
+    
+</div>
+</body>
+</html>

+ 116 - 0
app/admin/view/address/publish.html

@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>地址编辑</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="/static/public/layui/css/layui.css"  media="all">
+    <link rel="stylesheet" href="/static/public/font-awesome/css/font-awesome.min.css" media="all" />
+    <link rel="stylesheet" href="/static/admin/css/admin.css"  media="all">
+    <script src="/static/public/layui/layui.js"></script>
+    <script src="/static/public/jquery/jquery.min.js"></script>
+</head>
+<style>
+  .layui-upload-img{
+    cursor: pointer;
+    width:150px;
+    height:150px;
+    background: url('/static/public/images/uploadimg.jpg');
+    background-size:contain;
+    border-radius: 2px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #e6e6e6;
+  }
+</style>
+<body style="padding:10px;">
+  <div class="tplay-body-div">
+
+    {empty name="$data"}
+    <div class="layui-tab">
+      <ul class="layui-tab-title">
+        <li><a href="index" class="a_menu">列表</a></li>
+        <li class="layui-this">新增</li>
+      </ul>
+    </div>
+    {/empty}
+
+    <div style="margin-top: 20px;"></div>
+    <form class="layui-form" id="publish" method="post">
+
+                      <!-- 输入框 -->
+              <div class="layui-form-item">
+                <label class="layui-form-label">标题</label>
+                <div class="layui-input-inline" style="max-width:300px;">
+                  <input name="title"  autocomplete="off" placeholder="请输入" class="layui-input" type="text" {notempty name="$data"}value="{$data.title}"{/notempty}>
+                </div>
+                                </div>
+                          <!-- 输入框 -->
+              <div class="layui-form-item">
+                <label class="layui-form-label">地址</label>
+                <div class="layui-input-inline" style="max-width:300px;">
+                  <input name="address"  autocomplete="off" placeholder="请输入" class="layui-input" type="text" {notempty name="$data"}value="{$data.address}"{/notempty}>
+                </div>
+                                </div>
+            
+
+        
+        {notempty name="$data"}
+          <input type="hidden" name="id" value="{$data.id}">
+        {/notempty}
+       
+      <div class="layui-form-item">
+        <div class="layui-input-block">
+          <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+          <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+        </div>
+      </div>
+    </form>
+
+    <script>
+        layui.use(['layer', 'form' ,'laydate'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            var laydate = layui.laydate;
+
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('publish')}",
+                        data: $('#publish').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                if (res.msg == "添加成功") {
+                                    layer.alert(res.msg, function (index) {
+                                        location.href = res.url;
+                                    })
+                                }else{
+                                    layer.confirm(res.msg, {
+                                        btn: ['关闭', '继续编辑']
+                                    }, function (index) {
+                                        window.parent.tab.close('tplay_address{$data.id|default=0}');
+                                    }, function (index, layero) {
+                                        location.href = res.url;
+                                    });
+                                }
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+
+                
+            });
+        });
+    </script>
+  </div>
+</body>
+</html>

+ 90 - 0
app/admin/view/admin/edit_password.html

@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('personal')}" class="a_menu">个人信息</a></li>
+            <li class="layui-this">修改密码</li>
+        </ul>
+    </div>
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">原密码</label>
+            <div class="layui-input-inline">
+                <input name="password_old" lay-verify="pass" placeholder="请填写6到12位密码" autocomplete="off" class="layui-input"
+                       type="password">
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">新密码</label>
+            <div class="layui-input-inline">
+                <input name="password" lay-verify="pass" placeholder="请填写6到12位密码" autocomplete="off" class="layui-input"
+                       type="password">
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">确认密码</label>
+            <div class="layui-input-inline">
+                <input name="password_confirm" lay-verify="pass" placeholder="请再次输入新密码" autocomplete="off"
+                       class="layui-input" type="password">
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+            </div>
+        </div>
+
+    </form>
+
+    {include file="public/foot"}
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/admin/editPassword')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 193 - 0
app/admin/view/admin/index.html

@@ -0,0 +1,193 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">管理员列表</li>
+            <li><a href="{:url('admin/admin/publish')}" class="a_menu">新增</a></li>
+        </ul>
+    </div>
+
+    <form class="layui-form serch" action="{:url('index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+            <div class="layui-input-inline">
+                <input type="text" name="keywords" lay-verify="title" autocomplete="off" placeholder="请输入昵称"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <select name="admin_cate_id" lay-search="">
+                        <option value="">角色</option>
+                        {volist name="$cate" id="vo"}
+                        <option value="{$vo.id}">{$vo.name}</option>
+                        {/volist}
+                    </select>
+                </div>
+            </div>
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <div class="layui-input-inline">
+                        <input type="text" class="layui-input" id="date_time" autocomplete="off" placeholder="创建时间" name="create_time">
+                    </div>
+                </div>
+            </div>
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="edit" title="编辑"><i class="layui-icon"
+                                                                                         style="margin-right: 0;"></i>
+            </button>
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="resetpass" title="重置密码"><i class="layui-icon"
+                                                                                                style="margin-right: 0;"></i>
+            </button>
+            <button class="layui-btn layui-btn-xs delete" lay-event="del" title="删除"><i class="layui-icon"
+                                                                                        style="margin-right: 0;"></i>
+            </button>
+        </div>
+    </script>
+
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+//                , toolbar: '#toolbarDemo'
+//                , defaultToolbar: []
+                , limit: 20
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: '{:url("index")}' //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {field: 'id', title: 'ID', width: 80},
+                    {
+                        field: 'head_pic', title: '头像', width: 80, align: 'center', templet: function (row) {
+                        return row.head_pic == "" || row.head_pic == null ? "" : '<a href="' + row.head_pic + '" class="tooltip"><img src="' + row.head_pic + '" width="20" height="20"></a>';
+                    }
+                    },
+                    {field: 'nickname', title: '昵称', minWidth: 160},
+                    {field: 'name', title: '账号', minWidth: 150},
+                    {field: 'cate_name', title: '角色', minWidth: 100},
+                    {field: 'create_time', title: '创建时间', width: 200},
+                    {field: 'login_time', title: '最后登录时间', width: 200},
+                    {field: 'login_ip', title: '最后登录IP', width: 150},
+                    {field: 'action', title: '操作', toolbar: '#barDemo', width: 120, fixed: 'right'}
+                ]],
+                done: function () {
+                    showThumb();
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            //监听事件
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'edit') {
+                    location.href = "{:url('publish')}?id=" + obj.data.id;
+                }
+                else if (obj.event == 'del') {
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            data: {id: obj.data.id},
+                            dataType: 'json',
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    setTimeout(function () {
+                                        location.href = res.url;
+                                    }, 1500)
+                                }
+                            }
+                        })
+                    })
+                }
+                else if (obj.event == 'resetpass') {
+                    var id = obj.data.id;
+                    layer.confirm('将密码重置为123456', function (index) {
+                        $.ajax({
+                            url: "{:url('resetpass')}",
+                            data: {id: id},
+                            dataType: 'json',
+                            success: function (res) {
+                                layer.msg(res.msg);
+                            }
+                        })
+                    })
+                }
+            });
+
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                var data = checkStatus.data;
+                if (obj.event == 'deletes') {
+                    layer.confirm('确定要删除?', function (index, layero) {
+                        $.ajax({
+                            url: "{:url('clearLog')}",
+                            dataType: 'json',
+                            data: {},
+                            type: 'post',
+                            async: false,
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                        layer.close(index)
+                    });
+                }
+            });
+
+        });
+    </script>
+
+</div>
+</body>
+</html>

+ 121 - 0
app/admin/view/admin/personal.html

@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<style>
+    .layui-upload-img{
+        cursor: pointer;
+        width:150px;
+        height:150px;
+        background: url('/static/public/images/uploadimg.jpg');
+        background-size:contain;
+        border-radius: 2px;
+        border-width: 1px;
+        border-style: solid;
+        border-color: #e6e6e6;
+    }
+</style>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">个人信息</li>
+            <li><a href="{:url('editPassword')}" class="a_menu">修改密码</a></li>
+        </ul>
+    </div>
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">账号</label>
+            <div class="layui-input-inline">
+                <input name="nickname" lay-verify="required" placeholder="请输入" autocomplete="off" class="layui-input"
+                       type="text" {notempty name="$info.nickname" }value="{$info.nickname}" {/notempty}>
+            </div>
+            <div class="layui-form-mid layui-word-aux">({$info.admincate.name})</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">头像</label>
+            <div class="layui-input-inline" style="width: 152px">
+                <img class="layui-upload-img" id="upload_img" {notempty name="$info"}src='{:geturl($info.thumb,'/static/public/images/tx.jpg')}'{/notempty}>
+                <input type="hidden" name="thumb" id="upload_value" value='{notempty name="$info.thumb"}{$info.thumb}{/notempty}'>
+            </div>
+            <div class="layui-form-mid layui-word-aux" style="padding-top:124px !important;">(点击图片可修改头像)</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">登录IP</label>
+            <div class="layui-form-mid layui-word-aux">{$info.login_ip}</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">登录时间</label>
+            <div class="layui-form-mid layui-word-aux">{$info.login_time|date="Y-m-d H:i:s",###}</div>
+        </div>
+
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+            </div>
+        </div>
+    </form>
+
+    {include file="public/foot"}
+    <script>
+        $(function () {
+            $('#upload_img').click(function () {
+                layer.open({
+                    type: 2,
+                    title: '选择图片',
+                    area: ['570px', '485px'],
+                    id: 'layerDemo', //防止重复弹出
+                    anim: 4,
+                    content: "{:url('Attachment/selectimage')}",
+                    cancel: function () {
+                        //右上角关闭回调
+                    }
+                });
+            })
+        })
+    </script>
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/admin/personal')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 157 - 0
app/admin/view/admin/publish.html

@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<style>
+    .layui-upload-img{
+        cursor: pointer;
+        width:150px;
+        height:150px;
+        background: url('/static/public/images/uploadimg.jpg');
+        background-size:contain;
+        border-radius: 2px;
+        border-width: 1px;
+        border-style: solid;
+        border-color: #e6e6e6;
+    }
+</style>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('admin/admin/index')}" class="a_menu">管理员列表</a></li>
+            <li class="layui-this">{empty name="$info.admin"}新增{else/}编辑{/empty}</li>
+        </ul>
+    </div>
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">分组</label>
+            <div class="layui-input-inline">
+                <select name="admin_cate_id" lay-filter="">
+                    <option value="">请选择分组</option>
+                    {volist name="info['admin_cate']" id="vo"}
+                    <option value="{$vo.id}" {notempty name="$info.admin.admin_cate_id"}
+                            {eq name="$info.admin.admin_cate_id" value="$vo.id"} selected=""{/eq}{/notempty}>{$vo.name}</option>
+                    {/volist}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-upload">
+            <label class="layui-form-label">头像</label>
+            <div class="layui-upload-list">
+                <img class="layui-upload-img" id="upload_img" {notempty name="$info.admin.thumb"}src="{:geturl($info.admin.thumb,'/static/public/images/tx.jpg')}"{/notempty}>
+                <input type="hidden" name="thumb" id="upload_value" value='{notempty name="$info.admin.thumb"}{$info.admin.thumb}{/notempty}'>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            {empty name="$info.admin"}
+            <label class="layui-form-label">账号</label>
+            <div class="layui-input-inline">
+                <input name="name" id="admin_name" lay-verify="required" placeholder="请输入" autocomplete="off" class="layui-input"
+                       type="text" {notempty name="$info.admin.name" }value="{$info.admin.name}" {/notempty}>
+            </div>
+            {/empty}
+            <label class="layui-form-label">昵称</label>
+            <div class="layui-input-inline">
+                <input name="nickname" lay-verify="required" placeholder="请输入" autocomplete="off" class="layui-input"
+                       type="text" {notempty name="$info.admin.nickname" }value="{$info.admin.nickname}" {/notempty}>
+            </div>
+        </div>
+
+        {empty name="$info.admin"}
+        <div class="layui-form-item">
+            <label class="layui-form-label">密码</label>
+            <div class="layui-input-inline">
+                <input name="password" lay-verify="pass" placeholder="请输入密码" autocomplete="off" class="layui-input"
+                       type="password" id="password">
+            </div>
+            <label class="layui-form-label">重复密码</label>
+            <div class="layui-input-inline">
+                <input name="password_confirm" lay-verify="pass" placeholder="请再次输入密码" autocomplete="off"
+                       class="layui-input" type="password">
+            </div>
+        </div>
+        {/empty}
+
+        {notempty name="$info.admin"}
+        <input type="hidden" name="id" value="{$info.admin.id}">
+        {/notempty}
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+            </div>
+        </div>
+
+    </form>
+
+
+    {include file="public/foot"}
+    <script>
+        $(function () {
+            $('#upload_img').click(function () {
+                layer.open({
+                    type: 2,
+                    title: '选择图片',
+                    area: ['570px', '485px'],
+                    id: 'layerDemo', //防止重复弹出
+                    anim: 4,
+                    content: "{:url('Attachment/selectimage')}",
+                    cancel: function () {
+                        //右上角关闭回调
+                    }
+                });
+            })
+        })
+    </script>
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/admin/publish')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        async: false,
+                        dataType: 'json',
+                        success: function (res) {
+                            if (res.code == 1) {
+                                {notempty name="$info.admin"}
+                                    layer.alert(res.msg,{icon: 1}, function(index){
+                                        location.href = res.url;
+                                    })
+                                {else/}
+                                    var text = "后台地址:<br/> {:url('admin/common/login','',false,true)} <br/>账号:"+$('#admin_name').val() + "  <br/>密码:"+$('#password').val();
+                                    layer.alert(text,{icon: 1}, function(index){
+                                        location.href = res.url;
+                                    })
+                                {/notempty}
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 143 - 0
app/admin/view/admin_cate/index.html

@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+        table{
+            width:100px;
+            table-layout:fixed;/* 只有定义了表格的布局算法为fixed,下面td的定义才能起作用。 */
+        }
+        td{
+            width:100%;
+            word-break:keep-all;/* 不换行 */
+            white-space:nowrap;/* 不换行 */
+            overflow:hidden;/* 内容超出宽度时隐藏超出部分的内容 */
+            text-overflow:ellipsis;/* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用*/
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">角色管理</li>
+            <li><a href="{:url('admin/adminCate/publish')}" class="a_menu">新增角色</a></li>
+        </ul>
+    </div>
+    <form class="layui-form serch" action="{:url('admin/adminCate/index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+            <div class="layui-input-inline">
+                <input type="text" name="keywords" lay-verify="title" autocomplete="off" placeholder="请输入角色名称"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <div class="layui-input-inline">
+                        <input type="text" class="layui-input" id="date_time" autocomplete="off" placeholder="创建时间" name="create_time">
+                    </div>
+                </div>
+            </div>
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+    <table class="layui-table" lay-size="sm">
+        <colgroup>
+            <col width="50">
+            <col width="100">
+            <col width="150">
+            <col width="150">
+            <col width="150">
+            <col width="300">
+            <col width="100">
+        </colgroup>
+        <thead>
+        <tr>
+            <th>ID</th>
+            <th>角色名称</th>
+            <th>权限预览</th>
+            <th>创建时间</th>
+            <th>最后修改时间</th>
+            <th>备注</th>
+            <th>操作</th>
+        </tr>
+        </thead>
+        <tbody>
+        {volist name="cate" id="vo"}
+        <tr>
+            <td>{$vo.id}</td>
+            <td>{$vo.name}</td>
+            <td><a href="{:url('admin/adminCate/preview',['id'=>$vo.id])}" class="preview"
+                   style="margin-right: 0;font-size:12px;">点击查看</a></td>
+            <td>{$vo.create_time}</td>
+            <td>{$vo.update_time}</td>
+            <td>{$vo.desc}</td>
+            <td class="operation-menu">
+                <div class="layui-btn-group">
+                    <a href="{:url('admin/adminCate/publish',['id'=>$vo.id])}"
+                       class="layui-btn layui-btn-xs a_menu layui-btn-primary"
+                       style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>
+                    <a class="layui-btn layui-btn-xs layui-btn-primary delete" id="{$vo.id}"
+                       style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>
+                </div>
+            </td>
+        </tr>
+        {/volist}
+        </tbody>
+    </table>
+    <div style="padding:0 20px;">{$cate->render()}</div>
+
+    {include file="public/foot"}
+    <script type="text/javascript">
+
+        $('.delete').click(function () {
+            var id = $(this).attr('id');
+            layer.confirm('确定要删除?', function (index) {
+                $.ajax({
+                    url: "{:url('admin/adminCate/delete')}",
+                    data: {id: id},
+                    dataType: 'json',
+                    success: function (res) {
+                        layer.msg(res.msg);
+                        if (res.code == 1) {
+                            setTimeout(function () {
+                                location.href = res.url;
+                            }, 1500)
+                        }
+                    }
+                })
+            })
+        })
+    </script>
+    <script type="text/javascript">
+        layui.use('layer', function () {
+            var layer = layui.layer;
+
+            $('.preview').click(function () {
+                var url = $(this).attr('href');
+                layer.open({
+                    type: 2,
+                    content: url,
+                    area: ['550px', '400px']
+                });
+                return false;
+            })
+        });
+    </script>
+</div>
+</body>
+</html>

+ 74 - 0
app/admin/view/admin_cate/preview.html

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>layui</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css"  media="all">
+  <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all" />
+</head>
+<body>
+<div style="margin-top: 20px;">
+</div>
+
+<div class="layui-form-item">
+  <div class="layui-collapse" lay-accordion="" style="width:500px;margin-left:20px;">
+    {volist name="$info.menu" id="data"}
+    {notempty name="$data.list"}
+    <div class="layui-colla-item" style="">
+      <h2 class="layui-colla-title" style="background:0;">{$data.name}</h2>
+      <div class="layui-colla-content">
+        <table>
+          <tbody>
+            {eq name="data.type" value="1"}
+            <tr>
+              <td>
+                {$data.str}<input type="checkbox" lay-ignore lay-skin="primary" name="admin_menu_id[]" value="{$data.id}" {notempty name="$info.cate.permissions"}{volist name="$info.cate.permissions" id="datas"}{eq name="$datas" value="$data.id"}checked{/eq}{/volist}{/notempty}>{$data.name}
+              </td>
+            </tr>
+            {/eq}
+            {volist name="$data.list" id="vo"}
+            <tr>
+              {eq name="vo.is_display" value="1"} 
+              <td>
+                {$vo.str}<input type="checkbox" lay-ignore lay-skin="primary" name="admin_menu_id[]" value="{$vo.id}" {notempty name="$info.cate.permissions"}{volist name="$info.cate.permissions" id="datas"}{eq name="$datas" value="$vo.id"}checked {else /}{eq name="$vo.type" value="2"}checked disabled{/eq}{/eq}{/volist}{else /}{eq name="$vo.type" value="2"}checked disabled{/eq}{/notempty}>{$vo.name}
+              </td>
+              {else /}{eq name="vo.type" value="1"} 
+              <td>
+                {$vo.str}<input type="checkbox" lay-ignore lay-skin="primary" name="admin_menu_id[]" value="{$vo.id}" {notempty name="$info.cate.permissions"}{volist name="$info.cate.permissions" id="datas"}{eq name="$datas" value="$vo.id"}checked {else /}{eq name="$vo.type" value="2"}checked disabled{/eq}{/eq}{/volist}{else /}{eq name="$vo.type" value="2"}checked disabled{/eq}{/notempty}>{$vo.name}
+              </td>
+              {/eq}{/eq}
+            </tr>
+            {/volist}
+          </tbody>
+        </table>
+      </div>
+    </div>
+    {else /}
+    {eq name="data.type" value="1"}
+    <div class="layui-colla-item" style="border:0;">
+      <h2 class="layui-colla-title" style="background:0;">{$data.name}</h2>
+      <div class="layui-colla-content">
+        <table>
+          <tbody>
+            <tr>
+              <td>
+                {$data.str}<input type="checkbox" lay-ignore lay-skin="primary" name="admin_menu_id[]" value="{$data.id}" {notempty name="$info.cate.permissions"}{volist name="$info.cate.permissions" id="datas"}{eq name="$datas" value="$data.id"}checked{/eq}{/volist}{/notempty}>{$data.name}
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+    {/eq}
+    {/notempty}
+    {/volist}
+  </div>
+</div>
+
+
+{include file="public/foot"}
+</body>
+</html>

+ 157 - 0
app/admin/view/admin_cate/publish.html

@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('admin/adminCate/index')}" class="a_menu">角色管理</a></li>
+            <li class="layui-this">新增角色</li>
+        </ul>
+    </div>
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+        <div class="layui-form-item">
+            <label class="layui-form-label">角色名</label>
+            <div class="layui-input-inline">
+                <input name="name" lay-verify="required" placeholder="请输入" autocomplete="off" class="layui-input"
+                       type="text" {notempty name="$info.cate.name" }value="{$info.cate.name}" {/notempty}>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <div class="layui-collapse" lay-accordion="" style="margin-left:110px;">
+                {volist name="$info.menu" id="data"}
+                {notempty name="$data.list"}
+                <div class="layui-colla-item" style="">
+                    <h2 class="layui-colla-title" style="background:0;">{$data.name}</h2>
+                    <div class="layui-colla-content">
+                        <table>
+                            <tbody>
+                            {eq name="data.type" value="1"}
+                            <tr>
+                                <td>
+                                    {$data.str}<input type="checkbox" lay-ignore lay-skin="primary"
+                                                      name="admin_menu_id[]" value="{$data.id}" {notempty
+                                                      name="$info.cate.permissions" }{volist
+                                                      name="$info.cate.permissions" id="datas" }{eq name="$datas"
+                                                      value="$data.id" }checked{/eq}{/volist}{/notempty}>{$data.name}
+                                </td>
+                            </tr>
+                            {/eq}
+                            {volist name="$data.list" id="vo"}
+                            <tr>
+                                {eq name="vo.is_display" value="1"}
+                                <td>
+                                    {$vo.str}<input type="checkbox" lay-ignore lay-skin="primary" name="admin_menu_id[]"
+                                                    value="{$vo.id}" {notempty name="$info.cate.permissions" }{volist
+                                                    name="$info.cate.permissions" id="datas" }{eq name="$datas"
+                                                    value="$vo.id" }checked {else /}{eq name="$vo.type"
+                                    value="2"}checked disabled{/eq}{/eq}{/volist}{else /}{eq name="$vo.type"
+                                    value="2"}checked disabled{/eq}{/notempty}>{$vo.name}
+                                </td>
+                                {else /}{eq name="vo.type" value="1"}
+                                <td>
+                                    {$vo.str}<input type="checkbox" lay-ignore lay-skin="primary" name="admin_menu_id[]"
+                                                    value="{$vo.id}" {notempty name="$info.cate.permissions" }{volist
+                                                    name="$info.cate.permissions" id="datas" }{eq name="$datas"
+                                                    value="$vo.id" }checked {else /}{eq name="$vo.type"
+                                    value="2"}checked disabled{/eq}{/eq}{/volist}{else /}{eq name="$vo.type"
+                                    value="2"}checked disabled{/eq}{/notempty}>{$vo.name}
+                                </td>
+                                {/eq}{/eq}
+                            </tr>
+                            {/volist}
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+                {else /}
+                {eq name="data.type" value="1"}
+                <div class="layui-colla-item" style="border:0;">
+                    <h2 class="layui-colla-title" style="background:0;">{$data.name}</h2>
+                    <div class="layui-colla-content">
+                        <table>
+                            <tbody>
+                            <tr>
+                                <td>
+                                    {$data.str}<input type="checkbox" lay-ignore lay-skin="primary"
+                                                      name="admin_menu_id[]" value="{$data.id}" {notempty
+                                                      name="$info.cate.permissions" }{volist
+                                                      name="$info.cate.permissions" id="datas" }{eq name="$datas"
+                                                      value="$data.id" }checked{/eq}{/volist}{/notempty}>{$data.name}
+                                </td>
+                            </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+                {/eq}
+                {/notempty}
+                {/volist}
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-form-text">
+            <label class="layui-form-label">备注</label>
+            <div class="layui-input-block" style="max-width:500px;">
+                <textarea placeholder="请输入内容" class="layui-textarea" name="desc">{notempty name="$info.cate.desc"}{$info.cate.desc}{/notempty}</textarea>
+            </div>
+        </div>
+
+
+        {notempty name="$info.cate"}
+        <input type="hidden" name="id" value="{$info.cate.id}">
+        {/notempty}
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+            </div>
+        </div>
+
+    </form>
+
+
+    {include file="public/foot"}
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/adminCate/publish')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 136 - 0
app/admin/view/admin_log/index.html

@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <form class="layui-form serch" action="{:url('index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <select name="admin_menu_id" lay-search="">
+                        <option value="">操作</option>
+                        {volist name="$menus" id="vo"}
+                        <option value="{$vo.id}">{$vo.str}{$vo.name}</option>
+                        {/volist}
+                    </select>
+                </div>
+            </div>
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <select name="admin_id" lay-search="">
+                        <option value="">操作者</option>
+                        {volist name="$adminer" id="vo"}
+                        <option value="{$vo.id}">{$vo.nickname}</option>
+                        {/volist}
+                    </select>
+                </div>
+            </div>
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <div class="layui-input-inline">
+                        <input type="text" class="layui-input" id="time_range" autocomplete="off" placeholder="创建时间"
+                               name="create_time">
+                    </div>
+                </div>
+            </div>
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            <button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="deletes">清空日志</button>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                , toolbar: '#toolbarDemo'
+//                , defaultToolbar: []
+                , limit: 20
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: '{:url("index")}' //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {field: 'title', title: '操作', width: 200},
+                    {field: 'url', title: '操作url', minWidth: 80},
+                    {field: 'params', title: '记录请求参数', minWidth: 300},
+                    {field: 'person', title: '操作者', width: 160},
+                    {field: 'create_time', title: '记录时间', width: 160},
+                    {field: 'ip', title: 'IP', width: 120},
+                ]],
+                done: function () {
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                var data = checkStatus.data;
+                if (obj.event == 'deletes') {
+                    layer.confirm('确定要删除?', function (index, layero) {
+                        $.ajax({
+                            url: "{:url('clearLog')}",
+                            dataType: 'json',
+                            data: {},
+                            type: 'post',
+                            async: false,
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                        layer.close(index)
+                    });
+                }
+            });
+
+        });
+    </script>
+</div>
+</body>
+</html>

+ 117 - 0
app/admin/view/api_generation/index.html

@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<style>
+    .layui-form-item .layui-form-checkbox[lay-skin=primary] {
+        margin-top: auto;
+    }
+
+    .layui-table-cell, .layui-table-box, .layui-table-body {
+        overflow: visible;
+    }
+</style>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('admin/code_generation/index')}" class="a_menu">后台生成</a></li>
+            <li class="layui-this">接口生成</li>
+        </ul>
+    </div>
+
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">数据表</label>
+            <div class="layui-input-inline">
+                <select name="table" lay-search="" lay-filter="tablename" lay-verify="required">
+                    <option value="">请选择</option>
+                    {foreach name="$tables" item="vo" key="k"}
+                    <option value="{$vo.TABLE_NAME}|{$vo.TABLE_COMMENT}">{$vo.TABLE_NAME}({$vo.TABLE_COMMENT|default="无备注"})</option>
+                    {/foreach}
+                </select>
+            </div>
+            <div class="layui-form-mid layui-word-aux">选择要生成的表</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">接口功能</label>
+            <div class="layui-input-block">
+                <input type="checkbox" name="crud[create]" title="新增" checked>
+                <input type="checkbox" name="crud[delete]" title="删除" checked>
+                <input type="checkbox" name="crud[update]" title="修改" checked>
+                <input type="checkbox" name="crud[select]" title="查询" checked>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <div class="layui-input-block" style="display:none;" id="table_div">
+                <table class="layui-table" id="table" lay-filter="table"></table>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即生成</button>
+            </div>
+        </div>
+    </form>
+
+    {include file="public/foot"}
+    <script>
+        layui.use(['table', 'layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+
+            form.on('submit(admin)', function (data) {
+                $.ajax({
+                    url: "{:url('generation')}",
+                    data: $('#admin').serialize(),
+                    type: 'post',
+                    dataType: 'json',
+                    async: false,
+                    success: function (res) {
+                        if (1 == res.code) {
+                            layer.alert('执行成功')
+                        } else {
+                            layer.confirm(res.msg, {icon: 3, title: '覆盖前请备份文件!'}, function (index) {
+                                $.ajax({
+                                    url: "{:url('generation')}",
+                                    data: $('#admin').serialize() + '&cover=true',
+                                    type: 'post',
+                                    dataType: 'json',
+                                    async: false,
+                                    success: function (res) {
+                                        if (res.code == 1) {
+                                            layer.alert('执行成功')
+                                        } else {
+                                            layer.alert('执行失败')
+                                        }
+                                    }
+                                })
+                            });
+                        }
+                    }
+                })
+                return false;
+            });
+
+        });
+
+    </script>
+</div>
+</body>
+</html>

+ 295 - 0
app/admin/view/article/index.html

@@ -0,0 +1,295 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">文章管理</li>
+            <li><a href="{:url('admin/article/publish')}" class="a_menu">新增文章</a></li>
+        </ul>
+    </div>
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            <button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="deletes">批量删除</button>
+            <button class="layui-btn layui-btn-sm" lay-event="exportHtml_cover" title="生成并覆盖HTML文件"><i class="layui-icon">&#xe609;</i>生成HTML</button>
+        </div>
+    </script>
+
+    <form class="layui-form serch" action="{:url('index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+            <div class="layui-input-inline" style="width: 100px">
+                <input type="number" name="id" lay-verify="" autocomplete="off" placeholder="请输入ID"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline">
+                <input type="text" name="keywords" lay-verify="" autocomplete="off" placeholder="标题(模糊搜索)"
+                       class="layui-input layui-btn-sm">
+            </div>
+
+            {notempty name="$cates"}
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <select name="article_cate_id" lay-search="">
+                        <option value="">分类</option>
+                        <option value="-1">无分类</option>
+                        {volist name="$cates" id="vo"}
+                        <option value="{$vo.id}">{$vo.str}{$vo.title}</option>
+                        {/volist}
+                    </select>
+                </div>
+            </div>
+            {/notempty}
+
+            {notempty name="$catalogs"}
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <select name="catalog_id" lay-search="">
+                        <option value="">栏目</option>
+                        {volist name="$catalogs" id="vo"}
+                        <option value="{$vo.id}">{$vo.str}{$vo.title}</option>
+                        {/volist}
+                    </select>
+                </div>
+            </div>
+            {/notempty}
+
+            <div class="layui-input-inline" style="width: 100px">
+                <div class="layui-inline">
+                    <select name="status" lay-search="">
+                        <option value="">状态</option>
+                        <option value="0">待审核</option>
+                        <option value="1">已审核</option>
+                    </select>
+                </div>
+            </div>
+            <div class="layui-input-inline" style="width: 100px">
+                <div class="layui-inline">
+                    <select name="istop_time" lay-search="">
+                        <option value="">置顶</option>
+                        <option value="0">未置顶</option>
+                        <option value="1">已置顶</option>
+                    </select>
+                </div>
+            </div>
+            <div class="layui-input-inline" style="width: 100px">
+                <div class="layui-inline">
+                    <select name="admin_id" lay-search="">
+                        <option value="">创建人</option>
+                        {volist name="$admins" id="vo"}
+                        <option value="{$vo.id}">{$vo.nickname}</option>
+                        {/volist}
+                    </select>
+                </div>
+            </div>
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <div class="layui-input-inline">
+                        <input type="text" class="layui-input" id="time_range" autocomplete="off" placeholder="创建时间" name="create_time">
+                    </div>
+                </div>
+            </div>
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="edit"><i class="layui-icon"
+                                                                              style="margin-right: 0;"></i></button>
+            <button class="layui-btn layui-btn-xs delete" lay-event="del"><i class="layui-icon"
+                                                                             style="margin-right: 0;"></i></button>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {include file="public/foot"}
+
+
+
+    <script type="text/javascript">
+
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                , toolbar: '#toolbarDemo'
+//                , defaultToolbar: []
+                , limit: 15
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: '{:url("index")}' //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {type: 'checkbox'},
+                    {field: 'id', title: 'ID', width: 60},
+                    {field: 'title', title: '标题', width: 200,templet:function (row) {
+                        return '<a href="./perview?id='+row.id+'" target="_blank" title="单击预览">'+row.title+'</a>'
+                    }},
+                    {
+                        field: 'image', title: '缩略图', width: 70, align: 'center', templet: function (row) {
+                        return (row.thumb_url == '') ? '' : '<a href="' + row.thumb_url + '" class="tooltip" target="_blank"><img src="' + row.thumb_url + '" width="20" height="20"></a>';
+                    }
+                    },
+                    //{notempty name="$cates"}
+
+                    {field: 'article_cate', title: '分类', width: 120},
+                    //{/notempty}
+
+                    //{notempty name="$catalogs"}
+
+                    {field: 'catalog', title: '栏目', width: 120},
+                    //{/notempty}
+
+                    {field: 'seo_title', title: 'SEO标题',minWidth:80},
+                    {field: 'seo_keyword', title: 'SEO关键词',minWidth:80},
+                    {field: 'seo_description', title: 'SEO描述',minWidth:80},
+                    {field: 'tag', title: '标签'},
+                    {field: 'admin_name', title: '创建人',minWidth:60},
+                    {field: 'create_time', title: '创建时间',minWidth:80},
+                    {field: 'editor_name', title: '修改人',minWidth:60},
+                    {field: 'update_time', title: '修改时间',minWidth:80},
+                    {field: 'page_views', title: '浏览量', width: 70, align: 'center'},
+                    {
+                        field: 'status', title: '审核', align: 'center', width: 60, templet: function (row) {
+                        return '<a href="javascript:;" style="font-size:18px;" class="status" data-id="' + row.id + '" data-val="' + row.status + '">' + (row.status == 1 ? '<i class="fa fa-toggle-on"></i>' : '<i class="fa fa-toggle-off"></i>') + '</a>';
+                    }},
+                    {field: 'istop_time', title: '置顶', align: 'center', width: 60, templet: function (row) {
+                        return '<a href="javascript:;" style="font-size:18px;" class="istop_time" data-id="' + row.id + '" data-val="' + row.istop_time + '">' + (row.istop_time != 0 ? '<i class="fa fa-toggle-on"></i>' : '<i class="fa fa-toggle-off"></i>') + '</a>';
+                    }},
+                    {field: 'action', title: '操作', align: 'center', toolbar: '#barDemo', fixed: 'right',minWidth:80}
+                ]],
+                done: function () {
+                    showThumb();
+                    switchStatus('.status',"{:url('status')}");
+                    switchStatus('.istop_time',"{:url('istop_time')}");
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'edit') {
+                    window.parent.tab.tabAdd({
+                        icon: "fa-bookmark",
+                        id: 'article' + obj.data.id,
+                        title: obj.data.title,
+                        url: "{:url('publish')}?id=" + obj.data.id
+                    });
+                }
+                else if (obj.event == 'del') {
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            dataType: 'json',
+                            data: {id: obj.data.id},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }
+            });
+
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                var data = checkStatus.data;
+                if (obj.event == 'deletes') {
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            ids[key] = item.id;
+                        })
+                        layer.confirm('是否删除?', function (index, layero) {
+                            $.ajax({
+                                url: "{:url('deletes')}",
+                                dataType: 'json',
+                                data: {"ids": ids},
+                                type: 'post',
+                                success: function (res) {
+                                    layer.msg(res.msg);
+                                    if (res.code == 1) {
+                                        table.reload('table');
+                                    }
+                                }
+                            })
+                            layer.close(index)
+                        });
+                    } else {
+                        layer.msg('请先勾选需要操作的记录');
+                    }
+                }
+                else if(obj.event == 'exportHtml_cover'){
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            ids[key] = item.id;
+                        })
+                        layer.confirm('是否覆盖?', function (index, layero) {
+                            var load = layer.load(1, {
+                                shade: [0.1, '#fff'] //0.1透明度的白色背景
+                            });
+                            $.ajax({
+                                url: "{:url('createFile')}",
+                                dataType: 'json',
+                                data: {"ids": ids},
+                                type: 'post',
+                                success: function (res) {
+                                    layer.alert(res.msg, function (index) {
+                                        layer.msg(res.msg);
+                                    })
+                                },
+                                complete: function () {
+                                    layer.close(load);
+                                }
+                            })
+                            layer.close(index)
+                        });
+                    } else {
+                        layer.msg('请先勾选需要操作的记录');
+                    }
+                }
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 239 - 0
app/admin/view/article/publish.html

@@ -0,0 +1,239 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<style>
+    .layui-upload-img{
+        cursor: pointer;
+        width:150px;
+        height:150px;
+        background: url('__PUBLIC__/images/uploadimg.jpg');
+        background-size:contain;
+        border-radius: 2px;
+        border-width: 1px;
+        border-style: solid;
+        border-color: #e6e6e6;
+    }
+</style>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    {empty name="$article" }
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('admin/article/index')}" class="a_menu">文章管理</a></li>
+            <li class="layui-this">新增文章</li>
+        </ul>
+    </div>
+    {/empty}
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-col-md6">
+            <div class="layui-form-item">
+                <label class="layui-form-label">标题</label>
+                <div class="layui-input-block">
+                    <input name="title" lay-verify="title" autocomplete="off" placeholder="请输入标题" class="layui-input"
+                           type="text" {notempty name="$article.title" }value="{$article.title}" {/notempty}>
+                </div>
+            </div>
+
+            <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">SEO标题</label>
+                <div class="layui-input-block">
+                    <input placeholder="请输入" name="seo_title" class="layui-input" type="text" autocomplete="off"
+                           value="{notempty name=" $article.seo_title"}{$article.seo_title}{/notempty}">
+                </div>
+            </div>
+
+            <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">SEO关键词</label>
+                <div class="layui-input-block">
+                    <input placeholder="关键词之间用(英文,逗号)隔开" class="layui-input" name="seo_keyword" autocomplete="off"
+                           type="text" {notempty name="$article.seo_keyword" }value="{$article.seo_keyword}" {/notempty}>
+                </div>
+            </div>
+
+            <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">SEO描述</label>
+                <div class="layui-input-block">
+                    <textarea placeholder="请输入" class="layui-textarea" name="seo_description">{notempty name="$article.seo_description"}{$article.seo_description}{/notempty}</textarea>
+                </div>
+            </div>
+
+            <div class="layui-form-item">
+                <label class="layui-form-label">标签</label>
+                <div class="layui-input-block">
+                    <input name="tag" autocomplete="off" placeholder="标签之间用(英文,逗号)隔开" class="layui-input" type="text" {notempty name="$article.tag" }value="{$article.tag}" {/notempty}>
+                </div>
+            </div>
+
+        </div>
+
+        <div class="layui-col-md6">
+            <div class="layui-upload">
+                <label class="layui-form-label">配图</label>
+                <div class="layui-upload-list" style="margin-top: 0px">
+                    <img class="layui-upload-img" id="upload_img" {notempty name="$thumb"}src='{$thumb|geturl}'{/notempty}>
+                    <input type="hidden" name="thumb" id="upload_value" value='{notempty name="$thumb"}{$thumb}{/notempty}'>
+                </div>
+            </div>
+
+            {notempty name="$cates"}
+            <div class="layui-form-item">
+                <label class="layui-form-label">分类</label>
+                <div class="layui-input-inline" style="width:300px;">
+                    <select name="article_cate_id" lay-filter="" lay-search="">
+                        <option value="">请选择分类</option>
+                        {volist name="$cates" id="vo"}
+                        <option value="{$vo.id}"
+                                {notempty name="$article.article_cate_id" }
+                                {eq name="$article.article_cate_id" value="$vo.id" } selected="" {/eq}{/notempty}>{$vo.str}{$vo.title}</option>
+                        {/volist}
+                    </select>
+                </div>
+                <div class="layui-form-mid layui-word-aux">选择发布到哪个分类</div>
+            </div>
+            {/notempty}
+
+            {notempty name="$catalogs"}
+            <div class="layui-form-item">
+                <label class="layui-form-label">栏目</label>
+                <div class="layui-input-inline" style="width:300px;">
+                    <select name="catalog_id" lay-filter="" lay-search="">
+                        <option value="">请选择栏目</option>
+                        {volist name="$catalogs" id="vo"}
+                        <option value="{$vo.id}" {notempty name="$article" }{eq name="$article.catalog_id" value="$vo.id" } selected="" {/eq}{/notempty}>{$vo.str}{$vo.title}</option>
+                        {/volist}
+                    </select>
+                </div>
+                <div class="layui-form-mid layui-word-aux">选择发布到哪个栏目</div>
+            </div>
+            {/notempty}
+
+
+            {notempty name="$article"}
+            {php}try{{/php}
+            {notempty name="$article->getUri()"}
+            <div class="layui-form-item">
+                <label class="layui-form-label">路径</label>
+                <div class="layui-form-mid layui-word-aux">{$article->getUri()}</div>
+            </div>
+            {/notempty}
+            {php}}catch(\Exception $e){}{/php}
+            {/notempty}
+
+        </div>
+
+
+        <script src="__PUBLIC__/layui/layui.js"></script>
+        <script src="__PUBLIC__/jquery/jquery.min.js"></script>
+        <script src="__PUBLIC__/helper.js"></script>
+        <!--文章编辑器-->
+        {switch name="$article_editor" }
+        {case value="ueditor"} {include file="article/publish_ueditor" item="article" field="content"}{/case}
+        {case value="tinymce"} {include file="article/publish_tinymce" item="article" field="content"}{/case}
+        {case value="markdown"} {include file="article/publish_markdown" item="article" field="content"}{/case}
+        {default /} {include file="article/publish_wangEditor" item="article" field="content"}
+        {/switch}
+
+
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">发布状态</label>
+            <div class="layui-input-block" style="max-width:500px;">
+                <input type="radio" name="status" value="1" title="立即发布" {notempty name="$article"}{if condition="$article.status eq '1'"}checked{/if}{else/}checked{/notempty}>
+                <input type="radio" name="status" value="0" title="保存草稿" {notempty name="$article"}{if condition="$article.status eq '0'"}checked{/if}{/notempty}>
+            </div>
+        </div>
+
+
+
+        {notempty name="$article"}
+        <input type="hidden" name="id" value="{$article.id}">
+        {/notempty}
+        <input type="hidden" name="article_editor" value="{$article_editor}">
+
+
+
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">保存</button>
+                <!--<button type="reset" class="layui-btn layui-btn-primary">重置</button>-->
+                <button type="button" class="layui-btn layui-btn-primary" onclick="location.href='{$switch_editor_url}'">切换编辑器</button>
+            </div>
+        </div>
+
+    </form>
+
+
+    <script>
+        $(function () {
+            $('#upload_img').click(function () {
+                layer.open({
+                    type: 2,
+                    title: '选择图片',
+                    area: ['570px', '485px'],
+                    id: 'layerDemo', //防止重复弹出
+                    anim: 4,
+                    content: "{:url('Attachment/selectimage')}",
+                    cancel: function () {
+                        //右上角关闭回调
+                    }
+                });
+            })
+        })
+    </script>
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    if (isExitsFunction('beforeSubmit')) {
+                        beforeSubmit()
+                    }
+                    $.ajax({
+                        url: "{:url('admin/article/publish')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                if (res.msg == "添加成功") {
+                                    layer.alert(res.msg, function (index) {
+                                        location.href = res.url;
+                                    })
+                                }else{
+                                    layer.confirm(res.msg, {
+                                        btn: ['关闭', '继续编辑']
+                                    }, function (index) {
+                                        window.parent.tab.close('article{$article.id|default=0}');
+                                    }, function (index, layero) {
+                                        location.href = res.url;
+                                    });
+                                }
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+
+</div>
+</body>
+</html>

+ 71 - 0
app/admin/view/article/publish_markdown.html

@@ -0,0 +1,71 @@
+<div class="layui-form-item layui-form-text">
+    <label class="layui-form-label">内容</label>
+    <div class="layui-input-block">
+        <textarea name="[field]" id="container" style="display: none">{notempty name="$[item].[field]"}{$[item]->getData('[field]')}{/notempty}</textarea>
+        <!--用于保存html-->
+        <textarea name="[field]2" id="container2" style="display: none">{notempty name="$[item].[field]2"}{$[item]->getData('[field]2')}{/notempty}</textarea>
+    </div>
+</div>
+
+<!-- 配置文件 -->
+<link rel="stylesheet" href="__PUBLIC__/layui/extend/markdown/easymde-layui/mods/easymde/css/easymde.min.css"/>
+<!-- 实例化编辑器 -->
+<script type="text/javascript">
+    layui.config({
+        base: "__PUBLIC__/layui/extend/markdown/easymde-layui/mods/", //你存放新模块的目录,注意,不是 layui 的模块目录
+    }).extend({
+        easymde: 'easymde/easymde', //拓展一个模块别名
+    });
+
+    layui.use(["easymde"], function () { //加载模块
+        const easymde = layui.easymde;
+        const $ = layui.$;
+
+        const mde = easymde.init({
+            element: document.getElementById("container"), //文本域ID
+            autosave: false,
+            promptURLs: true, // 如果设置为true,则会显示一个JS警报窗口,要求提供链接或图像URL。默认为false。
+            renderingConfig: {
+                codeSyntaxHighlighting: true, //开启代码高亮
+            },
+            placeholder: "|",
+            toolbar: [
+                //展示所有工具栏,如果不指定有默认的选项。
+                "bold", //黑体
+                "italic", //斜体
+                "strikethrough", //删除线
+                "heading", //标题
+                "heading-smaller", //缩小标题
+                "heading-bigger", // 增大标题
+                "heading-1", //小标题
+                "heading-2", //中标题
+                "heading-3", //大标题
+                "|", //分割线
+                "code", // 代码块
+                "quote", //引用
+                "unordered-list", // 无序列表
+                "ordered-list", // 有序列表
+                "clean-block", // 清除块样式
+                "|", //分割线
+                "link", //添加超链接
+                "image", //添加图片
+                "table", //添加表格
+                "horizontal-rule", // 水平线
+                "|",
+                "preview", //全屏预览
+                "side-by-side", //分屏预览
+                "fullscreen", //全屏
+                "|", //分割线
+                "undo", //清空
+                "redo" // 重做
+            ]
+        });
+
+    });
+
+    //提交表单前调用此函数,保存html
+    function beforeSubmit() {
+        $('#container2').text(layui.easymde.html())
+    }
+
+</script>

+ 63 - 0
app/admin/view/article/publish_tinymce.html

@@ -0,0 +1,63 @@
+<div class="layui-form-item layui-form-text">
+    <label class="layui-form-label">内容</label>
+    <div class="layui-input-block">
+        <textarea name="[field]" id="container">{notempty name="$[item].[field]"}{$[item]->getData('[field]')}{/notempty}</textarea>
+    </div>
+</div>
+
+
+<!-- 配置文件 -->
+<script type="text/javascript" src="__PUBLIC__/tinymce/js/tinymce/tinymce.min.js"></script>
+<!-- 实例化编辑器 -->
+<script type="text/javascript">
+    //http://tinymce.ax-z.cn/advanced/some-example.php
+    var tinyID = 'container';
+    var host = location.protocol + location.port + "//" + document.domain;
+    tinymce.init({
+        selector: '#' + tinyID,
+        language: 'zh_CN',//注意大小写
+        height: 350,
+        plugins: 'link image autosave fullscreen autolink code media preview paste',
+        toolbar: 'undo redo restoredraft| bold italic underline | image media code | fullscreen ',
+        relative_urls: false,//绝对URL
+        document_base_url: host,
+        autosave_interval: "10s",//自动存稿的世界间隔
+        //上传自定义
+        images_upload_handler: function (blobInfo, succFun, failFun) {
+            var xhr, formData;
+            var file = blobInfo.blob();//转化为易于理解的file对象
+            xhr = new XMLHttpRequest();
+            xhr.withCredentials = false;
+            xhr.open('POST', "{:url('attachment/upload')}");
+            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+            xhr.onload = function () {
+                var json;
+                if (xhr.status != 200) {
+                    failFun('HTTP Error: ' + xhr.status);
+                    return;
+                }
+                json = JSON.parse(xhr.responseText);
+                //console.log(json);
+                if (!json || typeof json.data.src != 'string') {
+                    failFun('Invalid JSON: ' + xhr.responseText);
+                    return;
+                }
+                succFun(json.data.src);
+            };
+            formData = new FormData();
+            formData.append('file', file, file.name);//此处与源文档不一样
+            formData.append('use', 'article_content');//上传附件的参数
+            xhr.send(formData);
+        },
+        //传统点击submit提交按钮会自动同步内容,但ajax之类的用事件提交会导致内容没有同步,暂时的解决办法是在初始化参数中setup参数里加入事件监听,让他自动同步。
+        setup: function (editor) {
+            editor.on('change', function () {
+                editor.save();
+            });
+        },
+    });
+
+    function getContent() {
+        return tinyMCE.editors[tinyID].getContent();
+    }
+</script>

+ 16 - 0
app/admin/view/article/publish_ueditor.html

@@ -0,0 +1,16 @@
+<div class="layui-form-item layui-form-text">
+    <label class="layui-form-label">内容</label>
+    <div class="layui-input-block">
+        <textarea placeholder="请输入" class="layui-textarea" name="[field]" id="container" style="border:0;padding:0">{notempty name="$[item].[field]"}{$[item].[field]}{/notempty}</textarea>
+    </div>
+</div>
+
+
+<!-- 配置文件 -->
+<script type="text/javascript" src="__PUBLIC__/ueditor/ueditor.config.js"></script>
+<!-- 编辑器源码文件 -->
+<script type="text/javascript" src="__PUBLIC__/ueditor/ueditor.all.js"></script>
+<!-- 实例化编辑器 -->
+<script type="text/javascript">
+    var ue = UE.getEditor('container');
+</script>

+ 203 - 0
app/admin/view/article/publish_wangEditor.html

@@ -0,0 +1,203 @@
+<div class="layui-form-item layui-form-text">
+    <label class="layui-form-label">内容</label>
+    <div class="layui-input-block">
+        <div id="container"></div>
+        <textarea name="[field]" id="container1" style="display: none">{notempty name="$[item].[field]"}{$[item]->getData('[field]')}{/notempty}</textarea>
+    </div>
+</div>
+
+<!-- 配置文件 -->
+<script type="text/javascript" src="__PUBLIC__/wangEditor/wangEditor4.min.js"></script>
+<!-- 实例化编辑器 -->
+<script type="text/javascript">
+    //https://www.wangeditor.com/
+    const E = window.wangEditor
+    const editor = new E('#container')
+    //防止遮盖下拉框
+    editor.config.zIndex = 500;
+    //===============================定义图片上传================================//
+    editor.config.uploadImgServer = "{:url('attachment/upload')}";
+    editor.config.uploadImgMaxSize = '{$web_config.file_size}' * 1024; // 限制大小
+//    editor.config.uploadImgAccept = ['jpg']
+    editor.config.uploadImgMaxLength = 5 // 一次最多上传 5 个图片
+    editor.config.uploadImgParams = {
+        use: 'article_content',//自定义 上传参数
+    }
+    editor.config.uploadImgHeaders = {'X-Requested-With': 'XMLHttpRequest'} //自定义 header
+    editor.config.uploadFileName = 'file',//自定义 fileName
+        editor.config.uploadImgHooks = {
+            // 图片上传并返回了结果,图片插入已成功
+            success: function (xhr) {
+                console.log('success', xhr)
+            },
+            // 上传图片超时
+            timeout: function (xhr) {
+                console.log('timeout')
+            },
+            // 图片上传并返回了结果,想要自己把图片插入到编辑器中
+            // 例如服务器端返回的不是 { errno: 0, data: [...] } 这种格式,可使用 customInsert
+            customInsert: function (insertImgFn, result) {
+                // result 即服务端返回的接口
+                console.log('customInsert', result)
+                if (result.code == 1) {
+                    // insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
+                    insertImgFn(result.data.src)
+                }else{
+                    alert(result.msg);
+                }
+            }
+        }
+    //===============================定义图片上传================================//
+    //
+    //===============================定义上传视频================================//
+    // 配置 server 接口地址
+    editor.config.uploadVideoServer = "{:url('attachment/upload')}";
+    editor.config.uploadVideoMaxSize = '{$web_config.file_size}' * 1024; // 默认限制视频大小是 1024m ,可以自己修改
+    //    editor.config.uploadVideoAccept = ['mp4']
+    //考虑到文件较大,所以暂时只允许一个视频上传
+    editor.config.uploadVideoParams = {
+        use: 'article_content',//自定义 上传参数
+    }
+    editor.config.uploadVideoHeaders = {
+        'X-Requested-With': 'XMLHttpRequest' //自定义 header
+    }
+    editor.config.uploadVideoName = 'file';//自定义 fileName
+    editor.config.uploadVideoTimeout = 1000 * 60 * 5; //timeout 即上传接口等待的最大时间,默认是 5分钟,可以自己修改。
+    editor.config.uploadVideoHooks = {
+        // 上传视频之前
+//        before: function(xhr) {
+//            console.log(xhr)
+//            // 可阻止视频上传
+//            return {
+//                prevent: true,
+//                msg: '需要提示给用户的错误信息'
+//            }
+//        },
+        // 视频上传并返回了结果,视频插入已成功
+        success: function (xhr) {
+            console.log('success', xhr)
+        },
+        // 视频上传并返回了结果,但视频插入时出错了
+        fail: function (xhr, editor, resData) {
+            console.log('fail', resData)
+        },
+        // 上传视频出错,一般为 http 请求的错误
+        error: function (xhr, editor, resData) {
+            console.log('error', xhr, resData)
+        },
+        // 上传视频超时
+        timeout: function (xhr) {
+            console.log('timeout')
+        },
+        // 视频上传并返回了结果,想要自己把视频插入到编辑器中
+        // 例如服务器端返回的不是 { errno: 0, data: { url : '.....'} } 这种格式,可使用 customInsert
+        customInsert: function (insertVideoFn, result) {
+            // result 即服务端返回的接口
+            console.log('customInsert', result)
+            if (result.code == 1) {
+                // insertVideoFn 可把视频插入到编辑器,传入视频 src ,执行函数即可
+                insertVideoFn(result.data.src)
+            }else{
+                alert(result.msg);
+            }
+        }
+    }
+    // 自定义插入视频的形式
+    //    editor.config.customInsertVideo = function (videoUrl) {
+    //        // videoUrl 是返回的视频地址
+    //
+    //        var thumb = $('#upload_img').attr('src');
+    //        // 往编辑器插入 html 内容
+    //        editor.cmd.do(
+    //            'insertHTML',
+    //            '<video src="'+videoUrl+'" poster="'+thumb+'" controls="controls" style="max-width:100%"></video>'
+    //        )
+    //    }
+    //===============================定义上传视频================================//
+    //
+    //===============================自定义菜单================================//
+    //查看源码按钮
+    class CustomMenu extends E.BtnMenu {
+        constructor(editor_) {
+            const $elem = E.$(
+                '<div class="w-e-menu" data-title="查看源码"><a href="javaScript:;" id="menu_y" class=""><i class="layui-icon">&#xe64e;</i></a></div>'
+            )
+            super($elem, editor_)
+        }
+
+        // 点击事件
+        clickHandler() {
+            var editorHtml = editor.txt.html();
+            if ($("#menu_y").attr("class") == "active_y") {
+                $("#menu_y").attr("class", "")
+                editorHtml = editor.txt.text().replace(/&lt;/ig, "<").replace(/&gt;/ig, ">").replace(/&nbsp;/ig, " ");
+            } else {
+                $("#menu_y").attr("class", "active_y")
+                editorHtml = editorHtml.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/ /g, "&nbsp;");
+            }
+            editor.txt.html(editorHtml);
+//            editor.change && editor.change();
+        }
+
+        //激活状态
+        tryChangeActive() {
+            if ($("#menu_y").attr("class") == "active_y") {
+                this.active();//会增加一个 .w-e-active 的 css class
+            } else {
+                this.unActive();//会删掉 .w-e-active
+            }
+        }
+    }
+    // 注册菜单
+    const customMenuKey = 'customMenuKey';
+    editor.menus.extend('customMenuKey', CustomMenu);
+    editor.config.menus = editor.config.menus.concat(customMenuKey);
+    //===============================自定义菜单================================//
+    //清除格式
+    class ClearMenu extends E.BtnMenu {
+        constructor(editor_) {
+            const $elem = E.$(
+                '<div class="w-e-menu" data-title="清除格式"><a href="javaScript:;" id="clear_css" class=""><i class="layui-icon"></i></a></div>'
+            )
+            super($elem, editor_)
+        }
+
+        // 点击事件
+        clickHandler(value) {
+            var editor = this.editor;
+            var isSeleEmpty = editor.selection.isSelectionEmpty();
+            var selectionText = editor.selection.getSelectionText();
+
+            if (!isSeleEmpty) {
+                editor.cmd.do('insertHTML', selectionText);
+            }
+        }
+
+        tryChangeActive() {
+            //没有激活状态
+        }
+    }
+    // 注册菜单
+    const clearMenu = 'clearMenu';
+    editor.menus.extend('clearMenu', ClearMenu);
+    editor.config.menus = editor.config.menus.concat(clearMenu);
+    //===============================自定义菜单================================//
+    //实例化
+    const $text1 = $('#container1');
+    editor.config.onchange = function (html) {
+        $text1.val(html)// 第二步,监控变化,同步更新到 textarea
+    }
+    editor.create()
+    editor.txt.html($text1.val()) // 第一步,初始化编辑器内容
+</script>
+<script>
+    //提交表单前调用此函数,还原预览代码
+    function beforeSubmit() {
+//        console.log('before submit')
+        if ($("#menu_y").attr("class") == "active_y") {
+            $("#menu_y").attr("class", "")
+            var editorHtml = editor.txt.text().replace(/&lt;/ig, "<").replace(/&gt;/ig, ">").replace(/&nbsp;/ig, " ");
+            $text1.val(editorHtml);
+        }
+    }
+</script>

+ 196 - 0
app/admin/view/articlecate/index.html

@@ -0,0 +1,196 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<style type="text/css">
+    table {
+        table-layout: fixed;
+        width: 100%;
+    }
+
+    td {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+</style>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">分类管理</li>
+            <li><a href="{:url('admin/articlecate/publish')}" class="a_menu">新增分类</a></li>
+        </ul>
+    </div>
+
+    <div id="toolbar" style="display: none">
+        <button class="layui-btn layui-btn-sm layui-btn-primary" id="open-all"><i
+                class="layui-icon layui-icon-triangle-d"></i>展开
+        </button>
+        <button class="layui-btn layui-btn-sm layui-btn-primary" id="close-all"><i
+                class="layui-icon layui-icon-triangle-r"></i>收缩
+        </button>
+        <!--<button class="layui-btn layui-btn-sm" id="updateTreePath"><i class="layui-icon">&#xe609;</i>更新 tree_path</button>-->
+    </div>
+
+    <form class="layui-form" id="admin">
+        <table class="layui-table layui-form" id="tree-table" lay-size="sm"></table>
+        <button class="layui-btn layui-btn-sm" lay-submit lay-filter="admin" style="display: none" id="sort">更新排序</button>
+    </form>
+
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+
+        layui.extend({
+            treeTable: 'treeTable/js/treeTable'
+        }).use(['treeTable', 'layer', 'form'], function () {
+            var treeTable = layui.treeTable,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            var re = treeTable.render({
+                elem: '#tree-table',
+                url: '{:url("index")}', //数据接口
+                icon_key: 'title',
+                cols: [
+                    {
+                        key: 'sort', title: '排序', align: 'center', width: '40px', template: function (row) {
+                        return '<input type="text" name="sorts[]" value="' + row.sort + '" style="width: 20px;" class="sort"><input type="hidden" name="ids[]" value="' + row.id + '">';
+                    }
+                    },
+                    {key: 'id', title: 'ID', width: '40px'},
+                    {
+                        key: 'title', title: '分类名称', template: function (row) {
+                        return '<a href="./perview?id=' + row.id + '" target="_blank" title="单击预览">' + row.title + '</a>'
+                    }
+                    },
+                    {key: 'en_name', title: '分类别名'},
+                    {key: 'catalog', title: '关联的栏目'},
+                    {key: 'seo_title', title: 'SEO标题'},
+                    {key: 'seo_keyword', title: 'SEO关键词'},
+                    {key: 'seo_description', title: 'SEO描述'},
+                    {key: 'article_count', title: '文章数', width: '60px'},
+                    //{php}if(\think\App::$debug){{/php}
+
+                    {key: 'uri', title: 'URI'},
+                    {key: 'tree_path', title: 'TREE_PATH'},
+                    //{php}}{/php}
+
+                    {
+                        key: '状态', title: '审核', align: 'center', width: '60px', template: function (row) {
+                        return '<a href="javascript:;" style="font-size:18px;" class="status" data-id="' + row.id + '" data-val="' + row.status + '">' + (row.status == 1 ? '<i class="fa fa-toggle-on"></i>' : '<i class="fa fa-toggle-off"></i>') + '</a>';
+                    }
+                    },
+                    {
+                        title: '操作', align: 'center', width: '100px',
+                        template: function (item) {
+                            var html = '<div class="layui-btn-group">';
+                            html += '<a href="publish?id=' + item.id + '" class="layui-btn layui-btn-xs a_menu layui-btn-primary" style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>';
+                            html += '<a href="publish?pid=' + item.id + '" class="layui-btn layui-btn-xs a_menu layui-btn-primary" style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>';
+                            html += '<a href="javascript:;" class="layui-btn layui-btn-xs layui-btn-primary delete" id="' + item.id + '" style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>';
+                            html += '</div>';
+                            return html;
+                        }
+                    }
+                ],
+                end: function (e) {
+                    form.render();
+                    if (e.data.length > 0) {
+                        $('#toolbar').show();
+                        $('#sort').show();
+                    }
+                    // 全部展开
+                    $('#open-all').click(function () {
+                        treeTable.openAll(re);
+                    })
+                    // 全部关闭
+                    $('#close-all').click(function () {
+                        treeTable.closeAll(re);
+                    })
+                    //审核
+                    switchStatus('.status',"{:url('status')}");
+                    //删除
+                    $('.delete').click(function () {
+                        var id = $(this).attr('id');
+                        layer.confirm('确定要删除?', function (index) {
+                            $.ajax({
+                                url: "{:url('delete')}",
+                                data: {id: id},
+                                dataType: 'json',
+                                type: 'post',
+                                success: function (res) {
+                                    layer.msg(res.msg);
+                                    if (res.code == 1) {
+                                        setTimeout(function () {
+                                            location.href = res.url;
+                                        }, 1500)
+                                    }
+                                }
+                            })
+                        })
+                    })
+                },
+            });
+        });
+
+        $("#updateTreePath").click(function () {
+            var load = layer.load(1, {
+                shade: [0.1, '#fff'] //0.1透明度的白色背景
+            });
+            $.ajax({
+                url: "{:url('updateTreePath')}",
+                data: {},
+                type: 'post',
+                dataType: 'json',
+                success: function (res) {
+                    layer.alert(res.msg, function (index) {
+                        layer.msg(res.msg);
+                    })
+                },
+                complete: function () {
+                    layer.close(load);
+                }
+            })
+        })
+    </script>
+    <script>
+        // 排序
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('sort')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.reload();
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 188 - 0
app/admin/view/articlecate/publish.html

@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('admin/articlecate/index')}" class="a_menu">分类管理</a></li>
+            <li class="layui-this">新增分类</li>
+        </ul>
+    </div>
+
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">上级节点</label>
+            <div class="layui-input-inline">
+                <select name="pid" lay-filter="" lay-search="">
+                    <option value="0">作为顶级节点</option>
+                    {volist name="$cates" id="vo"}
+                    <option value="{$vo.id}" {notempty name="$cate.pid" }{eq name="$cate.pid" value="$vo.id" } selected="" {/eq}{else /}{notempty name="$pid"}{eq name="$pid" value="$vo.id"}selected=""{/eq}{/notempty}{/notempty}>{$vo.str}{$vo.title}</option>
+                    {/volist}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">分类名称</label>
+            <div class="layui-input-inline">
+                <input id="title" name="title" lay-verify="required" placeholder="例如:苹果" autocomplete="off" class="layui-input"
+                       type="text" {notempty name="$cate.title" }value="{$cate.title}" {/notempty}>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">分类别名</label>
+            <div class="layui-input-inline" style="width:300px;">
+                <input id="en_name" name="en_name" lay-verify="" placeholder="例如:apple" autocomplete="off" class="layui-input"
+                       type="text" {notempty name="$cate.en_name" }value="{$cate.en_name}" {/notempty}>
+            </div>
+            <div class="layui-form-mid layui-word-aux">用于发布路径,请填写英文字符</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">关联栏目</label>
+            <div class="layui-input-inline" style="width:300px;">
+                <div id="demo1"></div>
+            </div>
+            <div class="layui-form-mid layui-word-aux">关联文章列表,非必填</div>
+        </div>
+
+        <div class="layui-form-item layui-form-text">
+            <label class="layui-form-label">SEO标题</label>
+            <div class="layui-input-block" style="max-width:500px;">
+                <input placeholder="请输入" name="seo_title" class="layui-input" type="text" autocomplete="off"
+                       value="{notempty name="$cate.seo_title"}{$cate.seo_title}{/notempty}">
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-form-text">
+            <label class="layui-form-label">SEO关键词</label>
+            <div class="layui-input-block" style="max-width:500px;">
+                <input placeholder="关键词之间用(英文,逗号)隔开" class="layui-input" name="seo_keyword" type="text"
+                       autocomplete="off" {notempty name="$cate.seo_keyword" }value="{$cate.seo_keyword}" {/notempty}>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-form-text">
+            <label class="layui-form-label">SEO描述</label>
+            <div class="layui-input-block" style="max-width:500px;">
+                <textarea placeholder="请输入" class="layui-textarea" name="seo_description">{notempty name="$cate.seo_description"}{$cate.seo_description}{/notempty}</textarea>
+            </div>
+        </div>
+
+
+        <!--<div class="layui-form-item">-->
+            <!--<label class="layui-form-label">排序</label>-->
+            <!--<div class="layui-input-inline" style="max-width:100px;">-->
+                <!--<input name="sort" lay-verify="number" autocomplete="off" placeholder="默认0" class="layui-input"-->
+                       <!--type="number" value="{notempty name=" $cate.sort"}{$cate.sort}{else/}0{/notempty}">-->
+            <!--</div>-->
+            <!--<div class="layui-form-mid layui-word-aux">数字越大排序越前</div>-->
+        <!--</div>-->
+
+        {notempty name="$cate"}
+        <input type="hidden" name="id" value="{$cate.id}">
+        {/notempty}
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+            </div>
+        </div>
+
+    </form>
+
+
+    <script src="__PUBLIC__/layui/layui.js"></script>
+    <script src="__PUBLIC__/jquery/jquery.min.js"></script>
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/articlecate/publish')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    });
+                    return false;
+                });
+            });
+        });
+    </script>
+    <script>
+
+        var catalogs = [
+            {foreach name="$catalogs" item="vo" key="k"}
+            {name: '{$vo.title}', value: '{$vo.id}'},
+            {/foreach}
+        ];
+
+        var init = [{notempty name='$cate'}{$cate->getCatalogValues('id')}{/notempty}];
+
+        //加载组件-多选下拉
+        layui.config({
+            base: '__PUBLIC__/layui/extend/xmSelect/'
+        }).extend({
+            xmSelect: 'xm-select'
+        }).use(['xmSelect'], function(){
+            var xmSelect = layui.xmSelect;
+
+            //渲染多选
+            var demo1 = xmSelect.render({
+                el: '#demo1',
+                tips: '多选项',
+                name: 'catalog_ids',
+                size: 'small',
+                toolbar: {
+                    show: true,
+                },
+                filterable: true,
+                paging: true,
+                pageSize: 10,
+                model: {
+                    label: {
+                        type: 'block',
+                        block: {
+                            //最大显示数量, 0:不限制
+                            showCount: 1,
+                            //是否显示删除图标
+                            showIcon: true,
+                        }
+                    }
+                },
+                data: catalogs, //数据
+                initValue:init //初始化选中的数据, 需要在data中存在
+            })
+        })
+    </script>
+</div>
+</body>
+</html>

+ 332 - 0
app/admin/view/attachment/index.html

@@ -0,0 +1,332 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">附件管理</li>
+            <li><a href="javascript:;" permission="{:url('admin/attachment/upload')}" class="a_menu" id="myupload">上传文件</a></li>
+        </ul>
+    </div>
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            <button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="deletes">批量删除</button>
+            <!--<button class="layui-btn layui-btn-sm" lay-event="imgPersistence"><i class="layui-icon">&#xe656;</i>文件本地化</button>-->
+        </div>
+    </script>
+
+    <form class="layui-form serch" action="{:url('index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+            <div class="layui-input-inline">
+                <input type="text" name="filename" lay-verify="title" autocomplete="off" placeholder="请输入文件名"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline">
+                <input type="text" name="filepath" lay-verify="title" autocomplete="off" placeholder="请输入文件路径"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <select name="status" lay-search="">
+                        <option value="">状态</option>
+                        <option value="0">待审核</option>
+                        <option value="1">已审核</option>
+                        <option value="-1">已拒绝</option>
+                    </select>
+                </div>
+            </div>
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <select name="type">
+                        <option value="">所有类型</option>
+                        <option value="1">网络文件</option>
+                        <option value="-1">本地文件</option>
+                    </select>
+                </div>
+            </div>
+            <div class="layui-input-inline">
+                <div class="layui-inline">
+                    <div class="layui-input-inline">
+                        <input type="text" class="layui-input" id="date_time" placeholder="上传时间" name="create_time"
+                               autocomplete="off">
+                    </div>
+                </div>
+            </div>
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <button class="layui-btn layui-btn-xs layui-btn-primary" lay-event="down"><i class="fa fa-download"></i></button>
+            <button class="layui-btn layui-btn-xs delete layui-btn-primary" lay-event="del"><i class="layui-icon"
+                                                                             style="margin-right: 0;"></i></button>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {include file="public/foot"}
+
+    <a id="down"></a>
+
+    <script type="text/javascript">
+
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                , toolbar: '#toolbarDemo'
+//                , defaultToolbar: []
+                , limit: 15
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: '{:url("index")}' //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {type: 'checkbox'},
+                    {field: 'id', title: 'ID', width: 60},
+                    {
+                        field: 'thumb_url', title: '缩略图', width: 70, align: 'center', templet: function (row) {
+                        exts = {'jpg': '1', 'gif': '1', 'png': '1', 'jpeg': '1'};
+                        if (exts[row.fileext]) {
+                            return (row.thumb_url == '') ? '' : '<a href="' + row.thumb_url + '" class="tooltip" target="_blank"><img src="' + row.thumb_url + '" width="20" height="20"></a>';
+                        }
+                        return '<i class="fa fa-file"></i>';
+                    }
+                    },
+                    {field: 'filename', title: '名称', width: 180},
+                    {field: 'module', title: '模块',width: 80},
+                    {field: 'use', title: '来源',minWidth:80},
+                    {field: 'filepath', title: '路径',minWidth:200},
+                    {
+                        field: 'filesize', title: '大小', minWidth: 80, templet: function (row) {
+                        var i = 0;
+                        var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
+                        var size = row.filesize;
+                        for (i = 0; size >= 1024 && i < 5; i++) {
+                            size /= 1024;
+                        }
+                        return Math.round(size) + units[i];
+                    }
+                    },
+                    {field: 'fileext', title: '格式'},
+                    {field: 'user_id', title: '上传者id',minWidth:60},
+                    {field: 'uploadip', title: '上传IP',minWidth:60},
+                    {field: 'create_time', title: '上传时间',minWidth:80},
+                    {
+                        field: 'status', title: '审核', width: 80, templet: function (row) {
+                        if (row.status == 1) {
+                            return '<span class="layui-badge status" style="background-color: #8FCDA0" data-id="'+row.id+'">'+row.status_text+'</span>';
+                        } else if (row.status == -1) {
+                            return '<span class="layui-badge status" data-id="'+row.id+'">'+row.status_text+'</span>';
+                        } else {
+                            return '<span class="layui-badge layui-bg-gray status" data-id="'+row.id+'">'+row.status_text+'</span>';
+                        }
+                    }
+                    },
+                    {field: 'admin_id', title: '审核者id',minWidth:80},
+                    {field: 'audit_time', title: '审核时间',minWidth:80},
+                    {field: 'download', title: '已下载',minWidth:80},
+                    {field: 'action', title: '操作', align: 'center', toolbar: '#barDemo', fixed: 'right',minWidth:80}
+                ]],
+                done: function () {
+                    showThumb();
+
+                    $('.status').click(function () {
+                        var id = $(this).attr('data-id');
+                        layer.msg('审核', {
+                            time: 20000,
+                            btn: ['通过', '拒绝','待审', '再想想'],
+                            yes: function (index, layero) {
+                                $.ajax({
+                                    url: "{:url('status')}",
+                                    type: 'post',
+                                    dataType: 'json', data: {id: id, status: '1'},
+                                    success: function (res) {
+                                        layer.msg(res.msg);
+                                        if (res.code == 1) {
+                                            table.reload('table');
+                                        }
+                                    }
+                                })
+                            },
+                            btn2: function (index, layero) {
+                                $.ajax({
+                                    url: "{:url('status')}",
+                                    type: 'post',
+                                    dataType: 'json', data: {id: id, status: '-1'},
+                                    success: function (res) {
+                                        layer.msg(res.msg);
+                                        if (res.code == 1) {
+                                            table.reload('table');
+                                        }
+                                    }
+                                })
+                            }
+                            ,btn3: function(index, layero){
+                                $.ajax({
+                                    url: "{:url('status')}",
+                                    type: 'post',
+                                    dataType: 'json', data: {id: id, status: '0'},
+                                    success: function (res) {
+                                        layer.msg(res.msg);
+                                        if (res.code == 1) {
+                                            table.reload('table');
+                                        }
+                                    }
+                                })
+                            },
+                        })
+                    })
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'del') {
+                    layer.confirm('确定删除文件?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            dataType: 'json',
+                            data: {id: obj.data.id},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }else if(obj.event == 'down'){
+                    var id = obj.data.id;
+                    var download = document.getElementById('down');
+                    $.ajax({
+                        url: "{:url('admin/attachment/download')}",
+                        data: {id: id},
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                download.setAttribute('href', res.data);
+                                download.setAttribute('download', res.name);
+                                download.click();
+                                table.reload('table');
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                }
+            });
+
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                var data = checkStatus.data;
+                if (obj.event == 'deletes') {
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            ids[key] = item.id;
+                        })
+                        layer.confirm('确定删除附件?', function (index, layero) {
+                            $.ajax({
+                                url: "{:url('deletes')}",
+                                dataType: 'json',
+                                data: {"ids": ids},
+                                type: 'post',
+                                success: function (res) {
+                                    layer.msg(res.msg);
+                                    if (res.code == 1) {
+                                        table.reload('table');
+                                    }
+                                }
+                            })
+                            layer.close(index)
+                        });
+                    } else {
+                        layer.msg('请先勾选需要操作的记录');
+                    }
+                }
+                else if(obj.event == 'imgPersistence'){
+                    layer.confirm('网络文件将转为本地文件,是否继续?', function (index) {
+                        var load = layer.load(1, {
+                            shade: [0.1, '#fff'] //0.1透明度的白色背景
+                        });
+                        $.ajax({
+                            url: "{:url('imgPersistence')}",
+                            dataType: 'json',
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            },
+                            complete: function () {
+                                layer.close(load);
+                            }
+                        })
+                    })
+                }
+            });
+        });
+    </script>
+    <script>
+        layui.use('upload', function () {
+            var $ = layui.jquery
+                , upload = layui.upload;
+            //指定允许上传的文件类型
+            upload.render({
+                elem: '#myupload'
+                , url: "{:url('admin/attachment/upload')}"
+                , accept: 'file' //普通文件
+//                , exts: 'zip|rar|7z' //只允许上传压缩文件
+                , done: function (res) {
+                    layer.msg(res.msg);
+                    if (res.code == 1 && res.url!=null) {
+                        setTimeout(function () {
+                            location.href = res.url;
+                        }, 1500)
+                    }
+                }
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 133 - 0
app/admin/view/attachment/select_image.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+
+        .site-doc-necolor li {
+            margin-top: 15px;
+            display: inline-block;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">在线图片</li>
+            {if condition="$showUpload"}
+            <li><a href="javascript:;" id="upload_button">上传图片</a></li>
+            {/if}
+            <li><a href="javascript:setUploadValue('__PUBLIC__/images/uploadimg.jpg',0);" id="clear">取消图片</a></li>
+        </ul>
+        <div class="layui-tab-content">
+            <div class="layui-tab-item layui-show">
+                <form class="layui-form serch" method="post">
+                    <div class="layui-form-item" style="float: left;">
+                        <div class="layui-input-inline" style="width:300px;">
+                            <input type="text" id="url" placeholder="请输入,网络图片链接" class="layui-input layui-btn-sm">
+                        </div>
+                        <button class="layui-btn layui-btn-sm" onclick="setUrl();return false">提交</button>
+                    </div>
+                </form>
+            </div>
+            <div class="layui-tab-item"><div class="layui-row" id="demoText" style="background-color: antiquewhite"></div></div>
+            <div class="layui-tab-item"></div>
+        </div>
+    </div>
+
+    <div class="layui-row">
+        <ul class="site-doc-necolor">
+            {volist name="attachment" id="vo"}
+            <li>
+                <img src="{$vo.filepath}" width="60" height="60" class="img" id="{$vo.id}">
+            </li>
+            {/volist}
+        </ul>
+    </div>
+
+    <div style="padding:0 0 0 20px;">{$attachment->render()}</div>
+
+    {include file="public/foot"}
+    <script>
+        function setUploadValue(upload_img, upload_value) {
+            //注意:parent 是 JS 自带的全局对象,可用于操作父页面
+            var index = parent.layer.getFrameIndex(window.name); //获取窗口索引
+            //给父页面传值
+            var suffix = '{$Request.param.suffix}';
+            parent.$('#upload_img' + suffix).attr('src', upload_img);
+            parent.$('#upload_value' + suffix).val(upload_value);
+            //关闭本窗口
+            parent.layer.close(index);
+        }
+
+        //给父页面传值
+        $('.img').on('click', function () {
+            setUploadValue($(this).attr('src'), $(this).attr('id'));
+        });
+
+        function setUrl() {
+            $.ajax({
+                url: "{:url('admin/attachment/create')}",
+                type: 'post',
+                dataType: 'json',
+                data: {filepath: $('#url').val(), use: parent.location.pathname},
+                success: function (res) {
+                    if (res.code == 1) {
+                        setUploadValue(res.data.src, res.data.id);
+                    } else {
+                        layer.msg(res.msg);
+                    }
+                }
+            })
+        }
+    </script>
+    <script>
+        layui.use('upload', function () {
+            var upload = layui.upload;
+            var uploadInst = upload.render({
+                elem: '#upload_button' //绑定元素
+                , url: "{:url('admin/attachment/upload')}" //上传接口
+                , data: {use: parent.location.pathname}
+                , exts: 'jpg|png|gif|jpeg' //允许上传的文件
+                , done: function (res) {
+                    //上传完毕回调
+                    if (res.code == 1) {
+                        location.reload();
+                    } else {
+                        layer.msg(res.msg);
+                    }
+                }
+                , error: function () {
+                    //请求异常回调
+                    //失败状态,并实现重传
+                    var demoText = $('#demoText');
+                    demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-sm demo-reload">重试</a>');
+                    demoText.find('.demo-reload').on('click', function () {
+                        uploadInst.upload();
+                    });
+                }
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 237 - 0
app/admin/view/catalog/index.html

@@ -0,0 +1,237 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<style type="text/css">
+    .laytable-cell-1-action {
+        height: 22px;
+    }
+
+    .layui-icon.layui-tree-head {
+        line-height: initial;
+    }
+</style>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">栏目管理</li>
+            <li><a href="{:url('publish')}" class="a_menu">新增栏目</a></li>
+            <li><a href="javascript:filemanage()">管理HTML</a></li>
+        </ul>
+    </div>
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            <a class="layui-btn layui-btn-sm" lay-event="sort">排序</a>
+            <a class="layui-btn layui-btn-sm layui-btn-primary" lay-event="switch-tree"><i
+                    class="layui-icon layui-icon-triangle-r"></i>收缩
+            </a>
+            <a class="layui-btn layui-btn-sm" lay-event="exportHtml_cover" title="生成HTML并覆盖旧文件"><i
+                    class="layui-icon">&#xe609;</i>生成HTML
+            </a>
+        </div>
+    </script>
+
+
+    <form class="layui-form" id="admin">
+        <table class="layui-table layui-form" id="treeTable" lay-filter="treeTable"></table>
+    </form>
+
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+
+        layui.extend({
+            treeGrid: 'tree_table_treegrid/treeGrid' //拓展一个模块别名
+        })
+
+        var table = null, tableId = 'treeTable';
+
+        layui.use(['jquery', 'treeGrid', 'layer'], function () {
+            var $ = layui.jquery;
+            table = layui.treeGrid;//很重要
+            layer = layui.layer;
+
+            //第一个实例
+            var ptable = table.render({
+                id: tableId,
+                elem: '#' + tableId,
+                url: '{:url("index")}', //数据接口
+                cellMinWidth: 100,
+                treeId: 'id', //树形id字段名称
+                treeUpId: 'pid', //树形父id字段名称
+                treeShowName: 'title', //以树形式显示的字段
+                size: 'sm',  //小尺寸的表格
+                toolbar: '#toolbarDemo',
+//                defaultToolbar: [],//此参数无效
+                page: false,
+                cols: [[
+                    {
+                        width: '60', field: 'sort', title: '排序', align: 'center', templet: function (row) {
+                        return '<input type="text" name="sorts[]" value="' + row.sort + '" style="width: 20px;" class="sort"><input type="hidden" name="ids[]" value="' + row.id + '">';
+                    }
+                    },
+                    {type: 'checkbox'},
+                    {width: '60', field: 'id', title: 'ID'},
+                    {
+                        width: '250', field: 'title', title: '栏目名称', template: function (row) {
+                        return '<a href="./perview?id=' + row.id + '" target="_blank" title="单击预览">' + row.title + '</a>'
+                    }
+                    },
+                    {field: 'type_text', title: '类型', width: '80'},
+                    {
+                        field: 'catalog_templet', title: '栏目模板', width: '90', templet: function (row) {
+                        return row.catalog_templet ? "<a href='javascript:;' title='单击编辑' onclick=\"fileEdit('" + row.catalog_templet + "')\">" + '<i class="fa fa-file-code-o fa-lg"/> ' + row.catalog_templet + "</a>" : '';
+                    }
+                    },
+                    {
+                        field: 'article_templet', title: '文章模板', width: '90', templet: function (row) {
+                        return row.article_templet ? "<a href='javascript:;' title='单击编辑' onclick=\"fileEdit('" + row.article_templet + "')\">" + '<i class="fa fa-file-code-o fa-lg"/> ' + row.article_templet + "</a>" : '';
+                    }
+                    },
+                    {width: '200', field: 'path', title: '栏目路径'},
+                    {width: '250', field: 'articlelist_rule', title: '列表路径'},
+                    {width: '250', field: 'article_rule', title: '文章路径'},
+                    {width: '100', field: 'article_count', title: '文章数'},
+//                    {field: 'tpath', title: '发布路径', width: '100'},
+//                    {field: 'tree_path', title: 'TREE_PATH', width: '100'},
+                    {
+                        field: 'status', title: '审核', align: 'center', width: '60', templet: function (row) {
+                        return '<a href="javascript:;" style="font-size:18px;" class="status" data-id="' + row.id + '" data-val="' + row.status + '">' + (row.status == 1 ? '<i class="fa fa-toggle-on"></i>' : '<i class="fa fa-toggle-off"></i>') + '</a>';
+                    }
+                    },
+                    {
+                        field: 'action', title: '操作', align: 'center', width: '120',
+                        templet: function (item) {
+                            var editBtn = '<a href="publish?id=' + item.id + '" class="layui-btn layui-btn-xs a_menu" style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>';
+                            var addBtn = "";
+                            if (item.type == 2) {
+                                addBtn = '<a href="publish?pid=' + item.id + '" class="layui-btn layui-btn-xs a_menu" style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>';
+                            }
+                            var delBtn = '<a class="layui-btn layui-btn-xs a_menu" lay-event="del" style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>';
+                            return '<div class="layui-btn-group">' + editBtn + addBtn + delBtn + '</div>';
+                        }
+                    },
+                ]],
+                done: function (e) {
+                    //审核
+                    switchStatus('.status', "{:url('status')}");
+                },
+            });
+
+            table.on('tool(' + tableId + ')', function (obj) {
+                if (obj.event === 'del') {
+                    var id = obj.data.id;
+                    var title = obj.data.title;
+                    layer.confirm('确定删除 ? ' + title, function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            data: {id: id},
+                            dataType: 'json',
+                            type: 'post',
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    setTimeout(function () {
+                                        location.href = res.url;
+                                    }, 1500)
+                                }
+                            }
+                        })
+                    })
+                }
+            });
+
+            //监听事件
+            table.on('toolbar(' + tableId + ')', function (obj) {
+                if (obj.event == 'exportHtml_cover') {
+                    var checkStatus = table.checkStatus(tableId);//获取选中的数据
+                    var data = checkStatus.data;
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            ids[key] = item.id;
+                        })
+                        var load = layer.load(1, {
+                            shade: [0.1, '#fff'] //0.1透明度的白色背景
+                        });
+                        $.ajax({
+                            url: "{:url('exportHtml')}?cover=true",
+                            data: {ids: ids},
+                            type: 'post',
+                            dataType: 'json',
+                            success: function (res) {
+                                layer.alert(res.msg, function (index) {
+                                    layer.msg(res.msg);
+                                })
+                            },
+                            complete: function () {
+                                layer.close(load);
+                            }
+                        })
+                    } else {
+                        layer.msg('请先勾选需要操作的记录');
+                    }
+                }
+                else if (obj.event == 'sort') {
+                    $.ajax({
+                        url: "{:url('sort')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.reload();
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                }
+                else if (obj.event == 'switch-tree') {
+
+                    if (obj.ele.text() == "展开") {
+                        table.treeNodeOpenAll(tableId);
+                        obj.ele.html('<i class="layui-icon layui-icon-triangle-r"></i>收缩')
+                    } else {
+                        table.treeNodeCloseAll(tableId);
+                        obj.ele.html('<i class="layui-icon layui-icon-triangle-d"></i>展开')
+                    }
+                }
+            });
+
+        });
+
+        function filemanage() {
+            window.parent.tab.tabAdd({
+                icon: "fa-bookmark",
+                id: 'filemanage',
+                title: "管理HTML",
+                url: "{:url('admin/file_manage/index')}"
+            });
+        }
+        function fileEdit(filename) {
+            window.parent.tab.tabAdd({
+                icon: "fa-bookmark",
+                id: 'templet' + filename,
+                title: '<i class="fa fa-file-code-o"/> ' + filename,
+                url: "{:url('admin/templet/publish')}?filename=" + filename
+            });
+        }
+
+    </script>
+</div>
+</body>
+</html>

+ 254 - 0
app/admin/view/catalog/publish.html

@@ -0,0 +1,254 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="a_menu"><a href="{:url('index')}">栏目管理</a></li>
+            <li class="layui-this">{notempty name="$item"}编辑{else/}新增{/notempty}栏目</li>
+        </ul>
+    </div>
+
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="publish" method="post" lay-filter="myform">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">上级节点</label>
+            <div class="layui-input-inline">
+                <select name="pid" lay-filter="aihao" lay-search="">
+                    <option value="0">作为顶级节点</option>
+                    {volist name="$cates" id="vo"}
+                    <option value="{$vo.id}"
+                            {notempty name="$item.pid" }
+                            {eq name="$item.pid" value="$vo.id" } selected="" {/eq}
+                    {else /}
+                    {notempty name="$pid"}
+                    {eq name="$pid" value="$vo.id"}selected=""{/eq}
+                    {/notempty}
+                    {/notempty}>{$vo.str}{$vo.title}</option>
+                    {/volist}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">节点名称</label>
+            <div class="layui-input-inline">
+                <input id="title" name="title" lay-verify="required" autocomplete="off" placeholder="请输入"
+                       class="layui-input"
+                       type="text" {notempty name="$item.title" }value="{$item.title}" {/notempty}>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">节点类型</label>
+            <div class="layui-input-inline" style="max-width:300px;">
+                <select name="type" lay-filter="type">
+                    {foreach $types as $k=>$v}
+                    <option value="{$k}" {notempty name="$item"}{eq name="$item.type" value="$k"}selected=""{/eq}{/notempty}>{$v}</option>
+                    {/foreach}
+                </select>
+            </div>
+            <div class="layui-form-mid layui-word-aux">文章列表才可发布文章</div>
+        </div>
+
+        <div class="layui-form-item type_catalog type_article">
+            <label class="layui-form-label">栏目模板</label>
+            <div class="layui-input-inline" style="max-width:300px;">
+                <select name="catalog_templet" lay-search="">
+                    <option value="">请选择模板</option>
+                    {volist name="$templets" id="vo"}
+                    <option value="{$vo.filename}" {notempty name="$item" } {eq name="$item.catalog_templet" value="$vo.filename" } selected="" {/eq}{/notempty}>{$vo.filename}</option>
+                    {/volist}
+                </select>
+            </div>
+            <div class="layui-form-mid layui-word-aux">用于生成 “栏目页” 或 "列表页"</div>
+        </div>
+
+        <div class="layui-form-item type_article">
+            <label class="layui-form-label">文章模板</label>
+            <div class="layui-input-inline" style="max-width:300px;">
+                <select name="article_templet" lay-search="">
+                    <option value="">请选择模板</option>
+                    {volist name="$templets" id="vo"}
+                    <option value="{$vo.filename}" {notempty name="$item" } {eq name="$item.article_templet" value="$vo.filename" } selected="" {/eq}{/notempty}>{$vo.filename}</option>
+                    {/volist}
+                </select>
+            </div>
+            <div class="layui-form-mid layui-word-aux">用于生成 “文章页”</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">栏目路径</label>
+            <div class="layui-input-inline" style="min-width:300px;">
+                <input id="path" name="path" autocomplete="off" placeholder="请输入" class="layui-input" type="text"
+                       value="{notempty name=" $item.path"}{$item.path}{/notempty}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">格式:栏目页:/{栏目英文名}.html , 文章列表:/{栏目英文名}/index.html</div>
+        </div>
+
+        <div class="layui-form-item type_article">
+            <label class="layui-form-label">列表路径</label>
+            <div class="layui-input-inline" style="min-width:300px;">
+                <input id="articlelist_rule" name="articlelist_rule" autocomplete="off" placeholder="请输入路径规则"
+                       class="layui-input" type="text"
+                       value="{notempty name=" $item.articlelist_rule"}{$item.articlelist_rule}{/notempty}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">变量:{cpath}=栏目目录,{tpath}=分类路径,{page}=页码,{cid}=栏目id,{tid}=分类id
+            </div>
+        </div>
+
+        <div class="layui-form-item type_article">
+            <label class="layui-form-label">文章路径</label>
+            <div class="layui-input-inline" style="min-width:300px;">
+                <input id="article_rule" name="article_rule" autocomplete="off" placeholder="请输入路径规则"
+                       class="layui-input" type="text"
+                       value="{notempty name=" $item.article_rule"}{$item.article_rule}{/notempty}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">
+                变量:{cpath}=栏目目录,{tpath}=分类路径,{aid}=文章id,{cid}=栏目id,{tid}=分类id,{Y}{M}{D}=年月日
+            </div>
+        </div>
+
+
+        {notempty name="$item"}
+        <input type="hidden" name="id" value="{$item.id}">
+        {/notempty}
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+            </div>
+        </div>
+    </form>
+
+
+    <script src="__PUBLIC__/layui/layui.js"></script>
+    <script src="__PUBLIC__/jquery/jquery.min.js"></script>
+    <script src="__PUBLIC__/pinyin/ChinesePY.js?v=2"></script>
+
+    <script>
+        //切换到目录
+        function switch_dir() {
+            $(".type_article").hide();
+            $(".type_catalog").hide();
+            $('#path').parent().next().text('可设置下级节点的父路径')
+        }
+        //切换到栏目
+        function switch_catalog() {
+            $(".type_article").hide();
+            $(".type_catalog").show();
+            $('#path').parent().next().text('格式:/{栏目英文名}.html')
+        }
+        //切换到文章列表
+        function switch_article() {
+            $(".type_catalog").hide();
+            $(".type_article").show();
+            $('#path').parent().next().text('格式:/{栏目英文名}/index.html')
+        }
+        //默认显示目录
+        switch_dir();
+    </script>
+
+    {notempty name="$item"}
+    {eq name="$item.type" value="1"}
+    <script>
+        switch_article();
+    </script>
+    {/eq}
+    {eq name="$item.type" value="0"}
+    <script>
+        switch_catalog();
+    </script>
+    {/eq}
+    {/notempty}
+
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('publish')}",
+                        data: $('#publish').serialize(),
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+
+                //切换栏目类型
+                form.on('select(type)', function (data) {
+                    var name = $('#title').val();
+                    if (name == null || name == "") {
+                        layer.msg("请先填写栏目名称");
+                        name = "null";
+                        if (data.value != 2) {
+                            form.val("myform", {"type": 2});//改回2
+                            return false;
+                        }
+                    }
+                    //栏目
+                    if (data.value == 0) {
+                        switch_catalog();
+                        if ($('#path').val() == "") {
+                            var ename = getPinYin(name);
+                            $('#path').val("/" + ename + ".html");
+                        }
+                    }
+                    //文章列表
+                    if (data.value == 1) {
+                        switch_article();
+                        if ($('#path').val() == "") {
+                            var ename = getPinYin(name);
+                            $('#path').val("/" + ename + "/index.html");
+                        }
+                        if ($('#article_rule').val() == "") {
+                            $('#article_rule').val("{cpath}/{tpath}/{Y}{M}{D}/{aid}.html");
+                        }
+                        if ($('#articlelist_rule').val() == "") {
+                            $('#articlelist_rule').val("{cpath}/{tpath}/list_{tid}_{page}.html");
+                        }
+                    }
+                    //目录
+                    if (data.value == 2) {
+                        switch_dir();
+                    }
+                });
+
+                function getPinYin(name) {
+                    if ('首页' == name) {
+                        return 'index';
+                    }
+                    return Pinyin.GetJP(name);
+                }
+            });
+        });
+    </script>
+
+</div>
+</body>
+</html>

+ 227 - 0
app/admin/view/code_generation/index.html

@@ -0,0 +1,227 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<style>
+    .layui-form-item .layui-form-checkbox[lay-skin=primary] {
+        margin-top: auto;
+    }
+
+    .layui-table-cell, .layui-table-box, .layui-table-body {
+        overflow: visible;
+    }
+</style>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">后台生成</li>
+            <li><a href="{:url('admin/api_generation/index')}" class="a_menu">接口生成</a></li>
+        </ul>
+    </div>
+
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">数据表</label>
+            <div class="layui-input-inline">
+                <select name="table" lay-search="" lay-filter="tablename" lay-verify="required">
+                    <option value="">请选择</option>
+                    {foreach name="$tables" item="vo" key="k"}
+                    <option value="{$vo.TABLE_NAME}|{$vo.TABLE_COMMENT}"
+                    >{$vo.TABLE_NAME}({$vo.TABLE_COMMENT|default="无备注"})
+                    </option>
+                    {/foreach}
+                </select>
+            </div>
+            <div class="layui-form-mid layui-word-aux">选择要生成的表</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">后台功能</label>
+            <div class="layui-input-block">
+                <input type="checkbox" name="crud[create]" title="新增" checked>
+                <input type="checkbox" name="crud[delete]" title="删除" checked>
+                <input type="checkbox" name="crud[update]" title="修改" checked>
+                <input type="checkbox" name="crud[select]" title="查询" checked disabled>
+            </div>
+            <div class="layui-form-mid layui-word-aux">选择生成对应的后台功能,生成后须手动配置菜单和权限</div>
+        </div>
+
+        <div class="layui-form-item">
+            <div class="layui-input-block" style="display:none;" id="table_div">
+                <table class="layui-table" id="table" lay-filter="table"></table>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即生成</button>
+            </div>
+        </div>
+    </form>
+
+    {include file="public/foot"}
+    <script>
+        layui.use(['table', 'layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                table = layui.table,
+                form = layui.form;
+
+            var tableIns = table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'lg' //小尺寸的表格
+                , defaultToolbar: []
+                , page: false //开启分页
+                , text: {none: '请先选择一个表'}
+                , cols: [[ //表头
+                    {field: 'Field', title: '字段名称', minWidth: 60},
+                    {field: 'Type', title: '数据类型', minWidth: 80},
+                    {field: 'Default', title: '默认值', minWidth: 80},
+                    {field: 'Comment', title: '注释', minWidth: 80},
+                    {
+                        field: 'ShowList', title: '列表显示', align: 'center', width: 100, templet: function (row) {
+                        var unuse = ['password'];
+                        if (unuse.indexOf(row.Field) >= 0 || row.Type == 'text') {
+                            return '<input type="checkbox" name="' + row.Field + '_ShowList" lay-skin="primary">';
+                        }
+                        return '<input type="checkbox" name="' + row.Field + '_ShowList" lay-skin="primary" checked>';
+                    }
+                    },
+                    {
+                        field: 'ShowSearch', title: '支持查询', align: 'center', width: 100, templet: function (row) {
+                        var unuse = ['password', 'thumb', 'image', 'sort', 'update_time'];
+                        if (unuse.indexOf(row.Field) >= 0 || row.Type == 'text') {
+                            return '<input type="checkbox" name="' + row.Field + '_ShowSearch" lay-skin="primary">';
+                        }
+                        return '<input type="checkbox" name="' + row.Field + '_ShowSearch" lay-skin="primary" checked>';
+                    }
+                    },
+                    {
+                        field: 'ShowEdit', title: '表单显示', align: 'center', width: 100, templet: function (row) {
+                        var unuse = ['id', 'ip', 'create_time', 'update_time', 'sort'];
+                        if (unuse.indexOf(row.Field) >= 0) {
+                            return '<input type="checkbox" name="' + row.Field + '_ShowEdit" lay-skin="primary">';
+                        }
+                        return '<input type="checkbox" name="' + row.Field + '_ShowEdit" lay-skin="primary" checked>';
+                    }
+                    },
+                    {
+                        field: 'Component', title: '表单组件', minWidth: 100, templet: function (row) {
+                        return buildComponentSelect(row);
+                    }
+                    },
+                ]]
+            });
+
+            form.on('submit(admin)', function (data) {
+                $.ajax({
+                    url: "{:url('generation')}",
+                    data: $('#admin').serialize(),
+                    type: 'post',
+                    dataType: 'json',
+                    async: false,
+                    success: function (res) {
+                        if (1 == res.code) {
+                            layer.alert('执行成功')
+                        } else {
+                            layer.confirm(res.msg, {icon: 3, title: '覆盖前请备份文件!'}, function (index) {
+                                $.ajax({
+                                    url: "{:url('generation')}",
+                                    data: $('#admin').serialize() + '&cover=true',
+                                    type: 'post',
+                                    dataType: 'json',
+                                    async: false,
+                                    success: function (res) {
+                                        if (res.code == 1) {
+                                            layer.alert('执行成功')
+                                        } else {
+                                            layer.alert('执行失败')
+                                        }
+                                    }
+                                })
+                            });
+                        }
+                    }
+                })
+                return false;
+            });
+
+            form.on('select(tablename)', function (data) {
+                if (data.value) {
+                    $("#table_div").show();
+                    tableIns.reload({
+                        url: "{:url('getFieldsInfo')}",
+                        where: {table: data.value},
+                    })
+                } else {
+                    $("#table_div").hide();
+                }
+            });
+        });
+
+        function getCheckOption(row) {
+            var check_option = '输入框';
+            if (row.Type == 'text') {
+                return '纯文本段落';
+            }
+            if (row.Type == 'float') {
+                return '数字';
+            }
+            if (row.Type.indexOf('int') >= 0) {
+                check_option = '数字';
+                if (row.Field.indexOf('thumb') >= 0 || row.Field.indexOf('image') >= 0) {
+                    return '图片';
+                }
+                if (row.Field.indexOf('time') >= 0) {
+                    return '时间';
+                }
+                if (row.Type == 'int(1)' || row.Type == 'tinyint(1)') {
+                    return '开关';
+                }
+                if(row.Comment.indexOf(',') >= 0){
+                    if(row.Field.indexOf('radio') >= 0){
+                        return '单选框';
+                    }
+                    return '下拉框';
+                }
+            }
+            if(row.Field.indexOf('checkbox') >= 0){
+                return '复选框';
+            }
+            return check_option;
+        }
+        function buildComponentSelect(row) {
+
+            var check_option = getCheckOption(row);
+            var options = ['输入框', '数字', '时间', '纯文本段落', '图片', '开关', '下拉框', '复选框', '单选框', '文章编辑器'];
+
+            var html = '<select name="' + row.Field + '_Component" lay-search="" lay-filter=""><option value="">请选择</option>';
+
+            for (var i in options) {
+                if (check_option == options[i]) {
+                    html += '<option value="' + i + '" selected>' + options[i] + '</option>';
+                } else {
+                    html += '<option value="' + i + '">' + options[i] + '</option>';
+                }
+            }
+            html += '</select>';
+            return html;
+        }
+    </script>
+</div>
+</body>
+</html>

+ 250 - 0
app/admin/view/code_generation/tpl/AdminController.php.tp

@@ -0,0 +1,250 @@
+
+namespace app\admin\controller;
+
+use app\admin\controller\base\Permissions;
+use think\Db;
+
+class {$humpName} extends Permissions
+{
+    private function getModel()
+    {
+        return new \app\common\model\{$humpName}();
+    }
+
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $where = [];
+            {volist name="$fieldsInfo" id="vo"}
+                {php}
+                    if(empty($vo['ShowSearch'])){
+                        continue;
+                    }
+                {/php}
+
+                {if condition="$vo['Field'] == 'id'"}
+                    if (isset($post['ids']) and !empty($post['ids'])) {
+                        $where['id'] = ['in', $post['ids']];
+                    }
+                    {php}continue;{/php}
+                {/if}
+
+                {if condition="in_array($vo['Component'],[0,3,4,7])"}
+                    {php}//输入框,文章段落,图片,复选框{/php}
+                    if (!empty($post["{$vo['Field']}"])) {
+                        $where["{$vo['Field']}"] = ['like', '%' . $post["{$vo['Field']}"] . '%'];
+                    }
+                    {php}continue;{/php}
+                {/if}
+
+                {if condition="in_array($vo['Component'],[1,5,6,8])"}
+                    {php}//数字,开关,下拉框,单选{/php}
+                    if (isset($post["{$vo['Field']}"]) and ""!=$post["{$vo['Field']}"]) {
+                        $where["{$vo['Field']}"] = $post["{$vo['Field']}"];
+                    }
+                    {php}continue;{/php}
+                {/if}
+
+                {if condition="$vo['Component'] == 2"}
+                    {php}//时间{/php}
+                    if (isset($post["{$vo['Field']}"]) and !empty($post["{$vo['Field']}"])) {
+                        $timerang = explode(' - ', $post["{$vo['Field']}"]);
+                        $min_time = strtotime($timerang[0]);
+                        $max_time = $timerang[0] == $timerang[1] ? $min_time + 24 * 3600 - 1 : strtotime($timerang[1]??'');
+                        $where["{$vo['Field']}"] = [['>=', $min_time], ['<=', $max_time]];
+                    }
+                    {php}continue;{/php}
+                {/if}
+            {/volist}
+
+            $model = $this->getModel();
+            $count = $model->where($where)->count();
+            $data = $model->where($where)->page($post['page']??0, $post['limit']??15)->order({if condition="array_key_exists('sort',$fieldsInfo)"}'sort desc'{else/}'id desc'{/if})->select();
+
+            {volist name="$fieldsInfo" id="vo"}
+                {if condition="$vo['Component'] == 4"}
+                    foreach ($data as $key => $value) {
+                        $value['{$vo['Field']}_url'] = geturl($value['{$vo['Field']}']);
+                        $data[$key] = $value;
+                    }
+                {/if}
+            {/volist}
+            return array('code' => 0, 'count' => $count, 'data' => $data);
+        } else {
+            return $this->fetch();
+        }
+    }
+
+
+{if condition="array_key_exists('update',$crud) or array_key_exists('create',$crud)"}
+    public function publish()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        $model = $this->getModel();
+        $post = $this->request->post();
+        if ($this->request->isPost()) {
+
+            {volist name="$fieldsInfo" id="vo"}
+                {php}
+                    if(empty($vo['ShowEdit'])){
+                        continue;
+                    }
+                    if(in_array($vo['Field'],['id'])){
+                        continue;
+                    }
+                {/php}
+                {if condition="$vo['Component'] == 2"}
+                    if (isset($post['{$vo['Field']}']) && !empty($post['{$vo['Field']}'])) {
+                        $post['{$vo['Field']}'] = strtotime($post['{$vo['Field']}']);
+                    }
+                {/if}
+                {if condition="$vo['Component'] == 7"}
+                    if (isset($post['{$vo['Field']}']) && !empty($post['{$vo['Field']}'])) {
+                        $post['{$vo['Field']}'] = implode(',',$post['{$vo['Field']}']);
+                    }
+                {/if}
+            {/volist}
+
+            //验证
+            $validate = new \think\Validate([
+                {volist name="$fieldsInfo" id="vo"}
+                    {php}
+                        if(empty($vo['ShowEdit'])){
+                            continue;
+                        }
+                        $field_key = $vo['Field'];
+                        if(in_array($field_key,['id','create_time','update_time'])){
+                            continue;
+                        }
+                        if(!empty($vo['Comment'])){
+                            $comment = $vo['Comment'];
+                            if(in_array($vo['Component'],[5,6,7,8])){
+                                $arr = explode(':',$vo['Comment']);
+                                $comment = count($arr)==2 ? $arr[0] : $vo['Field'];
+                            }
+                            $field_key.= "|" . $comment;
+                        }
+                        $field_val = "";
+                        if($vo['Default'] === null){
+                            $field_val.= "require";
+                        }
+                        if(startWith($vo["Type"],'int') || startWith($vo["Type"],'tinyint')){
+                            $field_val .= empty($field_val) ? "number" : "|number";
+                        }
+                        if(startWith($vo["Type"],'varchar')){
+                            $maxLen = str_replace(['varchar(',')'],['',''],$vo["Type"]);
+                            $field_val .= empty($field_val) ? "max:".$maxLen : "|max:".$maxLen;
+                        }
+                    {/php}
+                    {notempty name="$field_val"}
+                    ['{$field_key}', '{$field_val}'],
+                    {/notempty}
+                {/volist}
+            ]);
+            if (!$validate->check($post)) {
+                $this->error('提交失败:' . $validate->getError());
+            }
+        }
+
+        if ($id > 0) {
+            {if condition="array_key_exists('update',$crud)"}
+                //修改
+                $data = $model->where('id', $id)->find();
+                if (empty($data)) {
+                    $this->error('id不正确');
+                }
+                if ($this->request->isPost()) {
+                    if (false == $model->allowField(true)->save($post, ['id' => $id])) {
+                        $this->error('修改失败');
+                    } else {
+                        $this->success('修改成功');
+                    }
+                } else {
+                    $this->assign('data', $data);
+                    return $this->fetch();
+                }
+            {/if}
+        } else {
+            {if condition="array_key_exists('create',$crud)"}
+                //新增
+                if ($this->request->isPost()) {
+                    if (false == $model->allowField(true)->save($post)) {
+                        $this->error('添加失败');
+                    } else {
+                        $this->success('添加成功', 'index');
+                    }
+                } else {
+                    return $this->fetch();
+                }
+            {/if}
+        }
+    }
+{/if}
+
+{if condition="array_key_exists('delete',$crud)"}
+    public function delete()
+    {
+        if ($this->request->isAjax()) {
+            $id = $this->request->param('id', 0, 'intval');
+            if (false == $this->getModel()->where('id', $id)->delete()) {
+                $this->error('删除失败');
+            } else {
+                $this->success('删除成功', 'index');
+            }
+        }
+    }
+
+    public function deletes()
+    {
+        if ($this->request->isAjax()) {
+            $post = $this->request->param();
+            $ids = $post['ids'];
+            if ($this->getModel()->where('id', 'in', $ids)->delete()) {
+                $this->success('删除成功');
+            }
+        }
+    }
+{/if}
+
+
+{volist name="$fieldsInfo" id="vo"}
+    {if condition="$vo['Component'] == 5"}
+        public function {$vo['Field']}()
+        {
+            if ($this->request->isPost()) {
+                $post = $this->request->post();
+                if (false == $this->getModel()->where('id', $post['id'])->update(['{$vo['Field']}' => $post['status']])) {
+                    $this->error('设置失败');
+                } else {
+                    $this->success('设置成功', 'index');
+                }
+            }
+        }
+    {/if}
+{/volist}
+
+
+{if condition="array_key_exists('sort',$fieldsInfo)"}
+    public function sort()
+    {
+        if ($this->request->isPost()) {
+            $post = $this->request->post();
+            $i = 0;
+            foreach ($post['ids'] as $k => $id) {
+                $sort = Db::name('{$underLineName}')->where('id', $id)->value('sort');
+                $newsort = $post['sorts'][$k]??$sort;
+                if ($sort != $newsort) {
+                    if (false == Db::name('{$underLineName}')->where('id', $id)->update(['sort' => $newsort])) {
+                        $this->error('更新失败');
+                    } else {
+                        $i++;
+                    }
+                }
+            }
+            $this->success('成功更新' . $i . '个数据', 'index');
+        }
+    }
+{/if}
+
+}

+ 165 - 0
app/admin/view/code_generation/tpl/ApiController.php.tp

@@ -0,0 +1,165 @@
+
+namespace app\api\controller;
+
+use app\api\controller\base\Base;
+
+class {$humpName} extends Base
+{
+    private function getModel()
+    {
+        return new \app\common\model\{$humpName}();
+    }
+
+{if condition="array_key_exists('select',$crud)"}
+    public function index()
+    {
+        $post = $this->request->param();
+        $validate = new \think\Validate([
+            ['id', 'number'],
+            ['page', 'number'],
+            ['pagenum', 'number|<=:1000']
+        ]);
+        if (!$validate->check($post)) {
+            $this->json_error('提交失败:' . $validate->getError());
+        }
+
+        $where = [];
+        if (isset($post['id'])) {
+            $where['id'] = $post['id'];
+        }
+
+        if (isset($post['id'])) {
+            $datalist = ($this->getModel())->where($where)->find();
+        } else {
+            $pagenum = $this->request->param('pagenum', 20, 'intval');
+            $datalist = ($this->getModel())->where($where)->paginate($pagenum, true);
+        }
+        if (empty($datalist)) {
+            $this->json_error("没有数据");
+        }
+        $this->json_success("查询成功", $datalist);
+    }
+{/if}
+
+{if condition="array_key_exists('create',$crud)"}
+    public function create()
+    {
+        $post = $this->request->param();
+        //验证
+        $validate = new \think\Validate([
+        {volist name="$fieldsInfo" id="vo"}
+            {php}
+                $field_key = $vo['Field'];
+                if(in_array($field_key,['id','create_time','update_time'])){
+                    continue;
+                }
+                if(!empty($vo['Comment'])){
+                    $comment = $vo['Comment'];
+                    $field_key.= "|" . $comment;
+                }
+                $field_val = "";
+                if($vo['Default'] === null){
+                    $field_val.= "require";
+                }
+                if(startWith($vo["Type"],'int') || startWith($vo["Type"],'tinyint')){
+                    $field_val .= empty($field_val) ? "number" : "|number";
+                }
+                if(startWith($vo["Type"],'varchar')){
+                    $maxLen = str_replace(['varchar(',')'],['',''],$vo["Type"]);
+                    $field_val .= empty($field_val) ? "max:".$maxLen : "|max:".$maxLen;
+                }
+            {/php}
+            {notempty name="$field_val"}
+            ['{$field_key}', '{$field_val}'],
+            {/notempty}
+         {/volist}
+        ]);
+        if (!$validate->check($post)) {
+            $this->error('提交失败:' . $validate->getError());
+        }
+
+        $model = $this->getModel();
+        $res = $model->allowField(true)->save($post);
+        if ($res) {
+            $this->json_success("创建成功", $model);
+        } else {
+            $this->json_error("创建失败");
+        }
+    }
+{/if}
+
+{if condition="array_key_exists('delete',$crud)"}
+    public function delete()
+    {
+        $post = $this->request->param();
+        $validate = new \think\Validate([
+            ['id', 'require|number'],
+        ]);
+        if (!$validate->check($post)) {
+            $this->json_error('提交失败:' . $validate->getError());
+        }
+
+        $item = ($this->getModel())->where(['id' => $post['id']])->find();
+        if (!$item) {
+            $this->json_error('找不到该id');
+        }
+
+        if (!$item->delete()) {
+            $this->json_error('删除失败');
+        }
+        $this->json_success('删除成功');
+    }
+{/if}
+
+{if condition="array_key_exists('update',$crud)"}
+    public function update()
+    {
+        $post = $this->request->param();
+        //验证
+        $validate = new \think\Validate([
+            ['id', 'require|number'],
+        {volist name="$fieldsInfo" id="vo"}
+            {php}
+                $field_key = $vo['Field'];
+                if(in_array($field_key,['id','create_time','update_time'])){
+                    continue;
+                }
+                if($vo['Default'] === null){
+                    continue;
+                }
+                if(!empty($vo['Comment'])){
+                    $comment = $vo['Comment'];
+                    $field_key.= "|" . $comment;
+                }
+                $field_val = "";
+                if(startWith($vo["Type"],'int') || startWith($vo["Type"],'tinyint')){
+                    $field_val .= empty($field_val) ? "number" : "|number";
+                }
+                if(startWith($vo["Type"],'varchar')){
+                    $maxLen = str_replace(['varchar(',')'],['',''],$vo["Type"]);
+                    $field_val .= empty($field_val) ? "max:".$maxLen : "|max:".$maxLen;
+                }
+            {/php}
+            {notempty name="$field_val"}
+            ['{$field_key}', '{$field_val}'],
+            {/notempty}
+         {/volist}
+        ]);
+        if (!$validate->check($post)) {
+            $this->json_error('提交失败:' . $validate->getError());
+        }
+
+        $item = ($this->getModel())->where(['id' => $post['id']])->find();
+        if (!$item) {
+            $this->json_error('找不到该id');
+        }
+
+        if ($item->allowField(true)->save($post)) {
+            $this->json_success("修改成功");
+        } else {
+            $this->json_error("修改失败");
+        }
+    }
+{/if}
+
+}

+ 145 - 0
app/admin/view/code_generation/tpl/ApiDoc.md.tp

@@ -0,0 +1,145 @@
+
+{if condition="array_key_exists('select',$crud)"}
+# 查询{$menuName}
+
+接口地址:/api/{$underLineName}/index
+
+请求方式:get / post
+
+请求数据:
+```
+id      | 指定ID,则只返回一条数据 |  int |
+page    | 第几页,默认1           |  int |
+pagenum | 每页几条,默认20        |  int,<=:1000 |
+```
+
+响应数据:(json格式)
+```
+code int 状态,1代表成功,0代表失败
+msg  string 消息
+time int 时间戳
+data json 返回数据
+```
+{/if}
+
+
+{if condition="array_key_exists('create',$crud)"}
+# 新增{$menuName}
+
+接口地址:/api/{$underLineName}/create
+
+请求方式:get / post
+
+请求数据:
+```
+     {volist name="$fieldsInfo" id="vo"}
+       {php}
+           $field_key = $vo['Field'];
+           if(in_array($field_key,['id','create_time','update_time'])){
+               continue;
+           }
+           if(!empty($vo['Comment'])){
+               $comment = $vo['Comment'];
+               $field_key.= " | " . $comment;
+           }
+           $field_val = "";
+           if($vo['Default'] === null){
+               $field_val.= "require";
+           }
+           if(startWith($vo["Type"],'int') || startWith($vo["Type"],'tinyint')){
+               $field_val .= empty($field_val) ? "int" : ",int";
+           }
+           if(startWith($vo["Type"],'varchar')){
+               $maxLen = str_replace(['varchar(',')'],['',''],$vo["Type"]);
+               $field_val .= empty($field_val) ? "max:".$maxLen : ",max:".$maxLen;
+           }
+       {/php}
+
+       {notempty name="$field_val"}
+  {$field_key} |  {$field_val} |
+       {/notempty}
+
+    {/volist}
+
+```
+
+响应数据:(json格式)
+```
+code int 状态,1代表成功,0代表失败
+msg string 消息
+time int 时间戳
+data json 返回数据
+```
+{/if}
+
+{if condition="array_key_exists('delete',$crud)"}
+# 删除{$menuName}
+
+接口地址:/api/{$underLineName}/delete
+
+请求方式:get / post
+
+请求数据:
+```
+ id | 数据ID |  int |
+```
+
+响应数据:(json格式)
+```
+code int 状态,1代表成功,0代表失败
+msg string 消息
+time int 时间戳
+data json 返回数据
+```
+{/if}
+
+
+{if condition="array_key_exists('update',$crud)"}
+# 修改{$menuName}
+
+接口地址:/api/{$underLineName}/update
+
+请求方式:get / post
+
+请求数据:
+```
+          id | 数据ID |  int |
+     {volist name="$fieldsInfo" id="vo"}
+       {php}
+           $field_key = $vo['Field'];
+           if(in_array($field_key,['id','create_time','update_time'])){
+               continue;
+           }
+           if($vo['Default'] === null){
+              continue;
+           }
+           if(!empty($vo['Comment'])){
+               $comment = $vo['Comment'];
+               $field_key.= " | " . $comment;
+           }
+           $field_val = "";
+           if(startWith($vo["Type"],'int') || startWith($vo["Type"],'tinyint')){
+               $field_val .= empty($field_val) ? "int" : ",int";
+           }
+           if(startWith($vo["Type"],'varchar')){
+               $maxLen = str_replace(['varchar(',')'],['',''],$vo["Type"]);
+               $field_val .= empty($field_val) ? "max:".$maxLen : ",max:".$maxLen;
+           }
+       {/php}
+
+       {notempty name="$field_val"}
+  {$field_key} |  {$field_val} |
+       {/notempty}
+
+    {/volist}
+
+```
+
+响应数据:(json格式)
+```
+code int 状态,1代表成功,0代表失败
+msg string 消息
+time int 时间戳
+data json 返回数据
+```
+{/if}

+ 40 - 0
app/admin/view/code_generation/tpl/Model.php.tp

@@ -0,0 +1,40 @@
+
+namespace app\common\model;
+
+use think\Model;
+
+class {$humpName} extends Model
+{
+    {php}
+        if(!array_key_exists('create_time',$fieldsInfo) && !array_key_exists('update_time',$fieldsInfo)){
+            echo 'protected $autoWriteTimestamp = false;';
+        }else if(!array_key_exists('update_time',$fieldsInfo)){
+            echo 'protected $updateTime = false;';
+        }else if(!array_key_exists('create_time',$fieldsInfo)){
+            echo 'protected $createTime = false;';
+        }
+    {/php}
+
+    {volist name="$fieldsInfo" id="vo"}
+        {php}
+            if(in_array($vo['Field'],['id','create_time','update_time'])){
+                continue;
+            }
+        {/php}
+        {if condition="isset($vo['Component']) && $vo['Component'] == 2"}
+            {php}
+                //拆分,首字母大写
+                $trems = explode('_', $vo['Field']);
+                $humpName = "";
+                foreach ($trems as $trem) {
+                    $humpName .= ucfirst($trem);
+                }
+            {/php}
+            public function get{$humpName}Attr($value, $data)
+            {
+                return $value ? date('Y-m-d H:i:s', $value) : '';
+            }
+        {/if}
+    {/volist}
+
+}

+ 365 - 0
app/admin/view/code_generation/tpl/index.html.tp

@@ -0,0 +1,365 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="{literal}__PUBLIC__{/literal}/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="{literal}__PUBLIC__{/literal}/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="{literal}__CSS__{/literal}/admin.css" media="all">
+    <style type="text/css">
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+        .tooltip > img{
+            width: 20px;
+            height: 20px;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    {if condition="array_key_exists('create',$crud)"}
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">列表</li>
+            <li><a href="{literal}{:url('{/literal}publish{literal}')}{/literal}" class="a_menu">新增</a></li>
+        </ul>
+    </div>
+    {/if}
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            {if condition="array_key_exists('delete',$crud)"}
+            <button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="deletes">批量删除</button>
+            {/if}
+            {if condition="array_key_exists('sort',$fieldsInfo)"}
+                <button class="layui-btn layui-btn-sm" lay-submit lay-filter="admin">更新排序</button>
+            {/if}
+        </div>
+    </script>
+
+    <form class="layui-form serch" action="index" method="post">
+        <div class="layui-form-item" style="float: left;">
+
+            {php}
+                foreach($fieldsInfo as $vo){
+                    if(empty($vo['ShowSearch'])){
+                        continue;
+                    }
+
+                    $comment = empty($vo['Comment']) ? $vo['Field'] : $vo['Comment'];
+
+                    if($vo['Field'] == 'id'){
+                        echo '<div class="layui-input-inline">
+                                <input type="text" name="ids" autocomplete="off" placeholder="请输入ID,多个id逗号分隔" class="layui-input layui-btn-sm">
+                            </div>';
+                        continue;
+                    }
+
+                    //输入框,文章段落,图片,复选框
+                    if(in_array($vo['Component'],[0,3,4,7])){
+                        echo '<div class="layui-input-inline">
+                            <input type="text" name="'.$vo['Field'].'" autocomplete="off" placeholder="'. $comment .'(模糊搜索)" class="layui-input layui-btn-sm">
+                        </div>';
+                        continue;
+                    }
+
+                    //数字
+                    if($vo['Component'] == 1){
+                        echo '<div class="layui-input-inline" style="width:100px">
+                                <input type="number" name="'.$vo['Field'].'" autocomplete="off" placeholder="'. $comment .'" class="layui-input layui-btn-sm">
+                            </div>';
+                        continue;
+                    }
+
+                    //时间
+                    if($vo['Component'] == 2){
+                        echo '<div class="layui-input-inline">
+                                <input type="text" class="layui-input time_range" id="time_range_'.$vo['Field'].'" autocomplete="off" placeholder="'. $comment .'" name="'.$vo['Field'].'">
+                            </div>';
+                        continue;
+                    }
+
+                    //开关,下拉框,单选
+                    if(in_array($vo['Component'],[5,6,8])){
+                        $arr = explode(':',$vo['Comment']);
+                        $comment = count($arr)==2 ? $arr[0] : ($vo['Component']==5 ? $vo['Comment'] : $vo['Field']);
+                        $options = count($arr)==2 ? $arr[1] : $arr[0];
+                        $option_arr = explode(',',$options);
+
+                        $option_html = '';
+                        foreach($option_arr as $option_item){
+                             $option_item_value = intval($option_item);
+                             $option_text = str_replace($option_item_value,'',$option_item);
+                             $option_html.= '<option value="'.$option_item_value.'">'.$option_text.'</option>';
+                        }
+
+                        echo '<div class="layui-input-inline" style="width: 100px">
+                                    <select name="'.$vo['Field'].'" lay-search="">
+                                        <option value="">'.$comment.'</option>
+                                        '.$option_html.'
+                                    </select>
+                            </div>';
+                        continue;
+                    }
+                }
+            {/php}
+
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+
+    {if condition="array_key_exists('update',$crud) or array_key_exists('delete',$crud)"}
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            {if condition="array_key_exists('update',$crud)"}
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="edit"><i class="layui-icon" style="margin-right: 0;"></i></button>
+            {/if}
+            {if condition="array_key_exists('delete',$crud)"}
+            <button class="layui-btn layui-btn-xs delete" lay-event="del"><i class="layui-icon" style="margin-right: 0;"></i></button>
+            {/if}
+        </div>
+    </script>
+    {/if}
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {literal}{include file="public/foot"}{/literal}
+
+    <script type="text/javascript">
+        layui.use(['table', 'layer', 'form','laydate'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            var laydate = layui.laydate;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                {if condition="array_key_exists('sort',$fieldsInfo) or array_key_exists('delete',$crud)"}
+                , toolbar: '#toolbarDemo'
+                {/if}
+                , limit: 15
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: "{literal}{:url('{/literal}index{literal}')}{/literal}" //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {type: 'checkbox'},
+
+                    {volist name="$fieldsInfo" id="vo"}
+                        {php}
+                            if(empty($vo['ShowList'])){
+                                continue;
+                            }
+                            $comment = empty($vo['Comment']) ? $vo['Field'] : $vo['Comment'];
+                        {/php}
+
+                        {if condition="$vo['Field'] == 'id'"}
+                            {field: 'id', title: 'ID', width: 60},
+                            {php}continue;{/php}
+                        {/if}
+
+                        {if condition="$vo['Field'] == 'sort'"}
+                            {field: 'sort', title: '排序', width: 60, templet: function (row) {
+                                return '<input type="text" name="sorts[]" value="' + row.sort + '" style="width: 20px;" class="sort"><input type="hidden" name="ids[]" value="' + row.id + '">';
+                            }},
+                            {php}continue;{/php}
+                        {/if}
+
+                        {if condition="$vo['Component'] == 4"}
+                            {php}//图片{/php}
+                            {field: "{$vo['Field']}", title: '{$comment}', width: 70, align: 'center', templet: function (row) {
+                                return (row.{$vo['Field']}_url == '') ? '' : '<a href="' + row.{$vo['Field']}_url + '" class="tooltip" target="_blank"><img src="' + row.{$vo['Field']}_url + '"></a>';
+                            }},
+                            {php}continue;{/php}
+                        {/if}
+
+                        {if condition="$vo['Component'] == 5"}
+                            {php}//开关
+                                $arr = explode(':',$vo['Comment']);
+                                $comment = count($arr)==2 ? $arr[0] : $vo['Field'];
+                            {/php}
+                            {field: "{$vo['Field']}", title: '{$comment}', width: 60, templet: function (row) {
+                                return '<a href="javascript:;" style="font-size:18px;" class="{$vo['Field']}" data-id="' + row.id + '" data-val="' + row.{$vo['Field']} + '">' + (row.{$vo['Field']} == 1 ? '<i class="fa fa-toggle-on"></i>' : '<i class="fa fa-toggle-off"></i>') + '</a>';
+                            }},
+                            {php}continue;{/php}
+                        {/if}
+
+                        {if condition="in_array($vo['Component'],[0,1,2,3,6,7,8])"}
+                            {php}//输入框,数字,时间,文章段落,下拉框,复选框,单选
+                                if(in_array($vo['Component'],[6,7,8])){
+                                    $arr = explode(':',$vo['Comment']);
+                                    $comment = count($arr)==2 ? $arr[0] : $vo['Field'];
+                                }
+                            {/php}
+                            {field: "{$vo['Field']}", title: '{$comment}'},
+                            {php}continue;{/php}
+                        {/if}
+                    {/volist}
+
+                    {if condition="array_key_exists('update',$crud) or array_key_exists('delete',$crud)"}
+                        {field: 'action', title: '操作', toolbar: '#barDemo', fixed: 'right'}
+                    {/if}
+                ]],
+                done: function () {
+                    if (isExitsFunction('showThumb')) {
+                        showThumb()
+                    }
+                    {volist name="$fieldsInfo" id="vo"}
+                        {if condition="$vo['Component'] == 5 && !empty($vo['ShowList'])"}
+                            switchStatus('.{$vo['Field']}', "{literal}{:url('{/literal}{$vo['Field']}{literal}')}{/literal}");
+                        {/if}
+                    {/volist}
+                }
+            });
+
+            {volist name="$fieldsInfo" id="vo"}
+                {if condition="$vo['Component'] == 2 && !empty($vo['ShowSearch'])"}
+                    laydate.render({
+                        elem: '#time_range_{$vo['Field']}'
+                        , type: 'datetime'
+                        , range: true
+                        , max: 0 //最大值0天后
+                        , theme: 'molv'
+                        , calendar: true
+                        , done: function (value, date, endDate) {
+                            if (endDate.hours == 0 && endDate.minutes == 0 && endDate.seconds == 0) {
+                                setTimeout(function () {
+                                    $('#time_range_{$vo['Field']}').val(value.replace(/00:00:00$/, '23:59:59'))
+                                }, 100)
+                            }
+                        }
+                    });
+                {/if}
+            {/volist}
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            {if condition="array_key_exists('update',$crud) or array_key_exists('delete',$crud)"}
+            table.on('tool(table)', function (obj) {
+                {if condition="array_key_exists('update',$crud)"}
+                if (obj.event == 'edit') {
+                    window.parent.tab.tabAdd({
+                        icon: "fa-bookmark",
+                        id: "{$tableName}" + obj.data.id,
+                        title: obj.data.title == null ? "{$menuName}" + obj.data.id : obj.data.title,
+                        url: "/admin/{$underLineName}/publish?id=" + obj.data.id
+                    });
+                }
+                {/if}
+                {if condition="array_key_exists('delete',$crud)"}
+                else if (obj.event == 'del') {
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{literal}{:url('{/literal}delete{literal}')}{/literal}",
+                            dataType: 'json',
+                            data: {id: obj.data.id},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }
+                {/if}
+            });
+            {/if}
+
+            {if condition="array_key_exists('delete',$crud)"}
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                if (obj.event == 'deletes') {
+                    var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                    var data = checkStatus.data;
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            ids[key] = item.id;
+                        })
+                        layer.confirm('是否删除?', function (index, layero) {
+                            $.ajax({
+                                url: "{literal}{:url('{/literal}deletes{literal}')}{/literal}",
+                                dataType: 'json',
+                                data: {"ids": ids},
+                                type: 'post',
+                                success: function (res) {
+                                    layer.msg(res.msg);
+                                    if (res.code == 1) {
+                                        table.reload('table');
+                                    }
+                                }
+                            })
+                            layer.close(index)
+                        });
+                    } else {
+                        layer.msg('请先勾选需要操作的记录');
+                    }
+                }
+            });
+            {/if}
+        });
+    </script>
+
+    {if condition="array_key_exists('sort',$fieldsInfo)"}
+    <script>
+        // 排序
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $sort_eles = $('.sort');
+                    $data = {};
+                    if ($sort_eles.length > 0) {
+                        for (var i = 0; i < $sort_eles.length; i++) {
+                            $data['sorts[' + i + ']'] = $sort_eles[i].value;
+                            $data['ids[' + i + ']'] = $sort_eles[i].nextSibling.value;
+                        }
+                    }
+                    console.log($data)
+                    $.ajax({
+                        url: "{literal}{:url('{/literal}sort{literal}')}{/literal}",
+                        data: $data,
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.reload();
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+    {/if}
+
+</div>
+</body>
+</html>

+ 324 - 0
app/admin/view/code_generation/tpl/publish.html.tp

@@ -0,0 +1,324 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>{$menuName}编辑</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="{literal}__PUBLIC__{/literal}/layui/css/layui.css"  media="all">
+    <link rel="stylesheet" href="{literal}__PUBLIC__{/literal}/font-awesome/css/font-awesome.min.css" media="all" />
+    <link rel="stylesheet" href="{literal}__CSS__{/literal}/admin.css"  media="all">
+    <script src="{literal}__PUBLIC__{/literal}/layui/layui.js"></script>
+    <script src="{literal}__PUBLIC__{/literal}/jquery/jquery.min.js"></script>
+</head>
+<style>
+  .layui-upload-img{
+    cursor: pointer;
+    width:150px;
+    height:150px;
+    background: url('{literal}__PUBLIC__{/literal}/images/uploadimg.jpg');
+    background-size:contain;
+    border-radius: 2px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #e6e6e6;
+  }
+</style>
+<body style="padding:10px;">
+  <div class="tplay-body-div">
+
+    {literal}{empty name="$data"}{/literal}
+    <div class="layui-tab">
+      <ul class="layui-tab-title">
+        <li><a href="index" class="a_menu">列表</a></li>
+        <li class="layui-this">新增</li>
+      </ul>
+    </div>
+    {literal}{/empty}{/literal}
+
+    <div style="margin-top: 20px;"></div>
+    <form class="layui-form" id="publish" method="post">
+
+        {volist name="$fieldsInfo" id="vo"}
+            {php}
+              if(empty($vo['ShowEdit'])){
+                continue;
+              }
+              if($vo['Field'] == 'id'){
+                continue;
+              }
+            {/php}
+
+            {if condition="$vo['Component'] == 0"}
+              <!-- 输入框 -->
+              <div class="layui-form-item">
+                <label class="layui-form-label">{$vo['Comment']|default=$vo['Field']}</label>
+                <div class="layui-input-inline" style="max-width:300px;">
+                  <input name="{$vo['Field']}" {if condition="$vo['Default']===null"}lay-verify="required"{/if} autocomplete="off" placeholder="请输入" class="layui-input" type="text" {literal}{notempty name="$data"}value="{$data.{/literal}{$vo['Field']}{literal}}"{/notempty}{/literal}>
+                </div>
+                  {if condition="$vo['Default']===null"}
+                    <div class="layui-form-mid layui-word-aux">必填</div>
+                  {/if}
+              </div>
+            {/if}
+
+            {if condition="$vo['Component'] == 1"}
+                <!-- 数字 -->
+              <div class="layui-form-item">
+                <label class="layui-form-label">{$vo['Comment']|default=$vo['Field']}</label>
+                <div class="layui-input-inline" style="max-width:300px;">
+                  <input name="{$vo['Field']}" {if condition="$vo['Default']===null"}lay-verify="required"{/if} autocomplete="off" placeholder="请输入" class="layui-input" type="number" {literal}{notempty name="$data"}value="{$data.{/literal}{$vo['Field']}{literal}}"{/notempty}{/literal}>
+                </div>
+                  {if condition="$vo['Default']===null"}
+                    <div class="layui-form-mid layui-word-aux">必填</div>
+                  {/if}
+              </div>
+            {/if}
+
+            {if condition="$vo['Component'] == 2"}
+                <!-- 时间 -->
+                <div class="layui-form-item">
+                    <label class="layui-form-label">{$vo['Comment']|default=$vo['Field']}</label>
+                    <div class="layui-input-inline" style="max-width:300px;">
+                        <input name="{$vo['Field']}" id="{$vo['Field']}" {if condition="$vo['Default']===null"}lay-verify="required"{/if} autocomplete="off" placeholder="请输入" class="layui-input" type="text" {literal}{notempty name="$data"}value="{$data.{/literal}{$vo['Field']}{literal}}"{/notempty}{/literal}>
+                    </div>
+                    {if condition="$vo['Default']===null"}
+                        <div class="layui-form-mid layui-word-aux">必填</div>
+                    {/if}
+                </div>
+            {/if}
+
+
+            {if condition="$vo['Component'] == 3"}
+                <!-- 纯文本段落 -->
+              <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">{$vo['Comment']|default=$vo['Field']}</label>
+                <div class="layui-input-inline" style="width:80%;">
+                  <textarea name="{$vo['Field']}" {if condition="$vo['Default']===null"}lay-verify="required"{/if} class="layui-textarea">{literal}{notempty name="$data"}{$data.{/literal}{$vo['Field']}{literal}}{/notempty}{/literal}</textarea>
+                </div>
+                  {if condition="$vo['Default']===null"}
+                      <div class="layui-form-mid layui-word-aux">必填</div>
+                  {/if}
+              </div>
+            {/if}
+
+            {if condition="$vo['Component'] == 9"}
+                <!-- 文章富文本 -->
+                {literal}
+                    {php}$web_config = \think\Db::name('webconfig')->where('id', 1)->find();{/php}
+                    {switch name="$web_config.article_editor" }
+                    {case value="ueditor"} {include file="article/publish_ueditor" item="data" field="{/literal}{$vo['Field']}{literal}"}{/case}
+                    {case value="tinymce"} {include file="article/publish_tinymce" item="data" field="{/literal}{$vo['Field']}{literal}"}{/case}
+                    {case value="markdown"} {include file="article/publish_markdown" item="data" field="{/literal}{$vo['Field']}{literal}"}{/case}
+                    {default /} {include file="article/publish_wangEditor" item="data" field="{/literal}{$vo['Field']}{literal}"}
+                    {/switch}
+                {/literal}
+            {/if}
+
+
+            {if condition="$vo['Component'] == 4"}
+                <!-- 图片 -->
+              <div class="layui-upload">
+                <label class="layui-form-label">{$vo['Comment']|default=$vo['Field']}</label>
+                <div class="layui-upload-list">
+                  <img class="layui-upload-img" id="upload_img_{$vo['Field']}" {literal}{notempty name="$data.{/literal}{$vo['Field']}{literal}"}src="{$data.{/literal}{$vo['Field']}{literal}|geturl}"{/notempty}{/literal}>
+                  <input type="hidden" id="upload_value_{$vo['Field']}" name="{$vo['Field']}" value='{literal}{notempty name="$data.{/literal}{$vo['Field']}{literal}"}{$data.{/literal}{$vo['Field']}{literal}}{/notempty}{/literal}'>
+                </div>
+              </div>
+            {/if}
+
+
+            {if condition="$vo['Component'] == 5"}
+                {php}
+                    $arr = explode(':',$vo['Comment']);
+                    $comment = count($arr)==2 ? $arr[0] : $vo['Comment'];
+                    $options = count($arr)==2 ? $arr[1] : $arr[0];
+                    $option_arr = ifContain($options,',') ? explode(',',$options) : ['0关闭','1开启'];
+                {/php}
+                <!-- 开关 -->
+                <div class="layui-form-item">
+                    <label class="layui-form-label">{$comment}</label>
+                    <div class="layui-input-block">
+                        {volist name="$option_arr" id="option_item"}
+                        {php}
+                            $option_item_value = intval($option_item);
+                            $option_text = str_replace($option_item_value,'',$option_item);
+                        {/php}
+                            <input type="radio" name="{$vo['Field']}" value="{$option_item_value}" title="{$option_text}" {literal}{notempty name="$data"}{eq name="$data.{/literal}{$vo['Field']}{literal}" value="{/literal}{$option_item_value}{literal}" } checked {/eq}{/notempty}{/literal}>
+                        {/volist}
+                    </div>
+                    {if condition="$vo['Default']===null"}
+                        <div class="layui-form-mid layui-word-aux">必填</div>
+                    {/if}
+                </div>
+            {/if}
+
+
+              {if condition="$vo['Component'] == 6"}
+                  {php}
+                    $arr = explode(':',$vo['Comment']);
+                    $comment = count($arr)==2 ? $arr[0] : $vo['Field'];
+                    $options = count($arr)==2 ? $arr[1] : $arr[0];
+                    $option_arr = explode(',',$options);
+                  {/php}
+                  <!-- 下拉框 -->
+                  <div class="layui-form-item">
+                    <label class="layui-form-label">{$comment}</label>
+                    <div class="layui-input-inline" style="max-width:300px;">
+                      <select name="{$vo['Field']}" lay-filter="" lay-search="" {if condition="$vo['Default']===null"}lay-verify="required"{/if}>
+                        <option value="">请选择</option>
+                          {volist name="$option_arr" id="option_item"}
+                              {php}
+                                $option_item_value = intval($option_item);
+                                $option_text = str_replace($option_item_value,'',$option_item);
+                              {/php}
+                              <option value="{$option_item_value}" {literal}{notempty name="$data"}{eq name="$data.{/literal}{$vo['Field']}{literal}" value="{/literal}{$option_item_value}{literal}" } selected="" {/eq}{/notempty}{/literal}>{$option_text}</option>
+                          {/volist}
+                      </select>
+                    </div>
+                      {if condition="$vo['Default']===null"}
+                          <div class="layui-form-mid layui-word-aux">必填</div>
+                      {/if}
+                  </div>
+              {/if}
+
+              {if condition="$vo['Component'] == 7"}
+                  {php}
+                    $arr = explode(':',$vo['Comment']);
+                    $comment = count($arr)==2 ? $arr[0] : $vo['Field'];
+                    $options = count($arr)==2 ? $arr[1] : $arr[0];
+                    $option_arr = explode(',',$options);
+                  {/php}
+                  <!-- 复选框 -->
+                <div class="layui-form-item">
+                  <label class="layui-form-label">{$comment}</label>
+                    <div class="layui-input-block">
+                        {volist name="$option_arr" id="option_item"}
+                        {php}
+                            $option_item_value = intval($option_item);
+                            $option_text = str_replace($option_item_value,'',$option_item);
+                        {/php}
+                            <input type="checkbox" name="{$vo['Field']}[]" value="{$option_item_value}" title="{$option_text}" lay-skin="primary" {literal}{notempty name="$data"}{in name="{/literal}{$option_item_value}{literal}" value="$data.{/literal}{$vo['Field']}{literal}" } checked {/in}{/notempty}{/literal}>
+                        {/volist}
+                    </div>
+                    {if condition="$vo['Default']===null"}
+                        <div class="layui-form-mid layui-word-aux">必填</div>
+                    {/if}
+                </div>
+              {/if}
+
+              {if condition="$vo['Component'] == 8"}
+                  {php}
+                    $arr = explode(':',$vo['Comment']);
+                    $comment = count($arr)==2 ? $arr[0] : $vo['Field'];
+                    $options = count($arr)==2 ? $arr[1] : $arr[0];
+                    $option_arr = explode(',',$options);
+                  {/php}
+                  <!-- 单选框 -->
+                <div class="layui-form-item">
+                  <label class="layui-form-label">{$comment}</label>
+                    <div class="layui-input-block">
+                        {volist name="$option_arr" id="option_item"}
+                        {php}
+                            $option_item_value = intval($option_item);
+                            $option_text = str_replace($option_item_value,'',$option_item);
+                        {/php}
+                            <input type="radio" name="{$vo['Field']}" value="{$option_item_value}" title="{$option_text}" {literal}{notempty name="$data" }{eq name="$data.{/literal}{$vo['Field']}{literal}" value="{/literal}{$option_item_value}{literal}" } checked {/eq}{/notempty}{/literal}>
+                        {/volist}
+                    </div>
+                    {if condition="$vo['Default']===null"}
+                        <div class="layui-form-mid layui-word-aux">必填</div>
+                    {/if}
+                </div>
+              {/if}
+
+        {/volist}
+
+
+        {literal}
+        {notempty name="$data"}
+          <input type="hidden" name="id" value="{$data.id}">
+        {/notempty}
+       {/literal}
+      <div class="layui-form-item">
+        <div class="layui-input-block">
+          <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+          <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+        </div>
+      </div>
+    </form>
+
+    <script>
+        layui.use(['layer', 'form' ,'laydate'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            var laydate = layui.laydate;
+
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{literal}{:url('{/literal}publish{literal}')}{/literal}",
+                        data: $('#publish').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                if (res.msg == "添加成功") {
+                                    layer.alert(res.msg, function (index) {
+                                        location.href = res.url;
+                                    })
+                                }else{
+                                    layer.confirm(res.msg, {
+                                        btn: ['关闭', '继续编辑']
+                                    }, function (index) {
+                                        window.parent.tab.close('{$tableName}{literal}{$data.id|default=0}{/literal}');
+                                    }, function (index, layero) {
+                                        location.href = res.url;
+                                    });
+                                }
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+
+                {volist name="$fieldsInfo" id="vo"}
+                    {empty name="$vo['ShowEdit']"}
+                    {php}continue;{/php}
+                    {/empty}
+
+                    {if condition="$vo['Component'] == 2"}
+                    laydate.render({
+                        elem: '#{$vo['Field']}', //指定元素
+                        type: 'datetime',
+                    });
+                    {/if}
+
+                    {if condition="$vo['Component'] == 4"}
+                    $('#upload_img_{$vo['Field']}').click(function () {
+                        layer.open({
+                            type: 2,
+                            title: '选择图片',
+                            area: ['570px', '485px'],
+                            id: 'layerDemo', //防止重复弹出
+                            anim: 4,
+                            content: "{:url('attachment/selectimage')}?suffix=_{$vo['Field']}",
+                            cancel: function () {
+                                //右上角关闭回调
+                            }
+                        });
+                    })
+                    {/if}
+                {/volist}
+
+            });
+        });
+    </script>
+  </div>
+</body>
+</html>

+ 81 - 0
app/admin/view/common/login.html

@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+  <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <title>登入</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
+  <link href="__PUBLIC__/layui/css/layui.css" rel="stylesheet" />
+  <link rel="stylesheet" href="__CSS__/admin-1.css" media="all">
+  <link href="__CSS__/login-1.css" rel="stylesheet" />
+  <link href="__PUBLIC__/font-awesome/css/font-awesome.css" rel="stylesheet" />
+  </head>
+<body class="layui-layout-body">
+  <div id="LAY_app">
+  <div class="layadmin-user-login" id="LAY-user-login" style="display: none;">
+  <div class="layadmin-user-login-main">
+    <div class="layadmin-user-login-box layadmin-user-login-header">
+      <h2>{:systemName()}后台管理</h2>
+      <p>您当前的ip:{$Request.ip}</p>
+    </div>
+    <div class="layadmin-user-login-box layadmin-user-login-body layui-form">
+      <form class="layui-form" id="login">
+        <div class="layui-form-item">
+          <label class="layadmin-user-login-icon layui-icon layui-icon-username" for="username-input"></label>
+          <input type="text" name="name" id="username-input" lay-verify="required" autocomplete="off" placeholder="账号" class="layui-input" {notempty name="usermember"}value="{$usermember}"{/notempty}>
+        </div>
+        <div class="layui-form-item">
+          <label class="layadmin-user-login-icon layui-icon layui-icon-password" for="password-input"></label>
+          <input type="password" name="password" id="password-input" lay-verify="required" autocomplete="off" placeholder="密码" class="layui-input">
+        </div>
+        <div class="layui-form-item">
+          <label class="layadmin-user-login-icon layui-icon layui-icon-auz" for="captcha-input"></label>
+          <input type="text" name="captcha" id="captcha-input" lay-verify="required" autocomplete="off" placeholder="验证码" class="layui-input" style="width:62%;float: left;margin-right:11px;"><img src="{:captcha_src()}" alt="captcha" onclick="this.src='{:captcha_src()}?seed='+Math.random()" height="36" id="captcha" style="margin-top: 1px" />
+        </div>
+        <div class="layui-form-item">
+          <input type="checkbox" lay-skin="primary" title="记住账号" name="remember" value="1" {notempty name="usermember"}checked=""{/notempty}><div class="layui-unselect layui-form-checkbox" lay-skin="primary"><span>记住账号?</span><i class="layui-icon"></i></div>
+        </div>
+        <div class="layui-form-item">
+          <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登 入</button>
+        </div>
+      </form>
+    </div>
+  </div>
+</div>
+</div>
+
+<script src="__PUBLIC__/layui/layui.js"></script>
+<script src="__PUBLIC__/jquery/jquery.min.js"></script>
+  <script>
+      layui.use(['layer', 'form'], function () {
+          var layer = layui.layer,
+              $ = layui.jquery,
+              form = layui.form;
+          $(window).on('load', function () {
+              form.on('submit(login)', function (data) {
+                  $.ajax({
+                      url: "{:url('admin/common/login')}",
+                      data: $('#login').serialize(),
+                      type: 'post',
+                      dataType: 'json',
+                      async: false,
+                      success: function (res) {
+                          //alert(res.msg);
+                          layer.msg(res.msg, {offset: '50px', anim: 1});
+                          if (res.code == 1) {
+                              setTimeout(function () {
+                                  location.href = res.url;
+                              }, 1500);
+                          } else {
+                              $('#captcha').click();
+                          }
+                      }
+                  })
+                  return false;
+              });
+          });
+      });
+  </script>
+</body>
+</html>

+ 214 - 0
app/admin/view/config/index.html

@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">全部配置</li>
+            <li><a href="{:url('publish',['tab_id'=>$Request.param.tab_id])}" class="a_menu">新增</a></li>
+        </ul>
+    </div>
+
+    <form class="layui-form serch" action="{:url('index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+            <div class="layui-input-inline">
+                <input type="text" name="keywords" lay-verify="title" autocomplete="off" placeholder="请输入名称"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline">
+                <select name="tab_id" lay-search="">
+                    <option value="">分组标签</option>
+                    {foreach name="$tabs" item="vo" key="k"}
+                    <option value="{$vo.id}">{$vo.name}</option>
+                    {/foreach}
+                </select>
+            </div>
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            <button class="layui-btn layui-btn-sm" lay-submit lay-filter="admin">排序</button>
+            <button class="layui-btn layui-btn-sm" lay-event="addGroupTab">配置标签</button>
+        </div>
+    </script>
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon"
+                                                                  style="margin-right: 0;"></i></a>
+            <a class="layui-btn layui-btn-xs delete" lay-event="del"><i class="layui-icon"
+                                                                        style="margin-right: 0;"></i></a>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                , toolbar: '#toolbarDemo'
+                , defaultToolbar: []
+                , limit: 15
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: "{:url('index')}?tab_id={$Request.param.tab_id}" //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {
+                        key: 'sort', title: '排序', align: 'center', width: 60, templet: function (row) {
+                        return '<input type="text" name="sorts[]" value="' + row.sort + '" style="width: 20px;" class="sort"><input type="hidden" name="ids[]" value="' + row.id + '">';
+                    }
+                    },
+                    {field: 'id', title: 'ID', width: 60, align: 'center'},
+                    {field: 'name', title: '名称'},
+                    {field: 'type_text', title: '配置类型', width: 100, align: 'center'},
+                    {field: 'value', title: '配置值',templet:function (row) {
+                        if(row.type == 0){
+                            return row.value;
+                        }
+                        return '<a class="layui-btn layui-btn-xs" lay-event="options">配置</a>';
+                    }},
+                    {field: 'remark', title: '备注'},
+                    {field: 'tab_text', title: '分组标签', width: 120, align: 'center'},
+                    {
+                        field: 'status', title: '状态', align: 'center', width: 100, templet: function (row) {
+                        return '<a href="javascript:;" style="font-size:18px;" class="status" data-id="' + row.id + '" data-val="' + row.status + '">' + (row.status == 1 ? '<i class="fa fa-toggle-on"></i>' : '<i class="fa fa-toggle-off"></i>') + '</a>';
+                    }
+                    },
+                    {field: 'action', title: '操作', align: 'center', toolbar: '#barDemo',width: 150, fixed: 'right'}
+                ]],
+                done: function () {
+                    //审核
+                    switchStatus('.status',"{:url('status')}");
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'edit') {
+                    location.href = "{:url('publish')}?id=" + obj.data.id;
+                }
+                else if (obj.event == 'del') {
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            dataType: 'json',
+                            data: {id: obj.data.id},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }
+                else if(obj.event == 'options'){
+                    layer.open({
+                        type: 2,
+                        title: obj.data.id + '.' + obj.data.name ,
+                        area: ['90%', '90%'],
+                        maxmin: true,
+                        id: 'layerDemo', //防止重复弹出
+                        content: "{:url('config_option/index')}?pid=" + obj.data.id
+                    });
+                }
+            });
+
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                if(obj.event == 'addGroupTab') {
+                    layer.open({
+                        type: 2,
+                        title: "配置标签",
+                        area: ['60%', '60%'],
+                        maxmin: true,
+                        id: 'layerDemo', //防止重复弹出
+                        content: "{:url('admin/config_tab/index')}"
+                    });
+                }
+            });
+
+        });
+    </script>
+    <script>
+        // 排序
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $sort_eles = $('.sort');
+                    $data = {};
+                    if ($sort_eles.length > 0) {
+                        for (var i = 0; i < $sort_eles.length; i++) {
+                            $data['sorts[' + i + ']'] = $sort_eles[i].value;
+                            $data['ids[' + i + ']'] = $sort_eles[i].nextSibling.value;
+                        }
+                    }
+                    console.log($data)
+                    $.ajax({
+                        url: "{:url('sort')}",
+                        data: $data,
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.reload();
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 101 - 0
app/admin/view/config/index2.html

@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('webconfig/index')}" class="a_menu">系统设置</a></li>
+            <li><a href="{:url('emailconfig/index')}" class="a_menu">邮件配置</a></li>
+            <li><a href="{:url('smsconfig/index')}" class="a_menu">短信配置</a></li>
+            <!--config_tab-->
+            {foreach $tabs as $tab}
+            {eq name="$tab.id" value="$Request.param.tab_id"}
+            <li class="layui-this">{$tab.name}</li>
+            {else/}
+            <li><a href="{:url('admin/config/index2')}?tab_id={$tab.id}">{$tab.name}</a></li>
+            {/eq}
+            {/foreach}
+        </ul>
+    </div>
+
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon"
+                                                                  style="margin-right: 0;"></i></a>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+//                , toolbar: '#toolbarDemo'
+                , defaultToolbar: []
+                , limit: 15
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: "{:url('index2')}?tab_id={$Request.param.tab_id}" //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {field: 'id', title: 'ID', width: 100, align: 'center'},
+                    {field: 'name', title: '名称'},
+                    {field: 'value_text', title: '配置值'},
+                    {field: 'action', title: '操作', align: 'center', toolbar: '#barDemo', width: 150, fixed: 'right'}
+                ]],
+                done: function () {
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'edit') {
+                    if (obj.data.type == 0) {
+                        location.href = "{:url('admin/config/publish2')}?id=" + obj.data.id;
+                    } else {
+                        layer.open({
+                            type: 2,
+                            title: obj.data.id + '.' + obj.data.name,
+                            area: ['90%', '90%'],
+                            maxmin: true,
+                            id: 'layerDemo', //防止重复弹出
+                            content: "{:url('config_option/index')}?pid=" + obj.data.id
+                        });
+                    }
+                }
+            });
+
+        });
+    </script>
+</div>
+</body>
+</html>

+ 141 - 0
app/admin/view/config/publish.html

@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('index')}" class="a_menu">全部配置</a></li>
+            <li class="layui-this">{notempty name="$cate"}编辑{else/}新增{/notempty}</li>
+        </ul>
+    </div>
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">配置名称</label>
+            <div class="layui-input-inline">
+                <input name="name" lay-verify="required" placeholder="自定义配置名称" autocomplete="off" class="layui-input"
+                       type="text" {notempty name="$cate.name" }value="{$cate.name}" {/notempty}>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">配置类型</label>
+            <div class="layui-input-inline" style="max-width:300px;">
+                <select name="type" lay-filter="type">
+                    {foreach name="$types" item="vo" key="k"}
+                    <option value="{$k}" {notempty name="$cate"}{if condition="$type eq $k"} selected=""{/if}{/notempty}>{$vo}</option>
+                    {/foreach}
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-form-text" id="val">
+            <label class="layui-form-label">配置值</label>
+            <div class="layui-input-block" style="max-width:500px;">
+                <input name="value" maxlength="50" placeholder="请输入,限制255字" autocomplete="off" class="layui-input"
+                       type="text" {notempty name="$cate.value" }value="{$cate.value}" {/notempty}>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">分组标签</label>
+            <div class="layui-input-inline" style="max-width:300px;">
+                <select name="tab_id">
+                    <option value="">请选择</option>
+                    {foreach name="$tabs" item="vo" key="k"}
+                    <option value="{$vo.id}"
+                            {notempty name="$cate"}
+                            {eq name="$cate.tab_id" value="$vo.id"} selected=""{/eq}
+                    {else/}
+                    {notempty name="$Request.param.tab_id"}{eq name="$Request.param.tab_id" value="$vo.id"} selected=""{/eq}{/notempty}
+                    {/notempty}>{$vo.name}</option>
+                    {/foreach}
+                </select>
+            </div>
+            <div class="layui-form-mid layui-word-aux">
+                按标签分组展示
+            </div>
+        </div>
+
+
+        <div class="layui-form-item layui-form-text">
+            <label class="layui-form-label">备注</label>
+            <div class="layui-input-block" style="max-width:500px;">
+                <textarea placeholder="请输入,限制500字" maxlength="255" class="layui-textarea" name="remark">{notempty name="$cate.remark"}{$cate.remark}{/notempty}</textarea>
+            </div>
+        </div>
+
+
+        {notempty name="$cate"}
+        <input type="hidden" name="id" value="{$cate.id}">
+        {/notempty}
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">保存</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+            </div>
+        </div>
+
+    </form>
+
+    {include file="public/foot"}
+
+    {notempty name="$cate"}
+    {neq name="$cate.type" value="0"}
+    <script>
+    $("#val").hide();
+    </script>
+    {/neq}
+    {/notempty}
+
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('publish')}",
+                        dataType: 'json',
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url + "?tab_id={$Request.param.tab_id}";
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+
+            form.on('select(type)', function (data) {
+                if (data.value == 0) {
+                    $("#val").show();
+                } else {
+                    $("#val").hide();
+                }
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 242 - 0
app/admin/view/config_option/index.html

@@ -0,0 +1,242 @@
+{php}
+$name_label = $json_config['config_option']['name']['label']??'';
+$value_label = $json_config['config_option']['value']['label']??'';
+$desc_label = $json_config['config_option']['description']['label']??'';
+$image_label = $json_config['config_option']['image']['label']??'';
+$image_show = $json_config['config_option']['image']['show']??'';
+{/php}
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">配置项</li>
+            <li><a href="{:url('publish',['pid'=>$Request.param.pid])}" class="a_menu">新增</a>
+        </ul>
+    </div>
+
+    <form class="layui-form serch" action="{:url('index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+            <div class="layui-input-inline">
+                <input type="text" name="keywords" lay-verify="title" autocomplete="off" placeholder="请输入名称"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            <button class="layui-btn layui-btn-sm" lay-submit lay-filter="admin">排序</button>
+            <button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="deletes">批量删除</button>
+        </div>
+    </script>
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon"
+                                                                  style="margin-right: 0;"></i></a>
+            <a class="layui-btn layui-btn-xs delete" lay-event="del"><i class="layui-icon"
+                                                                        style="margin-right: 0;"></i></a>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                , toolbar: '#toolbarDemo'
+                , defaultToolbar: []
+                , limit: 15
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: "{:url('index',['pid'=>$Request.param.pid])}" //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {
+                        key: 'sort', title: '排序', align: 'center', width: 60, templet: function (row) {
+                        return '<input type="text" name="sorts[]" value="' + row.sort + '" style="width: 20px;" class="sort"><input type="hidden" name="ids[]" value="' + row.id + '">';
+                    }
+                    },
+                    {type: 'checkbox'},
+                    {field: 'id', title: 'ID', width: 60, align: 'center'},
+                    {field: 'name', title: '{if condition="!empty($name_label)"}{$name_label}{else/}名称{/if}', minWidth: 150},
+                    {field: 'value', title: '{if condition="!empty($value_label)"}{$value_label}{else/}配置值{/if}', align: 'center',minWidth:150},
+                    {field: 'description', title: '{if condition="!empty($desc_label)"}{$desc_label}{else/}备注{/if}', minWidth:150},
+                    //{if condition="$image_show==true"}
+
+                    {
+                        field: 'image', title: '{if condition="!empty($image_label)"}{$image_label}{else/}配图{/if}', minWidth: 80, align: 'center', templet: function (row) {
+                        return (row.thumb_url == '') ? '' : '<a href="' + row.thumb_url + '" class="tooltip" target="_blank"><img src="' + row.thumb_url + '" width="20" height="20"></a>';
+                    }
+                    },
+                    //{/if}
+
+                    {
+                        field: 'status', title: '状态', align: 'center', width: 100, templet: function (row) {
+                        if (row.group_type == 2) {
+                            return '<a style="font-size:18px;color: #009688" lay-event="single_status">' + (row.single_status == 1 ? '<i class="layui-icon layui-icon-radio"></i>' : '<i class="layui-icon layui-icon-circle"></i>') + '</a>';
+                        }
+                        return '<a href="javascript:;" style="font-size:18px;" class="status" data-id="' + row.id + '" data-val="' + row.status + '">' + (row.status == 1 ? '<i class="fa fa-toggle-on"></i>' : '<i class="fa fa-toggle-off"></i>') + '</a>';
+                    }
+                    },
+                    {field: 'action', title: '操作', toolbar: '#barDemo',  align: 'center',fixed: 'right',minWidth:80}
+                ]],
+                done: function () {
+                    showThumb();
+                    switchStatus('.status', "{:url('status')}");
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'edit') {
+                    location.href = "{:url('publish',['pid'=>$Request.param.pid])}?id=" + obj.data.id;
+                }
+                else if (obj.event == 'del') {
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            dataType: 'json',
+                            data: {id: obj.data.id},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }
+                else if (obj.event == 'single_status') {
+                    var status = obj.data.single_status == 1 ? 0 : 1;
+                    $.ajax({
+                        type: "post",
+                        url: '{:url("single_status")}',
+                        data: {status: status, id: obj.data.id , pid:'{$Request.param.pid}'},
+                        dataType: 'json',
+                        success: function (res) {
+                            if (res.code == 1) {
+                                table.reload('table');
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                }
+            });
+
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                if (obj.event == 'deletes') {
+                    var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                    var data = checkStatus.data;
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            ids[key] = item.id;
+                        })
+                        layer.confirm('是否删除?', function (index, layero) {
+                            $.ajax({
+                                url: "{:url('deletes')}",
+                                dataType: 'json',
+                                data: {"ids": ids},
+                                type: 'post',
+                                success: function (res) {
+                                    layer.msg(res.msg);
+                                    if (res.code == 1) {
+                                        table.reload('table');
+                                    }
+                                }
+                            })
+                            layer.close(index)
+                        });
+                    } else {
+                        layer.msg('请先勾选需要操作的记录');
+                    }
+                }
+            });
+
+        });
+    </script>
+    <script>
+        // 排序
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $sort_eles = $('.sort');
+                    $data = {};
+                    if ($sort_eles.length > 0) {
+                        for (var i = 0; i < $sort_eles.length; i++) {
+                            $data['sorts[' + i + ']'] = $sort_eles[i].value;
+                            $data['ids[' + i + ']'] = $sort_eles[i].nextSibling.value;
+                        }
+                    }
+                    console.log($data)
+                    $.ajax({
+                        url: "{:url('sort')}",
+                        data: $data,
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.reload();
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 150 - 0
app/admin/view/config_option/publish.html

@@ -0,0 +1,150 @@
+{php}
+$name_label = $json_config['config_option']['name']['label']??'';
+$value_label = $json_config['config_option']['value']['label']??'';
+$desc_label = $json_config['config_option']['description']['label']??'';
+$image_label = $json_config['config_option']['image']['label']??'';
+$image_show = $json_config['config_option']['image']['show']??'';
+$name_placeholder = $json_config['config_option']['name']['placeholder']??'';
+$value_placeholder = $json_config['config_option']['value']['placeholder']??'';
+$desc_placeholder = $json_config['config_option']['description']['placeholder']??'';
+{/php}
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>layui</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css"  media="all">
+  <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all" />
+  <link rel="stylesheet" href="__CSS__/admin.css"  media="all">
+</head>
+<style>
+  .layui-upload-img{
+    cursor: pointer;
+    width:150px;
+    height:150px;
+    background: url('/static/public/images/uploadimg.jpg');
+    background-size:contain;
+    border-radius: 2px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #e6e6e6;
+  }
+</style>
+<body style="padding:10px;">
+  <div class="tplay-body-div">
+
+    <div class="layui-tab">
+      <ul class="layui-tab-title">
+        <li><a href="{:url('index',['pid'=>$Request.param.pid])}" class="a_menu">配置项</a></li>
+        <li class="layui-this">{notempty name="$link"}编辑{else/}新增{/notempty}</li>
+      </ul>
+    </div>
+
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="publish" method="post">
+
+      <div class="layui-col-md7">
+        <div class="layui-form-item">
+          <label class="layui-form-label">{if condition="!empty($name_label)"}{$name_label}{else/}名称{/if}</label>
+          <div class="layui-input-inline" style="max-width:300px;">
+            <input name="name" lay-verify="required" maxlength="30" autocomplete="off" placeholder="{if condition='!empty($name_placeholder)'}{$name_placeholder}{else/}请输入{/if}" class="layui-input" type="text" {notempty name="$link.name"}value="{$link.name}"{/notempty}>
+          </div>
+        </div>
+
+        <div class="layui-form-item">
+          <label class="layui-form-label">{if condition="!empty($value_label)"}{$value_label}{else/}配置值{/if}</label>
+          <div class="layui-input-block" style="max-width:600px;">
+            <input name="value" lay-verify="" maxlength="255" autocomplete="off" placeholder="{notempty name='$value_placeholder'}{$value_placeholder}{else/}请输入,限制255字{/notempty}" class="layui-input" type="text" {notempty name="$link.value"}value="{$link.value}"{/notempty}>
+          </div>
+        </div>
+
+        <div class="layui-form-item layui-form-text">
+          <label class="layui-form-label">{if condition="!empty($desc_label)"}{$desc_label}{else/}备注{/if}</label>
+          <div class="layui-input-block" style="max-width:600px;">
+            <textarea placeholder="{notempty name='$desc_placeholder'}{$desc_placeholder}{else/}请输入,限制500字{/notempty}" maxlength="500" class="layui-textarea" name="description">{notempty name="$link.description"}{$link.description}{/notempty}</textarea>
+          </div>
+        </div>
+      </div>
+
+      {if condition="$image_show==true"}
+      <div class="layui-col-md5">
+        <div class="layui-upload">
+          <label class="layui-form-label">{if condition="!empty($image_label)"}{$image_label}{else/}配图{/if}</label>
+          <div class="layui-upload-list">
+            <img class="layui-upload-img" id="upload_img" {notempty name="$link.image"}src="{$link.image|geturl}"{/notempty}>
+            <input type="hidden" id="upload_value" name="image" value='{notempty name="$link.image"}{$link.image}{/notempty}'>
+          </div>
+        </div>
+      </div>
+      {/if}
+
+
+      <input type="hidden" name="pid" value="{$Request.param.pid}">
+
+      {notempty name="$link"}
+      <input type="hidden" name="id" value="{$link.id}">
+      {/notempty}
+      <div class="layui-form-item">
+        <div class="layui-input-block">
+          <button class="layui-btn" lay-submit lay-filter="admin">保存</button>
+          <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+        </div>
+      </div>
+    </form>
+
+
+    <script src="__PUBLIC__/layui/layui.js"></script>
+    <script src="__PUBLIC__/jquery/jquery.min.js"></script>
+    <script>
+
+        $(function () {
+            $('#upload_img').click(function () {
+                layer.open({
+                    type: 2,
+                    title: '选择图片',
+                    area: ['570px', '485px'],
+                    id: 'layerDemo', //防止重复弹出
+                    anim: 4,
+                    content: "{:url('Attachment/selectimage')}",
+                    cancel: function () {
+                        //右上角关闭回调
+                    }
+                });
+            })
+        })
+    </script>
+    <script>
+      layui.use(['layer', 'form'], function() {
+          var layer = layui.layer,
+              $ = layui.jquery,
+              form = layui.form;
+              $(window).on('load', function() {
+                  form.on('submit(admin)', function(data) {
+                      $.ajax({
+                          url:"{:url('publish')}",
+                          data:$('#publish').serialize(),
+                          type:'post',
+                          async: false,
+                          success:function(res) {
+                              if(res.code == 1) {
+                                  layer.alert(res.msg, function(index){
+                                      location.href = res.url + "?pid=" + res.data.pid;
+                                  })
+                              } else {
+                                  layer.msg(res.msg);
+                              }
+                          }
+                      })
+                      return false;
+                  });
+              });
+      });
+    </script>
+
+  </div>
+</body>
+</html>

+ 168 - 0
app/admin/view/config_tab/index.html

@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            <button class="layui-btn layui-btn-sm" lay-submit lay-filter="admin">排序</button>
+            <button class="layui-btn layui-btn-sm" lay-event="addGroupTab">新增</button>
+        </div>
+    </script>
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon"
+                                                                  style="margin-right: 0;"></i></a>
+            <a class="layui-btn layui-btn-xs delete" lay-event="del"><i class="layui-icon"
+                                                                        style="margin-right: 0;"></i></a>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                , toolbar: '#toolbarDemo'
+                , defaultToolbar: []
+                , limit: 15
+                , limits: [15, 20, 30, 50]
+                , url: "{:url('index')}" //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {
+                        key: 'sort', title: '排序', width: 60, templet: function (row) {
+                        return '<input type="text" name="sorts[]" value="' + row.sort + '" style="width: 20px;" class="sort"><input type="hidden" name="ids[]" value="' + row.id + '">';
+                    }
+                    },
+                    {field: 'id', title: 'ID', width: 60},
+                    {field: 'name', title: '名称'},
+                    {field: 'remark', title: '备注', align: 'center'},
+                    {field: 'action', title: '操作', toolbar: '#barDemo',width: 150, fixed: 'right'}
+                ]],
+                done: function () {
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'edit') {
+                    location.href = "{:url('publish')}?id=" + obj.data.id;
+                }
+                else if (obj.event == 'del') {
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            dataType: 'json',
+                            data: {id: obj.data.id},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }
+            });
+
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                if(obj.event == 'addGroupTab') {
+                    layer.prompt({
+                        title: '输入标签名'
+                    }, function (value, index, elem) {
+                        $.post("{:url('publish')}", {
+                            name: value
+                        }, function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                table.reload('table');
+                            }
+                        });
+                        layer.close(index);
+                    });
+                }
+            });
+        });
+    </script>
+    <script>
+        // 排序
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $sort_eles = $('.sort');
+                    $data = {};
+                    if ($sort_eles.length > 0) {
+                        for (var i = 0; i < $sort_eles.length; i++) {
+                            $data['sorts[' + i + ']'] = $sort_eles[i].value;
+                            $data['ids[' + i + ']'] = $sort_eles[i].nextSibling.value;
+                        }
+                    }
+                    console.log($data)
+                    $.ajax({
+                        url: "{:url('sort')}",
+                        data: $data,
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.reload();
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 84 - 0
app/admin/view/config_tab/publish.html

@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('index')}" class="a_menu">列表</a></li>
+            <li class="layui-this">编辑</li>
+        </ul>
+    </div>
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">名称</label>
+            <div class="layui-input-inline">
+                <input name="name" lay-verify="required" placeholder="请输入" autocomplete="off" class="layui-input"
+                       type="text" {notempty name="$cate.name" }value="{$cate.name}" {/notempty}>
+            </div>
+        </div>
+
+        <div class="layui-form-item layui-form-text">
+            <label class="layui-form-label">备注</label>
+            <div class="layui-input-block" style="max-width:500px;">
+                <textarea placeholder="请输入内容" class="layui-textarea" name="remark">{notempty name="$cate.remark"}{$cate.remark}{/notempty}</textarea>
+            </div>
+        </div>
+
+        {notempty name="$cate"}
+        <input type="hidden" name="id" value="{$cate.id}">
+        {/notempty}
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+            </div>
+        </div>
+
+    </form>
+
+
+    {include file="public/foot"}
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('publish')}",
+                        dataType: 'json',
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 133 - 0
app/admin/view/databackup/importlist.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <script src="__PUBLIC__/layui/layui.js" charset="utf-8"></script>
+</head>
+<body style="padding:10px;">
+
+<div class="layui-tab">
+    <ul class="layui-tab-title">
+        <li><a href="{:url('admin/databackup/index')}" class="a_menu">备份</a></li>
+        <li class="layui-this">还原</li>
+    </ul>
+</div>
+
+<div class="layui-form" style="margin:20px 0;">
+    <table class="layui-table" lay-even="" lay-skin="row" lay-size="sm">
+
+        <colgroup>
+            <col width="150">
+            <col width="150">
+            <col width="150">
+            <col width="150">
+            <col width="200">
+            <col width="200">
+            <col width="150">
+        </colgroup>
+        <thead>
+        <tr>
+            <th>备份名称</th>
+            <th>卷数</th>
+            <th>压缩</th>
+            <th>数据大小</th>
+            <th>备份时间</th>
+            <th>状态</th>
+            <th>操作</th>
+        </tr>
+        </thead>
+
+        <tbody>
+        {foreach name='list' item='data'}
+        <tr>
+            <td>{$data.time|date='Ymd-His',###}</td>
+            <td>{$data.part}</td>
+            <td>{$data.compress}</td>
+            <td>{$data.size|format_bytes}</td>
+            <td>{$key}</td>
+            <td class="status">-</td>
+            <td class="action">
+                <a class="layui-btn layui-btn-xs db-import" href="{:url('admin/databackup/import',['time'=>$data['time']])}">还原</a>&nbsp;
+                <a class="layui-btn layui-btn-xs ajax-get confirm delete" href="javascript:;" time="{$data.time}">删除</a>
+            </td>
+        </tr>
+        {/foreach}
+        </tbody>
+        <script>
+            layui.use(['jquery', 'layer'], function () {
+                window.$ = layui.$;
+                var layer = layui.layer;
+
+                $(".db-import").click(function () {
+                    var self = this, status = ".";
+                    $(this).parent().prevAll('.status').html("").html('等待还原');
+
+                    $.get(self.href, success, "json");
+                    window.onbeforeunload = function () {
+                        return "正在还原数据库,请不要关闭!"
+                    }
+                    return false;
+
+                    function success(data) {
+                        if (data.code == 1) {
+                            $(self).parent().prev().text(data.msg + '-' + (data.data.start?data.data.start:''));
+                            if (data.data.part) {
+                                $.get(self.href,
+                                    {"part": data.data.part, "start": data.data.start},
+                                    success,
+                                    "json"
+                                );
+                            } else {
+                                layer.alert(data.msg);
+                                //window.onbeforeunload = function(){ return null; }
+                            }
+                        } else {
+                            layer.alert(data.msg);
+                        }
+                    }
+                });
+
+                //   $(".db-import").click(function(){
+                //     // console.log($(this).parents().find(".status").html() );//正常
+                //     // console.log($(this).parent().prevAll('.status').html() );
+                //     var statusem=$(this).parent().prevAll('.status');
+                //     $(this).parent().prevAll('.status').html("").html('等待还原');
+                //     thisobj=this;
+                //     $.post(this.href, function(data){
+
+                //       if(data.code==1){
+                //         // statusem.text(""); // 清空数据
+                //         // statusem.append('data');
+                //         // statusem.text("").append('132');
+                //         // $(this).parent().prevAll('.status').html("").html(data.msg);//error :异常原因无法获取当前节点
+                //         statusem.html(data.msg);
+                //         getdbimport(thisobj,data.data);
+                //       }
+                //     }, "json");
+                //     return false;
+                // });
+
+                $('.delete').click(function () {
+                    var time = $(this).attr('time');
+                    $.ajax({
+                        url: "{:url('admin/databackup/del')}",
+                        dataType: 'json',
+                        data: {time: time},
+                        success: function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                setTimeout(function () {
+                                    location.href = res.url;
+                                }, 1500)
+                            }
+                        }
+                    })
+                })
+            });
+        </script>
+    </table>
+</div>
+</body>
+</html>

+ 204 - 0
app/admin/view/databackup/index.html

@@ -0,0 +1,204 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <script src="__PUBLIC__/layui/layui.js" charset="utf-8"></script>
+</head>
+<body style="padding:10px;">
+<div style="margin:20px 0px;">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">备份</li>
+            <li><a href="{:url('admin/databackup/importlist')}" class="a_menu">还原</a></li>
+        </ul>
+    </div>
+
+    <a id="export" class="layui-btn layui-btn-sm" href="javascript:;" autocomplete="off">立即备份</a>
+    <a id="optimize" href="{:url('admin/databackup/optimize')}" class="layui-btn layui-btn-sm ">优化数据表</a>
+    <a id="repair" href="{:url('admin/databackup/repair')}" class="layui-btn layui-btn-sm">修复数据表</a>
+
+    <form id="export-form" method="post" action="{:url('admin/databackup/export')}">
+        <table class="layui-table" lay-even="" lay-skin="row" lay-size="sm">
+
+            <colgroup>
+                <col width="50">
+                <col width="150">
+                <col width="150">
+                <col width="150">
+                <col width="200">
+                <col width="200">
+                <col width="150">
+            </colgroup>
+            <thead>
+            <tr>
+                <th width="48"><input lay-skin="primary" checked="chedked" type="checkbox" value=""
+                                      id="checkOrCancelAll"></th>
+                <th>表名</th>
+                <th>数据量</th>
+                <th>数据大小</th>
+                <th>创建时间</th>
+                <th>状态</th>
+                <th>操作</th>
+            </tr>
+            </thead>
+
+            <tbody>
+            {volist name='list' id='table'}
+            <tr>
+                <td>
+                    <input class="ids" checked="chedked" type="checkbox" name="tables[]" value="{$table.name}">
+                </td>
+                <td class="tablename">{$table.name}</td>
+                <td>{$table.rows}</td>
+                <td>{$table.data_length|format_bytes}</td>
+                <td>{$table.create_time}</td>
+                <td class="info">-</td>
+                <td>
+                    <a href="javascript:;" class="layui-btn layui-btn-xs optimiz" tables="{$table.name}">优化表</a>&nbsp;
+                    <a href="javascript:;" class="layui-btn layui-btn-xs repair" tables="{$table.name}">修复表</a>
+                </td>
+            </tr>
+            {/volist}
+            </tbody>
+        </table>
+    </form>
+
+    <span style="color:red;">注意 :此功能虽然便捷,但是效率差,数据多时请使用其它备份工具</span>
+</div>
+
+
+<script src="__PUBLIC__/jquery/jquery.min.js"></script>
+<script>
+    layui.use(['jquery', 'layer'], function () {
+        window.$ = layui.$;
+        var layer = layui.layer;
+        //备份表方法
+        $("#export").click(function () {
+            $(this).html("正在发送备份请求...");
+            clearmsg();
+            $.post(
+                $("#export-form").attr("action"),
+                $("#export-form").serialize(),
+                function (data) {
+                    if (data.code == 1) {
+                        $("#export").html("开始备份,请不要关闭本页面!");
+                        myload = layer.load(1, {
+                            shade: [0.5, '#fff'] //0.1透明度的白色背景
+                        });
+                        backup(data.data.tab);
+                        window.onbeforeunload = function () {
+                            return "正在备份数据库,请不要关闭!"
+                        }
+                    } else {
+                        layer.tips(data.msg, "#export", {
+                            tips: [1, '#3595CC'],
+                            time: 4000
+                        });
+                        $("#export").html("立即备份");
+                    }
+
+                }, "json");
+            return false;
+        });
+        //递归备份表
+        function backup(tab, status) {
+            status && showmsg(tab.id, "开始备份...(0%)");
+            $.get($("#export-form").attr("action"), tab, function (data) {
+                // console.log(data)
+                if (data.code == 1) {
+                    showmsg(tab, data.msg);
+
+                    if (!$.isPlainObject(data.data.tab)) {
+                        $("#export").html("备份完成");
+                        layer.close(myload);
+                        window.onbeforeunload = function () {
+                            return null
+                        }
+                        return;
+                    }
+
+                    backup(data.data.tab, tab.id != data.data.tab.id);
+                } else {
+                    $("#export").html("立即备份");
+                }
+            }, "json");
+        }
+
+        //修改备份状态
+        function showmsg(tab, msg) {
+            $(".tablename").each(function (index) {
+                if ($(this).text() == tab.tablename) {
+                    $("table tbody tr").eq(index).find(".info").html(msg)
+                }
+            })
+        }
+
+        function clearmsg() {
+            $("table tbody tr").each(function (index) {
+                $(this).find(".info").html('-');
+            })
+        }
+
+        //优化表
+        $("#optimize").click(function () {
+            $.post(this.href, $("#export-form").serialize(), function (data) {
+
+                layer.tips(data.msg, "#optimize", {
+                    tips: [1, '#3595CC'],
+                    time: 4000
+                });
+
+            }, "json");
+            return false;
+        });
+
+        //修复表
+        $("#repair").on("click", function (e) {
+            $.post(this.href, $("#export-form").serialize(), function (data) {
+                layer.tips(data.msg, "#repair", {
+                    tips: [1, '#3595CC'],
+                    time: 4000
+                });
+            }, "json");
+            return false;
+        });
+    });
+</script>
+<script type="text/javascript">
+    //全选
+    $(function () {
+        $('#checkOrCancelAll').on('click', function () {
+            if (this.checked) {
+                $(".ids").prop('checked', true);
+            } else {
+                $(".ids").prop('checked', false);
+            }
+        });
+    });
+
+    $('.optimiz').click(function () {
+        var tables = $(this).attr('tables');
+        $.ajax({
+            url: "{:url('admin/databackup/optimize')}"
+            , dataType: 'json', data: {tables: tables}
+            , success: function (res) {
+                layer.msg(res.msg);
+            }
+        })
+    })
+
+    $('.repair').on('click', function () {
+        var tables = $(this).attr('tables');
+        $.ajax({
+            url: "{:url('admin/databackup/repair')}"
+            , dataType: 'json', data: {tables: tables}
+            , success: function (res) {
+                layer.msg(res.msg);
+            }
+        })
+    })
+</script>
+</body>
+</html>

+ 166 - 0
app/admin/view/emailconfig/index.html

@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('webconfig/index')}" class="a_menu">系统设置</a></li>
+            <li class="layui-this">邮件配置</li>
+            <li><a href="{:url('smsconfig/index')}" class="a_menu">短信配置</a></li>
+            <!--config_tab-->
+            {foreach $tabs as $tab}
+            <li><a href="{:url('admin/config/index2')}?tab_id={$tab.id}">{$tab.name}</a></li>
+            {/foreach}
+        </ul>
+    </div>
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">发件邮箱</label>
+            <div class="layui-input-inline">
+                <input name="from_email" type="text" lay-verify="email" autocomplete="off" class="layui-input"
+                       value="{$data.from_email}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">发件的邮箱地址</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">账号</label>
+            <div class="layui-input-inline">
+                <input name="username" type="text" lay-verify="email" autocomplete="off" autocomplete="off"
+                       class="layui-input" value="{$data.username}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">发件邮箱的账号(一般也是邮箱地址)</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">密码</label>
+            <div class="layui-input-inline">
+                <input type="password" name="password" lay-verify="pass" autocomplete="off" class="layui-input"
+                       value="{$data.password}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">发件邮箱的密码(或授权码)</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">smtp服务器</label>
+            <div class="layui-input-inline">
+                <input name="smtp" type="text" lay-verify="pass" autocomplete="off" class="layui-input"
+                       value="{$data.smtp}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">如:smtp.126.com 或 smtp.qq.com</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">发件人</label>
+            <div class="layui-input-inline">
+                <input name="from_name" lay-verify="pass" autocomplete="off" class="layui-input" type="text"
+                       value="{$data.from_name}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">发件人名字</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">邮件标题</label>
+            <div class="layui-input-block" style="max-width: 400px">
+                <input name="title" lay-verify="pass" autocomplete="off" class="layui-input" type="text"
+                       value="{$data.title}">
+            </div>
+        </div>
+
+
+        {include file="emailconfig/publish_tinymce"}
+
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">保存</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+                <button class="layui-btn layui-btn-normal" id="mailto">发送测试</button>
+            </div>
+        </div>
+    </form>
+
+    <div class="layui-form-item">
+        <div class="layui-input-block">
+        </div>
+    </div>
+
+    <script src="__PUBLIC__/layui/layui.js"></script>
+    <script src="__PUBLIC__/jquery/jquery.min.js"></script>
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/emailconfig/publish')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+
+    <script type="text/javascript">
+        layui.use('layer', function () {
+            var layer = layui.layer;
+            var email;
+            $('#mailto').click(function () {
+                layer.prompt({
+                    formType: 0,
+                    value: '',
+                    title: '请输入收件邮箱,并点击确定'
+                }, function (value, index, elem) {
+                    email = value;
+                    // if(email = null) {
+                    //   layer.msg('收件箱不能为空');
+                    //   return false;
+                    // }
+                    $.ajax({
+                        url: "{:url('admin/emailconfig/mailto')}"
+                        , type: 'post'
+                        , dataType: 'json', data: {email: email}
+                        , success: function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                layer.close(index);
+                            }
+                        }
+                    })
+                });
+                return false;
+            })
+        });
+    </script>
+</div>
+</body>
+</html>

+ 63 - 0
app/admin/view/emailconfig/publish_tinymce.html

@@ -0,0 +1,63 @@
+<div class="layui-form-item layui-form-text">
+    <label class="layui-form-label">邮件模板</label>
+    <div class="layui-input-block">
+        <textarea name="content" id="container">{notempty name="$data.content"}{$data.content}{/notempty}</textarea>
+    </div>
+</div>
+
+
+<!-- 配置文件 -->
+<script type="text/javascript" src="__PUBLIC__/tinymce/js/tinymce/tinymce.min.js"></script>
+<!-- 实例化编辑器 -->
+<script type="text/javascript">
+    //http://tinymce.ax-z.cn/advanced/some-example.php
+    var tinyID = 'container';
+    var host = location.protocol + location.port + "//" + document.domain;
+    tinymce.init({
+        selector: '#' + tinyID,
+        language: 'zh_CN',//注意大小写
+        height: 350,
+        plugins: 'link image autosave fullscreen autolink code media preview paste',
+        toolbar: 'undo redo restoredraft| bold italic underline | image media code | fullscreen ',
+        relative_urls: false,//绝对URL
+        document_base_url: host,
+        autosave_interval: "10s",//自动存稿的世界间隔
+        //上传自定义
+        images_upload_handler: function (blobInfo, succFun, failFun) {
+            var xhr, formData;
+            var file = blobInfo.blob();//转化为易于理解的file对象
+            xhr = new XMLHttpRequest();
+            xhr.withCredentials = false;
+            xhr.open('POST', "{:url('attachment/upload')}");
+            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+            xhr.onload = function () {
+                var json;
+                if (xhr.status != 200) {
+                    failFun('HTTP Error: ' + xhr.status);
+                    return;
+                }
+                json = JSON.parse(xhr.responseText);
+                //console.log(json);
+                if (!json || typeof json.data.src != 'string') {
+                    failFun('Invalid JSON: ' + xhr.responseText);
+                    return;
+                }
+                succFun(json.data.src);
+            };
+            formData = new FormData();
+            formData.append('file', file, file.name);//此处与源文档不一样
+            formData.append('use', 'emailconfig');
+            xhr.send(formData);
+        },
+        //传统点击submit提交按钮会自动同步内容,但ajax之类的用事件提交会导致内容没有同步,暂时的解决办法是在初始化参数中setup参数里加入事件监听,让他自动同步。
+        setup: function (editor) {
+            editor.on('change', function () {
+                editor.save();
+            });
+        },
+    });
+
+    function getContent() {
+        return tinyMCE.editors[tinyID].getContent();
+    }
+</script>

+ 367 - 0
app/admin/view/file_manage/index.html

@@ -0,0 +1,367 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">文件列表</li>
+            <li><a href="javascript:;" permission="{:url('upload')}" class="a_menu" id="upload">上传文件</a></li>
+        </ul>
+    </div>
+
+    <form class="layui-form serch" action="{:url('index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+            <div class="layui-input-inline" style="width:260px">
+                <input type="text" name="keyword" autocomplete="off" placeholder="请输入文件名称,或文件路径"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline" style="width:75px">
+                <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">筛选</button>
+            </div>
+        </div>
+    </form>
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            <button class="layui-btn layui-btn-sm" lay-event="createDir">新建目录</button>
+            {notempty name="relative_path"}
+            <span style="display: inline-block;margin-right: 10px;"><i class="layui-icon">当前目录 {$Think.DS}{$relative_path}</i></span>
+            <button class="layui-btn layui-btn-sm" lay-event="goback">返回上级</button>
+            {/notempty}
+        </div>
+    </script>
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="edit" title="打开">打开</button>
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="rename" title="改名">改名</button>
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="del" title="删除">删除</button>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+
+    {include file="public/foot"}
+    <script src="__PUBLIC__/clipboard.min.js"></script>
+
+    <a id="down"></a>
+
+    <script type="text/javascript">
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                , toolbar: '#toolbarDemo'
+                , defaultToolbar: []
+                , limits: [10]
+                , url: "{:url('index')}?relative_path={$relative_path|replaceUrlDS}" //数据接口
+                , cols: [[ //表头
+                    {type: 'checkbox'},
+                    {
+                        field: 'filename', title: '名称', minWidth: 120, templet: function (row) {
+                        var fullname = getfullname(row.filename, row.fileext, row.filetype);
+                        var relative_path = "{notempty name='$relative_path'}{$relative_path|replaceUrlDS}/{/notempty}";
+                        if(row.filetype == 'dir'){
+                            return "<a href=\"{:url('index')}?relative_path=" + relative_path + fullname + "\">" + '<i class="fa fa-folder fa-lg" style="color: orange"/> ' + fullname + "</a>";
+                        }else{
+                            return "<a href='javascript:;' lay-event=\"edit\">" + '<i class="fa fa-file-o"/> ' + fullname + "</a>";
+                        }
+                    }
+                    },
+                    {
+                        field: 'path', title: '路径', templet: function (row) {
+                        var fullname = getfullname(row.filename, row.fileext, row.filetype);
+                        return "{$fullPath|addslashes}" + fullname;
+                    }
+                    },
+                    {
+                        field: 'filetype', title: '类型', width: 80, templet: function (row) {
+                        return row.filetype == 'file' ? "文件" : (row.filetype == "dir" ? "目录" : row.filetype );
+                    }
+                    },
+                    {
+                        field: 'filesize', title: '大小', width: 100, templet: function (row) {
+                        return row.filetype == 'dir' ? '' : format_bytes(row.filesize, '');
+                    }
+                    },
+                    {field: 'fileatime', title: '最后访问时间', width: 150},
+                    {field: 'filectime', title: '索引修改时间', width: 150},
+                    {field: 'filemtime', title: '内容修改时间', width: 150},
+                    {field: 'action', title: '操作', align: 'center', toolbar: '#barDemo', fixed: 'right', width: 150}
+                ]],
+                done: function (res, curr, count) {
+                    if (res.url) {
+                        console.log(res.url + '?relative_path=' + res.msg)
+                        location.href = res.url + '?relative_path=' + res.msg
+                    }
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            //编辑
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'edit') {
+                    var filename = obj.data.filename;
+                    var fileext = obj.data.fileext;
+                    var filetype = obj.data.filetype;
+                    var relative_path = "{notempty name='$relative_path'}{$relative_path|replaceUrlDS}/{/notempty}";
+                    var fullname = getfullname(filename, fileext, filetype);
+
+                    if (filetype == 'dir') {
+                        location.href = "{:url('index')}?relative_path=" + relative_path + fullname;
+                        return;
+                    }
+
+
+                    var editable = ["txt", "html", "css", "js", "log"];
+                    if (editable.indexOf(fileext) > -1) {
+                        window.parent.tab.tabAdd({
+                            icon: "fa-bookmark",
+                            id: 'filemanage_' + filename + fileext,
+                            title: '文件:' + fullname,
+                            url: "{:url('publish')}?relative_path={$relative_path|replaceUrlDS}&fullname=" + fullname
+                        });
+                        return;
+                    }
+
+                    var imgs = ["jpg", "png", "gif", "jpeg"];
+                    if (imgs.indexOf(fileext) > -1) {
+                        window.open("/uploads/q1464674022/" + relative_path + fullname, "_blank")
+                        return;
+                    }
+
+                    var videos = ["mp4"];
+                    if (videos.indexOf(fileext) > -1) {
+                        var src = "/uploads/q1464674022/" + relative_path + fullname;
+                        layer.open({
+                            type: 2,
+                            title: fullname,
+                            content: src,
+                            area: ['500px', '300px'],
+                            btn: ['复制代码'],
+                            btnAlign: 'c',
+                            yes: function(index, layero){
+                                var clipboard = new ClipboardJS('.layui-layer-btn0', {
+                                    text: function () {
+                                        return '<video width="100%" height="100%" src="'+src+'" autoplay="autoplay" controls="controls"> <source src="'+src+'" type="video/mp4"> </video>';
+                                    }
+                                });
+                                clipboard.on('success', function (e) {
+                                    layer.close(index);//如果设定了yes回调,需进行手工关闭
+                                    layer.msg('复制成功');
+                                });
+                                clipboard.on('error', function (e) {
+                                    console.log(e);
+                                    layer.msg('复制失败');
+                                });
+                            }
+                        });
+                        return;
+                    }
+
+                    var src = "/uploads/q1464674022/" + relative_path + fullname;
+                    var download = document.getElementById('down');
+                    download.setAttribute('href', src);
+                    download.setAttribute('download', fullname);
+                    download.click();
+
+                }
+                else if (obj.event == 'del') {
+                    var fullname = getfullname(obj.data.filename, obj.data.fileext, obj.data.filetype);
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            dataType: 'json',
+                            type: 'post',
+                            data: {fullname: fullname, relative_path: '{$relative_path|replaceUrlDS}'},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }
+                else if (obj.event == 'rename') {
+                    var fullname = getfullname(obj.data.filename, obj.data.fileext, obj.data.filetype);
+                    layer.prompt({
+                        title: '重命名 ' + fullname,
+                        value: fullname,
+                    }, function (value, index, elem) {
+                        $.post("{:url('rename')}", {
+                            oldname: fullname,
+                            newname: value,
+                            relative_path: '{$relative_path|replaceUrlDS}'
+                        }, function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                table.reload('table');
+                            }
+                        });
+                        layer.close(index);
+                    });
+                }
+            });
+
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                if (obj.event == 'ftp_cover') {
+                    var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                    var data = checkStatus.data;
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            var fullname = getfullname(item.filename, item.fileext, item.filetype);
+                            ids[key] = fullname;
+                        })
+                        layer.confirm('确认覆盖远程?', function (index, layero) {
+                            var load = layer.load(1, {
+                                shade: [0.1, '#fff'] //0.1透明度的白色背景
+                            });
+                            $.ajax({
+                                url: "{:url('ftpCover')}",
+                                dataType: 'json',
+                                data: {"file_list": ids, relative_path: '{$relative_path|replaceUrlDS}'},
+                                type: 'post',
+                                success: function (res) {
+                                    layer.alert(res.msg, function (index) {
+                                        layer.msg(res.msg);
+                                    })
+                                },
+                                complete: function () {
+                                    layer.close(load);
+                                }
+                            })
+                            layer.close(index)
+                        });
+                    } else {
+                        layer.msg('请先勾选需要操作的记录');
+                    }
+                }
+                else if (obj.event == 'goback') {
+                    location.href = "{:url('index')}?relative_path={$parentPath|replaceUrlDS}"
+                }
+                else if (obj.event == 'createDir') {
+                    layer.prompt({
+                        title: '新建',
+                        value: '新建文件夹',
+                    }, function (value, index, elem) {
+                        $.post("{:url('createDir')}", {
+                            name: value,
+                            relative_path: '{$relative_path|replaceUrlDS}'
+                        }, function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                table.reload('table');
+                            }
+                        });
+                        layer.close(index);
+                    });
+                }
+                else if (obj.event == 'unzip') {
+                    var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                    var data = checkStatus.data;
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        var stop = 0;
+                        data.forEach(function (item, key) {
+                            if ('zip' != item.fileext) {
+                                stop = 1;
+                            }
+                            var fullname = getfullname(item.filename, item.fileext, item.filetype);
+                            ids[key] = fullname;
+                        })
+                        if (stop) {
+                            layer.msg('只支持zip文件解压');
+                            return;
+                        }
+                        layer.confirm('解压会覆盖同名文件,请确认?', function (index, layero) {
+                            var load = layer.load(1, {
+                                shade: [0.1, '#fff'] //0.1透明度的白色背景
+                            });
+                            $.ajax({
+                                url: "{:url('unzip')}",
+                                dataType: 'json',
+                                data: {"file_list": ids, relative_path: '{$relative_path|replaceUrlDS}'},
+                                type: 'post',
+                                success: function (res) {
+                                    layer.alert(res.msg, function (index) {
+                                        layer.msg(res.msg);
+                                    })
+                                },
+                                complete: function () {
+                                    layer.close(load);
+                                    table.reload('table');
+                                }
+                            })
+                            layer.close(index)
+                        });
+                    } else {
+                        layer.msg('请先勾选需要操作的记录');
+                    }
+                }
+            });
+        });
+
+        function getfullname(filename, fileext, filetype) {
+            return filetype == 'dir' || fileext == "" ? filename : filename + '.' + fileext;
+        }
+
+    </script>
+    <script>
+        layui.use('upload', function () {
+            var $ = layui.jquery,
+                upload = layui.upload;
+
+            //指定允许上传的文件类型
+            upload.render({
+                elem: '#upload'
+                , url: "{:url('upload')}?relative_path={$relative_path|replaceUrlDS}"
+                , accept: 'file' //普通文件
+                , multiple: true
+                , exts: 'html|js|css|mp4|jpg|png|gif|zip' //只允许上传的文件
+                , allDone: function (obj) { //当文件全部被提交后,才触发
+                    console.log('总文件数:' + obj.total + '  成功的文件数:' + obj.successful + '  失败的文件数:' + obj.aborted);
+                    setTimeout(function () {
+                        location.reload();
+                    }, 1500)
+                }
+                , progress: function (n) {
+                    var percent = n + '%' //获取进度百分比
+                    element.progress('demo', percent); //可配合 layui 进度条元素使用
+                }
+                , done: function (res) { //每个文件提交一次触发一次
+                    layer.msg(res.msg);
+                }
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 149 - 0
app/admin/view/file_manage/publish.html

@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/codemirror/lib/codemirror.css">
+    <link rel="stylesheet" href="__PUBLIC__/codemirror/addon/fold/foldgutter.css" />
+    <script src="__PUBLIC__/codemirror/lib/codemirror.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/foldcode.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/foldgutter.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/brace-fold.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/xml-fold.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/indent-fold.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/comment-fold.js"></script>
+    <script src="__PUBLIC__/codemirror/mode/javascript/javascript.js"></script>
+    <script src="__PUBLIC__/codemirror/mode/xml/xml.js"></script>
+    <script src="__PUBLIC__/codemirror/mode/css/css.js"></script>
+    <script src="__PUBLIC__/codemirror/mode/htmlmixed/htmlmixed.js"></script>
+    <link rel="stylesheet" href="__PUBLIC__/codemirror/addon/display/fullscreen.css">
+    <script src="__PUBLIC__/codemirror/addon/display/fullscreen.js"></script>
+</head>
+<style>
+    .CodeMirror {border-top: 1px solid #eee; border-bottom: 1px solid #eee; height: 500px}
+</style>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    {empty name="$fullname"}
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="a_menu"><a href="{:url('index')}?relative_path={$relative_path}">文件列表</a></li>
+            <li class="layui-this">新建文件</li>
+        </ul>
+    </div>
+    {/empty}
+
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="publish" method="post">
+
+        {empty name="$fullname"}
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">当前路径</label>
+            <div class="layui-form-mid layui-word-aux">\{$relative_path}</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">文件名称</label>
+            <div class="layui-input-inline" style="max-width:300px;">
+                <input name="name" lay-verify="required" autocomplete="off" placeholder="请输入如:index.html" class="layui-input"
+                       type="text" value="{$fullname|default=''}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">须包含文件后缀</div>
+        </div>
+        {/empty}
+
+        <div class="layui-form-item layui-form-text">
+            <div class="layui-input-inline" style="width:100%;">
+                <textarea id="code-html" placeholder="请输入内容" class="layui-textarea" rows="30" name="content">{notempty name="$content"}{$content|htmlspecialchars}{/notempty}</textarea>
+            </div>
+            <div class="layui-form-mid layui-word-aux">快捷键:F11全屏,Ctrl+Q折叠代码</div>
+        </div>
+
+
+        {notempty name="$fullname"}
+        <input type="hidden" name="fullname" value="{$fullname}">
+        <input type="hidden" name="filemtime" id="filemtime" value="{$filemtime}">
+        {/notempty}
+        <input type="hidden" name="relative_path" value="{$relative_path}">
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary" onclick="location.reload()">刷新</button>
+            </div>
+        </div>
+    </form>
+
+
+    <script src="__PUBLIC__/layui/layui.js"></script>
+    <script src="__PUBLIC__/jquery/jquery.min.js"></script>
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                //
+                var te_html = document.getElementById("code-html");
+                var editor = CodeMirror.fromTextArea(te_html, {
+                    mode: "text/html",
+                    lineNumbers: true,
+                    lineWrapping: true,
+                    extraKeys: {
+                        "Ctrl-Q": function (cm) {
+                            cm.foldCode(cm.getCursor());
+                        },
+                        "F11": function(cm) {
+                            cm.setOption("fullScreen", !cm.getOption("fullScreen"));
+                        },
+                        "Esc": function(cm) {
+                            if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
+                        }
+                    },
+                    foldGutter: true,
+                    gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
+                });
+                //
+                form.on('submit(admin)', function (data) {
+                    te_html.value = editor.getValue();
+                    $.ajax({
+                        url: "{:url('publish')}",
+                        data: $('#publish').serialize(),
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                {notempty name="$fullname"}
+                                layer.confirm(res.msg, {
+                                    btn: ['关闭','继续编辑']
+                                }, function (index) {
+                                    window.parent.tab.close('filemanage_{:str_replace(".","",$fullname)}');
+                                }, function (index, layero) {
+                                    $('#filemtime').val(res.data)
+                                });
+                                {else/}
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.data.relative_path != null ? res.url + "?relative_path=" + res.data.relative_path : res.url;
+                                })
+                                {/notempty}
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 10 - 0
app/admin/view/index/index.html

@@ -0,0 +1,10 @@
+<!-- 头部公共文件 -->
+{include file="public/header"}
+<!-- 左侧公共菜单文件 -->
+{include file="public/left"}
+<div class="layui-body" id="container" style="padding:0 2px;">
+    <!-- 内容主体区域 -->
+    <div style="padding: 15px;"><i class="layui-icon layui-anim layui-anim-rotate layui-anim-loop">&#xe63e;</i> 正在拼命加载...</div>
+</div>
+<!-- 底部固定区域 -->
+{include file="public/footer"}

+ 401 - 0
app/admin/view/main/index.html

@@ -0,0 +1,401 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <title>main</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <meta name="viewport"
+        content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
+  <script src="__PUBLIC__/echarts/echarts.min.js"></script>
+  <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+  <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all">
+  <link rel="stylesheet" href="__CSS__/admin-1.css" media="all">
+<body class="layui-layout-body" style="overflow-y:visible;">
+<div class="layadmin-tabsbody-item layui-show">
+  <div class="layui-fluid">
+    <div class="layui-row layui-col-space15">
+      <div class="layui-col-md8">
+        <div class="layui-row layui-col-space15">
+          <div class="layui-col-md6">
+            <div class="layui-card">
+              <div class="layui-card-header">网站数据</div>
+              <div class="layui-card-body">
+
+                <div class="layui-carousel layadmin-carousel layadmin-backlog" lay-anim=""
+                     lay-indicator="inside" lay-arrow="none" style="width: 100%; height: 280px;">
+                  <div carousel-item="">
+                    <ul class="layui-row layui-col-space10 layui-this">
+                      <li class="layui-col-xs6">
+                        <a lay-href="" class="layadmin-backlog-body">
+                          <h3>会员</h3>
+                          <p><cite>{$web.user_num}</cite></p>
+                        </a>
+                      </li>
+                      <li class="layui-col-xs6">
+                        <a lay-href="" class="layadmin-backlog-body">
+                          <h3>文章</h3>
+                          <p><cite>{$web.article_num}</cite></p>
+                        </a>
+                      </li>
+                      <li class="layui-col-xs6">
+                        <a lay-href="" class="layadmin-backlog-body">
+                          <h3>文件</h3>
+                          <p><cite>{$web.file_num}</cite></p>
+                        </a>
+                      </li>
+                      <li class="layui-col-xs6">
+                        <a lay-href="" class="layadmin-backlog-body">
+                          <h3>消息</h3>
+                          <p><cite>{$web.message_num}</cite></p>
+                        </a>
+                      </li>
+                    </ul>
+                  </div>
+                  <button class="layui-icon layui-carousel-arrow" lay-type="sub"></button>
+                  <button class="layui-icon layui-carousel-arrow" lay-type="add"></button>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="layui-col-md6">
+            <div class="layui-card">
+              <div class="layui-card-header">待办事项</div>
+              <div class="layui-card-body">
+
+                <div class="layui-carousel layadmin-carousel layadmin-backlog" lay-anim=""
+                     lay-indicator="inside" lay-arrow="none" style="width: 100%; height: 280px;">
+                  <div carousel-item="">
+                    <ul class="layui-row layui-col-space10 layui-this">
+                      <li class="layui-col-xs6">
+                        <a lay-href="" class="layadmin-backlog-body">
+                          <h3>会员</h3>
+                          <p><cite>{$web.user_num_wait}</cite></p>
+                        </a>
+                      </li>
+                      <li class="layui-col-xs6">
+                        <a lay-href="" class="layadmin-backlog-body">
+                          <h3>文章</h3>
+                          <p><cite>{$web.status_article}</cite></p>
+                        </a>
+                      </li>
+                      <li class="layui-col-xs6">
+                        <a lay-href="" class="layadmin-backlog-body">
+                          <h3>文件</h3>
+                          <p><cite>{$web.status_file}</cite></p>
+                        </a>
+                      </li>
+                      <li class="layui-col-xs6">
+                        <a lay-href="" class="layadmin-backlog-body">
+                          <h3>消息</h3>
+                          <p><cite>{$web.look_message}</cite></p>
+                        </a>
+                      </li>
+                    </ul>
+                  </div>
+                  <button class="layui-icon layui-carousel-arrow" lay-type="sub"></button>
+                  <button class="layui-icon layui-carousel-arrow" lay-type="add"></button>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="layui-col-md12">
+            <div class="layui-card">
+              <div class="layui-card-header">管理员操作记录</div>
+              <div class="layui-card-body" id="main" style="height: 450px;">
+
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="layui-col-md4">
+        {if condition="$safe==false"}
+        <div class="layui-card">
+          <div class="layui-card-header">
+            风险提示
+          </div>
+          <div class="layui-card-body layui-text layadmin-text">
+            <ul style="color:red">
+              {notempty name="delete_install"}
+              <li>安装执行文件未删除,<a href="javascript:delinstall()">立即删除</a>?</li>
+              {/notempty}
+              {notempty name="weekpass"}
+              <li>当前密码过于简单,<a href="javascript:editpassword()">立即修改</a>?</li>
+              {/notempty}
+              {notempty name="week_backend"}
+              <li>还未设置安全入口,<a href="javascript:urlsconfig()">立即设置</a>?</li>
+              {/notempty}
+            </ul>
+          </div>
+        </div>
+        <script>
+            function delinstall() {
+                layer.confirm('确定要删除?', function (index) {
+                    $.ajax({
+                        url: "{:url('install/init/delete')}",
+                        dataType: 'json',
+                        success: function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                setTimeout(function () {
+                                    location.reload();
+                                }, 1500)
+                            }
+                        }
+                    })
+                })
+            }
+            function editpassword() {
+                window.parent.tab.tabAdd({
+                    icon: "fa-bookmark",
+                    id: '7',
+                    title: "个人信息",
+                    url: "{:url('admin/admin/editpassword')}"
+                });
+            }
+            function urlsconfig() {
+                window.parent.tab.tabAdd({
+                    icon: "fa-bookmark",
+                    id: '10',
+                    title: "系统设置",
+                    url: "{:url('admin/webconfig/index')}"
+                });
+            }
+        </script>
+        {/if}
+
+        <div class="layui-card">
+          <div class="layui-card-header">
+            公告
+          </div>
+          <div class="layui-card-body layui-text layadmin-text">
+            <p>
+              Tplay后台管理框架搭载的是国内最受欢迎的两大框架Layui和ThinkPHP,ThinkPHP的大道至简和Layui的简而全在理念上可谓是不谋而合,两大框架结合所产生的结果就是将WEB开发精简到了极致。
+              QQ交流群:<a href="https://jq.qq.com/?_wv=1027&k=AUVg7vSC" target="_blank">609048497</a>
+            </p>
+          </div>
+        </div>
+
+        <div class="layui-card">
+          <div class="layui-card-header">版本信息</div>
+          <div class="layui-card-body layui-text">
+            <table class="layui-table">
+              <colgroup>
+                <col width="100">
+                <col>
+              </colgroup>
+              <tbody>
+              <tr>
+                <td>当前版本</td>
+                <td>
+                  {$Think.const.PRODUCT_NAME}-{$Think.const.PRODUCT_VERSION}
+                  <a href="{$Think.const.PRODUCT_URL}" target="_blank"
+                     class="layui-btn layui-btn-sm">源码</a>
+                </td>
+              </tr>
+              <tr>
+                <td>下载交流</td>
+                <td style="padding-bottom: 0;">
+                  layui-2.5.6
+                  <a href="https://gitee.com/layui" target="_blank"
+                     class="layui-btn layui-btn-sm">文档</a>
+                  <a href="https://dev.layuion.com/extend/" target="_blank"
+                     class="layui-btn layui-btn-sm">插件</a>
+                </td>
+              </tr>
+              <tr>
+                <td>基于框架</td>
+                <td>
+                  thinkphp-{$info.tp}
+                  <a href="https://www.kancloud.cn/manual/thinkphp5/118003" target="_blank"
+                     class="layui-btn layui-btn-sm">文档</a>
+                </td>
+              </tr>
+              <tr>
+                <td>主要特色</td>
+                <td>适配强 / 高颜值 / 清爽 / 简洁</td>
+              </tr>
+
+              </tbody>
+            </table>
+          </div>
+        </div>
+
+        <div class="layui-card">
+          <div class="layui-card-header">系统信息</div>
+          <div class="layui-card-body layui-text">
+            <table class="layui-table">
+              <colgroup>
+                <col width="200">
+                <col>
+              </colgroup>
+              <tbody>
+              <tr>
+                <td>操作系统</td>
+                <td>
+                  {$info.win}
+                </td>
+              </tr>
+              <tr>
+                <td>运行环境</td>
+                <td>{$info.environment}</td>
+              </tr>
+              <tr>
+                <td>PHP版本</td>
+                <td>
+                  {$info.php}
+                </td>
+              </tr>
+              <tr>
+                <td>后台最大上传值</td>
+                <td>
+                  {$info.tplay_filesize|format_bytes}
+                </td>
+              </tr>
+              <tr>
+                <td title="upload_max_filesize">PHP最大上传值</td>
+                <td>
+                  {$info.upload_max_filesize}
+                </td>
+              </tr>
+              <tr>
+                <td title="post_max_size">PHP最大POST值 </td>
+                <td>
+                  {$info.post_max_size}
+                </td>
+              </tr>
+              <tr>
+                <td title="memory_limit">PHP内存限制</td>
+                <td>
+                  {$info.memory_limit}
+                </td>
+              </tr>
+              <tr>
+                <td title="max_execution_time">PHP执行时间限制</td>
+                <td>
+                  {$info.max_execution_time}
+                </td>
+              </tr>
+              {notempty name="$info.disk"}
+              <tr>
+                <td>剩余空间大小</td>
+                <td>
+                  {$info.disk}
+                </td>
+              </tr>
+              {/notempty}
+              </tbody>
+            </table>
+          </div>
+        </div>
+
+      </div>
+
+    </div>
+  </div>
+</div>
+
+<script src="__PUBLIC__/layui/layui.js"></script>
+<script>
+    layui.use(['jquery','layer'],function() {
+        window.$ = layui.$;
+        var layer = layui.layer;
+
+        {notempty name="$waring"}
+        layer.alert('{$waring.msg}', {icon: 5}, function(index){
+            location.href = "{$waring.url}";
+        });
+        {/notempty}
+    })
+</script>
+<script type="text/javascript">
+    var a = "{$web.date_string}";
+    var date = a.split(",");
+
+    var b = "{$web.login_sum}";
+    var login_sum = b.split(",");
+
+
+    var myChart = echarts.init(document.getElementById('main'));
+
+    option = {
+        tooltip: {
+            trigger: 'axis',
+            position: function (pt) {
+                return [pt[0], '10%'];
+            }
+        },
+        grid: {
+            top: 50,
+            bottom: 70,
+            left: 40,
+            right: 50
+        },
+        toolbox: {
+            feature: {
+                dataZoom: {
+                    yAxisIndex: 'none'
+                },
+                restore: {},
+                saveAsImage: {}
+            }
+        },
+        xAxis: {
+            type: 'category',
+            boundaryGap: false,
+            data: date
+        },
+        yAxis: {
+            type: 'value',
+            boundaryGap: [0, '100%']
+        },
+        dataZoom: [{
+            type: 'inside',
+            start: 0,
+            end: 100
+        }, {
+            start: 0,
+            end: 100,
+            handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
+            handleSize: '100%',
+            handleStyle: {
+                color: '#fff',
+                shadowBlur: 3,
+                shadowColor: '#009688',
+                shadowOffsetX: 2,
+                shadowOffsetY: 2
+            }
+        }],
+        series: [
+            {
+                name: '操作记录',
+                type: 'line',
+                smooth: true,
+                symbol: 'none',
+                sampling: 'average',
+                itemStyle: {
+                    normal: {
+                        color: '#009688'
+                    }
+                },
+                areaStyle: {
+                    normal: {
+                        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                            offset: 0,
+                            color: '#009688'
+                        }, {
+                            offset: 1,
+                            color: '#009688'
+                        }])
+                    }
+                },
+                data: login_sum
+            }
+        ]
+    };
+    myChart.setOption(option);
+</script>
+</body>
+</html>

+ 222 - 0
app/admin/view/menu/index.html

@@ -0,0 +1,222 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">系统菜单</li>
+            <li><a href="{:url('admin/menu/publish')}" class="a_menu">添加新菜单</a></li>
+        </ul>
+    </div>
+
+    <div id="toolbar" style="display: none">
+        <button class="layui-btn layui-btn-sm layui-btn-primary" id="open-all"><i
+                class="layui-icon layui-icon-triangle-d"></i>展开
+        </button>
+        <button class="layui-btn layui-btn-sm layui-btn-primary" id="close-all"><i
+                class="layui-icon layui-icon-triangle-r"></i>收缩
+        </button>
+        <button class="layui-btn layui-btn-sm" onclick="batchAdd()">批量添加</button>
+    </div>
+
+    <form class="layui-form" id="admin">
+        <table class="layui-table layui-form" id="tree-table" lay-size="sm"></table>
+        <button class="layui-btn layui-btn-sm" lay-submit lay-filter="admin" style="display: none" id="sort">更新排序
+        </button>
+    </form>
+
+    {include file="public/foot"}
+    <script type="text/javascript">
+        layui.extend({
+            treeTable: 'treeTable/js/treeTable'
+        }).use(['treeTable', 'layer', 'form'], function () {
+            var treeTable = layui.treeTable,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            var re = treeTable.render({
+                elem: '#tree-table',
+                url: '{:url("index")}', //数据接口
+                icon_key: 'name',
+                cols: [
+                    {
+                        key: 'sort', title: '排序', width: '40px', template: function (row) {
+                        return '<input type="text" name="orders[]" value="' + row.orders + '" style="width: 20px;" class="orders"><input type="hidden" name="id[]" value="' + row.id + '">';
+                    }
+                    },
+                    {key: 'id', title: 'ID', width: '40px'},
+                    {key: 'name', title: '名称', width: '250px'},
+                    {key: 'controller', title: '控制器', width: '100px'},
+                    {key: 'function', title: '方法', width: '100px'},
+                    {key: 'description', title: '备注', width: '100px'},
+                    {key: 'type_text', title: '状态', width: '100px'},
+                    {key: 'is_display_text', title: '类型', width: '120px'},
+                    {
+                        title: '操作', align: 'center', width: '120px',
+                        template: function (item) {
+                            var html = '<div class="layui-btn-group">';
+                            html += '<a href="publish?id=' + item.id + '" class="layui-btn layui-btn-xs a_menu layui-btn-primary" style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>';
+                            html += '<a href="publish?pid=' + item.id + '" class="layui-btn layui-btn-xs a_menu layui-btn-primary" style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>';
+                            html += '<a href="javascript:;" class="layui-btn layui-btn-xs layui-btn-primary delete" id="' + item.id + '" style="margin-right: 0;font-size:12px;"><i class="layui-icon"></i></a>';
+                            html += '</div>';
+                            return html;
+                        }
+                    }
+                ],
+                end: function (e) {
+                    form.render();
+                    if (e.data.length > 0) {
+                        $('#toolbar').show();
+                        $('#sort').show();
+                    }
+                    // 全部展开
+                    $('#open-all').click(function () {
+                        treeTable.openAll(re);
+                    })
+                    // 全部关闭
+                    $('#close-all').click(function () {
+                        treeTable.closeAll(re);
+                    })
+                    del();
+                },
+            });
+        });
+    </script>
+
+    <form id="myForm" style="display:none;margin: 10px 30px;" class="layui-form alert-form">
+        {empty name="noInsertRoutes"}当前没有路由可添加{/empty}
+        {foreach name="noInsertRoutes" item="noInsertRoute" key="k"}
+        <div class="layui-form-item">
+            <div class="layui-inline">
+                <div class="layui-input-inline">
+                    <select name="pid[{$k}]" lay-search="" lay-verify="required">
+                        <option value="">选择上级节点</option>
+                        <option value="0">作为顶级菜单</option>
+                        {volist name="$menus" id="vo"}
+                        <option value="{$vo.id}">{$vo.str}{$vo.name}</option>
+                        {/volist}
+                    </select>
+                </div>
+                <div class="layui-input-inline" style="width: 150px;">
+                    <input type="text" name="name[{$k}]" placeholder="节点名称" autocomplete="off" class="layui-input">
+                </div>
+                <div class="layui-input-inline" style="width: 100px;">
+                    <input type="text" name="module[{$k}]" placeholder="模块名" value="{$noInsertRoute[0]}" autocomplete="off" class="layui-input">
+                </div>
+                <div class="layui-input-inline" style="width: 100px;">
+                    <input type="text" name="controller[{$k}]" placeholder="控制器名" value="{$noInsertRoute[1]}" autocomplete="off" class="layui-input">
+                </div>
+                <div class="layui-input-inline" style="width: 100px;">
+                    <input type="text" name="function[{$k}]" placeholder="方法名" value="{$noInsertRoute[2]}" autocomplete="off" class="layui-input">
+                </div>
+                <div class="layui-input-inline" style="width: 140px;">
+                    <select name="is_display[{$k}]">
+                        <option value="2">只做为操作节点</option>
+                        <option value="1">显示在左侧菜单</option>
+                    </select>
+                </div>
+                <div class="layui-input-inline" style="width: 100px;">
+                    <select name="type[{$k}]">
+                        <option value="1">权限节点</option>
+                        <option value="2">普通节点</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+        {/foreach}
+    </form>
+    <script>
+        function batchAdd() {
+            var fromobj = $('#myForm');
+            if(fromobj.text().trim() == '当前没有路由可添加'){
+                layer.msg('当前没有路由可添加');
+                return;
+            }
+            //弹出框
+            layer.open({
+                type: 1,
+                title: '未添加的路由 (只有填上节点名称的才会添加路由)',
+                area: ['1100px', '500px'],
+                id: 'layerDemo', //防止重复弹出
+                content: fromobj,
+                btn: ['批量添加', '关闭'],
+                btnAlign: 'c', //按钮居中
+                yes: function (index, layero) {
+                    var post_data = fromobj.serializeJson();
+                    $.post("{:url('admin/menu/batchAdd')}", post_data, function (json) {
+                        layer.msg(json.msg);
+                        if (json.code > 0) {
+                            layer.close(index);
+                            location.reload()
+                        }
+                    });
+                },
+                btn2: function (index) {
+                },
+                zIndex: 999 //重点1
+            });
+        }
+    </script>
+    <script>
+        function del() {
+            $('.delete').click(function () {
+                var id = $(this).attr('id');
+                layer.confirm('确定要删除?', function (index) {
+                    $.ajax({
+                        url: "{:url('admin/menu/delete')}",
+                        data: {id: id},
+                        dataType: 'json',
+                        success: function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                setTimeout(function () {
+                                    location.href = res.url;
+                                }, 1500)
+                            }
+                        }
+                    })
+                })
+            })
+        }
+        //排序
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/menu/orders')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 164 - 0
app/admin/view/menu/publish.html

@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('admin/menu/index')}" class="a_menu">系统菜单</a></li>
+            <li class="layui-this">添加新菜单</li>
+        </ul>
+    </div>
+    <form class="layui-form" id="admin">
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">上级菜单</label>
+            <div class="layui-input-inline">
+            <select name="pid" lay-filter="aihao" lay-search="" lay-verify="required">
+                    <option value="0">作为顶级菜单</option>
+                    {volist name="$menus" id="vo"}
+                    <option value="{$vo.id}" {notempty name="$menu.pid"}{eq name="$menu.pid" value="$vo.id"}selected=""{/eq}{else/}{notempty name="$pid"}{eq name="$pid" value="$vo.id"}selected=""{/eq}{/notempty}{/notempty}>{$vo.str}{$vo.name}</option>
+                    {/volist}
+            </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">名称</label>
+            <div class="layui-input-inline">
+                <input name="name" lay-verify="required" placeholder="请输入" autocomplete="off" class="layui-input" type="text" {notempty name="$menu.name"}value="{$menu.name}"{/notempty}>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">模块名</label>
+            <div class="layui-input-inline">
+                <input name="module" placeholder="请输入module" autocomplete="off" class="layui-input" type="text" value="{notempty name='$menu.module'}{$menu.module}{else/}admin{/notempty}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">如果仅作为父级菜单,请留空</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">控制器名</label>
+            <div class="layui-input-inline">
+                <input name="controller" placeholder="请输入controller" autocomplete="off" class="layui-input" type="text" {notempty name="$menu.controller"}value="{$menu.controller}"{/notempty}>
+            </div>
+            <div class="layui-form-mid layui-word-aux">如果仅作为父级菜单,请留空</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">方法名</label>
+            <div class="layui-input-inline">
+                <input name="function" placeholder="请输入action" autocomplete="off" class="layui-input" type="text" {notempty name="$menu.function"}value="{$menu.function}"{/notempty}>
+            </div>
+            <div class="layui-form-mid layui-word-aux">如果仅作为父级菜单,请留空</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">参数</label>
+            <div class="layui-input-inline">
+                <input name="parameter" placeholder="请输入" autocomplete="off" class="layui-input" type="text" {notempty name="$menu.parameter" }value="{$menu.parameter}" {/notempty}>
+            </div>
+            <div class="layui-form-mid layui-word-aux">请用'&'隔开,例如:name=tingyu&id=10</div>
+        </div>
+
+        <div class="layui-form-item layui-form-text">
+            <label class="layui-form-label">备注</label>
+            <div class="layui-input-block" style="max-width:600px;">
+                <textarea placeholder="请输入内容" class="layui-textarea" name="description">{notempty name="$menu.description"}{$menu.description}{/notempty}</textarea>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">图标</label>
+            <div class="layui-input-inline">
+                <input name="icon" placeholder="请输入" autocomplete="off" class="layui-input" type="text" value="{notempty name='$menu.icon'}{$menu.icon}{else/}fa-tag{/notempty}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">例如:fa-tag,图标参考:<a href="https://fontawesome.dashgame.com/" target="_block">点击查看</a></div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">类型</label>
+            <div class="layui-input-inline">
+                <select name="is_display" lay-filter="aihao">
+                    <option value="1" {notempty name="$menu.is_display" }{eq name="$menu.is_display" value="1" } selected="" {/eq}{/notempty}>显示在左侧菜单</option>
+                    <option value="2" {notempty name="$menu.is_display" }{eq name="$menu.is_display" value="2" } selected="" {/eq}{/notempty}>只做为操作节点</option>
+                </select>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">状态</label>
+            <div class="layui-input-inline">
+                <select name="type" lay-filter="aihao">
+                    <option value="1" {notempty name="$menu.type" }{eq name="$menu.type" value="1" } selected="" {/eq}{/notempty}>权限节点</option>
+                    <option value="2" {notempty name="$menu.type" }{eq name="$menu.type" value="2" } selected="" {/eq}{/notempty}>普通节点</option>
+                </select>
+            </div>
+            <div class="layui-form-mid layui-word-aux">注意:如果是权限节点,要配置权限组才会开放使用</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">默认展开</label>
+            <div class="layui-input-block">
+                <div class="layui-input-inline">
+                    <input type="checkbox" name="is_open" lay-skin="switch" lay-text="ON|OFF" value="1" {notempty name="$menu.is_open" }{eq name="$menu.is_open" value="1" }checked="" {/eq}{/notempty}>
+                </div>
+                <div class="layui-form-mid layui-word-aux">仅支持二级菜单</div>
+            </div>
+        </div>
+
+        {notempty name="$menu"}
+        <input type="hidden" name="id" value="{$menu.id}">
+        {/notempty}
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+            </div>
+        </div>
+
+    </form>
+
+
+    {include file="public/foot"}
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/menu/publish')}",
+                        data: $('#admin').serialize(),
+                        type: 'post',
+                        dataType: 'json',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 129 - 0
app/admin/view/point_log/index.html

@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>layui</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+  <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+  <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+  {include file="user/tab"}
+
+  <form class="layui-form serch" action="{:url('index')}" method="post">
+    <div class="layui-form-item" style="float: left;">
+      <div class="layui-input-inline">
+        <input type="text" class="layui-input" autocomplete="off" placeholder="用户ID" name="user_id">
+      </div>
+      <div class="layui-input-inline">
+        <div class="layui-inline">
+          <select name="type" lay-search="">
+            <option value="">类型</option>
+            {foreach $types as $k=>$vo}
+            <option value="{$k}">{$vo}</option>
+            {/foreach}
+          </select>
+        </div>
+      </div>
+      <div class="layui-input-inline">
+            <input type="text" class="layui-input" autocomplete="off" id="time_range" placeholder="操作时间" name="create_time">
+      </div>
+      <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+    </div>
+  </form>
+
+  <script type="text/html" id="toolbarDemo">
+    <div class="layui-btn-container">
+      <button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="deletes">批量删除</button>
+    </div>
+  </script>
+
+  <table class="layui-table" id="table" lay-filter="table"></table>
+
+  {include file="public/foot"}
+
+  <script type="text/javascript">
+
+      layui.use(['table', 'layer', 'form'], function () {
+          var table = layui.table,
+              form = layui.form,
+              layer = layui.layer;
+          //第一个实例
+          table.render({
+              id: 'table'
+              , elem: '#table'
+              , size: 'sm' //小尺寸的表格
+              , toolbar: '#toolbarDemo' //
+              , defaultToolbar: ['filter']  //
+              , limit: 15
+              , limits: [15, 20, 30, 40, 50, 100]
+              , url: '{:url("index")}' //数据接口
+              , page: true //开启分页
+              , cols: [[ //表头
+                  {type: 'checkbox'},
+                  {field: 'id', title: 'ID', width: 80},
+                  {field: 'user_id', title: '用户id'},
+                  {field: 'before', title: '变化前的积分'},
+                  {field: 'change', title: '变化的积分',templet:function (row) {
+                      return row.symbol +" "+ row.change;
+                  }},
+                  {field: 'final', title: '变换后的积分'},
+                  {field: 'type_text', title: '类型'},
+                  {field: 'remark', title: '说明'},
+                  {field: 'create_time', title: '时间'}
+              ]],
+          });
+
+          form.on('submit(serch)', function (data) {
+              table.reload('table', {
+                  where: data.field
+                  , page: {
+                      curr: 1 //重新从第 1 页开始
+                  }
+              });
+              return false;
+          });
+
+          //监听事件
+          table.on('toolbar(table)', function (obj) {
+              if (obj.event == 'deletes') {
+                  var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                  var data = checkStatus.data;
+                  if (data.length > 0) {
+
+                      var ids = [];//数组
+                      data.forEach(function (item, key) {
+                          ids[key] = item.id;
+                      })
+
+                      layer.confirm('是否删除?', function (index, layero) {
+                          $.ajax({
+                              url: "{:url('deletes')}",
+                              data: {"ids": ids},
+                              type: 'post',
+                              dataType: 'json',
+                              success: function (res) {
+                                  layer.msg(res.msg);
+                                  if (res.code == 1) {
+                                      table.reload('table');
+                                  }
+                              }
+                          })
+                          layer.close(index)
+                      });
+                  } else {
+                      layer.msg('请先勾选需要操作的记录');
+                  }
+              }
+          })
+      });
+  </script>
+
+</div>
+</body>
+</html>

+ 134 - 0
app/admin/view/public/foot.html

@@ -0,0 +1,134 @@
+<script src="__PUBLIC__/layui/layui.js" charset="utf-8"></script>
+<script src="__PUBLIC__/jquery/jquery.min.js"></script>
+<script src="__PUBLIC__/helper.js"></script>
+<script>
+    var message;
+    layui.config({
+        base: '__EXTEND_JS__/',
+        version: '1.0.2'
+    }).use(['app', 'message'], function () {
+        var app = layui.app,
+            $ = layui.jquery,
+            layer = layui.layer;
+        //将message设置为全局以便子页面调用
+        message = layui.message;
+        //主入口
+        app.set({
+            type: 'iframe'
+        }).init();
+    });
+</script>
+<script type="text/javascript">
+    $(function () {
+        showThumb()
+    })
+    function showThumb() {
+        var x = 10;
+        var y = 20;
+        $(".tooltip").mouseover(function (e) {
+            var tooltip = "<div id='tooltip'><img src='" + this.href + "' alt='产品预览图' height='200'/>" + "<\/div>"; //创建 div 元素
+            $("body").append(tooltip);  //把它追加到文档中
+            $("#tooltip")
+                .css({
+                    "top": (e.pageY + y) + "px",
+                    "left": (e.pageX + x) + "px"
+                }).show("fast");    //设置x坐标和y坐标,并且显示
+        }).mouseout(function () {
+            $("#tooltip").remove();  //移除
+        }).mousemove(function (e) {
+            $("#tooltip")
+                .css({
+                    "top": (e.pageY + y) + "px",
+                    "left": (e.pageX + x) + "px"
+                });
+        });
+    }
+</script>
+<script type="text/javascript">
+    //判断tab是否有权限
+    $('.a_menu').click(function () {
+        var url = $(this).attr('href');
+        var permission = $(this).attr('permission');
+        if (url == 'javascript:;') {
+            url = permission;
+        }
+        console.log('tab链接 ' + url);
+        var a = true;
+        $.ajax({
+            url: url,
+            type: 'get',
+            async: false,
+            data: {check_permission: true},
+            success: function (res) {
+                if (res.code == 0 && res.msg) {
+                    //提示并终止打开tab标签
+                    layer.msg(res.msg);
+                    a = false;
+                }
+            }
+        })
+        return a;
+    })
+</script>
+<script>
+    layui.use('laydate', function () {
+        var laydate = layui.laydate;
+
+        laydate.render({
+            elem: '#date_time',
+            max: 0 //最大值0天后
+        });
+
+        laydate.render({
+            elem: '#time_range'
+            , type: 'datetime'
+            , range: true
+            , max: '{:date("Y-m-d 23:59:59",time())}' //最大值
+            , mark: {
+                '{:date("Y-m-d",time())}': '今天'
+            }
+            , theme: 'molv'
+            , calendar: true
+            , done: function (value, date, endDate) {
+                if (endDate.hours == 0 && endDate.minutes == 0 && endDate.seconds == 0) {
+                    setTimeout(function () {
+                        $('#time_range').val(value.replace(/00:00:00$/, '23:59:59'))
+                    }, 100)
+                }
+            }
+        });
+    });
+</script>
+<script>
+    function switchStatus(classname, url) {
+        $(classname).click(function () {
+            var val = $(this).attr('data-val');
+            var id = $(this).attr('data-id');
+            var i = $(this).find('i');
+            var the = $(this);
+            var status = (val == 0) ? 1 : 0;
+            $.ajax({
+                type: "post",
+                url: url,
+                data: {status: status, id: id},
+                dataType: 'json',
+                success: function (res) {
+                    if (res.code == 1) {
+                        tostatus();
+                    } else {
+                        layer.msg(res.msg);
+                    }
+                }
+            })
+            function tostatus() {
+                if (val == 0) {
+                    i.attr("class", "fa fa-toggle-on");
+                    the.attr('data-val', 1);
+                } else {
+                    i.attr("class", "fa fa-toggle-off");
+                    the.attr('data-val', 0);
+                }
+            }
+        })
+    }
+</script>

+ 178 - 0
app/admin/view/public/footer.html

@@ -0,0 +1,178 @@
+</div>
+<script src="__PUBLIC__/layui/layui.js"></script>
+<script src="__PUBLIC__/jquery/jquery.min.js"></script>
+<script>
+    var message;
+    layui.config({
+        base: '__EXTEND_JS__/',
+        version: '1.0.2'
+    }).use(['app', 'message'], function () {
+        var app = layui.app,
+            $ = layui.jquery,
+            layer = layui.layer;
+        //将message设置为全局以便子页面调用
+        message = layui.message;
+        //主入口
+        app.set({
+            type: 'iframe'
+        }).init();
+
+        $('dl.skin > dd').on('click', function () {
+            var $that = $(this);
+            var skin = $that.children('a').data('skin');
+            switchSkin(skin);
+        });
+        var setSkin = function (value) {
+                layui.data('kit_skin', {
+                    key: 'skin',
+                    value: value
+                });
+            },
+            getSkinName = function () {
+                return layui.data('kit_skin').skin;
+            },
+            switchSkin = function (value) {
+                var _target = $('link[kit-skin]')[0];
+                _target.href = _target.href.substring(0, _target.href.lastIndexOf('/') + 1) + value + _target.href.substring(_target.href.lastIndexOf('.'));
+                setSkin(value);
+            },
+            initSkin = function () {
+                var skin = getSkinName();
+                switchSkin(skin === undefined ? 'default' : skin);
+            }();
+
+        $('#color').click(function () {
+            layer.open({
+                type: 1,
+                title: '配色方案',
+                area: ['290px', 'calc(100% - 52px)'],
+                offset: 'rb',
+                shadeClose: true,
+                id: 'colors',
+                anim: 2,
+                shade: 0.2,
+                closeBtn: 0,
+                isOutAnim: false,
+                resize: false,
+                move: false,
+                skin: 'color-class',
+                btn: ['黑白格', '橘子橙', '原谅绿', '少女粉', '天空蓝', '枫叶红'],
+                yes: function (index, layero) {
+                    switchSkin('default');
+                }
+                , btn2: function (index, layero) {
+                    switchSkin('orange');
+                    return false;
+                }
+                , btn3: function (index, layero) {
+                    switchSkin('green');
+                    return false;
+                }
+                , btn4: function (index, layero) {
+                    switchSkin('pink');
+                    return false;
+                }
+                , btn5: function (index, layero) {
+                    switchSkin('blue.1');
+                    return false;
+                }
+                , btn6: function (index, layero) {
+                    switchSkin('red');
+                    return false;
+                }
+
+            });
+        })
+    });
+</script>
+<script type="text/javascript">
+    layui.use('jquery', function () {
+        var $ = layui.jquery;
+        $('#clear').on('click', function () {
+            var the = $(this).find('i');
+            the.attr("class", "fa fa-spinner");
+            $.ajax({
+                url: "{:url('admin/main/clear')}"
+                , success: function (res) {
+                    if (res.code == 1) {
+                        setTimeout(function () {
+                            parent.message.show({
+                                skin: 'cyan',
+                                msg: res.msg
+                            });
+                            $('#clear i').attr("class", "fa fa-envira");
+                        }, 1000)
+                    }
+                }
+            })
+        });
+    });
+
+    $('#logout').click(function () {
+        layer.confirm('真的要退出?', {icon: 3, title: '提示', anim: 2}, function (index) {
+            $.ajax({
+                url: "{:url('admin/index/logout')}"
+                , success: function (res) {
+                    layer.msg(res.msg, {offset: '250px', anim: 4});
+                    if (res.code == 1) {
+                        setTimeout(function () {
+                            location.href = res.url;
+                        }, 2000)
+                    }
+                }
+            })
+        })
+    })
+
+    $('.layui-nav-item').click(function () {
+        $(this).siblings('li').attr('class', 'layui-nav-item');
+    })
+
+    function gologin(){
+        location.href = "{:url('admin/common/login')}"
+    }
+</script>
+
+<script type="text/javascript">
+    layui.use('layer', function () {
+        var layer = layui.layer;
+        var remember = '';
+
+        $('#tag').click(function () {
+            var tag = localStorage.getItem("tag");
+            layer.prompt({
+                formType: 2,
+                anim: 1,
+                offset: ['52px', 'calc(100% - 500px)'],
+                value: tag,
+                title: '消息',
+                skin: 'demo-class',
+                area: ['280px', '150px'],
+                id: 'remember',//设定一个id,防止重复弹出
+                btn: ['发送消息', '保存草稿'],
+                shade: 0,
+                moveType: 1, //拖拽模式,0或者1
+                btn2: function (index, layero) {
+                    var value = $('#remember textarea').val();
+                    localStorage.setItem("tag", value);
+                }
+            }, function (value, index, elem) {
+                layer.confirm('确认发送?', function (index2, layero) {
+                    $.ajax({
+                        url: "{:url('admin/tomessages/publish')}",
+                        dataType: 'json',
+                        data: {message: value},
+                        type: 'post',
+                        success: function (res) {
+                            layer.msg(res.msg);
+                            $('#refresh').click();
+                        }
+                    });
+                    layer.close(index2);
+                });
+            })
+        });
+    });
+</script>
+</body>
+</html>

+ 48 - 0
app/admin/view/public/header.html

@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <!--<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">-->
+    <title>{:systemName()}后台管理</title>
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all" />
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all" />
+    <link rel="stylesheet" href="__CSS__/app.css" media="all" />
+    <link rel="stylesheet" href="__CSS__/themes/default.css" media="all" id="skin" kit-skin />
+    <link rel="stylesheet" href="__CSS__/admin.css" />
+</head>
+
+<body class="kit-theme">
+    <div class="layui-layout layui-layout-admin kit-layout-admin">
+        <div class="layui-header">
+            <div class="layui-logo" id="logo"><span>{:systemName()}</span></div>
+            <ul class="layui-nav layui-layout-left kit-nav kit-tab-tool-body">
+                <li class="layui-nav-item"><a id="kit-side-fold" title="左侧菜单" style="color:#333;"><i class="layui-icon layui-icon-shrink-right" aria-hidden="true"></i></a></li>
+                <li class="layui-nav-item"><a href="/" target="_block" title="主页" style="color:#333;"><i class="fa fa-desktop" aria-hidden="true"></i></a></li>
+                <li class="layui-nav-item"><a id="clear" title="清空缓存" style="color:#333;"><i class="fa fa-envira" aria-hidden="true"></i></a></li>
+                <li class="layui-nav-item"><a class="kit-item" data-target="refresh" title="刷新当前页" style="color:#333;" id="refresh"><i class="fa fa-refresh" aria-hidden="true"></i></a></li>
+            </ul>
+            <ul class="layui-nav layui-layout-right kit-nav">
+                <li class="layui-nav-item"><a href="javascript:;" id="tag" style="color:#333;"><i class="fa fa-tag" aria-hidden="true"></i></a></li>
+                <li class="layui-nav-item" style="background-color: transparent !important;">
+                    <a href="javascript:;" id="color" style="color:#333;" class="refresh">皮肤<span class="layui-badge-dot"></span></a>
+                    </a>
+                </li>
+                <li class="layui-nav-item">
+                    <a href="javascript:edituser()" style="color:#333;">
+                        <img src="{:geturl($admin.thumb,'/static/public/images/tx.jpg')}" class="layui-nav-img">欢迎, {$Think.session.admin_name}
+                    </a>
+                    <script>
+                        function edituser() {
+                            //window.frames[0].location.href = "{:url('admin/admin/personal')}";
+                            window.parent.tab.tabAdd({
+                                icon: "fa-bookmark",
+                                id: '7',
+                                title: "个人信息",
+                                url: "{:url('admin/admin/personal')}"
+                            });
+                        }
+                    </script>
+                </li>
+                <li class="layui-nav-item"><a href="javascript:;"  id="logout" style="color:#333;"><span><i class="fa fa-sign-out" aria-hidden="true"></i> 退出</span></a></li>
+            </ul>
+        </div>

+ 29 - 0
app/admin/view/public/left.html

@@ -0,0 +1,29 @@
+        <div class="layui-side layui-bg-black kit-side">
+            <div class="layui-side-scroll">
+                <ul class="layui-nav layui-nav-tree" lay-filter="kitNavbar" kit-navbar>
+                {volist name="menus" id="data"}
+                    <li class="layui-nav-item {if condition="$data.is_open eq 1"}layui-nav-itemed{/if}">
+                        <a href="javascript:;"><i class="fa {$data.icon}" aria-hidden="true"></i><span> {$data.name}</span></a>
+                        <ul class="menu_ul layui-nav-child">
+                        {notempty name="$data.list"}{volist name="$data.list" id="vo"}
+                            {notempty name="$vo.list"}
+                            <li>
+                                <a class="table" href="javascript:;"><i class="fa {$vo.icon}"></i><span> {$vo.name}</span> <span class="angle"><i class="fa fa-angle-down"></i><span></span></a>
+                                <ul {if condition="$vo.is_open eq 1"}style="display:block;"{/if}>
+                                    {volist name="$vo.list" id="co"}
+                                    <li><a href="javascript:;" data-url="{$co.url}" data-icon="{$co.icon}" data-title="{$co.name}" kit-target data-id='{$co.id}'><i class="fa {$co.icon}" aria-hidden="true"></i><span> {$co.name}</span></a></li>
+                                    {/volist}
+                                </ul>
+                            </li>
+                            {else /}
+                            <li>
+                                <a href="javascript:;" data-url="{$vo.url}" data-icon="{$vo.icon}" data-title="{$vo.name}" kit-target data-id='{$vo.id}'><i class="fa {$vo.icon}" aria-hidden="true"></i><span> {$vo.name}</span></a>
+                            </li>
+                            {/notempty}
+                        {/volist}{/notempty}
+                        </ul>
+                    </li>
+                {/volist}
+                </ul>
+            </div>
+        </div>

+ 164 - 0
app/admin/view/smsconfig/index.html

@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li><a href="{:url('webconfig/index')}" class="a_menu">系统设置</a></li>
+            <li><a href="{:url('emailconfig/index')}" class="a_menu">邮件配置</a></li>
+            <li class="layui-this">短信配置</li>
+            <!--config_tab-->
+            {foreach $tabs as $tab}
+            <li><a href="{:url('admin/config/index2')}?tab_id={$tab.id}">{$tab.name}</a></li>
+            {/foreach}
+        </ul>
+    </div>
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="admin">
+
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">AppKey</label>
+            <div class="layui-input-inline">
+                <input name="appkey" type="keywords" lay-verify="pass" autocomplete="off" class="layui-input"
+                       value="{$data.appkey}" placeholder="">
+            </div>
+            <div class="layui-form-mid layui-word-aux">阿里大鱼短信接口的AppKey,<a href="https://dayu.aliyun.com/"
+                                                                          target="_blank">点击申请</a></div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">SecretKey</label>
+            <div class="layui-input-inline">
+                <input name="secretkey" lay-verify="pass" autocomplete="off" class="layui-input" type="keywords"
+                       value="{$data.secretkey}" placeholder="">
+            </div>
+            <div class="layui-form-mid layui-word-aux">阿里大鱼短信接口的SecretKey,<a href="https://dayu.aliyun.com/"
+                                                                             target="_blank">点击申请</a></div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">短信类型</label>
+            <div class="layui-input-inline">
+                <input name="type" type="text" lay-verify="pass" autocomplete="off" class="layui-input"
+                       value="{$data.type}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">文字短信默认为:normal</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">签名</label>
+            <div class="layui-input-inline">
+                <input name="name" type="text" lay-verify="pass" autocomplete="off" autocomplete="off"
+                       class="layui-input" value="{$data.name}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">必须是在阿里大于“管理中心-验证码/短信通知/推广短信-配置短信签名”中的可用签名</div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">短信模板id</label>
+            <div class="layui-input-inline">
+                <input type="text" name="code" lay-verify="pass" autocomplete="off" class="layui-input"
+                       value="{$data.code}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">短信模板ID,传入的模板必须是在阿里大于“管理中心-短信模板管理”中的可用模板</div>
+        </div>
+
+        <div class="layui-form-item layui-form-text">
+            <label class="layui-form-label">附加内容</label>
+            <div class="layui-input-block" style="max-width:600px;">
+                <textarea placeholder="可通过设置阿里短信的系统变量来调用" class="layui-textarea" name="content" id="container">{$data.content}</textarea>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+                <button class="layui-btn layui-btn-normal" id="smsto">发送测试</button>
+            </div>
+        </div>
+    </form>
+
+    <div class="layui-form-item">
+        <div class="layui-input-block">
+        </div>
+    </div>
+
+    <script src="__PUBLIC__/layui/layui.js"></script>
+    <script src="__PUBLIC__/jquery/jquery.min.js"></script>
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                form.on('submit(admin)', function (data) {
+                    $.ajax({
+                        url: "{:url('admin/smsconfig/publish')}",
+                        data: $('#admin').serialize(),
+                        dataType: 'json',
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+
+
+    <script type="text/javascript">
+        layui.use('layer', function () {
+            var layer = layui.layer;
+            var phone;
+            $('#smsto').click(function () {
+                layer.prompt({
+                    formType: 0,
+                    value: '',
+                    title: '请输入手机号码,不要重复点确定键'
+                }, function (value, index, elem) {
+                    phone = value;
+                    // if(email = null) {
+                    //   layer.msg('收件箱不能为空');
+                    //   return false;
+                    // }
+                    $.ajax({
+                        url: "{:url('admin/smsconfig/smsto')}"
+                        , type: 'post'
+                        , dataType: 'json', data: {phone: phone}
+                        , success: function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                layer.close(index);
+                            }
+                        }
+                    })
+                });
+                return false;
+            })
+        });
+    </script>
+</div>
+</body>
+</html>

+ 208 - 0
app/admin/view/templet/index.html

@@ -0,0 +1,208 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="layui-this">模板管理</li>
+            <li><a href="{:url('publish')}" class="a_menu">新增模板</a></li>
+            <li><a href="javascript:;" permission="{:url('upload')}" class="a_menu" id="upload">上传模板</a></li>
+        </ul>
+    </div>
+
+    <form class="layui-form serch" action="{:url('index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+            <div class="layui-input-inline">
+                <input type="text" name="name" autocomplete="off" placeholder="请输入名称"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline" style="width:75px">
+                <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">查询</button>
+            </div>
+        </div>
+    </form>
+
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="edit" title="编辑">编辑</button>
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="rename" title="改名">改名</button>
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="del" title="删除">删除</button>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                , defaultToolbar: []
+                , url: '{:url("index")}' //数据接口
+                , page: true //开启分页
+                , limit: 15
+                , limits: [15, 20, 30, 40, 50, 100]
+                , cols: [[ //表头
+                    {type: 'checkbox'},
+                    {
+                        field: 'filename', title: '模板名称', minWidth: 120, templet: function (row) {
+                        return "<a href='javascript:;' title='单击打开' lay-event='edit'>" + '<i class="fa fa-file-code-o fa-lg"/> ' + row.filename + "</a>";
+                    }
+                    },
+                    {
+                        field: 'path', title: '路径', templet: function (row) {
+                        return "{$tpPath|addslashes}" + row.filename + "." + row.fileext;
+                    }
+                    },
+                    {
+                        field: 'fileext', title: '格式', width: 100, templet: function (row) {
+                        if (row.fileext != 'html') {
+                            return "<span style='color:red;cursor: pointer;' lay-event='rename2'>无效格式</span>";
+                        } else {
+                            return row.fileext;
+                        }
+                    }
+                    },
+                    {
+                        field: 'filesize', title: '文件大小', width: 100, templet: function (row) {
+                        return format_bytes(row.filesize, '');
+                    }
+                    },
+                    {field: 'create_time', title: '创建时间', width: 200},
+                    {field: 'update_time', title: '修改时间', width: 200},
+                    {field: 'action', title: '操作', align: 'center', toolbar: '#barDemo', fixed: 'right', width: 200}
+                ]],
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            //编辑
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'edit') {
+                    window.parent.tab.tabAdd({
+                        icon: "fa-bookmark",
+                        id: 'templet' + obj.data.filename,
+                        title: '<i class="fa fa-file-code-o"/> ' + obj.data.filename,
+                        url: "{:url('admin/templet/publish')}?filename=" + obj.data.filename
+                    });
+                }
+                else if (obj.event == 'del') {
+                    var fullname = getfullname(obj.data.filename, obj.data.fileext, obj.data.filetype);
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            dataType: 'json',
+                            type: 'post',
+                            data: {fullname: fullname},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }
+                else if (obj.event == 'rename') {
+                    var fullname = getfullname(obj.data.filename, obj.data.fileext, obj.data.filetype);
+                    layer.prompt({
+                        title: '重命名 ' + fullname,
+                        value: fullname,
+                    }, function (value, index, elem) {
+                        $.post("{:url('rename')}", {
+                            oldname: fullname,
+                            newname: value,
+                        }, function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                table.reload('table');
+                            }
+                        });
+                        layer.close(index);
+                    });
+                }
+                else if (obj.event == 'rename2') {
+                    var fullname = getfullname(obj.data.filename, obj.data.fileext, obj.data.filetype);
+                    layer.prompt({
+                        title: '重命名 ' + fullname,
+                        value: obj.data.filename + '.html',
+                    }, function (value, index, elem) {
+                        $.post("{:url('rename')}", {
+                            oldname: fullname,
+                            newname: value,
+                        }, function (res) {
+                            layer.msg(res.msg);
+                            if (res.code == 1) {
+                                table.reload('table');
+                            }
+                        });
+                        layer.close(index);
+                    });
+                }
+                //
+            });
+
+        });
+
+        function getfullname(filename, fileext, filetype) {
+            return filetype == 'dir' || fileext == "" ? filename : filename + '.' + fileext;
+        }
+    </script>
+    <script>
+        layui.use('upload', function () {
+            var $ = layui.jquery,
+                upload = layui.upload;
+
+            //指定允许上传的文件类型
+            upload.render({
+                elem: '#upload'
+                , url: "{:url('upload')}"
+                , accept: 'file' //普通文件
+                , multiple: true
+                , exts: 'html' //允许上传的文件格式
+                , allDone: function (obj) { //当文件全部被提交后,才触发
+                    console.log('总文件数:' + obj.total + '  成功的文件数:' + obj.successful + '  失败的文件数:' + obj.aborted);
+                    setTimeout(function () {
+                        location.reload();
+                    }, 1500)
+                }
+                , progress: function (n) {
+                    var percent = n + '%' //获取进度百分比
+                    element.progress('demo', percent); //可配合 layui 进度条元素使用
+                }
+                , done: function (res) { //每个文件提交一次触发一次
+                    layer.msg(res.msg);
+                }
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 149 - 0
app/admin/view/templet/publish.html

@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/codemirror/lib/codemirror.css">
+    <link rel="stylesheet" href="__PUBLIC__/codemirror/addon/fold/foldgutter.css" />
+    <script src="__PUBLIC__/codemirror/lib/codemirror.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/foldcode.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/foldgutter.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/brace-fold.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/xml-fold.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/indent-fold.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/fold/comment-fold.js"></script>
+    <script src="__PUBLIC__/codemirror/mode/javascript/javascript.js"></script>
+    <script src="__PUBLIC__/codemirror/mode/xml/xml.js"></script>
+    <script src="__PUBLIC__/codemirror/mode/css/css.js"></script>
+    <script src="__PUBLIC__/codemirror/mode/htmlmixed/htmlmixed.js"></script>
+    <link rel="stylesheet" href="__PUBLIC__/codemirror/addon/display/fullscreen.css">
+    <script src="__PUBLIC__/codemirror/addon/display/fullscreen.js"></script>
+    <link rel="stylesheet" href="__PUBLIC__/codemirror/addon/dialog/dialog.css">
+    <script src="__PUBLIC__/codemirror/addon/dialog/dialog.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/search/searchcursor.js"></script>
+    <script src="__PUBLIC__/codemirror/addon/search/search.js"></script>
+</head>
+<style>
+    .CodeMirror {border-top: 1px solid #eee; border-bottom: 1px solid #eee; height: 500px}
+</style>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    {empty name="$filename"}
+    <div class="layui-tab">
+        <ul class="layui-tab-title">
+            <li class="a_menu"><a href="{:url('index')}">模板管理</a></li>
+            <li class="layui-this">新增模板</li>
+        </ul>
+    </div>
+    {/empty}
+
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="publish" method="post">
+
+        {empty name="$filename"}
+        <div class="layui-form-item">
+            <!--<label class="layui-form-label">模板名称</label>-->
+            <div class="layui-input-inline" style="max-width:300px;">
+                <input name="name" lay-verify="required" autocomplete="off" placeholder="请输入模板名称" class="layui-input"
+                       type="text" value="{$filename|default=''}">
+            </div>
+            <div class="layui-form-mid layui-word-aux">可使用字母、数字和下划线_命名</div>
+        </div>
+        {/empty}
+
+
+        <div class="layui-form-item layui-form-text">
+            <div class="layui-input-inline" style="width:100%;">
+                <textarea id="code-html" placeholder="请输入内容" class="layui-textarea" rows="30" name="content">{notempty name="$content"}{$content|htmlspecialchars}{/notempty}</textarea>
+            </div>
+            <div class="layui-form-mid layui-word-aux">快捷键:F11全屏,Ctrl+Q:折叠代码,Alt-F:开始搜索,输入Enter:查找下一个,Shift-Enter:查找上一个,Shift-Ctrl-R:替换全部</div>
+        </div>
+
+
+        {notempty name="$filename"}
+        <input type="hidden" name="filename" value="{$filename}">
+        <input type="hidden" name="filemtime" id="filemtime" value="{$filemtime}">
+        {/notempty}
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+                <button type="reset" class="layui-btn layui-btn-primary" onclick="location.reload()">刷新</button>
+            </div>
+        </div>
+    </form>
+
+
+    <script src="__PUBLIC__/layui/layui.js"></script>
+    <script src="__PUBLIC__/jquery/jquery.min.js"></script>
+    <script>
+        layui.use(['layer', 'form'], function () {
+            var layer = layui.layer,
+                $ = layui.jquery,
+                form = layui.form;
+            $(window).on('load', function () {
+                //
+                var te_html = document.getElementById("code-html");
+                var editor = CodeMirror.fromTextArea(te_html, {
+                    mode: "text/html",
+                    lineNumbers: true,
+                    lineWrapping: true,
+                    extraKeys: {
+                        "Ctrl-Q": function (cm) {
+                            cm.foldCode(cm.getCursor());
+                        },
+                        "F11": function(cm) {
+                            cm.setOption("fullScreen", !cm.getOption("fullScreen"));
+                        },
+                        "Esc": function(cm) {
+                            if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
+                        },
+                        "Alt-F": "findPersistent"
+                    },
+                    foldGutter: true,
+                    gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
+                });
+                //
+                form.on('submit(admin)', function (data) {
+                    te_html.value = editor.getValue();
+                    $.ajax({
+                        url: "{:url('publish')}",
+                        data: $('#publish').serialize(),
+                        type: 'post',
+                        async: false,
+                        success: function (res) {
+                            if (res.code == 1) {
+                                {notempty name="$filename"}
+                                layer.confirm(res.msg, {
+                                    btn: ['关闭','继续编辑']
+                                }, function (index) {
+                                    window.parent.tab.close('templet{$filename|default=""}');
+                                }, function (index, layero) {
+                                    $('#filemtime').val(res.data)
+                                    console.log(res.data);
+                                });
+                                {else/}
+                                layer.alert(res.msg, function (index) {
+                                    location.href = res.url;
+                                })
+                                {/notempty}
+                            } else {
+                                layer.msg(res.msg);
+                            }
+                        }
+                    })
+                    return false;
+                });
+            });
+        });
+    </script>
+</div>
+</body>
+</html>

+ 346 - 0
app/admin/view/tomessages/index.html

@@ -0,0 +1,346 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>layui</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all"/>
+    <link rel="stylesheet" href="__CSS__/admin.css" media="all">
+    <style type="text/css">
+
+        /* tooltip */
+        #tooltip {
+            position: absolute;
+            border: 1px solid #ccc;
+            background: #333;
+            padding: 2px;
+            display: none;
+            color: #fff;
+        }
+    </style>
+</head>
+<body style="padding:10px;">
+<div class="tplay-body-div">
+
+    {include file="user/tab"}
+
+    <form class="layui-form serch" action="{:url('index')}" method="post">
+        <div class="layui-form-item" style="float: left;">
+
+            <div class="layui-input-inline" style="width: 90px;">
+                <input type="text" name="id" lay-verify="title" autocomplete="off" placeholder="消息ID"
+                       class="layui-input layui-btn-sm">
+            </div>
+
+            <div class="layui-input-inline" style="width: 90px;">
+                <input type="text" name="from_user_id" lay-verify="title" autocomplete="off" placeholder="发送人ID"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline" style="width: 90px;">
+                <input type="text" name="to_user_id" lay-verify="title" autocomplete="off" placeholder="接收人ID"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline">
+                <input type="text" name="keywords" lay-verify="title" autocomplete="off" placeholder="查找内容"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline">
+                <input type="text" name="ip" lay-verify="title" autocomplete="off" placeholder="IP"
+                       class="layui-input layui-btn-sm">
+            </div>
+            <div class="layui-input-inline">
+                <input type="text" class="layui-input" id="time_range" placeholder="时间" autocomplete="off"
+                       name="create_time">
+            </div>
+            <div class="layui-input-inline" style="width: 90px;">
+                <select name="is_look" lay-search="">
+                    <option value="">阅读状态</option>
+                    <option value="0">未读</option>
+                    <option value="1">已读</option>
+                </select>
+            </div>
+            <div class="layui-input-inline" style="width: 90px;">
+                <select name="status" lay-search="">
+                    <option value="">审核</option>
+                    <option value="0">待审核</option>
+                    <option value="1">已审核</option>
+                    <option value="-1">已拒绝</option>
+                </select>
+            </div>
+            <button class="layui-btn layui-btn-sm" lay-submit="" lay-filter="serch">立即提交</button>
+        </div>
+    </form>
+
+    <script type="text/html" id="toolbarDemo">
+        <div class="layui-btn-container">
+            <button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="deletes">批量删除</button>
+            <button class="layui-btn layui-btn-sm" lay-event="sysmessage">回复消息</button>
+            <button class="layui-btn layui-btn-sm" lay-event="sendmessage">发送公告</button>
+        </div>
+    </script>
+
+    <script type="text/html" id="barDemo">
+        <div class="layui-btn-group">
+            <button class="layui-btn layui-btn-xs a_menu" lay-event="edit"><i class="layui-icon"
+                                                                              style="margin-right: 0;"></i></button>
+            <button class="layui-btn layui-btn-xs delete" lay-event="del"><i class="layui-icon"
+                                                                             style="margin-right: 0;"></i></button>
+        </div>
+    </script>
+
+    <table class="layui-table" id="table" lay-filter="table"></table>
+
+
+    {include file="public/foot"}
+
+    <script type="text/javascript">
+
+        layui.use(['table', 'layer', 'form'], function () {
+            var table = layui.table,
+                form = layui.form,
+                layer = layui.layer;
+            //第一个实例
+            table.render({
+                id: 'table'
+                , elem: '#table'
+                , size: 'sm' //小尺寸的表格
+                , toolbar: '#toolbarDemo'
+//                , defaultToolbar: []
+                , limit: 15
+                , limits: [15, 20, 30, 40, 50, 100]
+                , url: "{:url('index')}" //数据接口
+                , page: true //开启分页
+                , cols: [[ //表头
+                    {type: 'checkbox'},
+                    {field: 'id', title: 'ID', width: 60},
+                    {field: 'from_user', title: '消息发送人', width: 160},
+                    {field: 'message', title: '内容', minWidth:200},
+                    {field: 'to_user', title: '消息接收人', width: 160},
+                    {field: 'to_msg_id', title: '回复消息ID', width: 160},
+                    {field: 'create_time', title: '时间', width: 160},
+                    {
+                        field: 'is_look', title: '阅读状态', align: 'center', width: 80, templet: function (row) {
+                        return '<a href="javascript:;" style="font-size:18px;" class="is_look" data-id="' + row.id + '" data-val="' + row.is_look + '">' + (row.is_look == 1 ? '<i class="fa fa-toggle-on"></i>' : '<i class="fa fa-toggle-off"></i>') + '</a>';
+                    }
+                    },
+                    {
+                        field: 'status', title: '审核', width: 80, fixed: 'right', templet: function (row) {
+                        if (row.status == 1) {
+                            return '<span class="layui-badge status" style="background-color: #8FCDA0" data-id="'+row.id+'">'+row.status_text+'</span>';
+                        } else if (row.status == -1) {
+                            return '<span class="layui-badge status" data-id="'+row.id+'">'+row.status_text+'</span>';
+                        } else {
+                            return '<span class="layui-badge layui-bg-gray status" data-id="'+row.id+'">'+row.status_text+'</span>';
+                        }
+                    }
+                    },
+                    {field: 'ip', title: 'IP', width: 100},
+//                    {field: 'action', title: '操作', align: 'center', toolbar: '#barDemo', fixed: 'right',width:100}
+                ]],
+                done: function () {
+                    switchLook();
+                    switchStatus(table);
+                }
+            });
+
+            form.on('submit(serch)', function (data) {
+                table.reload('table', {
+                    where: data.field
+                    , page: {
+                        curr: 1 //重新从第 1 页开始
+                    }
+                });
+                return false;
+            });
+
+            //编辑
+            table.on('tool(table)', function (obj) {
+                if (obj.event == 'edit') {
+                    location.href = "{:url('publish')}?id=" + obj.data.id;
+                }
+                else if (obj.event == 'del') {
+                    layer.confirm('确定要删除?', function (index) {
+                        $.ajax({
+                            url: "{:url('delete')}",
+                            dataType: 'json',
+                            data: {id: obj.data.id},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    })
+                }
+                //
+            });
+
+            //监听事件
+            table.on('toolbar(table)', function (obj) {
+                if (obj.event == 'deletes') {
+                    var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                    var data = checkStatus.data;
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            ids[key] = item.id;
+                        })
+                        layer.confirm('是否删除?', function (index, layero) {
+                            $.ajax({
+                                url: "{:url('deletes')}",
+                                dataType: 'json',
+                                data: {"ids": ids},
+                                type: 'post',
+                                success: function (res) {
+                                    layer.msg(res.msg);
+                                    if (res.code == 1) {
+                                        table.reload('table');
+                                    }
+                                }
+                            })
+                            layer.close(index)
+                        });
+                    } else {
+                        layer.msg('请先勾选,需要操作的记录');
+                    }
+                }
+                else if (obj.event == 'sysmessage') {
+                    var checkStatus = table.checkStatus(obj.config.id);//获取选中的数据
+                    var data = checkStatus.data;
+                    if (data.length > 0) {
+                        var ids = [];//数组
+                        data.forEach(function (item, key) {
+                            ids[key] = item.id;
+                        })
+
+                        layer.prompt({
+                            formType: 2,
+                            anim: 1,
+                            title: '请输入内容',
+                            skin: 'demo-class',
+                            area: ['280px', '150px'],
+                            id: 'remember',//设定一个id,防止重复弹出
+                            moveType: 1, //拖拽模式,0或者1
+                        }, function (value, index, elem) {
+                            layer.confirm('确认发送?', function (index2, layero) {
+                                $.ajax({
+                                    url: "{:url('sysmessage')}",
+                                    dataType: 'json',
+                                    data: {"ids": ids, message: value},
+                                    type: 'post',
+                                    success: function (res) {
+                                        layer.msg(res.msg);
+                                        if (res.code == 1) {
+                                            table.reload('table');
+                                        }
+                                    }
+                                });
+                                layer.close(index2);
+                                layer.close(index);
+                            });
+                        })
+                    } else {
+                        layer.msg('请先勾选,需要回复的消息');
+                    }
+                }
+                else if(obj.event == 'sendmessage'){
+                    window.parent.document.getElementById('tag').click();
+                }
+            });
+
+        });
+    </script>
+    <script>
+        function switchLook() {
+            $('.is_look').click(function () {
+                var val = $(this).attr('data-val');
+                var id = $(this).attr('data-id');
+                var i = $(this).find('i');
+                var the = $(this);
+                if (val == 1) {
+                    var status = 0;
+                } else {
+                    var status = 1;
+                }
+                $.ajax({
+                    type: "post",
+                    url: "{:url('look')}",
+                    dataType: 'json',
+                    data: {id: id, status: status},
+                    success: function (res) {
+                        if (res.code == 1) {
+                            tostatus();
+                        } else {
+                            layer.msg(res.msg);
+                        }
+                    }
+                })
+
+                function tostatus() {
+                    if (val == 1) {
+                        i.attr("class", "fa fa-toggle-off");
+                        the.attr('data-val', 0);
+                    } else {
+                        i.attr("class", "fa fa-toggle-on");
+                        the.attr('data-val', 1);
+                    }
+                }
+            })
+        }
+
+        function switchStatus(table) {
+            $('.status').click(function () {
+                var id = $(this).attr('data-id');
+                layer.msg('审核', {
+                    time: 20000,
+                    btn: ['通过', '拒绝','待审', '再想想'],
+                    yes: function (index, layero) {
+                        $.ajax({
+                            url: "{:url('admin/tomessages/status')}",
+                            type: 'post',
+                            dataType: 'json', data: {id: id, status: '1'},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    },
+                    btn2: function (index, layero) {
+                        $.ajax({
+                            url: "{:url('admin/tomessages/status')}",
+                            type: 'post',
+                            dataType: 'json', data: {id: id, status: '-1'},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    }
+                    ,btn3: function(index, layero){
+                        $.ajax({
+                            url: "{:url('admin/tomessages/status')}",
+                            type: 'post',
+                            dataType: 'json', data: {id: id, status: '0'},
+                            success: function (res) {
+                                layer.msg(res.msg);
+                                if (res.code == 1) {
+                                    table.reload('table');
+                                }
+                            }
+                        })
+                    },
+                })
+            })
+        }
+    </script>
+</div>
+</body>
+</html>

+ 94 - 0
app/admin/view/tomessages/publish.html

@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>layui</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <link rel="stylesheet" href="__PUBLIC__/layui/css/layui.css"  media="all">
+  <link rel="stylesheet" href="__PUBLIC__/font-awesome/css/font-awesome.min.css" media="all" />
+  <link rel="stylesheet" href="__CSS__/admin.css"  media="all">
+</head>
+<body style="padding:10px;">
+  <div class="tplay-body-div">
+
+    <div class="layui-tab">
+      <ul class="layui-tab-title">
+        <li><a href="{:url('index')}" class="a_menu">消息管理</a></li>
+        <li class="layui-this">修改消息</li>
+      </ul>
+    </div>
+
+    <div style="margin-top: 20px;">
+    </div>
+    <form class="layui-form" id="publish" method="post">
+
+      <div class="layui-form-item">
+        <label class="layui-form-label">发送者id</label>
+        <div class="layui-input-block" style="max-width:300px;">
+          <input name="from_user_id" lay-verify="" autocomplete="off" placeholder="请输入" class="layui-input" type="text" {notempty name="$link.from_user_id"}value="{$link.from_user_id}"{/notempty}>
+        </div>
+      </div>
+
+      <div class="layui-form-item">
+        <label class="layui-form-label">接收者id</label>
+        <div class="layui-input-block" style="max-width:300px;">
+          <input name="to_user_id"  autocomplete="off" placeholder="请输入" class="layui-input" type="text" {notempty name="$link.to_user_id"}value="{$link.to_user_id}"{/notempty}>
+        </div>
+      </div>
+
+      <div class="layui-form-item layui-form-text">
+        <label class="layui-form-label">消息内容</label>
+        <div class="layui-input-block" style="max-width:600px;">
+          <textarea placeholder="请输入内容" lay-verify="required" class="layui-textarea" name="message">{notempty name="$link.message"}{$link.message}{/notempty}</textarea>
+        </div>
+      </div>
+
+
+
+      {notempty name="$link"}
+      <input type="hidden" name="id" value="{$link.id}">
+      {/notempty}
+      <div class="layui-form-item">
+        <div class="layui-input-block">
+          <button class="layui-btn" lay-submit lay-filter="admin">立即提交</button>
+          <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+        </div>
+      </div>
+    </form>
+
+
+    <script src="__PUBLIC__/layui/layui.js"></script>
+    <script src="__PUBLIC__/jquery/jquery.min.js"></script>
+    <script>
+      layui.use(['layer', 'form'], function() {
+          var layer = layui.layer,
+              $ = layui.jquery,
+              form = layui.form;
+              $(window).on('load', function() {
+                  form.on('submit(admin)', function(data) {
+                      $.ajax({
+                          url:"{:url('publish')}",
+                          data:$('#publish').serialize(),
+                          type:'post',
+                          async: false,
+                          success:function(res) {
+                              if(res.code == 1) {
+                                  layer.alert(res.msg, function(index){
+                                      location.href = res.url;
+                                  })
+                              } else {
+                                  layer.msg(res.msg);
+                              }
+                          }
+                      })
+                      return false;
+                  });
+              });
+      });
+    </script>
+
+  </div>
+</body>
+</html>

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно