浏览代码

新增CRUD生成回收站视图和JS文件
新增一键生成API文档排序功能
新增表格默认导出选项
新增buttons中text和title的function支持
修复buttons中visible和disable为false时的判断Bug
修复后台添加子管理员的权限错误

Karson 6 年之前
父节点
当前提交
88226015a8
共有 35 个文件被更改,包括 2968 次插入2023 次删除
  1. 3 5
      application/admin/command/Addon.php
  2. 3 4
      application/admin/command/Api.php
  3. 37 9
      application/admin/command/Api/library/Builder.php
  4. 48 21
      application/admin/command/Api/library/Extractor.php
  5. 6 0
      application/admin/command/Api/template/index.html
  6. 114 62
      application/admin/command/Crud.php
  7. 1 0
      application/admin/command/Crud/stubs/html/recyclebin-html.stub
  8. 2 0
      application/admin/command/Crud/stubs/index.stub
  9. 1 1
      application/admin/command/Crud/stubs/javascript.stub
  10. 58 0
      application/admin/command/Crud/stubs/mixins/recyclebinjs.stub
  11. 6 1
      application/admin/command/Crud/stubs/model.stub
  12. 25 0
      application/admin/command/Crud/stubs/recyclebin.stub
  13. 1 3
      application/admin/command/Install.php
  14. 18 11
      application/admin/command/Menu.php
  15. 19 39
      application/admin/command/Min.php
  16. 7 5
      application/admin/common.php
  17. 53 83
      application/admin/controller/auth/Group.php
  18. 7 0
      application/admin/lang/zh-cn.php
  19. 7 10
      application/admin/library/traits/Backend.php
  20. 2 0
      application/admin/view/general/config/index.html
  21. 2 2
      application/admin/view/user/group/add.html
  22. 2 2
      application/admin/view/user/group/edit.html
  23. 3 3
      application/admin/view/user/rule/add.html
  24. 3 3
      application/admin/view/user/rule/edit.html
  25. 1 1
      application/admin/view/user/user/edit.html
  26. 51 93
      application/api/controller/User.php
  27. 1 1
      application/common/controller/Frontend.php
  28. 7 1
      public/api.html
  29. 1 1
      public/assets/css/backend.min.css
  30. 2 2
      public/assets/js/backend/general/attachment.js
  31. 1 1
      public/assets/js/bootstrap-table-commonsearch.js
  32. 1 1
      public/assets/js/fast.js
  33. 1262 845
      public/assets/js/require-backend.min.js
  34. 1172 804
      public/assets/js/require-frontend.min.js
  35. 41 9
      public/assets/js/require-table.js

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

@@ -15,7 +15,6 @@ use think\exception\PDOException;
 
 class Addon extends Command
 {
-
     protected function configure()
     {
         $this
@@ -80,7 +79,6 @@ class Addon extends Command
                         $createTableSql = $result[0]['Create Table'];
                     }
                 } catch (PDOException $e) {
-
                 }
 
                 $data = [
@@ -235,7 +233,8 @@ class Addon extends Command
                 $zip->open($addonFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
 
                 $files = new \RecursiveIteratorIterator(
-                    new \RecursiveDirectoryIterator($addonDir), \RecursiveIteratorIterator::LEAVES_ONLY
+                    new \RecursiveDirectoryIterator($addonDir),
+                    \RecursiveIteratorIterator::LEAVES_ONLY
                 );
 
                 foreach ($files as $name => $file) {
@@ -251,7 +250,7 @@ class Addon extends Command
                 $output->info("Package Successed!");
                 break;
 
-            default :
+            default:
                 break;
         }
     }
@@ -315,5 +314,4 @@ class Addon extends Command
     {
         return __DIR__ . '/Addon/stubs/' . $name . '.stub';
     }
-
 }

+ 3 - 4
application/admin/command/Api.php

@@ -12,7 +12,6 @@ use think\Exception;
 
 class Api extends Command
 {
-
     protected function configure()
     {
         $site = Config::get('site');
@@ -84,7 +83,8 @@ class Api extends Command
 
         $controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
         $files = new \RecursiveIteratorIterator(
-            new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
+            new \RecursiveDirectoryIterator($controllerDir),
+            \RecursiveIteratorIterator::LEAVES_ONLY
         );
 
         foreach ($files as $name => $file) {
@@ -150,7 +150,7 @@ class Api extends Command
 
                     //Append the token's value to the name of the namespace
                     $namespace .= $token[1];
-                } else if ($token === ';') {
+                } elseif ($token === ';') {
 
                     //If the token is the semicolon, then we're done with the namespace declaration
                     $getting_namespace = false;
@@ -175,5 +175,4 @@ class Api extends Command
         //Build the fully-qualified class name and return it
         return $namespace ? $namespace . '\\' . $class : $class;
     }
-
 }

+ 37 - 9
application/admin/command/Api/library/Builder.php

@@ -36,16 +36,27 @@ class Builder
 
     protected function extractAnnotations()
     {
-        $st_output = [];
         foreach ($this->classes as $class) {
             $classAnnotation = Extractor::getClassAnnotations($class);
             // 如果忽略
             if (isset($classAnnotation['ApiInternal'])) {
                 continue;
             }
-            $st_output[] = Extractor::getAllClassAnnotations($class);
+            Extractor::getClassMethodAnnotations($class);
         }
-        return end($st_output);
+        $allClassAnnotation = Extractor::getAllClassAnnotations();
+        $allClassMethodAnnotation = Extractor::getAllClassMethodAnnotations();
+
+//        foreach ($allClassMethodAnnotation as $className => &$methods) {
+//            foreach ($methods as &$method) {
+//                //权重判断
+//                if ($method && !isset($method['ApiWeigh']) && isset($allClassAnnotation[$className]['ApiWeigh'])) {
+//                    $method['ApiWeigh'] = $allClassAnnotation[$className]['ApiWeigh'];
+//                }
+//            }
+//        }
+//        unset($methods);
+        return [$allClassAnnotation, $allClassMethodAnnotation];
     }
 
     protected function generateHeadersTemplate($docs)
@@ -148,12 +159,19 @@ class Builder
 
     public function parse()
     {
-        $annotations = $this->extractAnnotations();
+        list($allClassAnnotations, $allClassMethodAnnotations) = $this->extractAnnotations();
 
+        $sectorArr = [];
+        foreach ($allClassAnnotations as $index => $allClassAnnotation) {
+            $sector = isset($allClassAnnotation['ApiSector']) ? $allClassAnnotation['ApiSector'][0] : $allClassAnnotation['ApiTitle'][0];
+            $sectorArr[$sector] = isset($allClassAnnotation['ApiWeigh']) ? $allClassAnnotation['ApiWeigh'][0] : 0;
+        }
+        arsort($sectorArr);
         $counter = 0;
         $section = null;
+        $weigh = 0;
         $docslist = [];
-        foreach ($annotations as $class => $methods) {
+        foreach ($allClassMethodAnnotations as $class => $methods) {
             foreach ($methods as $name => $docs) {
                 if (isset($docs['ApiSector'][0])) {
                     $section = is_array($docs['ApiSector'][0]) ? $docs['ApiSector'][0]['data'] : $docs['ApiSector'][0];
@@ -163,25 +181,36 @@ class Builder
                 if (0 === count($docs)) {
                     continue;
                 }
-
-                $docslist[$section][] = [
+                $docslist[$section][$name] = [
                     'id'                => $counter,
                     'method'            => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
                     'method_label'      => $this->generateBadgeForMethod($docs),
                     'section'           => $section,
                     'route'             => is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0],
-                    'title'           => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0],
+                    'title'             => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0],
                     'summary'           => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
                     'body'              => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '',
                     'headerslist'       => $this->generateHeadersTemplate($docs),
                     'paramslist'        => $this->generateParamsTemplate($docs),
                     'returnheaderslist' => $this->generateReturnHeadersTemplate($docs),
                     'returnparamslist'  => $this->generateReturnParamsTemplate($docs),
+                    'weigh'             => is_array($docs['ApiWeigh'][0]) ? $docs['ApiWeigh'][0]['data'] : $docs['ApiWeigh'][0],
                     'return'            => isset($docs['ApiReturn']) ? is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0] : '',
                 ];
                 $counter++;
             }
         }
+        
+        //重建排序
+        foreach ($docslist as $index => &$methods) {
+            $methodSectorArr = [];
+            foreach ($methods as $name => $method) {
+                $methodSectorArr[$name] = isset($method['weigh']) ? $method['weigh'] : 0;
+            }
+            arsort($methodSectorArr);
+            $methods = array_merge(array_flip(array_keys($methodSectorArr)), $methods);
+        }
+        $docslist = array_merge(array_flip(array_keys($sectorArr)), $docslist);
 
         return $docslist;
     }
@@ -203,5 +232,4 @@ class Builder
 
         return $this->view->display(file_get_contents($template), array_merge($vars, ['docslist' => $docslist]));
     }
-
 }

+ 48 - 21
application/admin/command/Api/library/Extractor.php

@@ -20,6 +20,10 @@ class Extractor
      */
     private static $annotationCache;
 
+    private static $classAnnotationCache;
+
+    private static $classMethodAnnotationCache;
+
     /**
      * Indicates that annotations should has strict behavior, 'false' by default
      * @var boolean
@@ -63,27 +67,43 @@ class Extractor
      * Gets all anotations with pattern @SomeAnnotation() from a given class
      *
      * @param  string $className class name to get annotations
-     * @return array  self::$annotationCache all annotated elements
+     * @return array  self::$classAnnotationCache all annotated elements
      */
     public static function getClassAnnotations($className)
     {
-        if (!isset(self::$annotationCache[$className])) {
+        if (!isset(self::$classAnnotationCache[$className])) {
             $class = new \ReflectionClass($className);
-            self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment());
+            self::$classAnnotationCache[$className] = self::parseAnnotations($class->getDocComment());
         }
 
-        return self::$annotationCache[$className];
+        return self::$classAnnotationCache[$className];
     }
 
-    public static function getAllClassAnnotations($className)
+    /**
+     * 获取类所有方法的属性配置
+     * @param $className
+     * @return mixed
+     * @throws \ReflectionException
+     */
+    public static function getClassMethodAnnotations($className)
     {
         $class = new \ReflectionClass($className);
 
         foreach ($class->getMethods() as $object) {
-            self::$annotationCache['annotations'][$className][$object->name] = self::getMethodAnnotations($className, $object->name);
+            self::$classMethodAnnotationCache[$className][$object->name] = self::getMethodAnnotations($className, $object->name);
         }
 
-        return self::$annotationCache['annotations'];
+        return self::$classMethodAnnotationCache[$className];
+    }
+
+    public static function getAllClassAnnotations()
+    {
+        return self::$classAnnotationCache;
+    }
+
+    public static function getAllClassMethodAnnotations()
+    {
+        return self::$classMethodAnnotationCache;
     }
 
     /**
@@ -181,14 +201,14 @@ class Extractor
         preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblockMethod), $methodArr);
         preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $dockblockClass), $classArr);
 
-        $methodTitle = isset($methodArr[1]) && isset($methodArr[1][0]) ? $methodArr[1][0] : '';
-        $classTitle = isset($classArr[1]) && isset($classArr[1][0]) ? $classArr[1][0] : '';
-
         if (!isset($methodAnnotations['ApiMethod'])) {
             $methodAnnotations['ApiMethod'] = ['get'];
         }
+        if (!isset($methodAnnotations['ApiWeigh'])) {
+            $methodAnnotations['ApiWeigh'] = [0];
+        }
         if (!isset($methodAnnotations['ApiSummary'])) {
-            $methodAnnotations['ApiSummary'] = [$methodTitle];
+            $methodAnnotations['ApiSummary'] = $methodAnnotations['ApiTitle'];
         }
 
         if ($methodAnnotations) {
@@ -210,9 +230,6 @@ class Extractor
                 }
             }
         }
-        if (!isset($methodAnnotations['ApiTitle'])) {
-            $methodAnnotations['ApiTitle'] = [$methodTitle];
-        }
         if (!isset($methodAnnotations['ApiRoute'])) {
             $urlArr = [];
             $className = $class->getName();
@@ -231,7 +248,7 @@ class Extractor
             $methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
         }
         if (!isset($methodAnnotations['ApiSector'])) {
-            $methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : [$classTitle];
+            $methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : $classAnnotations['ApiTitle'];
         }
         if (!isset($methodAnnotations['ApiParams'])) {
             $params = self::parseCustomAnnotations($docblockMethod, 'param');
@@ -292,7 +309,7 @@ class Extractor
                     $argsParts = trim($matches['args'][$i]);
                     if ($name == 'ApiReturn') {
                         $value = $argsParts;
-                    } else if ($matches['args'][$i] != '') {
+                    } elseif ($matches['args'][$i] != '') {
                         $argsParts = preg_replace("/\{(\w+)\}/", '#$1#', $argsParts);
                         $value = self::parseArgs($argsParts);
                         if (is_string($value)) {
@@ -307,6 +324,11 @@ class Extractor
         if (stripos($docblock, '@ApiInternal') !== false) {
             $annotations['ApiInternal'] = [true];
         }
+        if (!isset($annotations['ApiTitle'])) {
+            preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblock), $matchArr);
+            $title = isset($matchArr[1]) && isset($matchArr[1][0]) ? $matchArr[1][0] : '';
+            $annotations['ApiTitle'] = [$title];
+        }
 
         return $annotations;
     }
@@ -354,7 +376,9 @@ class Extractor
                     // close delimiter
                     if ($c !== $nextDelimiter) {
                         throw new Exception(sprintf(
-                            "Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
+                            "Parse Error: enclosing error -> expected: [%s], given: [%s]",
+                            $nextDelimiter,
+                            $c
                         ));
                     }
 
@@ -362,7 +386,8 @@ class Extractor
                     if ($i < $len) {
                         if (',' !== substr($content, $i, 1) && '\\' !== $prev_c) {
                             throw new Exception(sprintf(
-                                "Parse Error: missing comma separator near: ...%s<--", substr($content, ($i - 10), $i)
+                                "Parse Error: missing comma separator near: ...%s<--",
+                                substr($content, ($i - 10), $i)
                             ));
                         }
                     }
@@ -387,7 +412,9 @@ class Extractor
                         // it means that the string was not enclosed, so it is parsing error.
                         if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter)) {
                             throw new Exception(sprintf(
-                                "Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
+                                "Parse Error: enclosing error -> expected: [%s], given: [%s]",
+                                $nextDelimiter,
+                                $c
                             ));
                         }
 
@@ -416,7 +443,8 @@ class Extractor
                         // if the string is composing yet means that the structure of var. never was enclosed with '}'
                         if ($subComposing) {
                             throw new Exception(sprintf(
-                                "Parse Error: Composite variable is not enclosed correctly. near: ...%s'", $subc
+                                "Parse Error: Composite variable is not enclosed correctly. near: ...%s'",
+                                $subc
                             ));
                         }
 
@@ -479,5 +507,4 @@ class Extractor
 
         return $val;
     }
-
 }

+ 6 - 0
application/admin/command/Api/template/index.html

@@ -92,6 +92,9 @@
                     padding-left:0px;
                 }
             }
+            .label-primary {
+                background-color: #248aff;
+            }
 
         </style>
     </head>
@@ -547,6 +550,9 @@
                         contentType: false,
                         processData: false,
                         headers: headers,
+                        xhrFields: {
+                            withCredentials: true
+                        },
                         success: function (data, textStatus, xhr) {
                             if (typeof data === 'object') {
                                 var str = JSON.stringify(data, null, 2);

+ 114 - 62
application/admin/command/Crud.php

@@ -15,7 +15,6 @@ use think\Loader;
 
 class Crud extends Command
 {
-
     protected $stubList = [];
 
     /**
@@ -93,7 +92,7 @@ class Crud extends Command
     /**
      * 保留字段
      */
-    protected $reservedField = ['admin_id', 'createtime', 'updatetime'];
+    protected $reservedField = ['admin_id'];
 
     /**
      * 排除字段
@@ -112,13 +111,31 @@ class Crud extends Command
     protected $headingFilterField = 'status';
 
     /**
+     * 添加时间字段
+     * @var string
+     */
+    protected $createTimeField = 'createtime';
+
+    /**
+     * 更新时间字段
+     * @var string
+     */
+    protected $updateTimeField = 'updatetime';
+
+    /**
+     * 软删除时间字段
+     * @var string
+     */
+    protected $deleteTimeField = 'deletetime';
+
+    /**
      * 编辑器的Class
      */
     protected $editorClass = 'editor';
 
     /**
      * langList的key最长字节数
-    */
+     */
     protected $fieldMaxLen = 0;
 
     protected function configure()
@@ -215,32 +232,47 @@ class Crud extends Command
         $headingfilterfield = $input->getOption('headingfilterfield');
         //编辑器Class
         $editorclass = $input->getOption('editorclass');
-        if ($setcheckboxsuffix)
+        if ($setcheckboxsuffix) {
             $this->setCheckboxSuffix = $setcheckboxsuffix;
-        if ($enumradiosuffix)
+        }
+        if ($enumradiosuffix) {
             $this->enumRadioSuffix = $enumradiosuffix;
-        if ($imagefield)
+        }
+        if ($imagefield) {
             $this->imageField = $imagefield;
-        if ($filefield)
+        }
+        if ($filefield) {
             $this->fileField = $filefield;
-        if ($intdatesuffix)
+        }
+        if ($intdatesuffix) {
             $this->intDateSuffix = $intdatesuffix;
-        if ($switchsuffix)
+        }
+        if ($switchsuffix) {
             $this->switchSuffix = $switchsuffix;
-        if ($citysuffix)
+        }
+        if ($citysuffix) {
             $this->citySuffix = $citysuffix;
-        if ($selectpagesuffix)
+        }
+        if ($selectpagesuffix) {
             $this->selectpageSuffix = $selectpagesuffix;
-        if ($selectpagessuffix)
+        }
+        if ($selectpagessuffix) {
             $this->selectpagesSuffix = $selectpagessuffix;
-        if ($ignoreFields)
+        }
+        if ($ignoreFields) {
             $this->ignoreFields = $ignoreFields;
-        if ($editorclass)
+        }
+        if ($editorclass) {
             $this->editorClass = $editorclass;
-        if ($sortfield)
+        }
+        if ($sortfield) {
             $this->sortField = $sortfield;
-        if ($headingfilterfield)
+        }
+        if ($headingfilterfield) {
             $this->headingFilterField = $headingfilterfield;
+        }
+
+        $this->reservedField = array_merge($this->reservedField, [$this->createTimeField, $this->updateTimeField, $this->deleteTimeField]);
 
         $dbname = Config::get('database.database');
         $prefix = Config::get('database.prefix');
@@ -254,11 +286,11 @@ class Crud extends Command
         $modelName = $table = stripos($table, $prefix) === 0 ? substr($table, strlen($prefix)) : $table;
         $modelTableType = 'table';
         $modelTableTypeName = $modelTableName = $modelName;
-        $modelTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$modelTableName}'", [], TRUE);
+        $modelTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$modelTableName}'", [], true);
         if (!$modelTableInfo) {
             $modelTableType = 'name';
             $modelTableName = $prefix . $modelName;
-            $modelTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$modelTableName}'", [], TRUE);
+            $modelTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$modelTableName}'", [], true);
             if (!$modelTableInfo) {
                 throw new Exception("table not found");
             }
@@ -275,11 +307,11 @@ class Crud extends Command
                 $relationName = stripos($relationTable, $prefix) === 0 ? substr($relationTable, strlen($prefix)) : $relationTable;
                 $relationTableType = 'table';
                 $relationTableTypeName = $relationTableName = $relationName;
-                $relationTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$relationTableName}'", [], TRUE);
+                $relationTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$relationTableName}'", [], true);
                 if (!$relationTableInfo) {
                     $relationTableType = 'name';
                     $relationTableName = $prefix . $relationName;
-                    $relationTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$relationTableName}'", [], TRUE);
+                    $relationTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$relationTableName}'", [], true);
                     if (!$relationTableInfo) {
                         throw new Exception("relation table not found");
                     }
@@ -338,6 +370,7 @@ class Crud extends Command
         $addFile = $viewDir . 'add.html';
         $editFile = $viewDir . 'edit.html';
         $indexFile = $viewDir . 'index.html';
+        $recyclebinFile = $viewDir . 'recyclebin.html';
         $langFile = $adminPath . 'lang' . DS . Lang::detect() . DS . $controllerBaseName . '.php';
 
         //是否为删除模式
@@ -355,12 +388,13 @@ class Crud extends Command
                 }
             }
             foreach ($readyFiles as $k => $v) {
-                if (file_exists($v))
+                if (file_exists($v)) {
                     unlink($v);
+                }
                 //删除空文件夹
                 if ($v == $modelFile) {
                     $this->removeEmptyBaseDir($v, $modelArr);
-                } else if ($v == $validateFile) {
+                } elseif ($v == $validateFile) {
                     $this->removeEmptyBaseDir($v, $validateArr);
                 } else {
                     $this->removeEmptyBaseDir($v, $controllerArr);
@@ -434,7 +468,7 @@ class Crud extends Command
         $langList = [];
         $field = 'id';
         $order = 'id';
-        $priDefined = FALSE;
+        $priDefined = false;
         $priKey = '';
         $relationPrimaryKey = '';
         foreach ($columnList as $k => $v) {
@@ -484,6 +518,7 @@ class Crud extends Command
             $appendAttrList = [];
             $controllerAssignList = [];
             $headingHtml = '{:build_heading()}';
+            $recyclebinHtml = '';
 
             //循环所有字段,开始构造视图的HTML和JS信息
             foreach ($columnList as $k => $v) {
@@ -506,7 +541,7 @@ class Crud extends Command
                     $langList[] = $this->getLangItem($field, $v['COLUMN_COMMENT']);
                 }
                 $inputType = '';
-                //createtime和updatetime是保留字段不能修改和添加
+                //保留字段不能修改和添加
                 if ($v['COLUMN_KEY'] != 'PRI' && !in_array($field, $this->reservedField) && !in_array($field, $this->ignoreFields)) {
                     $inputType = $this->getFieldType($v);
 
@@ -534,7 +569,7 @@ class Crud extends Command
 
                         $this->getEnum($getEnumArr, $controllerAssignList, $field, $itemArr, $v['DATA_TYPE'] == 'set' ? 'multiple' : 'select');
 
-                        $itemArr = $this->getLangArray($itemArr, FALSE);
+                        $itemArr = $this->getLangArray($itemArr, false);
                         //添加一个获取器
                         $this->getAttr($getAttrArr, $field, $v['DATA_TYPE'] == 'set' ? 'multiple' : 'select');
                         if ($v['DATA_TYPE'] == 'set') {
@@ -543,28 +578,29 @@ class Crud extends Command
                         $this->appendAttr($appendAttrList, $field);
                         $formAddElement = $this->getReplacedStub('html/select', ['field' => $field, 'fieldName' => $fieldName, 'fieldList' => $this->getFieldListName($field), 'attrStr' => Form::attributes($attrArr), 'selectedValue' => $defaultValue]);
                         $formEditElement = $this->getReplacedStub('html/select', ['field' => $field, 'fieldName' => $fieldName, 'fieldList' => $this->getFieldListName($field), 'attrStr' => Form::attributes($attrArr), 'selectedValue' => "\$row.{$field}"]);
-                    } else if ($inputType == 'datetime') {
+                    } elseif ($inputType == 'datetime') {
                         $cssClassArr[] = 'datetimepicker';
                         $attrArr['class'] = implode(' ', $cssClassArr);
                         $format = "YYYY-MM-DD HH:mm:ss";
                         $phpFormat = "Y-m-d H:i:s";
                         $fieldFunc = '';
                         switch ($v['DATA_TYPE']) {
-                            case 'year';
+                            case 'year':
                                 $format = "YYYY";
                                 $phpFormat = 'Y';
                                 break;
-                            case 'date';
+                            case 'date':
                                 $format = "YYYY-MM-DD";
                                 $phpFormat = 'Y-m-d';
                                 break;
-                            case 'time';
+                            case 'time':
                                 $format = "HH:mm:ss";
                                 $phpFormat = 'H:i:s';
                                 break;
-                            case 'timestamp';
+                            case 'timestamp':
                                 $fieldFunc = 'datetime';
-                            case 'datetime';
+                            // no break
+                            case 'datetime':
                                 $format = "YYYY-MM-DD HH:mm:ss";
                                 $phpFormat = 'Y-m-d H:i:s';
                                 break;
@@ -581,13 +617,13 @@ class Crud extends Command
                         $fieldFunc = $fieldFunc ? "|{$fieldFunc}" : "";
                         $formAddElement = Form::text($fieldName, $defaultDateTime, $attrArr);
                         $formEditElement = Form::text($fieldName, "{\$row.{$field}{$fieldFunc}}", $attrArr);
-                    } else if ($inputType == 'checkbox' || $inputType == 'radio') {
+                    } elseif ($inputType == 'checkbox' || $inputType == 'radio') {
                         unset($attrArr['data-rule']);
                         $fieldName = $inputType == 'checkbox' ? $fieldName .= "[]" : $fieldName;
                         $attrArr['name'] = "row[{$fieldName}]";
 
                         $this->getEnum($getEnumArr, $controllerAssignList, $field, $itemArr, $inputType);
-                        $itemArr = $this->getLangArray($itemArr, FALSE);
+                        $itemArr = $this->getLangArray($itemArr, false);
                         //添加一个获取器
                         $this->getAttr($getAttrArr, $field, $inputType);
                         if ($inputType == 'checkbox') {
@@ -598,13 +634,13 @@ class Crud extends Command
 
                         $formAddElement = $this->getReplacedStub('html/' . $inputType, ['field' => $field, 'fieldName' => $fieldName, 'fieldList' => $this->getFieldListName($field), 'attrStr' => Form::attributes($attrArr), 'selectedValue' => $defaultValue]);
                         $formEditElement = $this->getReplacedStub('html/' . $inputType, ['field' => $field, 'fieldName' => $fieldName, 'fieldList' => $this->getFieldListName($field), 'attrStr' => Form::attributes($attrArr), 'selectedValue' => "\$row.{$field}"]);
-                    } else if ($inputType == 'textarea') {
+                    } elseif ($inputType == 'textarea') {
                         $cssClassArr[] = $this->isMatchSuffix($field, $this->editorSuffix) ? $this->editorClass : '';
                         $attrArr['class'] = implode(' ', $cssClassArr);
                         $attrArr['rows'] = 5;
                         $formAddElement = Form::textarea($fieldName, $defaultValue, $attrArr);
                         $formEditElement = Form::textarea($fieldName, $editValue, $attrArr);
-                    } else if ($inputType == 'switch') {
+                    } elseif ($inputType == 'switch') {
                         unset($attrArr['data-rule']);
                         if ($defaultValue === '1' || $defaultValue === 'Y') {
                             $yes = $defaultValue;
@@ -619,7 +655,7 @@ class Crud extends Command
                         $stateNoClass = 'fa-flip-horizontal text-gray';
                         $formAddElement = $this->getReplacedStub('html/' . $inputType, ['field' => $field, 'fieldName' => $fieldName, 'fieldYes' => $yes, 'fieldNo' => $no, 'attrStr' => Form::attributes($attrArr), 'fieldValue' => $defaultValue, 'fieldSwitchClass' => $defaultValue == $no ? $stateNoClass : '']);
                         $formEditElement = $this->getReplacedStub('html/' . $inputType, ['field' => $field, 'fieldName' => $fieldName, 'fieldYes' => $yes, 'fieldNo' => $no, 'attrStr' => Form::attributes($attrArr), 'fieldValue' => "{\$row.{$field}}", 'fieldSwitchClass' => "{eq name=\"\$row.{$field}\" value=\"{$no}\"}fa-flip-horizontal text-gray{/eq}"]);
-                    } else if ($inputType == 'citypicker') {
+                    } elseif ($inputType == 'citypicker') {
                         $attrArr['class'] = implode(' ', $cssClassArr);
                         $attrArr['data-toggle'] = "city-picker";
                         $formAddElement = sprintf("<div class='control-relative'>%s</div>", Form::input('text', $fieldName, $defaultValue, $attrArr));
@@ -692,9 +728,13 @@ class Crud extends Command
                 if ($v['DATA_TYPE'] != 'text') {
                     //主键
                     if ($v['COLUMN_KEY'] == 'PRI' && !$priDefined) {
-                        $priDefined = TRUE;
+                        $priDefined = true;
                         $javascriptList[] = "{checkbox: true}";
                     }
+                    if ($this->deleteTimeField == $field) {
+                        $recyclebinHtml = $this->getReplacedStub('html/recyclebin-html', ['controllerUrl' => $controllerUrl]);
+                        continue;
+                    }
                     if (!$fields || in_array($field, explode(',', $fields))) {
                         //构造JS列信息
                         $javascriptList[] = $this->getJsColumn($field, $v['DATA_TYPE'], $inputType && in_array($inputType, ['select', 'checkbox', 'radio']) ? '_text' : '', $itemArr);
@@ -743,7 +783,7 @@ class Crud extends Command
                 }
             }
             unset($line);
-            $langList = implode(",\n", array_filter($langList)). ",";
+            $langList = implode(",\n", array_filter($langList)) . ",";
 
             //表注释
             $tableComment = $modelTableInfo['Comment'];
@@ -754,7 +794,6 @@ class Crud extends Command
                 $modelInit = $this->getReplacedStub('mixins' . DS . 'modelinit', ['order' => $order]);
             }
 
-
             $data = [
                 'controllerNamespace'     => $controllerNamespace,
                 'modelNamespace'          => $modelNamespace,
@@ -777,14 +816,18 @@ class Crud extends Command
                 'editList'                => $editList,
                 'javascriptList'          => $javascriptList,
                 'langList'                => $langList,
-                'modelAutoWriteTimestamp' => in_array('createtime', $fieldArr) || in_array('updatetime', $fieldArr) ? "'int'" : 'false',
-                'createTime'              => in_array('createtime', $fieldArr) ? "'createtime'" : 'false',
-                'updateTime'              => in_array('updatetime', $fieldArr) ? "'updatetime'" : 'false',
+                'sofeDeleteClassPath'     => in_array($this->deleteTimeField, $fieldArr) ? "use traits\model\SoftDelete;" : '',
+                'softDelete'              => in_array($this->deleteTimeField, $fieldArr) ? "use SoftDelete;" : '',
+                'modelAutoWriteTimestamp' => in_array($this->createTimeField, $fieldArr) || in_array($this->updateTimeField, $fieldArr) ? "'int'" : 'false',
+                'createTime'              => in_array($this->createTimeField, $fieldArr) ? "'{$this->createTimeField}'" : 'false',
+                'updateTime'              => in_array($this->updateTimeField, $fieldArr) ? "'{$this->updateTimeField}'" : 'false',
+                'deleteTime'              => in_array($this->deleteTimeField, $fieldArr) ? "'{$this->deleteTimeField}'" : 'false',
                 'relationSearch'          => $relations ? 'true' : 'false',
                 'relationWithList'        => '',
                 'relationMethodList'      => '',
                 'controllerIndex'         => '',
                 'headingHtml'             => $headingHtml,
+                'recyclebinHtml'          => $recyclebinHtml,
                 'visibleFieldList'        => $fields ? "\$row->visible(['" . implode("','", array_filter(explode(',', $fields))) . "']);" : '',
                 'appendAttrList'          => implode(",\n", $appendAttrList),
                 'getEnumList'             => implode("\n\n", $getEnumArr),
@@ -832,8 +875,7 @@ class Crud extends Command
 
                 //需要重写index方法
                 $data['controllerIndex'] = $this->getReplacedStub('controllerindex', $data);
-
-            } else if ($fields) {
+            } elseif ($fields) {
                 $data = array_merge($data, ['relationWithList' => '', 'relationMethodList' => '', 'relationVisibleFieldList' => '']);
                 //需要重写index方法
                 $data['controllerIndex'] = $this->getReplacedStub('controllerindex', $data);
@@ -852,7 +894,6 @@ class Crud extends Command
                         $result = $this->writeToFile('relationmodel', $relation, $relation['relationFile']);
                     }
                 }
-
             }
             // 生成验证文件
             $result = $this->writeToFile('validate', $data, $validateFile);
@@ -860,6 +901,12 @@ class Crud extends Command
             $result = $this->writeToFile('add', $data, $addFile);
             $result = $this->writeToFile('edit', $data, $editFile);
             $result = $this->writeToFile('index', $data, $indexFile);
+            if ($recyclebinHtml) {
+                $result = $this->writeToFile('recyclebin', $data, $recyclebinFile);
+                $recyclebinTitle = in_array('title', $fieldArr) ? 'title' : (in_array('name', $fieldArr) ? 'name' : '');
+                $recyclebinTitleJs = $recyclebinTitle ? "\n                        {field: '{$recyclebinTitle}', title: __('" . (ucfirst($recyclebinTitle)) . "'), align: 'left'}," : '';
+                $data['recyclebinJs'] = $this->getReplacedStub('mixins/recyclebinjs', ['recyclebinTitleJs' => $recyclebinTitleJs, 'controllerUrl' => $controllerUrl]);
+            }
             // 生成JS文件
             $result = $this->writeToFile('javascript', $data, $javascriptFile);
             // 生成语言文件
@@ -880,8 +927,9 @@ class Crud extends Command
 
     protected function getEnum(&$getEnum, &$controllerAssignList, $field, $itemArr = '', $inputType = '')
     {
-        if (!in_array($inputType, ['datetime', 'select', 'multiple', 'checkbox', 'radio']))
+        if (!in_array($inputType, ['datetime', 'select', 'multiple', 'checkbox', 'radio'])) {
             return;
+        }
         $fieldList = $this->getFieldListName($field);
         $methodName = 'get' . ucfirst($fieldList);
         foreach ($itemArr as $k => &$v) {
@@ -902,22 +950,24 @@ EOD;
 
     protected function getAttr(&$getAttr, $field, $inputType = '')
     {
-        if (!in_array($inputType, ['datetime', 'select', 'multiple', 'checkbox', 'radio']))
+        if (!in_array($inputType, ['datetime', 'select', 'multiple', 'checkbox', 'radio'])) {
             return;
+        }
         $attrField = ucfirst($this->getCamelizeName($field));
         $getAttr[] = $this->getReplacedStub("mixins" . DS . $inputType, ['field' => $field, 'methodName' => "get{$attrField}TextAttr", 'listMethodName' => "get{$attrField}List"]);
     }
 
     protected function setAttr(&$setAttr, $field, $inputType = '')
     {
-        if (!in_array($inputType, ['datetime', 'checkbox', 'select']))
+        if (!in_array($inputType, ['datetime', 'checkbox', 'select'])) {
             return;
+        }
         $attrField = ucfirst($this->getCamelizeName($field));
         if ($inputType == 'datetime') {
             $return = <<<EOD
 return \$value && !is_numeric(\$value) ? strtotime(\$value) : \$value;
 EOD;
-        } else if (in_array($inputType, ['checkbox', 'select'])) {
+        } elseif (in_array($inputType, ['checkbox', 'select'])) {
             $return = <<<EOD
 return is_array(\$value) ? implode(',', \$value) : \$value;
 EOD;
@@ -1118,7 +1168,7 @@ EOD;
      * @param boolean $withTpl
      * @return array
      */
-    protected function getLangArray($arr, $withTpl = TRUE)
+    protected function getLangArray($arr, $withTpl = true)
     {
         $langArr = [];
         foreach ($arr as $k => $v) {
@@ -1134,8 +1184,9 @@ EOD;
      */
     protected function getArrayString($arr)
     {
-        if (!is_array($arr))
+        if (!is_array($arr)) {
             return $arr;
+        }
         $stringArr = [];
         foreach ($arr as $k => $v) {
             $is_var = in_array(substr($v, 0, 1), ['$', '_']);
@@ -1197,11 +1248,11 @@ EOD;
             case 'tinytext':
                 $inputType = 'textarea';
                 break;
-            case 'year';
-            case 'date';
-            case 'time';
-            case 'datetime';
-            case 'timestamp';
+            case 'year':
+            case 'date':
+            case 'time':
+            case 'datetime':
+            case 'timestamp':
                 $inputType = 'datetime';
                 break;
             default:
@@ -1327,8 +1378,9 @@ EOD;
             $formatter = 'label';
         }
         foreach ($itemArr as $k => &$v) {
-            if (substr($v, 0, 3) !== '__(')
+            if (substr($v, 0, 3) !== '__(') {
                 $v = "__('" . mb_ucfirst($v) . "')";
+            }
         }
         unset($v);
         $searchList = json_encode($itemArr, JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE);
@@ -1338,22 +1390,23 @@ EOD;
         }
         if (in_array($datatype, ['date', 'datetime']) || $formatter === 'datetime') {
             $html .= ", operate:'RANGE', addclass:'datetimerange'";
-        } else if (in_array($datatype, ['float', 'double', 'decimal'])) {
+        } elseif (in_array($datatype, ['float', 'double', 'decimal'])) {
             $html .= ", operate:'BETWEEN'";
         }
         if (in_array($datatype, ['set'])) {
             $html .= ", operate:'FIND_IN_SET'";
         }
-        if (in_array($formatter, ['image','images'])) {
+        if (in_array($formatter, ['image', 'images'])) {
             $html .= ", events: Table.api.events.image";
         }
         if ($itemArr && !$formatter) {
             $formatter = 'normal';
         }
-        if ($formatter)
+        if ($formatter) {
             $html .= ", formatter: Table.api.formatter." . $formatter . "}";
-        else
+        } else {
             $html .= "}";
+        }
         return $html;
     }
 
@@ -1367,5 +1420,4 @@ EOD;
     {
         return $this->getCamelizeName($field) . 'List';
     }
-
 }

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

@@ -0,0 +1 @@
+<a class="btn btn-success btn-recyclebin btn-dialog" href="{%controllerUrl%}/recyclebin" title="{:__('Recycle bin')}"><i class="fa fa-recycle"></i> {:__('Recycle bin')}</a>

+ 2 - 0
application/admin/command/Crud/stubs/index.stub

@@ -19,6 +19,8 @@
                                 <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
                             </ul>
                         </div>
+
+                        {%recyclebinHtml%}
                     </div>
                     <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
                            data-operate-edit="{:$auth->check('{%controllerUrl%}/edit')}" 

+ 1 - 1
application/admin/command/Crud/stubs/javascript.stub

@@ -30,7 +30,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
 
             // 为表格绑定事件
             Table.api.bindevent(table);
-        },
+        },{%recyclebinJs%}
         add: function () {
             Controller.api.bindevent();
         },

+ 58 - 0
application/admin/command/Crud/stubs/mixins/recyclebinjs.stub

@@ -0,0 +1,58 @@
+
+        recyclebin: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    'dragsort_url': ''
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: '{%controllerUrl%}/recyclebin',
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},{%recyclebinTitleJs%}
+                        {
+                            field: 'deletetime',
+                            title: __('Deletetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate',
+                            width: '130px',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            buttons: [
+                                {
+                                    name: 'Restore',
+                                    text: __('Restore'),
+                                    classname: 'btn btn-xs btn-info btn-restoreit',
+                                    icon: 'fa fa-rotate-left',
+                                    url: '{%controllerUrl%}/restore'
+                                },
+                                {
+                                    name: 'Destroy',
+                                    text: __('Destroy'),
+                                    classname: 'btn btn-xs btn-danger btn-destroyit',
+                                    icon: 'fa fa-times',
+                                    url: '{%controllerUrl%}/destroy'
+                                }
+                            ],
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },

+ 6 - 1
application/admin/command/Crud/stubs/model.stub

@@ -3,9 +3,13 @@
 namespace {%modelNamespace%};
 
 use think\Model;
+{%sofeDeleteClassPath%}
 
 class {%modelName%} extends Model
 {
+
+    {%softDelete%}
+
     // 表名
     protected ${%modelTableType%} = '{%modelTableTypeName%}';
     
@@ -15,7 +19,8 @@ class {%modelName%} extends Model
     // 定义时间戳字段名
     protected $createTime = {%createTime%};
     protected $updateTime = {%updateTime%};
-    
+    protected $deleteTime = {%deleteTime%};
+
     // 追加属性
     protected $append = [
 {%appendAttrList%}

+ 25 - 0
application/admin/command/Crud/stubs/recyclebin.stub

@@ -0,0 +1,25 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh')}
+                        <a class="btn btn-info btn-multi btn-disabled disabled {:$auth->check('{%controllerUrl%}/restore')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/restore" data-action="restore"><i class="fa fa-rotate-left"></i> {:__('Restore')}</a>
+                        <a class="btn btn-danger btn-multi btn-disabled disabled {:$auth->check('{%controllerUrl%}/destroy')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/destroy" data-action="destroy"><i class="fa fa-times"></i> {:__('Destroy')}</a>
+                        <a class="btn btn-success btn-restoreall {:$auth->check('{%controllerUrl%}/restore')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/restore" title="{:__('Restore all')}"><i class="fa fa-rotate-left"></i> {:__('Restore all')}</a>
+                        <a class="btn btn-danger btn-destroyall {:$auth->check('{%controllerUrl%}/destroy')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/destroy" title="{:__('Destroy all')}"><i class="fa fa-times"></i> {:__('Destroy all')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover"
+                           data-operate-restore="{:$auth->check('{%controllerUrl%}/restore')}"
+                           data-operate-destroy="{:$auth->check('{%controllerUrl%}/destroy')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 1 - 3
application/admin/command/Install.php

@@ -13,7 +13,6 @@ use think\Exception;
 
 class Install extends Command
 {
-
     protected $model = null;
 
     protected function configure()
@@ -27,7 +26,7 @@ class Install extends Command
             ->addOption('prefix', 'r', Option::VALUE_OPTIONAL, 'table prefix', $config['prefix'])
             ->addOption('username', 'u', Option::VALUE_OPTIONAL, 'mysql username', $config['username'])
             ->addOption('password', 'p', Option::VALUE_OPTIONAL, 'mysql password', $config['password'])
-            ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', FALSE)
+            ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', false)
             ->setDescription('New installation of FastAdmin');
     }
 
@@ -93,5 +92,4 @@ class Install extends Command
 
         $output->info("Install Successed!");
     }
-
 }

+ 18 - 11
application/admin/command/Menu.php

@@ -15,7 +15,6 @@ use think\Exception;
 
 class Menu extends Command
 {
-
     protected $model = null;
 
     protected function configure()
@@ -32,7 +31,6 @@ class Menu extends Command
 
     protected function execute(Input $input, Output $output)
     {
-
         $this->model = new AuthRule();
         $adminPath = dirname(__DIR__) . DS;
         //控制器名
@@ -54,10 +52,11 @@ class Menu extends Command
             $ids = [];
             $list = $this->model->where(function ($query) use ($controller, $equal) {
                 foreach ($controller as $index => $item) {
-                    if ($equal)
+                    if ($equal) {
                         $query->whereOr('name', 'eq', $item);
-                    else
+                    } else {
                         $query->whereOr('name', 'like', strtolower($item) . "%");
+                    }
                 }
             })->select();
             foreach ($list as $k => $v) {
@@ -94,8 +93,11 @@ class Menu extends Command
                 }
                 $this->importRule($item);
             }
-
         } else {
+            $authRuleList = AuthRule::select();
+            //生成权限规则备份文件
+            file_put_contents(RUNTIME_PATH . 'authrule.json', json_encode(collection($authRuleList)->toArray()));
+
             $this->model->where('id', '>', 0)->delete();
             $controllerDir = $adminPath . 'controller' . DS;
             // 扫描新的节点信息并导入
@@ -199,7 +201,7 @@ class Menu extends Command
             }
         }
         //忽略的类
-        if (stripos($classComment, "@internal") !== FALSE) {
+        if (stripos($classComment, "@internal") !== false) {
             return;
         }
         preg_match_all('#(@.*?)\n#s', $classComment, $annotations);
@@ -208,10 +210,10 @@ class Menu extends Command
         //判断注释中是否设置了icon值
         if (isset($annotations[1])) {
             foreach ($annotations[1] as $tag) {
-                if (stripos($tag, '@icon') !== FALSE) {
+                if (stripos($tag, '@icon') !== false) {
                     $controllerIcon = substr($tag, stripos($tag, ' ') + 1);
                 }
-                if (stripos($tag, '@remark') !== FALSE) {
+                if (stripos($tag, '@remark') !== false) {
                     $controllerRemark = substr($tag, stripos($tag, ' ') + 1);
                 }
             }
@@ -226,7 +228,13 @@ class Menu extends Command
         $pid = 0;
         foreach ($controllerArr as $k => $v) {
             $key = $k + 1;
-            $name = strtolower(implode('/', array_slice($controllerArr, 0, $key)));
+            //驼峰转下划线
+            $controllerNameArr = array_slice($controllerArr, 0, $key);
+            foreach ($controllerNameArr as &$val) {
+                $val = strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $val), "_"));
+            }
+            unset($val);
+            $name = implode('/', $controllerNameArr);
             $title = (!isset($controllerArr[$key]) ? $controllerTitle : '');
             $icon = (!isset($controllerArr[$key]) ? $controllerIcon : 'fa fa-list');
             $remark = (!isset($controllerArr[$key]) ? $controllerRemark : '');
@@ -259,7 +267,7 @@ class Menu extends Command
             }
             $comment = $reflector->getMethod($n->name)->getDocComment();
             //忽略的方法
-            if (stripos($comment, "@internal") !== FALSE) {
+            if (stripos($comment, "@internal") !== false) {
                 continue;
             }
             //过滤掉其它字符
@@ -285,5 +293,4 @@ class Menu extends Command
             return $id ? $id : null;
         }
     }
-
 }

+ 19 - 39
application/admin/command/Min.php

@@ -37,12 +37,10 @@ class Min extends Command
         $resource = $input->getOption('resource') ?: '';
         $optimize = $input->getOption('optimize') ?: 'none';
 
-        if (!$module || !in_array($module, ['frontend', 'backend', 'all']))
-        {
+        if (!$module || !in_array($module, ['frontend', 'backend', 'all'])) {
             throw new Exception('Please input correct module name');
         }
-        if (!$resource || !in_array($resource, ['js', 'css', 'all']))
-        {
+        if (!$resource || !in_array($resource, ['js', 'css', 'all'])) {
             throw new Exception('Please input correct resource name');
         }
 
@@ -55,41 +53,31 @@ class Min extends Command
 
         $nodeExec = '';
 
-        if (!$nodeExec)
-        {
-            if (IS_WIN)
-            {
+        if (!$nodeExec) {
+            if (IS_WIN) {
                 // Winsows下请手动配置配置该值,一般将该值配置为 '"C:\Program Files\nodejs\node.exe"',除非你的Node安装路径有变更
                 $nodeExec = 'C:\Program Files\nodejs\node.exe';
-                if (file_exists($nodeExec)){
+                if (file_exists($nodeExec)) {
                     $nodeExec = '"' . $nodeExec . '"';
-                }else{
+                } else {
                     // 如果 '"C:\Program Files\nodejs\node.exe"' 不存在,可能是node安装路径有变更
                     // 但安装node会自动配置环境变量,直接执行 '"node.exe"' 提高第一次使用压缩打包的成功率
                     $nodeExec = '"node.exe"';
                 }
-            }
-            else
-            {
-                try
-                {
+            } else {
+                try {
                     $nodeExec = exec("which node");
-                    if (!$nodeExec)
-                    {
+                    if (!$nodeExec) {
                         throw new Exception("node environment not found!please install node first!");
                     }
-                }
-                catch (Exception $e)
-                {
+                } catch (Exception $e) {
                     throw new Exception($e->getMessage());
                 }
             }
         }
 
-        foreach ($moduleArr as $mod)
-        {
-            foreach ($resourceArr as $res)
-            {
+        foreach ($moduleArr as $mod) {
+            foreach ($resourceArr as $res) {
                 $data = [
                     'publicPath'  => $publicPath,
                     'jsBaseName'  => str_replace('{module}', $mod, $this->options['jsBaseName']),
@@ -104,17 +92,14 @@ class Min extends Command
 
                 //源文件
                 $from = $data["{$res}BasePath"] . $data["{$res}BaseName"] . '.' . $res;
-                if (!is_file($from))
-                {
+                if (!is_file($from)) {
                     $output->error("{$res} source file not found!file:{$from}");
                     continue;
                 }
-                if ($res == "js")
-                {
+                if ($res == "js") {
                     $content = file_get_contents($from);
                     preg_match("/require\.config\(\{[\r\n]?[\n]?+(.*?)[\r\n]?[\n]?}\);/is", $content, $matches);
-                    if (!isset($matches[1]))
-                    {
+                    if (!isset($matches[1])) {
                         $output->error("js config not found!");
                         continue;
                     }
@@ -128,16 +113,14 @@ class Min extends Command
 
                 // 执行压缩
                 $command = "{$nodeExec} \"{$minPath}r.js\" -o \"{$tempFile}\" >> \"{$minPath}node.log\"";
-                if ($output->isDebug())
-                {
+                if ($output->isDebug()) {
                     $output->warning($command);
                 }
                 echo exec($command);
             }
         }
 
-        if (!$output->isDebug())
-        {
+        if (!$output->isDebug()) {
             @unlink($tempFile);
         }
 
@@ -154,16 +137,14 @@ class Min extends Command
     protected function writeToFile($name, $data, $pathname)
     {
         $search = $replace = [];
-        foreach ($data as $k => $v)
-        {
+        foreach ($data as $k => $v) {
             $search[] = "{%{$k}%}";
             $replace[] = $v;
         }
         $stub = file_get_contents($this->getStub($name));
         $content = str_replace($search, $replace, $stub);
 
-        if (!is_dir(dirname($pathname)))
-        {
+        if (!is_dir(dirname($pathname))) {
             mkdir(strtolower(dirname($pathname)), 0755, true);
         }
         return file_put_contents($pathname, $content);
@@ -178,5 +159,4 @@ class Min extends Command
     {
         return __DIR__ . DS . 'Min' . DS . 'stubs' . DS . $name . '.stub';
     }
-
 }

+ 7 - 5
application/admin/common.php

@@ -74,6 +74,7 @@ if (!function_exists('build_category_select')) {
      * @param string $type
      * @param mixed $selected
      * @param array $attr
+     * @param array $header
      * @return string
      */
     function build_category_select($name, $type, $selected = null, $attr = [], $header = [])
@@ -98,14 +99,14 @@ if (!function_exists('build_toolbar')) {
      * @param array $attr 按钮属性值
      * @return string
      */
-    function build_toolbar($btns = NULL, $attr = [])
+    function build_toolbar($btns = null, $attr = [])
     {
         $auth = \app\admin\library\Auth::instance();
         $controller = str_replace('.', '/', strtolower(think\Request::instance()->controller()));
         $btns = $btns ? $btns : ['refresh', 'add', 'edit', 'del', 'import'];
         $btns = is_array($btns) ? $btns : explode(',', $btns);
         $index = array_search('delete', $btns);
-        if ($index !== FALSE) {
+        if ($index !== false) {
             $btns[$index] = 'del';
         }
         $btnAttr = [
@@ -140,7 +141,7 @@ if (!function_exists('build_toolbar')) {
                 }
                 $download .= empty($download) ? '' : "\n                            ";
                 if (!empty($download)) {
-                $html[] = <<<EOT
+                    $html[] = <<<EOT
                         <div class="btn-group">
                             <button type="button" href="{$href}" class="btn btn-info btn-import" title="{$title}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="{$icon}"></i> {$text}</button>
                             <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" title="下载批量导入模版">
@@ -169,7 +170,7 @@ if (!function_exists('build_heading')) {
      * @param string $path 指定的path
      * @return string
      */
-    function build_heading($path = NULL, $container = TRUE)
+    function build_heading($path = null, $container = true)
     {
         $title = $content = '';
         if (is_null($path)) {
@@ -183,8 +184,9 @@ if (!function_exists('build_heading')) {
             $title = __($data['title']);
             $content = __($data['remark']);
         }
-        if (!$content)
+        if (!$content) {
             return '';
+        }
         $result = '<div class="panel-lead"><em>' . $title . '</em>' . $content . '</div>';
         if ($container) {
             $result = '<div class="panel-heading">' . $result . '</div>';

+ 53 - 83
application/admin/controller/auth/Group.php

@@ -37,21 +37,16 @@ class Group extends Backend
 
         Tree::instance()->init($groupList);
         $result = [];
-        if ($this->auth->isSuperAdmin())
-        {
+        if ($this->auth->isSuperAdmin()) {
             $result = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0));
-        }
-        else
-        {
+        } else {
             $groups = $this->auth->getGroups();
-            foreach ($groups as $m => $n)
-            {
+            foreach ($groups as $m => $n) {
                 $result = array_merge($result, Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['pid'])));
             }
         }
         $groupName = [];
-        foreach ($result as $k => $v)
-        {
+        foreach ($result as $k => $v) {
             $groupName[$v['id']] = $v['name'];
         }
 
@@ -66,20 +61,16 @@ class Group extends Backend
      */
     public function index()
     {
-        if ($this->request->isAjax())
-        {
+        if ($this->request->isAjax()) {
             $list = AuthGroup::all(array_keys($this->groupdata));
             $list = collection($list)->toArray();
             $groupList = [];
-            foreach ($list as $k => $v)
-            {
+            foreach ($list as $k => $v) {
                 $groupList[$v['id']] = $v;
             }
             $list = [];
-            foreach ($this->groupdata as $k => $v)
-            {
-                if (isset($groupList[$k]))
-                {
+            foreach ($this->groupdata as $k => $v) {
+                if (isset($groupList[$k])) {
                     $groupList[$k]['name'] = $v;
                     $list[] = $groupList[$k];
                 }
@@ -97,17 +88,14 @@ class Group extends Backend
      */
     public function add()
     {
-        if ($this->request->isPost())
-        {
+        if ($this->request->isPost()) {
             $params = $this->request->post("row/a", [], 'strip_tags');
             $params['rules'] = explode(',', $params['rules']);
-            if (!in_array($params['pid'], $this->childrenGroupIds))
-            {
+            if (!in_array($params['pid'], $this->childrenGroupIds)) {
                 $this->error(__('The parent group can not be its own child'));
             }
             $parentmodel = model("AuthGroup")->get($params['pid']);
-            if (!$parentmodel)
-            {
+            if (!$parentmodel) {
                 $this->error(__('The parent group can not found'));
             }
             // 父级别的规则节点
@@ -120,8 +108,7 @@ class Group extends Backend
             // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
             $rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
             $params['rules'] = implode(',', $rules);
-            if ($params)
-            {
+            if ($params) {
                 $this->model->create($params);
                 $this->success();
             }
@@ -133,24 +120,22 @@ class Group extends Backend
     /**
      * 编辑
      */
-    public function edit($ids = NULL)
+    public function edit($ids = null)
     {
         $row = $this->model->get(['id' => $ids]);
-        if (!$row)
+        if (!$row) {
             $this->error(__('No Results were found'));
-        if ($this->request->isPost())
-        {
+        }
+        if ($this->request->isPost()) {
             $params = $this->request->post("row/a", [], 'strip_tags');
             // 父节点不能是它自身的子节点
-            if (!in_array($params['pid'], $this->childrenGroupIds))
-            {
+            if (!in_array($params['pid'], $this->childrenGroupIds)) {
                 $this->error(__('The parent group can not be its own child'));
             }
             $params['rules'] = explode(',', $params['rules']);
 
             $parentmodel = model("AuthGroup")->get($params['pid']);
-            if (!$parentmodel)
-            {
+            if (!$parentmodel) {
                 $this->error(__('The parent group can not found'));
             }
             // 父级别的规则节点
@@ -163,8 +148,7 @@ class Group extends Backend
             // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
             $rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
             $params['rules'] = implode(',', $rules);
-            if ($params)
-            {
+            if ($params) {
                 $row->save($params);
                 $this->success();
             }
@@ -180,11 +164,10 @@ class Group extends Backend
      */
     public function del($ids = "")
     {
-        if ($ids)
-        {
+        if ($ids) {
             $ids = explode(',', $ids);
             $grouplist = $this->auth->getGroups();
-            $group_ids = array_map(function($group) {
+            $group_ids = array_map(function ($group) {
                 return $group['id'];
             }, $grouplist);
             // 移除掉当前管理员所在组别
@@ -193,30 +176,25 @@ class Group extends Backend
             // 循环判断每一个组别是否可删除
             $grouplist = $this->model->where('id', 'in', $ids)->select();
             $groupaccessmodel = model('AuthGroupAccess');
-            foreach ($grouplist as $k => $v)
-            {
+            foreach ($grouplist as $k => $v) {
                 // 当前组别下有管理员
                 $groupone = $groupaccessmodel->get(['group_id' => $v['id']]);
-                if ($groupone)
-                {
+                if ($groupone) {
                     $ids = array_diff($ids, [$v['id']]);
                     continue;
                 }
                 // 当前组别下有子组别
                 $groupone = $this->model->get(['pid' => $v['id']]);
-                if ($groupone)
-                {
+                if ($groupone) {
                     $ids = array_diff($ids, [$v['id']]);
                     continue;
                 }
             }
-            if (!$ids)
-            {
+            if (!$ids) {
                 $this->error(__('You can not delete group that contain child group and administrators'));
             }
             $count = $this->model->where('id', 'in', $ids)->delete();
-            if ($count)
-            {
+            if ($count) {
                 $this->success();
             }
         }
@@ -235,7 +213,7 @@ class Group extends Backend
 
     /**
      * 读取角色权限树
-     * 
+     *
      * @internal
      */
     public function roletree()
@@ -246,35 +224,32 @@ class Group extends Backend
         $id = $this->request->post("id");
         $pid = $this->request->post("pid");
         $parentGroupModel = $model->get($pid);
-        $currentGroupModel = NULL;
-        if ($id)
-        {
+        $currentGroupModel = null;
+        if ($id) {
             $currentGroupModel = $model->get($id);
         }
-        if (($pid || $parentGroupModel) && (!$id || $currentGroupModel))
-        {
-            $id = $id ? $id : NULL;
+        if (($pid || $parentGroupModel) && (!$id || $currentGroupModel)) {
+            $id = $id ? $id : null;
             $ruleList = collection(model('AuthRule')->order('weigh', 'desc')->order('id', 'asc')->select())->toArray();
             //读取父类角色所有节点列表
             $parentRuleList = [];
-            if (in_array('*', explode(',', $parentGroupModel->rules)))
-            {
+            if (in_array('*', explode(',', $parentGroupModel->rules))) {
                 $parentRuleList = $ruleList;
-            }
-            else
-            {
+            } else {
                 $parentRuleIds = explode(',', $parentGroupModel->rules);
-                foreach ($ruleList as $k => $v)
-                {
-                    if (in_array($v['id'], $parentRuleIds))
-                    {
+                foreach ($ruleList as $k => $v) {
+                    if (in_array($v['id'], $parentRuleIds)) {
                         $parentRuleList[] = $v;
                     }
                 }
             }
 
+            $ruleTree = new Tree();
+            $groupTree = new Tree();
             //当前所有正常规则列表
-            Tree::instance()->init($parentRuleList);
+            $ruleTree->init($parentRuleList);
+            //角色组列表
+            $groupTree->init(collection(model('AuthGroup')->where('id', 'in', $this->childrenGroupIds)->select())->toArray());
 
             //读取当前角色下规则ID集合
             $adminRuleIds = $this->auth->getRuleIds();
@@ -283,39 +258,34 @@ class Group extends Backend
             //当前拥有的规则ID集合
             $currentRuleIds = $id ? explode(',', $currentGroupModel->rules) : [];
 
-            if (!$id || !in_array($pid, Tree::instance()->getChildrenIds($id, TRUE)))
-            {
-                $parentRuleList = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'name');
+            if (!$id || !in_array($pid, $this->childrenGroupIds) || !in_array($pid, $groupTree->getChildrenIds($id, true))) {
+                $parentRuleList = $ruleTree->getTreeList($ruleTree->getTreeArray(0), 'name');
                 $hasChildrens = [];
-                foreach ($parentRuleList as $k => $v)
-                {
-                    if ($v['haschild'])
+                foreach ($parentRuleList as $k => $v) {
+                    if ($v['haschild']) {
                         $hasChildrens[] = $v['id'];
+                    }
                 }
-                $parentRuleIds = array_map(function($item) {
+                $parentRuleIds = array_map(function ($item) {
                     return $item['id'];
                 }, $parentRuleList);
                 $nodeList = [];
-                foreach ($parentRuleList as $k => $v)
-                {
-                    if (!$superadmin && !in_array($v['id'], $adminRuleIds))
+                foreach ($parentRuleList as $k => $v) {
+                    if (!$superadmin && !in_array($v['id'], $adminRuleIds)) {
                         continue;
-                    if ($v['pid'] && !in_array($v['pid'], $parentRuleIds))
+                    }
+                    if ($v['pid'] && !in_array($v['pid'], $parentRuleIds)) {
                         continue;
+                    }
                     $state = array('selected' => in_array($v['id'], $currentRuleIds) && !in_array($v['id'], $hasChildrens));
                     $nodeList[] = array('id' => $v['id'], 'parent' => $v['pid'] ? $v['pid'] : '#', 'text' => __($v['title']), 'type' => 'menu', 'state' => $state);
                 }
                 $this->success('', null, $nodeList);
-            }
-            else
-            {
+            } else {
                 $this->error(__('Can not change the parent to child'));
             }
-        }
-        else
-        {
+        } else {
             $this->error(__('Group not found'));
         }
     }
-
 }

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

@@ -113,6 +113,12 @@ return [
     '%d year%s ago'                                         => '%d年前',
     'Set to normal'                                         => '设为正常',
     'Set to hidden'                                         => '设为隐藏',
+    'Recycle bin'                                           => '回收站',
+    'Restore'                                               => '还原',
+    'Restore all'                                           => '还原全部',
+    'Destroy'                                               => '销毁',
+    'Destroy all'                                           => '清空回收站',
+    'Nothing need restore'                                  => '没有需要还原的数据',
     //提示
     'Go back'                                               => '返回首页',
     'Jump now'                                              => '立即跳转',
@@ -130,6 +136,7 @@ return [
     'Are you sure you want to delete the %s selected item?' => '确定删除选中的 %s 项?',
     'Are you sure you want to delete this item?'            => '确定删除此项?',
     'Are you sure you want to delete or turncate?'          => '确定删除或清空?',
+    'Are you sure you want to truncate?'                    => '确定清空?',
     'You have no permission'                                => '你没有权限访问',
     'Please enter your username'                            => '请输入你的用户名',
     'Please enter your password'                            => '请输入你的密码',

+ 7 - 10
application/admin/library/traits/Backend.php

@@ -7,6 +7,8 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
 use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
 use PhpOffice\PhpSpreadsheet\Reader\Xls;
 use PhpOffice\PhpSpreadsheet\Reader\Csv;
+use think\Exception;
+
 trait Backend
 {
 
@@ -19,15 +21,12 @@ trait Backend
     {
         if (is_array($this->excludeFields)) {
             foreach ($this->excludeFields as $field) {
-                if (key_exists($field,$params))
-                {
+                if (key_exists($field, $params)) {
                     unset($params[$field]);
                 }
             }
         } else {
-
-            if (key_exists($this->excludeFields,$params))
-            {
+            if (key_exists($this->excludeFields, $params)) {
                 unset($params[$this->excludeFields]);
             }
         }
@@ -104,7 +103,6 @@ trait Backend
         if ($this->request->isPost()) {
             $params = $this->request->post("row/a");
             if ($params) {
-
                 $params = $this->preExcludeFields($params);
 
                 if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
@@ -137,11 +135,12 @@ trait Backend
     /**
      * 编辑
      */
-    public function edit($ids = NULL)
+    public function edit($ids = null)
     {
         $row = $this->model->get($ids);
-        if (!$row)
+        if (!$row) {
             $this->error(__('No Results were found'));
+        }
         $adminIds = $this->getDataLimitAdminIds();
         if (is_array($adminIds)) {
             if (!in_array($row[$this->dataLimitField], $adminIds)) {
@@ -151,7 +150,6 @@ trait Backend
         if ($this->request->isPost()) {
             $params = $this->request->post("row/a");
             if ($params) {
-
                 $params = $this->preExcludeFields($params);
 
                 try {
@@ -419,5 +417,4 @@ trait Backend
 
         $this->success();
     }
-
 }

+ 2 - 0
application/admin/view/general/config/index.html

@@ -100,6 +100,7 @@
                                                     <input id="c-{$item.name}" class="form-control" size="50" name="row[{$item.name}]" type="text" value="{$item.value}" data-tip="{$item.tip}">
                                                     <span><button type="button" id="plupload-{$item.name}" class="btn btn-danger plupload" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}" data-preview-id="p-{$item.name}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
                                                     <span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
+                                                    <span class="msg-box n-right" for="c-{$item.name}"></span>
                                                     <ul class="row list-inline plupload-preview" id="p-{$item.name}"></ul>
                                                 </div>
                                                 {/case}
@@ -109,6 +110,7 @@
                                                     <input id="c-{$item.name}" class="form-control" size="50" name="row[{$item.name}]" type="text" value="{$item.value}" data-tip="{$item.tip}">
                                                     <span><button type="button" id="plupload-{$item.name}" class="btn btn-danger plupload" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
                                                     <span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
+                                                    <span class="msg-box n-right" for="c-{$item.name}"></span>
                                                 </div>
                                                 {/case}
                                                 {case switch}

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

@@ -7,7 +7,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="nickname" class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
         <div class="col-xs-12 col-sm-8">
             <span class="text-muted"><input type="checkbox" name="" id="checkall" /> <label for="checkall"><small>{:__('Check all')}</small></label></span>
             <span class="text-muted"><input type="checkbox" name="" id="expandall" /> <label for="expandall"><small>{:__('Expand all')}</small></label></span>
@@ -16,7 +16,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="c-status" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
 
             <div class="radio">

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

@@ -7,7 +7,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="nickname" class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
         <div class="col-xs-12 col-sm-8">
             <span class="text-muted"><input type="checkbox" name="" id="checkall" /> <label for="checkall"><small>{:__('Check all')}</small></label></span>
             <span class="text-muted"><input type="checkbox" name="" id="expandall" /> <label for="expandall"><small>{:__('Expand all')}</small></label></span>
@@ -16,7 +16,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="c-status" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             
             <div class="radio">

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

@@ -1,13 +1,13 @@
 <form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[ismenu]', ['1'=>__('Yes'), '0'=>__('No')])}
         </div>
     </div>
 
     <div class="form-group">
-        <label for="c-pid" class="control-label col-xs-12 col-sm-2">{:__('Pid')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Pid')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_select('row[pid]', $ruledata, null, ['class'=>'form-control', 'required'=>''])}
         </div>
@@ -37,7 +37,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <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')])}
         </div>

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

@@ -1,13 +1,13 @@
 <form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
 
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[ismenu]', ['1'=>__('Yes'), '0'=>__('No')], $row['ismenu'])}
         </div>
     </div>
     <div class="form-group">
-        <label for="pid" class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_select('row[pid]', $ruledata, $row['pid'], ['class'=>'form-control', 'required'=>''])}
         </div>
@@ -37,7 +37,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <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')], $row['status'])}
         </div>

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

@@ -21,7 +21,7 @@
     <div class="form-group">
         <label for="c-password" class="control-label col-xs-12 col-sm-2">{:__('Password')}:</label>
         <div class="col-xs-12 col-sm-4">
-            <input id="c-password" data-rule="" class="form-control" name="row[password]" type="text" value="" placeholder="{:__('Leave password blank if dont want to change')}" autocomplete="new-password" />
+            <input id="c-password" data-rule="password" class="form-control" name="row[password]" type="text" value="" placeholder="{:__('Leave password blank if dont want to change')}" autocomplete="new-password" />
         </div>
     </div>
     <div class="form-group">

+ 51 - 93
application/api/controller/User.php

@@ -32,7 +32,7 @@ class User extends Api
 
     /**
      * 会员登录
-     * 
+     *
      * @param string $account 账号
      * @param string $password 密码
      */
@@ -40,25 +40,21 @@ class User extends Api
     {
         $account = $this->request->request('account');
         $password = $this->request->request('password');
-        if (!$account || !$password)
-        {
+        if (!$account || !$password) {
             $this->error(__('Invalid parameters'));
         }
         $ret = $this->auth->login($account, $password);
-        if ($ret)
-        {
+        if ($ret) {
             $data = ['userinfo' => $this->auth->getUserinfo()];
             $this->success(__('Logged in successful'), $data);
-        }
-        else
-        {
+        } else {
             $this->error($this->auth->getError());
         }
     }
 
     /**
      * 手机验证码登录
-     * 
+     *
      * @param string $mobile 手机号
      * @param string $captcha 验证码
      */
@@ -66,43 +62,34 @@ class User extends Api
     {
         $mobile = $this->request->request('mobile');
         $captcha = $this->request->request('captcha');
-        if (!$mobile || !$captcha)
-        {
+        if (!$mobile || !$captcha) {
             $this->error(__('Invalid parameters'));
         }
-        if (!Validate::regex($mobile, "^1\d{10}$"))
-        {
+        if (!Validate::regex($mobile, "^1\d{10}$")) {
             $this->error(__('Mobile is incorrect'));
         }
-        if (!Sms::check($mobile, $captcha, 'mobilelogin'))
-        {
+        if (!Sms::check($mobile, $captcha, 'mobilelogin')) {
             $this->error(__('Captcha is incorrect'));
         }
         $user = \app\common\model\User::getByMobile($mobile);
-        if ($user)
-        {
+        if ($user) {
             //如果已经有账号则直接登录
             $ret = $this->auth->direct($user->id);
-        }
-        else
-        {
+        } else {
             $ret = $this->auth->register($mobile, Random::alnum(), '', $mobile, []);
         }
-        if ($ret)
-        {
+        if ($ret) {
             Sms::flush($mobile, 'mobilelogin');
             $data = ['userinfo' => $this->auth->getUserinfo()];
             $this->success(__('Logged in successful'), $data);
-        }
-        else
-        {
+        } else {
             $this->error($this->auth->getError());
         }
     }
 
     /**
      * 注册会员
-     * 
+     *
      * @param string $username 用户名
      * @param string $password 密码
      * @param string $email 邮箱
@@ -114,26 +101,20 @@ class User extends Api
         $password = $this->request->request('password');
         $email = $this->request->request('email');
         $mobile = $this->request->request('mobile');
-        if (!$username || !$password)
-        {
+        if (!$username || !$password) {
             $this->error(__('Invalid parameters'));
         }
-        if ($email && !Validate::is($email, "email"))
-        {
+        if ($email && !Validate::is($email, "email")) {
             $this->error(__('Email is incorrect'));
         }
-        if ($mobile && !Validate::regex($mobile, "^1\d{10}$"))
-        {
+        if ($mobile && !Validate::regex($mobile, "^1\d{10}$")) {
             $this->error(__('Mobile is incorrect'));
         }
         $ret = $this->auth->register($username, $password, $email, $mobile, []);
-        if ($ret)
-        {
+        if ($ret) {
             $data = ['userinfo' => $this->auth->getUserinfo()];
             $this->success(__('Sign up successful'), $data);
-        }
-        else
-        {
+        } else {
             $this->error($this->auth->getError());
         }
     }
@@ -149,7 +130,7 @@ class User extends Api
 
     /**
      * 修改会员个人信息
-     * 
+     *
      * @param string $avatar 头像地址
      * @param string $username 用户名
      * @param string $nickname 昵称
@@ -162,12 +143,13 @@ class User extends Api
         $nickname = $this->request->request('nickname');
         $bio = $this->request->request('bio');
         $avatar = $this->request->request('avatar');
-        $exists = \app\common\model\User::where('username', $username)->where('id', '<>', $this->auth->id)->find();
-        if ($exists)
-        {
-            $this->error(__('Username already exists'));
+        if ($username) {
+            $exists = \app\common\model\User::where('username', $username)->where('id', '<>', $this->auth->id)->find();
+            if ($exists) {
+                $this->error(__('Username already exists'));
+            }
+            $user->username = $username;
         }
-        $user->username = $username;
         $user->nickname = $nickname;
         $user->bio = $bio;
         $user->avatar = $avatar;
@@ -177,7 +159,7 @@ class User extends Api
 
     /**
      * 修改邮箱
-     * 
+     *
      * @param string $email 邮箱
      * @param string $captcha 验证码
      */
@@ -186,21 +168,17 @@ class User extends Api
         $user = $this->auth->getUser();
         $email = $this->request->post('email');
         $captcha = $this->request->request('captcha');
-        if (!$email || !$captcha)
-        {
+        if (!$email || !$captcha) {
             $this->error(__('Invalid parameters'));
         }
-        if (!Validate::is($email, "email"))
-        {
+        if (!Validate::is($email, "email")) {
             $this->error(__('Email is incorrect'));
         }
-        if (\app\common\model\User::where('email', $email)->where('id', '<>', $user->id)->find())
-        {
+        if (\app\common\model\User::where('email', $email)->where('id', '<>', $user->id)->find()) {
             $this->error(__('Email already exists'));
         }
         $result = Ems::check($email, $captcha, 'changeemail');
-        if (!$result)
-        {
+        if (!$result) {
             $this->error(__('Captcha is incorrect'));
         }
         $verification = $user->verification;
@@ -215,7 +193,7 @@ class User extends Api
 
     /**
      * 修改手机号
-     * 
+     *
      * @param string $email 手机号
      * @param string $captcha 验证码
      */
@@ -224,21 +202,17 @@ class User extends Api
         $user = $this->auth->getUser();
         $mobile = $this->request->request('mobile');
         $captcha = $this->request->request('captcha');
-        if (!$mobile || !$captcha)
-        {
+        if (!$mobile || !$captcha) {
             $this->error(__('Invalid parameters'));
         }
-        if (!Validate::regex($mobile, "^1\d{10}$"))
-        {
+        if (!Validate::regex($mobile, "^1\d{10}$")) {
             $this->error(__('Mobile is incorrect'));
         }
-        if (\app\common\model\User::where('mobile', $mobile)->where('id', '<>', $user->id)->find())
-        {
+        if (\app\common\model\User::where('mobile', $mobile)->where('id', '<>', $user->id)->find()) {
             $this->error(__('Mobile already exists'));
         }
         $result = Sms::check($mobile, $captcha, 'changemobile');
-        if (!$result)
-        {
+        if (!$result) {
             $this->error(__('Captcha is incorrect'));
         }
         $verification = $user->verification;
@@ -253,7 +227,7 @@ class User extends Api
 
     /**
      * 第三方登录
-     * 
+     *
      * @param string $platform 平台名称
      * @param string $code Code码
      */
@@ -263,18 +237,15 @@ class User extends Api
         $platform = $this->request->request("platform");
         $code = $this->request->request("code");
         $config = get_addon_config('third');
-        if (!$config || !isset($config[$platform]))
-        {
+        if (!$config || !isset($config[$platform])) {
             $this->error(__('Invalid parameters'));
         }
         $app = new \addons\third\library\Application($config);
         //通过code换access_token和绑定会员
         $result = $app->{$platform}->getUserInfo(['code' => $code]);
-        if ($result)
-        {
+        if ($result) {
             $loginret = \addons\third\library\Service::connect($platform, $result);
-            if ($loginret)
-            {
+            if ($loginret) {
                 $data = [
                     'userinfo'  => $this->auth->getUserinfo(),
                     'thirdinfo' => $result
@@ -287,7 +258,7 @@ class User extends Api
 
     /**
      * 重置密码
-     * 
+     *
      * @param string $mobile 手机号
      * @param string $newpassword 新密码
      * @param string $captcha 验证码
@@ -299,42 +270,32 @@ class User extends Api
         $email = $this->request->request("email");
         $newpassword = $this->request->request("newpassword");
         $captcha = $this->request->request("captcha");
-        if (!$newpassword || !$captcha)
-        {
+        if (!$newpassword || !$captcha) {
             $this->error(__('Invalid parameters'));
         }
-        if ($type == 'mobile')
-        {
-            if (!Validate::regex($mobile, "^1\d{10}$"))
-            {
+        if ($type == 'mobile') {
+            if (!Validate::regex($mobile, "^1\d{10}$")) {
                 $this->error(__('Mobile is incorrect'));
             }
             $user = \app\common\model\User::getByMobile($mobile);
-            if (!$user)
-            {
+            if (!$user) {
                 $this->error(__('User not found'));
             }
             $ret = Sms::check($mobile, $captcha, 'resetpwd');
-            if (!$ret)
-            {
+            if (!$ret) {
                 $this->error(__('Captcha is incorrect'));
             }
             Sms::flush($mobile, 'resetpwd');
-        }
-        else
-        {
-            if (!Validate::is($email, "email"))
-            {
+        } else {
+            if (!Validate::is($email, "email")) {
                 $this->error(__('Email is incorrect'));
             }
             $user = \app\common\model\User::getByEmail($email);
-            if (!$user)
-            {
+            if (!$user) {
                 $this->error(__('User not found'));
             }
             $ret = Ems::check($email, $captcha, 'resetpwd');
-            if (!$ret)
-            {
+            if (!$ret) {
                 $this->error(__('Captcha is incorrect'));
             }
             Ems::flush($email, 'resetpwd');
@@ -342,12 +303,9 @@ class User extends Api
         //模拟一次登录
         $this->auth->direct($user->id);
         $ret = $this->auth->changepwd($newpassword, '', true);
-        if ($ret)
-        {
+        if ($ret) {
             $this->success(__('Reset password successful'));
-        }
-        else
-        {
+        } else {
             $this->error($this->auth->getError());
         }
     }

+ 1 - 1
application/common/controller/Frontend.php

@@ -64,7 +64,7 @@ class Frontend extends Controller
             $this->auth->init($token);
             //检测是否登录
             if (!$this->auth->isLogin()) {
-                $this->error(__('Please login first'), 'user/login');
+                $this->error(__('Please login first'), 'index/user/login');
             }
             // 判断是否需要验证权限
             if (!$this->auth->match($this->noNeedRight)) {

+ 7 - 1
public/api.html

@@ -92,6 +92,9 @@
                     padding-left:0px;
                 }
             }
+            .label-primary {
+                background-color: #248aff;
+            }
 
         </style>
     </head>
@@ -3727,7 +3730,7 @@
 
             <div class="row mt0 footer">
                 <div class="col-md-6" align="left">
-                    Generated on 2018-10-19 17:00:36                </div>
+                    Generated on 2019-02-26 17:13:43                </div>
                 <div class="col-md-6" align="right">
                     <a href="https://www.fastadmin.net" target="_blank">FastAdmin</a>
                 </div>
@@ -3929,6 +3932,9 @@
                         contentType: false,
                         processData: false,
                         headers: headers,
+                        xhrFields: {
+                            withCredentials: true
+                        },
                         success: function (data, textStatus, xhr) {
                             if (typeof data === 'object') {
                                 var str = JSON.stringify(data, null, 2);

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


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

@@ -92,7 +92,7 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
                                 'click .btn-chooseone': function (e, value, row, index) {
                                     var multiple = Backend.api.query('multiple');
                                     multiple = multiple == 'true' ? true : false;
-                                    Fast.api.close({url: row.url, multiple: false});
+                                    Fast.api.close({url: row.url, multiple: multiple});
                                 },
                             }, formatter: function () {
                                 return '<a href="javascript:;" class="btn btn-danger btn-chooseone btn-xs"><i class="fa fa-check"></i> ' + __('Choose') + '</a>';
@@ -110,7 +110,7 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
                 });
                 var multiple = Backend.api.query('multiple');
                 multiple = multiple == 'true' ? true : false;
-                Fast.api.close({url: urlArr.join(","), multiple: true});
+                Fast.api.close({url: urlArr.join(","), multiple: multiple});
             });
 
             // 为表格绑定事件

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

@@ -350,7 +350,7 @@
         var searchQuery = getSearchQuery(this);
         this.trigger('common-search', this, searchQuery);
         this.options.pageNumber = 1;
-        this.options.pageSize = $.fn.bootstrapTable.defaults.pageSize;
+        //this.options.pageSize = $.fn.bootstrapTable.defaults.pageSize;
         this.refresh({});
     };
 

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

@@ -114,7 +114,7 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
             },
             //打开一个弹出窗口
             open: function (url, title, options) {
-                title = title ? title : "";
+                title = options && options.title ? options.title : (title ? title : "");
                 url = Fast.api.fixurl(url);
                 url = url + (url.indexOf("?") > -1 ? "&" : "?") + "dialog=1";
                 var area = [$(window).width() > 800 ? '800px' : '95%', $(window).height() > 600 ? '600px' : '95%'];

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


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


+ 41 - 9
public/assets/js/require-table.js

@@ -16,6 +16,10 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
             showExport: true,
             exportDataType: "all",
             exportTypes: ['json', 'xml', 'csv', 'txt', 'doc', 'excel'],
+            exportOptions: {
+                fileName: 'export_' + Moment().format("YYYY-MM-DD"),
+                ignoreColumn: [0, 'operate'] //默认不导出第一列(checkbox)与操作(operate)列
+            },
             pageSize: 10,
             pageList: [10, 25, 50, 'All'],
             pagination: true,
@@ -62,6 +66,10 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
             multibtn: '.btn-multi',
             disabledbtn: '.btn-disabled',
             editonebtn: '.btn-editone',
+            restoreonebtn: '.btn-restoreone',
+            destroyonebtn: '.btn-destroyone',
+            restoreallbtn: '.btn-restoreall',
+            destroyallbtn: '.btn-destroyall',
             dragsortfield: 'weigh',
         },
         api: {
@@ -203,6 +211,29 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                         Fast.api.open(url, __('Edit'), $(that).data() || {});
                     });
                 });
+                //清空回收站
+                $(document).on('click', Table.config.destroyallbtn, function () {
+                    var that = this;
+                    Layer.confirm(__('Are you sure you want to truncate?'), function () {
+                        var url = $(that).data("url") ? $(that).data("url") : $(that).attr("href");
+                        Fast.api.ajax(url, function () {
+                            Layer.closeAll();
+                            table.bootstrapTable('refresh');
+                        }, function () {
+                            Layer.closeAll();
+                        });
+                    });
+                    return false;
+                });
+                //还原或删除
+                $(document).on('click', Table.config.restoreallbtn + ',' + Table.config.restoreonebtn + ',' + Table.config.destroyonebtn, function () {
+                    var that = this;
+                    var url = $(that).data("url") ? $(that).data("url") : $(that).attr("href");
+                    Fast.api.ajax(url, function () {
+                        table.bootstrapTable('refresh');
+                    });
+                    return false;
+                });
                 // 批量操作按钮事件
                 $(toolbar).on('click', Table.config.multibtn, function () {
                     var ids = Table.api.selectedids(table);
@@ -369,14 +400,14 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                 },//单元格图片预览
                 image: {
                     'click .img-center': function (e, value, row, index) {
-                        data = [];
-                        value= value.split(",");
+                        var data = [];
+                        value = value.split(",");
                         $.each(value, function (index, value) {
                             data.push({
                                 src: Fast.api.cdnurl(value),
                             });
                         });
-                        layer.photos({
+                        Layer.photos({
                             photos: {
                                 "data": data
                             },
@@ -558,7 +589,8 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                 type = typeof type === 'undefined' ? 'buttons' : type;
                 var options = table ? table.bootstrapTable('getOptions') : {};
                 var html = [];
-                var hidden, visible, disable, url, classname, icon, text, title, refresh, confirm, extend, click, dropdown, link;
+                var hidden, visible, disable, url, classname, icon, text, title, refresh, confirm, extend,
+                    dropdown, link;
                 var fieldIndex = column.fieldIndex;
                 var dropdowns = {};
 
@@ -573,11 +605,11 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                     }
                     var attr = table.data(type + "-" + j.name);
                     if (typeof attr === 'undefined' || attr) {
-                        hidden = typeof j.hidden === 'function' ? j.hidden.call(table, row, j) : (j.hidden ? j.hidden : false);
+                        hidden = typeof j.hidden === 'function' ? j.hidden.call(table, row, j) : (typeof j.hidden !== 'undefined' ? j.hidden : false);
                         if (hidden) {
                             return true;
                         }
-                        visible = typeof j.visible === 'function' ? j.visible.call(table, row, j) : (j.visible ? j.visible : true);
+                        visible = typeof j.visible === 'function' ? j.visible.call(table, row, j) : (typeof j.visible !== 'undefined' ? j.visible : true);
                         if (!visible) {
                             return true;
                         }
@@ -586,12 +618,12 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                         url = typeof url === 'function' ? url.call(table, row, j) : (url ? Fast.api.fixurl(Table.api.replaceurl(url, row, table)) : 'javascript:;');
                         classname = j.classname ? j.classname : 'btn-primary btn-' + name + 'one';
                         icon = j.icon ? j.icon : '';
-                        text = j.text ? j.text : '';
-                        title = j.title ? j.title : text;
+                        text = typeof j.text === 'function' ? j.text.call(table, row, j) : j.text ? j.text : '';
+                        title = typeof j.title === 'function' ? j.title.call(table, row, j) : j.title ? j.title : text;
                         refresh = j.refresh ? 'data-refresh="' + j.refresh + '"' : '';
                         confirm = j.confirm ? 'data-confirm="' + j.confirm + '"' : '';
                         extend = j.extend ? j.extend : '';
-                        disable = typeof j.disable === 'function' ? j.disable.call(table, row, j) : (j.disable ? j.disable : false);
+                        disable = typeof j.disable === 'function' ? j.disable.call(table, row, j) : (typeof j.disable !== 'undefined' ? j.disable : false);
                         if (disable) {
                             classname = classname + ' disabled';
                         }