浏览代码

Merge branch '1.x' into develop

# Conflicts:
#	README.md
#	application/admin/command/Install/fastadmin.sql
#	application/admin/controller/Index.php
#	application/admin/controller/auth/Adminlog.php
#	application/common.php
#	application/common/controller/Backend.php
#	application/common/library/Email.php
#	application/config.php
#	application/index/controller/User.php
#	bower.json
#	composer.json
#	extend/fast/Http.php
#	public/assets/css/backend.min.css
#	public/assets/css/frontend.min.css
#	public/assets/js/backend/addon.js
#	public/assets/js/bootstrap-table-commonsearch.js
#	public/assets/js/fast.js
#	public/assets/js/frontend/user.js
#	public/assets/js/jquery.drag.min.js
#	public/assets/js/require-backend.js
#	public/assets/js/require-backend.min.js
#	public/assets/js/require-form.js
#	public/assets/js/require-frontend.min.js
#	public/assets/js/require-table.js
Karson 1 年之前
父节点
当前提交
8caeeb8a78
共有 71 个文件被更改,包括 752 次插入293 次删除
  1. 1 1
      application/admin/behavior/AdminLog.php
  2. 3 5
      application/admin/command/Addon.php
  3. 25 15
      application/admin/command/Crud.php
  4. 0 1
      application/admin/command/Crud/stubs/add.stub
  5. 0 1
      application/admin/command/Crud/stubs/edit.stub
  6. 21 0
      application/admin/command/Crud/stubs/html/fieldlist-array.stub
  7. 8 7
      application/admin/command/Install/fastadmin.sql
  8. 12 3
      application/admin/controller/Addon.php
  9. 3 3
      application/admin/controller/Index.php
  10. 1 0
      application/admin/controller/auth/Adminlog.php
  11. 1 0
      application/admin/controller/general/Profile.php
  12. 1 1
      application/admin/controller/user/User.php
  13. 2 0
      application/admin/lang/zh-cn.php
  14. 2 6
      application/admin/lang/zh-cn/addon.php
  15. 6 8
      application/admin/library/Auth.php
  16. 1 1
      application/admin/library/traits/Backend.php
  17. 11 8
      application/admin/model/AdminLog.php
  18. 6 0
      application/admin/view/addon/config.html
  19. 7 2
      application/admin/view/addon/index.html
  20. 2 0
      application/api/controller/Common.php
  21. 6 0
      application/api/controller/Ems.php
  22. 13 63
      application/common.php
  23. 2 2
      application/common/controller/Api.php
  24. 5 4
      application/common/controller/Backend.php
  25. 3 3
      application/common/library/Auth.php
  26. 2 2
      application/common/library/Menu.php
  27. 4 6
      application/common/library/Security.php
  28. 1 1
      application/common/model/Config.php
  29. 1 1
      application/config.php
  30. 3 3
      application/extra/site.php
  31. 7 8
      application/index/controller/User.php
  32. 2 13
      application/index/view/user/index.html
  33. 1 2
      application/index/view/user/login.html
  34. 2 4
      application/index/view/user/profile.html
  35. 6 2
      composer.json
  36. 5 5
      extend/fast/Http.php
  37. 32 0
      public/assets/css/backend.css
  38. 1 1
      public/assets/css/backend.min.css
  39. 39 0
      public/assets/css/frontend.css
  40. 1 1
      public/assets/css/frontend.min.css
  41. 30 0
      public/assets/css/skins/_all-skins.css
  42. 5 0
      public/assets/css/skins/skin-black-blue.css
  43. 5 0
      public/assets/css/skins/skin-black-green.css
  44. 5 0
      public/assets/css/skins/skin-black-pink.css
  45. 5 0
      public/assets/css/skins/skin-black-purple.css
  46. 5 0
      public/assets/css/skins/skin-black-red.css
  47. 5 0
      public/assets/css/skins/skin-black-yellow.css
  48. 1 1
      public/assets/js/adminlte.js
  49. 1 1
      public/assets/js/backend.js
  50. 101 20
      public/assets/js/backend/addon.js
  51. 0 1
      public/assets/js/backend/auth/adminlog.js
  52. 9 6
      public/assets/js/backend/index.js
  53. 2 2
      public/assets/js/bootstrap-table-commonsearch.js
  54. 16 2
      public/assets/js/fast.js
  55. 2 2
      public/assets/js/frontend/user.js
  56. 1 1
      public/assets/js/jquery.drag.min.js
  57. 0 2
      public/assets/js/require-backend.js
  58. 55 18
      public/assets/js/require-form.js
  59. 1 3
      public/assets/js/require-frontend.js
  60. 109 45
      public/assets/js/require-table.js
  61. 9 4
      public/assets/js/require-upload.js
  62. 1 2
      public/assets/js/require.min.js
  63. 9 0
      public/assets/js/tagsinput.js
  64. 33 0
      public/assets/less/backend.less
  65. 51 0
      public/assets/less/frontend.less
  66. 7 0
      public/assets/less/skins/skin-black-blue.less
  67. 7 0
      public/assets/less/skins/skin-black-green.less
  68. 7 0
      public/assets/less/skins/skin-black-pink.less
  69. 7 0
      public/assets/less/skins/skin-black-purple.less
  70. 7 0
      public/assets/less/skins/skin-black-red.less
  71. 7 0
      public/assets/less/skins/skin-black-yellow.less

+ 1 - 1
application/admin/behavior/AdminLog.php

@@ -4,7 +4,7 @@ namespace app\admin\behavior;
 
 class AdminLog
 {
-    public function run(&$params)
+    public function run(&$response)
     {
         //只记录POST请求的日志
         if (request()->isPost() && config('fastadmin.auto_record_log')) {

+ 3 - 5
application/admin/command/Addon.php

@@ -15,7 +15,6 @@ use think\exception\PDOException;
 
 class Addon extends Command
 {
-
     protected function configure()
     {
         $this
@@ -33,6 +32,7 @@ class Addon extends Command
 
     protected function execute(Input $input, Output $output)
     {
+        \think\Config::load(dirname(dirname(__FILE__)) . DS . 'config.php');
         $name = $input->getOption('name') ?: '';
         $action = $input->getOption('action') ?: '';
         if (stripos($name, 'addons' . DS) !== false) {
@@ -82,7 +82,6 @@ class Addon extends Command
                         $createTableSql = $result[0]['Create Table'];
                     }
                 } catch (PDOException $e) {
-
                 }
 
                 $data = [
@@ -177,12 +176,12 @@ class Addon extends Command
                 if (!$info) {
                     throw new Exception(__('Addon info file data incorrect'));
                 }
-                $infoname = isset($info['name']) ? $info['name'] : '';
+                $infoname = $info['name'] ?? '';
                 if (!$infoname || !preg_match("/^[a-z]+$/i", $infoname) || $infoname != $name) {
                     throw new Exception(__('Addon info name incorrect'));
                 }
 
-                $infoversion = isset($info['version']) ? $info['version'] : '';
+                $infoversion = $info['version'] ?? '';
                 if (!$infoversion || !preg_match("/^\d+\.\d+\.\d+$/i", $infoversion)) {
                     throw new Exception(__('Addon info version incorrect'));
                 }
@@ -340,5 +339,4 @@ class Addon extends Command
     {
         return __DIR__ . '/Addon/stubs/' . $name . '.stub';
     }
-
 }

+ 25 - 15
application/admin/command/Crud.php

@@ -152,7 +152,7 @@ class Crud extends Command
     /**
      * JSON后缀
      */
-    protected $jsonSuffix = ['json'];
+    protected $jsonSuffix = ['json', 'array'];
 
     /**
      * 标签后缀
@@ -466,7 +466,7 @@ class Crud extends Command
                     }
                 }
                 $relationTableInfo = $relationTableInfo[0];
-                $relationModel = isset($relationModels[$index]) ? $relationModels[$index] : '';
+                $relationModel = $relationModels[$index] ?? '';
 
                 list($relationNamespace, $relationName, $relationFile) = $this->getModelData($modelModuleName, $relationModel, $relationName);
 
@@ -666,8 +666,8 @@ class Crud extends Command
         //如果是关联模型
         foreach ($relations as $index => &$relation) {
             if ($relation['relationMode'] == 'hasone') {
-                $relationForeignKey = $relation['relationForeignKey'] ? $relation['relationForeignKey'] : $table . "_id";
-                $relationPrimaryKey = $relation['relationPrimaryKey'] ? $relation['relationPrimaryKey'] : $priKey;
+                $relationForeignKey = $relation['relationForeignKey'] ?: $table . "_id";
+                $relationPrimaryKey = $relation['relationPrimaryKey'] ?: $priKey;
 
                 if (!in_array($relationForeignKey, $relation['relationFieldList'])) {
                     throw new Exception('relation table [' . $relation['relationTableName'] . '] must be contain field [' . $relationForeignKey . ']');
@@ -676,8 +676,8 @@ class Crud extends Command
                     throw new Exception('table [' . $modelTableName . '] must be contain field [' . $relationPrimaryKey . ']');
                 }
             } elseif ($relation['relationMode'] == 'belongsto') {
-                $relationForeignKey = $relation['relationForeignKey'] ? $relation['relationForeignKey'] : Loader::parseName($relation['relationName']) . "_id";
-                $relationPrimaryKey = $relation['relationPrimaryKey'] ? $relation['relationPrimaryKey'] : $relation['relationPriKey'];
+                $relationForeignKey = $relation['relationForeignKey'] ?: Loader::parseName($relation['relationName']) . "_id";
+                $relationPrimaryKey = $relation['relationPrimaryKey'] ?: $relation['relationPriKey'];
                 if (!in_array($relationForeignKey, $fieldArr)) {
                     throw new Exception('table [' . $modelTableName . '] must be contain field [' . $relationForeignKey . ']');
                 }
@@ -685,8 +685,8 @@ class Crud extends Command
                     throw new Exception('relation table [' . $relation['relationTableName'] . '] must be contain field [' . $relationPrimaryKey . ']');
                 }
             } elseif ($relation['relationMode'] == 'hasmany') {
-                $relationForeignKey = $relation['relationForeignKey'] ? $relation['relationForeignKey'] : $table . "_id";
-                $relationPrimaryKey = $relation['relationPrimaryKey'] ? $relation['relationPrimaryKey'] : $priKey;
+                $relationForeignKey = $relation['relationForeignKey'] ?: $table . "_id";
+                $relationPrimaryKey = $relation['relationPrimaryKey'] ?: $priKey;
                 if (!in_array($relationForeignKey, $relation['relationFieldList'])) {
                     throw new Exception('relation table [' . $relation['relationTableName'] . '] must be contain field [' . $relationForeignKey . ']');
                 }
@@ -879,7 +879,7 @@ class Crud extends Command
                         $formEditElement = Form::input('text', $fieldName, $editValue, $attrArr);
                     } elseif ($inputType == 'fieldlist') {
                         $itemArr = $this->getItemArray($itemArr, $field, $v['COLUMN_COMMENT']);
-                        $templateName = !isset($itemArr['key']) && !isset($itemArr['value']) && count($itemArr) > 0 ? 'fieldlist-template' : 'fieldlist';
+                        $templateName = !isset($itemArr['key']) && count($itemArr) > 0 ? (isset($itemArr['value']) && count($itemArr) === 1 ? 'fieldlist-array' : 'fieldlist-template') : 'fieldlist';
                         $itemKey = isset($itemArr['key']) ? ucfirst($itemArr['key']) : 'Key';
                         $itemValue = isset($itemArr['value']) ? ucfirst($itemArr['value']) : 'Value';
                         $theadListArr = $tbodyListArr = [];
@@ -901,6 +901,12 @@ class Crud extends Command
                             $cssClassArr[] = 'selectpage';
                             $selectpageTable = substr($field, 0, strripos($field, '_'));
                             $selectpageField = '';
+                            foreach ($relations as $index => $relation) {
+                                if ($relation['relationForeignKey'] === $field) {
+                                    $selectpageTable = substr($relation['relationTableName'], strlen($prefix));
+                                    break;
+                                }
+                            }
                             $selectpageController = str_replace('_', '/', $selectpageTable);
                             $attrArr['data-source'] = $selectpageController . "/index";
                             //如果是类型表需要特殊处理下
@@ -931,7 +937,6 @@ class Crud extends Command
                                     }
                                 }
                             } catch (\Exception $e) {
-
                             }
                             if (!$selectpageField) {
                                 foreach ($this->fieldSelectpageMap as $m => $n) {
@@ -993,7 +998,7 @@ class Crud extends Command
                     }
                     if (!$fields || in_array($field, explode(',', $fields))) {
                         //构造JS列信息
-                        $javascriptList[] = $this->getJsColumn($field, $v['DATA_TYPE'], $inputType && in_array($inputType, ['select', 'checkbox', 'radio']) ? '_text' : '', $itemArr);
+                        $javascriptList[] = $this->getJsColumn($field, $v['DATA_TYPE'], $inputType && in_array($inputType, ['select', 'checkbox', 'radio']) ? '_text' : '', $itemArr, $v);
                     }
                     if ($this->headingFilterField && $this->headingFilterField == $field && $itemArr) {
                         $headingHtml = $this->getReplacedStub('html/heading-html', ['field' => $field, 'fieldName' => Loader::parseName($field, 1, false)]);
@@ -1048,7 +1053,7 @@ class Crud extends Command
                     //过滤text类型字段
                     if ($v['DATA_TYPE'] != 'text') {
                         //构造JS列信息
-                        $javascriptList[] = $this->getJsColumn($relationField, $v['DATA_TYPE']);
+                        $javascriptList[] = $this->getJsColumn($relationField, $v['DATA_TYPE'], '', [], $v);
                     }
                 }
             }
@@ -1537,7 +1542,7 @@ EOD;
     {
         $itemArr = [];
         $comment = str_replace(',', ',', $comment);
-        if (stripos($comment, ':') !== false && stripos($comment, ',') && stripos($comment, '=') !== false) {
+        if (stripos($comment, ':') !== false && stripos($comment, '=') !== false) {
             list($fieldLang, $item) = explode(':', $comment);
             $itemArr = [];
             foreach (explode(',', $item) as $k => $v) {
@@ -1699,9 +1704,10 @@ EOD;
      * @param string $datatype
      * @param string $extend
      * @param array  $itemArr
+     * @param array  $fieldConfig
      * @return string
      */
-    protected function getJsColumn($field, $datatype = '', $extend = '', $itemArr = [])
+    protected function getJsColumn($field, $datatype = '', $extend = '', $itemArr = [], $fieldConfig = [])
     {
         $lang = mb_ucfirst($field);
         $formatter = '';
@@ -1739,7 +1745,7 @@ EOD;
         $noSearchFiles = ['file$', 'files$', 'image$', 'images$', '^weigh$'];
         if (preg_match("/" . implode('|', $noSearchFiles) . "/i", $field)) {
             $html .= ", operate: false";
-        } else if (in_array($datatype, ['varchar'])) {
+        } elseif (in_array($datatype, ['varchar'])) {
             $html .= ", operate: 'LIKE'";
         }
 
@@ -1751,6 +1757,10 @@ EOD;
         if (in_array($datatype, ['set'])) {
             $html .= ", operate:'FIND_IN_SET'";
         }
+        if (isset($fieldConfig['CHARACTER_MAXIMUM_LENGTH']) && $fieldConfig['CHARACTER_MAXIMUM_LENGTH'] >= 255 && in_array($datatype, ['varchar']) && !$formatter) {
+            $formatter = 'content';
+            $html .= ", table: table, class: 'autocontent'";
+        }
         if (in_array($formatter, ['image', 'images'])) {
             $html .= ", events: Table.api.events.image";
         }

+ 0 - 1
application/admin/command/Crud/stubs/add.stub

@@ -5,7 +5,6 @@
         <label class="control-label col-xs-12 col-sm-2"></label>
         <div class="col-xs-12 col-sm-8">
             <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
-            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
         </div>
     </div>
 </form>

+ 0 - 1
application/admin/command/Crud/stubs/edit.stub

@@ -5,7 +5,6 @@
         <label class="control-label col-xs-12 col-sm-2"></label>
         <div class="col-xs-12 col-sm-8">
             <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
-            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
         </div>
     </div>
 </form>

+ 21 - 0
application/admin/command/Crud/stubs/html/fieldlist-array.stub

@@ -0,0 +1,21 @@
+
+            <dl class="list-unstyled fieldlist" data-name="{%fieldName%}" data-template="{%fieldName%}tpl">
+                <dd>
+                    <ins>{:__('{%itemValue%}')}</ins>
+                </dd>
+                <dd>
+                    <ins><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></ins>
+                </dd>
+            </dl>
+
+            <textarea name="{%fieldName%}" class="form-control hide" cols="30" rows="5">{%fieldValue%}</textarea>
+            <script id="{%fieldName%}tpl" type="text/html">
+                <dd class="form-inline">
+                    <ins><input type="text" name="<%=name%>[<%=index%>][value]" class="form-control" size="15" value="<%=row%>"/></ins>
+                    <ins>
+                        <span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span>
+                        <span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span>
+                    </ins>
+                </dd>
+            </script>
+

文件差异内容过多而无法显示
+ 8 - 7
application/admin/command/Install/fastadmin.sql


+ 12 - 3
application/admin/controller/Addon.php

@@ -95,19 +95,24 @@ class Addon extends Backend
         }
         $tips = [];
         $groupList = [];
+        $ungroupList = [];
         foreach ($config as $index => &$item) {
             //如果有设置分组
             if (isset($item['group']) && $item['group']) {
                 if (!in_array($item['group'], $groupList)) {
                     $groupList["custom" . (count($groupList) + 1)] = $item['group'];
                 }
+            } elseif ($item['name'] != '__tips__') {
+                $ungroupList[] = $item['name'];
             }
             if ($item['name'] == '__tips__') {
                 $tips = $item;
                 unset($config[$index]);
             }
         }
-        $groupList['other'] = '其它';
+        if ($ungroupList) {
+            $groupList['other'] = '其它';
+        }
         $this->view->assign("groupList", $groupList);
         $this->view->assign("addon", ['info' => $info, 'config' => $config, 'tips' => $tips]);
         $configFile = ADDON_PATH . $name . DS . 'config.html';
@@ -230,6 +235,7 @@ class Addon extends Backend
             $uid = $this->request->post("uid");
             $token = $this->request->post("token");
             $faversion = $this->request->post("faversion");
+            $force = $this->request->post("force");
             if (!$uid || !$token) {
                 throw new Exception(__('Please login and try to install'));
             }
@@ -238,7 +244,7 @@ class Addon extends Backend
                 'token'     => $token,
                 'faversion' => $faversion
             ];
-            $info = Service::local($file, $extend);
+            $info = Service::local($file, $extend, $force);
         } catch (AddonException $e) {
             $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
         } catch (Exception $e) {
@@ -441,8 +447,11 @@ class Addon extends Backend
             } catch (\Exception $e) {
 
             }
-            $rows = isset($json['rows']) ? $json['rows'] : [];
+            $rows = $json['rows'] ?? [];
             foreach ($rows as $index => $row) {
+                if (!isset($row['name'])) {
+                    continue;
+                }
                 $onlineaddons[$row['name']] = $row;
             }
             Cache::set("onlineaddons", $onlineaddons, 600);

+ 3 - 3
application/admin/controller/Index.php

@@ -44,7 +44,6 @@ class Index extends Backend
             'dashboard' => 'hot',
             'addon'     => ['new', 'red', 'badge'],
             'auth/rule' => __('Menu'),
-            'general'   => ['new', 'purple'],
         ], $this->view->site['fixedpage']);
         $action = $this->request->request('action');
         if ($this->request->isPost()) {
@@ -66,7 +65,8 @@ class Index extends Backend
      */
     public function login()
     {
-        $url = $this->request->get('url', 'index/index', 'url_clean');
+        $url = $this->request->get('url', '', 'url_clean');
+        $url = $url ?: 'index/index';
         if ($this->auth->isLogin()) {
             $this->success(__("You've logged in, do not login again"), $url);
         }
@@ -74,7 +74,7 @@ class Index extends Backend
         $keeyloginhours = 24;
         if ($this->request->isPost()) {
             $username = $this->request->post('username');
-            $password = $this->request->post('password');
+            $password = $this->request->post('password', '', null);
             $keeplogin = $this->request->post('keeplogin');
             $token = $this->request->post('__token__');
             $rule = [

+ 1 - 0
application/admin/controller/auth/Adminlog.php

@@ -53,6 +53,7 @@ class Adminlog extends Backend
                         $query->where('admin_id', 'in', $childrenAdminIds);
                     }
                 })
+                ->field('content,useragent', true)
                 ->order($sort, $order)
                 ->paginate($limit);
 

+ 1 - 0
application/admin/controller/general/Profile.php

@@ -74,6 +74,7 @@ class Profile extends Backend
                 $admin->save($params);
                 //因为个人资料面板读取的Session显示,修改自己资料后同时更新Session
                 Session::set("admin", $admin->toArray());
+                Session::set("admin.safecode", $this->auth->getEncryptSafecode($admin));
                 $this->success();
             }
             $this->error();

+ 1 - 1
application/admin/controller/user/User.php

@@ -24,7 +24,7 @@ class User extends Backend
     public function _initialize()
     {
         parent::_initialize();
-        $this->model = model('User');
+        $this->model = new \app\admin\model\User;
     }
 
     /**

+ 2 - 0
application/admin/lang/zh-cn.php

@@ -4,6 +4,8 @@ return [
     'User id'                                               => '会员ID',
     'Username'                                              => '用户名',
     'Nickname'                                              => '昵称',
+    'Mobile'                                                => '手机',
+    'Email'                                                 => '邮箱',
     'Password'                                              => '密码',
     'Mobile'                                                => '手机号',
     'Sign up'                                               => '注 册',

+ 2 - 6
application/admin/lang/zh-cn/addon.php

@@ -14,11 +14,8 @@ return [
     'Refresh addon cache'                                     => '刷新插件缓存',
     'Userinfo'                                                => '会员信息',
     'Reload authorization'                                    => '刷新授权',
-    'Online store'                                            => '在线商店',
     'Local addon'                                             => '本地插件',
     'Conflict tips'                                           => '此插件中发现和现有系统中部分文件发现冲突!以下文件将会被影响,请备份好相关文件后再继续操作',
-    'Login tips'                                              => '此处登录账号为<a href="https://www.fastadmin.net" target="_blank">FastAdmin官网账号</a>',
-    'Logined tips'                                            => '你好!%s<br />当前你已经登录,将同步保存你的购买记录',
     'Pay tips'                                                => '扫码支付后如果仍然无法安装,请不要重复支付,请稍后再重试安装!',
     'Pay successful tips'                                     => '购买成功!请点击继续安装按钮完成安装!',
     'Pay click tips'                                          => '请点击这里在新窗口中进行支付!',
@@ -26,8 +23,7 @@ return [
     'Upgrade tips'                                            => '确认升级<b>《%s》</b>?<p class="text-danger">1、请务必做好代码和数据库备份!备份!备份!<br>2、升级后如出现冗余数据,请根据需要移除即可!<br>3、不建议在生产环境升级,请在本地完成升级测试</p>如有重要数据请备份后再操作!',
     'Offline installed tips'                                  => '安装成功!清除浏览器缓存和框架缓存后生效!',
     'Online installed tips'                                   => '安装成功!清除浏览器缓存和框架缓存后生效!',
-    'Not login tips'                                          => '你当前未登录FastAdmin,请登录后操作!',
-    'Please login and try to install'                         => '请登录FastAdmin后再进行离线安装!',
+    'Please login and try to install'                         => '请登录FastAdmin后再进行本地安装!',
     'Not installed tips'                                      => '请安装后再访问插件前台页面!',
     'Not enabled tips'                                        => '插件已经禁用,请启用后再访问插件前台页面!',
     'New version tips'                                        => '发现新版本:%s 点击查看更新日志',
@@ -37,6 +33,7 @@ return [
     'Store not available tips'                                => '插件市场暂不可用,是否切换到本地插件?',
     'Switch to the local'                                     => '切换到本地插件',
     'try to reload'                                           => '重新尝试加载',
+    'Please disable addon first'                              => '请先禁用插件再进行操作',
     'Please disable the add before trying to upgrade'         => '请先禁用插件再进行升级',
     'Please disable the add before trying to uninstall'       => '请先禁用插件再进行卸载',
     'Login now'                                               => '立即登录',
@@ -80,7 +77,6 @@ return [
     'Enable'                                                  => '启用',
     'Your username or email'                                  => '你的手机号、用户名或邮箱',
     'Your password'                                           => '你的密码',
-    'Login FastAdmin'                                         => '登录',
     'Login'                                                   => '登录',
     'Logout'                                                  => '退出登录',
     'Register'                                                => '注册账号',

+ 6 - 8
application/admin/library/Auth.php

@@ -339,7 +339,7 @@ class Auth extends \fast\Auth
             }
         }
         // 取出所有分组
-        $groupList = \app\admin\model\AuthGroup::where(['status' => 'normal'])->select();
+        $groupList = \app\admin\model\AuthGroup::where($this->isSuperAdmin() ? '1=1' : ['status' => 'normal'])->select();
         $objList = [];
         foreach ($groups as $k => $v) {
             if ($v['rules'] === '*') {
@@ -371,8 +371,7 @@ class Auth extends \fast\Auth
         $childrenAdminIds = [];
         if (!$this->isSuperAdmin()) {
             $groupIds = $this->getChildrenGroupIds(false);
-            $authGroupList = \app\admin\model\AuthGroupAccess::
-            field('uid,group_id')
+            $authGroupList = \app\admin\model\AuthGroupAccess::field('uid,group_id')
                 ->where('group_id', 'in', $groupIds)
                 ->select();
             foreach ($authGroupList as $k => $v) {
@@ -418,7 +417,6 @@ class Auth extends \fast\Auth
                 $titleArr[$pathArr[$rule['name']]] = $rule['title'];
                 $menuArr[$pathArr[$rule['name']]] = $rule;
             }
-
         }
         ksort($menuArr);
         $this->breadcrumb = $menuArr;
@@ -444,9 +442,9 @@ class Auth extends \fast\Auth
         foreach ($params as $k => $v) {
             $url = $k;
             if (is_array($v)) {
-                $nums = isset($v[0]) ? $v[0] : 0;
-                $color = isset($v[1]) ? $v[1] : $colorArr[(is_numeric($nums) ? $nums : strlen($nums)) % $colorNums];
-                $class = isset($v[2]) ? $v[2] : 'label';
+                $nums = $v[0] ?? 0;
+                $color = $v[1] ?? $colorArr[(is_numeric($nums) ? $nums : strlen($nums)) % $colorNums];
+                $class = $v[2] ?? 'label';
             } else {
                 $nums = $v;
                 $color = $colorArr[(is_numeric($nums) ? $nums : strlen($nums)) % $colorNums];
@@ -485,7 +483,7 @@ class Auth extends \fast\Auth
             }
             $v['icon'] = $v['icon'] . ' fa-fw';
             $v['url'] = isset($v['url']) && $v['url'] ? $v['url'] : '/' . $module . '/' . $v['name'];
-            $v['badge'] = isset($badgeList[$v['name']]) ? $badgeList[$v['name']] : '';
+            $v['badge'] = $badgeList[$v['name']] ?? '';
             $v['title'] = __($v['title']);
             $v['url'] = preg_match("/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i", $v['url']) ? $v['url'] : url($v['url']);
             $v['menuclass'] = in_array($v['menutype'], ['dialog', 'ajax']) ? 'btn-' . $v['menutype'] : '';

+ 1 - 1
application/admin/library/traits/Backend.php

@@ -460,7 +460,7 @@ trait Backend
             if ($has_admin_id) {
                 $auth = Auth::instance();
                 foreach ($insert as &$val) {
-                    if (!isset($val['admin_id']) || empty($val['admin_id'])) {
+                    if (empty($val['admin_id'])) {
                         $val['admin_id'] = $auth->isLogin() ? $auth->id : 0;
                     }
                 }

+ 11 - 8
application/admin/model/AdminLog.php

@@ -41,8 +41,8 @@ class AdminLog extends Model
 
     /**
      * 记录日志
-     * @param string $title
-     * @param string $content
+     * @param string $title   日志标题
+     * @param string $content 日志内容
      */
     public static function record($title = '', $content = '')
     {
@@ -50,6 +50,9 @@ class AdminLog extends Model
         $admin_id = $auth->isLogin() ? $auth->id : 0;
         $username = $auth->isLogin() ? $auth->username : __('Unknown');
 
+        // 设置过滤函数
+        request()->filter('trim,strip_tags,htmlspecialchars');
+
         $controllername = Loader::parseName(request()->controller());
         $actionname = strtolower(request()->action());
         $path = str_replace('.', '/', $controllername) . '/' . $actionname;
@@ -60,12 +63,12 @@ class AdminLog extends Model
                 }
             }
         }
-        $content = $content ? $content : self::$content;
+        $content = $content ?: self::$content;
         if (!$content) {
-            $content = request()->param('', null, 'trim,strip_tags,htmlspecialchars');
+            $content = request()->param('') ?: file_get_contents("php://input");
             $content = self::getPureContent($content);
         }
-        $title = $title ? $title : self::$title;
+        $title = $title ?: self::$title;
         if (!$title) {
             $title = [];
             $breadcrumb = Auth::instance()->getBreadcrumb($path);
@@ -77,18 +80,18 @@ class AdminLog extends Model
         self::create([
             'title'     => $title,
             'content'   => !is_scalar($content) ? json_encode($content, JSON_UNESCAPED_UNICODE) : $content,
-            'url'       => substr(request()->url(), 0, 1500),
+            'url'       => substr(xss_clean(strip_tags(request()->url())), 0, 1500),
             'admin_id'  => $admin_id,
             'username'  => $username,
             'useragent' => substr(request()->server('HTTP_USER_AGENT'), 0, 255),
-            'ip'        => request()->ip()
+            'ip'        => xss_clean(strip_tags(request()->ip()))
         ]);
     }
 
     /**
      * 获取已屏蔽关键信息的数据
      * @param $content
-     * @return false|string
+     * @return array
      */
     protected static function getPureContent($content)
     {

+ 6 - 0
application/admin/view/addon/config.html

@@ -105,6 +105,12 @@
                                             <span class="msg-box n-right" for="c-{$item.name}"></span>
                                         </div>
                                         {/case}
+                                        {case switch}
+                                        <input id="c-{$item.name}" name="row[{$item.name}]" type="hidden" value="{:$item.value?1:0}">
+                                        <a href="javascript:;" data-toggle="switcher" class="btn-switcher" data-input-id="c-{$item.name}" data-yes="1" data-no="0">
+                                            <i class="fa fa-toggle-on text-success {if !$item.value}fa-flip-horizontal text-gray{/if} fa-2x"></i>
+                                        </a>
+                                        {/case}
                                         {case bool}
                                         <label for="row[{$item.name}]-yes"><input id="row[{$item.name}]-yes" name="row[{$item.name}]" type="radio" value="1" {$item.value?'checked':''} data-tip="{$item.tip}" /> {:__('Yes')}</label>
                                         <label for="row[{$item.name}]-no"><input id="row[{$item.name}]-no" name="row[{$item.name}]" type="radio" value="0" {$item.value?'':'checked'} data-tip="{$item.tip}" /> {:__('No')}</label>

+ 7 - 2
application/admin/view/addon/index.html

@@ -160,13 +160,18 @@
     <div class="">
         <div class=""><%=#__("Are you sure you want to unstall %s?", addon['title'])%>
             <p class="text-danger">{:__('Delete all the addon file and cannot be recovered!')} </p>
-                      {if config('app_debug')}
+            {if config('app_debug')}
             <p class="text-danger"><input type="checkbox" name="droptables" id="droptables" data-name="<%=addon['name']%>"/> {:__('Delete all the addon database and cannot be recovered!')} </p>
-                      {/if}
+            {/if}
             <p class="text-danger">{:__('Please backup important data manually before uninstall!')}</p>
         </div>
     </div>
 </script>
+<script id="upgradetpl" type="text/html">
+    <div class="">
+        <div class=""><%=#__("Upgrade tips", addon['title'])%></div>
+    </div>
+</script>
 <script id="conflicttpl" type="text/html">
     <div class="alert alert-dismissable alert-danger">
         <button type="button" class="close" data-dismiss="alert">×</button>

+ 2 - 0
application/api/controller/Common.php

@@ -137,6 +137,8 @@ class Common extends Api
                 $attachment = $upload->upload();
             } catch (UploadException $e) {
                 $this->error($e->getMessage());
+            } catch (\Exception $e) {
+                $this->error($e->getMessage());
             }
 
             $this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);

+ 6 - 0
application/api/controller/Ems.php

@@ -45,6 +45,12 @@ class Ems extends Api
         if ($last && time() - $last['createtime'] < 60) {
             $this->error(__('发送频繁'));
         }
+
+        $ipSendTotal = \app\common\model\Ems::where(['ip' => $this->request->ip()])->whereTime('createtime', '-1 hours')->count();
+        if ($ipSendTotal >= 5) {
+            $this->error(__('发送频繁'));
+        }
+
         if ($event) {
             $userinfo = User::getByEmail($email);
             if ($event == 'register' && $userinfo) {

+ 13 - 63
application/common.php

@@ -2,7 +2,6 @@
 
 // 公共助手函数
 
-use Symfony\Component\VarExporter\VarExporter;
 use think\exception\HttpResponseException;
 use think\Response;
 
@@ -88,8 +87,8 @@ if (!function_exists('cdnurl')) {
     function cdnurl($url, $domain = false)
     {
         $regex = "/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i";
-        if (is_bool($domain)) {
-            $cdnurl = \think\Config::get('upload.cdnurl');
+        $cdnurl = \think\Config::get('upload.cdnurl');
+        if (is_bool($domain) || stripos($cdnurl, '/') === 0) {
             $url = preg_match($regex, $url) || ($cdnurl && stripos($url, $cdnurl) === 0) ? $url : $cdnurl . $url;
         }
         if ($domain && !preg_match($regex, $url)) {
@@ -249,9 +248,10 @@ if (!function_exists('addtion')) {
             if ($v['model']) {
                 $model = new $v['model'];
             } else {
-                $model = $v['name'] ? \think\Db::name($v['name']) : \think\Db::table($v['table']);
+                // 优先判断使用table的配置
+                $model = $v['table'] ? \think\Db::table($v['table']) : \think\Db::name($v['name']);
             }
-            $primary = $v['primary'] ? $v['primary'] : $model->getPk();
+            $primary = $v['primary'] ?: $model->getPk();
             $result[$v['field']] = isset($ids[$v['field']]) ? $model->where($primary, 'in', $ids[$v['field']])->column($v['column'], $primary) : [];
         }
 
@@ -280,60 +280,6 @@ if (!function_exists('var_export_short')) {
     function var_export_short($data, $return = true)
     {
         return var_export($data, $return);
-        $replaced = [];
-        $count = 0;
-
-        //判断是否是对象
-        if (is_resource($data) || is_object($data)) {
-            return var_export($data, $return);
-        }
-
-        //判断是否有特殊的键名
-        $specialKey = false;
-        array_walk_recursive($data, function (&$value, &$key) use (&$specialKey) {
-            if (is_string($key) && (stripos($key, "\n") !== false || stripos($key, "array (") !== false)) {
-                $specialKey = true;
-            }
-        });
-        if ($specialKey) {
-            return var_export($data, $return);
-        }
-        array_walk_recursive($data, function (&$value, &$key) use (&$replaced, &$count, &$stringcheck) {
-            if (is_object($value) || is_resource($value)) {
-                $replaced[$count] = var_export($value, true);
-                $value = "##<{$count}>##";
-            } else {
-                if (is_string($value) && (stripos($value, "\n") !== false || stripos($value, "array (") !== false)) {
-                    $index = array_search($value, $replaced);
-                    if ($index === false) {
-                        $replaced[$count] = var_export($value, true);
-                        $value = "##<{$count}>##";
-                    } else {
-                        $value = "##<{$index}>##";
-                    }
-                }
-            }
-            $count++;
-        });
-
-        $dump = var_export($data, true);
-
-        $dump = preg_replace('#(?:\A|\n)([ ]*)array \(#i', '[', $dump); // Starts
-        $dump = preg_replace('#\n([ ]*)\),#', "\n$1],", $dump); // Ends
-        $dump = preg_replace('#=> \[\n\s+\],\n#', "=> [],\n", $dump); // Empties
-        $dump = preg_replace('#\)$#', "]", $dump); //End
-
-        if ($replaced) {
-            $dump = preg_replace_callback("/'##<(\d+)>##'/", function ($matches) use ($replaced) {
-                return $replaced[$matches[1]] ?? "''";
-            }, $dump);
-        }
-
-        if ($return === true) {
-            return $dump;
-        } else {
-            echo $dump;
-        }
     }
 }
 
@@ -429,7 +375,7 @@ if (!function_exists('check_cors_request')) {
      */
     function check_cors_request()
     {
-        if (isset($_SERVER['HTTP_ORIGIN']) && $_SERVER['HTTP_ORIGIN']) {
+        if (isset($_SERVER['HTTP_ORIGIN']) && $_SERVER['HTTP_ORIGIN'] && config('fastadmin.cors_request_domain')) {
             $info = parse_url($_SERVER['HTTP_ORIGIN']);
             $domainArr = explode(',', config('fastadmin.cors_request_domain'));
             $domainArr[] = request()->host(true);
@@ -504,20 +450,24 @@ if (!function_exists('check_url_allowed')) {
      * @param string $url URL
      * @return bool
      */
-    function check_url_allowed($url = null)
+    function check_url_allowed($url = '')
     {
         //允许的主机列表
         $allowedHostArr = [
             strtolower(request()->host())
         ];
 
+        if (empty($url)) {
+            return true;
+        }
+
         //如果是站内相对链接则允许
-        if (preg_match("/^[\/a-z][a-z0-9][a-z0-9\.\/]+\$/i", $url) && substr($url, 0, 2) !== '//') {
+        if (preg_match("/^[\/a-z][a-z0-9][a-z0-9\.\/]+((\?|#).*)?\$/i", $url) && substr($url, 0, 2) !== '//') {
             return true;
         }
 
         //如果是站外链接则需要判断HOST是否允许
-        if (preg_match("/((http[s]?:\/\/)+(?>[a-z\-0-9]{2,}\.){1,}[a-z]{2,8})(?:\s|\/)/i", $url)) {
+        if (preg_match("/((http[s]?:\/\/)+((?>[a-z\-0-9]{2,}\.)+[a-z]{2,8}|((?>([0-9]{1,3}\.)){3}[0-9]{1,3}))(:[0-9]{1,5})?)(?:\s|\/)/i", $url)) {
             $chkHost = parse_url(strtolower($url), PHP_URL_HOST);
             if ($chkHost && in_array($chkHost, $allowedHostArr)) {
                 return true;

+ 2 - 2
application/common/controller/Api.php

@@ -204,8 +204,8 @@ class Api
             'time' => Request::instance()->server('REQUEST_TIME'),
             'data' => $data,
         ];
-        // 如果未设置类型则自动判断
-        $type = $type ? $type : ($this->request->param(config('var_jsonp_handler')) ? 'jsonp' : $this->responseType);
+        // 如果未设置类型则使用默认类型判断
+        $type = $type ? : $this->responseType;
 
         if (isset($header['statuscode'])) {
             $code = $header['statuscode'];

+ 5 - 4
application/common/controller/Backend.php

@@ -123,10 +123,10 @@ class Backend extends Controller
         $path = str_replace('.', '/', $controllername) . '/' . $actionname;
 
         // 定义是否Addtabs请求
-        !defined('IS_ADDTABS') && define('IS_ADDTABS', input("addtabs") ? true : false);
+        !defined('IS_ADDTABS') && define('IS_ADDTABS', (bool)input("addtabs"));
 
         // 定义是否Dialog请求
-        !defined('IS_DIALOG') && define('IS_DIALOG', input("dialog") ? true : false);
+        !defined('IS_DIALOG') && define('IS_DIALOG', (bool)input("dialog"));
 
         // 定义是否AJAX请求
         !defined('IS_AJAX') && define('IS_AJAX', $this->request->isAjax());
@@ -269,7 +269,8 @@ class Backend extends Controller
         $sort = $this->request->get("sort", !empty($this->model) && $this->model->getPk() ? $this->model->getPk() : 'id');
         $order = $this->request->get("order", "DESC");
         $offset = $this->request->get("offset/d", 0);
-        $limit = $this->request->get("limit/d", 999999);
+        $limit = $this->request->get("limit/d", 0);
+        $limit = $limit ?: 999999;
         //新增自动计算页码
         $page = $limit ? intval($offset / $limit) + 1 : 1;
         if ($this->request->has("page")) {
@@ -397,7 +398,7 @@ class Backend extends Controller
                         $arr = $arr[0];
                     }
                     $tableArr = explode('.', $k);
-                    if (count($tableArr) > 1 && $tableArr[0] != $name && !in_array($tableArr[0], $alias) 
+                    if (count($tableArr) > 1 && $tableArr[0] != $name && !in_array($tableArr[0], $alias)
                         && !empty($this->model) && $this->relationSearch) {
                         //修复关联模型下时间无法搜索的BUG
                         $relation = Loader::parseName($tableArr[0], 1, false);

+ 3 - 3
application/common/library/Auth.php

@@ -164,7 +164,7 @@ class Auth
             'avatar'   => '',
         ];
         $params = array_merge($data, [
-            'nickname'  => preg_match("/^1[3-9]{1}\d{9}$/",$username) ? substr_replace($username,'****',3,4) : $username,
+            'nickname'  => preg_match("/^1[3-9]{1}\d{9}$/", $username) ? substr_replace($username, '****', 3, 4) : $username,
             'salt'      => Random::alnum(),
             'jointime'  => $time,
             'joinip'    => $ip,
@@ -356,7 +356,7 @@ class Auth
         }
         $url = ($module ? $module : request()->module()) . '/' . (is_null($path) ? $this->getRequestUri() : $path);
         $url = strtolower(str_replace('.', '/', $url));
-        return in_array($url, $rules) ? true : false;
+        return in_array($url, $rules);
     }
 
     /**
@@ -394,7 +394,7 @@ class Auth
 
     /**
      * 获取会员组别规则列表
-     * @return array
+     * @return array|bool|\PDOStatement|string|\think\Collection
      */
     public function getRuleList()
     {

+ 2 - 2
application/common/library/Menu.php

@@ -182,14 +182,14 @@ class Menu
         } else {
             $pid = $parent;
         }
-        $allow = array_flip(['file', 'name', 'title', 'url', 'icon', 'condition', 'remark', 'ismenu', 'menutype', 'extend', 'weigh']);
+        $allow = array_flip(['file', 'name', 'title', 'url', 'icon', 'condition', 'remark', 'ismenu', 'menutype', 'extend', 'weigh', 'status']);
         foreach ($newMenu as $k => $v) {
             $hasChild = isset($v['sublist']) && $v['sublist'];
             $data = array_intersect_key($v, $allow);
             $data['ismenu'] = $data['ismenu'] ?? ($hasChild ? 1 : 0);
             $data['icon'] = $data['icon'] ?? ($hasChild ? 'fa fa-list' : 'fa fa-circle-o');
             $data['pid'] = $pid;
-            $data['status'] = 'normal';
+            $data['status'] = $data['status'] ?? 'normal';
             if (!isset($oldMenu[$data['name']])) {
                 $menu = AuthRule::create($data);
             } else {

+ 4 - 6
application/common/library/Security.php

@@ -16,7 +16,6 @@ use Exception;
  */
 class Security
 {
-
     protected static $instance = null;
 
     /**
@@ -420,7 +419,7 @@ class Security
      */
     public function get_random_bytes($length)
     {
-        if (empty($length) OR !ctype_digit((string)$length)) {
+        if (empty($length) or !ctype_digit((string)$length)) {
             return false;
         }
 
@@ -485,8 +484,8 @@ class Security
 
         static $_entities;
 
-        isset($charset) OR $charset = $this->charset;
-        isset($_entities) OR $_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, ENT_COMPAT | ENT_HTML5, $charset));
+        isset($charset) or $charset = $this->charset;
+        isset($_entities) or $_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, ENT_COMPAT | ENT_HTML5, $charset));
 
         do {
             $str_compare = $str;
@@ -698,7 +697,7 @@ class Security
                     // Is it indeed an "evil" attribute?
                     preg_match($is_evil_pattern, $attribute['name'][0])
                     // Or does it have an equals sign, but no value and not quoted? Strip that too!
-                    OR (trim($attribute['value'][0]) === '')
+                    or (trim($attribute['value'][0]) === '')
                 ) {
                     $attributes[] = 'xss=removed';
                 } else {
@@ -870,5 +869,4 @@ class Security
 
         return $str;
     }
-
 }

+ 1 - 1
application/common/model/Config.php

@@ -175,7 +175,7 @@ class Config extends Model
         if (!preg_match("/^((?:[a-z]+:)?\/\/)(.*)/i", $uploadurl) && substr($uploadurl, 0, 1) !== '/') {
             $uploadurl = url($uploadurl, '', false);
         }
-        $uploadcfg['fullmode'] = isset($uploadcfg['fullmode']) && $uploadcfg['fullmode'] ? true : false;
+        $uploadcfg['fullmode'] = isset($uploadcfg['fullmode']) && $uploadcfg['fullmode'];
         $uploadcfg['thumbstyle'] = $uploadcfg['thumbstyle'] ?? '';
 
         $upload = [

+ 1 - 1
application/config.php

@@ -316,7 +316,7 @@ return [
         //允许跨域的域名,多个以,分隔
         'cors_request_domain'   => 'localhost,127.0.0.1',
         //版本号
-        'version'               => '1.4.0.20230315',
+        'version'               => '2.0.0.20240401',
         //API接口地址
         'api_url'               => 'https://api.fastadmin.net',
     ],

+ 3 - 3
application/extra/site.php

@@ -33,8 +33,8 @@ return [
     'mail_type' => '1',
     'mail_smtp_host' => 'smtp.qq.com',
     'mail_smtp_port' => '465',
-    'mail_smtp_user' => '10000',
-    'mail_smtp_pass' => 'password',
+    'mail_smtp_user' => '',
+    'mail_smtp_pass' => '',
     'mail_verify_type' => '2',
-    'mail_from' => '10000@qq.com',
+    'mail_from' => '',
 ];

+ 7 - 8
application/index/controller/User.php

@@ -66,13 +66,13 @@ class User extends Frontend
      */
     public function register()
     {
-        $url = $this->request->request('url', '', 'trim,xss_clean');
+        $url = $this->request->request('url', '', 'url_clean');
         if ($this->auth->id) {
             $this->success(__('You\'ve logged in, do not login again'), $url ? $url : url('user/index'));
         }
         if ($this->request->isPost()) {
             $username = $this->request->post('username');
-            $password = $this->request->post('password');
+            $password = $this->request->post('password', '', null);
             $email = $this->request->post('email');
             $mobile = $this->request->post('mobile', '');
             $captcha = $this->request->post('captcha');
@@ -144,13 +144,13 @@ class User extends Frontend
      */
     public function login()
     {
-        $url = $this->request->request('url', '', 'trim,xss_clean');
+        $url = $this->request->request('url', '', 'url_clean');
         if ($this->auth->id) {
             $this->success(__('You\'ve logged in, do not login again'), $url ?: url('user/index'));
         }
         if ($this->request->isPost()) {
             $account = $this->request->post('account');
-            $password = $this->request->post('password');
+            $password = $this->request->post('password', '', null);
             $keeplogin = (int)$this->request->post('keeplogin');
             $token = $this->request->post('__token__');
             $rule = [
@@ -270,9 +270,9 @@ class User extends Frontend
     public function changepwd()
     {
         if ($this->request->isPost()) {
-            $oldpassword = $this->request->post("oldpassword");
-            $newpassword = $this->request->post("newpassword");
-            $renewpassword = $this->request->post("renewpassword");
+            $oldpassword = $this->request->post("oldpassword", '', null);
+            $newpassword = $this->request->post("newpassword", '', null);
+            $renewpassword = $this->request->post("renewpassword", '', null);
             $token = $this->request->post('__token__');
             $rule = [
                 'oldpassword'   => 'require|regex:\S{6,30}',
@@ -299,7 +299,6 @@ class User extends Frontend
             $result = $validate->check($data);
             if (!$result) {
                 $this->error(__($validate->getError()), null, ['token' => $this->request->token()]);
-                return false;
             }
 
             $ret = $this->auth->changepwd($newpassword, $oldpassword);

+ 2 - 13
application/index/view/user/index.html

@@ -24,26 +24,21 @@
                         <a href="{:url('user/profile')}" class="btn btn-primary pull-right"><i class="fa fa-pencil"></i> {:__('Profile')}</a>
                     </h2>
                     <div class="row user-baseinfo">
-                        <div class="col-md-3 col-sm-3 col-xs-2 text-center user-center">
+                        <div class="col-md-3 col-sm-3 col-xs-3 text-center user-center">
                             <a href="{:url('user/profile')}" title="{:__('Click to edit')}">
                                 <span class="avatar-img"><img src="{$user.avatar|htmlentities|cdnurl}" alt=""></span>
                             </a>
                         </div>
-                        <div class="col-md-9 col-sm-9 col-xs-10">
-                            <!-- Content -->
+                        <div class="col-md-9 col-sm-9 col-xs-9">
                             <div class="ui-content">
-                                <!-- Heading -->
                                 <h4><a href="{:url('user/profile')}">{$user.nickname|htmlentities}</a></h4>
-                                <!-- Paragraph -->
                                 <p class="text-muted">
                                     {$user.bio|default=__("This guy hasn't written anything yet")|htmlentities}
                                 </p>
-                                <!-- Success -->
                             </div>
                         </div>
 
                         <div class="col-md-9 col-sm-9 col-xs-12">
-                            <!-- Content -->
                             <div class="ui-content">
                                 <div class="basicinfo">
                                     <div class="row">
@@ -57,12 +52,6 @@
                                         </div>
                                     </div>
                                     <div class="row">
-                                        <div class="col-xs-4 col-md-2">{:__('Successions')}</div>
-                                        <div class="col-xs-8 col-md-4">{$user.successions} {:__('Day')}</div>
-                                        <div class="col-xs-4 col-md-2">{:__('Maxsuccessions')}</div>
-                                        <div class="col-xs-8 col-md-4">{$user.maxsuccessions} {:__('Day')}</div>
-                                    </div>
-                                    <div class="row">
                                         <div class="col-xs-4 col-md-2">{:__('Logintime')}</div>
                                         <div class="col-xs-8 col-md-4">{$user.logintime|date="Y-m-d H:i:s",###}</div>
                                         <div class="col-xs-4 col-md-2">{:__('Prevtime')}</div>

+ 1 - 2
application/index/view/user/login.html

@@ -118,8 +118,7 @@
             </div>
         </div>
         <div class="form-group form-footer">
-            <label class="control-label col-xs-12 col-sm-3"></label>
-            <div class="col-xs-12 col-sm-8">
+            <div class="col-xs-12 col-sm-8 col-sm-offset-3">
                 <button type="submit" class="btn btn-md btn-primary">{:__('Ok')}</button>
             </div>
         </div>

+ 2 - 4
application/index/view/user/profile.html

@@ -132,8 +132,7 @@
         </div>
         <div class="form-footer">
             <div class="form-group" style="margin-bottom:0;">
-                <label class="control-label col-xs-12 col-sm-3"></label>
-                <div class="col-xs-12 col-sm-8">
+                <div class="col-xs-12 col-sm-8 col-sm-offset-3">
                     <button type="submit" class="btn btn-md btn-primary">{:__('Submit')}</button>
                 </div>
             </div>
@@ -166,8 +165,7 @@
         </div>
         <div class="form-footer">
             <div class="form-group" style="margin-bottom:0;">
-                <label class="control-label col-xs-12 col-sm-3"></label>
-                <div class="col-xs-12 col-sm-8">
+                <div class="col-xs-12 col-sm-8 col-sm-offset-3">
                     <button type="submit" class="btn btn-md btn-primary">{:__('Submit')}</button>
                 </div>
             </div>

+ 6 - 2
composer.json

@@ -21,7 +21,7 @@
         "topthink/think-installer": "^1.0.14",
         "topthink/think-queue": "1.1.6",
         "topthink/think-helper": "^1.0.7",
-        "karsonzhang/fastadmin-addons": "~1.3.2",
+        "karsonzhang/fastadmin-addons": "~1.4.0",
         "overtrue/pinyin": "^3.0",
         "phpoffice/phpspreadsheet": "1.19.0",
         "overtrue/wechat": "^4.6.0",
@@ -32,7 +32,11 @@
         "txthinking/mailer": "^2.0"
     },
     "config": {
-        "preferred-install": "dist"
+        "preferred-install": "dist",
+        "allow-plugins": {
+            "topthink/think-installer": true,
+            "easywechat-composer/easywechat-composer": true
+        }
     },
     "repositories": [
         {

+ 5 - 5
extend/fast/Http.php

@@ -60,15 +60,15 @@ class Http
             } else {
                 $defaults[CURLOPT_CUSTOMREQUEST] = $method;
             }
-            $defaults[CURLOPT_POSTFIELDS] = $params;
+            $defaults[CURLOPT_POSTFIELDS] = is_array($params) && count(array_filter($params, 'is_array')) > 0 ? $query_string : $params;
         }
 
         $defaults[CURLOPT_HEADER] = false;
         $defaults[CURLOPT_USERAGENT] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36";
         $defaults[CURLOPT_FOLLOWLOCATION] = true;
         $defaults[CURLOPT_RETURNTRANSFER] = true;
-        $defaults[CURLOPT_CONNECTTIMEOUT] = 3;
-        $defaults[CURLOPT_TIMEOUT] = 3;
+        $defaults[CURLOPT_CONNECTTIMEOUT] = 10;
+        $defaults[CURLOPT_TIMEOUT] = 10;
 
         // disable 100-continue
         curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
@@ -133,12 +133,12 @@ class Http
         }
         $parts['query'] = isset($parts['query']) && $parts['query'] ? '?' . $parts['query'] : '';
         //发送socket请求,获得连接句柄
-        $fp = fsockopen($parts['host'], $parts['port'] ?? 80, $errno, $errstr, 3);
+        $fp = fsockopen($parts['host'], $parts['port'] ?? 80, $errno, $errstr, 10);
         if (!$fp) {
             return false;
         }
         //设置超时时间
-        stream_set_timeout($fp, 3);
+        stream_set_timeout($fp, 10);
         $out = "{$method} {$parts['path']}{$parts['query']} HTTP/1.1\r\n";
         $out .= "Host: {$parts['host']}\r\n";
         $out .= "Content-Type: application/x-www-form-urlencoded\r\n";

+ 32 - 0
public/assets/css/backend.css

@@ -970,6 +970,9 @@ form.form-horizontal .control-label {
 .fixed-table-container .bs-checkbox {
   min-width: 36px;
 }
+.fixed-table-container tr[data-origpos] > td > .tooltip.in {
+  display: none !important;
+}
 /*修复nice-validator新版下的一处BUG*/
 .nice-validator input,
 .nice-validator select,
@@ -1519,6 +1522,14 @@ table.table-nowrap thead > tr > th {
   border-bottom: 1px solid #eee;
   border-radius: 0;
 }
+.sidebar-menu li.active > a > .fa-angle-left,
+.sidebar-menu li.active > a > .pull-right-container > .fa-angle-left {
+  -webkit-transform: rotate(0deg);
+  -moz-transform: rotate(0deg);
+  -o-transform: rotate(0deg);
+  -ms-transform: rotate(0deg);
+  transform: rotate(0deg);
+}
 .sidebar-menu li.treeview-open > a > .fa-angle-left,
 .sidebar-menu li.treeview-open > a > .pull-right-container > .fa-angle-left {
   -webkit-transform: rotate(-90deg);
@@ -1610,4 +1621,25 @@ table.table-nowrap thead > tr > th {
   display: block;
   border-bottom: 1px solid #ddd;
 }
+.autocontent {
+  position: relative;
+}
+.autocontent .autocontent-caret {
+  position: absolute;
+  right: 0;
+  top: 0;
+  height: 100%;
+  line-height: 1;
+  background: #eee;
+  color: #ddd;
+  vertical-align: middle;
+  padding: 0 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+}
+.autocontent .autocontent-caret:hover {
+  color: #ccc;
+}
 /*# sourceMappingURL=backend.css.map */

文件差异内容过多而无法显示
+ 1 - 1
public/assets/css/backend.min.css


+ 39 - 0
public/assets/css/frontend.css

@@ -317,6 +317,10 @@ a:focus {
     height: 60px;
     line-height: 27px;
   }
+  .navbar-white .navbar-brand {
+    height: 60px;
+    line-height: 27px;
+  }
   .navbar-white .navbar-nav > li > a {
     height: 60px;
     line-height: 27px;
@@ -652,6 +656,20 @@ form.form-horizontal .control-label {
     padding: 15px;
     min-height: 300px;
   }
+  .n-bootstrap .n-right {
+    margin-top: 0;
+    top: -20px;
+    position: absolute;
+    left: 0;
+    text-align: right;
+    width: 100%;
+  }
+  .n-bootstrap .n-right .msg-wrap {
+    position: relative;
+  }
+  .n-bootstrap .col-xs-12 > .n-right .msg-wrap {
+    margin-right: 15px;
+  }
 }
 .nav-pills > li {
   margin-right: 5px;
@@ -1094,4 +1112,25 @@ main.content {
   display: block;
   border-bottom: 1px solid #ddd;
 }
+.autocontent {
+  position: relative;
+}
+.autocontent .autocontent-caret {
+  position: absolute;
+  right: 0;
+  top: 0;
+  height: 100%;
+  line-height: 1;
+  background: #eee;
+  color: #ddd;
+  vertical-align: middle;
+  padding: 0 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+}
+.autocontent .autocontent-caret:hover {
+  color: #ccc;
+}
 /*# sourceMappingURL=frontend.css.map */

文件差异内容过多而无法显示
+ 1 - 1
public/assets/css/frontend.min.css


+ 30 - 0
public/assets/css/skins/_all-skins.css

@@ -2481,6 +2481,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-blue .sidebar-menu li.treeview.active > a,
+.skin-black-blue .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #555299;
+  border-left-color: #555299;
+}
 .skin-black-blue .sidebar-menu .treeview-menu {
   padding-left: 0;
 }
@@ -2717,6 +2722,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-purple .sidebar-menu li.treeview.active > a,
+.skin-black-purple .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #555299;
+  border-left-color: #555299;
+}
 .skin-black-purple .sidebar-menu .treeview-menu {
   padding-left: 0;
 }
@@ -2953,6 +2963,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-green .sidebar-menu li.treeview.active > a,
+.skin-black-green .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #555299;
+  border-left-color: #555299;
+}
 .skin-black-green .sidebar-menu .treeview-menu {
   padding-left: 0;
 }
@@ -3189,6 +3204,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-red .sidebar-menu li.treeview.active > a,
+.skin-black-red .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #555299;
+  border-left-color: #555299;
+}
 .skin-black-red .sidebar-menu .treeview-menu {
   padding-left: 0;
 }
@@ -3425,6 +3445,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-yellow .sidebar-menu li.treeview.active > a,
+.skin-black-yellow .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #555299;
+  border-left-color: #555299;
+}
 .skin-black-yellow .sidebar-menu .treeview-menu {
   padding-left: 0;
 }
@@ -3661,6 +3686,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-pink .sidebar-menu li.treeview.active > a,
+.skin-black-pink .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #555299;
+  border-left-color: #555299;
+}
 .skin-black-pink .sidebar-menu .treeview-menu {
   padding-left: 0;
 }

+ 5 - 0
public/assets/css/skins/skin-black-blue.css

@@ -198,6 +198,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-blue .sidebar-menu li.treeview.active > a,
+.skin-black-blue .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #181f23;
+  border-left-color: #181f23;
+}
 .skin-black-blue .sidebar-menu .treeview-menu {
   padding-left: 0;
 }

+ 5 - 0
public/assets/css/skins/skin-black-green.css

@@ -198,6 +198,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-green .sidebar-menu li.treeview.active > a,
+.skin-black-green .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #181f23;
+  border-left-color: #181f23;
+}
 .skin-black-green .sidebar-menu .treeview-menu {
   padding-left: 0;
 }

+ 5 - 0
public/assets/css/skins/skin-black-pink.css

@@ -198,6 +198,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-pink .sidebar-menu li.treeview.active > a,
+.skin-black-pink .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #181f23;
+  border-left-color: #181f23;
+}
 .skin-black-pink .sidebar-menu .treeview-menu {
   padding-left: 0;
 }

+ 5 - 0
public/assets/css/skins/skin-black-purple.css

@@ -198,6 +198,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-purple .sidebar-menu li.treeview.active > a,
+.skin-black-purple .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #181f23;
+  border-left-color: #181f23;
+}
 .skin-black-purple .sidebar-menu .treeview-menu {
   padding-left: 0;
 }

+ 5 - 0
public/assets/css/skins/skin-black-red.css

@@ -198,6 +198,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-red .sidebar-menu li.treeview.active > a,
+.skin-black-red .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #181f23;
+  border-left-color: #181f23;
+}
 .skin-black-red .sidebar-menu .treeview-menu {
   padding-left: 0;
 }

+ 5 - 0
public/assets/css/skins/skin-black-yellow.css

@@ -198,6 +198,11 @@
   background: transparent;
   border-left-color: transparent;
 }
+.skin-black-yellow .sidebar-menu li.treeview.active > a,
+.skin-black-yellow .sidebar-menu li.treeview.treeview-open > a {
+  background-color: #181f23;
+  border-left-color: #181f23;
+}
 .skin-black-yellow .sidebar-menu .treeview-menu {
   padding-left: 0;
 }

+ 1 - 1
public/assets/js/adminlte.js

@@ -468,7 +468,7 @@ function _init() {
                         //Fix the layout in case the sidebar stretches over the height of the window
                         //_this.layout.fix();
                     });
-                    checkElement.parent("li").removeClass("active");
+                    // checkElement.parent("li").removeClass("active");
                     checkElement.parent("li").removeClass('treeview-open');
                 }
                 //If the menu is not visible

+ 1 - 1
public/assets/js/backend.js

@@ -242,7 +242,7 @@ define(['fast', 'template', 'moment'], function (Fast, Template, Moment) {
             }
             //tooltip和popover
             if (!('ontouchstart' in document.documentElement)) {
-                $('body').tooltip({selector: '[data-toggle="tooltip"]'});
+                $('body').tooltip({selector: '[data-toggle="tooltip"]', trigger: 'hover'});
             }
             $('body').popover({selector: '[data-toggle="popover"]'});
         }

+ 101 - 20
public/assets/js/backend/addon.js

@@ -73,12 +73,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
             Template.helper("Moment", Moment);
             Template.helper("addons", Config['addons']);
 
-            $("#faupload-addon").data("params", function () {
+            $("#faupload-addon").data("params", function (files, xhr) {
                 var userinfo = Controller.api.userinfo.get();
                 return {
                     uid: userinfo ? userinfo.id : '',
                     token: userinfo ? userinfo.token : '',
-                    version: Config.faversion
+                    version: Config.faversion,
+                    force: (files[0].force || false) ? 1 : 0
                 };
             });
 
@@ -92,7 +93,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                         uid: userinfo ? userinfo.id : '',
                         token: userinfo ? userinfo.token : '',
                         domain: Config.domain,
-                        version: Config.faversion
+                        version: Config.faversion,
+                        sid: Controller.api.sid()
                     });
                     return params;
                 },
@@ -113,7 +115,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                             align: 'left',
                             formatter: Controller.api.formatter.title
                         },
-                        {field: 'intro', title: __('Intro'), operate: 'LIKE', align: 'left', class: 'visible-lg'},
+                        {
+                            field: 'intro',
+                            title: __('Intro'),
+                            operate: 'LIKE',
+                            align: 'left',
+                            class: 'visible-lg',
+                            formatter: Controller.api.formatter.intro
+                        },
                         {
                             field: 'author',
                             title: __('Author'),
@@ -187,7 +196,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
 
             // 离线安装
             require(['upload'], function (Upload) {
-                Upload.api.upload("#faupload-addon", function (data, ret) {
+                Upload.api.upload("#faupload-addon", function (data, ret, up, file) {
                     Config['addons'][data.addon.name] = data.addon;
                     var addon = data.addon;
                     var testdata = data.addon.testdata;
@@ -215,7 +224,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                         });
                     });
                     return false;
-                }, function (data, ret) {
+                }, function (data, ret, up, file) {
                     if (ret.msg && ret.msg.match(/(login|登录)/g)) {
                         return Layer.alert(ret.msg, {
                             title: __('Warning'),
@@ -224,6 +233,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                                 $(".btn-userinfo").trigger("click");
                             }
                         });
+                    } else if (ret.code === -1) {
+                        Layer.confirm(__('Upgrade tips', data.title), {title: __('Warmtips')}, function (index, layero) {
+                            up.removeFile(file);
+                            file.force = true;
+                            up.uploadFile(file);
+                            Layer.close(index);
+                        });
+                        return false;
                     }
                 });
 
@@ -231,10 +248,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                 $(document).on("mousedown", "#faupload-addon", function (e) {
                     var userinfo = Controller.api.userinfo.get();
                     var uid = userinfo ? userinfo.id : 0;
+                    var uploadBtn = Upload.list['faupload-addon'];
 
                     if (parseInt(uid) === 0) {
+                        uploadBtn.disable();
                         $(".btn-userinfo").trigger("click");
                         return false;
+                    } else {
+                        if (uploadBtn.disabled) {
+                            uploadBtn.enable();
+                        }
                     }
                 });
             });
@@ -299,20 +322,23 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                     Fast.api.ajax({
                         url: Config.api_url + '/user/logintpl',
                         type: 'post',
+                        loading: false,
                         data: {
-                            version: Config.faversion
+                            version: Config.faversion,
+                            sid: Controller.api.sid()
                         }
                     }, function (tpldata, ret) {
                         Layer.open({
                             content: Template.render(tpldata, {}),
                             zIndex: 99,
                             area: area,
-                            title: __('Login FastAdmin'),
+                            title: __('Login'),
                             resize: false,
                             btn: [__('Login')],
                             yes: function (index, layero) {
                                 var data = $("form", layero).serializeArray();
                                 data.push({name: "faversion", value: Config.faversion});
+                                data.push({name: "sid", value: Controller.api.sid()});
                                 Fast.api.ajax({
                                     url: Config.api_url + '/user/login',
                                     type: 'post',
@@ -345,6 +371,57 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                         url: Config.api_url + '/user/userinfotpl',
                         type: 'post',
                         data: {
+                            uid: userinfo.id,
+                            token: userinfo.token,
+                            version: Config.faversion,
+                            sid: Controller.api.sid()
+                        }
+                    }, function (tpldata, ret) {
+                        Layer.open({
+                            content: Template.render(tpldata, userinfo),
+                            area: area,
+                            title: __('Login FastAdmin'),
+                            resize: false,
+                            btn: [__('Login')],
+                            yes: function (index, layero) {
+                                var data = $("form", layero).serializeArray();
+                                data.push({name: "faversion", value: Config.faversion});
+                                Fast.api.ajax({
+                                    url: Config.api_url + '/user/logout',
+                                    data: {
+                                        uid: userinfo.id,
+                                        token: userinfo.token,
+                                        version: Config.faversion,
+                                        sid: Controller.api.sid()
+                                    }
+                                }, function (data, ret) {
+                                    Controller.api.userinfo.set(data);
+                                    Layer.closeAll();
+                                    Layer.alert(ret.msg, {title: __('Warning'), icon: 1});
+                                    return false;
+                                }, function (data, ret) {
+                                });
+                            },
+                            success: function (layero, index) {
+                                this.checkEnterKey = function (event) {
+                                    if (event.keyCode === 13) {
+                                        $(".layui-layer-btn0").trigger("click");
+                                        return false;
+                                    }
+                                };
+                                $(document).on('keydown', this.checkEnterKey);
+                            },
+                            end: function () {
+                                $(document).off('keydown', this.checkEnterKey);
+                            }
+                        });
+                        return false;
+                    });
+                } else {
+                    Fast.api.ajax({
+                        url: Config.api_url + '/user/userinfotpl',
+                        type: 'post',
+                        data: {
                             version: Config.faversion
                         }
                     }, function (tpldata, ret) {
@@ -610,16 +687,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                 var uid = userinfo ? userinfo.id : 0;
 
                 if (parseInt(uid) === 0) {
-                    return Layer.alert(__('Not login tips'), {
-                        title: __('Warning'),
-                        btn: [__('Login now')],
-                        yes: function (index, layero) {
-                            $(".btn-userinfo").trigger("click", name, version);
-                        },
-                        btn2: function () {
-                            install(name, version, false);
-                        }
-                    });
+                    $(".btn-userinfo").trigger("click", name, version);
+                    return false;
                 }
                 install(name, version, false);
             });
@@ -632,6 +701,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                     return false;
                 }
                 Template.helper("__", __);
+                tables = [];
                 Layer.confirm(Template("uninstalltpl", {addon: Config['addons'][name]}), {focusBtn: false, title: __("Warning")}, function (index, layero) {
                     uninstall(name, false, $("input[name='droptables']", layero).prop("checked"));
                 });
@@ -659,7 +729,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                 }
                 var version = $(this).data("version");
 
-                Layer.confirm(__('Upgrade tips', Config['addons'][name].title), function (index, layero) {
+                Layer.confirm(__('Upgrade tips', Config['addons'][name].title), {title: __('Warmtips')}, function (index, layero) {
                     upgrade(name, version);
                 });
             });
@@ -709,12 +779,15 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                     if ($(".btn-switch.active").data("type") == "local") {
                         // return value;
                     }
-                    var title = '<a class="title" href="' + row.url + '" data-toggle="tooltip" title="' + __('View addon home page') + '" target="_blank">' + value + '</a>';
+                    var title = '<a class="title" href="' + row.url + '" data-toggle="tooltip" title="' + __('View addon home page') + '" target="_blank"><span class="' + Fast.api.escape(row.color) + '">' + value + '</span></a>';
                     if (row.screenshots && row.screenshots.length > 0) {
                         title += ' <a href="javascript:;" data-index="' + index + '" class="view-screenshots text-success" title="' + __('View addon screenshots') + '" data-toggle="tooltip"><i class="fa fa-image"></i></a>';
                     }
                     return title;
                 },
+                intro: function (value, row, index) {
+                    return row.intro + (row.extend ? "<a href='" + Fast.api.escape(row.extend[1]) + "' class='" + Fast.api.escape(row.extend[2]) + "'>" + Fast.api.escape(row.extend[0]) + "</a>" : "");
+                },
                 operate: function (value, row, index) {
                     return Template("operatetpl", {item: row, index: index});
                 },
@@ -777,6 +850,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
                     }
                 }
             },
+            sid: function () {
+                var sid = $.cookie('fastadmin_sid');
+                if (!sid) {
+                    sid = Math.random().toString(20).substr(2, 12);
+                    $.cookie('fastadmin_sid', sid);
+                }
+                return sid;
+            },
             refresh: function (table, name) {
                 //刷新左侧边栏
                 Fast.api.refreshmenu();

+ 0 - 1
public/assets/js/backend/auth/adminlog.js

@@ -29,7 +29,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {field: 'title', title: __('Title'), operate: 'LIKE %...%', placeholder: '模糊搜索'},
                         {field: 'url', title: __('Url'), formatter: Table.api.formatter.url},
                         {field: 'ip', title: __('IP'), events: Table.api.events.ip, formatter: Table.api.formatter.search},
-                        {field: 'browser', title: __('Browser'), operate: false, formatter: Controller.api.formatter.browser},
                         {field: 'createtime', title: __('Create time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
                         {
                             field: 'operate', title: __('Operate'), table: table,

+ 9 - 6
public/assets/js/backend/index.js

@@ -33,7 +33,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
                 if (val != '') {
                     $("ul.sidebar-menu li a[addtabs]:not([href^='javascript:;'])").each(function () {
                         if ($("span:first", this).text().indexOf(val) > -1 || $(this).attr("py").indexOf(val) > -1 || $(this).attr("pinyin").indexOf(val) > -1) {
-                            html.push('<a data-url="' + $(this).attr("href") + '" href="javascript:;">' + $("span:first", this).text() + '</a>');
+                            html.push('<a data-url="' + ($(this).attr("url") || $(this).attr("href")) + '" href="javascript:;">' + $("span:first", this).text() + '</a>');
                             if (html.length >= 100) {
                                 return false;
                             }
@@ -67,6 +67,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
                 var visible = nextul.is(":visible");
                 if (nextul.length == 0) {
                     $(this).parents("li").addClass("active");
+                    $(this).closest(".treeview").addClass("treeview-open");
                 } else {
                 }
                 e.stopPropagation();
@@ -333,11 +334,13 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
 
             // 切换菜单栏
             $(document).on("click", ".sidebar-toggle", function () {
-                var value = $("body").hasClass("sidebar-collapse") ? 1 : 0;
-                setTimeout(function () {
-                    $(window).trigger("resize");
-                }, 300);
-                createCookie('sidebar_collapse', value);
+                setTimeout(function(){
+                    var value = $("body").hasClass("sidebar-collapse") ? 1 : 0;
+                    setTimeout(function () {
+                        $(window).trigger("resize");
+                    }, 300);
+                    createCookie('sidebar_collapse', value);
+                }, 0);
             });
 
             // 切换简洁模式菜单

+ 2 - 2
public/assets/js/bootstrap-table-commonsearch.js

@@ -43,7 +43,7 @@
 
             setTimeout(function () {
                 that.onCommonSearch();
-            }, 10);
+            }, 1);
         });
 
     };
@@ -61,7 +61,7 @@
         htmlForm.push('<div class="row">');
         for (var i in pColumns) {
             var vObjCol = pColumns[i];
-            if (!vObjCol.checkbox && vObjCol.field !== 'operate' && vObjCol.searchable && vObjCol.operate !== false) {
+            if (!vObjCol.checkbox && !vObjCol.radio && vObjCol.field && vObjCol.field !== 'operate' && vObjCol.searchable && vObjCol.operate !== false) {
                 var query = Fast.api.query(vObjCol.field);
                 var operate = Fast.api.query(vObjCol.field + "-operate");
 

+ 16 - 2
public/assets/js/fast.js

@@ -103,8 +103,8 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
             //获取修复后可访问的cdn链接
             cdnurl: function (url, domain) {
                 var rule = new RegExp("^((?:[a-z]+:)?\\/\\/|data:image\\/)", "i");
-                if(typeof domain === 'undefined'){
-                    var cdnurl = Config.upload.cdnurl;
+                var cdnurl = Config.upload.cdnurl;
+                if (typeof domain === 'undefined' || domain === true || cdnurl.indexOf("/") === 0) {
                     url = rule.test(url) || (cdnurl && url.indexOf(cdnurl) === 0) ? url : cdnurl + url;
                 }
                 if (domain && !rule.test(url)) {
@@ -118,6 +118,8 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
                 if (!url) {
                     url = window.location.href;
                 }
+                if (!name)
+                    return '';
                 name = name.replace(/[\[\]]/g, "\\$&");
                 var regex = new RegExp("[?&/]" + name + "([=/]([^&#/?]*)|&|#|$)"),
                     results = regex.exec(url);
@@ -276,6 +278,18 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
                     time: 2000
                 }, callback);
             },
+            escape: function (text) {
+                if (typeof text === 'string') {
+                    return text
+                        .replace(/&/g, '&amp;')
+                        .replace(/</g, '&lt;')
+                        .replace(/>/g, '&gt;')
+                        .replace(/"/g, '&quot;')
+                        .replace(/'/g, '&#039;')
+                        .replace(/`/g, '&#x60;');
+                }
+                return text;
+            },
             toastr: Toastr,
             layer: Layer
         },

+ 2 - 2
public/assets/js/frontend/user.js

@@ -27,7 +27,7 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
                 Layer.open({
                     type: 1,
                     title: __('Reset password'),
-                    area: [Math.min($(window).width(), 450) + "px", "355px"],
+                    area: [$(window).width() < 450 ? ($(window).width() - 10) + "px" : "450px", "355px"],
                     content: content,
                     success: function (layero) {
                         var rule = $("#resetpwd-form input[name='captcha']").data("rule");
@@ -98,7 +98,7 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
                 Layer.open({
                     type: 1,
                     title: "修改",
-                    area: [Math.min($(window).width(), 400) + "px", "250px"],
+                    area: [$(window).width() < 450 ? ($(window).width() - 10) + "px" : "450px", "355px"],
                     content: content,
                     success: function (layero) {
                         var form = $("form", layero);

文件差异内容过多而无法显示
+ 1 - 1
public/assets/js/jquery.drag.min.js


+ 0 - 2
public/assets/js/require-backend.js

@@ -12,8 +12,6 @@ require.config({
         'form': 'require-form',
         'table': 'require-table',
         'upload': 'require-upload',
-        'drag': 'jquery.drag.min',
-        'drop': 'jquery.drop.min',
         'dropzone': 'dropzone.min',
         'echarts': 'echarts.min',
         'echarts-theme': 'echarts-theme',

+ 55 - 18
public/assets/js/require-form.js

@@ -133,7 +133,11 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                     });
                     $(form).on("reset", function () {
                         setTimeout(function () {
-                            $('.selectpage', form).selectPageClear();
+                            $(".selectpage", form).each(function () {
+                                var selectpage = $(this).data("selectPageObject");
+                                selectpage.elem.hidden.val($(this).val());
+                                $(this).selectPageRefresh();
+                            });
                         }, 1);
                     });
                 }
@@ -321,9 +325,14 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                             var result = template ? [] : {};
                             $.each(data, function (i, j) {
                                 if (j) {
-                                    if (!template) {
-                                        if (j.key !== '') {
-                                            result['__PLACEHOLDKEY__' + j.key] = j.value;
+                                    var keys = Object.keys(j);
+                                    if (keys.indexOf("value") > -1 && (keys.length === 1 || (keys.length === 2 && keys.indexOf("key") > -1))) {
+                                        if (keys.length === 2) {
+                                            if (j.key != '') {
+                                                result['__PLACEHOLDKEY__' + j.key] = j.value;
+                                            }
+                                        } else {
+                                            result.push(j.value);
                                         }
                                     } else {
                                         result.push(j);
@@ -366,6 +375,12 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                             return obj;
                         };
                         var fieldlist = $(".fieldlist", form);
+                        //表单重置
+                        form.on("reset", function () {
+                            setTimeout(function () {
+                                fieldlist.trigger("fa.event.refreshfieldlist");
+                            });
+                        });
                         //监听文本框改变事件
                         $(document).on('change keyup changed', ".fieldlist input,.fieldlist textarea,.fieldlist select", function () {
                             var container = $(this).closest(".fieldlist");
@@ -375,7 +390,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                         fieldlist.on("click", ".btn-append,.append", function (e, row) {
                             var container = $(this).closest(".fieldlist");
                             append(container, row);
-                            // refresh(container);
+                            refresh(container);
                         });
                         //移除控制(点击按钮)
                         fieldlist.on("click", ".btn-remove", function () {
@@ -467,9 +482,9 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
 
             },
             slider: function (form) {
-                if ($(".slider", form).length > 0) {
+                if ($("[data-role='slider'],input.slider", form).length > 0) {
                     require(['bootstrap-slider'], function () {
-                        $('.slider').removeClass('hidden').css('width', function (index, value) {
+                        $("[data-role='slider'],input.slider").removeClass('hidden').css('width', function (index, value) {
                             return $(this).parents('.form-control').width();
                         }).slider().on('slide', function (ev) {
                             var data = $(this).data();
@@ -484,6 +499,11 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                 if ($("[data-role='tagsinput']", form).length > 0) {
                     require(['tagsinput', 'autocomplete'], function () {
                         $("[data-role='tagsinput']").tagsinput();
+                        form.on("reset", function () {
+                            setTimeout(function () {
+                                $("[data-role='tagsinput']").tagsinput('reset');
+                            }, 0);
+                        });
                     });
                 }
             },
@@ -504,13 +524,27 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                     var baseregex = /^([a-z0-9\_]+)([>|<|=|\!]=?)(.*)$/i, strregex = /^('|")(.*)('|")$/, regregex = /^regex:(.*)$/;
                     // @formatter:off
                     var operator_result = {
-                        '>': function(a, b) { return a > b; },
-                        '>=': function(a, b) { return a >= b; },
-                        '<': function(a, b) { return a < b; },
-                        '<=': function(a, b) { return a <= b; },
-                        '==': function(a, b) { return a == b; },
-                        '!=': function(a, b) { return a != b; },
-                        'in': function(a, b) { return b.split(/\,/).indexOf(a) > -1; },
+                        '>': function (a, b) {
+                            return a > b;
+                        },
+                        '>=': function (a, b) {
+                            return a >= b;
+                        },
+                        '<': function (a, b) {
+                            return a < b;
+                        },
+                        '<=': function (a, b) {
+                            return a <= b;
+                        },
+                        '==': function (a, b) {
+                            return a == b;
+                        },
+                        '!=': function (a, b) {
+                            return a != b;
+                        },
+                        'in': function (a, b) {
+                            return b.split(/\,/).indexOf(a) > -1;
+                        },
                         'regex': function (a, b) {
                             var regParts = b.match(/^\/(.*?)\/([gim]*)$/);
                             var regexp = regParts ? new RegExp(regParts[1], regParts[2]) : new RegExp(b);
@@ -558,10 +592,10 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                     });
                     return success === conditionArr.length;
                 };
-                form.on("keyup change click configchange", "input,select", function () {
+                form.on("keyup change click configchange", "input,textarea,select", function () {
                     $("[data-favisible][data-favisible!='']", form).each(function () {
                         var visible = $(this).data("favisible");
-                        var groupArr = visible.split(/\|\|/);
+                        var groupArr = visible ? visible.toString().split(/\|\|/) : [];
                         var success = 0;
                         $.each(groupArr, function (i, j) {
                             if (checkCondition(j)) {
@@ -578,10 +612,13 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
 
                 //追加上忽略元素
                 setTimeout(function () {
-                    form.data('validator').options.ignore += ((form.data('validator').options.ignore ? ',' : '') + '[data-favisible] :hidden,[data-favisible]:hidden');
+                    var validator = form.data('validator');
+                    if (validator) {
+                        validator.options.ignore += ((validator.options.ignore ? ',' : '') + '.hidden[data-favisible] :hidden,.hidden[data-favisible]:hidden');
+                    }
                 }, 0);
 
-                $("input,select", form).trigger("configchange");
+                $("input,textarea,select", form).trigger("configchange");
             }
         },
         api: {

+ 1 - 3
public/assets/js/require-frontend.js

@@ -6,14 +6,12 @@ require.config({
         main: 'moment'
     }],
     //在打包压缩时将会把include中的模块合并到主文件中
-    include: ['css', 'layer', 'toastr', 'fast', 'frontend', 'frontend-init', 'table', 'form', 'dragsort', 'drag', 'drop', 'selectpage'],
+    include: ['css', 'layer', 'toastr', 'fast', 'frontend', 'frontend-init', 'table', 'form', 'dragsort', 'selectpage'],
     paths: {
         'lang': "empty:",
         'form': 'require-form',
         'table': 'require-table',
         'upload': 'require-upload',
-        'drag': 'jquery.drag.min',
-        'drop': 'jquery.drop.min',
         'dropzone': 'dropzone.min',
         'echarts': 'echarts.min',
         'echarts-theme': 'echarts-theme',

+ 109 - 45
public/assets/js/require-table.js

@@ -14,7 +14,7 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
             titleForm: '', //为空则不显示标题,不定义默认显示:普通搜索
             idTable: 'commonTable',
             showExport: true,
-            exportDataType: "auto",
+            exportDataType: "auto", //支持auto,selected,all 当设定为auto时自动时有选中则导出选中,没有选中则导出全部
             exportTypes: ['json', 'xml', 'csv', 'txt', 'doc', 'excel'],
             exportOptions: {
                 fileName: 'export_' + Moment().format("YYYY-MM-DD"),
@@ -51,6 +51,7 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
             checkOnInit: true, //是否在初始化时判断
             escape: true, //是否对内容进行转义
             fixDropdownPosition: true, //是否修复下拉的定位
+            dragCheckboxMultiselect: true, //拖拽时复选框是否多选模式
             selectedIds: [],
             selectedData: [],
             extend: {
@@ -90,14 +91,14 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
                 name: 'edit',
                 icon: 'fa fa-pencil',
                 title: __('Edit'),
-                extend: 'data-toggle="tooltip"',
+                extend: 'data-toggle="tooltip" data-container="body"',
                 classname: 'btn btn-xs btn-success btn-editone'
             },
             del: {
                 name: 'del',
                 icon: 'fa fa-trash',
                 title: __('Del'),
-                extend: 'data-toggle="tooltip"',
+                extend: 'data-toggle="tooltip" data-container="body"',
                 classname: 'btn btn-xs btn-danger btn-delone'
             },
             dragsort: {
@@ -197,6 +198,8 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
                 //当刷新表格时
                 table.on('refresh.bs.table', function (e, settings, data) {
                     $(Table.config.refreshbtn, toolbar).find(".fa").addClass("fa-spin");
+                    //移除指定浮动弹窗
+                    $(".layui-layer-autocontent").remove();
                 });
                 //当执行搜索时
                 table.on('search.bs.table common-search.bs.table', function (e, settings, data) {
@@ -220,39 +223,82 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
                 table.on('post-body.bs.table', function (e, data) {
                     $(Table.config.refreshbtn, toolbar).find(".fa").removeClass("fa-spin");
                     if ($(Table.config.checkboxtd + ":first", table).find("input[type='checkbox'][data-index]").length > 0) {
-                        // 拖拽选择,需要重新绑定事件
-                        require(['drag', 'drop'], function () {
-                            var checkboxtd = $(Table.config.checkboxtd, table);
-                            checkboxtd.drag("start", function (ev, dd) {
-                                return $('<div class="selection" />').css('opacity', .65).appendTo(document.body);
-                            }).drag(function (ev, dd) {
-                                setTimeout(function () {
-                                    $(dd.proxy).css({
-                                        top: Math.min(ev.pageY, dd.startY),
-                                        left: Math.min(ev.pageX, dd.startX),
-                                        height: Math.abs(ev.pageY - dd.startY),
-                                        width: Math.abs(ev.pageX - dd.startX)
-                                    });
-                                }, 0);
-                            }).drag("end", function (ev, dd) {
-                                $(dd.proxy).remove();
-                            });
-                            checkboxtd.drop("start", function () {
-                                Table.api.toggleattr(this);
-                            }).drop(function () {
-                                // Table.api.toggleattr(this);
-                            }).drop("end", function (e) {
-                                var that = this;
-                                setTimeout(function () {
-                                    if (e.type === 'mousemove') {
-                                        Table.api.toggleattr(that);
+                        //拖拽选择复选框
+                        var posx, posy, dragdiv, drag = false, prepare = false;
+                        var mousemove = function (e) {
+                            if (drag) {
+                                var left = Math.min(e.pageX, posx);
+                                var top = Math.min(e.pageY, posy);
+                                var width = Math.abs(posx - e.pageX);
+                                var height = Math.abs(posy - e.pageY);
+                                dragdiv.css({left: left + "px", top: top + "px", width: width + "px", height: height + "px"});
+                                var dragrect = {x: left, y: top, width: width, height: height};
+                                $(Table.config.checkboxtd, table).each(function () {
+                                    var checkbox = $("input:checkbox", this);
+                                    var tdrect = this.getBoundingClientRect();
+                                    tdrect.x += document.documentElement.scrollLeft;
+                                    tdrect.y += document.documentElement.scrollTop;
+
+                                    var td_min_x = tdrect.x;
+                                    var td_min_y = tdrect.y;
+                                    var td_max_x = tdrect.x + tdrect.width;
+                                    var td_max_y = tdrect.y + tdrect.height;
+
+                                    var drag_min_x = dragrect.x;
+                                    var drag_min_y = dragrect.y;
+                                    var drag_max_x = dragrect.x + dragrect.width;
+                                    var drag_max_y = dragrect.y + dragrect.height;
+                                    var overlapped = td_min_x <= drag_max_x && td_max_x >= drag_min_x && td_min_y <= drag_max_y && td_max_y >= drag_min_y;
+                                    if (overlapped) {
+                                        if (!$(this).hasClass("overlaped")) {
+                                            $(this).addClass("overlaped");
+                                            checkbox.trigger("click");
+                                        }
+                                    } else {
+                                        if ($(this).hasClass("overlaped")) {
+                                            $(this).removeClass("overlaped");
+                                            checkbox.trigger("click");
+                                        }
                                     }
-                                }, 0);
-                            });
-                            $.drop({
-                                multi: true
-                            });
+                                });
+                            }
+                        };
+                        var selectstart = function () {
+                            return false;
+                        };
+                        var mouseup = function () {
+                            if (drag) {
+                                $(document).off("mousemove", mousemove);
+                                $(document).off("selectstart", selectstart);
+                                dragdiv.remove();
+                            }
+                            drag = false;
+                            prepare = false;
+                            $(document.body).css({'MozUserSelect': '', 'webkitUserSelect': ''}).attr('unselectable', 'off');
+                        };
+
+                        $(Table.config.checkboxtd, table).on("mousedown", function (e) {
+                            //禁止鼠标右键事件和文本框
+                            if (e.button === 2 || $(e.target).is("input")) {
+                                return false;
+                            }
+                            posx = e.pageX;
+                            posy = e.pageY;
+                            prepare = true;
+                        }).on("mousemove", function (e) {
+                            if (prepare && !drag) {
+                                drag = true;
+                                dragdiv = $("<div />");
+                                dragdiv.css({position: 'absolute', width: 0, height: 0, border: "1px dashed blue", background: "#0029ff", left: e.pageX + "px", top: e.pageY + "px", opacity: .1});
+                                dragdiv.appendTo(document.body);
+                                $(document.body).css({'MozUserSelect': 'none', 'webkitUserSelect': 'none'}).attr('unselectable', 'on');
+                                $(document).on("mousemove", mousemove).on("mouseup", mouseup).on("selectstart", selectstart);
+                                if (options.dragCheckboxMultiselect) {
+                                    $(Table.config.checkboxtd, table).removeClass("overlaped");
+                                }
+                            }
                         });
+
                     }
                 });
                 var exportDataType = options.exportDataType;
@@ -282,23 +328,40 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
                         options.selectedIds = selectedIds;
                         options.selectedData = selectedData;
                     }
+
                     //如果导出类型为auto时则自动判断
                     if (exportDataType === 'auto') {
                         options.exportDataType = selectedIds.length > 0 ? 'selected' : 'all';
+                        if ($(".export .exporttips").length === 0) {
+                            $(".export .dropdown-menu").prepend("<li class='exporttips alert alert-warning-light mb-0 no-border p-2'></li>")
+                        }
+                        $(".export .exporttips").html("导出记录:" + (selectedIds.length > 0 ? "选中" : "全部"));
+
                     }
                     $(Table.config.disabledbtn, toolbar).toggleClass('disabled', !options.selectedIds.length);
                 });
+                // 提交通用搜索时判断是否和Tabs筛选一致
+                table.on('common-search.bs.table', function (e, setting, query) {
+                    var tabs = $('.panel-heading [data-field]', table.closest(".panel-intro"));
+                    var field = tabs.data("field");
+                    var value = $("li.active > a", tabs).data("value");
+                    if (query.filter && typeof query.filter[field] !== 'undefined' && query.filter[field] != value) {
+                        $("li", tabs).removeClass("active");
+                        $("li > a[data-value='" + query.filter[field] + "']", tabs).parent().addClass("active");
+                    }
+                });
                 // 绑定TAB事件
                 $('.panel-heading [data-field] a[data-toggle="tab"]', table.closest(".panel-intro")).on('shown.bs.tab', function (e) {
                     var field = $(this).closest("[data-field]").data("field");
                     var value = $(this).data("value");
                     var object = $("[name='" + field + "']", table.closest(".bootstrap-table").find(".commonsearch-table"));
-                    if (object.prop('tagName') == "SELECT") {
+                    if (object.prop('tagName') === "SELECT") {
                         $("option[value='" + value + "']", object).prop("selected", true);
                     } else {
                         object.val(value);
                     }
                     table.trigger("uncheckbox");
+                    table.bootstrapTable('getOptions').totalRows = 0;
                     table.bootstrapTable('refresh', {pageNumber: 1});
                     return false;
                 });
@@ -677,25 +740,25 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
                 images: function (value, row, index) {
                     value = value == null || value.length === 0 ? '' : value.toString();
                     var classname = typeof this.classname !== 'undefined' ? this.classname : 'img-sm img-center';
-                    var arr = value !== '' ? value.split(',') : [];
+                    var arr = value !== '' ? (value.indexOf('data:image/') === -1 ? value.split(',') : [value]) : [];
                     var html = [];
                     var url;
                     $.each(arr, function (i, value) {
                         value = value ? value : '/assets/img/blank.gif';
                         url = Fast.api.cdnurl(value, true);
-
-                        url = url.match(/^(\/|data:image\\)/) ? url : url + Config.upload.thumbstyle;
-                        html.push('<a href="javascript:"><img class="' + classname + '" src="' + url + '" width="30" height="30" /></a>');
+                        //匹配本地、data:image、或已包含标识符首字符
+                        url = !Config.upload.thumbstyle || url.match(/^(\/|data:image\/)/) || url.indexOf(Config.upload.thumbstyle.substring(0, 1)) > -1 ? url : url + Config.upload.thumbstyle;
+                        html.push('<a href="javascript:"><img class="' + classname + '" src="' + url + '" /></a>');
                     });
                     return html.join(' ');
                 },
                 file: function (value, row, index) {
-                    return Table.api.formatter.files.call(this, value, row, index);
+                    Table.api.formatter.files.call(this, value, row, index);
                 },
                 files: function (value, row, index) {
                     value = value == null || value.length === 0 ? '' : value.toString();
                     var classname = typeof this.classname !== 'undefined' ? this.classname : 'img-sm img-center';
-                    var arr = value !== '' ? value.split(',') : [];
+                    var arr = value !== '' ? (value.indexOf('data:image/') === -1 ? value.split(',') : [value]) : [];
                     var html = [];
                     var suffix, url;
                     $.each(arr, function (i, value) {
@@ -708,8 +771,9 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
                     return html.join(' ');
                 },
                 content: function (value, row, index) {
-                    var width = this.width != undefined ? (this.width.match(/^\d+$/) ? this.width + "px" : this.width) : "250px";
-                    return "<div style='white-space: nowrap; text-overflow:ellipsis; overflow: hidden; max-width:" + width + ";'>" + value + "</div>";
+                    var width = this.width != undefined ? (this.width.toString().match(/^\d+$/) ? this.width + "px" : this.width) : "250px";
+                    var hover = this.hover != undefined && this.hover ? "autocontent-hover" : "";
+                    return "<div class='autocontent-item " + hover + "' style='white-space: nowrap; text-overflow:ellipsis; overflow: hidden; max-width:" + width + ";'>" + value + "</div>";
                 },
                 status: function (value, row, index) {
                     var custom = {normal: 'success', hidden: 'gray', deleted: 'danger', locked: 'info'};
@@ -932,7 +996,7 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
                     $.each(dropdowns, function (i, j) {
                         dropdownHtml.push('<div class="btn-group"><button type="button" class="btn btn-primary dropdown-toggle btn-xs" data-toggle="dropdown">' + i + '</button><button type="button" class="btn btn-primary dropdown-toggle btn-xs" data-toggle="dropdown"><span class="caret"></span></button><ul class="dropdown-menu dropdown-menu-right"><li>' + j.join('</li><li>') + '</li></ul></div>');
                     });
-                    html.unshift(dropdownHtml);
+                    html.unshift(dropdownHtml.join(' '));
                 }
                 return html.join(' ');
             },
@@ -943,7 +1007,7 @@ define(['jquery', 'bootstrap'], function ($, undefined) {
                 row.ids = ids ? ids : (typeof row.ids !== 'undefined' ? row.ids : 0);
                 url = url == null || url.length === 0 ? '' : url.toString();
                 //自动添加ids参数
-                url = !url.match(/(?=([?&]ids=)|(\/ids\/)|(\{ids}))/i) ? 
+                url = !url.match(/(?=([?&]ids=)|(\/ids\/)|(\{ids}))/i) ?
                     url + (url.match(/(\?|&)+/) ? "&ids=" : "/ids/") + '{ids}' : url;
                 url = url.replace(/\{(.*?)\}/gi, function (matched) {
                     matched = matched.substring(1, matched.length - 1);

+ 9 - 4
public/assets/js/require-upload.js

@@ -38,7 +38,7 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                                 onDomUploadSuccess = Upload.api.custom[onDomUploadSuccess];
                             }
                             if (typeof onDomUploadSuccess === 'function') {
-                                var result = onDomUploadSuccess.call(button, data, ret);
+                                var result = onDomUploadSuccess.call(button, data, ret, up, file);
                                 if (result === false)
                                     return;
                             }
@@ -46,7 +46,7 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                     }
 
                     if (typeof onUploadSuccess === 'function') {
-                        var result = onUploadSuccess.call(button, data, ret);
+                        var result = onUploadSuccess.call(button, data, ret, up, file);
                         if (result === false)
                             return;
                     }
@@ -63,7 +63,7 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                                 onDomUploadError = Upload.api.custom[onDomUploadError];
                             }
                             if (typeof onDomUploadError === 'function') {
-                                var result = onDomUploadError.call(button, data, ret);
+                                var result = onDomUploadError.call(button, data, ret, up, file);
                                 if (result === false)
                                     return;
                             }
@@ -71,7 +71,7 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                     }
 
                     if (typeof onUploadError === 'function') {
-                        var result = onUploadError.call(button, data, ret);
+                        var result = onUploadError.call(button, data, ret, up, file);
                         if (result === false) {
                             return;
                         }
@@ -398,6 +398,11 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                             });
                         }
                         if (input_id) {
+                            $("#" + input_id).closest("form").on("reset", function () {
+                                setTimeout($.proxy(function () {
+                                    $("#" + input_id, this).trigger("change");
+                                }, this), 0);
+                            });
                             //粘贴上传、拖拽上传
                             $("body").on('paste drop', "#" + input_id, function (event) {
                                 var originEvent = event.originalEvent;

文件差异内容过多而无法显示
+ 1 - 2
public/assets/js/require.min.js


+ 9 - 0
public/assets/js/tagsinput.js

@@ -274,6 +274,15 @@
         },
 
         /**
+         * reset the items
+         */
+        reset: function () {
+            var val = this.$element.val();
+            this.removeAll();
+            this.add(val);
+        },
+
+        /**
          * Returns the items added as tags
          */
         items: function () {

+ 33 - 0
public/assets/less/backend.less

@@ -888,6 +888,11 @@ form.form-horizontal .control-label {
     .bs-checkbox {
         min-width: 36px;
     }
+
+    //拖拽时隐藏tooltip,避免出现错位
+    tr[data-origpos] > td > .tooltip.in {
+        display: none !important;
+    }
 }
 
 /*修复nice-validator新版下的一处BUG*/
@@ -1547,6 +1552,10 @@ table.table-nowrap {
     }
 }
 
+.sidebar-menu li.active > a > .fa-angle-left, .sidebar-menu li.active > a > .pull-right-container > .fa-angle-left {
+    .rotate(0deg);
+}
+
 .sidebar-menu li.treeview-open > a > .fa-angle-left, .sidebar-menu li.treeview-open > a > .pull-right-container > .fa-angle-left {
     .rotate(-90deg);
 }
@@ -1658,3 +1667,27 @@ table.table-nowrap {
 
     }
 }
+
+.autocontent {
+    position: relative;
+
+    .autocontent-caret {
+        position: absolute;
+        right: 0;
+        top: 0;
+        height: 100%;
+        line-height: 1;
+        background: #eee;
+        color: #ddd;
+        vertical-align: middle;
+        padding: 0 5px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        cursor: pointer;
+
+        &:hover {
+            color: #ccc;
+        }
+    }
+}

+ 51 - 0
public/assets/less/frontend.less

@@ -87,6 +87,12 @@ a {
         }
     }
 
+    .navbar-white {
+        .navbar-brand {
+            height: 60px;
+            line-height: 27px;
+        }
+    }
     .navbar-white .navbar-nav {
         > li > a {
             height: 60px;
@@ -475,6 +481,27 @@ form.form-horizontal .control-label {
         padding: 15px;
         min-height: 300px;
     }
+
+    .n-bootstrap {
+        .n-right {
+            margin-top: 0;
+            top: -20px;
+            position: absolute;
+            left: 0;
+            text-align: right;
+            width: 100%;
+
+            .msg-wrap {
+                position: relative;
+            }
+        }
+
+        .col-xs-12 > .n-right {
+            .msg-wrap {
+                margin-right: 15px;
+            }
+        }
+    }
 }
 
 .nav-pills > li {
@@ -948,3 +975,27 @@ main.content {
 
     }
 }
+
+.autocontent {
+    position: relative;
+
+    .autocontent-caret {
+        position: absolute;
+        right: 0;
+        top: 0;
+        height: 100%;
+        line-height: 1;
+        background: #eee;
+        color: #ddd;
+        vertical-align: middle;
+        padding: 0 5px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        cursor: pointer;
+
+        &:hover {
+            color: #ccc;
+        }
+    }
+}

+ 7 - 0
public/assets/less/skins/skin-black-blue.less

@@ -99,6 +99,13 @@
             border-left-color: transparent;
         }
 
+        li.treeview {
+            &.active > a,&.treeview-open > a {
+                background-color: @sidebar-dark-submenu-bg;
+                border-left-color: @sidebar-dark-submenu-bg;
+            }
+        }
+
         .treeview-menu {
             padding-left: 0;
 

+ 7 - 0
public/assets/less/skins/skin-black-green.less

@@ -99,6 +99,13 @@
             border-left-color: transparent;
         }
 
+        li.treeview {
+            &.active > a,&.treeview-open > a {
+                background-color: @sidebar-dark-submenu-bg;
+                border-left-color: @sidebar-dark-submenu-bg;
+            }
+        }
+
         .treeview-menu {
             padding-left: 0;
 

+ 7 - 0
public/assets/less/skins/skin-black-pink.less

@@ -99,6 +99,13 @@
             border-left-color: transparent;
         }
 
+        li.treeview {
+            &.active > a,&.treeview-open > a {
+                background-color: @sidebar-dark-submenu-bg;
+                border-left-color: @sidebar-dark-submenu-bg;
+            }
+        }
+
         .treeview-menu {
             padding-left: 0;
 

+ 7 - 0
public/assets/less/skins/skin-black-purple.less

@@ -99,6 +99,13 @@
             border-left-color: transparent;
         }
 
+        li.treeview {
+            &.active > a,&.treeview-open > a {
+                background-color: @sidebar-dark-submenu-bg;
+                border-left-color: @sidebar-dark-submenu-bg;
+            }
+        }
+
         .treeview-menu {
             padding-left: 0;
 

+ 7 - 0
public/assets/less/skins/skin-black-red.less

@@ -99,6 +99,13 @@
             border-left-color: transparent;
         }
 
+        li.treeview {
+            &.active > a,&.treeview-open > a {
+                background-color: @sidebar-dark-submenu-bg;
+                border-left-color: @sidebar-dark-submenu-bg;
+            }
+        }
+
         .treeview-menu {
             padding-left: 0;
 

+ 7 - 0
public/assets/less/skins/skin-black-yellow.less

@@ -99,6 +99,13 @@
             border-left-color: transparent;
         }
 
+        li.treeview {
+            &.active > a,&.treeview-open > a {
+                background-color: @sidebar-dark-submenu-bg;
+                border-left-color: @sidebar-dark-submenu-bg;
+            }
+        }
+
         .treeview-menu {
             padding-left: 0;