Browse Source

新增一对多CRUD功能

新增一键生成tagsinput组件
新增autocomplete和tagsinput前端组件
新增配置全局启用上传时使用完整URL功能
新增附件列表上传归类
新增后台管理布局个性化配置
新增插件安装成功导入测试数据提示
新增后缀生成icon缓存
新增全局列表图片缩略图样式
优化插件安装后自动刷新JS缓存
优化后台管理左侧菜单展现方式
优化全局样式
优化fieldlist组件
优化上传组件归类功能
优化Bootstrap-table固定列高度
优化Layer弹窗焦点框
Karson 3 years ago
parent
commit
a7317ec08c
100 changed files with 7388 additions and 1420 deletions
  1. 1 1
      application/admin/command/Api/library/Builder.php
  2. 130 23
      application/admin/command/Crud.php
  3. 3 6
      application/admin/command/Crud/stubs/controller.stub
  4. 3 3
      application/admin/command/Crud/stubs/index.stub
  5. 4 0
      application/admin/command/Crud/stubs/mixins/import.stub
  6. 5 0
      application/admin/command/Crud/stubs/mixins/modelrelationmethod-hasmany.stub
  7. 0 30
      application/admin/common.php
  8. 181 28
      application/admin/controller/Addon.php
  9. 17 9
      application/admin/controller/Ajax.php
  10. 10 0
      application/admin/controller/Index.php
  11. 1 1
      application/admin/controller/auth/Rule.php
  12. 6 0
      application/admin/controller/general/Config.php
  13. 2 2
      application/admin/lang/zh-cn.php
  14. 16 4
      application/admin/lang/zh-cn/addon.php
  15. 78 77
      application/admin/lang/zh-cn/general/config.php
  16. 6 2
      application/admin/lang/zh-cn/index.php
  17. 1 1
      application/admin/validate/AuthRule.php
  18. 1 1
      application/admin/view/addon/config.html
  19. 10 18
      application/admin/view/addon/index.html
  20. 2 2
      application/admin/view/auth/admin/add.html
  21. 2 2
      application/admin/view/auth/admin/edit.html
  22. 1 1
      application/admin/view/auth/group/add.html
  23. 1 1
      application/admin/view/auth/group/edit.html
  24. 8 7
      application/admin/view/auth/rule/add.html
  25. 9 8
      application/admin/view/auth/rule/edit.html
  26. 1 1
      application/admin/view/category/add.html
  27. 1 1
      application/admin/view/category/edit.html
  28. 9 2
      application/admin/view/common/control.html
  29. 2 2
      application/admin/view/common/menu.html
  30. 13 12
      application/admin/view/dashboard/index.html
  31. 24 13
      application/admin/view/general/attachment/add.html
  32. 1 1
      application/admin/view/general/attachment/edit.html
  33. 19 4
      application/admin/view/general/config/index.html
  34. 1 1
      application/admin/view/index/index.html
  35. 1 1
      application/admin/view/user/group/add.html
  36. 1 1
      application/admin/view/user/group/edit.html
  37. 1 1
      application/admin/view/user/rule/add.html
  38. 1 1
      application/admin/view/user/rule/edit.html
  39. 1 1
      application/admin/view/user/user/edit.html
  40. 32 1
      application/common.php
  41. 2 4
      application/common/library/Auth.php
  42. 3 3
      application/common/library/Email.php
  43. 14 12
      application/common/library/Token.php
  44. 53 13
      application/common/library/Upload.php
  45. 4 0
      application/common/model/Attachment.php
  46. 15 11
      application/common/model/Config.php
  47. 1 1
      application/common/view/tpl/dispatch_jump.tpl
  48. 1 1
      application/common/view/tpl/think_exception.tpl
  49. 9 1
      application/extra/upload.php
  50. 26 8
      application/index/controller/Ajax.php
  51. 4 0
      application/index/controller/User.php
  52. 6 6
      application/index/view/common/captcha.html
  53. 4 3
      application/index/view/layout/default.html
  54. 2 2
      application/index/view/user/changepwd.html
  55. 3 6
      application/index/view/user/index.html
  56. 5 4
      application/index/view/user/login.html
  57. 3 3
      application/index/view/user/profile.html
  58. 5 4
      application/index/view/user/register.html
  59. 353 27
      public/assets/css/backend.css
  60. 1 1
      public/assets/css/backend.min.css
  61. 239 239
      public/assets/css/bootstrap.css
  62. 161 144
      public/assets/css/fastadmin.css
  63. 411 17
      public/assets/css/frontend.css
  64. 1 1
      public/assets/css/frontend.min.css
  65. 0 1
      public/assets/css/lesshat.css
  66. 129 93
      public/assets/css/skins/_all-skins.css
  67. 0 6
      public/assets/css/skins/skin-black-blue.css
  68. 0 6
      public/assets/css/skins/skin-black-green.css
  69. 9 3
      public/assets/css/skins/skin-black-light.css
  70. 0 6
      public/assets/css/skins/skin-black-pink.css
  71. 0 6
      public/assets/css/skins/skin-black-purple.css
  72. 6 12
      public/assets/css/skins/skin-black-red.css
  73. 0 6
      public/assets/css/skins/skin-black-yellow.css
  74. 9 3
      public/assets/css/skins/skin-black.css
  75. 9 3
      public/assets/css/skins/skin-blue-light.css
  76. 9 3
      public/assets/css/skins/skin-blue.css
  77. 9 3
      public/assets/css/skins/skin-green-light.css
  78. 9 3
      public/assets/css/skins/skin-green.css
  79. 9 3
      public/assets/css/skins/skin-purple-light.css
  80. 9 3
      public/assets/css/skins/skin-purple.css
  81. 19 13
      public/assets/css/skins/skin-red-light.css
  82. 20 14
      public/assets/css/skins/skin-red.css
  83. 9 3
      public/assets/css/skins/skin-yellow-light.css
  84. 9 3
      public/assets/css/skins/skin-yellow.css
  85. 209 0
      public/assets/css/tinycss.css
  86. 59 7
      public/assets/js/adminlte.js
  87. 1032 0
      public/assets/js/autocomplete.js
  88. 151 58
      public/assets/js/backend/addon.js
  89. 4 1
      public/assets/js/backend/auth/rule.js
  90. 19 3
      public/assets/js/backend/general/attachment.js
  91. 2 2
      public/assets/js/backend/general/profile.js
  92. 105 79
      public/assets/js/backend/index.js
  93. 1 1
      public/assets/js/bootstrap-table-commonsearch.js
  94. 12 7
      public/assets/js/frontend/user.js
  95. 3347 181
      public/assets/js/require-backend.min.js
  96. 99 45
      public/assets/js/require-form.js
  97. 125 57
      public/assets/js/require-frontend.min.js
  98. 6 3
      public/assets/js/require-table.js
  99. 21 2
      public/assets/js/require-upload.js
  100. 0 0
      public/assets/js/tagsinput.js

+ 1 - 1
application/admin/command/Api/library/Builder.php

@@ -7,7 +7,7 @@ use think\Config;
 /**
  * @website https://github.com/calinrada/php-apidoc
  * @author  Calin Rada <rada.calin@gmail.com>
- * @author  Karson <karsonzhang@163.com>
+ * @author  Karson <karson@fastadmin.net>
  */
 class Builder
 {

+ 130 - 23
application/admin/command/Crud.php

@@ -11,6 +11,7 @@ use think\console\Output;
 use think\Db;
 use think\Exception;
 use think\exception\ErrorException;
+use think\exception\PDOException;
 use think\Lang;
 use think\Loader;
 
@@ -254,12 +255,14 @@ class Crud extends Command
             ->addOption('fields', 'i', Option::VALUE_OPTIONAL, 'model visible fields', null)
             ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override or force delete,without tips', null)
             ->addOption('local', 'l', Option::VALUE_OPTIONAL, 'local model', 1)
+            ->addOption('import', 'a', Option::VALUE_OPTIONAL, 'enable import function', 0)
             ->addOption('relation', 'r', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation table name without prefix', null)
             ->addOption('relationmodel', 'e', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation model name', null)
             ->addOption('relationforeignkey', 'k', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation foreign key', null)
             ->addOption('relationprimarykey', 'p', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation primary key', null)
             ->addOption('relationfields', 's', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation table fields', null)
-            ->addOption('relationmode', 'o', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation table mode,hasone or belongsto', null)
+            ->addOption('relationmode', 'o', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation table mode,hasone/belongsto/hasmany', null)
+            ->addOption('relationcontroller', 'w', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation table controller,only work at hasmany mode', null)
             ->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete all files generated by CRUD', null)
             ->addOption('menu', 'u', Option::VALUE_OPTIONAL, 'create menu when CRUD completed', null)
             ->addOption('setcheckboxsuffix', null, Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'automatically generate checkbox component with suffix', null)
@@ -303,6 +306,8 @@ class Crud extends Command
         $force = $input->getOption('force');
         //是否为本地model,为0时表示为全局model将会把model放在app/common/model中
         $local = $input->getOption('local');
+        //是否启用导入功能
+        $import = $input->getOption('import');
 
         if (!$table) {
             throw new Exception('table name can\'t empty');
@@ -323,6 +328,8 @@ class Crud extends Command
         $relationPrimaryKey = $input->getOption('relationprimarykey');
         //关联表显示字段
         $relationFields = $input->getOption('relationfields');
+        //关联表显示字段
+        $relationController = $input->getOption('relationcontroller');
         //复选框后缀
         $setcheckboxsuffix = $input->getOption('setcheckboxsuffix');
         //单选框后缀
@@ -482,8 +489,10 @@ class Crud extends Command
                     'relationFields'        => isset($relationFields[$index]) ? explode(',', $relationFields[$index]) : [],
                     //关联模式
                     'relationMode'          => isset($relationMode[$index]) ? $relationMode[$index] : 'belongsto',
+                    //关联模型控制器
+                    'relationController'    => isset($relationController[$index]) ? $relationController[$index] : '',
                     //关联表外键
-                    'relationForeignKey'    => isset($relationForeignKey[$index]) ? $relationForeignKey[$index] : Loader::parseName($relationName) . '_id',
+                    'relationForeignKey'    => isset($relationForeignKey[$index]) ? $relationForeignKey[$index] : '',
                     //关联表主键
                     'relationPrimaryKey'    => isset($relationPrimaryKey[$index]) ? $relationPrimaryKey[$index] : '',
                 ];
@@ -506,7 +515,8 @@ class Crud extends Command
         $baseFileName = Loader::parseName(array_pop($baseNameArr), 0);
         array_push($baseNameArr, $baseFileName);
         $controllerBaseName = strtolower(implode(DS, $baseNameArr));
-        $controllerUrl = strtolower(implode('/', $baseNameArr));
+        //$controllerUrl = strtolower(implode('/', $baseNameArr));
+        $controllerUrl = $this->getControllerUrl($moduleName, $baseNameArr);
 
         //视图文件
         $viewArr = $controllerArr;
@@ -630,6 +640,7 @@ class Crud extends Command
         $editList = [];
         $javascriptList = [];
         $langList = [];
+        $operateButtonList = [];
         $field = 'id';
         $order = 'id';
         $priDefined = false;
@@ -662,7 +673,7 @@ class Crud extends Command
                 if (!in_array($relationPrimaryKey, $fieldArr)) {
                     throw new Exception('table [' . $modelTableName . '] must be contain field [' . $relationPrimaryKey . ']');
                 }
-            } else {
+            } elseif ($relation['relationMode'] == 'belongsto') {
                 $relationForeignKey = $relation['relationForeignKey'] ? $relation['relationForeignKey'] : Loader::parseName($relation['relationName']) . "_id";
                 $relationPrimaryKey = $relation['relationPrimaryKey'] ? $relation['relationPrimaryKey'] : $relation['relationPriKey'];
                 if (!in_array($relationForeignKey, $fieldArr)) {
@@ -671,6 +682,17 @@ class Crud extends Command
                 if (!in_array($relationPrimaryKey, $relation['relationFieldList'])) {
                     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;
+                if (!in_array($relationForeignKey, $relation['relationFieldList'])) {
+                    throw new Exception('relation table [' . $relation['relationTableName'] . '] must be contain field [' . $relationForeignKey . ']');
+                }
+                if (!in_array($relationPrimaryKey, $fieldArr)) {
+                    throw new Exception('table [' . $modelTableName . '] must be contain field [' . $relationPrimaryKey . ']');
+                }
+                $relation['relationColumnList'] = [];
+                $relation['relationFieldList'] = [];
             }
             $relation['relationForeignKey'] = $relationForeignKey;
             $relation['relationPrimaryKey'] = $relationPrimaryKey;
@@ -686,8 +708,15 @@ class Crud extends Command
             $appendAttrList = [];
             $controllerAssignList = [];
             $headingHtml = '{:build_heading()}';
+            $controllerImport = '';
+            $importHtml = '';
             $recyclebinHtml = '';
 
+            if ($import) {
+                $controllerImport = $this->getReplacedStub('mixins/import', []);
+                $importHtml = '<a href="javascript:;" class="btn btn-danger btn-import {:$auth->check(\'' . $controllerUrl . '/import\')?\'\':\'hide\'}" title="{:__(\'Import\')}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="fa fa-upload"></i> {:__(\'Import\')}</a>';
+            }
+
             //循环所有字段,开始构造视图的HTML和JS信息
             foreach ($columnList as $k => $v) {
                 $field = $v['COLUMN_NAME'];
@@ -867,7 +896,9 @@ class Crud extends Command
                             $defaultValue = '';
                             $attrArr['data-rule'] = 'required';
                             $cssClassArr[] = 'selectpage';
-                            $selectpageController = str_replace('_', '/', substr($field, 0, strripos($field, '_')));
+                            $selectpageTable = substr($field, 0, strripos($field, '_'));
+                            $selectpageField = '';
+                            $selectpageController = str_replace('_', '/', $selectpageTable);
                             $attrArr['data-source'] = $selectpageController . "/index";
                             //如果是类型表需要特殊处理下
                             if ($selectpageController == 'category') {
@@ -883,10 +914,27 @@ class Crud extends Command
                             if ($this->isMatchSuffix($field, $this->selectpagesSuffix)) {
                                 $attrArr['data-multiple'] = 'true';
                             }
-                            foreach ($this->fieldSelectpageMap as $m => $n) {
-                                if (in_array($field, $n)) {
-                                    $attrArr['data-field'] = $m;
-                                    break;
+
+                            $tableInfo = null;
+                            try {
+                                $tableInfo = \think\Db::name($selectpageTable)->getTableInfo();
+                                if (isset($tableInfo['fields'])) {
+                                    foreach ($tableInfo['fields'] as $m => $n) {
+                                        if (in_array($n, ['nickname', 'title', 'name'])) {
+                                            $selectpageField = $n;
+                                            break;
+                                        }
+                                    }
+                                }
+                            } catch (\Exception $e) {
+
+                            }
+                            if (!$selectpageField) {
+                                foreach ($this->fieldSelectpageMap as $m => $n) {
+                                    if (in_array($field, $n)) {
+                                        $attrArr['data-field'] = $m;
+                                        break;
+                                    }
                                 }
                             }
                         }
@@ -948,6 +996,33 @@ class Crud extends Command
 
             //循环关联表,追加语言包和JS列
             foreach ($relations as $index => $relation) {
+                if ($relation['relationMode'] == 'hasmany') {
+                    $relationFieldText = ucfirst(strtolower($relation['relationName'])) . ' List';
+                    // 语言列表
+                    if ($relation['relationTableInfo']['Comment']) {
+                        $langList[] = $this->getLangItem($relationFieldText, rtrim($relation['relationTableInfo']['Comment'], "表") . "列表");
+                    }
+
+                    $relationTableName = $relation['relationTableName'];
+                    $relationTableName = stripos($relationTableName, $prefix) === 0 ? substr($relationTableName, strlen($prefix)) : $relationTableName;
+
+                    list($realtionControllerNamespace, $realtionControllerName, $realtionControllerFile, $realtionControllerArr) = $this->getControllerData($moduleName, $relation['relationController'], $relationTableName);
+                    $realtionControllerArr = array_map("strtolower", $realtionControllerArr);
+                    if (count($realtionControllerArr) > 1) {
+                        $realtionControllerArr = [implode('.', $realtionControllerArr)];
+                    }
+                    $realtionControllerArr[] = 'index';
+                    $realtionControllerArr[] = $relation['relationForeignKey'] . '/{ids}';
+                    $relationControllerUrl = implode('/', $realtionControllerArr);
+
+                    //构造JS列信息
+                    $operateButtonList[] = "{name: 'addtabs',title: __('{$relationFieldText}'),text: __('{$relationFieldText}'),classname: 'btn btn-xs btn-info btn-dialog',icon: 'fa fa-list',url: '" . $relationControllerUrl . "'}";
+                    //echo "php think crud -t {$relation['relationTableName']} -c {$relation['relationController']} -m {$relation['relationModel']} -i " . implode(',', $relation['relationFields']);
+                    //不存在关联表控制器的情况下才进行生成
+                    if (!is_file($realtionControllerFile)) {
+                        exec("php think crud -t {$relation['relationTableName']} -c {$relation['relationController']} -m {$relation['relationModel']} -i " . implode(',', $relation['relationFields']));
+                    }
+                }
                 foreach ($relation['relationColumnList'] as $k => $v) {
                     // 不显示的字段直接过滤掉
                     if ($relation['relationFields'] && !in_array($v['COLUMN_NAME'], $relation['relationFields'])) {
@@ -969,7 +1044,7 @@ class Crud extends Command
             }
 
             //JS最后一列加上操作列
-            $javascriptList[] = str_repeat(" ", 24) . "{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}";
+            $javascriptList[] = str_repeat(" ", 24) . "{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, " . ($operateButtonList ? "buttons: [" . implode(',', $operateButtonList) . "], " : "") . "formatter: Table.api.formatter.operate}";
             $addList = implode("\n", array_filter($addList));
             $editList = implode("\n", array_filter($editList));
             $javascriptList = implode(",\n", array_filter($javascriptList));
@@ -1032,9 +1107,11 @@ class Crud extends Command
                 'relationSearch'          => $relations ? 'true' : 'false',
                 'relationWithList'        => '',
                 'relationMethodList'      => '',
+                'controllerImport'        => $controllerImport,
                 'controllerIndex'         => '',
                 'recyclebinJs'            => '',
                 'headingHtml'             => $headingHtml,
+                'importHtml'              => $importHtml,
                 'recyclebinHtml'          => $recyclebinHtml,
                 'visibleFieldList'        => $fields ? "\$row->visible(['" . implode("','", array_filter(in_array($priKey, explode(',', $fields)) ? explode(',', $fields) : explode(',', $priKey . ',' . $fields))) . "']);" : '',
                 'appendAttrList'          => implode(",\n", $appendAttrList),
@@ -1047,24 +1124,30 @@ class Crud extends Command
             //如果使用关联模型
             if ($relations) {
                 $relationWithList = $relationMethodList = $relationVisibleFieldList = [];
+                $relationKeyArr = ['hasone' => 'hasOne', 'belongsto' => 'belongsTo', 'hasmany' => 'hasMany'];
                 foreach ($relations as $index => $relation) {
                     //需要构造关联的方法
                     $relation['relationMethod'] = strtolower($relation['relationName']);
 
                     //关联的模式
-                    $relation['relationMode'] = $relation['relationMode'] == 'hasone' ? 'hasOne' : 'belongsTo';
+                    $relation['relationMode'] = strtolower($relation['relationMode']);
+                    $relation['relationMode'] = array_key_exists($relation['relationMode'], $relationKeyArr) ? $relationKeyArr[$relation['relationMode']] : '';
 
                     //关联字段
                     $relation['relationPrimaryKey'] = $relation['relationPrimaryKey'] ? $relation['relationPrimaryKey'] : $priKey;
 
+                    //构造关联模型的方法
+                    $relationMethodList[] = $this->getReplacedStub('mixins' . DS . 'modelrelationmethod' . ($relation['relationMode'] == 'hasMany' ? '-hasmany' : ''), $relation);
+
+                    if ($relation['relationMode'] == 'hasMany') {
+                        continue;
+                    }
+
                     //预载入的方法
                     $relationWithList[] = $relation['relationMethod'];
 
                     unset($relation['relationColumnList'], $relation['relationFieldList'], $relation['relationTableInfo']);
 
-                    //构造关联模型的方法
-                    $relationMethodList[] = $this->getReplacedStub('mixins' . DS . 'modelrelationmethod', $relation);
-
                     //如果设置了显示主表字段,则必须显式将关联表字段显示
                     if ($fields) {
                         $relationVisibleFieldList[] = "\$row->visible(['{$relation['relationMethod']}']);";
@@ -1080,8 +1163,10 @@ class Crud extends Command
                 $data['relationMethodList'] = implode("\n\n", $relationMethodList);
                 $data['relationVisibleFieldList'] = implode("\n\t\t\t\t", $relationVisibleFieldList);
 
-                //需要重写index方法
-                $data['controllerIndex'] = $this->getReplacedStub('controllerindex', $data);
+                if ($relationWithList) {
+                    //需要重写index方法
+                    $data['controllerIndex'] = $this->getReplacedStub('controllerindex', $data);
+                }
             } elseif ($fields) {
                 $data = array_merge($data, ['relationWithList' => '', 'relationMethodList' => '', 'relationVisibleFieldList' => '']);
                 //需要重写index方法
@@ -1221,6 +1306,28 @@ EOD;
     }
 
     /**
+     * 获取控制器URL
+     * @param string $moduleName
+     * @param array  $baseNameArr
+     * @return string
+     */
+    protected function getControllerUrl($moduleName, $baseNameArr)
+    {
+        for ($i = 0; $i < count($baseNameArr) - 1; $i++) {
+            $temp = array_slice($baseNameArr, 0, $i + 1);
+            $temp[$i] = ucfirst($temp[$i]);
+            $controllerFile = APP_PATH . $moduleName . DS . 'controller' . DS . implode(DS, $temp) . '.php';
+            //检测父级目录同名控制器是否存在,存在则变更URL格式
+            if (is_file($controllerFile)) {
+                $baseNameArr = [implode('.', $baseNameArr)];
+                break;
+            }
+        }
+        $controllerUrl = strtolower(implode('/', $baseNameArr));
+        return $controllerUrl;
+    }
+
+    /**
      * 获取控制器相关信息
      * @param $module
      * @param $controller
@@ -1269,14 +1376,14 @@ EOD;
         $arr = [];
         if (!$name) {
             $parseName = Loader::parseName($table, 1);
-            $parseArr = [$table];
-        } else {
-            $name = str_replace(['.', '/', '\\'], '/', $name);
-            $arr = explode('/', $name);
-            $parseName = ucfirst(array_pop($arr));
-            $parseArr = $arr;
-            array_push($parseArr, $parseName);
+            $name = str_replace('_', '/', $table);
         }
+
+        $name = str_replace(['.', '/', '\\'], '/', $name);
+        $arr = explode('/', $name);
+        $parseName = ucfirst(array_pop($arr));
+        $parseArr = $arr;
+        array_push($parseArr, $parseName);
         //类名不能为内部关键字
         if (in_array(strtolower($parseName), $this->internalKeywords)) {
             throw new Exception('Unable to use internal variable:' . $parseName);

+ 3 - 6
application/admin/command/Crud/stubs/controller.stub

@@ -11,7 +11,7 @@ use app\common\controller\Backend;
  */
 class {%controllerName%} extends Backend
 {
-    
+
     /**
      * {%modelName%}模型对象
      * @var \{%modelNamespace%}\{%modelName%}
@@ -25,16 +25,13 @@ class {%controllerName%} extends Backend
 {%controllerAssignList%}
     }
 
-    public function import()
-    {
-        parent::import();
-    }
+{%controllerImport%}
 
     /**
      * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
      * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
      * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
      */
-    
+
 {%controllerIndex%}
 }

+ 3 - 3
application/admin/command/Crud/stubs/index.stub

@@ -10,7 +10,7 @@
                         <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('{%controllerUrl%}/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
                         <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('{%controllerUrl%}/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
                         <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('{%controllerUrl%}/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
-                        <a href="javascript:;" class="btn btn-danger btn-import {:$auth->check('{%controllerUrl%}/import')?'':'hide'}" title="{:__('Import')}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="fa fa-upload"></i> {:__('Import')}</a>
+                        {%importHtml%}
 
                         <div class="dropdown btn-group {:$auth->check('{%controllerUrl%}/multi')?'':'hide'}">
                             <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
@@ -23,8 +23,8 @@
                         {%recyclebinHtml%}
                     </div>
                     <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
-                           data-operate-edit="{:$auth->check('{%controllerUrl%}/edit')}" 
-                           data-operate-del="{:$auth->check('{%controllerUrl%}/del')}" 
+                           data-operate-edit="{:$auth->check('{%controllerUrl%}/edit')}"
+                           data-operate-del="{:$auth->check('{%controllerUrl%}/del')}"
                            width="100%">
                     </table>
                 </div>

+ 4 - 0
application/admin/command/Crud/stubs/mixins/import.stub

@@ -0,0 +1,4 @@
+    public function import()
+    {
+        parent::import();
+    }

+ 5 - 0
application/admin/command/Crud/stubs/mixins/modelrelationmethod-hasmany.stub

@@ -0,0 +1,5 @@
+
+    public function {%relationMethod%}s()
+    {
+        return $this->{%relationMode%}('{%relationClassName%}', '{%relationForeignKey%}', '{%relationPrimaryKey%}');
+    }

+ 0 - 30
application/admin/common.php

@@ -194,33 +194,3 @@ if (!function_exists('build_heading')) {
         return $result;
     }
 }
-
-if (!function_exists('build_suffix_image')) {
-    /**
-     * 生成文件后缀图片
-     * @param string $suffix 后缀
-     * @param null   $background
-     * @return string
-     */
-    function build_suffix_image($suffix, $background = null)
-    {
-        $suffix = mb_substr(strtoupper($suffix), 0, 4);
-        $total = unpack('L', hash('adler32', $suffix, true))[1];
-        $hue = $total % 360;
-        list($r, $g, $b) = hsv2rgb($hue / 360, 0.3, 0.9);
-
-        $background = $background ? $background : "rgb({$r},{$g},{$b})";
-
-        $icon = <<<EOT
-        <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
-            <path style="fill:#E2E5E7;" d="M128,0c-17.6,0-32,14.4-32,32v448c0,17.6,14.4,32,32,32h320c17.6,0,32-14.4,32-32V128L352,0H128z"/>
-            <path style="fill:#B0B7BD;" d="M384,128h96L352,0v96C352,113.6,366.4,128,384,128z"/>
-            <polygon style="fill:#CAD1D8;" points="480,224 384,128 480,128 "/>
-            <path style="fill:{$background};" d="M416,416c0,8.8-7.2,16-16,16H48c-8.8,0-16-7.2-16-16V256c0-8.8,7.2-16,16-16h352c8.8,0,16,7.2,16,16 V416z"/>
-            <path style="fill:#CAD1D8;" d="M400,432H96v16h304c8.8,0,16-7.2,16-16v-16C416,424.8,408.8,432,400,432z"/>
-            <g><text><tspan x="220" y="380" font-size="124" font-family="Verdana, Helvetica, Arial, sans-serif" fill="white" text-anchor="middle">{$suffix}</tspan></text></g>
-        </svg>
-EOT;
-        return $icon;
-    }
-}

+ 181 - 28
application/admin/controller/Addon.php

@@ -25,13 +25,13 @@ class Addon extends Backend
     public function _initialize()
     {
         parent::_initialize();
-        if (!$this->auth->isSuperAdmin() && in_array($this->request->action(), ['install', 'uninstall', 'local', 'upgrade'])) {
+        if (!$this->auth->isSuperAdmin() && in_array($this->request->action(), ['install', 'uninstall', 'local', 'upgrade', 'authorization', 'testdata'])) {
             $this->error(__('Access is allowed only to the super management group'));
         }
     }
 
     /**
-     * 查看
+     * 插件列表
      */
     public function index()
     {
@@ -41,6 +41,18 @@ class Addon extends Backend
             $v['config'] = $config ? 1 : 0;
             $v['url'] = str_replace($this->request->server('SCRIPT_NAME'), '', $v['url']);
         }
+        if ($this->request->isAjax()) {
+            $result = [];
+            debug('begin');
+            try {
+                $result = Service::addons($this->request->get());
+            } catch (\Exception $e) {
+                $this->error($e->getMessage());
+            }
+            debug('end');
+            \think\Log::record("tx:" . debug('begin', 'end', 6) . 's');
+            return json($result);
+        }
         $this->assignconfig(['addons' => $addons, 'api_url' => config('fastadmin.api_url'), 'faversion' => config('fastadmin.version')]);
         return $this->view->fetch();
     }
@@ -57,13 +69,10 @@ class Addon extends Backend
         if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
             $this->error(__('Addon name incorrect'));
         }
-        if (!is_dir(ADDON_PATH . $name)) {
-            $this->error(__('Directory not found'));
-        }
         $info = get_addon_info($name);
         $config = get_addon_fullconfig($name);
         if (!$info) {
-            $this->error(__('No Results were found'));
+            $this->error(__('Addon not exists'));
         }
         if ($this->request->isPost()) {
             $params = $this->request->post("row/a", [], 'trim');
@@ -80,13 +89,19 @@ class Addon extends Backend
                     }
                 }
                 try {
-                    //更新配置文件
-                    set_addon_fullconfig($name, $config);
-                    Service::refresh();
-                    $this->success();
+                    $addon = get_addon_instance($name);
+                    //插件自定义配置实现逻辑
+                    if (method_exists($addon, 'config')) {
+                        $addon->config($name, $config);
+                    } else {
+                        //更新配置文件
+                        set_addon_fullconfig($name, $config);
+                        Service::refresh();
+                    }
                 } catch (Exception $e) {
                     $this->error(__($e->getMessage()));
                 }
+                $this->success();
             }
             $this->error(__('Parameter %s can not be empty', ''));
         }
@@ -212,6 +227,9 @@ class Addon extends Backend
     {
         Config::set('default_return_type', 'json');
 
+        if (!config('app_debug')) {
+            $this->error(__('Only work at debug mode'));
+        }
         $info = [];
         $file = $this->request->file('file');
         try {
@@ -276,6 +294,29 @@ class Addon extends Backend
     }
 
     /**
+     * 测试数据
+     */
+    public function testdata()
+    {
+        $name = $this->request->post("name");
+        if (!$name) {
+            $this->error(__('Parameter %s can not be empty', 'name'));
+        }
+        if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
+            $this->error(__('Addon name incorrect'));
+        }
+
+        try {
+            Service::importsql($name, 'testdata.sql');
+        } catch (AddonException $e) {
+            $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
+        } catch (Exception $e) {
+            $this->error(__($e->getMessage()), $e->getCode());
+        }
+        $this->success(__('Import successful'), '');
+    }
+
+    /**
      * 已装插件
      */
     public function downloaded()
@@ -285,22 +326,7 @@ class Addon extends Backend
         $filter = $this->request->get("filter");
         $search = $this->request->get("search");
         $search = htmlspecialchars(strip_tags($search));
-        $onlineaddons = Cache::get("onlineaddons");
-        if (!is_array($onlineaddons) && config('fastadmin.api_url')) {
-            $onlineaddons = [];
-            $result = Http::sendRequest(config('fastadmin.api_url') . '/addon/index', [], 'GET', [
-                CURLOPT_HTTPHEADER => ['Accept-Encoding:gzip'],
-                CURLOPT_ENCODING   => "gzip"
-            ]);
-            if ($result['ret']) {
-                $json = (array)json_decode($result['msg'], true);
-                $rows = isset($json['rows']) ? $json['rows'] : [];
-                foreach ($rows as $index => $row) {
-                    $onlineaddons[$row['name']] = $row;
-                }
-            }
-            Cache::set("onlineaddons", $onlineaddons, 600);
-        }
+        $onlineaddons = $this->getAddonList();
         $filter = (array)json_decode($filter, true);
         $addons = get_addon_list();
         $list = [];
@@ -311,6 +337,7 @@ class Addon extends Backend
 
             if (isset($onlineaddons[$v['name']])) {
                 $v = array_merge($v, $onlineaddons[$v['name']]);
+                $v['price'] = '-';
             } else {
                 $v['category_id'] = 0;
                 $v['flag'] = '';
@@ -321,9 +348,9 @@ class Addon extends Backend
                 $v['price'] = __('None');
                 $v['screenshots'] = [];
                 $v['releaselist'] = [];
+                $v['url'] = addon_url($v['name']);
+                $v['url'] = str_replace($this->request->server('SCRIPT_NAME'), '', $v['url']);
             }
-            $v['url'] = addon_url($v['name']);
-            $v['url'] = str_replace($this->request->server('SCRIPT_NAME'), '', $v['url']);
             $v['createtime'] = filemtime(ADDON_PATH . $v['name']);
             if ($filter && isset($filter['category_id']) && is_numeric($filter['category_id']) && $filter['category_id'] != $v['category_id']) {
                 continue;
@@ -341,6 +368,105 @@ class Addon extends Backend
     }
 
     /**
+     * 登录
+     */
+    public function login()
+    {
+        $params = [
+            'account'   => $this->request->post('account'),
+            'password'  => $this->request->post('password'),
+            'url'       => $this->request->url(true),
+            'faversion' => config('fastadmin.version')
+        ];
+        try {
+            $result = Service::login($params);
+        } catch (Exception $e) {
+            $this->error(__($e->getMessage()));
+        }
+        return json($result);
+    }
+
+    /**
+     * 会员信息
+     */
+    public function userinfo()
+    {
+        $params = [
+            'uid'       => $this->request->post('uid'),
+            'token'     => $this->request->post('token'),
+            'url'       => $this->request->url(true),
+            'faversion' => config('fastadmin.version')
+        ];
+        try {
+            $result = Service::userinfo($params);
+        } catch (Exception $e) {
+            $this->error($e->getMessage());
+        }
+        return json($result);
+    }
+
+    /**
+     * 退出
+     */
+    public function logout()
+    {
+        $params = [
+            'uid'       => $this->request->post('uid'),
+            'token'     => $this->request->post('token'),
+            'url'       => $this->request->url(true),
+            'faversion' => config('fastadmin.version')
+        ];
+        try {
+            $result = Service::logout($params);
+        } catch (Exception $e) {
+            $this->error(__($e->getMessage()));
+        }
+        return json($result);
+    }
+
+    /**
+     * 检测
+     */
+    public function isbuy()
+    {
+        $name = $this->request->post("name");
+        $uid = $this->request->post("uid");
+        $token = $this->request->post("token");
+        $version = $this->request->post("version");
+        $faversion = $this->request->post("faversion");
+        $extend = [
+            'uid'       => $uid,
+            'token'     => $token,
+            'version'   => $version,
+            'faversion' => $faversion
+        ];
+        try {
+            $result = Service::isBuy($name, $extend);
+        } catch (Exception $e) {
+            $this->error(__($e->getMessage()));
+        }
+        return json($result);
+    }
+
+    /**
+     * 刷新授权
+     */
+    public function authorization()
+    {
+        $params = [
+            'uid'       => $this->request->post('uid'),
+            'token'     => $this->request->post('token'),
+            'faversion' => $this->request->post('faversion'),
+        ];
+        try {
+            Service::authorization($params);
+        } catch (Exception $e) {
+            $this->error(__($e->getMessage()));
+        }
+        $this->success(__('Operate successful'));
+    }
+
+    /**
      * 获取插件相关表
      */
     public function get_table_list()
@@ -360,4 +486,31 @@ class Addon extends Backend
         $tables = array_values($tables);
         $this->success('', null, ['tables' => $tables]);
     }
+
+    protected function getAddonList()
+    {
+        $onlineaddons = Cache::get("onlineaddons");
+        if (!is_array($onlineaddons) && config('fastadmin.api_url')) {
+            $onlineaddons = [];
+            $params = [
+                'uid'       => $this->request->post('uid'),
+                'token'     => $this->request->post('token'),
+                'version'   => config('fastadmin.version'),
+                'faversion' => config('fastadmin.version'),
+            ];
+            $json = [];
+            try {
+                $json = Service::addons($params);
+            } catch (\Exception $e) {
+
+            }
+            $rows = isset($json['rows']) ? $json['rows'] : [];
+            foreach ($rows as $index => $row) {
+                $onlineaddons[$row['name']] = $row;
+            }
+            Cache::set("onlineaddons", $onlineaddons, 600);
+        }
+        return $onlineaddons;
+    }
+
 }

+ 17 - 9
application/admin/controller/Ajax.php

@@ -11,6 +11,7 @@ use think\Cache;
 use think\Config;
 use think\Db;
 use think\Lang;
+use think\Response;
 use think\Validate;
 
 /**
@@ -37,17 +38,19 @@ class Ajax extends Backend
      */
     public function lang()
     {
-        header('Content-Type: application/javascript');
-        header("Cache-Control: public");
-        header("Pragma: cache");
 
-        $offset = 30 * 60 * 60 * 24; // 缓存一个月
-        header("Expires: " . gmdate("D, d M Y H:i:s", time() + $offset) . " GMT");
+        $header = ['Content-Type' => 'application/javascript'];
+        if (!config('app_debug')) {
+            $offset = 30 * 60 * 60 * 24; // 缓存一个月
+            $header['Cache-Control'] = 'public';
+            $header['Pragma'] = 'cache';
+            $header['Expires'] = gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
+        }
 
         $controllername = input("controllername");
         //默认只加载了控制器对应的语言名,你还根据控制器名来加载额外的语言包
         $this->loadlang($controllername);
-        return jsonp(Lang::get(), 200, [], ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
+        return jsonp(Lang::get(), 200, $header, ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
     }
 
     /**
@@ -296,10 +299,15 @@ class Ajax extends Backend
     public function icon()
     {
         $suffix = $this->request->request("suffix");
-        header('Content-type: image/svg+xml');
         $suffix = $suffix ? $suffix : "FILE";
-        echo build_suffix_image($suffix);
-        exit;
+        $data = build_suffix_image($suffix);
+        $header = ['Content-Type' => 'image/svg+xml'];
+        $offset = 30 * 60 * 60 * 24; // 缓存一个月
+        $header['Cache-Control'] = 'public';
+        $header['Pragma'] = 'cache';
+        $header['Expires'] = gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
+        $response = Response::create($data, '', 200, $header);
+        return $response;
     }
 
 }

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

@@ -6,6 +6,7 @@ use app\admin\model\AdminLog;
 use app\common\controller\Backend;
 use think\Config;
 use think\Hook;
+use think\Session;
 use think\Validate;
 
 /**
@@ -31,6 +32,13 @@ class Index extends Backend
      */
     public function index()
     {
+        $cookieArr = ['adminskin' => "/^skin\-([a-z\-]+)\$/i", 'multiplenav' => "/^(0|1)\$/", 'multipletab' => "/^(0|1)\$/"];
+        foreach ($cookieArr as $key => $regex) {
+            $cookieValue = $this->request->cookie($key);
+            if (!is_null($cookieValue) && preg_match($regex, $cookieValue)) {
+                config('fastadmin.' . $key, $cookieValue);
+            }
+        }
         //左侧菜单
         list($menulist, $navlist, $fixedmenu, $referermenu) = $this->auth->getSidebar([
             'dashboard' => 'hot',
@@ -44,6 +52,7 @@ class Index extends Backend
                 $this->success('', null, ['menulist' => $menulist, 'navlist' => $navlist]);
             }
         }
+        $this->assignconfig('cookie', ['prefix' => config('cookie.prefix')]);
         $this->view->assign('menulist', $menulist);
         $this->view->assign('navlist', $navlist);
         $this->view->assign('fixedmenu', $fixedmenu);
@@ -99,6 +108,7 @@ class Index extends Backend
 
         // 根据客户端的cookie,判断是否可以自动登录
         if ($this->auth->autologin()) {
+            Session::delete("referer");
             $this->redirect($url);
         }
         $background = Config::get('fastadmin.login_background');

+ 1 - 1
application/admin/controller/auth/Rule.php

@@ -118,7 +118,7 @@ class Rule extends Backend
                 //这里需要针对name做唯一验证
                 $ruleValidate = \think\Loader::validate('AuthRule');
                 $ruleValidate->rule([
-                    'name' => 'require|format|unique:AuthRule,name,' . $row->id,
+                    'name' => 'require|unique:AuthRule,name,' . $row->id,
                 ]);
                 $result = $row->validate()->save($params);
                 if ($result === false) {

+ 6 - 0
application/admin/controller/general/Config.php

@@ -88,6 +88,9 @@ class Config extends Backend
      */
     public function add()
     {
+        if (!config('app_debug')) {
+            $this->error(__('Only work at development environment'));
+        }
         if ($this->request->isPost()) {
             $this->token();
             $params = $this->request->post("row/a", [], 'trim');
@@ -166,6 +169,9 @@ class Config extends Backend
      */
     public function del($ids = "")
     {
+        if (!config('app_debug')) {
+            $this->error(__('Only work at development environment'));
+        }
         $name = $this->request->post('name');
         $config = ConfigModel::getByName($name);
         if ($name && $config) {

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

@@ -201,13 +201,13 @@ return [
     'Third group 2'                                         => '三级管理组2',
     'Dashboard tips'                                        => '用于展示当前系统中的统计数据、统计报表及重要实时数据',
     'Config tips'                                           => '可以在此增改系统的变量和分组,也可以自定义分组和变量',
-    'Category tips'                                         => '用于管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加',
+    'Category tips'                                         => '分类类型请在常规管理->系统配置->字典配置中添加',
     'Attachment tips'                                       => '主要用于管理上传到服务器或第三方存储的数据',
     'Addon tips'                                            => '可在线安装、卸载、禁用、启用、配置、升级插件,插件升级前请做好备份。',
     'Admin tips'                                            => '一个管理员可以有多个角色组,左侧的菜单根据管理员所拥有的权限进行生成',
     'Admin log tips'                                        => '管理员可以查看自己所拥有的权限的管理员日志',
     'Group tips'                                            => '角色组可以有多个,角色有上下级层级关系,如果子角色有角色组和管理员的权限则可以派生属于自己组别的下级角色组或管理员',
-    'Rule tips'                                             => '规则通常对应一个控制器的方法,同时左侧的菜单栏数据也从规则中体现,通常建议通过命令行进行生成规则节点',
+    'Rule tips'                                             => '菜单规则通常对应一个控制器的方法,同时菜单栏数据也从规则中获取',
     'Access is allowed only to the super management group'  => '仅超级管理组能访问',
     'Local addon'                                           => '本地插件',
     // 前台菜单

+ 16 - 4
application/admin/lang/zh-cn/addon.php

@@ -10,26 +10,31 @@ return [
     'Donate'                                                  => '打赏作者',
     'Warmtips'                                                => '温馨提示',
     'Pay now'                                                 => '立即支付',
-    'Offline install'                                         => '离线安装',
+    'Local install'                                           => '本地安装',
     '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 tips'                                                => '扫码支付后如果仍然无法安装,请不要重复支付,请稍后再重试安装!',
+    'Pay successful tips'                                     => '购买成功!请点击继续安装按钮完成安装!',
     'Pay click tips'                                          => '请点击这里在新窗口中进行支付!',
     'Pay new window tips'                                     => '请在新弹出的窗口中进行支付,支付完成后再重新点击安装按钮进行安装!',
     '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,登录后将同步已购买的记录,下载时无需二次付费!',
+    'Not login tips'                                          => '你当前未登录FastAdmin,请登录后操作!',
     'Please login and try to install'                         => '请登录FastAdmin后再进行离线安装!',
     'Not installed tips'                                      => '请安装后再访问插件前台页面!',
     'Not enabled tips'                                        => '插件已经禁用,请启用后再访问插件前台页面!',
     'New version tips'                                        => '发现新版本:%s 点击查看更新日志',
-    'Store now available tips'                                => '插件市场暂不可用,是否切换到本地插件?',
+    'Testdata tips'                                           => '你还可以继续导入测试数据!',
+    'Import testdata'                                         => '导入测试数据',
+    'Skip testdata'                                           => '暂不导入',
+    'Store not available tips'                                => '插件市场暂不可用,是否切换到本地插件?',
     'Switch to the local'                                     => '切换到本地插件',
     'try to reload'                                           => '重新尝试加载',
     'Please disable the add before trying to upgrade'         => '请先禁用插件再进行升级',
@@ -41,6 +46,7 @@ return [
     'View addon screenshots'                                  => '点击查看插件截图',
     'Click to toggle status'                                  => '点击切换插件状态',
     'Click to contact developer'                              => '点击与插件开发者取得联系',
+    'Continue installation'                                   => '继续安装',
     'My addons'                                               => '我购买的插件',
     'Index'                                                   => '前台',
     'All'                                                     => '全部',
@@ -84,13 +90,18 @@ return [
     'Install successful'                                      => '安装成功',
     'Uninstall successful'                                    => '卸载成功',
     'Operate successful'                                      => '操作成功',
+    'Import successful'                                       => '导入测试数据成功!清除浏览器缓存和框架缓存后生效!',
+    'Initialize successful'                                   => '初始化成功',
+    'Initialize template not found'                           => '初始化模板未找到',
     'Addon name incorrect'                                    => '插件名称不正确',
     'Addon info file was not found'                           => '插件配置文件未找到',
     'Addon info file data incorrect'                          => '插件配置信息不正确',
     'Addon already exists'                                    => '插件已经存在',
+    'Addon not exists'                                        => '插件不存在',
     'Addon package download failed'                           => '插件下载失败',
     'Conflicting file found'                                  => '发现冲突文件',
     'Invalid addon package'                                   => '未验证的插件',
+    'No initialize method'                                    => '未找到初始化方法',
     'No permission to write temporary files'                  => '没有权限写入临时文件',
     'The addon file does not exist'                           => '插件主启动程序不存在',
     'The configuration file content is incorrect'             => '配置文件不完整',
@@ -98,6 +109,7 @@ return [
     'Unable to extract the file'                              => '无法解压ZIP文件',
     'Unable to open file \'%s\' for writing'                  => '文件(%s)没有写入权限',
     'Are you sure you want to unstall %s?'                    => '确认卸载<b>《%s》</b>?',
+    'Are you sure you want to refresh authorization?'         => '确认刷新应用插件授权?',
     'Delete all the addon file and cannot be recovered!'      => '卸载将会删除所有插件文件且不可找回!!!',
     'Delete all the addon database and cannot be recovered!'  => '删除所有插件相关数据表且不可找回!!!',
     'Please backup important data manually before uninstall!' => '如有重要数据请备份后再操作!!!',

+ 78 - 77
application/admin/lang/zh-cn/general/config.php

@@ -1,81 +1,82 @@
 <?php
 
 return [
-    'Name'                        => '变量名',
-    'Tip'                         => '提示信息',
-    'Group'                       => '分组',
-    'Type'                        => '类型',
-    'Title'                       => '变量标题',
-    'Value'                       => '变量值',
-    'Basic'                       => '基础配置',
-    'Email'                       => '邮件配置',
-    'Attachment'                  => '附件配置',
-    'Dictionary'                  => '字典配置',
-    'User'                        => '会员配置',
-    'Example'                     => '示例分组',
-    'Extend'                      => '扩展属性',
-    'String'                      => '字符',
-    'Password'                    => '密码',
-    'Text'                        => '文本',
-    'Editor'                      => '编辑器',
-    'Number'                      => '数字',
-    'Date'                        => '日期',
-    'Time'                        => '时间',
-    'Datetime'                    => '日期时间',
-    'Datetimerange'               => '日期时间区间',
-    'Image'                       => '图片',
-    'Images'                      => '图片(多)',
-    'File'                        => '文件',
-    'Files'                       => '文件(多)',
-    'Select'                      => '列表',
-    'Selects'                     => '列表(多选)',
-    'Switch'                      => '开关',
-    'Checkbox'                    => '复选',
-    'Radio'                       => '单选',
-    'Array'                       => '数组',
-    'Array key'                   => '键名',
-    'Array value'                 => '键值',
-    'City'                        => '城市地区',
-    'Selectpage'                  => '关联表',
-    'Selectpages'                 => '关联表(多选)',
-    'Custom'                      => '自定义',
-    'Please select table'         => '关联表',
-    'Selectpage table'            => '关联表',
-    'Selectpage primarykey'       => '存储字段',
-    'Selectpage field'            => '显示字段',
-    'Selectpage conditions'       => '筛选条件',
-    'Field title'                 => '字段名',
-    'Field value'                 => '字段值',
-    'Content'                     => '数据列表',
-    'Rule'                        => '校验规则',
-    'Site name'                   => '站点名称',
-    'Beian'                       => '备案号',
-    'Cdn url'                     => 'CDN地址',
-    'Version'                     => '版本号',
-    'Timezone'                    => '时区',
-    'Forbidden ip'                => '禁止IP',
-    'Languages'                   => '语言',
-    'Fixed page'                  => '后台固定页',
-    'Category type'               => '分类类型',
-    'Config group'                => '配置分组',
-    'Attachment category'         => '附件类别',
-    'Category1'                   => '分类一',
-    'Category2'                   => '分类二',
-    'Rule tips'                   => '校验规则使用请参考Nice-validator文档',
-    'Extend tips'                 => '扩展属性支持{id}、{name}、{group}、{title}、{value}、{content}、{rule}替换',
-    'Mail type'                   => '邮件发送方式',
-    'Mail smtp host'              => 'SMTP服务器',
-    'Mail smtp port'              => 'SMTP端口',
-    'Mail smtp user'              => 'SMTP用户名',
-    'Mail smtp password'          => 'SMTP密码',
-    'Mail vertify type'           => 'SMTP验证方式',
-    'Mail from'                   => '发件人邮箱',
-    'Site name incorrect'         => '网站名称错误',
-    'Name already exist'          => '变量名称已经存在',
-    'Add new config'              => '点击添加新的配置',
-    'Send a test message'         => '发送测试邮件',
-    'This is a test mail content' => '这是一封来自%s的校验邮件,用于校验邮件配置是否正常!',
-    'This is a test mail'         => '这是一封来自%s的邮件',
-    'Please input your email'     => '请输入测试接收者邮箱',
-    'Please input correct email'  => '请输入正确的邮箱地址',
+    'Name'                                 => '变量名',
+    'Tip'                                  => '提示信息',
+    'Group'                                => '分组',
+    'Type'                                 => '类型',
+    'Title'                                => '变量标题',
+    'Value'                                => '变量值',
+    'Basic'                                => '基础配置',
+    'Email'                                => '邮件配置',
+    'Attachment'                           => '附件配置',
+    'Dictionary'                           => '字典配置',
+    'User'                                 => '会员配置',
+    'Example'                              => '示例分组',
+    'Extend'                               => '扩展属性',
+    'String'                               => '字符',
+    'Password'                             => '密码',
+    'Text'                                 => '文本',
+    'Editor'                               => '编辑器',
+    'Number'                               => '数字',
+    'Date'                                 => '日期',
+    'Time'                                 => '时间',
+    'Datetime'                             => '日期时间',
+    'Datetimerange'                        => '日期时间区间',
+    'Image'                                => '图片',
+    'Images'                               => '图片(多)',
+    'File'                                 => '文件',
+    'Files'                                => '文件(多)',
+    'Select'                               => '列表',
+    'Selects'                              => '列表(多选)',
+    'Switch'                               => '开关',
+    'Checkbox'                             => '复选',
+    'Radio'                                => '单选',
+    'Array'                                => '数组',
+    'Array key'                            => '键名',
+    'Array value'                          => '键值',
+    'City'                                 => '城市地区',
+    'Selectpage'                           => '关联表',
+    'Selectpages'                          => '关联表(多选)',
+    'Custom'                               => '自定义',
+    'Please select table'                  => '关联表',
+    'Selectpage table'                     => '关联表',
+    'Selectpage primarykey'                => '存储字段',
+    'Selectpage field'                     => '显示字段',
+    'Selectpage conditions'                => '筛选条件',
+    'Field title'                          => '字段名',
+    'Field value'                          => '字段值',
+    'Content'                              => '数据列表',
+    'Rule'                                 => '校验规则',
+    'Site name'                            => '站点名称',
+    'Beian'                                => '备案号',
+    'Cdn url'                              => 'CDN地址',
+    'Version'                              => '版本号',
+    'Timezone'                             => '时区',
+    'Forbidden ip'                         => '禁止IP',
+    'Languages'                            => '语言',
+    'Fixed page'                           => '后台固定页',
+    'Category type'                        => '分类类型',
+    'Config group'                         => '配置分组',
+    'Attachment category'                  => '附件类别',
+    'Category1'                            => '分类一',
+    'Category2'                            => '分类二',
+    'Rule tips'                            => '校验规则使用请参考Nice-validator文档',
+    'Extend tips'                          => '扩展属性支持{id}、{name}、{group}、{title}、{value}、{content}、{rule}替换',
+    'Mail type'                            => '邮件发送方式',
+    'Mail smtp host'                       => 'SMTP服务器',
+    'Mail smtp port'                       => 'SMTP端口',
+    'Mail smtp user'                       => 'SMTP用户名',
+    'Mail smtp password'                   => 'SMTP密码',
+    'Mail vertify type'                    => 'SMTP验证方式',
+    'Mail from'                            => '发件人邮箱',
+    'Site name incorrect'                  => '网站名称错误',
+    'Name already exist'                   => '变量名称已经存在',
+    'Add new config'                       => '点击添加新的配置',
+    'Send a test message'                  => '发送测试邮件',
+    'Only work at development environment' => '只允许在开发环境开操作',
+    'This is a test mail content'          => '这是一封来自%s的校验邮件,用于校验邮件配置是否正常!',
+    'This is a test mail'                  => '这是一封来自%s的邮件',
+    'Please input your email'              => '请输入测试接收者邮箱',
+    'Please input correct email'           => '请输入正确的邮箱地址',
 ];

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

@@ -8,14 +8,18 @@ return [
     'You can\'t use fixed and boxed layouts together'            => '盒子模型和固定布局不能同时启作用',
     'Boxed Layout'                                               => '盒子布局',
     'Activate the boxed layout'                                  => '盒子布局最大宽度将被限定为1250px',
-    'Toggle Sidebar'                                             => '切换菜单栏',
-    'Toggle the left sidebar\'s state (open or collapse)'        => '切换菜单栏的展或收起',
+    'Toggle Sidebar'                                             => '收起菜单栏',
+    'Toggle the left sidebar\'s state (open or collapse)'        => '切换菜单栏的展或收起',
     'Sidebar Expand on Hover'                                    => '菜单栏自动展开',
     'Let the sidebar mini expand on hover'                       => '鼠标移到菜单栏自动展开',
     'Toggle Right Sidebar Slide'                                 => '切换右侧操作栏',
     'Toggle between slide over content and push content effects' => '切换右侧操作栏覆盖或独占',
     'Toggle Right Sidebar Skin'                                  => '切换右侧操作栏背景',
     'Toggle between dark and light skins for the right sidebar'  => '将右侧操作栏背景亮色或深色切换',
+    'Multiple nav'                                               => '多级菜单导航',
+    'Toggle the top menu state (multiple or single)'             => '切换顶部菜单为多级菜单导航模式',
+    'Multiple tab'                                               => '多选项卡',
+    'Always show multiple tab when multiple nav is set'          => '当配置为多级菜单导航时是否启用多选项卡',
     'Show sub menu'                                              => '显示菜单栏子菜单',
     'Always show sub menu'                                       => '菜单栏子菜单将始终显示',
     'Disable top menu badge'                                     => '禁用顶部彩色小角标',

+ 1 - 1
application/admin/validate/AuthRule.php

@@ -16,7 +16,7 @@ class AuthRule extends Validate
      * 验证规则
      */
     protected $rule = [
-        'name'  => 'require|format|unique:AuthRule',
+        'name'  => 'require|unique:AuthRule',
         'title' => 'require',
     ];
 

+ 1 - 1
application/admin/view/addon/config.html

@@ -111,7 +111,7 @@
     <div class="form-group layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 10 - 18
application/admin/view/addon/index.html

@@ -61,6 +61,7 @@
     .btn-toggle {
         padding: 0;
     }
+
 </style>
 <div class="panel panel-default panel-intro">
     <div class="panel-heading">
@@ -80,9 +81,11 @@
                     <div id="toolbar" class="toolbar">
                         <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" data-force-refresh="false"><i class="fa fa-refresh"></i> </a>
                         {if $Think.config.fastadmin.api_url}
+                        {if $Think.config.app_debug}
                         <button type="button" id="faupload-addon" class="btn btn-danger faupload btn-mini-xs" data-url="addon/local" data-chunking="false" data-mimetype="zip,fastaddon" data-multiple="false"><i class="fa fa-upload"></i>
-                            {:__('Offline install')}
+                            {:__('Local install')}
                         </button>
+                        {/if}
                         <div class="btn-group">
                             <a href="#" class="btn btn-info btn-switch active btn-mini-xs" data-type="all"><i class="fa fa-list"></i> {:__('All')}</a>
                             <a href="#" class="btn btn-info btn-switch btn-mini-xs" data-type="free"><i class="fa fa-gift"></i> {:__('Free')}</a>
@@ -90,6 +93,7 @@
                             <a href="#" class="btn btn-info btn-switch btn-mini-xs" data-type="local" data-url="addon/downloaded"><i class="fa fa-laptop"></i> {:__('Local addon')}</a>
                         </div>
                         <a class="btn btn-primary btn-userinfo btn-mini-xs" href="javascript:;"><i class="fa fa-user"></i> {:__('Userinfo')}</a>
+                        <a class="btn btn-warning btn-authorization btn-mini-xs" href="javascript:;"><i class="fa fa-cloud"></i> {:__('Reload authorization')}</a>
                         {/if}
                     </div>
                     <table id="table" class="table table-striped table-bordered table-hover" width="100%">
@@ -190,29 +194,17 @@
                     <strong>{:__('Warning')}</strong><br/>{:__('Logined tips', '<%=username%>')}
                 </div>
             </fieldset>
-            <div class="breadcrumb"><a href="https://www.fastadmin.net/user/myaddon.html" target="_blank"><i class="fa fa-money"></i> {:__('My addons')}</a></div>
+            <div class="breadcrumb"><a href="https://www.fastadmin.net/user/myaddon.html" target="_blank"><i class="fa fa-cube"></i> {:__('My addons')}</a></div>
         </form>
     </div>
 </script>
-<script id="paytpl" type="text/html">
-    <div class="payimg" style="background:url('<%=payimg%>') 0 0 no-repeat;background-size:cover;">
-        <%if(paycode){%>
-        <div class="alipaycode">
-            <%=paycode%>
-        </div>
-        <div class="wechatcode">
-            <%=paycode%>
-        </div>
-        <%}%>
-    </div>
-</script>
 <script id="uninstalltpl" type="text/html">
     <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>
@@ -245,7 +237,7 @@
     <% var label = labelarr[item.id % 5]; %>
     <% var addon = item.addon; %>
 
-    <div class="operate" data-id="<%=item.id%>" data-name="<%=item.name%>">
+    <span class="operate" data-id="<%=item.id%>" data-name="<%=item.name%>">
         <% if(!addon){ %>
             <% if(typeof item.releaselist !="undefined" && item.releaselist.length>1){%>
                 <span class="btn-group">
@@ -309,6 +301,6 @@
             <a href="javascript:;" class="btn btn-xs btn-danger btn-uninstall" title="{:__('Uninstall')}"><i class="fa fa-times"></i>
                 {:__('Uninstall')}</a>
         <% } %>
-    </div>
+    </span>
 </script>
 <!--@formatter:on-->

+ 2 - 2
application/admin/view/auth/admin/add.html

@@ -39,8 +39,8 @@
     <div class="form-group hidden layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>
+</form>

+ 2 - 2
application/admin/view/auth/admin/edit.html

@@ -45,8 +45,8 @@
     <div class="form-group hidden layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>
+</form>

+ 1 - 1
application/admin/view/auth/group/add.html

@@ -31,7 +31,7 @@
     <div class="form-group hidden layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 1 - 1
application/admin/view/auth/group/edit.html

@@ -31,7 +31,7 @@
     <div class="form-group hidden layer-footer">
         <label class="control-label col-xs-12 col-sm-2 col-xs-2"></label>
         <div class="col-xs-12 col-sm-8">
-            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 8 - 7
application/admin/view/auth/rule/add.html

@@ -34,18 +34,13 @@
         <label for="icon" class="control-label col-xs-12 col-sm-2">{:__('Icon')}:</label>
         <div class="col-xs-12 col-sm-8">
             <div class="input-group input-groupp-md">
+                <span class="input-group-addon"><i class="fa fa-circle-o" id="icon-style"></i></span>
                 <input type="text" class="form-control" id="icon" name="row[icon]" value="fa fa-circle-o" />
                 <a href="javascript:;" class="btn-search-icon input-group-addon">{:__('Search icon')}</a>
             </div>
         </div>
     </div>
     <div class="form-group">
-        <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
-        <div class="col-xs-12 col-sm-8">
-            <input type="text" class="form-control" id="weigh" name="row[weigh]" value="0" data-rule="required" />
-        </div>
-    </div>
-    <div class="form-group">
         <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Condition')}:</label>
         <div class="col-xs-12 col-sm-8">
             <textarea class="form-control" id="condition" name="row[condition]"></textarea>
@@ -70,6 +65,12 @@
         </div>
     </div>
     <div class="form-group">
+        <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="weigh" name="row[weigh]" value="0" data-rule="required" />
+        </div>
+    </div>
+    <div class="form-group">
         <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
@@ -78,7 +79,7 @@
     <div class="form-group hidden layer-footer">
         <div class="col-xs-2"></div>
         <div class="col-xs-12 col-sm-8">
-            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 9 - 8
application/admin/view/auth/rule/edit.html

@@ -34,18 +34,13 @@
         <label for="icon" class="control-label col-xs-12 col-sm-2">{:__('Icon')}:</label>
         <div class="col-xs-12 col-sm-8">
             <div class="input-group input-groupp-md">
+                <span class="input-group-addon"><i class="{$row.icon}" id="icon-style"></i></span>
                 <input type="text" class="form-control" id="icon" name="row[icon]" value="{$row.icon}" />
                 <a href="javascript:;" class="btn-search-icon input-group-addon">{:__('Search icon')}</a>
             </div>
         </div>
     </div>
     <div class="form-group">
-        <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
-        <div class="col-xs-12 col-sm-8">
-            <input type="text" class="form-control" id="weigh" name="row[weigh]" value="{$row.weigh}" data-rule="required" />
-        </div>
-    </div>
-    <div class="form-group">
         <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Condition')}:</label>
         <div class="col-xs-12 col-sm-8">
             <textarea class="form-control" id="condition" name="row[condition]">{$row.condition|htmlentities}</textarea>
@@ -66,7 +61,13 @@
     <div class="form-group">
         <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Remark')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <textarea class="form-control" id="remark" name="row[remark]">{$row.remark|htmlentities}</textarea>
+            <textarea class="form-control" id="remark" name="row[remark]">{$row.remark|__|htmlentities}</textarea>
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="weigh" name="row[weigh]" value="{$row.weigh}" data-rule="required" />
         </div>
     </div>
     <div class="form-group">
@@ -78,7 +79,7 @@
     <div class="form-group hidden layer-footer">
         <div class="col-xs-2"></div>
         <div class="col-xs-12 col-sm-8">
-            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 1 - 1
application/admin/view/category/add.html

@@ -93,7 +93,7 @@
     <div class="form-group layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 1 - 1
application/admin/view/category/edit.html

@@ -89,7 +89,7 @@
     <div class="form-group layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 9 - 2
application/admin/view/common/control.html

@@ -9,6 +9,13 @@
         display: block;
         float:left;
     }
+    .skin-list li.active a {
+        opacity: 1;
+        filter: alpha(opacity=100);
+    }
+    .skin-list li.active p {
+        color: #fff;
+    }
 </style>
 <!-- Control Sidebar -->
 <aside class="control-sidebar control-sidebar-dark">
@@ -23,8 +30,8 @@
         <!-- Home tab content -->
         <div class="tab-pane active" id="control-sidebar-setting-tab">
             <h4 class="control-sidebar-heading">{:__('Layout Options')}</h4>
-            <div class="form-group"><label class="control-sidebar-subheading"><input type="checkbox" data-layout="fixed" class="pull-right"> {:__('Fixed Layout')}</label><p>{:__("You can't use fixed and boxed layouts together")}</p></div>
-            <div class="form-group"><label class="control-sidebar-subheading"><input type="checkbox" data-layout="layout-boxed" class="pull-right"> {:__('Boxed Layout')}</label><p>{:__('Activate the boxed layout')}</p></div>
+            <div class="form-group"><label class="control-sidebar-subheading"><input type="checkbox" data-config="multiplenav" {if $Think.config.fastadmin.multiplenav}checked{/if} class="pull-right"> {:__('Multiple Nav')}</label><p>{:__("Toggle the top menu state (multiple or single)")}</p></div>
+            <div class="form-group"><label class="control-sidebar-subheading"><input type="checkbox" data-config="multipletab" {if $Think.config.fastadmin.multipletab}checked{/if} class="pull-right"> {:__('Multiple Tab')}</label><p>{:__("Always show multiple tab when multiple nav is set")}</p></div>
             <div class="form-group"><label class="control-sidebar-subheading"><input type="checkbox" data-layout="sidebar-collapse" class="pull-right"> {:__('Toggle Sidebar')}</label><p>{:__("Toggle the left sidebar's state (open or collapse)")}</p></div>
             <div class="form-group"><label class="control-sidebar-subheading"><input type="checkbox" data-enable="expandOnHover" class="pull-right"> {:__('Sidebar Expand on Hover')}</label><p>{:__('Let the sidebar mini expand on hover')}</p></div>
             <div class="form-group"><label class="control-sidebar-subheading"><input type="checkbox" data-menu="show-submenu" class="pull-right"> {:__('Show sub menu')}</label><p>{:__('Always show sub menu')}</p></div>

+ 2 - 2
application/admin/view/common/menu.html

@@ -30,10 +30,10 @@
     </div>
 
     <!--如果想始终显示子菜单,则给ul加上show-submenu类即可,当multiplenav开启的情况下默认为展开-->
-    <ul class="sidebar-menu {if $Think.config.fastadmin.multiplenav}show-submenu{/if}">
+    <ul class="sidebar-menu {if $Think.config.fastadmin.multiplenav||$Think.cookie.show_submenu}show-submenu{/if}">
 
         <!-- 菜单可以在 后台管理->权限管理->菜单规则 中进行增删改排序 -->
         {$menulist}
 
     </ul>
-</section>
+</section>

+ 13 - 12
application/admin/view/dashboard/index.html

@@ -6,8 +6,6 @@
         -moz-border-radius: 3px;
         border-radius: 3px;
         margin-bottom: 20px;
-        -webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.05);
-        box-shadow: 0 1px 0px rgba(0, 0, 0, 0.05);
     }
 
     .sm-st-icon {
@@ -27,7 +25,6 @@
     }
 
     .sm-st-info {
-        font-size: 12px;
         padding-top: 2px;
     }
 
@@ -125,6 +122,7 @@
     .stat .name {
         overflow: hidden;
         text-overflow: ellipsis;
+        margin: 5px 0;
     }
 
     .stat.lg .value {
@@ -132,6 +130,9 @@
         line-height: 28px;
     }
 
+    .stat-col {
+        margin:0 0 10px 0;
+    }
     .stat.lg .name {
         font-size: 16px;
     }
@@ -155,7 +156,7 @@
     }
 
     #statistics .panel h5 {
-        font-size: 13px;
+        font-size: 14px;
     }
 </style>
 <div class="panel panel-default panel-intro">
@@ -211,7 +212,7 @@
 
                 <div class="row">
                     <div class="col-lg-8">
-                        <div id="echart" class="btn-refresh" style="height:200px;width:100%;"></div>
+                        <div id="echart" class="btn-refresh" style="height:300px;width:100%;"></div>
                     </div>
                     <div class="col-lg-4">
                         <div class="card sameheight-item stats">
@@ -224,7 +225,7 @@
                                             <div class="name"> {:__('Today user signup')}</div>
                                         </div>
                                         <div class="progress">
-                                            <div class="progress-bar progress-bar-success" style="width: 30%"></div>
+                                            <div class="progress-bar progress-bar-success" style="width: 20%"></div>
                                         </div>
                                     </div>
                                     <div class="col-xs-6 stat-col">
@@ -234,7 +235,7 @@
                                             <div class="name"> {:__('Today user login')}</div>
                                         </div>
                                         <div class="progress">
-                                            <div class="progress-bar progress-bar-success" style="width: 25%"></div>
+                                            <div class="progress-bar progress-bar-success" style="width: 20%"></div>
                                         </div>
                                     </div>
                                     <div class="col-xs-6  stat-col">
@@ -244,7 +245,7 @@
                                             <div class="name"> {:__('Three dnu')}</div>
                                         </div>
                                         <div class="progress">
-                                            <div class="progress-bar progress-bar-success" style="width: 25%"></div>
+                                            <div class="progress-bar progress-bar-success" style="width: 20%"></div>
                                         </div>
                                     </div>
                                     <div class="col-xs-6 stat-col">
@@ -254,7 +255,7 @@
                                             <div class="name"> {:__('Seven dnu')}</div>
                                         </div>
                                         <div class="progress">
-                                            <div class="progress-bar progress-bar-success" style="width: 25%"></div>
+                                            <div class="progress-bar progress-bar-success" style="width: 20%"></div>
                                         </div>
                                     </div>
                                     <div class="col-xs-6  stat-col">
@@ -264,7 +265,7 @@
                                             <div class="name"> {:__('Seven dau')}</div>
                                         </div>
                                         <div class="progress">
-                                            <div class="progress-bar progress-bar-success" style="width: 25%"></div>
+                                            <div class="progress-bar progress-bar-success" style="width: 20%"></div>
                                         </div>
                                     </div>
                                     <div class="col-xs-6  stat-col">
@@ -274,7 +275,7 @@
                                             <div class="name"> {:__('Thirty dau')}</div>
                                         </div>
                                         <div class="progress">
-                                            <div class="progress-bar progress-bar-success" style="width: 25%"></div>
+                                            <div class="progress-bar progress-bar-success" style="width: 20%"></div>
                                         </div>
                                     </div>
                                 </div>
@@ -308,7 +309,7 @@
                         </div>
                     </div>
                     <div class="col-xs-6 col-md-3">
-                        <div class="panel bg-aqua-gradient no-border">
+                        <div class="panel bg-teal-gradient no-border">
                             <div class="panel-body">
                                 <div class="ibox-title">
                                     <span class="label label-primary pull-right">{:__('Real time')}</span>

+ 24 - 13
application/admin/view/general/attachment/add.html

@@ -3,16 +3,25 @@
     <div class="form-group">
         <label for="c-third" class="control-label col-xs-12 col-sm-2">{:__('Upload to third')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input type="text" name="row[third]" id="c-third" class="form-control" />
+            <input type="text" name="row[third]" id="c-third" class="form-control"/>
+            <ul class="row list-inline faupload-preview" id="p-third"></ul>
         </div>
     </div>
 
     <div class="form-group">
         <label for="c-third" class="control-label col-xs-12 col-sm-2"></label>
         <div class="col-xs-12 col-sm-8">
-            <button type="button" id="faupload-third" class="btn btn-danger faupload" data-multiple="true" data-input-id="c-third" ><i class="fa fa-upload"></i> {:__("Upload to third")}</button>
+            <div style="width:180px;display:inline-block;">
+                <select name="category-third" id="category-third" class="form-control selectpicker">
+                    <option value="">{:__('Please select category')}</option>
+                    {foreach name="categoryList" id="item"}
+                    <option value="{$key}">{$item}</option>
+                    {/foreach}
+                </select>
+            </div>
+            <button type="button" id="faupload-third" class="btn btn-danger faupload" data-multiple="true" data-input-id="c-third" data-preview-id="p-third"><i class="fa fa-upload"></i> {:__("Upload to third")}</button>
             {if $config.upload.chunking}
-            <button type="button" id="faupload-third-chunking" class="btn btn-danger faupload" data-chunking="true" data-maxsize="1gb" data-multiple="true" data-input-id="c-third" ><i class="fa fa-upload"></i> {:__("Upload to third by chunk")}</button>
+            <button type="button" id="faupload-third-chunking" class="btn btn-danger faupload" data-chunking="true" data-maxsize="1gb" data-multiple="true" data-input-id="c-third" data-preview-id="p-third"><i class="fa fa-upload"></i> {:__("Upload to third by chunk")}</button>
             {/if}
         </div>
     </div>
@@ -21,27 +30,29 @@
     <div class="form-group">
         <label for="c-local" class="control-label col-xs-12 col-sm-2">{:__('Upload to local')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input type="text" name="row[local]" id="c-local" class="form-control" />
+            <input type="text" name="row[local]" id="c-local" class="form-control"/>
+            <ul class="row list-inline faupload-preview" id="p-local"></ul>
         </div>
     </div>
 
     <div class="form-group">
         <label for="c-local" class="control-label col-xs-12 col-sm-2"></label>
         <div class="col-xs-12 col-sm-8">
-            <button type="button" id="faupload-local" class="btn btn-primary faupload" data-input-id="c-local" data-url="{:url('ajax/upload')}"><i class="fa fa-upload"></i> {:__("Upload to local")}</button>
+            <div style="width:180px;display:inline-block;">
+                <select name="category-local" id="category-local" class="form-control selectpicker">
+                    <option value="">{:__('Please select category')}</option>
+                    {foreach name="categoryList" id="item"}
+                    <option value="{$key}">{$item}</option>
+                    {/foreach}
+                </select>
+            </div>
+            <button type="button" id="faupload-local" class="btn btn-primary faupload" data-input-id="c-local" data-multiple="true" data-preview-id="p-local" data-url="{:url('ajax/upload')}"><i class="fa fa-upload"></i> {:__("Upload to local")}</button>
             {if $config.upload.chunking}
-            <button type="button" id="faupload-local-chunking" class="btn btn-primary faupload" data-chunking="true" data-maxsize="1gb" data-input-id="c-local" data-url="{:url('ajax/upload')}"><i class="fa fa-upload"></i> {:__("Upload to local by chunk")}</button>
+            <button type="button" id="faupload-local-chunking" class="btn btn-primary faupload" data-chunking="true" data-maxsize="1gb" data-input-id="c-local" data-multiple="true" data-preview-id="p-local" data-url="{:url('ajax/upload')}"><i class="fa fa-upload"></i> {:__("Upload to local by chunk")}</button>
             {/if}
         </div>
     </div>
 
-
-    <div class="form-group">
-        <label for="c-editor" class="control-label col-xs-12 col-sm-2">{:__('Upload from editor')}:</label>
-        <div class="col-xs-12 col-sm-8">
-            <textarea name="row[editor]" id="c-editor" cols="60" rows="5" class="form-control editor"></textarea>
-        </div>
-    </div>
     <div class="form-group hidden layer-footer">
         <div class="col-xs-2"></div>
         <div class="col-xs-12 col-sm-8">

+ 1 - 1
application/admin/view/general/attachment/edit.html

@@ -82,7 +82,7 @@
     <div class="form-group hide layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 19 - 4
application/admin/view/general/config/index.html

@@ -25,6 +25,7 @@
         .edit-form table tr th:nth-last-child(-n + 2), .edit-form table tr td:nth-last-child(-n + 2) {
             display: none;
         }
+
         .edit-form table tr td .msg-box {
             display: none;
         }
@@ -37,9 +38,11 @@
             {foreach $siteList as $index=>$vo}
             <li class="{$vo.active?'active':''}"><a href="#{$vo.name}" data-toggle="tab">{:__($vo.title)}</a></li>
             {/foreach}
+            {if $Think.config.app_debug}
             <li data-toggle="tooltip" title="{:__('Add new config')}">
                 <a href="#addcfg" data-toggle="tab"><i class="fa fa-plus"></i></a>
             </li>
+            {/if}
         </ul>
     </div>
 
@@ -56,8 +59,10 @@
                             <tr>
                                 <th width="15%">{:__('Title')}</th>
                                 <th width="68%">{:__('Value')}</th>
+                                {if $Think.config.app_debug}
                                 <th width="15%">{:__('Name')}</th>
                                 <th width="2%"></th>
+                                {/if}
                             </tr>
                             </thead>
                             <tbody>
@@ -174,8 +179,10 @@
                                     </div>
 
                                 </td>
+                                {if $Think.config.app_debug}
                                 <td>{php}echo "{\$site.". $item['name'] . "}";{/php}</td>
-                                <td>{if $item['id']>17}<a href="javascript:;" class="btn-delcfg text-muted" data-name="{$item.name}"><i class="fa fa-times"></i></a>{/if}</td>
+                                <td>{if $item['id']>18}<a href="javascript:;" class="btn-delcfg text-muted" data-name="{$item.name}"><i class="fa fa-times"></i></a>{/if}</td>
+                                {/if}
                             </tr>
                             {/foreach}
                             </tbody>
@@ -183,11 +190,15 @@
                             <tr>
                                 <td></td>
                                 <td>
-                                    <button type="submit" class="btn btn-success btn-embossed">{:__('OK')}</button>
-                                    <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+                                    <div class="layer-footer">
+                                        <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
+                                        <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+                                    </div>
                                 </td>
+                                {if $Think.config.app_debug}
                                 <td></td>
                                 <td></td>
+                                {/if}
                             </tr>
                             </tfoot>
                         </table>
@@ -321,8 +332,12 @@ value2|title2</textarea>
                     <div class="form-group">
                         <label class="control-label col-xs-12 col-sm-2"></label>
                         <div class="col-xs-12 col-sm-4">
-                            <button type="submit" class="btn btn-success btn-embossed">{:__('OK')}</button>
+                            {if !$Think.config.app_debug}
+                            <button type="button" class="btn btn-primary disabled">{:__('Only work at development environment')}</button>
+                            {else/}
+                            <button type="submit" class="btn btn-primary btn-embossed">{:__('OK')}</button>
                             <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+                            {/if}
                         </div>
                     </div>
 

+ 1 - 1
application/admin/view/index/index.html

@@ -4,7 +4,7 @@
         <!-- 加载样式及META信息 -->
         {include file="common/meta" /}
     </head>
-    <body class="hold-transition {$Think.config.fastadmin.adminskin|default='skin-black-green'} sidebar-mini fixed {:$Think.config.fastadmin.multipletab?'multipletab':''} {:$Think.config.fastadmin.multiplenav?'multiplenav':''}" id="tabs">
+    <body class="hold-transition {$Think.config.fastadmin.adminskin|default='skin-black-green'} sidebar-mini {:$Think.cookie.sidebar_collapse?'sidebar-collapse':''} fixed {:$Think.config.fastadmin.multipletab?'multipletab':''} {:$Think.config.fastadmin.multiplenav?'multiplenav':''}" id="tabs">
 
         <div class="wrapper">
 

+ 1 - 1
application/admin/view/user/group/add.html

@@ -31,7 +31,7 @@
     <div class="form-group layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 1 - 1
application/admin/view/user/group/edit.html

@@ -31,7 +31,7 @@
     <div class="form-group layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 1 - 1
application/admin/view/user/rule/add.html

@@ -46,7 +46,7 @@
     <div class="form-group layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 1 - 1
application/admin/view/user/rule/edit.html

@@ -45,7 +45,7 @@
     <div class="form-group layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 1 - 1
application/admin/view/user/user/edit.html

@@ -144,7 +144,7 @@
     <div class="form-group layer-footer">
         <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-success btn-embossed disabled">{:__('OK')}</button>
+            <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>

+ 32 - 1
application/common.php

@@ -258,7 +258,8 @@ if (!function_exists('addtion')) {
                 if (isset($v[$n])) {
                     $curr = array_flip(explode(',', $v[$n]));
 
-                    $v[$fieldsArr[$n]['display']] = implode(',', array_intersect_key($result[$n], $curr));
+                    $linedata = array_intersect_key($result[$n], $curr);
+                    $v[$fieldsArr[$n]['display']] = $fieldsArr[$n]['column'] == '*' ? $linedata : implode(',', $linedata);
                 }
             }
         }
@@ -481,3 +482,33 @@ if (!function_exists('check_ip_allowed')) {
         }
     }
 }
+
+if (!function_exists('build_suffix_image')) {
+    /**
+     * 生成文件后缀图片
+     * @param string $suffix 后缀
+     * @param null   $background
+     * @return string
+     */
+    function build_suffix_image($suffix, $background = null)
+    {
+        $suffix = mb_substr(strtoupper($suffix), 0, 4);
+        $total = unpack('L', hash('adler32', $suffix, true))[1];
+        $hue = $total % 360;
+        list($r, $g, $b) = hsv2rgb($hue / 360, 0.3, 0.9);
+
+        $background = $background ? $background : "rgb({$r},{$g},{$b})";
+
+        $icon = <<<EOT
+        <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+            <path style="fill:#E2E5E7;" d="M128,0c-17.6,0-32,14.4-32,32v448c0,17.6,14.4,32,32,32h320c17.6,0,32-14.4,32-32V128L352,0H128z"/>
+            <path style="fill:#B0B7BD;" d="M384,128h96L352,0v96C352,113.6,366.4,128,384,128z"/>
+            <polygon style="fill:#CAD1D8;" points="480,224 384,128 480,128 "/>
+            <path style="fill:{$background};" d="M416,416c0,8.8-7.2,16-16,16H48c-8.8,0-16-7.2-16-16V256c0-8.8,7.2-16,16-16h352c8.8,0,16,7.2,16,16 V416z"/>
+            <path style="fill:#CAD1D8;" d="M400,432H96v16h304c8.8,0,16-7.2,16-16v-16C416,424.8,408.8,432,400,432z"/>
+            <g><text><tspan x="220" y="380" font-size="124" font-family="Verdana, Helvetica, Arial, sans-serif" fill="white" text-anchor="middle">{$suffix}</tspan></text></g>
+        </svg>
+EOT;
+        return $icon;
+    }
+}

+ 2 - 4
application/common/library/Auth.php

@@ -227,9 +227,7 @@ class Auth
         }
 
         //直接登录会员
-        $this->direct($user->id);
-
-        return true;
+        return $this->direct($user->id);
     }
 
     /**
@@ -558,7 +556,7 @@ class Auth
     /**
      * 设置错误信息
      *
-     * @param $error 错误信息
+     * @param string $error 错误信息
      * @return Auth
      */
     public function setError($error)

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

@@ -18,7 +18,7 @@ class Email
     /**
      * phpmailer对象
      */
-    protected $mail = [];
+    protected $mail = null;
 
     /**
      * 错误内容
@@ -96,7 +96,7 @@ class Email
 
     /**
      * 设置收件人
-     * @param mixed  $email 收件人,多个收件人以,进行分隔
+     * @param mixed $email 收件人,多个收件人以,进行分隔
      * @return $this
      */
     public function to($email)
@@ -122,7 +122,7 @@ class Email
             $emailArr[key($emailArr)] = $name;
         }
         foreach ($emailArr as $address => $name) {
-            $this->mail->addCC($address, $name);
+            $this->mail->addCC($name, $address);
         }
         return $this;
     }

+ 14 - 12
application/common/library/Token.php

@@ -25,8 +25,8 @@ class Token
     /**
      * 连接Token驱动
      * @access public
-     * @param  array       $options 配置数组
-     * @param  bool|string $name    Token连接标识 true 强制重新连接
+     * @param array       $options 配置数组
+     * @param bool|string $name    Token连接标识 true 强制重新连接
      * @return Driver
      */
     public static function connect(array $options = [], $name = false)
@@ -58,7 +58,7 @@ class Token
     /**
      * 自动初始化Token
      * @access public
-     * @param  array $options 配置数组
+     * @param array $options 配置数组
      * @return Driver
      */
     public static function init(array $options = [])
@@ -81,7 +81,8 @@ class Token
     /**
      * 判断Token是否可用(check别名)
      * @access public
-     * @param  string $token Token标识
+     * @param string $token   Token标识
+     * @param int    $user_id 会员ID
      * @return bool
      */
     public static function has($token, $user_id)
@@ -91,7 +92,8 @@ class Token
 
     /**
      * 判断Token是否可用
-     * @param string $token Token标识
+     * @param string $token   Token标识
+     * @param int    $user_id 会员ID
      * @return bool
      */
     public static function check($token, $user_id)
@@ -102,8 +104,8 @@ class Token
     /**
      * 读取Token
      * @access public
-     * @param  string $token   Token标识
-     * @param  mixed  $default 默认值
+     * @param string $token   Token标识
+     * @param mixed  $default 默认值
      * @return mixed
      */
     public static function get($token, $default = false)
@@ -114,9 +116,9 @@ class Token
     /**
      * 写入Token
      * @access public
-     * @param  string   $token   Token标识
-     * @param  mixed    $user_id 存储数据
-     * @param  int|null $expire  有效时间 0为永久
+     * @param string   $token   Token标识
+     * @param mixed    $user_id 会员ID
+     * @param int|null $expire  有效时间 0为永久
      * @return boolean
      */
     public static function set($token, $user_id, $expire = null)
@@ -127,7 +129,7 @@ class Token
     /**
      * 删除Token(delete别名)
      * @access public
-     * @param  string $token Token标识
+     * @param string $token Token标识
      * @return boolean
      */
     public static function rm($token)
@@ -148,7 +150,7 @@ class Token
     /**
      * 清除Token
      * @access public
-     * @param  int user_id 用户编号
+     * @param int user_id 会员ID
      * @return boolean
      */
     public static function clear($user_id = null)

+ 53 - 13
application/common/library/Upload.php

@@ -16,18 +16,6 @@ use think\Hook;
 class Upload
 {
 
-    /**
-     * 验证码有效时长
-     * @var int
-     */
-    protected static $expire = 120;
-
-    /**
-     * 最大允许检测的次数
-     * @var int
-     */
-    protected static $maxCheckNums = 10;
-
     protected $merging = false;
 
     protected $chunkDir = null;
@@ -37,7 +25,7 @@ class Upload
     protected $error = '';
 
     /**
-     * @var \think\File
+     * @var File
      */
     protected $file = null;
     protected $fileInfo = null;
@@ -51,16 +39,29 @@ class Upload
         }
     }
 
+    /**
+     * 设置分片目录
+     * @param $dir
+     */
     public function setChunkDir($dir)
     {
         $this->chunkDir = $dir;
     }
 
+    /**
+     * 获取文件
+     * @return File
+     */
     public function getFile()
     {
         return $this->file;
     }
 
+    /**
+     * 设置文件
+     * @param $file
+     * @throws UploadException
+     */
     public function setFile($file)
     {
         if (empty($file)) {
@@ -79,6 +80,11 @@ class Upload
         $this->checkExecutable();
     }
 
+    /**
+     * 检测是否为可执行脚本
+     * @return bool
+     * @throws UploadException
+     */
     protected function checkExecutable()
     {
         //禁止上传PHP和HTML文件
@@ -88,6 +94,11 @@ class Upload
         return true;
     }
 
+    /**
+     * 检测文件类型
+     * @return bool
+     * @throws UploadException
+     */
     protected function checkMimetype()
     {
         $mimetypeArr = explode(',', strtolower($this->config['mimetype']));
@@ -105,6 +116,12 @@ class Upload
         throw new UploadException(__('Uploaded file format is limited'));
     }
 
+    /**
+     * 检测是否图片
+     * @param bool $force
+     * @return bool
+     * @throws UploadException
+     */
     protected function checkImage($force = false)
     {
         //验证是否为图片文件
@@ -121,6 +138,10 @@ class Upload
         }
     }
 
+    /**
+     * 检测文件大小
+     * @throws UploadException
+     */
     protected function checkSize()
     {
         preg_match('/([0-9\.]+)(\w+)/', $this->config['maxsize'], $matches);
@@ -135,11 +156,22 @@ class Upload
         }
     }
 
+    /**
+     * 获取后缀
+     * @return string
+     */
     public function getSuffix()
     {
         return $this->fileInfo['suffix'] ?: 'file';
     }
 
+    /**
+     * 获取存储的文件名
+     * @param string $savekey
+     * @param string $filename
+     * @param string $md5
+     * @return mixed|null
+     */
     public function getSavekey($savekey = null, $filename = null, $md5 = null)
     {
         if ($filename) {
@@ -384,11 +416,19 @@ class Upload
         return $attachment;
     }
 
+    /**
+     * 设置错误信息
+     * @param $msg
+     */
     public function setError($msg)
     {
         $this->error = $msg;
     }
 
+    /**
+     * 获取错误信息
+     * @return string
+     */
     public function getError()
     {
         return $this->error;

+ 4 - 0
application/common/model/Attachment.php

@@ -65,6 +65,10 @@ class Attachment extends Model
         return '';
     }
 
+    /**
+     * 获取Mimetype列表
+     * @return array
+     */
     public static function getMimetypeList()
     {
         $data = [

+ 15 - 11
application/common/model/Config.php

@@ -175,19 +175,23 @@ 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['thumbstyle'] = $uploadcfg['thumbstyle'] ?? '';
 
         $upload = [
-            'cdnurl'    => $uploadcfg['cdnurl'],
-            'uploadurl' => $uploadurl,
-            'bucket'    => 'local',
-            'maxsize'   => $uploadcfg['maxsize'],
-            'mimetype'  => $uploadcfg['mimetype'],
-            'chunking'  => $uploadcfg['chunking'],
-            'chunksize' => $uploadcfg['chunksize'],
-            'savekey'   => $uploadcfg['savekey'],
-            'multipart' => [],
-            'multiple'  => $uploadcfg['multiple'],
-            'storage'   => 'local'
+            'cdnurl'     => $uploadcfg['cdnurl'],
+            'uploadurl'  => $uploadurl,
+            'bucket'     => 'local',
+            'maxsize'    => $uploadcfg['maxsize'],
+            'mimetype'   => $uploadcfg['mimetype'],
+            'chunking'   => $uploadcfg['chunking'],
+            'chunksize'  => $uploadcfg['chunksize'],
+            'savekey'    => $uploadcfg['savekey'],
+            'multipart'  => [],
+            'multiple'   => $uploadcfg['multiple'],
+            'fullmode'   => $uploadcfg['fullmode'],
+            'thumbstyle' => $uploadcfg['thumbstyle'],
+            'storage'    => 'local'
         ];
         return $upload;
     }

+ 1 - 1
application/common/view/tpl/dispatch_jump.tpl

@@ -7,7 +7,7 @@
     <link rel="shortcut icon" href="__CDN__/assets/img/favicon.ico" />
     <style type="text/css">
         *{box-sizing:border-box;margin:0;padding:0;font-family:Lantinghei SC,Open Sans,Arial,Hiragino Sans GB,Microsoft YaHei,"微软雅黑",STHeiti,WenQuanYi Micro Hei,SimSun,sans-serif;-webkit-font-smoothing:antialiased}
-        body{padding:70px 50px;background:#edf1f4;font-weight:400;font-size:1pc;-webkit-text-size-adjust:none;color:#333}
+        body{padding:70px 50px;background:#f4f6f8;font-weight:400;font-size:1pc;-webkit-text-size-adjust:none;color:#333}
         a{outline:0;color:#3498db;text-decoration:none;cursor:pointer}
         .system-message{margin:20px auto;padding:50px 0px;background:#fff;box-shadow:0 0 30px hsla(0,0%,39%,.06);text-align:center;width:100%;border-radius:2px;}
         .system-message h1{margin:0;margin-bottom:9pt;color:#444;font-weight:400;font-size:30px}

+ 1 - 1
application/common/view/tpl/think_exception.tpl

@@ -39,7 +39,7 @@ $langSet == 'en' && $lang = array_combine(array_keys($lang), array_keys($lang));
         article,aside,details,figcaption,figure,footer,header,hgroup,nav,section {display:block;}
         html {font-size:16px;line-height:24px;width:100%;height:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;overflow-y:scroll;overflow-x:hidden;}
         img {vertical-align:middle;max-width:100%;height:auto;border:0;-ms-interpolation-mode:bicubic;}
-        body {min-height:100%;background:#edf1f4;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",微软雅黑,Arial,sans-serif;}
+        body {min-height:100%;background:#f4f6f8;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",微软雅黑,Arial,sans-serif;}
         .clearfix {clear:both;zoom:1;}
         .clearfix:before,.clearfix:after {content:"\0020";display:block;height:0;visibility:hidden;}
         .clearfix:after {clear:both;}

+ 9 - 1
application/extra/upload.php

@@ -21,7 +21,7 @@ return [
     /**
      * 可上传的文件类型
      */
-    'mimetype'  => 'jpg,png,bmp,jpeg,gif,zip,rar,xls,xlsx,wav,mp4,mp3,pdf',
+    'mimetype'  => 'jpg,png,bmp,jpeg,gif,webp,zip,rar,xls,xlsx,wav,mp4,mp3,webm,pdf',
     /**
      * 是否支持批量上传
      */
@@ -34,4 +34,12 @@ return [
      * 默认分片大小
      */
     'chunksize' => 2097152,
+    /**
+     * 完整URL模式
+     */
+    'fullmode' => false,
+    /**
+     * 缩略图样式
+     */
+    'thumbstyle' => '',
 ];

+ 26 - 8
application/index/controller/Ajax.php

@@ -4,6 +4,7 @@ namespace app\index\controller;
 
 use app\common\controller\Frontend;
 use think\Lang;
+use think\Response;
 
 /**
  * Ajax异步请求接口
@@ -21,18 +22,35 @@ class Ajax extends Frontend
      */
     public function lang()
     {
-        header('Content-Type: application/javascript');
-        header("Cache-Control: public");
-        header("Pragma: cache");
-
-        $offset = 30 * 60 * 60 * 24; // 缓存一个月
-        header("Expires: " . gmdate("D, d M Y H:i:s", time() + $offset) . " GMT");
+        $header = ['Content-Type' => 'application/javascript'];
+        if (!config('app_debug')) {
+            $offset = 30 * 60 * 60 * 24; // 缓存一个月
+            $header['Cache-Control'] = 'public';
+            $header['Pragma'] = 'cache';
+            $header['Expires'] = gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
+        }
 
         $controllername = input("controllername");
         $this->loadlang($controllername);
         //强制输出JSON Object
-        $result = jsonp(Lang::get(), 200, [], ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
-        return $result;
+        return jsonp(Lang::get(), 200, $header, ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
+    }
+
+    /**
+     * 生成后缀图标
+     */
+    public function icon()
+    {
+        $suffix = $this->request->request("suffix");
+        $suffix = $suffix ? $suffix : "FILE";
+        $data = build_suffix_image($suffix);
+        $header = ['Content-Type' => 'image/svg+xml'];
+        $offset = 30 * 60 * 60 * 24; // 缓存一个月
+        $header['Cache-Control'] = 'public';
+        $header['Pragma'] = 'cache';
+        $header['Expires'] = gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
+        $response = Response::create($data, '', 200, $header);
+        return $response;
     }
 
     /**

+ 4 - 0
application/index/controller/User.php

@@ -301,6 +301,10 @@ class User extends Frontend
                 $timeArr = explode(' - ', $filterArr['createtime']);
                 $where['createtime'] = ['between', [strtotime($timeArr[0]), strtotime($timeArr[1])]];
             }
+            $search = $this->request->get('search');
+            if ($search) {
+                $where['filename'] = ['like', '%' . $search . '%'];
+            }
 
             $model = new Attachment();
             $offset = $this->request->get("offset", 0);

+ 6 - 6
application/index/view/common/captcha.html

@@ -1,17 +1,17 @@
 <!--@formatter:off-->
 {if "[type]" == 'email'}
-    <input type="text" name="captcha" class="form-control input-lg" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_ems_correct')}, event=[event], email:#email)" />
+    <input type="text" name="captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_ems_correct')}, event=[event], email:#email)" />
     <span class="input-group-btn" style="padding:0;border:none;">
         <a href="javascript:;" class="btn btn-info btn-captcha btn-lg" data-url="{:url('api/ems/send')}" data-type="email" data-event="[event]">发送验证码</a>
     </span>
 {elseif "[type]" == 'mobile'/}
-    <input type="text" name="captcha" class="form-control input-lg" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_sms_correct')}, event=[event], mobile:#mobile)" />
+    <input type="text" name="captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_sms_correct')}, event=[event], mobile:#mobile)" />
     <span class="input-group-btn" style="padding:0;border:none;">
         <a href="javascript:;" class="btn btn-info btn-captcha btn-lg" data-url="{:url('api/sms/send')}" data-type="mobile" data-event="[event]">发送验证码</a>
     </span>
 {elseif "[type]" == 'wechat'/}
     {if get_addon_info('wechat')}
-        <input type="text" name="captcha" class="form-control input-lg" data-rule="required;length(4);remote({:addon_url('wechat/captcha/check')}, event=[event])" />
+        <input type="text" name="captcha" class="form-control" data-rule="required;length(4);remote({:addon_url('wechat/captcha/check')}, event=[event])" />
         <span class="input-group-btn" style="padding:0;border:none;">
             <a href="javascript:;" class="btn btn-info btn-captcha btn-lg" data-url="{:addon_url('wechat/captcha/send')}" data-type="wechat" data-event="[event]">获取验证码</a>
         </span>
@@ -19,9 +19,9 @@
         请在后台插件管理中安装《微信管理插件》
     {/if}
 {elseif "[type]" == 'text' /}
-    <input type="text" name="captcha" class="form-control input-lg" data-rule="required;length(4)" />
+    <input type="text" name="captcha" class="form-control" data-rule="required;length(4)" />
     <span class="input-group-btn" style="padding:0;border:none;">
-        <img src="{:captcha_src()}" width="100" height="40" onclick="this.src = '{:captcha_src()}?r=' + Math.random();"/>
+        <img src="{:captcha_src()}" width="100" height="32" onclick="this.src = '{:captcha_src()}?r=' + Math.random();"/>
     </span>
 {/if}
-<!--@formatter:on-->
+<!--@formatter:on-->

+ 4 - 3
application/index/view/layout/default.html

@@ -7,7 +7,7 @@
 
     <body>
 
-        <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+        <nav class="navbar navbar-white navbar-fixed-top" role="navigation">
             <div class="container">
                 <div class="navbar-header">
                     <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#header-navbar">
@@ -23,8 +23,9 @@
                         <li><a href="{:url('/')}">{:__('Home')}</a></li>
                         <li class="dropdown">
                             {if $user}
-                            <a href="{:url('user/index')}" class="dropdown-toggle" data-toggle="dropdown" style="padding-top: 10px;height: 50px;">
+                            <a href="{:url('user/index')}" class="dropdown-toggle" data-toggle="dropdown">
                                 <span class="avatar-img"><img src="{$user.avatar|htmlentities|cdnurl}" alt=""></span>
+                                <span class="visible-xs-inline-block" style="padding:5px;">{$user.nickname} <b class="caret"></b></span>
                             </a>
                             {else /}
                             <a href="{:url('user/index')}" class="dropdown-toggle" data-toggle="dropdown">{:__('User center')} <b class="caret"></b></a>
@@ -52,7 +53,7 @@
         </main>
 
         <footer class="footer" style="clear:both">
-            <p class="copyright">Copyright&nbsp;©&nbsp;2017-2020 {$site.name|htmlentities} All Rights Reserved <a href="https://beian.miit.gov.cn" target="_blank">{$site.beian|htmlentities}</a></p>
+            <p class="copyright">Copyright&nbsp;©&nbsp;{:date("Y")} {$site.name|htmlentities} All Rights Reserved <a href="https://beian.miit.gov.cn" target="_blank">{$site.beian|htmlentities}</a></p>
         </footer>
 
         {include file="common/script" /}

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

@@ -31,7 +31,7 @@
                         <div class="form-group normal-footer">
                             <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-success btn-embossed disabled">{:__('Submit')}</button>
+                                <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('Submit')}</button>
                                 <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
                             </div>
                         </div>
@@ -40,4 +40,4 @@
             </div>
         </div>
     </div>
-</div>
+</div>

+ 3 - 6
application/index/view/user/index.html

@@ -21,8 +21,7 @@
                 <div class="panel-body">
                     <h2 class="page-header">
                         {:__('Member center')}
-                        <a href="{:url('user/profile')}" class="btn btn-success pull-right"><i class="fa fa-pencil"></i>
-                            {:__('Profile')}</a>
+                        <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">
@@ -36,10 +35,8 @@
                                 <!-- Heading -->
                                 <h4><a href="{:url('user/profile')}">{$user.nickname|htmlentities}</a></h4>
                                 <!-- Paragraph -->
-                                <p>
-                                    <a href="{:url('user/profile')}">
-                                        {$user.bio|default=__("This guy hasn't written anything yet")|htmlentities}
-                                    </a>
+                                <p class="text-muted">
+                                    {$user.bio|default=__("This guy hasn't written anything yet")|htmlentities}
                                 </p>
                                 <!-- Success -->
                             </div>

+ 5 - 4
application/index/view/user/login.html

@@ -8,14 +8,14 @@
                 <div class="form-group">
                     <label class="control-label" for="account">{:__('Account')}</label>
                     <div class="controls">
-                        <input class="form-control input-lg" id="account" type="text" name="account" value="" data-rule="required" placeholder="{:__('Email/Mobile/Username')}" autocomplete="off">
+                        <input class="form-control" id="account" type="text" name="account" value="" data-rule="required" placeholder="{:__('Email/Mobile/Username')}" autocomplete="off">
                         <div class="help-block"></div>
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="control-label" for="password">{:__('Password')}</label>
                     <div class="controls">
-                        <input class="form-control input-lg" id="password" type="password" name="password" data-rule="required;password" placeholder="{:__('Password')}" autocomplete="off">
+                        <input class="form-control" id="password" type="password" name="password" data-rule="required;password" placeholder="{:__('Password')}" autocomplete="off">
                     </div>
                 </div>
                 <div class="form-group">
@@ -30,6 +30,7 @@
                 </div>
                 <div class="form-group">
                     <button type="submit" class="btn btn-primary btn-lg btn-block">{:__('Sign in')}</button>
+                    <a href="{:url('user/register')}?url={$url|urlencode|htmlentities}" class="btn btn-default btn-lg btn-block mt-3 no-border">还没有账号?点击注册</a>
                 </div>
             </form>
         </div>
@@ -68,7 +69,7 @@
                     <div class="input-group">
                         <input type="text" name="captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_ems_correct')}, event=resetpwd, email:#email)"/>
                         <span class="input-group-btn" style="padding:0;border:none;">
-                            <a href="javascript:;" class="btn btn-info btn-captcha" data-url="{:url('api/ems/send')}" data-type="email" data-event="resetpwd">{:__('Send verification code')}</a>
+                            <a href="javascript:;" class="btn btn-primary btn-captcha" data-url="{:url('api/ems/send')}" data-type="email" data-event="resetpwd">{:__('Send verification code')}</a>
                         </span>
                     </div>
                     <span class="msg-box"></span>
@@ -85,7 +86,7 @@
         <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">
-                <button type="submit" class="btn btn-md btn-info">{:__('Ok')}</button>
+                <button type="submit" class="btn btn-md btn-primary">{:__('Ok')}</button>
             </div>
         </div>
     </form>

+ 3 - 3
application/index/view/user/profile.html

@@ -95,7 +95,7 @@
                         <div class="form-group normal-footer">
                             <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-success btn-embossed disabled">{:__('Ok')}</button>
+                                <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>
@@ -134,7 +134,7 @@
             <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">
-                    <button type="submit" class="btn btn-md btn-info">{:__('Submit')}</button>
+                    <button type="submit" class="btn btn-md btn-primary">{:__('Submit')}</button>
                 </div>
             </div>
         </div>
@@ -168,7 +168,7 @@
             <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">
-                    <button type="submit" class="btn btn-md btn-info">{:__('Submit')}</button>
+                    <button type="submit" class="btn btn-md btn-primary">{:__('Submit')}</button>
                 </div>
             </div>
         </div>

+ 5 - 4
application/index/view/user/register.html

@@ -9,28 +9,28 @@
                 <div class="form-group">
                     <label class="control-label required">{:__('Email')}<span class="text-success"></span></label>
                     <div class="controls">
-                        <input type="text" name="email" id="email" data-rule="required;email" class="form-control input-lg" placeholder="{:__('Email')}">
+                        <input type="text" name="email" id="email" data-rule="required;email" class="form-control" placeholder="{:__('Email')}">
                         <p class="help-block"></p>
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="control-label">{:__('Username')}</label>
                     <div class="controls">
-                        <input type="text" id="username" name="username" data-rule="required;username" class="form-control input-lg" placeholder="{:__('Username must be 3 to 30 characters')}">
+                        <input type="text" id="username" name="username" data-rule="required;username" class="form-control" placeholder="{:__('Username must be 3 to 30 characters')}">
                         <p class="help-block"></p>
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="control-label">{:__('Password')}</label>
                     <div class="controls">
-                        <input type="password" id="password" name="password" data-rule="required;password" class="form-control input-lg" placeholder="{:__('Password must be 6 to 30 characters')}">
+                        <input type="password" id="password" name="password" data-rule="required;password" class="form-control" placeholder="{:__('Password must be 6 to 30 characters')}">
                         <p class="help-block"></p>
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="control-label">{:__('Mobile')}</label>
                     <div class="controls">
-                        <input type="text" id="mobile" name="mobile" data-rule="required;mobile" class="form-control input-lg" placeholder="{:__('Mobile')}">
+                        <input type="text" id="mobile" name="mobile" data-rule="required;mobile" class="form-control" placeholder="{:__('Mobile')}">
                         <p class="help-block"></p>
                     </div>
                 </div>
@@ -49,6 +49,7 @@
 
                 <div class="form-group">
                     <button type="submit" class="btn btn-primary btn-lg btn-block">{:__('Sign up')}</button>
+                    <a href="{:url('user/login')}?url={$url|urlencode|htmlentities}" class="btn btn-default btn-lg btn-block mt-3 no-border">已经有账号?点击登录</a>
                 </div>
             </form>
         </div>

+ 353 - 27
public/assets/css/backend.css

@@ -1,6 +1,6 @@
 @import url("../css/bootstrap.css");
 @import url("../css/fastadmin.css");
-@import url("../css/skins/skin-black-green.css");
+@import url("../css/skins/skin-black-blue.css");
 @import url("../css/iconfont.css");
 @import url("../libs/font-awesome/css/font-awesome.min.css");
 @import url("../libs/toastr/toastr.min.css");
@@ -12,19 +12,227 @@
 @import url("../libs/bootstrap-select/dist/css/bootstrap-select.min.css");
 @import url("../libs/fastadmin-selectpage/selectpage.css");
 @import url("../libs/bootstrap-slider/slider.css");
+.m-1 {
+  margin-top: 5px !important;
+  margin-right: 5px !important;
+  margin-bottom: 5px !important;
+  margin-left: 5px !important;
+}
+.mt-1 {
+  margin-top: 5px !important;
+}
+.mr-1 {
+  margin-right: 5px !important;
+}
+.mb-1 {
+  margin-bottom: 5px !important;
+}
+.ml-1 {
+  margin-left: 5px !important;
+}
+.mx-1 {
+  margin-left: 5px !important;
+  margin-right: 5px !important;
+}
+.my-1 {
+  margin-top: 5px !important;
+  margin-bottom: 5px !important;
+}
+.m-2 {
+  margin-top: 10px !important;
+  margin-right: 10px !important;
+  margin-bottom: 10px !important;
+  margin-left: 10px !important;
+}
+.mt-2 {
+  margin-top: 10px !important;
+}
+.mr-2 {
+  margin-right: 10px !important;
+}
+.mb-2 {
+  margin-bottom: 10px !important;
+}
+.ml-2 {
+  margin-left: 10px !important;
+}
+.mx-2 {
+  margin-left: 10px !important;
+  margin-right: 10px !important;
+}
+.my-2 {
+  margin-top: 10px !important;
+  margin-bottom: 10px !important;
+}
+.m-3 {
+  margin-top: 15px !important;
+  margin-right: 15px !important;
+  margin-bottom: 15px !important;
+  margin-left: 15px !important;
+}
+.mt-3 {
+  margin-top: 15px !important;
+}
+.mr-3 {
+  margin-right: 15px !important;
+}
+.mb-3 {
+  margin-bottom: 15px !important;
+}
+.ml-3 {
+  margin-left: 15px !important;
+}
+.mx-3 {
+  margin-left: 15px !important;
+  margin-right: 15px !important;
+}
+.my-3 {
+  margin-top: 15px !important;
+  margin-bottom: 15px !important;
+}
+.m-4 {
+  margin-top: 20px !important;
+  margin-right: 20px !important;
+  margin-bottom: 20px !important;
+  margin-left: 20px !important;
+}
+.mt-4 {
+  margin-top: 20px !important;
+}
+.mr-4 {
+  margin-right: 20px !important;
+}
+.mb-4 {
+  margin-bottom: 20px !important;
+}
+.ml-4 {
+  margin-left: 20px !important;
+}
+.mx-4 {
+  margin-left: 20px !important;
+  margin-right: 20px !important;
+}
+.my-4 {
+  margin-top: 20px !important;
+  margin-bottom: 20px !important;
+}
+.p-1 {
+  padding-top: 5px !important;
+  padding-right: 5px !important;
+  padding-bottom: 5px !important;
+  padding-left: 5px !important;
+}
+.pt-1 {
+  padding-top: 5px !important;
+}
+.pr-1 {
+  padding-right: 5px !important;
+}
+.pb-1 {
+  padding-bottom: 5px !important;
+}
+.pl-1 {
+  padding-left: 5px !important;
+}
+.px-1 {
+  padding-left: 5px !important;
+  padding-right: 5px !important;
+}
+.py-1 {
+  padding-top: 5px !important;
+  padding-bottom: 5px !important;
+}
+.p-2 {
+  padding-top: 10px !important;
+  padding-right: 10px !important;
+  padding-bottom: 10px !important;
+  padding-left: 10px !important;
+}
+.pt-2 {
+  padding-top: 10px !important;
+}
+.pr-2 {
+  padding-right: 10px !important;
+}
+.pb-2 {
+  padding-bottom: 10px !important;
+}
+.pl-2 {
+  padding-left: 10px !important;
+}
+.px-2 {
+  padding-left: 10px !important;
+  padding-right: 10px !important;
+}
+.py-2 {
+  padding-top: 10px !important;
+  padding-bottom: 10px !important;
+}
+.p-3 {
+  padding-top: 15px !important;
+  padding-right: 15px !important;
+  padding-bottom: 15px !important;
+  padding-left: 15px !important;
+}
+.pt-3 {
+  padding-top: 15px !important;
+}
+.pr-3 {
+  padding-right: 15px !important;
+}
+.pb-3 {
+  padding-bottom: 15px !important;
+}
+.pl-3 {
+  padding-left: 15px !important;
+}
+.px-3 {
+  padding-left: 15px !important;
+  padding-right: 15px !important;
+}
+.py-3 {
+  padding-top: 15px !important;
+  padding-bottom: 15px !important;
+}
+.p-4 {
+  padding-top: 20px !important;
+  padding-right: 20px !important;
+  padding-bottom: 20px !important;
+  padding-left: 20px !important;
+}
+.pt-4 {
+  padding-top: 20px !important;
+}
+.pr-4 {
+  padding-right: 20px !important;
+}
+.pb-4 {
+  padding-bottom: 20px !important;
+}
+.pl-4 {
+  padding-left: 20px !important;
+}
+.px-4 {
+  padding-left: 20px !important;
+  padding-right: 20px !important;
+}
+.py-4 {
+  padding-top: 20px !important;
+  padding-bottom: 20px !important;
+}
 html,
 body {
   height: 100%;
 }
 body {
   background: #f1f4f6;
-  font-size: 13px;
+  font-size: 14px;
+  line-height: 1.5715;
 }
 body.is-dialog {
   background: #fff;
 }
 .dropdown-menu > li > a {
-  font-size: 13px;
   padding: 5px 12px;
 }
 .selection {
@@ -36,10 +244,10 @@ body.is-dialog {
   position: relative;
 }
 .main-header .navbar .dropdown-menu {
-  font-size: 13px;
+  font-size: 14px;
 }
 .main-header .navbar .dropdown-menu > li > a {
-  padding: 5px 20px;
+  padding: 8px 15px;
 }
 .bootstrap-dialog .modal-dialog {
   /*width: 70%;*/
@@ -96,11 +304,11 @@ html.ios-fix body .tab-pane {
 @media (max-width: 991px) {
   .main-header .navbar-custom-menu a.btn-danger {
     color: #fff;
-    background-color: #e74c3c;
+    background-color: #f75444;
   }
   .main-header .navbar-custom-menu a.btn-primary {
     color: #fff;
-    background-color: #2c3e50;
+    background-color: #444c69;
   }
 }
 .common-search-table {
@@ -139,6 +347,9 @@ table.table-template {
   .sp_container .sp_element_box .msg-box {
     left: inherit;
   }
+  .card-views .card-view {
+    padding: 5px 0;
+  }
 }
 .toast-top-right-index {
   top: 62px;
@@ -148,7 +359,7 @@ table.table-template {
   background: #f0f0f0;
   clear: both;
   color: #999;
-  font-size: 12px;
+  font-size: 13px;
   font-weight: 500;
   line-height: 1;
   margin-bottom: -5px;
@@ -485,7 +696,7 @@ form.form-horizontal .control-label {
   height: 100%;
   padding: 0;
   line-height: 28px;
-  font-size: 12px;
+  font-size: 13px;
   vertical-align: middle;
   opacity: 1;
   overflow: hidden;
@@ -589,25 +800,20 @@ form.form-horizontal .control-label {
 .sidebar-menu > li .badge {
   margin-top: 0;
 }
-.sidebar-menu .treeview-menu > li > a {
-  font-size: inherit;
-}
 .sidebar-collapse .user-panel > .image img {
   width: 25px;
   height: 25px;
 }
 @media (min-width: 768px) {
-  .sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > .treeview-menu {
-    top: 42px;
-  }
   .sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > .pull-right-container {
     top: 7px !important;
+    right: 10px;
     height: 17px;
   }
 }
 .fieldlist dd {
   display: block;
-  margin: 5px 0;
+  margin: 8px 0;
 }
 .fieldlist dd input {
   display: inline-block;
@@ -620,7 +826,6 @@ form.form-horizontal .control-label {
   width: 110px;
   display: inline-block;
   text-decoration: none;
-  font-weight: bold;
 }
 /* 弹窗中的表单 */
 .form-layer {
@@ -759,8 +964,15 @@ form.form-horizontal .control-label {
   padding: 10px 0;
 }
 .bootstrap-table .fixed-table-toolbar .dropdown-menu {
+  overflow: inherit;
+}
+.bootstrap-table .fixed-table-toolbar .columns-right .dropdown-menu {
   overflow: auto;
 }
+.bootstrap-table .bs-bars .fixed-table-toolbar .dropdown-menu > li:hover > a {
+  background-color: #e1e3e9;
+  color: #333;
+}
 .bootstrap-table .fa-toggle-on.fa-2x {
   font-size: 1.86em;
 }
@@ -769,6 +981,18 @@ form.form-horizontal .control-label {
   margin-right: 0;
   white-space: nowrap;
 }
+.bootstrap-table .table:not(.table-condensed) > tbody > tr > th,
+.bootstrap-table .table:not(.table-condensed) > tfoot > tr > th,
+.bootstrap-table .table:not(.table-condensed) > thead > tr > td,
+.bootstrap-table .table:not(.table-condensed) > tbody > tr > td,
+.bootstrap-table .table:not(.table-condensed) > tfoot > tr > td {
+  padding: 8px 15px;
+  height: 47px;
+}
+.fixed-table-container tbody td .th-inner,
+.fixed-table-container thead th .th-inner {
+  padding: 8px 10px;
+}
 .toolbar {
   margin-top: 10px;
   margin-bottom: 10px;
@@ -793,7 +1017,7 @@ table.table-nowrap thead > tr > th {
   white-space: nowrap;
 }
 .fixed-table-container thead th .sortable {
-  padding-right: 0;
+  padding: 8px 15px;
 }
 .dropdown-submenu {
   position: relative;
@@ -910,11 +1134,12 @@ table.table-nowrap thead > tr > th {
 }
 .layui-layer-fast .layui-layer-btn a {
   background-color: #95a5a6;
-  border-color: #95a5a6;
   color: #fff !important;
-  height: 31px;
+  height: 32px;
+  line-height: 32px;
   margin-top: 0;
-  border: 1px solid transparent;
+  font-size: 13px;
+  border: none;
 }
 .layui-layer-fast .layui-layer-btn .layui-layer-btn0 {
   background-color: #18bc9c;
@@ -924,16 +1149,30 @@ table.table-nowrap thead > tr > th {
   padding: 8px 20px;
   background-color: #ecf0f1;
   height: auto;
+  min-height: 53px;
   text-align: inherit !important;
 }
 .layui-layer-fast .layui-layer-confirm {
   position: absolute;
-  width: 1px;
-  height: 1px;
+  width: 100%;
+  height: 100%;
   left: 0;
   bottom: 0;
-  border: none;
+  border: 1px solid transparent;
   background: transparent;
+  color: transparent;
+}
+.layui-layer-fast .layui-layer-confirm:focus {
+  border: 1px solid #444c69;
+  -webkit-border-radius: 2px;
+  -webkit-background-clip: padding-box;
+  -moz-border-radius: 2px;
+  -moz-background-clip: padding;
+  border-radius: 2px;
+  background-clip: padding-box;
+}
+.layui-layer-fast .layui-layer-confirm:focus-visible {
+  outline: 0;
 }
 .layui-layer-fast .layui-layer-tab .layui-layer-title span.layui-this {
   height: 46px;
@@ -1030,6 +1269,9 @@ table.table-nowrap thead > tr > th {
   .fixed-table-toolbar > .bs-bars {
     float: none !important;
   }
+  .fixed-table-toolbar .toolbar .btn {
+    min-height: 33px;
+  }
   .fixed-table-toolbar .toolbar a.btn-refresh,
   .fixed-table-toolbar .toolbar a.btn-del,
   .fixed-table-toolbar .toolbar a.btn-add,
@@ -1037,7 +1279,8 @@ table.table-nowrap thead > tr > th {
   .fixed-table-toolbar .toolbar a.btn-import,
   .fixed-table-toolbar .toolbar a.btn-more,
   .fixed-table-toolbar .toolbar a.btn-recyclebin,
-  .fixed-table-toolbar .toolbar .btn-mini-xs {
+  .fixed-table-toolbar .toolbar .btn-mini-xs,
+  .fixed-table-toolbar .toolbar .btn-multi {
     font-size: 0;
   }
   .fixed-table-toolbar .toolbar a.btn-refresh .fa,
@@ -1047,7 +1290,8 @@ table.table-nowrap thead > tr > th {
   .fixed-table-toolbar .toolbar a.btn-import .fa,
   .fixed-table-toolbar .toolbar a.btn-more .fa,
   .fixed-table-toolbar .toolbar a.btn-recyclebin .fa,
-  .fixed-table-toolbar .toolbar .btn-mini-xs .fa {
+  .fixed-table-toolbar .toolbar .btn-mini-xs .fa,
+  .fixed-table-toolbar .toolbar .btn-multi .fa {
     font-size: initial;
   }
   .fixed-table-toolbar .search {
@@ -1148,7 +1392,6 @@ table.table-nowrap thead > tr > th {
   color: #d2d6de !important;
 }
 .jumpto input {
-  height: 31px;
   width: 50px;
   margin-left: 5px;
   margin-right: 5px;
@@ -1199,4 +1442,87 @@ table.table-nowrap thead > tr > th {
   -o-transform: rotate(-90deg);
   transform: rotate(-90deg);
 }
+.sidebar-menu .treeview-menu > li {
+  margin: 4px 0 4px 0;
+}
+.bootstrap-tagsinput {
+  background-color: #fff;
+  border: 1px solid #ccc;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  display: inline-block;
+  padding: 4px 6px;
+  margin-bottom: 10px;
+  color: #555;
+  vertical-align: middle;
+  width: 100%;
+  line-height: 22px;
+  cursor: text;
+}
+.bootstrap-tagsinput input {
+  border: none;
+  box-shadow: none;
+  outline: none;
+  background-color: transparent;
+  padding: 0;
+  margin: 0;
+  font-size: 13px;
+  width: 80px;
+  max-width: inherit;
+}
+.bootstrap-tagsinput input:focus {
+  border: none;
+  box-shadow: none;
+}
+.bootstrap-tagsinput .tagsinput-text {
+  display: inline-block;
+  overflow: auto;
+  visibility: hidden;
+  height: 1px;
+  position: absolute;
+  bottom: -1px;
+  left: 0;
+}
+.bootstrap-tagsinput .tag {
+  margin-right: 2px;
+  color: white;
+}
+.bootstrap-tagsinput .tag [data-role="remove"] {
+  margin-left: 5px;
+  cursor: pointer;
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:after {
+  content: "x";
+  padding: 0px 2px;
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:hover {
+  background-color: rgba(255, 255, 255, 0.16);
+}
+.autocomplete-suggestions {
+  border-radius: 2px;
+  background: #FFF;
+  overflow: auto;
+  min-width: 200px;
+  -webkit-box-shadow: 0px 20px 30px rgba(83, 88, 93, 0.05), 0px 0px 30px rgba(83, 88, 93, 0.1);
+  -moz-box-shadow: 0px 20px 30px rgba(83, 88, 93, 0.05), 0px 0px 30px rgba(83, 88, 93, 0.1);
+  box-shadow: 0px 20px 30px rgba(83, 88, 93, 0.05), 0px 0px 30px rgba(83, 88, 93, 0.1);
+}
+.autocomplete-suggestions strong {
+  font-weight: normal;
+  color: red;
+}
+.autocomplete-suggestions .autocomplete-suggestion {
+  padding: 5px 10px;
+  white-space: nowrap;
+  overflow: hidden;
+}
+.autocomplete-suggestions .autocomplete-selected {
+  background: #F0F0F0;
+}
+.autocomplete-suggestions .autocomplete-group {
+  padding: 5px 10px;
+}
+.autocomplete-suggestions .autocomplete-group strong {
+  display: block;
+  border-bottom: 1px solid #ddd;
+}
 /*# sourceMappingURL=backend.css.map */

File diff suppressed because it is too large
+ 1 - 1
public/assets/css/backend.min.css


File diff suppressed because it is too large
+ 239 - 239
public/assets/css/bootstrap.css


+ 161 - 144
public/assets/css/fastadmin.css

@@ -168,14 +168,14 @@ h6,
 }
 /* General Links */
 a {
-  color: #3c8dbc;
+  color: #4397fd;
 }
 a:hover,
 a:active,
 a:focus {
   outline: none;
   text-decoration: none;
-  color: #72afd2;
+  color: #8fc1fe;
 }
 /* Page Header */
 .page-header {
@@ -255,7 +255,7 @@ a:focus {
   float: left;
   background-color: transparent;
   background-image: none;
-  padding: 16.5px 15px;
+  padding: 16px 15px;
   font-family: fontAwesome;
 }
 .main-header .sidebar-toggle:before {
@@ -367,7 +367,7 @@ a:focus {
   color: #fff;
   border: 0;
   margin: 0;
-  padding: 16.5px 15px;
+  padding: 16px 15px;
 }
 @media (max-width: 991px) {
   .navbar-custom-menu .navbar-nav > li {
@@ -522,7 +522,7 @@ a:focus {
 }
 .sidebar-menu > li {
   position: relative;
-  margin: 0;
+  margin: 5px 0;
   padding: 0;
 }
 .sidebar-menu > li > a {
@@ -565,20 +565,27 @@ a:focus {
 .sidebar-menu .treeview-menu {
   display: none;
   list-style: none;
-  padding: 0;
+  padding: 0px 0;
   margin: 0;
   padding-left: 5px;
 }
 .sidebar-menu .treeview-menu .treeview-menu {
   padding-left: 20px;
 }
+.sidebar-menu .treeview-menu:before,
+.sidebar-menu .treeview-menu:after {
+  content: "";
+  display: table;
+}
+.sidebar-menu .treeview-menu.menu-open {
+  display: block;
+}
 .sidebar-menu .treeview-menu > li {
   margin: 0;
 }
 .sidebar-menu .treeview-menu > li > a {
   padding: 12px 5px 12px 15px;
   display: block;
-  font-size: 12px;
 }
 .sidebar-menu .treeview-menu > li > a > .fa,
 .sidebar-menu .treeview-menu > li > a > .glyphicon,
@@ -595,6 +602,9 @@ a:focus {
  * Component: Sidebar Mini
  */
 @media (min-width: 768px) {
+  .sidebar-mini.sidebar-collapse .sidebar-menu:hover {
+    overflow: visible;
+  }
   .sidebar-mini.sidebar-collapse .content-wrapper,
   .sidebar-mini.sidebar-collapse .right-side,
   .sidebar-mini.sidebar-collapse .main-footer {
@@ -615,16 +625,26 @@ a:focus {
   .sidebar-mini.sidebar-collapse .sidebar-menu > li > a {
     margin-right: 0;
   }
-  .sidebar-mini.sidebar-collapse .sidebar-menu > li > a > span {
-    border-top-right-radius: 4px;
-  }
-  .sidebar-mini.sidebar-collapse .sidebar-menu > li:not(.treeview) > a > span {
-    border-bottom-right-radius: 4px;
-  }
   .sidebar-mini.sidebar-collapse .sidebar-menu > li > .treeview-menu {
     padding-top: 5px;
     padding-bottom: 5px;
-    border-bottom-right-radius: 4px;
+    scrollbar-width: thin;
+    scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
+    /* Works on Chrome, Edge, and Safari */
+  }
+  .sidebar-mini.sidebar-collapse .sidebar-menu > li > .treeview-menu::-webkit-scrollbar {
+    width: 8px;
+  }
+  .sidebar-mini.sidebar-collapse .sidebar-menu > li > .treeview-menu::-webkit-scrollbar-track {
+    background: transparent;
+  }
+  .sidebar-mini.sidebar-collapse .sidebar-menu > li > .treeview-menu::-webkit-scrollbar-thumb {
+    background-color: rgba(255, 255, 255, 0.15);
+    border-radius: 20px;
+    border: 3px solid transparent;
+  }
+  .sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a {
+    width: 230px;
   }
   .sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right),
   .sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > .treeview-menu {
@@ -635,21 +655,21 @@ a:focus {
   }
   .sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span {
     top: 0;
-    margin-left: -3px;
     padding: 12px 5px 12px 20px;
     background-color: inherit;
   }
   .sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > .pull-right-container {
+    display: block!important;
     float: right;
     width: auto!important;
-    left: 200px!important;
+    left: 195px!important;
     top: 10px!important;
   }
   .sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > .pull-right-container > .label:not(:first-of-type) {
     display: none;
   }
   .sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > .treeview-menu {
-    top: 44px;
+    top: 46px;
     margin-left: 0;
   }
   .sidebar-mini.sidebar-collapse .main-sidebar .user-panel > .info,
@@ -683,9 +703,6 @@ a:focus {
   white-space: nowrap;
   overflow: hidden;
 }
-.sidebar-menu:hover {
-  overflow: visible;
-}
 .sidebar-form,
 .sidebar-menu > li.header {
   overflow: hidden;
@@ -1333,7 +1350,7 @@ a:focus {
   appearance: none;
 }
 .form-control:focus {
-  border-color: #3c8dbc;
+  border-color: #4397fd;
   box-shadow: none;
 }
 .form-control::-moz-placeholder,
@@ -1375,15 +1392,15 @@ select.form-control {
   color: #f39c12;
 }
 .form-group.has-error label {
-  color: #e74c3c;
+  color: #f75444;
 }
 .form-group.has-error .form-control,
 .form-group.has-error .input-group-addon {
-  border-color: #e74c3c;
+  border-color: #f75444;
   box-shadow: none;
 }
 .form-group.has-error .help-block {
-  color: #e74c3c;
+  color: #f75444;
 }
 /* Input group */
 .input-group .input-group-addon {
@@ -1401,17 +1418,17 @@ select.form-control {
 }
 /* support Font Awesome icons in form-control */
 .form-control-feedback.fa {
-  line-height: 31px;
+  line-height: 33px;
 }
 .input-lg + .form-control-feedback.fa,
 .input-group-lg + .form-control-feedback.fa,
 .form-group-lg .form-control + .form-control-feedback.fa {
-  line-height: 42px;
+  line-height: 45px;
 }
 .input-sm + .form-control-feedback.fa,
 .input-group-sm + .form-control-feedback.fa,
 .form-group-sm .form-control + .form-control-feedback.fa {
-  line-height: 28px;
+  line-height: 30px;
 }
 /*
  * Component: Progress Bar
@@ -1496,7 +1513,7 @@ select.form-control {
 }
 .progress-bar-light-blue,
 .progress-bar-primary {
-  background-color: #3c8dbc;
+  background-color: #4397fd;
 }
 .progress-striped .progress-bar-light-blue,
 .progress-striped .progress-bar-primary {
@@ -1516,7 +1533,7 @@ select.form-control {
 }
 .progress-bar-aqua,
 .progress-bar-info {
-  background-color: #3498db;
+  background-color: #1688f1;
 }
 .progress-striped .progress-bar-aqua,
 .progress-striped .progress-bar-info {
@@ -1536,7 +1553,7 @@ select.form-control {
 }
 .progress-bar-red,
 .progress-bar-danger {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .progress-striped .progress-bar-red,
 .progress-striped .progress-bar-danger {
@@ -1636,13 +1653,13 @@ select.form-control {
   box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
 }
 .box.box-primary {
-  border-top-color: #3c8dbc;
+  border-top-color: #4397fd;
 }
 .box.box-info {
-  border-top-color: #3498db;
+  border-top-color: #1688f1;
 }
 .box.box-danger {
-  border-top-color: #e74c3c;
+  border-top-color: #f75444;
 }
 .box.box-warning {
   border-top-color: #f39c12;
@@ -1697,36 +1714,36 @@ select.form-control {
   color: #444;
 }
 .box.box-solid.box-primary {
-  border: 1px solid #3c8dbc;
+  border: 1px solid #4397fd;
 }
 .box.box-solid.box-primary > .box-header {
   color: #fff;
-  background: #3c8dbc;
-  background-color: #3c8dbc;
+  background: #4397fd;
+  background-color: #4397fd;
 }
 .box.box-solid.box-primary > .box-header a,
 .box.box-solid.box-primary > .box-header .btn {
   color: #fff;
 }
 .box.box-solid.box-info {
-  border: 1px solid #3498db;
+  border: 1px solid #1688f1;
 }
 .box.box-solid.box-info > .box-header {
   color: #fff;
-  background: #3498db;
-  background-color: #3498db;
+  background: #1688f1;
+  background-color: #1688f1;
 }
 .box.box-solid.box-info > .box-header a,
 .box.box-solid.box-info > .box-header .btn {
   color: #fff;
 }
 .box.box-solid.box-danger {
-  border: 1px solid #e74c3c;
+  border: 1px solid #f75444;
 }
 .box.box-solid.box-danger > .box-header {
   color: #fff;
-  background: #e74c3c;
-  background-color: #e74c3c;
+  background: #f75444;
+  background-color: #f75444;
 }
 .box.box-solid.box-danger > .box-header a,
 .box.box-solid.box-danger > .box-header .btn {
@@ -2013,7 +2030,7 @@ select.form-control {
 .todo-list > li .tools {
   display: none;
   float: right;
-  color: #e74c3c;
+  color: #f75444;
 }
 .todo-list > li .tools > .fa,
 .todo-list > li .tools > .glyphicon,
@@ -2035,19 +2052,19 @@ select.form-control {
   background: #d2d6de !important;
 }
 .todo-list .danger {
-  border-left-color: #e74c3c;
+  border-left-color: #f75444;
 }
 .todo-list .warning {
   border-left-color: #f39c12;
 }
 .todo-list .info {
-  border-left-color: #3498db;
+  border-left-color: #1688f1;
 }
 .todo-list .success {
   border-left-color: #18bc9c;
 }
 .todo-list .primary {
-  border-left-color: #3c8dbc;
+  border-left-color: #4397fd;
 }
 .todo-list .handle {
   display: inline-block;
@@ -2087,7 +2104,7 @@ select.form-control {
   border: 2px solid #18bc9c;
 }
 .chat .item > .offline {
-  border: 2px solid #e74c3c;
+  border: 2px solid #f75444;
 }
 .chat .item > .message {
   margin-left: 55px;
@@ -2521,13 +2538,13 @@ select.form-control {
   background-color: #fff;
 }
 .callout.callout-danger {
-  border-color: #d62c1a;
+  border-color: #f52713;
 }
 .callout.callout-warning {
   border-color: #c87f0a;
 }
 .callout.callout-info {
-  border-color: #217dbb;
+  border-color: #0c6ec8;
 }
 .callout.callout-success {
   border-color: #128f76;
@@ -2563,13 +2580,13 @@ select.form-control {
 }
 .alert-danger,
 .alert-error {
-  border-color: #e43725;
+  border-color: #f63e2c;
 }
 .alert-warning {
   border-color: #e08e0b;
 }
 .alert-info {
-  border-color: #258cd1;
+  border-color: #0d7be0;
 }
 .alert-primary-light {
   background-color: #E2E5E8;
@@ -2637,7 +2654,7 @@ select.form-control {
 .nav-pills > li.active > a,
 .nav-pills > li.active > a:hover,
 .nav-pills > li.active > a:focus {
-  border-top-color: #3c8dbc;
+  border-top-color: #4397fd;
 }
 .nav-pills > li.active > a {
   font-weight: 600;
@@ -2654,7 +2671,7 @@ select.form-control {
   background: transparent;
   color: #444;
   border-top: 0;
-  border-left-color: #3c8dbc;
+  border-left-color: #4397fd;
 }
 .nav-stacked > li.header {
   border-bottom: 1px solid #ddd;
@@ -2702,7 +2719,7 @@ select.form-control {
   border-color: transparent;
 }
 .nav-tabs-custom > .nav-tabs > li.active {
-  border-top-color: #3c8dbc;
+  border-top-color: #4397fd;
 }
 .nav-tabs-custom > .nav-tabs > li.active > a,
 .nav-tabs-custom > .nav-tabs > li.active:hover > a {
@@ -2759,13 +2776,13 @@ select.form-control {
   color: #999;
 }
 .nav-tabs-custom.tab-primary > .nav-tabs > li.active {
-  border-top-color: #3c8dbc;
+  border-top-color: #4397fd;
 }
 .nav-tabs-custom.tab-info > .nav-tabs > li.active {
-  border-top-color: #3498db;
+  border-top-color: #1688f1;
 }
 .nav-tabs-custom.tab-danger > .nav-tabs > li.active {
-  border-top-color: #e74c3c;
+  border-top-color: #f75444;
 }
 .nav-tabs-custom.tab-warning > .nav-tabs > li.active {
   border-top-color: #f39c12;
@@ -3082,22 +3099,22 @@ table.text-center th {
   color: #999;
 }
 .direct-chat-danger .right > .direct-chat-text {
-  background: #e74c3c;
-  border-color: #e74c3c;
+  background: #f75444;
+  border-color: #f75444;
   color: #fff;
 }
 .direct-chat-danger .right > .direct-chat-text:after,
 .direct-chat-danger .right > .direct-chat-text:before {
-  border-left-color: #e74c3c;
+  border-left-color: #f75444;
 }
 .direct-chat-primary .right > .direct-chat-text {
-  background: #3c8dbc;
-  border-color: #3c8dbc;
+  background: #4397fd;
+  border-color: #4397fd;
   color: #fff;
 }
 .direct-chat-primary .right > .direct-chat-text:after,
 .direct-chat-primary .right > .direct-chat-text:before {
-  border-left-color: #3c8dbc;
+  border-left-color: #4397fd;
 }
 .direct-chat-warning .right > .direct-chat-text {
   background: #f39c12;
@@ -3109,13 +3126,13 @@ table.text-center th {
   border-left-color: #f39c12;
 }
 .direct-chat-info .right > .direct-chat-text {
-  background: #3498db;
-  border-color: #3498db;
+  background: #1688f1;
+  border-color: #1688f1;
   color: #fff;
 }
 .direct-chat-info .right > .direct-chat-text:after,
 .direct-chat-info .right > .direct-chat-text:before {
-  border-left-color: #3498db;
+  border-left-color: #1688f1;
 }
 .direct-chat-success .right > .direct-chat-text {
   background: #18bc9c;
@@ -3203,7 +3220,7 @@ table.text-center th {
 }
 .modal-primary .modal-header,
 .modal-primary .modal-footer {
-  border-color: #307095;
+  border-color: #117bfc;
 }
 .modal-warning .modal-header,
 .modal-warning .modal-footer {
@@ -3211,7 +3228,7 @@ table.text-center th {
 }
 .modal-info .modal-header,
 .modal-info .modal-footer {
-  border-color: #217dbb;
+  border-color: #0c6ec8;
 }
 .modal-success .modal-header,
 .modal-success .modal-footer {
@@ -3219,7 +3236,7 @@ table.text-center th {
 }
 .modal-danger .modal-header,
 .modal-danger .modal-footer {
-  border-color: #d62c1a;
+  border-color: #f52713;
 }
 /*
  * Component: Social Widgets
@@ -3285,7 +3302,7 @@ table.text-center th {
 .close,
 .mailbox-attachment-close {
   float: right;
-  font-size: 18px;
+  font-size: 19.5px;
   font-weight: bold;
   line-height: 1;
   color: #000;
@@ -3632,7 +3649,7 @@ button.close {
  */
 .btn-social {
   position: relative;
-  padding-left: 41px;
+  padding-left: 42px;
   text-align: left;
   white-space: nowrap;
   overflow: hidden;
@@ -3643,45 +3660,45 @@ button.close {
   left: 0;
   top: 0;
   bottom: 0;
-  width: 29px;
-  line-height: 31px;
+  width: 30px;
+  line-height: 32px;
   font-size: 1.6em;
   text-align: center;
   border-right: 1px solid rgba(0, 0, 0, 0.2);
 }
 .btn-social.btn-lg {
-  padding-left: 57px;
+  padding-left: 60px;
 }
 .btn-social.btn-lg > :first-child {
-  line-height: 41px;
-  width: 41px;
+  line-height: 44px;
+  width: 44px;
   font-size: 1.8em;
 }
 .btn-social.btn-sm {
-  padding-left: 36px;
+  padding-left: 38px;
 }
 .btn-social.btn-sm > :first-child {
-  line-height: 26px;
-  width: 26px;
+  line-height: 28px;
+  width: 28px;
   font-size: 1.4em;
 }
 .btn-social.btn-xs {
-  padding-left: 29px;
+  padding-left: 30px;
 }
 .btn-social.btn-xs > :first-child {
-  line-height: 19px;
-  width: 19px;
+  line-height: 20px;
+  width: 20px;
   font-size: 1.2em;
 }
 .btn-social-icon {
   position: relative;
-  padding-left: 41px;
+  padding-left: 42px;
   text-align: left;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
-  height: 31px;
-  width: 31px;
+  height: 32px;
+  width: 32px;
   padding: 0;
 }
 .btn-social-icon > :first-child {
@@ -3689,34 +3706,34 @@ button.close {
   left: 0;
   top: 0;
   bottom: 0;
-  width: 29px;
-  line-height: 31px;
+  width: 30px;
+  line-height: 32px;
   font-size: 1.6em;
   text-align: center;
   border-right: 1px solid rgba(0, 0, 0, 0.2);
 }
 .btn-social-icon.btn-lg {
-  padding-left: 57px;
+  padding-left: 60px;
 }
 .btn-social-icon.btn-lg > :first-child {
-  line-height: 41px;
-  width: 41px;
+  line-height: 44px;
+  width: 44px;
   font-size: 1.8em;
 }
 .btn-social-icon.btn-sm {
-  padding-left: 36px;
+  padding-left: 38px;
 }
 .btn-social-icon.btn-sm > :first-child {
-  line-height: 26px;
-  width: 26px;
+  line-height: 28px;
+  width: 28px;
   font-size: 1.4em;
 }
 .btn-social-icon.btn-xs {
-  padding-left: 29px;
+  padding-left: 30px;
 }
 .btn-social-icon.btn-xs > :first-child {
-  line-height: 19px;
-  width: 19px;
+  line-height: 20px;
+  width: 20px;
   font-size: 1.2em;
 }
 .btn-social-icon > :first-child {
@@ -3725,20 +3742,20 @@ button.close {
   width: 100%;
 }
 .btn-social-icon.btn-lg {
-  height: 41px;
-  width: 41px;
+  height: 44px;
+  width: 44px;
   padding-left: 0;
   padding-right: 0;
 }
 .btn-social-icon.btn-sm {
-  height: 28px;
-  width: 28px;
+  height: 30px;
+  width: 30px;
   padding-left: 0;
   padding-right: 0;
 }
 .btn-social-icon.btn-xs {
-  height: 21px;
-  width: 21px;
+  height: 22px;
+  width: 22px;
   padding-left: 0;
   padding-right: 0;
 }
@@ -5175,14 +5192,14 @@ fieldset[disabled] .btn-yahoo.active {
   height: 34px;
 }
 .select2-container--default.select2-container--open {
-  border-color: #3c8dbc;
+  border-color: #4397fd;
 }
 .select2-dropdown {
   border: 1px solid #d2d6de;
   border-radius: 0;
 }
 .select2-container--default .select2-results__option--highlighted[aria-selected] {
-  background-color: #3c8dbc;
+  background-color: #4397fd;
   color: white;
 }
 .select2-results__option {
@@ -5214,7 +5231,7 @@ fieldset[disabled] .btn-yahoo.active {
 .select2-dropdown .select2-search__field:focus,
 .select2-search--inline .select2-search__field:focus {
   outline: none;
-  border: 1px solid #3c8dbc;
+  border: 1px solid #4397fd;
 }
 .select2-container--default .select2-results__option[aria-disabled=true] {
   color: #999;
@@ -5231,14 +5248,14 @@ fieldset[disabled] .btn-yahoo.active {
   border-radius: 0;
 }
 .select2-container--default .select2-selection--multiple:focus {
-  border-color: #3c8dbc;
+  border-color: #4397fd;
 }
 .select2-container--default.select2-container--focus .select2-selection--multiple {
   border-color: #d2d6de;
 }
 .select2-container--default .select2-selection--multiple .select2-selection__choice {
-  background-color: #3c8dbc;
-  border-color: #367fa9;
+  background-color: #4397fd;
+  border-color: #2a89fd;
   padding: 1px 10px;
   color: #fff;
 }
@@ -5362,7 +5379,7 @@ fieldset[disabled] .btn-yahoo.active {
 .alert-danger,
 .alert-error,
 .modal-danger .modal-body {
-  background-color: #e74c3c !important;
+  background-color: #f75444 !important;
 }
 .bg-yellow,
 .callout.callout-warning,
@@ -5374,14 +5391,14 @@ fieldset[disabled] .btn-yahoo.active {
 .callout.callout-info,
 .alert-info,
 .modal-info .modal-body {
-  background-color: #3498db !important;
+  background-color: #1688f1 !important;
 }
 .bg-blue {
-  background-color: #0073b7 !important;
+  background-color: #1688f1 !important;
 }
 .bg-light-blue,
 .modal-primary .modal-body {
-  background-color: #3c8dbc !important;
+  background-color: #4397fd !important;
 }
 .bg-green,
 .callout.callout-success,
@@ -5423,7 +5440,7 @@ fieldset[disabled] .btn-yahoo.active {
 .bg-red-active,
 .modal-danger .modal-header,
 .modal-danger .modal-footer {
-  background-color: #e43321 !important;
+  background-color: #f63927 !important;
 }
 .bg-yellow-active,
 .modal-warning .modal-header,
@@ -5433,15 +5450,15 @@ fieldset[disabled] .btn-yahoo.active {
 .bg-aqua-active,
 .modal-info .modal-header,
 .modal-info .modal-footer {
-  background-color: #2489cc !important;
+  background-color: #0d78db !important;
 }
 .bg-blue-active {
-  background-color: #005384 !important;
+  background-color: #0c6ec8 !important;
 }
 .bg-light-blue-active,
 .modal-primary .modal-header,
 .modal-primary .modal-footer {
-  background-color: #357ca5 !important;
+  background-color: #2586fd !important;
 }
 .bg-green-active,
 .modal-success .modal-header,
@@ -5477,22 +5494,22 @@ fieldset[disabled] .btn-yahoo.active {
   filter: alpha(opacity=65);
 }
 .text-red {
-  color: #e74c3c !important;
+  color: #f75444 !important;
 }
 .text-yellow {
   color: #f39c12 !important;
 }
 .text-aqua {
-  color: #3498db !important;
+  color: #1688f1 !important;
 }
 .text-blue {
-  color: #0073b7 !important;
+  color: #1688f1 !important;
 }
 .text-black {
   color: #111 !important;
 }
 .text-light-blue {
-  color: #3c8dbc !important;
+  color: #4397fd !important;
 }
 .text-green {
   color: #18bc9c !important;
@@ -5595,30 +5612,30 @@ fieldset[disabled] .btn-yahoo.active {
   color: #fff;
 }
 .bg-light-blue-gradient {
-  background: #3c8dbc !important;
-  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #3c8dbc), color-stop(1, #67a8ce)) !important;
-  background: -ms-linear-gradient(bottom, #3c8dbc, #67a8ce) !important;
-  background: -moz-linear-gradient(center bottom, #3c8dbc 0%, #67a8ce 100%) !important;
-  background: -o-linear-gradient(#67a8ce, #3c8dbc) !important;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#67a8ce', endColorstr='#3c8dbc', GradientType=0) !important;
+  background: #4397fd !important;
+  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #4397fd), color-stop(1, #80b8fe)) !important;
+  background: -ms-linear-gradient(bottom, #4397fd, #80b8fe) !important;
+  background: -moz-linear-gradient(center bottom, #4397fd 0%, #80b8fe 100%) !important;
+  background: -o-linear-gradient(#80b8fe, #4397fd) !important;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80b8fe', endColorstr='#4397fd', GradientType=0) !important;
   color: #fff;
 }
 .bg-blue-gradient {
-  background: #0073b7 !important;
-  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #0073b7), color-stop(1, #0089db)) !important;
-  background: -ms-linear-gradient(bottom, #0073b7, #0089db) !important;
-  background: -moz-linear-gradient(center bottom, #0073b7 0%, #0089db 100%) !important;
-  background: -o-linear-gradient(#0089db, #0073b7) !important;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0089db', endColorstr='#0073b7', GradientType=0) !important;
+  background: #1688f1 !important;
+  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #1688f1), color-stop(1, #3899f3)) !important;
+  background: -ms-linear-gradient(bottom, #1688f1, #3899f3) !important;
+  background: -moz-linear-gradient(center bottom, #1688f1 0%, #3899f3 100%) !important;
+  background: -o-linear-gradient(#3899f3, #1688f1) !important;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#3899f3', endColorstr='#1688f1', GradientType=0) !important;
   color: #fff;
 }
 .bg-aqua-gradient {
-  background: #3498db !important;
-  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #3498db), color-stop(1, #52a7e0)) !important;
-  background: -ms-linear-gradient(bottom, #3498db, #52a7e0) !important;
-  background: -moz-linear-gradient(center bottom, #3498db 0%, #52a7e0 100%) !important;
-  background: -o-linear-gradient(#52a7e0, #3498db) !important;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#52a7e0', endColorstr='#3498db', GradientType=0) !important;
+  background: #1688f1 !important;
+  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #1688f1), color-stop(1, #3899f3)) !important;
+  background: -ms-linear-gradient(bottom, #1688f1, #3899f3) !important;
+  background: -moz-linear-gradient(center bottom, #1688f1 0%, #3899f3 100%) !important;
+  background: -o-linear-gradient(#3899f3, #1688f1) !important;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#3899f3', endColorstr='#1688f1', GradientType=0) !important;
   color: #fff;
 }
 .bg-yellow-gradient {
@@ -5649,12 +5666,12 @@ fieldset[disabled] .btn-yahoo.active {
   color: #fff;
 }
 .bg-red-gradient {
-  background: #e74c3c !important;
-  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #e74c3c), color-stop(1, #ed7669)) !important;
-  background: -ms-linear-gradient(bottom, #e74c3c, #ed7669) !important;
-  background: -moz-linear-gradient(center bottom, #e74c3c 0%, #ed7669 100%) !important;
-  background: -o-linear-gradient(#ed7669, #e74c3c) !important;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ed7669', endColorstr='#e74c3c', GradientType=0) !important;
+  background: #f75444 !important;
+  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #f75444), color-stop(1, #f98175)) !important;
+  background: -ms-linear-gradient(bottom, #f75444, #f98175) !important;
+  background: -moz-linear-gradient(center bottom, #f75444 0%, #f98175 100%) !important;
+  background: -o-linear-gradient(#f98175, #f75444) !important;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f98175', endColorstr='#f75444', GradientType=0) !important;
   color: #fff;
 }
 .bg-black-gradient {

+ 411 - 17
public/assets/css/frontend.css

@@ -11,14 +11,288 @@
 @import url("../libs/bootstrap-select/dist/css/bootstrap-select.min.css");
 @import url("../libs/fastadmin-selectpage/selectpage.css");
 @import url("../libs/bootstrap-slider/slider.css");
+.m-1 {
+  margin-top: 5px !important;
+  margin-right: 5px !important;
+  margin-bottom: 5px !important;
+  margin-left: 5px !important;
+}
+.mt-1 {
+  margin-top: 5px !important;
+}
+.mr-1 {
+  margin-right: 5px !important;
+}
+.mb-1 {
+  margin-bottom: 5px !important;
+}
+.ml-1 {
+  margin-left: 5px !important;
+}
+.mx-1 {
+  margin-left: 5px !important;
+  margin-right: 5px !important;
+}
+.my-1 {
+  margin-top: 5px !important;
+  margin-bottom: 5px !important;
+}
+.m-2 {
+  margin-top: 10px !important;
+  margin-right: 10px !important;
+  margin-bottom: 10px !important;
+  margin-left: 10px !important;
+}
+.mt-2 {
+  margin-top: 10px !important;
+}
+.mr-2 {
+  margin-right: 10px !important;
+}
+.mb-2 {
+  margin-bottom: 10px !important;
+}
+.ml-2 {
+  margin-left: 10px !important;
+}
+.mx-2 {
+  margin-left: 10px !important;
+  margin-right: 10px !important;
+}
+.my-2 {
+  margin-top: 10px !important;
+  margin-bottom: 10px !important;
+}
+.m-3 {
+  margin-top: 15px !important;
+  margin-right: 15px !important;
+  margin-bottom: 15px !important;
+  margin-left: 15px !important;
+}
+.mt-3 {
+  margin-top: 15px !important;
+}
+.mr-3 {
+  margin-right: 15px !important;
+}
+.mb-3 {
+  margin-bottom: 15px !important;
+}
+.ml-3 {
+  margin-left: 15px !important;
+}
+.mx-3 {
+  margin-left: 15px !important;
+  margin-right: 15px !important;
+}
+.my-3 {
+  margin-top: 15px !important;
+  margin-bottom: 15px !important;
+}
+.m-4 {
+  margin-top: 20px !important;
+  margin-right: 20px !important;
+  margin-bottom: 20px !important;
+  margin-left: 20px !important;
+}
+.mt-4 {
+  margin-top: 20px !important;
+}
+.mr-4 {
+  margin-right: 20px !important;
+}
+.mb-4 {
+  margin-bottom: 20px !important;
+}
+.ml-4 {
+  margin-left: 20px !important;
+}
+.mx-4 {
+  margin-left: 20px !important;
+  margin-right: 20px !important;
+}
+.my-4 {
+  margin-top: 20px !important;
+  margin-bottom: 20px !important;
+}
+.p-1 {
+  padding-top: 5px !important;
+  padding-right: 5px !important;
+  padding-bottom: 5px !important;
+  padding-left: 5px !important;
+}
+.pt-1 {
+  padding-top: 5px !important;
+}
+.pr-1 {
+  padding-right: 5px !important;
+}
+.pb-1 {
+  padding-bottom: 5px !important;
+}
+.pl-1 {
+  padding-left: 5px !important;
+}
+.px-1 {
+  padding-left: 5px !important;
+  padding-right: 5px !important;
+}
+.py-1 {
+  padding-top: 5px !important;
+  padding-bottom: 5px !important;
+}
+.p-2 {
+  padding-top: 10px !important;
+  padding-right: 10px !important;
+  padding-bottom: 10px !important;
+  padding-left: 10px !important;
+}
+.pt-2 {
+  padding-top: 10px !important;
+}
+.pr-2 {
+  padding-right: 10px !important;
+}
+.pb-2 {
+  padding-bottom: 10px !important;
+}
+.pl-2 {
+  padding-left: 10px !important;
+}
+.px-2 {
+  padding-left: 10px !important;
+  padding-right: 10px !important;
+}
+.py-2 {
+  padding-top: 10px !important;
+  padding-bottom: 10px !important;
+}
+.p-3 {
+  padding-top: 15px !important;
+  padding-right: 15px !important;
+  padding-bottom: 15px !important;
+  padding-left: 15px !important;
+}
+.pt-3 {
+  padding-top: 15px !important;
+}
+.pr-3 {
+  padding-right: 15px !important;
+}
+.pb-3 {
+  padding-bottom: 15px !important;
+}
+.pl-3 {
+  padding-left: 15px !important;
+}
+.px-3 {
+  padding-left: 15px !important;
+  padding-right: 15px !important;
+}
+.py-3 {
+  padding-top: 15px !important;
+  padding-bottom: 15px !important;
+}
+.p-4 {
+  padding-top: 20px !important;
+  padding-right: 20px !important;
+  padding-bottom: 20px !important;
+  padding-left: 20px !important;
+}
+.pt-4 {
+  padding-top: 20px !important;
+}
+.pr-4 {
+  padding-right: 20px !important;
+}
+.pb-4 {
+  padding-bottom: 20px !important;
+}
+.pl-4 {
+  padding-left: 20px !important;
+}
+.px-4 {
+  padding-left: 20px !important;
+  padding-right: 20px !important;
+}
+.py-4 {
+  padding-top: 20px !important;
+  padding-bottom: 20px !important;
+}
 html,
 body {
   height: 100%;
 }
 body {
-  padding-top: 50px;
-  font-size: 13px;
+  padding-top: 60px;
+  font-size: 14px;
   background: #f4f6f8;
+  height: 100%;
+  line-height: 1.5715;
+  -webkit-font-smoothing: antialiased;
+  text-rendering: optimizeLegibility;
+  -moz-osx-font-smoothing: grayscale;
+  font-feature-settings: 'liga';
+  -webkit-text-size-adjust: 100%;
+  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Ubuntu, Helvetica Neue, Helvetica, Arial, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Source Han Sans CN, sans-serif;
+  font-weight: 400;
+  color: #616161;
+}
+a {
+  color: #007bff;
+}
+a:hover,
+a:focus {
+  color: #007bff;
+}
+.navbar-white {
+  background-color: #fff;
+  border-color: #fff;
+  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.08);
+}
+.navbar-white .dropdown-menu {
+  border-radius: 5px;
+  -webkit-box-shadow: 0px 20px 30px rgba(83, 88, 93, 0.05), 0px 0px 30px rgba(83, 88, 93, 0.1);
+  -moz-box-shadow: 0px 20px 30px rgba(83, 88, 93, 0.05), 0px 0px 30px rgba(83, 88, 93, 0.1);
+  box-shadow: 0px 20px 30px rgba(83, 88, 93, 0.05), 0px 0px 30px rgba(83, 88, 93, 0.1);
+}
+@media (min-width: 768px) {
+  .navbar-white .navbar-brand {
+    height: 60px;
+    line-height: 27px;
+  }
+  .navbar-white .navbar-nav > li > a {
+    height: 60px;
+    line-height: 27px;
+    color: #555;
+  }
+  .navbar-white .navbar-nav > li > a:hover,
+  .navbar-white .navbar-nav > li > a:focus {
+    color: #007bff;
+  }
+  .navbar-white .navbar-nav > .active > a,
+  .navbar-white .navbar-nav > .active > a:hover,
+  .navbar-white .navbar-nav > .active > a:focus {
+    background-color: inherit;
+    color: #007bff;
+  }
+}
+@media (max-width: 768px) {
+  body {
+    padding-top: 50px;
+  }
+  .navbar-white .navbar-nav .open .dropdown-menu {
+    background: #eee;
+  }
+  .navbar-white .navbar-toggle {
+    border-color: #ddd;
+  }
+  .navbar-white .navbar-toggle .icon-bar {
+    background-color: #888;
+  }
+  .navbar-white .navbar-collapse.in {
+    border-top-color: #f5f5f5;
+  }
 }
 .dropdown:hover .dropdown-menu {
   display: block;
@@ -30,12 +304,37 @@ body {
 .navbar-nav > li > a {
   font-size: 14px;
 }
+#header-navbar li.dropdown ul.dropdown-menu {
+  min-width: 100px;
+}
 .dropdown-menu > li > a {
-  font-size: 13px;
+  font-size: 14px;
   padding: 5px 20px;
 }
+.dropdown-menu {
+  border-radius: 2px;
+  border: 0px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  padding: 5px 0px;
+}
+.dropdown-menu li a {
+  padding-top: 10px !important;
+  padding-bottom: 10px;
+}
+.dropdown-menu > li > a {
+  font-weight: 400;
+  color: #444;
+  padding: 5px 15px;
+  padding-bottom: 10px;
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  text-decoration: none;
+  color: #777;
+  background: rgba(0, 0, 0, 0.05);
+}
 .toast-top-center {
-  top: 50px;
+  top: 60px;
 }
 #toast-container > div {
   -webkit-box-shadow: none;
@@ -149,9 +448,6 @@ input.selectpage {
 .checkbox > label > input {
   margin: 2px 0 0;
 }
-#header-navbar li.dropdown ul.dropdown-menu {
-  min-width: 94px;
-}
 form.form-horizontal .control-label {
   font-weight: normal;
 }
@@ -282,7 +578,7 @@ form.form-horizontal .control-label {
 .nav-pills > li.active > a {
   border: none;
   color: #fff;
-  background: #46c37b;
+  background: #007bff;
   -webkit-transition: all 0.3s ease;
   -moz-transition: all 0.3s ease;
   -o-transition: all 0.3s ease;
@@ -309,7 +605,6 @@ form.form-horizontal .control-label {
   width: 110px;
   display: inline-block;
   text-decoration: none;
-  font-weight: bold;
 }
 /* 弹窗中的表单 */
 .form-layer {
@@ -413,7 +708,7 @@ footer.footer .copyright a:hover {
   font-size: 16px;
   text-align: center;
   color: #616161;
-  background-color: #ececec;
+  background-color: #efefef;
   -webkit-transition: all 0.3s ease;
   -moz-transition: all 0.3s ease;
   -o-transition: all 0.3s ease;
@@ -436,9 +731,6 @@ footer.footer .copyright a:hover {
 .login-section .login-main {
   padding: 40px 45px 20px 45px;
 }
-.login-section .control-label {
-  font-size: 13px;
-}
 .login-section .n-bootstrap .form-group {
   position: relative;
 }
@@ -461,7 +753,7 @@ main.content {
   overflow: auto;
   padding: 15px;
   padding-top: 20px;
-  min-height: calc(100vh - 125px);
+  min-height: calc(100vh - 135px);
 }
 .sidenav {
   padding: 20px 0 10px 0;
@@ -508,11 +800,11 @@ main.content {
   padding: 10px 15px 10px 35px;
 }
 .sidenav .list-group .list-group-item.active {
-  border-left: 2px solid #46c37b;
+  border-left: 2px solid #007bff;
   background-color: rgba(245, 245, 245, 0.38);
 }
 .sidenav .list-group .list-group-item.active > a {
-  color: #46c37b;
+  color: #007bff;
 }
 .nav li .avatar-text,
 .nav li .avatar-img {
@@ -594,11 +886,113 @@ main.content {
   font-size: 14px;
 }
 .jumpto input {
-  height: 31px;
   width: 50px;
   margin-left: 5px;
   margin-right: 5px;
   text-align: center;
   display: inline-block;
 }
+.fixed-columns,
+.fixed-columns-right {
+  position: absolute;
+  top: 0;
+  height: 100%;
+  min-height: 41px;
+  background-color: #fff;
+  box-sizing: border-box;
+  z-index: 2;
+  box-shadow: 0 -1px 8px rgba(0, 0, 0, 0.08);
+}
+.fixed-columns .fixed-table-body,
+.fixed-columns-right .fixed-table-body {
+  min-height: 41px;
+  overflow-x: hidden !important;
+}
+.fixed-columns {
+  left: 0;
+}
+.fixed-columns-right {
+  right: 0;
+  box-shadow: -1px 0 8px rgba(0, 0, 0, 0.08);
+}
+.bootstrap-tagsinput {
+  background-color: #fff;
+  border: 1px solid #ccc;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  display: inline-block;
+  padding: 4px 6px;
+  margin-bottom: 10px;
+  color: #555;
+  vertical-align: middle;
+  width: 100%;
+  line-height: 22px;
+  cursor: text;
+}
+.bootstrap-tagsinput input {
+  border: none;
+  box-shadow: none;
+  outline: none;
+  background-color: transparent;
+  padding: 0;
+  margin: 0;
+  font-size: 13px;
+  width: 80px;
+  max-width: inherit;
+}
+.bootstrap-tagsinput input:focus {
+  border: none;
+  box-shadow: none;
+}
+.bootstrap-tagsinput .tagsinput-text {
+  display: inline-block;
+  overflow: auto;
+  visibility: hidden;
+  height: 1px;
+  position: absolute;
+  bottom: -1px;
+  left: 0;
+}
+.bootstrap-tagsinput .tag {
+  margin-right: 2px;
+  color: white;
+}
+.bootstrap-tagsinput .tag [data-role="remove"] {
+  margin-left: 5px;
+  cursor: pointer;
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:after {
+  content: "x";
+  padding: 0px 2px;
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:hover {
+  background-color: rgba(255, 255, 255, 0.16);
+}
+.autocomplete-suggestions {
+  border-radius: 2px;
+  background: #FFF;
+  overflow: auto;
+  min-width: 200px;
+  -webkit-box-shadow: 0px 20px 30px rgba(83, 88, 93, 0.05), 0px 0px 30px rgba(83, 88, 93, 0.1);
+  -moz-box-shadow: 0px 20px 30px rgba(83, 88, 93, 0.05), 0px 0px 30px rgba(83, 88, 93, 0.1);
+  box-shadow: 0px 20px 30px rgba(83, 88, 93, 0.05), 0px 0px 30px rgba(83, 88, 93, 0.1);
+}
+.autocomplete-suggestions strong {
+  font-weight: normal;
+  color: red;
+}
+.autocomplete-suggestions .autocomplete-suggestion {
+  padding: 5px 10px;
+  white-space: nowrap;
+  overflow: hidden;
+}
+.autocomplete-suggestions .autocomplete-selected {
+  background: #F0F0F0;
+}
+.autocomplete-suggestions .autocomplete-group {
+  padding: 5px 10px;
+}
+.autocomplete-suggestions .autocomplete-group strong {
+  display: block;
+  border-bottom: 1px solid #ddd;
+}
 /*# sourceMappingURL=frontend.css.map */

File diff suppressed because it is too large
+ 1 - 1
public/assets/css/frontend.min.css


+ 0 - 1
public/assets/css/lesshat.css

@@ -1 +0,0 @@
-/*# sourceMappingURL=lesshat.css.map */

+ 129 - 93
public/assets/css/skins/_all-skins.css

@@ -96,9 +96,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-blue .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-blue .sidebar-menu > li:hover > a,
 .skin-blue .sidebar-menu > li.active > a {
   color: #fff;
@@ -163,6 +160,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-blue .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-blue.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-blue .sidebar-form input[type="text"]::-moz-placeholder {
   color: #fff;
   opacity: 1;
@@ -304,9 +310,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-blue-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-blue-light .sidebar-menu > li:hover > a,
 .skin-blue-light .sidebar-menu > li.active > a {
   color: #000;
@@ -374,6 +377,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-blue-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-blue-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-blue-light .main-footer {
   border-top-color: #d2d6de;
 }
@@ -496,9 +508,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-black .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black .sidebar-menu > li:hover > a,
 .skin-black .sidebar-menu > li.active > a {
   color: #fff;
@@ -563,6 +572,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-black .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-black.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 @media (max-width: 767px) {
   .skin-black.multiplenav .main-header .navbar {
     background-color: #605ca8;
@@ -708,9 +726,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-black-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-light .sidebar-menu > li:hover > a,
 .skin-black-light .sidebar-menu > li.active > a {
   color: #000;
@@ -778,6 +793,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-black-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-black-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-black-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
@@ -887,9 +911,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-green .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-green .sidebar-menu > li:hover > a,
 .skin-green .sidebar-menu > li.active > a {
   color: #fff;
@@ -954,6 +975,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-green .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-green.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 @media (max-width: 767px) {
   .skin-green.multiplenav .sidebar .mobilenav a.btn-app {
     background: #807dba;
@@ -1050,9 +1080,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-green-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-green-light .sidebar-menu > li:hover > a,
 .skin-green-light .sidebar-menu > li.active > a {
   color: #000;
@@ -1120,6 +1147,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-green-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-green-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-green-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
@@ -1143,10 +1179,10 @@
  * ---------
  */
 .skin-red .main-header {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red .main-header .navbar {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red .main-header .navbar .nav > li > a {
   color: #fff;
@@ -1175,7 +1211,7 @@
   color: #fff;
 }
 .skin-red .main-header .navbar .sidebar-toggle:hover {
-  background-color: #e43725;
+  background-color: #f63e2c;
 }
 @media (max-width: 767px) {
   .skin-red .main-header .navbar .dropdown-menu li.divider {
@@ -1185,30 +1221,30 @@
     color: #fff;
   }
   .skin-red .main-header .navbar .dropdown-menu li a:hover {
-    background: #e43725;
+    background: #f63e2c;
   }
 }
 .skin-red .main-header .logo {
-  background-color: #e43725;
+  background-color: #f63e2c;
   color: #fff;
   border-bottom: 0 solid transparent;
 }
 .skin-red .main-header .logo:hover {
-  background-color: #e43321;
+  background-color: #f63927;
 }
 @media (max-width: 767px) {
   .skin-red .main-header .logo {
-    background-color: #e74c3c;
+    background-color: #f75444;
     color: #fff;
     border-bottom: 0 solid transparent;
     border-right: none;
   }
   .skin-red .main-header .logo:hover {
-    background-color: #e64837;
+    background-color: #f7503f;
   }
 }
 .skin-red .main-header li.user-header {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red .content-header {
   background: transparent;
@@ -1229,14 +1265,11 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-red .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-red .sidebar-menu > li:hover > a,
 .skin-red .sidebar-menu > li.active > a {
   color: #fff;
   background: #5b57a3;
-  border-left-color: #e74c3c;
+  border-left-color: #f75444;
 }
 .skin-red .sidebar-menu > li > .treeview-menu {
   background: #555299;
@@ -1296,13 +1329,22 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-red .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-red.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 @media (max-width: 767px) {
   .skin-red.multiplenav .sidebar .mobilenav a.btn-app {
     background: #807dba;
     color: #fff;
   }
   .skin-red.multiplenav .sidebar .mobilenav a.btn-app.active {
-    background: #e74c3c;
+    background: #f75444;
     color: #fff;
   }
 }
@@ -1311,10 +1353,10 @@
  * ---------
  */
 .skin-red-light .main-header {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red-light .main-header .navbar {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red-light .main-header .navbar .nav > li > a {
   color: #fff;
@@ -1343,7 +1385,7 @@
   color: #fff;
 }
 .skin-red-light .main-header .navbar .sidebar-toggle:hover {
-  background-color: #e43725;
+  background-color: #f63e2c;
 }
 @media (max-width: 767px) {
   .skin-red-light .main-header .navbar .dropdown-menu li.divider {
@@ -1353,19 +1395,19 @@
     color: #fff;
   }
   .skin-red-light .main-header .navbar .dropdown-menu li a:hover {
-    background: #e43725;
+    background: #f63e2c;
   }
 }
 .skin-red-light .main-header .logo {
-  background-color: #e74c3c;
+  background-color: #f75444;
   color: #fff;
   border-bottom: 0 solid transparent;
 }
 .skin-red-light .main-header .logo:hover {
-  background-color: #e64837;
+  background-color: #f7503f;
 }
 .skin-red-light .main-header li.user-header {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red-light .content-header {
   background: transparent;
@@ -1392,17 +1434,14 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-red-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-red-light .sidebar-menu > li:hover > a,
 .skin-red-light .sidebar-menu > li.active > a {
   color: #000;
   background: #f4f4f5;
-  border-left-color: #e74c3c;
+  border-left-color: #f75444;
 }
 .skin-red-light .sidebar-menu > li.active {
-  border-left-color: #e74c3c;
+  border-left-color: #f75444;
 }
 .skin-red-light .sidebar-menu > li > .treeview-menu {
   background: #f4f4f5;
@@ -1462,6 +1501,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-red-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-red-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-red-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
@@ -1476,7 +1524,7 @@
     color: #757575;
   }
   .skin-red-light.multiplenav .sidebar .mobilenav a.btn-app.active {
-    background: #e74c3c;
+    background: #f75444;
     color: #fff;
   }
 }
@@ -1571,9 +1619,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-yellow .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-yellow .sidebar-menu > li:hover > a,
 .skin-yellow .sidebar-menu > li.active > a {
   color: #fff;
@@ -1638,6 +1683,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-yellow .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-yellow.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 @media (max-width: 767px) {
   .skin-yellow.multiplenav .sidebar .mobilenav a.btn-app {
     background: #807dba;
@@ -1734,9 +1788,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-yellow-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-yellow-light .sidebar-menu > li:hover > a,
 .skin-yellow-light .sidebar-menu > li.active > a {
   color: #000;
@@ -1804,6 +1855,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-yellow-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-yellow-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-yellow-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
@@ -1910,9 +1970,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-purple .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-purple .sidebar-menu > li:hover > a,
 .skin-purple .sidebar-menu > li.active > a {
   color: #fff;
@@ -1977,6 +2034,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-purple .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-purple.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-purple .sidebar-form input[type="text"]::-moz-placeholder {
   color: #fff;
   opacity: 1;
@@ -2121,9 +2187,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-purple-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-purple-light .sidebar-menu > li:hover > a,
 .skin-purple-light .sidebar-menu > li.active > a {
   color: #000;
@@ -2191,6 +2254,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-purple-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-purple-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-purple-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
@@ -2327,9 +2399,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-black-blue .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-blue .sidebar-menu > li:hover > a,
 .skin-black-blue .sidebar-menu > li.active > a {
   color: #fff;
@@ -2400,9 +2469,6 @@
 .skin-black-blue .treeview-menu > li.active > a {
   background-color: #f5549f;
 }
-.skin-black-blue .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-blue .sidebar-menu > li.active > a {
   color: #fff;
   background: #f5549f;
@@ -2569,9 +2635,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-black-purple .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-purple .sidebar-menu > li:hover > a,
 .skin-black-purple .sidebar-menu > li.active > a {
   color: #fff;
@@ -2642,9 +2705,6 @@
 .skin-black-purple .treeview-menu > li.active > a {
   background-color: #f5549f;
 }
-.skin-black-purple .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-purple .sidebar-menu > li.active > a {
   color: #fff;
   background: #f5549f;
@@ -2811,9 +2871,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-black-green .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-green .sidebar-menu > li:hover > a,
 .skin-black-green .sidebar-menu > li.active > a {
   color: #fff;
@@ -2884,9 +2941,6 @@
 .skin-black-green .treeview-menu > li.active > a {
   background-color: #f5549f;
 }
-.skin-black-green .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-green .sidebar-menu > li.active > a {
   color: #fff;
   background: #f5549f;
@@ -3053,9 +3107,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-black-red .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-red .sidebar-menu > li:hover > a,
 .skin-black-red .sidebar-menu > li.active > a {
   color: #fff;
@@ -3126,9 +3177,6 @@
 .skin-black-red .treeview-menu > li.active > a {
   background-color: #f5549f;
 }
-.skin-black-red .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-red .sidebar-menu > li.active > a {
   color: #fff;
   background: #f5549f;
@@ -3295,9 +3343,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-black-yellow .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-yellow .sidebar-menu > li:hover > a,
 .skin-black-yellow .sidebar-menu > li.active > a {
   color: #fff;
@@ -3368,9 +3413,6 @@
 .skin-black-yellow .treeview-menu > li.active > a {
   background-color: #f5549f;
 }
-.skin-black-yellow .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-yellow .sidebar-menu > li.active > a {
   color: #fff;
   background: #f5549f;
@@ -3537,9 +3579,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-black-pink .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-pink .sidebar-menu > li:hover > a,
 .skin-black-pink .sidebar-menu > li.active > a {
   color: #fff;
@@ -3610,9 +3649,6 @@
 .skin-black-pink .treeview-menu > li.active > a {
   background-color: #f5549f;
 }
-.skin-black-pink .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-pink .sidebar-menu > li.active > a {
   color: #fff;
   background: #f5549f;

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

@@ -116,9 +116,6 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-black-blue .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-blue .sidebar-menu > li:hover > a,
 .skin-black-blue .sidebar-menu > li.active > a {
   color: #fff;
@@ -189,9 +186,6 @@
 .skin-black-blue .treeview-menu > li.active > a {
   background-color: #4e73df;
 }
-.skin-black-blue .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-blue .sidebar-menu > li.active > a {
   color: #fff;
   background: #4e73df;

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

@@ -116,9 +116,6 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-black-green .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-green .sidebar-menu > li:hover > a,
 .skin-black-green .sidebar-menu > li.active > a {
   color: #fff;
@@ -189,9 +186,6 @@
 .skin-black-green .treeview-menu > li.active > a {
   background-color: #18bc9c;
 }
-.skin-black-green .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-green .sidebar-menu > li.active > a {
   color: #fff;
   background: #18bc9c;

+ 9 - 3
public/assets/css/skins/skin-black-light.css

@@ -99,9 +99,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-black-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-light .sidebar-menu > li:hover > a,
 .skin-black-light .sidebar-menu > li.active > a {
   color: #000;
@@ -169,6 +166,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-black-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-black-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-black-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);

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

@@ -116,9 +116,6 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-black-pink .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-pink .sidebar-menu > li:hover > a,
 .skin-black-pink .sidebar-menu > li.active > a {
   color: #fff;
@@ -189,9 +186,6 @@
 .skin-black-pink .treeview-menu > li.active > a {
   background-color: #f5549f;
 }
-.skin-black-pink .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-pink .sidebar-menu > li.active > a {
   color: #fff;
   background: #f5549f;

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

@@ -116,9 +116,6 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-black-purple .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-purple .sidebar-menu > li:hover > a,
 .skin-black-purple .sidebar-menu > li.active > a {
   color: #fff;
@@ -189,9 +186,6 @@
 .skin-black-purple .treeview-menu > li.active > a {
   background-color: #605ca8;
 }
-.skin-black-purple .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-purple .sidebar-menu > li.active > a {
   color: #fff;
   background: #605ca8;

+ 6 - 12
public/assets/css/skins/skin-black-red.css

@@ -116,9 +116,6 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-black-red .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-red .sidebar-menu > li:hover > a,
 .skin-black-red .sidebar-menu > li.active > a {
   color: #fff;
@@ -187,15 +184,12 @@
   padding-left: 18px;
 }
 .skin-black-red .treeview-menu > li.active > a {
-  background-color: #e74c3c;
-}
-.skin-black-red .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
+  background-color: #f75444;
 }
 .skin-black-red .sidebar-menu > li.active > a {
   color: #fff;
-  background: #e74c3c;
-  border-left-color: #e74c3c;
+  background: #f75444;
+  border-left-color: #f75444;
 }
 .skin-black-red .sidebar-menu > li:hover > a {
   border-left-color: transparent;
@@ -220,11 +214,11 @@
 .skin-black-red.sidebar-collapse .sidebar-menu li:hover > a,
 .skin-black-red.sidebar-collapse .sidebar-menu li.active > a {
   color: #fff;
-  background: #e74c3c;
+  background: #f75444;
 }
 .skin-black-red.sidebar-collapse .sidebar-menu .treeview-menu li.active > a {
   color: #fff;
-  background: #e74c3c;
+  background: #f75444;
 }
 .skin-black-red.sidebar-collapse .sidebar-menu .treeview-menu li.treeview > a {
   background: transparent;
@@ -236,7 +230,7 @@
     color: #fff;
   }
   .skin-black-red.multiplenav .sidebar .mobilenav a.btn-app.active {
-    background: #e74c3c;
+    background: #f75444;
     color: #fff;
   }
 }

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

@@ -116,9 +116,6 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-black-yellow .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-yellow .sidebar-menu > li:hover > a,
 .skin-black-yellow .sidebar-menu > li.active > a {
   color: #fff;
@@ -189,9 +186,6 @@
 .skin-black-yellow .treeview-menu > li.active > a {
   background-color: #f39c12;
 }
-.skin-black-yellow .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black-yellow .sidebar-menu > li.active > a {
   color: #fff;
   background: #f39c12;

+ 9 - 3
public/assets/css/skins/skin-black.css

@@ -99,9 +99,6 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-black .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-black .sidebar-menu > li:hover > a,
 .skin-black .sidebar-menu > li.active > a {
   color: #fff;
@@ -166,6 +163,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-black .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-black.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 @media (max-width: 767px) {
   .skin-black.multiplenav .main-header .navbar {
     background-color: #222d32;

+ 9 - 3
public/assets/css/skins/skin-blue-light.css

@@ -81,9 +81,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-blue-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-blue-light .sidebar-menu > li:hover > a,
 .skin-blue-light .sidebar-menu > li.active > a {
   color: #000;
@@ -151,6 +148,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-blue-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-blue-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-blue-light .main-footer {
   border-top-color: #d2d6de;
 }

+ 9 - 3
public/assets/css/skins/skin-blue.css

@@ -96,9 +96,6 @@
   color: #a4b7ef;
   background: #3d65dc;
 }
-.skin-blue .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-blue .sidebar-menu > li:hover > a,
 .skin-blue .sidebar-menu > li.active > a {
   color: #fff;
@@ -163,6 +160,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-blue .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-blue.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-blue .sidebar-form input[type="text"]::-moz-placeholder {
   color: #fff;
   opacity: 1;

+ 9 - 3
public/assets/css/skins/skin-green-light.css

@@ -84,9 +84,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-green-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-green-light .sidebar-menu > li:hover > a,
 .skin-green-light .sidebar-menu > li.active > a {
   color: #000;
@@ -154,6 +151,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-green-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-green-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-green-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);

+ 9 - 3
public/assets/css/skins/skin-green.css

@@ -89,9 +89,6 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-green .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-green .sidebar-menu > li:hover > a,
 .skin-green .sidebar-menu > li.active > a {
   color: #fff;
@@ -156,6 +153,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-green .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-green.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 @media (max-width: 767px) {
   .skin-green.multiplenav .sidebar .mobilenav a.btn-app {
     background: #374850;

+ 9 - 3
public/assets/css/skins/skin-purple-light.css

@@ -84,9 +84,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-purple-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-purple-light .sidebar-menu > li:hover > a,
 .skin-purple-light .sidebar-menu > li.active > a {
   color: #000;
@@ -154,6 +151,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-purple-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-purple-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-purple-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);

+ 9 - 3
public/assets/css/skins/skin-purple.css

@@ -86,9 +86,6 @@
   color: #a19fcb;
   background: #57539c;
 }
-.skin-purple .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-purple .sidebar-menu > li:hover > a,
 .skin-purple .sidebar-menu > li.active > a {
   color: #fff;
@@ -153,6 +150,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-purple .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-purple.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-purple .sidebar-form input[type="text"]::-moz-placeholder {
   color: #fff;
   opacity: 1;

+ 19 - 13
public/assets/css/skins/skin-red-light.css

@@ -3,10 +3,10 @@
  * ---------
  */
 .skin-red-light .main-header {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red-light .main-header .navbar {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red-light .main-header .navbar .nav > li > a {
   color: #fff;
@@ -35,7 +35,7 @@
   color: #fff;
 }
 .skin-red-light .main-header .navbar .sidebar-toggle:hover {
-  background-color: #e43725;
+  background-color: #f63e2c;
 }
 @media (max-width: 767px) {
   .skin-red-light .main-header .navbar .dropdown-menu li.divider {
@@ -45,19 +45,19 @@
     color: #fff;
   }
   .skin-red-light .main-header .navbar .dropdown-menu li a:hover {
-    background: #e43725;
+    background: #f63e2c;
   }
 }
 .skin-red-light .main-header .logo {
-  background-color: #e74c3c;
+  background-color: #f75444;
   color: #fff;
   border-bottom: 0 solid transparent;
 }
 .skin-red-light .main-header .logo:hover {
-  background-color: #e64837;
+  background-color: #f7503f;
 }
 .skin-red-light .main-header li.user-header {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red-light .content-header {
   background: transparent;
@@ -84,17 +84,14 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-red-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-red-light .sidebar-menu > li:hover > a,
 .skin-red-light .sidebar-menu > li.active > a {
   color: #000;
   background: #f4f4f5;
-  border-left-color: #e74c3c;
+  border-left-color: #f75444;
 }
 .skin-red-light .sidebar-menu > li.active {
-  border-left-color: #e74c3c;
+  border-left-color: #f75444;
 }
 .skin-red-light .sidebar-menu > li > .treeview-menu {
   background: #f4f4f5;
@@ -154,6 +151,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-red-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-red-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-red-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
@@ -168,7 +174,7 @@
     color: #757575;
   }
   .skin-red-light.multiplenav .sidebar .mobilenav a.btn-app.active {
-    background: #e74c3c;
+    background: #f75444;
     color: #fff;
   }
 }

+ 20 - 14
public/assets/css/skins/skin-red.css

@@ -3,10 +3,10 @@
  * ---------
  */
 .skin-red .main-header {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red .main-header .navbar {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red .main-header .navbar .nav > li > a {
   color: #fff;
@@ -35,7 +35,7 @@
   color: #fff;
 }
 .skin-red .main-header .navbar .sidebar-toggle:hover {
-  background-color: #e43725;
+  background-color: #f63e2c;
 }
 @media (max-width: 767px) {
   .skin-red .main-header .navbar .dropdown-menu li.divider {
@@ -45,30 +45,30 @@
     color: #fff;
   }
   .skin-red .main-header .navbar .dropdown-menu li a:hover {
-    background: #e43725;
+    background: #f63e2c;
   }
 }
 .skin-red .main-header .logo {
-  background-color: #e43725;
+  background-color: #f63e2c;
   color: #fff;
   border-bottom: 0 solid transparent;
 }
 .skin-red .main-header .logo:hover {
-  background-color: #e43321;
+  background-color: #f63927;
 }
 @media (max-width: 767px) {
   .skin-red .main-header .logo {
-    background-color: #e74c3c;
+    background-color: #f75444;
     color: #fff;
     border-bottom: 0 solid transparent;
     border-right: none;
   }
   .skin-red .main-header .logo:hover {
-    background-color: #e64837;
+    background-color: #f7503f;
   }
 }
 .skin-red .main-header li.user-header {
-  background-color: #e74c3c;
+  background-color: #f75444;
 }
 .skin-red .content-header {
   background: transparent;
@@ -89,14 +89,11 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-red .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-red .sidebar-menu > li:hover > a,
 .skin-red .sidebar-menu > li.active > a {
   color: #fff;
   background: #1e282c;
-  border-left-color: #e74c3c;
+  border-left-color: #f75444;
 }
 .skin-red .sidebar-menu > li > .treeview-menu {
   background: #2c3b41;
@@ -156,13 +153,22 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-red .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-red.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 @media (max-width: 767px) {
   .skin-red.multiplenav .sidebar .mobilenav a.btn-app {
     background: #374850;
     color: #fff;
   }
   .skin-red.multiplenav .sidebar .mobilenav a.btn-app.active {
-    background: #e74c3c;
+    background: #f75444;
     color: #fff;
   }
 }

+ 9 - 3
public/assets/css/skins/skin-yellow-light.css

@@ -84,9 +84,6 @@
   color: #848484;
   background: #f9fafc;
 }
-.skin-yellow-light .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-yellow-light .sidebar-menu > li:hover > a,
 .skin-yellow-light .sidebar-menu > li.active > a {
   color: #000;
@@ -154,6 +151,15 @@
     border-left: 1px solid #d2d6de;
   }
 }
+.skin-yellow-light .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-yellow-light.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 .skin-yellow-light .main-sidebar {
   -webkit-box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);
   box-shadow: 7px 0 14px rgba(0, 0, 0, 0.03);

+ 9 - 3
public/assets/css/skins/skin-yellow.css

@@ -89,9 +89,6 @@
   color: #4b646f;
   background: #1a2226;
 }
-.skin-yellow .sidebar-menu > li > a {
-  border-left: 3px solid transparent;
-}
 .skin-yellow .sidebar-menu > li:hover > a,
 .skin-yellow .sidebar-menu > li.active > a {
   color: #fff;
@@ -156,6 +153,15 @@
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 0;
 }
+.skin-yellow .sidebar-menu > li > a {
+  border-left: 3px solid transparent;
+  padding-left: 12px;
+}
+@media (min-width: 768px) {
+  .skin-yellow.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
+    margin-left: -3px;
+  }
+}
 @media (max-width: 767px) {
   .skin-yellow.multiplenav .sidebar .mobilenav a.btn-app {
     background: #374850;

+ 209 - 0
public/assets/css/tinycss.css

@@ -0,0 +1,209 @@
+.m-1 {
+  margin-top: 5px !important;
+  margin-right: 5px !important;
+  margin-bottom: 5px !important;
+  margin-left: 5px !important;
+}
+.mt-1 {
+  margin-top: 5px !important;
+}
+.mr-1 {
+  margin-right: 5px !important;
+}
+.mb-1 {
+  margin-bottom: 5px !important;
+}
+.ml-1 {
+  margin-left: 5px !important;
+}
+.mx-1 {
+  margin-left: 5px !important;
+  margin-right: 5px !important;
+}
+.my-1 {
+  margin-top: 5px !important;
+  margin-bottom: 5px !important;
+}
+.m-2 {
+  margin-top: 10px !important;
+  margin-right: 10px !important;
+  margin-bottom: 10px !important;
+  margin-left: 10px !important;
+}
+.mt-2 {
+  margin-top: 10px !important;
+}
+.mr-2 {
+  margin-right: 10px !important;
+}
+.mb-2 {
+  margin-bottom: 10px !important;
+}
+.ml-2 {
+  margin-left: 10px !important;
+}
+.mx-2 {
+  margin-left: 10px !important;
+  margin-right: 10px !important;
+}
+.my-2 {
+  margin-top: 10px !important;
+  margin-bottom: 10px !important;
+}
+.m-3 {
+  margin-top: 15px !important;
+  margin-right: 15px !important;
+  margin-bottom: 15px !important;
+  margin-left: 15px !important;
+}
+.mt-3 {
+  margin-top: 15px !important;
+}
+.mr-3 {
+  margin-right: 15px !important;
+}
+.mb-3 {
+  margin-bottom: 15px !important;
+}
+.ml-3 {
+  margin-left: 15px !important;
+}
+.mx-3 {
+  margin-left: 15px !important;
+  margin-right: 15px !important;
+}
+.my-3 {
+  margin-top: 15px !important;
+  margin-bottom: 15px !important;
+}
+.m-4 {
+  margin-top: 20px !important;
+  margin-right: 20px !important;
+  margin-bottom: 20px !important;
+  margin-left: 20px !important;
+}
+.mt-4 {
+  margin-top: 20px !important;
+}
+.mr-4 {
+  margin-right: 20px !important;
+}
+.mb-4 {
+  margin-bottom: 20px !important;
+}
+.ml-4 {
+  margin-left: 20px !important;
+}
+.mx-4 {
+  margin-left: 20px !important;
+  margin-right: 20px !important;
+}
+.my-4 {
+  margin-top: 20px !important;
+  margin-bottom: 20px !important;
+}
+.p-1 {
+  padding-top: 5px !important;
+  padding-right: 5px !important;
+  padding-bottom: 5px !important;
+  padding-left: 5px !important;
+}
+.pt-1 {
+  padding-top: 5px !important;
+}
+.pr-1 {
+  padding-right: 5px !important;
+}
+.pb-1 {
+  padding-bottom: 5px !important;
+}
+.pl-1 {
+  padding-left: 5px !important;
+}
+.px-1 {
+  padding-left: 5px !important;
+  padding-right: 5px !important;
+}
+.py-1 {
+  padding-top: 5px !important;
+  padding-bottom: 5px !important;
+}
+.p-2 {
+  padding-top: 10px !important;
+  padding-right: 10px !important;
+  padding-bottom: 10px !important;
+  padding-left: 10px !important;
+}
+.pt-2 {
+  padding-top: 10px !important;
+}
+.pr-2 {
+  padding-right: 10px !important;
+}
+.pb-2 {
+  padding-bottom: 10px !important;
+}
+.pl-2 {
+  padding-left: 10px !important;
+}
+.px-2 {
+  padding-left: 10px !important;
+  padding-right: 10px !important;
+}
+.py-2 {
+  padding-top: 10px !important;
+  padding-bottom: 10px !important;
+}
+.p-3 {
+  padding-top: 15px !important;
+  padding-right: 15px !important;
+  padding-bottom: 15px !important;
+  padding-left: 15px !important;
+}
+.pt-3 {
+  padding-top: 15px !important;
+}
+.pr-3 {
+  padding-right: 15px !important;
+}
+.pb-3 {
+  padding-bottom: 15px !important;
+}
+.pl-3 {
+  padding-left: 15px !important;
+}
+.px-3 {
+  padding-left: 15px !important;
+  padding-right: 15px !important;
+}
+.py-3 {
+  padding-top: 15px !important;
+  padding-bottom: 15px !important;
+}
+.p-4 {
+  padding-top: 20px !important;
+  padding-right: 20px !important;
+  padding-bottom: 20px !important;
+  padding-left: 20px !important;
+}
+.pt-4 {
+  padding-top: 20px !important;
+}
+.pr-4 {
+  padding-right: 20px !important;
+}
+.pb-4 {
+  padding-bottom: 20px !important;
+}
+.pl-4 {
+  padding-left: 20px !important;
+}
+.px-4 {
+  padding-left: 20px !important;
+  padding-right: 20px !important;
+}
+.py-4 {
+  padding-top: 20px !important;
+  padding-bottom: 20px !important;
+}
+/*# sourceMappingURL=tinycss.css.map */

+ 59 - 7
public/assets/js/adminlte.js

@@ -305,14 +305,38 @@ function _init() {
                     //Destroy if it exists
                     $(".sidebar").slimScroll({destroy: true}).height("auto").css("overflow", "inherit");
                     if (!$("body").hasClass('sidebar-collapse')) {
+                        $(".sidebar").off("mousewheel").css("margin-top", 0);
+                        $('.sidebar .treeview-menu').off('mousewheel').removeAttr("style");
                         //Add slimscroll
                         $(".sidebar").slimscroll({
                             height: ($(window).height() - $(".main-header").height()) + "px",
                             color: "rgba(0,0,0,0.2)",
                             size: "8px"
                         });
+                        $(".sidebar").trigger("mouseover");
+                    } else {
+                        var sidebarHeight = $(".sidebar").height();
+                        var maxHeight = $(window).height() - $(".main-header").height();
+                        var overflowHeight = sidebarHeight + $(".main-header").height() - $(window).height();
+                        if (overflowHeight > 0) {
+                            $(".sidebar").height(maxHeight);
+                            $(".sidebar").on("mousewheel", function (e) {
+                                e.preventDefault();
+                                if (e.originalEvent.pageX < $(".sidebar").width()) {
+                                    var marginTop = parseInt($(".sidebar").css("margin-top").replace("px", "")) + e.originalEvent.wheelDelta;
+                                    if (marginTop < 0 && Math.abs(marginTop) > overflowHeight) {
+                                        marginTop = Math.min(overflowHeight, marginTop);
+                                        marginTop = -overflowHeight;
+                                    }
+                                    marginTop = Math.min(0, marginTop);
+                                    $(".sidebar").css("margin-top", marginTop);
+                                }
+                            });
+                            $('.sidebar .treeview-menu').on('mousewheel', function (e) {
+                                e.stopPropagation();
+                            });
+                        }
                     }
-                    $(".sidebar").trigger("mouseover");
                 }
             }
         }
@@ -370,10 +394,12 @@ function _init() {
             var screenWidth = $.AdminLTE.options.screenSizes.sm - 1;
             //Expand sidebar on hover
             $('.main-sidebar').hover(function () {
-                if ($('body').hasClass('sidebar-mini')
-                    && $("body").hasClass('sidebar-collapse')
-                    && $(window).width() > screenWidth) {
-                    _this.expand();
+                if ($.AdminLTE.options.sidebarExpandOnHover) {
+                    if ($('body').hasClass('sidebar-mini')
+                        && $("body").hasClass('sidebar-collapse')
+                        && $(window).width() > screenWidth) {
+                        _this.expand();
+                    }
                 }
             }, function () {
                 if ($('body').hasClass('sidebar-mini')
@@ -385,10 +411,12 @@ function _init() {
         },
         expand: function () {
             $("body").removeClass('sidebar-collapse').addClass('sidebar-expanded-on-hover');
+            $.AdminLTE.layout.fixSidebar();
         },
         collapse: function () {
             if ($('body').hasClass('sidebar-expanded-on-hover')) {
                 $('body').removeClass('sidebar-expanded-on-hover').addClass('sidebar-collapse');
+                // $.AdminLTE.layout.fixSidebar();
             }
         }
     };
@@ -404,13 +432,36 @@ function _init() {
     $.AdminLTE.tree = function (menu) {
         var _this = this;
         var animationSpeed = $.AdminLTE.options.animationSpeed;
+        $(document).off('mouseenter', menu + ' .sidebar-menu > li')
+            .on('mouseenter', menu + ' .sidebar-menu > li', function () {
+                var treemenu = $(this).find("> .treeview-menu");
+                if (treemenu.length > 0) {
+                    if ($("body").hasClass("sidebar-collapse")) {
+                        var liHeight = $(this).height();
+                        var headerHeight = $(".main-header").height();
+                        var maxBottomHeight = $(window).height() - ($(this).offset().top + headerHeight);
+                        var maxTopHeight = $(window).height() - maxBottomHeight - liHeight;
+                        var maxHeight = maxBottomHeight;
+                        if (maxBottomHeight < 300 || maxTopHeight > maxBottomHeight) {
+                            treemenu.css("top", "unset").css("bottom", liHeight);
+                            maxHeight = maxTopHeight;
+                        }
+                        treemenu.css("max-height", maxHeight).css("overflow-y", "auto");
+                    } else {
+                        treemenu.css("max-height", "inherit").css("overflow-y", "unset");
+                    }
+                }
+            });
         $(document).off('click', menu + ' li a')
             .on('click', menu + ' li a', function (e) {
                 //Get the clicked link and the next element
                 var $this = $(this);
                 var checkElement = $this.next();
                 //Check if the next element is a menu and is visible
-                if ((checkElement.is('.treeview-menu')) && (checkElement.is(':visible')) && (!$('body').hasClass('sidebar-collapse'))) {
+                if ((checkElement.is('.treeview-menu')) && (checkElement.is(':visible'))) {
+                    if ($("body").hasClass("sidebar-collapse") && $this.parent().parent().hasClass("sidebar-menu")) {
+                        return false;
+                    }
                     //Close the menu
                     checkElement.slideUp(animationSpeed, function () {
                         checkElement.removeClass('menu-open');
@@ -450,8 +501,9 @@ function _init() {
                         $this.parent().addClass("active");
                     }
                     // modified by FastAdmin
-                    if ($(".show-submenu", menu).size() == 0) {
+                    if ($(".show-submenu", menu).size() == 0 && $this.parent().parent().hasClass("sidebar-menu")) {
                         $this.parent().siblings().find("ul.menu-open").slideUp();
+                        $this.parent().siblings("li.treeview-open").removeClass("treeview-open");
                     }
                 }
                 //if this isn't a link, prevent the page from being redirected

File diff suppressed because it is too large
+ 1032 - 0
public/assets/js/autocomplete.js


+ 151 - 58
public/assets/js/backend/addon.js

@@ -4,7 +4,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
             // 初始化表格参数配置
             Table.api.init({
                 extend: {
-                    index_url: Config.api_url ? Config.api_url + '/addon/index' : "addon/downloaded",
+                    index_url: Config.api_url ? 'addon/index' : "addon/downloaded",
                     add_url: '',
                     edit_url: '',
                     del_url: '',
@@ -17,16 +17,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
             // 弹窗自适应宽高
             var area = Fast.config.openArea != undefined ? Fast.config.openArea : [$(window).width() > 800 ? '800px' : '95%', $(window).height() > 600 ? '600px' : '95%'];
 
-            table.on('load-success.bs.table', function (e, json) {
-                if (json && typeof json.category != 'undefined' && $(".nav-category li").size() == 2) {
-                    $.each(json.category, function (i, j) {
-                        $("<li><a href='javascript:;' data-id='" + j.id + "'>" + j.name + "</a></li>").insertBefore($(".nav-category li:last"));
-                    });
-                }
-            });
-            table.on('load-error.bs.table', function (e, status, res) {
-                if (status == 404 && $(".btn-switch.active").data("type") != "local") {
-                    Layer.confirm(__('Store now available tips'), {
+            var switch_local = function () {
+                if ($(".btn-switch.active").data("type") != "local") {
+                    Layer.confirm(__('Store not available tips'), {
                         title: __('Warmtips'),
                         btn: [__('Switch to the local'), __('Try to reload')]
                     }, function (index) {
@@ -40,6 +33,19 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                     });
                     return false;
                 }
+            };
+            table.on('load-success.bs.table', function (e, json) {
+                if (json && typeof json.category != 'undefined' && $(".nav-category li").size() == 2) {
+                    $.each(json.category, function (i, j) {
+                        $("<li><a href='javascript:;' data-id='" + j.id + "'>" + j.name + "</a></li>").insertBefore($(".nav-category li:last"));
+                    });
+                }
+                if (typeof json.rows === 'undefined' && typeof json.code != 'undefined') {
+                    switch_local();
+                }
+            });
+            table.on('load-error.bs.table', function (e, status, res) {
+                switch_local();
             });
             table.on('post-body.bs.table', function (e, settings, json, xhr) {
                 var parenttable = table.closest('.bootstrap-table');
@@ -156,7 +162,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                     });
                     return res;
                 },
-                dataType: 'jsonp',
                 templateView: false,
                 clickToSelect: false,
                 search: true,
@@ -167,7 +172,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                 commonSearch: true,
                 searchFormVisible: true,
                 searchFormTemplate: 'searchformtpl',
-                pageSize: 50,
+                pageSize: 5,
             });
 
             // 为表格绑定事件
@@ -177,8 +182,31 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
             require(['upload'], function (Upload) {
                 Upload.api.upload("#faupload-addon", function (data, ret) {
                     Config['addons'][data.addon.name] = data.addon;
-                    Toastr.success(ret.msg);
-                    operate(data.addon.name, 'enable', false);
+                    var addon = data.addon;
+                    var testdata = data.addon.testdata;
+                    operate(data.addon.name, 'enable', false, function (data, ret) {
+                        Layer.alert(__('Offline installed tips') + (testdata ? __('Testdata tips') : ""), {
+                            btn: testdata ? [__('Import testdata'), __('Skip testdata')] : [__('OK')],
+                            title: __('Warning'),
+                            yes: function (index) {
+                                if (testdata) {
+                                    Fast.api.ajax({
+                                        url: 'addon/testdata',
+                                        data: {
+                                            name: addon.name,
+                                            version: addon.version,
+                                            faversion: Config.faversion
+                                        }
+                                    }, function (data, ret) {
+                                        Layer.close(index);
+                                    });
+                                } else {
+                                    Layer.close(index);
+                                }
+                            },
+                            icon: 1
+                        });
+                    });
                     return false;
                 }, function (data, ret) {
                     if (ret.msg && ret.msg.match(/(login|登录)/g)) {
@@ -192,7 +220,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                     }
                 });
 
-                //检测是否登录
+                // 检测是否登录
                 $(document).on("mousedown", "#faupload-addon", function (e) {
                     var userinfo = Controller.api.userinfo.get();
                     var uid = userinfo ? userinfo.id : 0;
@@ -219,9 +247,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                 $(".btn-switch").removeClass("active");
                 $(this).addClass("active");
                 $("form.form-commonsearch input[name='type']").val($(this).data("type"));
+                var method = $(this).data("type") == 'local' ? 'hideColumn' : 'showColumn';
+                table.bootstrapTable(method, 'price');
+                table.bootstrapTable(method, 'downloads');
                 table.bootstrapTable('refresh', {url: ($(this).data("url") ? $(this).data("url") : $.fn.bootstrapTable.defaults.extend.index_url), pageNumber: 1});
                 return false;
             });
+
+            // 切换分类
             $(document).on("click", ".nav-category li a", function () {
                 $(".nav-category li").removeClass("active");
                 $(this).parent().addClass("active");
@@ -265,17 +298,18 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                         btn: [__('Login'), __('Register')],
                         yes: function (index, layero) {
                             Fast.api.ajax({
-                                url: Config.api_url + '/user/login',
-                                dataType: 'jsonp',
+                                url: 'addon/login',
+                                type: 'post',
                                 data: {
                                     account: $("#inputAccount", layero).val(),
                                     password: $("#inputPassword", layero).val(),
-                                    _method: 'POST'
+                                    version: Config.faversion,
                                 }
                             }, function (data, ret) {
                                 Controller.api.userinfo.set(data);
                                 Layer.closeAll();
-                                Layer.alert(ret.msg);
+                                Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
+                                return false;
                             }, function (data, ret) {
                             });
                         },
@@ -298,10 +332,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                     });
                 } else {
                     Fast.api.ajax({
-                        url: Config.api_url + '/user/index',
-                        dataType: 'jsonp',
+                        url: 'addon/userinfo',
                         data: {
-                            user_id: userinfo.id,
+                            uid: userinfo.id,
                             token: userinfo.token,
                         }
                     }, function (data) {
@@ -313,17 +346,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                             btn: [__('Logout'), __('Cancel')],
                             yes: function () {
                                 Fast.api.ajax({
-                                    url: Config.api_url + '/user/logout',
-                                    dataType: 'jsonp',
+                                    url: 'addon/logout',
                                     data: {uid: userinfo.id, token: userinfo.token}
                                 }, function (data, ret) {
                                     Controller.api.userinfo.set(null);
                                     Layer.closeAll();
-                                    Layer.alert(ret.msg);
+                                    Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
                                 }, function (data, ret) {
                                     Controller.api.userinfo.set(null);
                                     Layer.closeAll();
-                                    Layer.alert(ret.msg);
+                                    Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
                                 });
                             }
                         });
@@ -337,6 +369,28 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                 }
             });
 
+            //刷新授权
+            $(document).on("click", ".btn-authorization", function () {
+                var userinfo = Controller.api.userinfo.get();
+                if (!userinfo) {
+                    $(".btn-userinfo").trigger("click");
+                    return false;
+                }
+                Layer.confirm(__('Are you sure you want to refresh authorization?'), {icon: 3, title: __('Warmtips')}, function () {
+                    Fast.api.ajax({
+                        url: 'addon/authorization',
+                        data: {
+                            uid: userinfo.id,
+                            token: userinfo.token
+                        }
+                    }, function (data, ret) {
+                        $(".btn-refresh").trigger("click");
+                        Layer.closeAll();
+                    });
+                });
+                return false;
+            });
+
             var install = function (name, version, force) {
                 var userinfo = Controller.api.userinfo.get();
                 var uid = userinfo ? userinfo.id : 0;
@@ -354,30 +408,35 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                 }, function (data, ret) {
                     Layer.closeAll();
                     Config['addons'][data.addon.name] = ret.data.addon;
-                    Layer.alert(__('Online installed tips'), {
-                        btn: [__('OK')],
-                        title: __('Warning'),
-                        icon: 1
+                    operate(data.addon.name, 'enable', false, function () {
+                        Layer.alert(__('Online installed tips') + (data.addon.testdata ? __('Testdata tips') : ""), {
+                            btn: data.addon.testdata ? [__('Import testdata'), __('Skip testdata')] : [__('OK')],
+                            title: __('Warning'),
+                            yes: function (index) {
+                                if (data.addon.testdata) {
+                                    Fast.api.ajax({
+                                        url: 'addon/testdata',
+                                        data: {
+                                            name: name,
+                                            uid: uid,
+                                            token: token,
+                                            version: version,
+                                            faversion: Config.faversion
+                                        }
+                                    }, function (data, ret) {
+                                        Layer.close(index);
+                                    });
+                                } else {
+                                    Layer.close(index);
+                                }
+                            },
+                            icon: 1
+                        });
+                        Controller.api.refresh(table, name);
                     });
-                    Controller.api.refresh(table, name);
                 }, function (data, ret) {
-                    //如果是需要购买的插件则弹出二维码提示
-                    if (ret && ret.code === -1) {
-                        //扫码支付
-                        Layer.open({
-                            content: Template("paytpl", ret.data),
-                            shade: 0.8,
-                            area: area,
-                            skin: 'layui-layer-msg layui-layer-pay',
-                            title: false,
-                            closeBtn: true,
-                            btn: false,
-                            resize: false,
-                            end: function () {
-                                Layer.alert(__('Pay tips'));
-                            }
-                        });
-                    } else if (ret && ret.code === -2) {
+                    var area = Fast.config.openArea != undefined ? Fast.config.openArea : [$(window).width() > 650 ? '650px' : '95%', $(window).height() > 710 ? '710px' : '95%'];
+                    if (ret && ret.code === -2) {
                         //如果登录已经超时,重新提醒登录
                         if (uid && uid != ret.data.uid) {
                             Controller.api.userinfo.set(null);
@@ -387,7 +446,31 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                         top.Fast.api.open(ret.data.payurl, __('Pay now'), {
                             area: area,
                             end: function () {
-                                top.Layer.alert(__('Pay tips'));
+                                Fast.api.ajax({
+                                    url: 'addon/isbuy',
+                                    data: {
+                                        name: name,
+                                        force: force ? 1 : 0,
+                                        uid: uid,
+                                        token: token,
+                                        version: version,
+                                        faversion: Config.faversion
+                                    }
+                                }, function () {
+                                    top.Layer.alert(__('Pay successful tips'), {
+                                        btn: [__('Continue installation')],
+                                        title: __('Warning'),
+                                        icon: 1,
+                                        yes: function (index) {
+                                            top.Layer.close(index);
+                                            install(name, version);
+                                        }
+                                    });
+                                    return false;
+                                }, function () {
+                                    console.log(__('Canceled'));
+                                    return false;
+                                });
                             }
                         });
                     } else if (ret && ret.code === -3) {
@@ -407,7 +490,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                         });
 
                     } else {
-                        Layer.alert(ret.msg);
+                        Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
                     }
                     return false;
                 });
@@ -439,13 +522,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                         });
 
                     } else {
-                        Layer.alert(ret.msg);
+                        Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
                     }
                     return false;
                 });
             };
 
-            var operate = function (name, action, force) {
+            var operate = function (name, action, force, success) {
                 Fast.api.ajax({
                     url: 'addon/state',
                     data: {name: name, action: action, force: force ? 1 : 0}
@@ -453,6 +536,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                     var addon = Config['addons'][name];
                     addon.state = action === 'enable' ? 1 : 0;
                     Layer.closeAll();
+                    if (typeof success === 'function') {
+                        success(data, ret);
+                    }
                     Controller.api.refresh(table, name);
                 }, function (data, ret) {
                     if (ret && ret.code === -3) {
@@ -467,12 +553,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
 
                             },
                             yes: function () {
-                                operate(name, action, true);
+                                operate(name, action, true, success);
                             }
                         });
 
                     } else {
-                        Layer.alert(ret.msg);
+                        Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
                     }
                     return false;
                 });
@@ -490,7 +576,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                     Layer.closeAll();
                     Controller.api.refresh(table, name);
                 }, function (data, ret) {
-                    Layer.alert(ret.msg);
+                    Layer.alert(ret.msg, {title: __('Warning')});
                     return false;
                 });
             };
@@ -554,7 +640,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                 }
                 var version = $(this).data("version");
 
-                Layer.confirm(__('Upgrade tips', Config['addons'][name].title), function () {
+                Layer.confirm(__('Upgrade tips', Config['addons'][name].title), function (index, layero) {
                     upgrade(name, version);
                 });
             });
@@ -646,10 +732,17 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
             refresh: function (table, name) {
                 //刷新左侧边栏
                 Fast.api.refreshmenu();
+                //刷新插件JS缓存
+                Fast.api.ajax({url: require.toUrl('addons.js'), loading: false}, function () {
+                    return false;
+                }, function () {
+                    return false;
+                });
 
                 //刷新行数据
                 if ($(".operate[data-name='" + name + "']").length > 0) {
-                    var index = $(".operate[data-name='" + name + "']").closest("tr[data-index]").data("index");
+                    var tr = $(".operate[data-name='" + name + "']").closest("tr[data-index]");
+                    var index = tr.data("index");
                     var row = Table.api.getrowbyindex(table, index);
                     row.addon = typeof Config['addons'][name] !== 'undefined' ? Config['addons'][name] : undefined;
                     table.bootstrapTable("updateRow", {index: index, row: row});

+ 4 - 1
public/assets/js/backend/auth/rule.js

@@ -180,6 +180,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                 Form.api.bindevent($("form[role=form]"), function (data) {
                     Fast.api.refreshmenu();
                 });
+                $(document).on('change keyup', "#icon", function () {
+                    $(this).prev().find("i").prop("class", $(this).val());
+                });
                 $(document).on('click', ".btn-search-icon", function () {
                     if (iconlist.length == 0) {
                         $.get(Config.site.cdnurl + "/assets/libs/font-awesome/less/variables.less", function (ret) {
@@ -195,7 +198,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                     }
                 });
                 $(document).on('click', '#chooseicon ul li', function () {
-                    $("input[name='row[icon]']").val('fa fa-' + $(this).data("font"));
+                    $("input[name='row[icon]']").val('fa fa-' + $(this).data("font")).trigger("change");
                     Layer.closeAll();
                 });
                 $(document).on('keyup', 'input.js-icon-search', function () {

+ 19 - 3
public/assets/js/backend/general/attachment.js

@@ -143,6 +143,8 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
                 showToggle: false,
                 showExport: false,
                 maintainSelected: true,
+                fixedColumns: true,
+                fixedRightNumber: 1,
                 columns: [
                     [
                         {field: 'state', checkbox: multiple, visible: multiple, operate: false},
@@ -161,9 +163,9 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
                             },
                             formatter: Controller.api.formatter.mimetype
                         },
-                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, datetimeFormat: 'YYYY-MM-DD', operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+                        {field: 'createtime', title: __('Createtime'), width: 120, formatter: Table.api.formatter.datetime, datetimeFormat: 'YYYY-MM-DD', operate: 'RANGE', addclass: 'datetimerange', sortable: true},
                         {
-                            field: 'operate', title: __('Operate'), events: {
+                            field: 'operate', title: __('Operate'), width: 85, events: {
                                 'click .btn-chooseone': function (e, value, row, index) {
                                     Fast.api.close({url: row.url, multiple: multiple});
                                 },
@@ -199,6 +201,10 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
             // 为表格绑定事件
             Table.api.bindevent(table);
             require(['upload'], function (Upload) {
+                $("#toolbar .faupload").data("category", function (file) {
+                    var category = $("ul.nav-tabs[data-field='category'] li.active a").data("value");
+                    return category;
+                });
                 Upload.api.upload($("#toolbar .faupload"), function () {
                     $(".btn-refresh").trigger("click");
                 });
@@ -207,7 +213,17 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
         add: function () {
             //上传完成后刷新父窗口
             $(".faupload").data("upload-complete", function (files) {
-                window.parent.$(".btn-refresh").trigger("click");
+                setTimeout(function () {
+                    window.parent.$(".btn-refresh").trigger("click");
+                }, 100);
+            });
+            // 获取上传类别
+            $("#faupload-third,#faupload-third-chunking").data("category", function (file) {
+                return $("#category-third").val();
+            });
+            // 获取上传类别
+            $("#faupload-local,#faupload-local-chunking").data("category", function (file) {
+                return $("#category-local").val();
             });
             Controller.api.bindevent();
         },

+ 2 - 2
public/assets/js/backend/general/profile.js

@@ -26,8 +26,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'upload'], function (
                     [
                         {field: 'id', title: 'ID'},
                         {field: 'title', title: __('Title')},
-                        {field: 'url', title: __('Url'), formatter: Table.api.formatter.url},
-                        {field: 'ip', title: __('IP')},
+                        {field: 'url', title: __('Url'), align: 'left', formatter: Table.api.formatter.url},
+                        {field: 'ip', title: __('ip'), formatter:Table.api.formatter.search},
                         {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
                     ]
                 ],

+ 105 - 79
public/assets/js/backend/index.js

@@ -56,7 +56,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
             $(document).on("click fa.event.toggleitem", ".sidebar-menu li > a", function (e) {
                 var nextul = $(this).next("ul");
                 if (nextul.length == 0 && (!$(this).parent("li").hasClass("treeview") || ($("body").hasClass("multiplenav") && $(this).parent().parent().hasClass("sidebar-menu")))) {
-                    $(".sidebar-menu li").removeClass("active");
+                    $(".sidebar-menu li").not($(this).parents("li")).removeClass("active");
                 }
                 //当外部触发隐藏的a时,触发父辈a的事件
                 if (!$(this).closest("ul").is(":visible")) {
@@ -236,6 +236,12 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
                 }
             }
 
+            var createCookie = function (name, value) {
+                var date = new Date();
+                date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000));
+                document.cookie = encodeURIComponent(Config.cookie.prefix + name) + "=" + encodeURIComponent(value) + "; expires=" + date.toGMTString();
+            };
+
             var my_skins = [
                 "skin-blue",
                 "skin-black",
@@ -256,9 +262,39 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
                 "skin-black-yellow",
                 "skin-black-pink",
             ];
-            setup();
 
-            function change_layout(cls) {
+            // 皮肤切换
+            $("[data-skin]").on('click', function (e) {
+                var skin = $(this).data('skin');
+                if (!$("body").hasClass(skin)) {
+                    $("body").removeClass(my_skins.join(' ')).addClass(skin);
+                    var cssfile = Config.site.cdnurl + "/assets/css/skins/" + skin + ".css";
+                    $('head').append('<link rel="stylesheet" href="' + cssfile + '" type="text/css" />');
+                    $(".skin-list li.active").removeClass("active");
+                    $(".skin-list li a[data-skin='" + skin + "']").parent().addClass("active");
+                    createCookie('adminskin', skin);
+                }
+                return false;
+            });
+
+            // 收起菜单栏切换
+            $("[data-layout='sidebar-collapse']").on('click', function () {
+                $(".sidebar-toggle").trigger("click");
+            });
+
+            // 切换子菜单显示和菜单小图标的显示
+            $("[data-menu]").on('click', function () {
+                if ($(this).data("menu") == 'show-submenu') {
+                    $("ul.sidebar-menu").toggleClass("show-submenu");
+                    createCookie('show_submenu', $(this).prop("checked") ? 1 : 0)
+                } else {
+                    nav.toggleClass("disable-top-badge");
+                }
+            });
+
+            // 右侧控制栏切换
+            $("[data-controlsidebar]").on('click', function () {
+                var cls = $(this).data('controlsidebar');
                 $("body").toggleClass(cls);
                 AdminLTE.layout.fixSidebar();
                 //Fix the problem with right sidebar and layout boxed
@@ -270,92 +306,82 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
                 }
                 AdminLTE.controlSidebar._fix($(".control-sidebar-bg"));
                 AdminLTE.controlSidebar._fix($(".control-sidebar"));
-            }
+                var slide = !AdminLTE.options.controlSidebarOptions.slide;
+                AdminLTE.options.controlSidebarOptions.slide = slide;
+                if (!slide)
+                    $('.control-sidebar').removeClass('control-sidebar-open');
+            });
 
-            function change_skin(cls) {
-                if (!$("body").hasClass(cls)) {
-                    $("body").removeClass(my_skins.join(' ')).addClass(cls);
-                    localStorage.setItem('skin', cls);
-                    var cssfile = Config.site.cdnurl + "/assets/css/skins/" + cls + ".css";
-                    $('head').append('<link rel="stylesheet" href="' + cssfile + '" type="text/css" />');
+            // 右侧控制栏背景切换
+            $("[data-sidebarskin='toggle']").on('click', function () {
+                var sidebar = $(".control-sidebar");
+                if (sidebar.hasClass("control-sidebar-dark")) {
+                    sidebar.removeClass("control-sidebar-dark")
+                    sidebar.addClass("control-sidebar-light")
+                } else {
+                    sidebar.removeClass("control-sidebar-light")
+                    sidebar.addClass("control-sidebar-dark")
                 }
-                return false;
-            }
+            });
 
-            function setup() {
-                var tmp = localStorage.getItem('skin');
-                if (tmp && $.inArray(tmp, my_skins) != -1)
-                    change_skin(tmp);
-
-                // 皮肤切换
-                $("[data-skin]").on('click', function (e) {
-                    if ($(this).hasClass('knob'))
-                        return;
-                    e.preventDefault();
-                    change_skin($(this).data('skin'));
-                });
+            // 菜单栏展开或收起
+            $("[data-enable='expandOnHover']").on('click', function () {
+                $.AdminLTE.options.sidebarExpandOnHover = $(this).prop("checked") ? 1 : 0;
+                localStorage.setItem('sidebarExpandOnHover', $.AdminLTE.options.sidebarExpandOnHover);
+                AdminLTE.pushMenu.expandOnHover();
+                $.AdminLTE.layout.fixSidebar();
+            });
 
-                // 布局切换
-                $("[data-layout]").on('click', function () {
-                    change_layout($(this).data('layout'));
-                });
+            // 切换菜单栏
+            $(document).on("click", ".sidebar-toggle", function () {
+                var value = $("body").hasClass("sidebar-collapse") ? 1 : 0;
+                setTimeout(function () {
+                    $(window).trigger("resize");
+                }, 300);
+                createCookie('sidebar_collapse', value);
+            });
 
-                // 切换子菜单显示和菜单小图标的显示
-                $("[data-menu]").on('click', function () {
-                    if ($(this).data("menu") == 'show-submenu') {
-                        $("ul.sidebar-menu").toggleClass("show-submenu");
-                    } else {
-                        nav.toggleClass("disable-top-badge");
-                    }
-                });
+            // 切换多级菜单
+            $(document).on("click", "[data-config='multiplenav']", function () {
+                var value = $(this).prop("checked") ? 1 : 0;
+                createCookie('multiplenav', value);
+                location.reload();
+            });
 
-                // 右侧控制栏切换
-                $("[data-controlsidebar]").on('click', function () {
-                    change_layout($(this).data('controlsidebar'));
-                    var slide = !AdminLTE.options.controlSidebarOptions.slide;
-                    AdminLTE.options.controlSidebarOptions.slide = slide;
-                    if (!slide)
-                        $('.control-sidebar').removeClass('control-sidebar-open');
-                });
+            // 切换多选项卡
+            $(document).on("click", "[data-config='multipletab']", function () {
+                var value = $(this).prop("checked") ? 1 : 0;
+                $("body").toggleClass("multipletab", value);
+                createCookie('multipletab', value);
+            });
 
-                // 右侧控制栏背景切换
-                $("[data-sidebarskin='toggle']").on('click', function () {
-                    var sidebar = $(".control-sidebar");
-                    if (sidebar.hasClass("control-sidebar-dark")) {
-                        sidebar.removeClass("control-sidebar-dark")
-                        sidebar.addClass("control-sidebar-light")
-                    } else {
-                        sidebar.removeClass("control-sidebar-light")
-                        sidebar.addClass("control-sidebar-dark")
-                    }
-                });
+            // 重设选项
+            if ($('body').hasClass('fixed')) {
+                $("[data-layout='fixed']").attr('checked', 'checked');
+            }
+            if ($('body').hasClass('layout-boxed')) {
+                $("[data-layout='layout-boxed']").attr('checked', 'checked');
+            }
+            if ($('body').hasClass('sidebar-collapse')) {
+                $("[data-layout='sidebar-collapse']").attr('checked', 'checked');
+            }
+            if ($('ul.sidebar-menu').hasClass('show-submenu')) {
+                $("[data-menu='show-submenu']").attr('checked', 'checked');
+            }
+            if (nav.hasClass('disable-top-badge')) {
+                $("[data-menu='disable-top-badge']").attr('checked', 'checked');
+            }
 
-                // 菜单栏展开或收起
-                $("[data-enable='expandOnHover']").on('click', function () {
-                    $(this).attr('disabled', true);
-                    AdminLTE.pushMenu.expandOnHover();
-                    if (!$('body').hasClass('sidebar-collapse'))
-                        $("[data-layout='sidebar-collapse']").click();
-                });
+            var sidebarExpandOnHover = localStorage.getItem('sidebarExpandOnHover');
+            if (sidebarExpandOnHover == '1') {
+                $("[data-enable='expandOnHover']").trigger("click");
+            }
 
-                // 重设选项
-                if ($('body').hasClass('fixed')) {
-                    $("[data-layout='fixed']").attr('checked', 'checked');
-                }
-                if ($('body').hasClass('layout-boxed')) {
-                    $("[data-layout='layout-boxed']").attr('checked', 'checked');
-                }
-                if ($('body').hasClass('sidebar-collapse')) {
-                    $("[data-layout='sidebar-collapse']").attr('checked', 'checked');
+            $.each(my_skins, function (i, j) {
+                if ($("body").hasClass(j)) {
+                    $(".skin-list li a[data-skin='" + j + "']").parent().addClass("active");
                 }
-                if ($('ul.sidebar-menu').hasClass('show-submenu')) {
-                    $("[data-menu='show-submenu']").attr('checked', 'checked');
-                }
-                if (nav.hasClass('disable-top-badge')) {
-                    $("[data-menu='disable-top-badge']").attr('checked', 'checked');
-                }
-
-            }
+            });
 
             $(window).resize();
 

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

@@ -4,7 +4,7 @@
  * @author: pppscn <35696959@qq.com>
  * @update 2017-05-07 <https://gitee.com/pp/fastadmin>
  *
- * @author: Karson <karsonzhang@163.com>
+ * @author: Karson <karson@fastadmin.net>
  * @update 2018-04-05 <https://gitee.com/karson/fastadmin>
  */
 

+ 12 - 7
public/assets/js/frontend/user.js

@@ -138,27 +138,32 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
                     sortName: 'id',
                     showToggle: false,
                     showExport: false,
+                    fixedColumns: true,
+                    fixedRightNumber: 1,
                     columns: [
                         [
                             {field: 'state', checkbox: multiple, visible: multiple, operate: false},
                             {field: 'id', title: __('Id'), operate: false},
                             {
                                 field: 'url', title: __('Preview'), formatter: function (value, row, index) {
+                                    var html = '';
                                     if (row.mimetype.indexOf("image") > -1) {
-                                        var style = row.storage === 'upyun' ? '!/fwfh/120x90' : '';
-                                        return '<a href="' + row.fullurl + '" target="_blank"><img src="' + row.fullurl + style + '" alt="" style="max-height:90px;max-width:120px"></a>';
+                                        html = '<a href="' + row.fullurl + '" target="_blank"><img src="' + row.fullurl + row.thumb_style + '" alt="" style="max-height:60px;max-width:120px"></a>';
                                     } else {
-                                        return '<a href="' + row.fullurl + '" target="_blank"><img src="' + Fast.api.fixurl("ajax/icon") + "?suffix=" + row.imagetype + '" alt="" style="max-height:90px;max-width:120px"></a>';
+                                        html = '<a href="' + row.fullurl + '" target="_blank"><img src="' + Fast.api.fixurl("ajax/icon") + "?suffix=" + row.imagetype + '" alt="" style="max-height:90px;max-width:120px"></a>';
                                     }
-                                }, operate: false
+                                    return '<div style="width:120px;margin:0 auto;text-align:center;overflow:hidden;white-space: nowrap;text-overflow: ellipsis;">' + html + '</div>';
+                                }
                             },
-                            {field: 'filename', title: __('Filename'), formatter: Table.api.formatter.search, operate: 'like'},
+                            {field: 'filename', title: __('Filename'), formatter: function (value, row, index) {
+                                    return '<div style="width:150px;margin:0 auto;text-align:center;overflow:hidden;white-space: nowrap;text-overflow: ellipsis;">' + Table.api.formatter.search.call(this, value, row, index) + '</div>';
+                                }, operate: 'like'},
                             {field: 'imagewidth', title: __('Imagewidth'), operate: false},
                             {field: 'imageheight', title: __('Imageheight'), operate: false},
                             {field: 'mimetype', title: __('Mimetype'), formatter: Table.api.formatter.search},
-                            {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+                            {field: 'createtime', title: __('Createtime'), width: 120, formatter: Table.api.formatter.datetime, datetimeFormat: 'YYYY-MM-DD', operate: 'RANGE', addclass: 'datetimerange', sortable: true},
                             {
-                                field: 'operate', title: __('Operate'), events: {
+                                field: 'operate', title: __('Operate'), width: 85, events: {
                                     'click .btn-chooseone': function (e, value, row, index) {
                                         Fast.api.close({url: row.url, multiple: multiple});
                                     },

File diff suppressed because it is too large
+ 3347 - 181
public/assets/js/require-backend.min.js


File diff suppressed because it is too large
+ 99 - 45
public/assets/js/require-form.js


File diff suppressed because it is too large
+ 125 - 57
public/assets/js/require-frontend.min.js


+ 6 - 3
public/assets/js/require-table.js

@@ -611,9 +611,12 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                         var data = [];
                         value = value === null ? '' : value.toString();
                         var arr = value != '' ? value.split(",") : [];
+                        var url;
                         $.each(arr, function (index, value) {
+                            url = Fast.api.cdnurl(value);
                             data.push({
-                                src: Fast.api.cdnurl(value),
+                                src: url,
+                                thumb: url + Config.upload.thumbstyle
                             });
                         });
                         Layer.photos({
@@ -638,7 +641,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                     value = value == null || value.length === 0 ? '' : value.toString();
                     value = value ? value : '/assets/img/blank.gif';
                     var classname = typeof this.classname !== 'undefined' ? this.classname : 'img-sm img-center';
-                    return '<a href="javascript:"><img class="' + classname + '" src="' + Fast.api.cdnurl(value) + '" /></a>';
+                    return '<a href="javascript:"><img class="' + classname + '" src="' + Fast.api.cdnurl(value, true) + Config.upload.thumbstyle + '" /></a>';
                 },
                 images: function (value, row, index) {
                     value = value == null || value.length === 0 ? '' : value.toString();
@@ -647,7 +650,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                     var html = [];
                     $.each(arr, function (i, value) {
                         value = value ? value : '/assets/img/blank.gif';
-                        html.push('<a href="javascript:"><img class="' + classname + '" src="' + Fast.api.cdnurl(value) + '" /></a>');
+                        html.push('<a href="javascript:"><img class="' + classname + '" src="' + Fast.api.cdnurl(value, true) + Config.upload.thumbstyle + '" /></a>');
                     });
                     return html.join(' ');
                 },

+ 21 - 2
public/assets/js/require-upload.js

@@ -27,7 +27,8 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                             if ($(button).data("multiple") && inputObj.val() !== "") {
                                 urlArr.push(inputObj.val());
                             }
-                            urlArr.push(data.url);
+                            var url = Config.upload.fullmode ? Fast.api.cdnurl(data.url) : data.url;
+                            urlArr.push(url);
                             inputObj.val(urlArr.join(",")).trigger("change").trigger("validate");
                         }
                         //如果有回调函数
@@ -169,9 +170,15 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                         }(maxsize));
 
                         var options = $(this).data() || {};
+                        options = $.extend(true, {}, options, $(this).data("upload-options") || {});
                         delete options.success;
                         delete options.url;
                         multipart = $.isArray(multipart) ? {} : multipart;
+                        var params = $(this).data("params") || {};
+                        var category = typeof params.category !== 'undefined' ? params.category : ($(this).data("category") || '');
+                        if (category) {
+                            // multipart.category = category;
+                        }
 
                         Upload.list[id] = new Dropzone(this, $.extend({
                             url: url,
@@ -218,6 +225,16 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                                 $(">i", this.element).addClass("dz-message");
                                 this.options.elementHtml = $(this.element).html();
                             },
+                            sending: function (file, xhr, formData) {
+                                if (typeof file.category !== 'undefined') {
+                                    formData.append('category', file.category);
+                                }
+                            },
+                            addedfile: function (file) {
+                                var params = $(this.element).data("params") || {};
+                                var category = typeof params.category !== 'undefined' ? params.category : ($(this.element).data("category") || '');
+                                file.category = typeof category === 'function' ? category.call(this, file) : category;
+                            },
                             addedfiles: function (files) {
                                 if (this.options.maxFiles && (!this.options.maxFiles || this.options.maxFiles > 1) && this.options.inputId) {
                                     var inputObj = $("#" + this.options.inputId);
@@ -247,7 +264,8 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                             error: function (file, response, xhr) {
                                 var responseObj = $("<div>" + (xhr && typeof xhr.responseText !== 'undefined' ? xhr.responseText : response) + "</div>");
                                 responseObj.find("style, title, script").remove();
-                                var ret = {code: 0, data: null, msg: responseObj.text()};
+                                var msg = responseObj.text() || __('Network error');
+                                var ret = {code: 0, data: null, msg: msg};
                                 Upload.events.onUploadError(this, ret, file);
                             },
                             uploadprogress: function (file, progress, bytesSent) {
@@ -347,6 +365,7 @@ define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined,
                                     }
                                     var suffix = /[\.]?([a-zA-Z0-9]+)$/.exec(j);
                                     suffix = suffix ? suffix[1] : 'file';
+                                    j = Config.upload.fullmode ? Fast.api.cdnurl(j) : j;
                                     var value = (json && typeof json[i] !== 'undefined' ? json[i] : null);
                                     var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data(), key: i, index: i, value: value, row: value, suffix: suffix};
                                     var html = tpl ? Template(tpl, data) : Template.render(Upload.config.previewtpl, data);

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


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