Browse Source

同步升级

* 增加又拍云存储支持
* 增加ckeditor5支持
* 其他部分优化
Anyon 2 years ago
parent
commit
d33753cf39
100 changed files with 503 additions and 206 deletions
  1. 1 1
      app/admin/controller/Oplog.php
  2. 22 3
      app/admin/controller/api/Runtime.php
  3. 8 1
      app/admin/controller/api/Upload.php
  4. 282 123
      app/admin/view/api/upload.js
  5. 29 1
      app/admin/view/config/index.html
  6. 5 6
      app/admin/view/config/storage-0.html
  7. 7 7
      app/admin/view/config/storage-alioss.html
  8. 3 3
      app/admin/view/config/storage-local.html
  9. 7 7
      app/admin/view/config/storage-qiniu.html
  10. 8 8
      app/admin/view/config/storage-txcos.html
  11. 79 0
      app/admin/view/config/storage-upyun.html
  12. 4 1
      app/admin/view/index/index.html
  13. 1 1
      app/admin/view/oplog/index.html
  14. 2 4
      app/data/view/base/pager/form.html
  15. 2 2
      app/data/view/base/upgrade/index.html
  16. 2 2
      app/data/view/news/item/form.html
  17. 1 1
      app/data/view/shop/goods/form.html
  18. 3 0
      app/service.php
  19. 9 3
      app/wechat/view/news/form.html
  20. 25 30
      public/static/admin.js
  21. 3 2
      public/static/plugs/ckeditor4/ckeditor.js
  22. 0 0
      public/static/plugs/ckeditor4/config.js
  23. 0 0
      public/static/plugs/ckeditor4/contents.css
  24. 0 0
      public/static/plugs/ckeditor4/lang/en.js
  25. 0 0
      public/static/plugs/ckeditor4/lang/zh-cn.js
  26. 0 0
      public/static/plugs/ckeditor4/lang/zh.js
  27. 0 0
      public/static/plugs/ckeditor4/plugins/a11yhelp/dialogs/a11yhelp.js
  28. 0 0
      public/static/plugs/ckeditor4/plugins/a11yhelp/dialogs/lang/en.js
  29. 0 0
      public/static/plugs/ckeditor4/plugins/a11yhelp/dialogs/lang/zh-cn.js
  30. 0 0
      public/static/plugs/ckeditor4/plugins/a11yhelp/dialogs/lang/zh.js
  31. 0 0
      public/static/plugs/ckeditor4/plugins/about/dialogs/about.js
  32. 0 0
      public/static/plugs/ckeditor4/plugins/about/dialogs/hidpi/logo_ckeditor.png
  33. 0 0
      public/static/plugs/ckeditor4/plugins/about/dialogs/logo_ckeditor.png
  34. 0 0
      public/static/plugs/ckeditor4/plugins/clipboard/dialogs/paste.js
  35. 0 0
      public/static/plugs/ckeditor4/plugins/colordialog/dialogs/colordialog.css
  36. 0 0
      public/static/plugs/ckeditor4/plugins/colordialog/dialogs/colordialog.js
  37. 0 0
      public/static/plugs/ckeditor4/plugins/copyformatting/cursors/cursor-disabled.svg
  38. 0 0
      public/static/plugs/ckeditor4/plugins/copyformatting/cursors/cursor.svg
  39. 0 0
      public/static/plugs/ckeditor4/plugins/copyformatting/styles/copyformatting.css
  40. 0 0
      public/static/plugs/ckeditor4/plugins/dialog/dialogDefinition.js
  41. 0 0
      public/static/plugs/ckeditor4/plugins/dialog/styles/dialog.css
  42. 0 0
      public/static/plugs/ckeditor4/plugins/div/dialogs/div.js
  43. 0 0
      public/static/plugs/ckeditor4/plugins/exportpdf/plugindefinition.js
  44. 0 0
      public/static/plugs/ckeditor4/plugins/find/dialogs/find.js
  45. 0 0
      public/static/plugs/ckeditor4/plugins/forms/dialogs/button.js
  46. 0 0
      public/static/plugs/ckeditor4/plugins/forms/dialogs/checkbox.js
  47. 0 0
      public/static/plugs/ckeditor4/plugins/forms/dialogs/form.js
  48. 0 0
      public/static/plugs/ckeditor4/plugins/forms/dialogs/hiddenfield.js
  49. 0 0
      public/static/plugs/ckeditor4/plugins/forms/dialogs/radio.js
  50. 0 0
      public/static/plugs/ckeditor4/plugins/forms/dialogs/select.js
  51. 0 0
      public/static/plugs/ckeditor4/plugins/forms/dialogs/textarea.js
  52. 0 0
      public/static/plugs/ckeditor4/plugins/forms/dialogs/textfield.js
  53. 0 0
      public/static/plugs/ckeditor4/plugins/forms/images/hiddenfield.gif
  54. 0 0
      public/static/plugs/ckeditor4/plugins/icons.png
  55. 0 0
      public/static/plugs/ckeditor4/plugins/icons_hidpi.png
  56. 0 0
      public/static/plugs/ckeditor4/plugins/iframe/dialogs/iframe.js
  57. 0 0
      public/static/plugs/ckeditor4/plugins/iframe/images/placeholder.png
  58. 0 0
      public/static/plugs/ckeditor4/plugins/image/dialogs/image.js
  59. 0 0
      public/static/plugs/ckeditor4/plugins/image/images/noimage.png
  60. 0 0
      public/static/plugs/ckeditor4/plugins/lineheight/lang/en.js
  61. 0 0
      public/static/plugs/ckeditor4/plugins/lineheight/lang/zh-cn.js
  62. 0 0
      public/static/plugs/ckeditor4/plugins/lineheight/lang/zh.js
  63. 0 0
      public/static/plugs/ckeditor4/plugins/lineheight/plugin.js
  64. 0 0
      public/static/plugs/ckeditor4/plugins/link/dialogs/anchor.js
  65. 0 0
      public/static/plugs/ckeditor4/plugins/link/dialogs/link.js
  66. 0 0
      public/static/plugs/ckeditor4/plugins/link/images/anchor.png
  67. 0 0
      public/static/plugs/ckeditor4/plugins/link/images/hidpi/anchor.png
  68. 0 0
      public/static/plugs/ckeditor4/plugins/liststyle/dialogs/liststyle.js
  69. 0 0
      public/static/plugs/ckeditor4/plugins/magicline/images/hidpi/icon-rtl.png
  70. 0 0
      public/static/plugs/ckeditor4/plugins/magicline/images/hidpi/icon.png
  71. 0 0
      public/static/plugs/ckeditor4/plugins/magicline/images/icon-rtl.png
  72. 0 0
      public/static/plugs/ckeditor4/plugins/magicline/images/icon.png
  73. 0 0
      public/static/plugs/ckeditor4/plugins/pagebreak/images/pagebreak.gif
  74. 0 0
      public/static/plugs/ckeditor4/plugins/pastefromgdocs/filter/default.js
  75. 0 0
      public/static/plugs/ckeditor4/plugins/pastefromlibreoffice/filter/default.js
  76. 0 0
      public/static/plugs/ckeditor4/plugins/pastefromword/filter/default.js
  77. 0 0
      public/static/plugs/ckeditor4/plugins/pastetools/filter/common.js
  78. 0 0
      public/static/plugs/ckeditor4/plugins/pastetools/filter/image.js
  79. 0 0
      public/static/plugs/ckeditor4/plugins/preview/images/pagebreak.gif
  80. 0 0
      public/static/plugs/ckeditor4/plugins/preview/preview.html
  81. 0 0
      public/static/plugs/ckeditor4/plugins/preview/styles/screen.css
  82. 0 0
      public/static/plugs/ckeditor4/plugins/scayt/dialogs/dialog.css
  83. 0 0
      public/static/plugs/ckeditor4/plugins/scayt/dialogs/options.js
  84. 0 0
      public/static/plugs/ckeditor4/plugins/scayt/dialogs/toolbar.css
  85. 0 0
      public/static/plugs/ckeditor4/plugins/scayt/skins/moono-lisa/scayt.css
  86. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_address.png
  87. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_blockquote.png
  88. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_div.png
  89. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_h1.png
  90. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_h2.png
  91. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_h3.png
  92. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_h4.png
  93. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_h5.png
  94. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_h6.png
  95. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_p.png
  96. 0 0
      public/static/plugs/ckeditor4/plugins/showblocks/images/block_pre.png
  97. 0 0
      public/static/plugs/ckeditor4/plugins/smiley/dialogs/smiley.js
  98. 0 0
      public/static/plugs/ckeditor4/plugins/smiley/images/angel_smile.gif
  99. 0 0
      public/static/plugs/ckeditor4/plugins/smiley/images/angel_smile.png
  100. 0 0
      public/static/plugs/ckeditor4/plugins/smiley/images/angry_smile.gif

+ 1 - 1
app/admin/controller/Oplog.php

@@ -73,7 +73,7 @@ class Oplog extends Controller
     {
         try {
             SystemOplog::mQuery()->empty();
-            sysoplog('系统运维管理', '成功清理所有日志数据');
+            sysoplog('系统运维管理', '成功清理所有日志');
             $this->success('日志清理成功!');
         } catch (HttpResponseException $exception) {
             throw $exception;

+ 22 - 3
app/admin/controller/api/Runtime.php

@@ -39,7 +39,7 @@ class Runtime extends Controller
         if (AdminService::instance()->isSuper()) try {
             AdminService::instance()->clearCache();
             SystemService::instance()->pushRuntime();
-            sysoplog('系统运维管理', '刷新创建路由缓存');
+            sysoplog('系统运维管理', '刷新创建路由缓存');
             $this->success('网站缓存加速成功!', 'javascript:location.reload()');
         } catch (HttpResponseException $exception) {
             throw $exception;
@@ -59,8 +59,8 @@ class Runtime extends Controller
         if (AdminService::instance()->isSuper()) try {
             AdminService::instance()->clearCache();
             SystemService::instance()->clearRuntime();
-            sysoplog('系统运维管理', '清理网站日志缓存数据');
-            $this->success('清空缓存日志成功!', 'javascript:location.reload()');
+            sysoplog('系统运维管理', '清理网站日志缓存');
+            $this->success('清空日志缓存成功!', 'javascript:location.reload()');
         } catch (HttpResponseException $exception) {
             throw $exception;
         } catch (\Exception $exception) {
@@ -90,6 +90,25 @@ class Runtime extends Controller
     }
 
     /**
+     * 修改富文本编辑器
+     * @return void
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function editor()
+    {
+        if (AdminService::instance()->isSuper()) {
+            $editor = input('editor', 'auto');
+            sysconf('base.editor', $editor);
+            sysoplog('系统运维管理', "切换编辑器为{$editor}");
+            $this->success('已切换后台编辑器!', 'javascript:location.reload()');
+        } else {
+            $this->error('只有超级管理员才能操作!');
+        }
+    }
+
+    /**
      * 清理系统配置
      * @login true
      */

+ 8 - 1
app/admin/controller/api/Upload.php

@@ -22,6 +22,7 @@ use think\admin\storage\AliossStorage;
 use think\admin\storage\LocalStorage;
 use think\admin\storage\QiniuStorage;
 use think\admin\storage\TxcosStorage;
+use think\admin\storage\UpyunStorage;
 use think\exception\HttpResponseException;
 use think\file\UploadedFile;
 use think\Response;
@@ -92,6 +93,12 @@ class Upload extends Controller
             $data['q-signature'] = $token['q-signature'];
             $data['q-sign-algorithm'] = $token['q-sign-algorithm'];
             $data['server'] = TxcosStorage::instance()->upload();
+        } elseif ('upyun' === $data['uptype']) {
+            $token = UpyunStorage::instance()->buildUploadToken($data['key'], 3600, $name, input('size'), input('hash'));
+            $data['url'] = $token['siteurl'];
+            $data['policy'] = $token['policy'];
+            $data['authorization'] = $token['authorization'];
+            $data['server'] = UpyunStorage::instance()->upload();
         }
         $this->success('获取上传授权参数', $data, 404);
     }
@@ -176,7 +183,7 @@ class Upload extends Controller
     private function getType(): string
     {
         $type = strtolower(input('uptype', ''));
-        if (in_array($type, ['local', 'qiniu', 'alioss', 'txcos'])) {
+        if (in_array($type, ['local', 'qiniu', 'alioss', 'txcos', 'uptype'])) {
             return $type;
         } else {
             return strtolower(sysconf('storage.type'));

+ 282 - 123
app/admin/view/api/upload.js

@@ -1,151 +1,310 @@
-define(['md5'], function (SparkMD5, allowMime) {
+define(['md5', 'notify'], function (SparkMD5, Notify, allowMime) {
     allowMime = JSON.parse('{$exts|raw}');
-    return function (element, callable) {
-
-        /*! 初始化变量 */
-        var opt = {elem: $(element), exts: [], mimes: [], files: {}, cache: {}, load: 0, count: {total: 0, uploaded: 0}};
-        opt.size = opt.elem.data('size') || 0, opt.mult = opt.elem.data('multiple') > 0;
-        opt.safe = opt.elem.data('safe') ? 1 : 0, opt.hide = opt.elem.data('hide-load') ? 1 : 0;
-        opt.type = opt.safe ? 'local' : opt.elem.attr('data-uptype') || '';
-
-        /*! 查找表单元素, 如果没有找到将不会自动写值 */
-        if (!opt.elem.data('input') && opt.elem.data('field')) {
-            var $input = $('input[name="' + opt.elem.data('field') + '"]:not([type=file])');
-            opt.elem.data('input', $input.size() > 0 ? $input.get(0) : null);
-        }
 
-        /*! 文件选择筛选,使用 MIME 规则过滤文件列表 */
-        $((opt.elem.data('type') || '').split(',')).map(function (i, e) {
-            if (allowMime[e]) opt.exts.push(e), opt.mimes.push(allowMime[e]);
+    function UploadAdapter(elem, done) {
+        return new (function (elem, done, that) {
+
+            /*! 初始化变量 */
+            that = this;
+            this.option = {elem: $(elem), exts: [], mimes: []};
+            this.option.size = this.option.elem.data('size') || 0;
+            this.option.safe = this.option.elem.data('safe') ? 1 : 0;
+            this.option.hide = this.option.elem.data('hload') ? 1 : 0;
+            this.option.mult = this.option.elem.data('multiple') > 0;
+            this.option.type = this.option.safe ? 'local' : this.option.elem.attr('data-uptype') || '';
+
+            /*! 查找表单元素, 如果没有找到将不会自动写值 */
+            if (!this.option.elem.data('input') && this.option.elem.data('field')) {
+                this.$input = $('input[name="' + this.option.elem.data('field') + '"]:not([type=file])');
+                this.option.elem.data('input', this.$input.size() > 0 ? this.$input.get(0) : null);
+            }
+
+            /*! 文件选择筛选,使用 MIME 规则过滤文件列表 */
+            $((this.option.elem.data('type') || '').split(',')).map(function (i, e) {
+                if (allowMime[e]) that.option.exts.push(e), that.option.mimes.push(allowMime[e]);
+            });
+
+            /*! 初始化上传组件 */
+            this.adapter = new Adapter(this.option, layui.upload.render({
+                url: '{:url("admin/api.upload/file")}', auto: false, elem: elem, accept: 'file', multiple: this.option.mult, exts: this.option.exts.join('|'), acceptMime: this.option.mimes.join(','), choose: function (object) {
+                    object.files = object.pushFile();
+                    that.adapter.event('upload.choose', object.files);
+                    that.adapter.upload(object.files, done), layui.each(object.files, function (index) {
+                        delete object.files[index];
+                    });
+                }
+            }));
+        })(elem, done)
+    }
+
+    // 创建对象
+    UploadAdapter.adapter = window.AdminUploadAdapter = Adapter;
+
+    // 上传文件
+    function Adapter(option, uploader) {
+        this.uploader = uploader, this.config = function (option) {
+            return (this.option = Object.assign({}, this.option || {}, option || {})), this;
+        }, this.init = function (option) {
+            this.uploader && this.uploader.config.elem.next().val('');
+            this.files = {}, this.loader = 0, this.count = {total: 0, error: 0, success: 0};
+            return this.config(option).config({safe: this.option.safe || 0, type: this.option.type || ''});
+        }, this.init(option);
+    }
+
+    // 文件推送
+    Adapter.prototype.upload = function (files, done) {
+        var that = this.init();
+        layui.each(files, function (index, file) {
+            that.count.total++, file.index = index, that.files[index] = file;
+            if (that.option.size && file.size > that.option.size) {
+                that.count.error++, file.xstate = -1, file.xstats = '大小超限';
+                return $.msg.tips('文件大小超出限制!');
+            }
+            if (!that.option.hide) {
+                file.notify = new NotifyExtend(file);
+            }
+        }), layui.each(files, function (index, file) {
+            that.hash(file).then(function (file) {
+                that.event('upload.hash', file).request(file, done);
+            });
         });
+    };
 
-        /*! 初始化上传组件 */
-        opt.uploader = layui.upload.render({
-            url: '{:sysuri("admin/api.upload/file")}', auto: false, elem: element, accept: 'file', multiple: opt.mult, exts: opt.exts.join('|'), acceptMime: opt.mimes.join(','), choose: function (object) {
-                opt.elem.triggerHandler('upload.choose', opt.files = object.pushFile());
-                opt.uploader.config.elem.next().val(''), layui.each(opt.files, function (index, file) {
-                    if (opt.size > 0 && file.size > opt.size) return delete opt.files[index], $.msg.tips('文件大小超出限制!');
-                    opt.load = opt.hide || $.msg.loading('上传进度 <span data-upload-progress>0%</span>');
-                    opt.count.total++, file.index = index, opt.cache[index] = file, delete opt.files[index];
-                    md5file(file).then(function (file) {
-                        opt.elem.triggerHandler('upload.hash', file), jQuery.ajax("{:sysuri('admin/api.upload/state')}", {
-                            data: {key: file.xkey, uptype: opt.type, safe: opt.safe, name: file.name}, method: 'post', success: function (ret) {
-                                file.xurl = ret.data.url, file.xsafe = ret.data.safe;
-                                file.xpath = ret.data.key, file.xtype = ret.data.uptype;
-                                if (parseInt(ret.code) === 404) {
-                                    opt.uploader.config.url = ret.data.server;
-                                    opt.uploader.config.data.key = ret.data.key;
-                                    opt.uploader.config.data.safe = ret.data.safe;
-                                    opt.uploader.config.data.uptype = ret.data.uptype;
-                                    if (ret.data.uptype === 'qiniu') {
-                                        opt.uploader.config.data.token = ret.data.token;
-                                    } else if (ret.data.uptype === 'alioss') {
-                                        opt.uploader.config.data['policy'] = ret.data.policy;
-                                        opt.uploader.config.data['signature'] = ret.data.signature;
-                                        opt.uploader.config.data['OSSAccessKeyId'] = ret.data.OSSAccessKeyId;
-                                        opt.uploader.config.data['success_action_status'] = 200;
-                                        opt.uploader.config.data['Content-Disposition'] = 'inline;filename=' + encodeURIComponent(file.name);
-                                    } else if (ret.data.uptype === 'txcos') {
-                                        opt.uploader.config.data['q-ak'] = ret.data['q-ak'];
-                                        opt.uploader.config.data['policy'] = ret.data['policy'];
-                                        opt.uploader.config.data['q-key-time'] = ret.data['q-key-time'];
-                                        opt.uploader.config.data['q-signature'] = ret.data['q-signature'];
-                                        opt.uploader.config.data['q-sign-algorithm'] = ret.data['q-sign-algorithm'];
-                                        opt.uploader.config.data['success_action_status'] = 200;
-                                        opt.uploader.config.data['Content-Disposition'] = 'inline;filename=' + encodeURIComponent(file.name);
-                                    }
-                                    object.upload(file.index, file);
-                                } else if (parseInt(ret.code) === 200) {
-                                    file.xurl = ret.data.url;
-                                    opt.uploader.config.done({code: 1, url: file.xurl, info: '文件秒传成功!'}, file.index);
-                                } else {
-                                    $.msg.tips(ret.info || ret.error.message || '文件上传出错!');
-                                }
+    // 文件上传
+    Adapter.prototype.request = function (file, done) {
+        var that = this, data = {key: file.xkey, safe: that.option.safe, uptype: that.option.type};
+        data.size = file.size, data.name = file.name, data.hash = file.xmd5;
+        jQuery.ajax("{:url('admin/api.upload/state')}", {
+            data: data, method: 'post', success: function (ret) {
+                file.xurl = ret.data.url, file.xsafe = ret.data.safe, file.xpath = ret.data.key, file.xtype = ret.data.uptype;
+                if (parseInt(ret.code) === 404) {
+                    var uploader = {};
+                    uploader.url = ret.data.server;
+                    uploader.form = new FormData();
+                    uploader.form.append('key', ret.data.key);
+                    uploader.form.append('safe', ret.data.safe);
+                    uploader.form.append('uptype', ret.data.uptype);
+                    if (ret.data.uptype === 'qiniu') {
+                        uploader.form.append('token', ret.data.token);
+                    } else if (ret.data.uptype === 'alioss') {
+                        uploader.form.append('policy', ret.data['policy']);
+                        uploader.form.append('signature', ret.data['signature']);
+                        uploader.form.append('OSSAccessKeyId', ret.data['OSSAccessKeyId']);
+                        uploader.form.append('success_action_status', '200');
+                        uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
+                    } else if (ret.data.uptype === 'txcos') {
+                        uploader.form.append('q-ak', ret.data['q-ak']);
+                        uploader.form.append('policy', ret.data['policy']);
+                        uploader.form.append('q-key-time', ret.data['q-key-time']);
+                        uploader.form.append('q-signature', ret.data['q-signature']);
+                        uploader.form.append('q-sign-algorithm', ret.data['q-sign-algorithm']);
+                        uploader.form.append('success_action_status', '200');
+                        uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
+                    } else if (ret.data.uptype === 'upyun') {
+                        uploader.form.delete('key');
+                        uploader.form.delete('safe');
+                        uploader.form.delete('uptype');
+                        uploader.form.append('save-key', ret.data['key']);
+                        uploader.form.append('policy', ret.data['policy']);
+                        uploader.form.append('authorization', ret.data['authorization']);
+                        uploader.form.append('Content-Disposition', 'inline;filename=' + encodeURIComponent(file.name));
+                    }
+                    uploader.form.append('file', file), jQuery.ajax({
+                        url: uploader.url, data: uploader.form, type: 'post', xhr: function (xhr) {
+                            xhr = new XMLHttpRequest();
+                            return xhr.upload.addEventListener('progress', function (event) {
+                                file.xtotal = event.total, file.xloaded = event.loaded || 0;
+                                that.progress((file.xloaded / file.xtotal * 100).toFixed(2), file)
+                            }), xhr;
+                        }, contentType: false, error: function () {
+                            that.event('upload.error', {file: file}, file, '接口异常');
+                        }, processData: false, success: function (ret) {
+                            // 兼容数据格式
+                            if (typeof ret === 'string' && ret.length > 0) try {
+                                ret = JSON.parse(ret) || ret;
+                            } catch (e) {
+                                console.log(e)
                             }
-                        });
+                            if (typeof ret !== 'object') {
+                                ret = {code: 1, url: file.xurl, info: '上传成功'};
+                            }
+                            /*! 检查单个文件上传返回的结果 */
+                            if (typeof ret === 'object' && ret.code < 1) {
+                                that.event('upload.error', {file: file}, file, ret.info || '上传失败');
+                            } else {
+                                that.done(ret, file.index, file, done, '上传成功');
+                            }
+                        }
                     });
-                });
-            }, progress: function (number) {
-                /*! 文件上传进度处理 */
-                opt.elem.triggerHandler('upload.progress', {number: number, event: arguments[2], file: arguments[3]});
-                if (opt.count.total > 1) {
-                    $('[data-upload-progress]').html(number + '%' + ' ' + (opt.count.uploaded + 1) + '/' + opt.count.total);
+                } else if (parseInt(ret.code) === 200) {
+                    (file.xurl = ret.data.url), that.progress('100.00', file);
+                    that.done({code: 1, url: file.xurl, info: file.xstats}, file.index, file, done, '秒传成功');
                 } else {
-                    $('[data-upload-progress]').html(number + '%');
-                }
-            }, done: function (ret, idx) {
-                // 兼容部分环境不解析 JSON 数据
-                if (typeof ret === 'string' && ret.length > 0) try {
-                    ret = JSON.parse(ret) || ret;
-                } catch (e) {
-                    console.log(e)
-                }
-                /*! 检查单个文件上传返回的结果 */
-                if (ret.code < 1) return $.msg.tips(ret.info || '文件上传失败!');
-                if (typeof opt.cache[idx].xurl !== 'string') return $.msg.tips('无效的文件上传对象!');
-                /*! 单个文件上传成功结果处理 */
-                if (typeof callable === 'function') {
-                    callable.call(opt.elem, opt.cache[idx].xurl, opt.cache['id']);
-                } else if (opt.mult < 1 && opt.elem.data('input')) {
-                    $(opt.elem.data('input')).val(opt.cache[idx].xurl).trigger('change', opt.cache[idx]);
-                }
-                opt.elem.html(opt.elem.data('html')).triggerHandler('upload.done', {file: opt.cache[idx], data: ret});
-                /*! 所有文件上传完成后结果处理 */
-                if (++opt.count.uploaded >= opt.count.total) {
-                    opt.hide || $.msg.close(opt.load);
-                    if (opt.mult > 0 && opt.elem.data('input')) {
-                        var urls = opt.elem.data('input').value || [];
-                        if (typeof urls === 'string') urls = urls.split('|');
-                        for (var i in opt.cache) urls.push(opt.cache[i].xurl);
-                        $(opt.elem.data('input')).val(urls.join('|')).trigger('change', opt.cache);
-                    }
-                    opt.elem.triggerHandler('upload.complete', {file: opt.cache});
-                    (opt.cache = [], opt.files = [], opt.count = {uploaded: 0, total: 0}), opt.uploader.reload();
+                    that.event('upload.error', {file: file}, file, ret.info || ret.error.message || '上传出错!');
                 }
             }
         });
     };
 
-    function md5file(file) {
-        var deferred = jQuery.Deferred();
+    // 上传进度
+    Adapter.prototype.progress = function (number, file) {
+        this.event('upload.progress', {number: number, file: file});
+        if (file.notify) file.notify.setProgress(number);
+    };
+
+    // 上传结果
+    Adapter.prototype.done = function (ret, idx, file, done, message) {
+        /*! 检查单个文件上传返回的结果 */
+        if (ret.code < 1) return $.msg.tips(ret.info || '文件上传失败!');
+        if (typeof file.xurl !== 'string') return $.msg.tips('无效的文件上传对象!');
+        /*! 单个文件上传成功结果处理 */
+        if (typeof done === 'function') {
+            done.call(this.option.elem, file.xurl, this.files['id']);
+        } else if (this.option.mult < 1 && this.option.elem.data('input')) {
+            $(this.option.elem.data('input')).val(file.xurl).trigger('change', file);
+        }
+        // 文件上传成功事件
+        this.event('upload.done', {file: file, data: ret}, file, message);
+        /*! 所有文件上传完成后结果处理 */
+        if (this.count.success + this.count.error >= this.count.total) {
+            this.option.hide || $.msg.close(this.loader);
+            if (this.option.mult > 0 && this.option.elem.data('input')) {
+                var urls = this.option.elem.data('input').value || [];
+                if (typeof urls === 'string') urls = urls.split('|');
+                for (var i in this.files) urls.push(this.files[i].xurl);
+                $(this.option.elem.data('input')).val(urls.join('|')).trigger('change', files);
+            }
+            this.event('upload.complete', {file: this.files}, file).init().uploader && this.uploader.reload();
+        }
+    };
+
+    /*! 触发事件过程 */
+    Adapter.prototype.event = function (name, data, file, message) {
+        if (name === 'upload.error') {
+            this.count.error++, file.xstate = -1, file.xstats = message;
+            if (file.notify) file.notify.setError(message || file.xstats || '');
+        } else if (name === 'upload.done') {
+            this.count.success++, file.xstate = 1, file.xstats = message;
+            if (file.notify) file.notify.setSuccess(message || file.xstats || '')
+        }
+        if (this.option.elem) {
+            this.option.elem.triggerHandler(name, data);
+        }
+        return this;
+    };
+
+    /*! 计算文件 HASH 值 */
+    Adapter.prototype.hash = function (file) {
+        var defer = jQuery.Deferred();
         file.xext = file.name.indexOf('.') > -1 ? file.name.split('.').pop() : 'tmp';
 
         /*! 兼容不能计算文件 HASH 的情况 */
         var IsDate = '{$nameType|default=""}'.indexOf('date') > -1;
         if (!window.FileReader || IsDate) return jQuery.when((function (xmd5, chars) {
             while (xmd5.length < 32) xmd5 += chars.charAt(Math.floor(Math.random() * chars.length));
-            return setFileXdata(file, xmd5, 6), deferred.resolve(file, file.xmd5, file.xkey), deferred;
+            return SetFileXdata(file, xmd5, 6), defer.promise();
         })(layui.util.toDateString(Date.now(), 'yyyyMMddHHmmss-'), '0123456789'));
 
         /*! 读取文件并计算 HASH 值 */
-        var spark = new SparkMD5.ArrayBuffer();
-        var slice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
-        file.chunkIdx = 0, file.chunkSize = 2097152, file.chunkTotal = Math.ceil(this.size / this.chunkSize);
-        return jQuery.when(loadNextChunk(file));
-
-        function setFileXdata(file, xmd5, slice) {
-            file.xmd5 = xmd5, file.xkey = file.xmd5.substr(0, slice || 2) + '/' + file.xmd5.substr(slice || 2, 30) + '.' + file.xext;
-            return delete file.chunkIdx, delete file.chunkSize, delete file.chunkTotal, file;
+        return new LoadNextChunk(file).ReadAsChunk();
+
+        function SetFileXdata(file, xmd5, slice) {
+            file.xmd5 = xmd5, file.xstate = 0, file.xstats = '';
+            file.xkey = file.xmd5.substring(0, slice || 2) + '/' + file.xmd5.substring(slice || 2) + '.' + file.xext;
+            return defer.resolve(file, file.xmd5, file.xkey), file;
         }
 
-        function loadNextChunk(file) {
-            this.reader = new FileReader();
-            this.reader.onload = function (event) {
+        function LoadNextChunk(file) {
+            var that = this, reader = new FileReader(), spark = new SparkMD5.ArrayBuffer();
+            var slice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
+            this.chunkIdx = 0, this.chunkSize = 2097152, this.chunkTotal = Math.ceil(file.size / this.chunkSize);
+            reader.onload = function (event) {
                 spark.append(event.target.result);
-                if (++file.chunkIdx < file.chunkTotal) {
-                    loadNextChunk(file);
-                } else {
-                    setFileXdata(file, spark.end());
-                    deferred.resolve(file, file.xmd5, file.xkey);
-                }
-            };
-            this.reader.onerror = function () {
-                deferred.reject();
+                ++that.chunkIdx < that.chunkTotal ? that.ReadAsChunk() : SetFileXdata(file, spark.end());
+            }, reader.onerror = function () {
+                defer.reject();
+            }, this.ReadAsChunk = function () {
+                this.start = that.chunkIdx * that.chunkSize;
+                this.loaded = this.start + that.chunkSize >= file.size ? file.size : this.start + that.chunkSize;
+                reader.readAsArrayBuffer(slice.call(file, this.start, this.loaded));
+                defer.notify(file, (this.loaded / file.size * 100).toFixed(2));
+                return defer.promise();
             };
-            this.start = file.chunkIdx * file.chunkSize;
-            this.loaded = (this.start + file.chunkSize >= file.size) ? file.size : this.start + file.chunkSize;
-            this.reader.readAsArrayBuffer(slice.call(file, this.start, this.loaded));
-            return deferred.notify(file, (this.loaded / file.size * 100).toFixed(2)), deferred;
         }
+    };
+
+    return UploadAdapter;
+
+    /*! Base64 内容转 File 对象 */
+    function Base64ToFile(base64, filename) {
+        var arr = base64.split(',');
+        var mime = arr[0].match(/:(.*?);/)[1], suffix = mime.split('/')[1];
+        var bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
+        while (n--) u8arr[n] = bstr.charCodeAt(n);
+        return new File([u8arr], filename + '.' + suffix, {type: mime});
+    }
+
+    /*! File 对象转 Base64 内容 */
+    function FileToBase64(file) {
+        var defer = jQuery.Deferred(), reader = new FileReader();
+        reader.onload = function () {
+            defer.resolve(this.result);
+        };
+        return reader.readAsDataURL(file), defer.promise();
+    }
+
+    /*! 图片压缩处理 */
+    function ImageToThumb(url, quality) {
+        var deferred = jQuery.Deferred();
+        var maxWidth = 500, maxHeight = 400, image = new Image();
+        image.src = url, image.onload = function () {
+            var canvas = document.createElement('canvas');
+            var context = canvas.getContext('2d');
+            // 原始尺寸
+            var originWidth = this.width, originHeight = this.height;
+            // 目标尺寸
+            var targetWidth = originWidth, targetHeight = originHeight;
+            // 图片尺寸超过最大值的限制
+            if (originWidth > maxWidth || originHeight > maxHeight) {
+                if (originWidth / originHeight > maxWidth / maxHeight) {
+                    targetWidth = maxWidth;
+                    targetHeight = Math.round(maxWidth * (originHeight / originWidth));
+                } else {
+                    targetHeight = maxHeight;
+                    targetWidth = Math.round(maxHeight * (originWidth / originHeight));
+                }
+            }
+            canvas.width = targetWidth, canvas.height = targetHeight;
+            context.clearRect(0, 0, targetWidth, targetHeight);
+            context.drawImage(this, 0, 0, targetWidth, targetHeight);
+            deferred.resolve(canvas.toDataURL('image/jpeg', quality || 0.92));
+        };
+    }
+
+    /*! 上传状态提示扩展插件 */
+    function NotifyExtend(file) {
+        var that = this;
+        this.notify = Notify.notify({width: 260, title: file.name, showProgress: true, description: '上传进度 <span data-upload-progress>0%</span>', type: 'default', position: 'top-right', closeTimeout: 0});
+        this.$elem = $(this.notify.notification.nodes);
+        this.$elem.find('.growl-notification__progress').addClass('is-visible');
+        this.$elem.find('.growl-notification__progress-bar').addClass('transition');
+        this.setProgress = function (number) {
+            this.$elem.find('[data-upload-progress]').html(number + '%');
+            this.$elem.find('.growl-notification__progress-bar').css({width: number + '%'});
+            return this;
+        }, this.setError = function (message) {
+            this.$elem.find('.growl-notification__desc').html(message || '文件上传失败!');
+            this.$elem.removeClass('growl-notification--default').addClass('growl-notification--error')
+            return this.close();
+        }, this.setSuccess = function (message) {
+            this.setProgress('100.00');
+            this.$elem.find('.growl-notification__desc').html(message || '文件上传成功!');
+            this.$elem.removeClass('growl-notification--default').addClass('growl-notification--success');
+            return this.close();
+        }, this.close = function (timeout) {
+            return setTimeout(function () {
+                that.notify.close();
+            }, timeout || 2000), this;
+        };
     }
 });

+ 29 - 1
app/admin/view/config/index.html

@@ -32,15 +32,42 @@
         </div>
     </div>
 </div>
+
+<div class="layui-card padding-20 shadow">
+    <div class="layui-card-header notselect">
+        <b>富编辑器</b><span class="color-desc font-s12 padding-left-5">Rich Text Editor</span>
+    </div>
+    <div class="layui-card-body layui-clear">
+        <div class="layui-btn-group shadow-mini nowrap">
+            {if !in_array(sysconf('base.editor'),['ckeditor4','ckeditor5','auto'])}{php}sysconf('base.editor','ckeditor4');{/php}{/if}
+            {foreach ['ckeditor4'=>'CKEditor4','ckeditor5'=>'CKEditor5','auto'=>'自适应模式'] as $k => $v}{if sysconf('base.editor') eq $k}
+            {if auth('storage')}<a data-title="配置{$v}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
+            {else}
+            {if auth('storage')}<a data-title="配置{$v}" data-action="{:url('admin/api.runtime/editor')}" data-value="editor#{$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
+            {/if}{/foreach}
+        </div>
+        <div class="margin-top-20 nowrap full-width pull-left">
+            <p><b>CKEditor4</b>:旧版本编辑器,对浏览器兼容较好,但内容编辑体验稍有不足。</p>
+            <p><b>CKEditor5</b>:新版本编辑器,只支持新特性浏览器,对内容编辑体验较好,推荐使用。</p>
+            <p><b>自适应模式</b>:优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。</p>
+        </div>
+    </div>
+</div>
 <!--{/notempty}-->
 
 <div class="layui-card padding-20 shadow">
     <div class="layui-card-header notselect">
         <b>存储引擎</b><span class="color-desc font-s12 padding-left-5">Storage Engine</span>
     </div>
+    <!-- 初始化存储配置 -->
+    {if !sysconf('storage.type')}{php}sysconf('storage.type','local');{/php}{/if}
+    {if !sysconf('storage.link_type')}{php}sysconf('storage.link_type','none');{/php}{/if}
+    {if !sysconf('storage.name_type')}{php}sysconf('storage.name_type','xmd5');{/php}{/if}
+    {if !sysconf('storage.allow_exts')}{php}sysconf('storage.allow_exts','doc,gif,ico,jpg,mp3,mp4,p12,pem,png,rar,xls,xlsx');{/php}{/if}
+    {if !sysconf('storage.local_http_protocol')}{php}sysconf('storage.local_http_protocol','http');{/php}{/if}
     <div class="layui-card-body layui-clear">
         <div class="layui-btn-group shadow-mini nowrap">
-            {foreach ['local' => '本地服务器存储','qiniu' => '七牛云对象存储','alioss' => '阿里云OSS存储','txcos' => '腾讯云COS存储'] as $k => $v} {if sysconf('storage.type') eq $k}
+            {foreach ['local'=>'本地服务器存储','qiniu'=>'七牛云对象存储','upyun'=>'又拍云USS存储','alioss'=>'阿里云OSS存储','txcos'=>'腾讯云COS存储'] as $k => $v}{if sysconf('storage.type') eq $k}
             {if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
             {else}
             {if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
@@ -49,6 +76,7 @@
         <div class="margin-top-20 nowrap full-width pull-left">
             <p><b>本地服务器存储</b>:文件直接上传到本地服务器的 `static/upload` 目录,不支持大文件上传,占用服务器磁盘空间,访问时消耗服务器带宽流量。</p>
             <p><b>七牛云对象存储</b>:文件直接上传到七牛云存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。</p>
+            <p><b>又拍云USS存储</b>:文件直接上传到又拍云 USS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。</p>
             <p><b>阿里云OSS存储</b>:文件直接上传到阿里云 OSS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。</p>
             <p><b>腾讯云COS存储</b>:文件直接上传到腾讯云 COS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。</p>
         </div>

+ 5 - 6
app/admin/view/config/storage-0.html

@@ -1,10 +1,9 @@
 <div class="layui-form-item">
     <label class="layui-form-label label-required">
-        <span class="color-green font-w7">命名方式</span><br><span class="nowrap color-desc">NameType</span>
+        <b class="color-green">命名方式</b><br><span class="nowrap color-desc">NameType</span>
     </label>
     <div class="layui-input-block">
-        {if !sysconf('storage.name_type')}{php}sysconf('storage.name_type','xmd5');{/php}{/if}
-        {foreach ['xmd5'=>'文件哈希值','date'=>'日期+随机'] as $k=>$v}
+        {foreach ['xmd5'=>'文件哈希值 ( 支持秒传 )','date'=>'日期+随机 ( 普通上传 )'] as $k=>$v}
         <label class="think-radio notselect">
             {if sysconf('storage.name_type') eq $k}
             <input checked type="radio" name="storage.name_type" value="{$k}" lay-ignore> {$v}
@@ -16,12 +15,12 @@
         <p class="help-block">类型为“文件哈希”时可以实现文件秒传功能,同一个文件只需上传一次节省存储空间,推荐使用。</p>
     </div>
 </div>
+
 <div class="layui-form-item">
     <label class="layui-form-label label-required">
-        <span class="color-green font-w7">链接类型</span><br><span class="nowrap color-desc">LinkType</span>
+        <b class="color-green">链接类型</b><br><span class="nowrap color-desc">LinkType</span>
     </label>
     <div class="layui-input-block">
-        {if !sysconf('storage.link_type')}{php}sysconf('storage.link_type','none');{/php}{/if}
         {foreach ['none'=>'简洁链接','full'=>'完整链接','none+compress'=>'简洁并压缩图片','full+compress'=>'完整并压缩图片'] as $k=>$v}
         <label class="think-radio notselect">
             {if sysconf('storage.link_type') eq $k}
@@ -37,7 +36,7 @@
 
 <div class="layui-form-item">
     <label class="layui-form-label" for="storage.allow_exts">
-        <span class="color-green font-w7">允许类型</span><br><span class="nowrap color-desc">AllowExts</span>
+        <b class="color-green">允许类型</b><br><span class="nowrap color-desc">AllowExts</span>
     </label>
     <div class="layui-input-block">
         <input id="storage.allow_exts" type="text" name="storage.allow_exts" required value="{:sysconf('storage.allow_exts')}" placeholder="请输入系统文件上传后缀" class="layui-input">

+ 7 - 7
app/admin/view/config/storage-alioss.html

@@ -1,7 +1,7 @@
 <form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
     <div class="layui-card-body padding-top-20">
 
-        <div class="color-text margin-left-40 margin-bottom-20 layui-code text-center layui-bg-gray" style="border-left-width:1px">
+        <div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
             <p class="margin-bottom-5 font-w7">文件将上传到阿里云 OSS 存储,需要配置 OSS 公开访问及跨域策略</p>
             <p>需要配置跨域访问 CORS 规则,设置:来源 Origin 为 *,允许 Methods 为 POST,允许 Headers 为 *</p>
         </div>
@@ -10,7 +10,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label label-required">
-                <span class="color-green font-w7">访问协议</span><br><span class="nowrap color-desc">Protocol</span>
+                <b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
             </label>
             <div class="layui-input-block">
                 {if !sysconf('storage.alioss_http_protocol')}{php}sysconf('storage.alioss_http_protocol','http');{/php}{/if}
@@ -29,7 +29,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label">
-                <span class="color-green font-w7">存储区域</span><br><span class="nowrap color-desc label-required">Region</span>
+                <b class="color-green">存储区域</b><br><span class="nowrap color-desc label-required">Region</span>
             </label>
             <div class="layui-input-block">
                 <select class="layui-select" name="storage.alioss_point" lay-search>
@@ -46,7 +46,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.alioss_bucket">
-                <span class="color-green font-w7">空间名称</span><br><span class="nowrap color-desc">Bucket</span>
+                <b class="color-green">空间名称</b><br><span class="nowrap color-desc">Bucket</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.alioss_bucket" type="text" name="storage.alioss_bucket" required value="{:sysconf('storage.alioss_bucket')}" placeholder="请输入阿里云OSS存储 Bucket (空间名称)" class="layui-input">
@@ -56,7 +56,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.alioss_http_domain">
-                <span class="color-green font-w7">访问域名</span><br><span class="nowrap color-desc">Domain</span>
+                <b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.alioss_http_domain" type="text" name="storage.alioss_http_domain" required value="{:sysconf('storage.alioss_http_domain')}" placeholder="请输入阿里云OSS存储 Domain (访问域名)" class="layui-input">
@@ -66,7 +66,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.alioss_access_key">
-                <span class="color-green font-w7">访问密钥</span><br><span class="nowrap color-desc">AccessKey</span>
+                <b class="color-green">访问密钥</b><br><span class="nowrap color-desc">AccessKey</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.alioss_access_key" type="text" name="storage.alioss_access_key" required value="{:sysconf('storage.alioss_access_key')}" placeholder="请输入阿里云OSS存储 AccessKey (访问密钥)" class="layui-input">
@@ -76,7 +76,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.alioss_secret_key">
-                <span class="color-green font-w7">安全密钥</span><br><span class="nowrap color-desc">SecretKey</span>
+                <b class="color-green">安全密钥</b><br><span class="nowrap color-desc">SecretKey</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.alioss_secret_key" type="text" name="storage.alioss_secret_key" required value="{:sysconf('storage.alioss_secret_key')}" maxlength="43" placeholder="请输入阿里云OSS存储 SecretKey (安全密钥)" class="layui-input">

+ 3 - 3
app/admin/view/config/storage-local.html

@@ -1,7 +1,7 @@
 <form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
     <div class="layui-card-body padding-top-20">
 
-        <div class="color-text margin-left-40 margin-bottom-20 layui-code text-center layui-bg-gray" style="border-left-width:1px">
+        <div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
             <p class="margin-bottom-5 font-w7">文件将存储在本地服务器,默认保存在 public/upload 目录,文件以 HASH 命名。</p>
             <p>文件存储的目录需要有读写权限,有足够的存储空间。<span class="color-red">特别注意,本地存储暂不支持图片压缩!</span></p>
         </div>
@@ -10,7 +10,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label label-required">
-                <span class="color-green font-w7">访问协议</span><br><span class="nowrap color-desc">Protocol</span>
+                <b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
             </label>
             <div class="layui-input-block">
                 {if !sysconf('storage.local_http_protocol')}{php}sysconf('storage.local_http_protocol','http');{/php}{/if}
@@ -29,7 +29,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.local_http_domain">
-                <span class="color-green font-w7">访问域名</span><br><span class="nowrap color-desc">Domain</span>
+                <b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.local_http_domain" type="text" name="storage.local_http_domain" value="{:sysconf('storage.local_http_domain')}" placeholder="请输入上传后的访问域名 (非必填项)" class="layui-input">

+ 7 - 7
app/admin/view/config/storage-qiniu.html

@@ -1,7 +1,7 @@
 <form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
     <div class="layui-card-body padding-top-20">
 
-        <div class="color-text margin-left-40 margin-bottom-20 layui-code text-center layui-bg-gray" style="border-left-width:1px">
+        <div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
             <p class="margin-bottom-5 font-w7">文件将上传到七牛云存储,对象存储需要配置为公开访问的 Bucket 空间</p>
             完成实名认证后可获得 10G 免费存储空间哦!<a target="_blank" href="https://portal.qiniu.com/signup?code=1hefnmobzees2">我要免费申请</a>
         </div>
@@ -10,7 +10,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label label-required">
-                <span class="color-green font-w7">访问协议</span><br><span class="nowrap color-desc">Protocol</span>
+                <b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
             </label>
             <div class="layui-input-block">
                 {if !sysconf('storage.qiniu_http_protocol')}{php}sysconf('storage.qiniu_http_protocol','http');{/php}{/if}
@@ -29,7 +29,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label">
-                <span class="color-green font-w7">存储区域</span><br><span class="nowrap color-desc label-required">Region</span>
+                <b class="color-green">存储区域</b><br><span class="nowrap color-desc label-required">Region</span>
             </label>
             <div class="layui-input-block">
                 {foreach ['华东','华北','华南','北美'] as $area}
@@ -47,7 +47,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.qiniu_bucket">
-                <span class="color-green font-w7">空间名称</span><br><span class="nowrap color-desc">Bucket</span>
+                <b class="color-green">空间名称</b><br><span class="nowrap color-desc">Bucket</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.qiniu_bucket" type="text" name="storage.qiniu_bucket" required value="{:sysconf('storage.qiniu_bucket')}" placeholder="请输入七牛云存储 Bucket (空间名称)" class="layui-input">
@@ -57,7 +57,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.qiniu_http_domain">
-                <span class="color-green font-w7">访问域名</span><br><span class="nowrap color-desc">Domain</span>
+                <b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.qiniu_http_domain" type="text" name="storage.qiniu_http_domain" required value="{:sysconf('storage.qiniu_http_domain')}" placeholder="请输入七牛云存储 Domain (访问域名)" class="layui-input">
@@ -67,7 +67,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.qiniu_access_key">
-                <span class="color-green font-w7">访问密钥</span><br><span class="nowrap color-desc">AccessKey</span>
+                <b class="color-green">访问密钥</b><br><span class="nowrap color-desc">AccessKey</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.qiniu_access_key" type="text" name="storage.qiniu_access_key" required value="{:sysconf('storage.qiniu_access_key')}" placeholder="请输入七牛云授权 AccessKey (访问密钥)" class="layui-input">
@@ -77,7 +77,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.qiniu_secret_key">
-                <span class="color-green font-w7">安全密钥</span><br><span class="nowrap color-desc">SecretKey</span>
+                <b class="color-green">安全密钥</b><br><span class="nowrap color-desc">SecretKey</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.qiniu_secret_key" type="text" name="storage.qiniu_secret_key" required value="{:sysconf('storage.qiniu_secret_key')}" maxlength="43" placeholder="请输入七牛云授权 SecretKey (安全密钥)" class="layui-input">

+ 8 - 8
app/admin/view/config/storage-txcos.html

@@ -1,16 +1,16 @@
 <form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
     <div class="layui-card-body padding-top-20">
 
-        <div class="color-text margin-left-40 margin-bottom-20 layui-code text-center layui-bg-gray" style="border-left-width:1px">
+        <div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
             <p class="margin-bottom-5 font-w7">文件将上传到 <a target="_blank" href="https://curl.qcloud.com/4t0Mbw2K">腾讯云</a> COS 存储,需要配置 COS 公有读私有写访问权限及跨域策略</p>
-            <p>需配置跨域访问 CORS 规则,设置来源 Origin 为 *,允许 Methods 为 POST,允许 Headers 为 *</p>
+            <p>需配置跨域访问 CORS 规则,设置来源 Origin 为 *,允许 Methods 为 POST,允许 Headers 为 *</p>
         </div>
 
         {include file='config/storage-0'}
 
         <div class="layui-form-item">
             <label class="layui-form-label label-required">
-                <span class="color-green font-w7">访问协议</span><br><span class="nowrap color-desc">Protocol</span>
+                <b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
             </label>
             <div class="layui-input-block">
                 {if !sysconf('storage.txcos_http_protocol')}{php}sysconf('storage.txcos_http_protocol','http');{/php}{/if}
@@ -29,7 +29,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label">
-                <span class="color-green font-w7">存储区域</span><br><span class="nowrap color-desc label-required">Region</span>
+                <b class="color-green">存储区域</b><br><span class="nowrap color-desc label-required">Region</span>
             </label>
             <div class="layui-input-block">
                 <select class="layui-select" name="storage.txcos_point" lay-search>
@@ -46,7 +46,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.txcos_bucket">
-                <span class="color-green font-w7">空间名称</span><br><span class="nowrap color-desc">Bucket</span>
+                <b class="color-green">空间名称</b><br><span class="nowrap color-desc">Bucket</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.txcos_bucket" type="text" name="storage.txcos_bucket" required value="{:sysconf('storage.txcos_bucket')}" placeholder="请输入腾讯云COS存储 Bucket" class="layui-input">
@@ -56,7 +56,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.txcos_http_domain">
-                <span class="color-green font-w7">访问域名</span><br><span class="nowrap color-desc">Domain</span>
+                <b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.txcos_http_domain" type="text" name="storage.txcos_http_domain" required value="{:sysconf('storage.txcos_http_domain')}" placeholder="请输入腾讯云COS存储 Domain" class="layui-input">
@@ -66,7 +66,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.txcos_access_key">
-                <span class="color-green font-w7">访问密钥</span><br><span class="nowrap color-desc">AccessKey</span>
+                <b class="color-green">访问密钥</b><br><span class="nowrap color-desc">AccessKey</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.txcos_access_key" type="text" name="storage.txcos_access_key" required value="{:sysconf('storage.txcos_access_key')}" placeholder="请输入腾讯云COS存储 AccessKey" class="layui-input">
@@ -76,7 +76,7 @@
 
         <div class="layui-form-item">
             <label class="layui-form-label" for="storage.txcos_secret_key">
-                <span class="color-green font-w7">安全密钥</span><br><span class="nowrap color-desc">SecretKey</span>
+                <b class="color-green">安全密钥</b><br><span class="nowrap color-desc">SecretKey</span>
             </label>
             <div class="layui-input-block">
                 <input id="storage.txcos_secret_key" type="text" name="storage.txcos_secret_key" required value="{:sysconf('storage.txcos_secret_key')}" maxlength="43" placeholder="请输入腾讯云COS存储 SecretKey" class="layui-input">

+ 79 - 0
app/admin/view/config/storage-upyun.html

@@ -0,0 +1,79 @@
+<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
+    <div class="layui-card-body padding-top-20">
+
+        <div class="color-text layui-code text-center layui-bg-gray" style="border-left-width:1px;margin:0 0 15px 40px">
+            <p class="margin-bottom-5 font-w7">文件将上传到 <a target="_blank" href="https://console.upyun.com/register/?invite=PN1cRmjRb">又拍云</a> USS 存储,需要配置 USS 公开访问及跨域策略</p>
+            <p>需配置跨域访问 CORS 规则,设置来源 Origin 为 *,允许 Methods 为 POST,允许 Headers 为 *</p>
+        </div>
+
+        {include file='config/storage-0'}
+
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">
+                <b class="color-green">访问协议</b><br><span class="nowrap color-desc">Protocol</span>
+            </label>
+            <div class="layui-input-block">
+                {if !sysconf('storage.upyun_http_protocol')}{php}sysconf('storage.upyun_http_protocol','http');{/php}{/if}
+                {foreach ['http'=>'HTTP','https'=>'HTTPS','auto'=>"AUTO"] as $protocol=>$remark}
+                <label class="think-radio">
+                    {if sysconf('storage.upyun_http_protocol') eq $protocol}
+                    <input checked type="radio" name="storage.upyun_http_protocol" value="{$protocol}" lay-ignore> {$remark}
+                    {else}
+                    <input type="radio" name="storage.upyun_http_protocol" value="{$protocol}" lay-ignore> {$remark}
+                    {/if}
+                </label>
+                {/foreach}
+                <p class="help-block">又拍云存储访问协议,其中 HTTPS 需要配置证书才能使用(AUTO 为相对协议)</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label" for="storage.upyun_bucket">
+                <b class="color-green">空间名称</b><br><span class="nowrap color-desc">Bucket</span>
+            </label>
+            <div class="layui-input-block">
+                <input id="storage.upyun_bucket" name="storage.upyun_bucket" required value="{:sysconf('storage.upyun_bucket')}" placeholder="请输入又拍云存储 Bucket (空间名称)" class="layui-input">
+                <p class="help-block">填写又拍云存储空间名称,如:think-admin-USS(需要是全区唯一的值,不存在时会自动创建)</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label" for="storage.upyun_http_domain">
+                <b class="color-green">访问域名</b><br><span class="nowrap color-desc">Domain</span>
+            </label>
+            <div class="layui-input-block">
+                <input id="storage.upyun_http_domain" name="storage.upyun_http_domain" required value="{:sysconf('storage.upyun_http_domain')}" placeholder="请输入又拍云存储 Domain (访问域名)" class="layui-input">
+                <p class="help-block">填写又拍云存储外部访问域名,如:static.thinkadmin.top</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label" for="storage.upyun_access_key">
+                <b class="color-green">操作账号</b><br><span class="nowrap color-desc">Username</span>
+            </label>
+            <div class="layui-input-block">
+                <input id="storage.upyun_access_key" name="storage.upyun_access_key" required value="{:sysconf('storage.upyun_access_key')}" maxlength="100" placeholder="请输入又拍云存储 Username (操作员账号)" class="layui-input">
+                <p class="help-block">可以在 [ 账户管理 > 操作员 ] 设置操作员账号并将空间给予授权。</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label" for="storage.upyun_secret_key">
+                <b class="color-green">操作密码</b><br><span class="nowrap color-desc">Password</span>
+            </label>
+            <div class="layui-input-block">
+                <input id="storage.upyun_secret_key" name="storage.upyun_secret_key" required value="{:sysconf('storage.upyun_secret_key')}" maxlength="100" placeholder="请输入又拍云存储 Password (操作员密码)" class="layui-input">
+                <p class="help-block">可以在 [ 账户管理 > 操作员 ] 设置操作员密码并将空间给予授权</p>
+            </div>
+        </div>
+
+        <div class="hr-line-dashed margin-left-40"></div>
+        <input type="hidden" name="storage.type" value="upyun">
+
+        <div class="layui-form-item text-center padding-left-40">
+            <button class="layui-btn" type="submit">保存配置</button>
+            <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
+        </div>
+
+    </div>
+</form>

+ 4 - 1
app/admin/view/index/index.html

@@ -15,7 +15,10 @@
     <link rel="stylesheet" href="__ROOT__/static/theme/css/console.css?at={:date('md')}">
     <link rel="stylesheet" href="__ROOT__/static/extra/style.css?at={:date('md')}">
     {block name="style"}{/block}
-    <script>window.tapiRoot = '{:sysuri("admin/index/index",[],false)}'</script>
+    <script>
+        window.taEditor = '{:sysconf("base.editor")?:"ckeditor4"}';
+        window.tapiRoot = '{:sysuri("admin/index/index",[],false)}'
+    </script>
     <script src="__ROOT__/static/plugs/jquery/pace.min.js"></script>
 </head>
 

+ 1 - 1
app/admin/view/oplog/index.html

@@ -29,7 +29,7 @@
                 {field: 'username', title: '操作账号', minWidth: 100, sort: true, align: 'center'},
                 {field: 'node', title: '操作节点', minWidth: 120},
                 {field: 'action', title: '操作行为', minWidth: 120},
-                {field: 'content', title: '操作描述', minWidth: 120},
+                {field: 'content', title: '操作描述', minWidth: 150},
                 {field: 'geoip', title: '访问地址', minWidth: 100},
                 {field: 'geoisp', title: '网络服务商', minWidth: 100},
                 {field: 'create_at', title: '操作时间', minWidth: 170, align: 'center', sort: true},

+ 2 - 4
app/data/view/base/pager/form.html

@@ -11,9 +11,7 @@
 
         <div class="layui-form-item label-required-prev">
             <span class="help-label"><b>页面内容</b>Page Content</span>
-            <label class="relative block" style="height:606px">
-                <textarea class="layui-hide" name="content">{$data.content|default=$base.content}</textarea>
-            </label>
+            <textarea class="layui-hide" name="content">{$data.content|default=$base.content}</textarea>
         </div>
 
         <div class="hr-line-dashed"></div>
@@ -29,7 +27,7 @@
 
 <script>
     require(['ckeditor'], function () {
-        window.createEditor('[name=content]', {height: 533});
+        window.createEditor('[name=content]', {height: 565});
     });
 </script>
 {/block}

+ 2 - 2
app/data/view/base/upgrade/index.html

@@ -25,7 +25,7 @@
             even: true, height: 'full',
             sort: {field: 'number', type: 'asc'},
             cols: [[
-                {field: 'number', title: '序号', align: "center", minWidth: 80, sort: true},
+                {field: 'number', title: '序号', align: "center", width: 80, sort: true},
                 {field: 'name', title: '等级名称', align: 'center', minWidth: 100},
                 {
                     field: 'upgrade_team', title: '团队计数', align: 'center', width: 80, templet: function (d) {
@@ -70,7 +70,7 @@
                     }
                 },
                 {
-                    field: 'rebate_rule', title: '奖利规则', align: 'center', minWidth: 100, templet: function (d) {
+                    field: 'rebate_rule', title: '奖利规则', align: 'left', minWidth: 100, templet: function (d) {
                         return (d.html = ''), layui.each(d.rebate_rule || {}, function (k, rule) {
                             d.html += laytpl('<span class="layui-badge layui-bg-gray">{{d.v}}</span>').render({v: rule});
                         }), d.html || '-';

+ 2 - 2
app/data/view/news/item/form.html

@@ -49,9 +49,9 @@
 </form>
 
 <script>
+    $('input[name="cover"]').uploadOneImage();
     require(['ckeditor'], function () {
-        $('input[name="cover"]').uploadOneImage();
-        window.createEditor('[name=content]', {height: 350});
+        window.createEditor('[name=content]', {height: 350})
     });
 </script>
 {/block}

+ 1 - 1
app/data/view/shop/goods/form.html

@@ -333,7 +333,7 @@
 
         function getRand(length, prefix) {
             return (function (time, code) {
-                code += parseInt(time.substr(0, 1)) + parseInt(time.substr(1, 1)) + time.substr(2, 8);
+                code += parseInt(time.substring(0, 1)) + parseInt(time.substring(1, 2)) + time.substring(2);
                 while (code.length < length) code += Math.round(Math.random() * 10);
                 return code;
             })(Date.now().toString(), prefix || '' + '')

+ 3 - 0
app/service.php

@@ -0,0 +1,3 @@
+<?php
+include_once __DIR__ . '/../extend/think/admin/common.php';
+return [\think\admin\Library::class];

+ 9 - 3
app/wechat/view/news/form.html

@@ -54,7 +54,7 @@
                 </div>
                 <div class="layui-form-item label-required-prev">
                     <span class="color-green">图文文章内容</span>
-                    <textarea ng-model="item.content" name='content'></textarea>
+                    <textarea class="layui-hide" ng-model="item.content" name='content'></textarea>
                 </div>
                 <label class="layui-form-item relative block">
                     <span class="help-block">摘要选填,如果不填写会默认抓取正文前54个字</span>
@@ -113,8 +113,14 @@
                 setTimeout(function () {
                     if (editor) editor.destroy();
                     editor = window.createEditor('[name="content"]');
-                    editor.setData($rootScope.item.content);
-                    $vali.checkAllInput();
+                    if (typeof editor !== 'undefined') {
+                        editor.setData($rootScope.item.content);
+                        $vali.checkAllInput();
+                    } else $('[name="content"]').on('editor.init', function (event, myEditor) {
+                        myEditor.setData($rootScope.item.content);
+                        editor = myEditor;
+                        $vali.checkAllInput();
+                    });
                 }, 100);
             }
 

+ 25 - 30
public/static/admin.js

@@ -50,9 +50,7 @@ window.jQuery = window.$ = window.jQuery || window.$ || layui.$;
 
 /*! 配置 require 参数  */
 require.config({
-    baseUrl: baseRoot, waitSeconds: 60,
-    map: {'*': {css: baseRoot + 'plugs/require/css.js'}},
-    paths: {
+    baseUrl: baseRoot, waitSeconds: 60, map: {'*': {css: baseRoot + 'plugs/require/css.js'}}, paths: {
         'vue': ['plugs/vue/vue.min'],
         'md5': ['plugs/jquery/md5.min'],
         'json': ['plugs/jquery/json.min'],
@@ -60,10 +58,12 @@ require.config({
         'excel': ['plugs/jquery/excel.xlsx'],
         'base64': ['plugs/jquery/base64.min'],
         'upload': [tapiRoot + '/api.upload/index?'],
+        'notify': ['plugs/notify/notify.min'],
         'angular': ['plugs/angular/angular.min'],
         'cropper': ['plugs/cropper/cropper.min'],
         'echarts': ['plugs/echarts/echarts.min'],
-        'ckeditor': ['plugs/ckeditor/ckeditor'],
+        'ckeditor4': ['plugs/ckeditor4/ckeditor'],
+        'ckeditor5': ['plugs/ckeditor5/ckeditor'],
         'websocket': ['plugs/socket/websocket'],
         'pcasunzips': ['plugs/jquery/pcasunzips'],
         'sortablejs': ['plugs/sortable/sortable.min'],
@@ -72,11 +72,12 @@ require.config({
         'jquery.masonry': ['plugs/jquery/masonry.min'],
         'jquery.cropper': ['plugs/cropper/cropper.min'],
         'jquery.autocompleter': ['plugs/jquery/autocompleter.min'],
-    },
-    shim: {
+    }, shim: {
         'excel': {deps: [baseRoot + 'plugs/layui_exts/excel.js']},
-        'websocket': {deps: [baseRoot + 'plugs/socket/swfobject.min.js']},
+        'notify': {deps: ['css!' + baseRoot + 'plugs/notify/light.css']},
         'cropper': {deps: ['css!' + baseRoot + 'plugs/cropper/cropper.min.css']},
+        'websocket': {deps: [baseRoot + 'plugs/socket/swfobject.min.js']},
+        'ckeditor5': {deps: ['jquery', 'upload', 'css!' + baseRoot + 'plugs/ckeditor5/ckeditor.css']},
         'vue.sortable': {deps: ['vue', 'sortablejs']},
         'jquery.ztree': {deps: ['jquery', 'css!' + baseRoot + 'plugs/ztree/zTreeStyle/zTreeStyle.css']},
         'jquery.autocompleter': {deps: ['jquery', 'css!' + baseRoot + 'plugs/jquery/autocompleter.css']},
@@ -88,6 +89,14 @@ define('jquery', [], function () {
     return layui.$;
 });
 
+/*! 注册 ckeditor 组件 */
+define('ckeditor', (function (type) {
+    if (/^ckeditor[45]$/.test(type)) return [type];
+    return [Object.fromEntries ? 'ckeditor5' : 'ckeditor4'];
+})(window.taEditor || 'ckeditor4'), function (ckeditor) {
+    return ckeditor;
+});
+
 $(function () {
     window.$body = $('body');
 
@@ -240,8 +249,7 @@ $(function () {
             $.vali.listen($dom = $dom || $(this.selecter)), $body.trigger('reInit', $dom);
             return $dom.find('[required]').map(function () {
                 this.$parent = $(this).parent();
-                if (this.$parent.is('label')) this.$parent.addClass('label-required-prev');
-                else this.$parent.prevAll('label.layui-form-label').addClass('label-required-next');
+                if (this.$parent.is('label')) this.$parent.addClass('label-required-prev'); else this.$parent.prevAll('label.layui-form-label').addClass('label-required-next');
             }), $dom.find('[data-lazy-src]:not([data-lazy-loaded])').map(function () {
                 if (this.dataset.lazyLoaded === 'true') return; else this.dataset.lazyLoaded = 'true';
                 if (this.nodeName === 'IMG') this.src = this.dataset.lazySrc; else this.style.backgroundImage = 'url(' + this.dataset.lazySrc + ')';
@@ -388,16 +396,14 @@ $(function () {
                 (layui.data('AdminMenuType')['mini'] || $body.width() < 1000) ? layout.addClass(mclass) : layout.removeClass(mclass);
             }).trigger('resize').on('hashchange', function () {
                 if (/^#(https?:)?(\/\/|\\\\)/.test(location.hash)) return $.msg.tips('禁止访问外部链接!');
-                if (location.hash.length < 1) return $body.find('[data-menu-node]:first').trigger('click');
-                else return that.href(location.hash);
+                if (location.hash.length < 1) return $body.find('[data-menu-node]:first').trigger('click'); else return that.href(location.hash);
             }).trigger('hashchange');
         };
         /*! 同步二级菜单展示状态 */
         this.sync = function (mode) {
             $('[data-submenu-layout]').map(function () {
                 var node = this.dataset.submenuLayout;
-                if (mode === 1) layui.data('AdminMenuState', {key: node, value: $(this).hasClass('layui-nav-itemed') ? 2 : 1});
-                else if (mode === 2) (layui.data('AdminMenuState')[node] || 2) === 2 && $(this).addClass('layui-nav-itemed');
+                if (mode === 1) layui.data('AdminMenuState', {key: node, value: $(this).hasClass('layui-nav-itemed') ? 2 : 1}); else if (mode === 2) (layui.data('AdminMenuState')[node] || 2) === 2 && $(this).addClass('layui-nav-itemed');
             });
         };
         /*! 页面 LOCATION-HASH 跳转 */
@@ -440,8 +446,7 @@ $(function () {
             this.tags = 'input,select,textarea';
             /* 预设检测规则 */
             this.patterns = {
-                phone: '^1[3-9][0-9]{9}$',
-                email: '^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$'
+                phone: '^1[3-9][0-9]{9}$', email: '^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$'
             };
             /*! 检测属性是否有定义 */
             this.hasProp = function (ele, prop) {
@@ -550,9 +555,7 @@ $(function () {
             var key, keys = this.name.match(rules.key), merge = this.value, name = this.name;
             while ((key = keys.pop()) !== undefined) {
                 name = name.replace(new RegExp("\\[" + key + "\\]$"), '');
-                if (key.match(rules.push)) merge = self.build([], self.pushCounter(name), merge);
-                else if (key.match(rules.fixed)) merge = self.build([], key, merge);
-                else if (key.match(rules.named)) merge = self.build({}, key, merge);
+                if (key.match(rules.push)) merge = self.build([], self.pushCounter(name), merge); else if (key.match(rules.fixed)) merge = self.build([], key, merge); else if (key.match(rules.named)) merge = self.build({}, key, merge);
             }
             data = $.extend(true, data, merge);
         });
@@ -598,8 +601,7 @@ $(function () {
                     $image = $('<div class="uploadimage uploadimagemtl transition"><div><a class="layui-icon">&#xe603;</a><a class="layui-icon">&#x1006;</a><a class="layui-icon">&#xe602;</a></div></div>');
                     $image.attr('data-tips-image', encodeURI(src)).css('backgroundImage', 'url(' + encodeURI(src) + ')').on('click', 'a', function (event, index, prevs, $item) {
                         event.stopPropagation(), $item = $(this).parent().parent(), index = $(this).index();
-                        if (index === 2 && $item.index() !== $bt.prevAll('div.uploadimage').length) $item.next().after($item);
-                        else if (index === 0 && $item.index() > 1) $item.prev().before($item); else if (index === 1) $item.remove();
+                        if (index === 2 && $item.index() !== $bt.prevAll('div.uploadimage').length) $item.next().after($item); else if (index === 0 && $item.index() > 1) $item.prev().before($item); else if (index === 1) $item.remove();
                         imgs = [], $bt.prevAll('.uploadimage').map(function () {
                             imgs.push($(this).attr('data-tips-image'));
                         });
@@ -660,7 +662,7 @@ $(function () {
                 (selection.text = value), selection.select(), selection.unselect();
             } else if (this.selectionStart || this.selectionStart === 0) {
                 var spos = this.selectionStart, apos = this.selectionEnd || spos;
-                this.value = this.value.substring(0, spos) + value + this.value.substring(apos, this.value.length);
+                this.value = this.value.substring(0, spos) + value + this.value.substring(apos);
                 this.selectionEnd = this.selectionStart = spos + value.length;
             } else {
                 this.value += value;
@@ -798,13 +800,7 @@ $(function () {
                 if (doReload && doScript) {
                     $.layTable.reload(((element || {}).dataset || {}).tableId || true);
                 }
-            }, content: '' +
-                '<div class="padding-30 padding-bottom-0"  data-queue-load="' + code + '">' +
-                '   <div class="layui-elip notselect nowrap" data-message-title></div>' +
-                '   <div class="margin-top-15 layui-progress layui-progress-big" lay-showPercent="yes"><div class="layui-progress-bar transition" lay-percent="0.00%"></div></div>' +
-                '   <div class="margin-top-15"><code class="layui-textarea layui-bg-black border-0" disabled style="resize:none;overflow:hidden;height:190px"></code></div>' +
-                '</div>',
-            success: function ($elem) {
+            }, content: '' + '<div class="padding-30 padding-bottom-0"  data-queue-load="' + code + '">' + '   <div class="layui-elip notselect nowrap" data-message-title></div>' + '   <div class="margin-top-15 layui-progress layui-progress-big" lay-showPercent="yes"><div class="layui-progress-bar transition" lay-percent="0.00%"></div></div>' + '   <div class="margin-top-15"><code class="layui-textarea layui-bg-black border-0" disabled style="resize:none;overflow:hidden;height:190px"></code></div>' + '</div>', success: function ($elem) {
                 new function () {
                     var that = this;
                     this.$box = $elem.find('[data-queue-load=' + code + ']');
@@ -839,8 +835,7 @@ $(function () {
                                 var lines = [];
                                 for (var idx in ret.data.history) {
                                     var line = ret.data.history[idx], percent = '[ ' + line.progress + '% ] ';
-                                    if (line.message.indexOf('javascript:') === -1) lines.push(line.message.indexOf('>>>') > -1 ? line.message : percent + line.message);
-                                    else if (!that.SetCache(code, idx) && doScript !== false) that.SetCache(code, idx, 1), location.href = line.message;
+                                    if (line.message.indexOf('javascript:') === -1) lines.push(line.message.indexOf('>>>') > -1 ? line.message : percent + line.message); else if (!that.SetCache(code, idx) && doScript !== false) that.SetCache(code, idx, 1), location.href = line.message;
                                 }
                                 if (ret.data.status > 0) {
                                     that.SetState(parseInt(ret.data.status), ret.data.message);

+ 3 - 2
public/static/plugs/ckeditor/ckeditor.js → public/static/plugs/ckeditor4/ckeditor.js

@@ -1436,7 +1436,8 @@ h&&l&&!g.data("cke-upload-id")&&!g.isReadOnly(1)&&(h=(h=d.match(/image\/([a-z]+?
 
 // 注册创建函数
 window.createEditor = function (selector, option) {
-    var $container = $(selector);
+    var $container = $(selector), editor;
     $container.attr('id', $container.attr('id') || (Math.random() + '').replace('.', '_'));
-    return CKEDITOR.replace($container.attr('id'), option || {});
+    editor = CKEDITOR.replace($container.attr('id'), option || {});
+    return $container.triggerHandler('editor.init', editor), editor;
 };

+ 0 - 0
public/static/plugs/ckeditor/config.js → public/static/plugs/ckeditor4/config.js


+ 0 - 0
public/static/plugs/ckeditor/contents.css → public/static/plugs/ckeditor4/contents.css


+ 0 - 0
public/static/plugs/ckeditor/lang/en.js → public/static/plugs/ckeditor4/lang/en.js


+ 0 - 0
public/static/plugs/ckeditor/lang/zh-cn.js → public/static/plugs/ckeditor4/lang/zh-cn.js


+ 0 - 0
public/static/plugs/ckeditor/lang/zh.js → public/static/plugs/ckeditor4/lang/zh.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js → public/static/plugs/ckeditor4/plugins/a11yhelp/dialogs/a11yhelp.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/a11yhelp/dialogs/lang/en.js → public/static/plugs/ckeditor4/plugins/a11yhelp/dialogs/lang/en.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/a11yhelp/dialogs/lang/zh-cn.js → public/static/plugs/ckeditor4/plugins/a11yhelp/dialogs/lang/zh-cn.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/a11yhelp/dialogs/lang/zh.js → public/static/plugs/ckeditor4/plugins/a11yhelp/dialogs/lang/zh.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/about/dialogs/about.js → public/static/plugs/ckeditor4/plugins/about/dialogs/about.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/about/dialogs/hidpi/logo_ckeditor.png → public/static/plugs/ckeditor4/plugins/about/dialogs/hidpi/logo_ckeditor.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/about/dialogs/logo_ckeditor.png → public/static/plugs/ckeditor4/plugins/about/dialogs/logo_ckeditor.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/clipboard/dialogs/paste.js → public/static/plugs/ckeditor4/plugins/clipboard/dialogs/paste.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/colordialog/dialogs/colordialog.css → public/static/plugs/ckeditor4/plugins/colordialog/dialogs/colordialog.css


+ 0 - 0
public/static/plugs/ckeditor/plugins/colordialog/dialogs/colordialog.js → public/static/plugs/ckeditor4/plugins/colordialog/dialogs/colordialog.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/copyformatting/cursors/cursor-disabled.svg → public/static/plugs/ckeditor4/plugins/copyformatting/cursors/cursor-disabled.svg


+ 0 - 0
public/static/plugs/ckeditor/plugins/copyformatting/cursors/cursor.svg → public/static/plugs/ckeditor4/plugins/copyformatting/cursors/cursor.svg


+ 0 - 0
public/static/plugs/ckeditor/plugins/copyformatting/styles/copyformatting.css → public/static/plugs/ckeditor4/plugins/copyformatting/styles/copyformatting.css


+ 0 - 0
public/static/plugs/ckeditor/plugins/dialog/dialogDefinition.js → public/static/plugs/ckeditor4/plugins/dialog/dialogDefinition.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/dialog/styles/dialog.css → public/static/plugs/ckeditor4/plugins/dialog/styles/dialog.css


+ 0 - 0
public/static/plugs/ckeditor/plugins/div/dialogs/div.js → public/static/plugs/ckeditor4/plugins/div/dialogs/div.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/exportpdf/plugindefinition.js → public/static/plugs/ckeditor4/plugins/exportpdf/plugindefinition.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/find/dialogs/find.js → public/static/plugs/ckeditor4/plugins/find/dialogs/find.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/forms/dialogs/button.js → public/static/plugs/ckeditor4/plugins/forms/dialogs/button.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/forms/dialogs/checkbox.js → public/static/plugs/ckeditor4/plugins/forms/dialogs/checkbox.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/forms/dialogs/form.js → public/static/plugs/ckeditor4/plugins/forms/dialogs/form.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/forms/dialogs/hiddenfield.js → public/static/plugs/ckeditor4/plugins/forms/dialogs/hiddenfield.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/forms/dialogs/radio.js → public/static/plugs/ckeditor4/plugins/forms/dialogs/radio.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/forms/dialogs/select.js → public/static/plugs/ckeditor4/plugins/forms/dialogs/select.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/forms/dialogs/textarea.js → public/static/plugs/ckeditor4/plugins/forms/dialogs/textarea.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/forms/dialogs/textfield.js → public/static/plugs/ckeditor4/plugins/forms/dialogs/textfield.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/forms/images/hiddenfield.gif → public/static/plugs/ckeditor4/plugins/forms/images/hiddenfield.gif


+ 0 - 0
public/static/plugs/ckeditor/plugins/icons.png → public/static/plugs/ckeditor4/plugins/icons.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/icons_hidpi.png → public/static/plugs/ckeditor4/plugins/icons_hidpi.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/iframe/dialogs/iframe.js → public/static/plugs/ckeditor4/plugins/iframe/dialogs/iframe.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/iframe/images/placeholder.png → public/static/plugs/ckeditor4/plugins/iframe/images/placeholder.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/image/dialogs/image.js → public/static/plugs/ckeditor4/plugins/image/dialogs/image.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/image/images/noimage.png → public/static/plugs/ckeditor4/plugins/image/images/noimage.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/lineheight/lang/en.js → public/static/plugs/ckeditor4/plugins/lineheight/lang/en.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/lineheight/lang/zh-cn.js → public/static/plugs/ckeditor4/plugins/lineheight/lang/zh-cn.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/lineheight/lang/zh.js → public/static/plugs/ckeditor4/plugins/lineheight/lang/zh.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/lineheight/plugin.js → public/static/plugs/ckeditor4/plugins/lineheight/plugin.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/link/dialogs/anchor.js → public/static/plugs/ckeditor4/plugins/link/dialogs/anchor.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/link/dialogs/link.js → public/static/plugs/ckeditor4/plugins/link/dialogs/link.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/link/images/anchor.png → public/static/plugs/ckeditor4/plugins/link/images/anchor.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/link/images/hidpi/anchor.png → public/static/plugs/ckeditor4/plugins/link/images/hidpi/anchor.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/liststyle/dialogs/liststyle.js → public/static/plugs/ckeditor4/plugins/liststyle/dialogs/liststyle.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/magicline/images/hidpi/icon-rtl.png → public/static/plugs/ckeditor4/plugins/magicline/images/hidpi/icon-rtl.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/magicline/images/hidpi/icon.png → public/static/plugs/ckeditor4/plugins/magicline/images/hidpi/icon.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/magicline/images/icon-rtl.png → public/static/plugs/ckeditor4/plugins/magicline/images/icon-rtl.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/magicline/images/icon.png → public/static/plugs/ckeditor4/plugins/magicline/images/icon.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/pagebreak/images/pagebreak.gif → public/static/plugs/ckeditor4/plugins/pagebreak/images/pagebreak.gif


+ 0 - 0
public/static/plugs/ckeditor/plugins/pastefromgdocs/filter/default.js → public/static/plugs/ckeditor4/plugins/pastefromgdocs/filter/default.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/pastefromlibreoffice/filter/default.js → public/static/plugs/ckeditor4/plugins/pastefromlibreoffice/filter/default.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/pastefromword/filter/default.js → public/static/plugs/ckeditor4/plugins/pastefromword/filter/default.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/pastetools/filter/common.js → public/static/plugs/ckeditor4/plugins/pastetools/filter/common.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/pastetools/filter/image.js → public/static/plugs/ckeditor4/plugins/pastetools/filter/image.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/preview/images/pagebreak.gif → public/static/plugs/ckeditor4/plugins/preview/images/pagebreak.gif


+ 0 - 0
public/static/plugs/ckeditor/plugins/preview/preview.html → public/static/plugs/ckeditor4/plugins/preview/preview.html


+ 0 - 0
public/static/plugs/ckeditor/plugins/preview/styles/screen.css → public/static/plugs/ckeditor4/plugins/preview/styles/screen.css


+ 0 - 0
public/static/plugs/ckeditor/plugins/scayt/dialogs/dialog.css → public/static/plugs/ckeditor4/plugins/scayt/dialogs/dialog.css


+ 0 - 0
public/static/plugs/ckeditor/plugins/scayt/dialogs/options.js → public/static/plugs/ckeditor4/plugins/scayt/dialogs/options.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/scayt/dialogs/toolbar.css → public/static/plugs/ckeditor4/plugins/scayt/dialogs/toolbar.css


+ 0 - 0
public/static/plugs/ckeditor/plugins/scayt/skins/moono-lisa/scayt.css → public/static/plugs/ckeditor4/plugins/scayt/skins/moono-lisa/scayt.css


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_address.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_address.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_blockquote.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_blockquote.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_div.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_div.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_h1.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_h1.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_h2.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_h2.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_h3.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_h3.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_h4.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_h4.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_h5.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_h5.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_h6.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_h6.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_p.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_p.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/showblocks/images/block_pre.png → public/static/plugs/ckeditor4/plugins/showblocks/images/block_pre.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/smiley/dialogs/smiley.js → public/static/plugs/ckeditor4/plugins/smiley/dialogs/smiley.js


+ 0 - 0
public/static/plugs/ckeditor/plugins/smiley/images/angel_smile.gif → public/static/plugs/ckeditor4/plugins/smiley/images/angel_smile.gif


+ 0 - 0
public/static/plugs/ckeditor/plugins/smiley/images/angel_smile.png → public/static/plugs/ckeditor4/plugins/smiley/images/angel_smile.png


+ 0 - 0
public/static/plugs/ckeditor/plugins/smiley/images/angry_smile.gif → public/static/plugs/ckeditor4/plugins/smiley/images/angry_smile.gif


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