作者 Karson

新增一键CRUD类名冲突检测

新增一键CRUD自动传递请求参数
新增一键菜单之支持ThinkPHP5标准命名规则
新增Table.api.formatter.toggle自定义URL
新增Table.api.formatter.search自定义搜索字段
修复关联模型不同命名空间之间的BUG
优化时间字段修改器的判断
@@ -10,6 +10,7 @@ use think\console\input\Option; @@ -10,6 +10,7 @@ use think\console\input\Option;
10 use think\console\Output; 10 use think\console\Output;
11 use think\Db; 11 use think\Db;
12 use think\Exception; 12 use think\Exception;
  13 +use think\exception\ErrorException;
13 use think\Lang; 14 use think\Lang;
14 use think\Loader; 15 use think\Loader;
15 16
@@ -17,6 +18,9 @@ class Crud extends Command @@ -17,6 +18,9 @@ class Crud extends Command
17 { 18 {
18 protected $stubList = []; 19 protected $stubList = [];
19 20
  21 + protected $internalKeywords = [
  22 + 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor'
  23 + ];
20 /** 24 /**
21 * Selectpage搜索字段关联 25 * Selectpage搜索字段关联
22 */ 26 */
@@ -181,6 +185,7 @@ class Crud extends Command @@ -181,6 +185,7 @@ class Crud extends Command
181 $controller = $input->getOption('controller'); 185 $controller = $input->getOption('controller');
182 //自定义模型 186 //自定义模型
183 $model = $input->getOption('model'); 187 $model = $input->getOption('model');
  188 + $model = $model ? $model : $controller;
184 //验证器类 189 //验证器类
185 $validate = $model; 190 $validate = $model;
186 //自定义显示字段 191 //自定义显示字段
@@ -324,6 +329,8 @@ class Crud extends Command @@ -324,6 +329,8 @@ class Crud extends Command
324 $relations[] = [ 329 $relations[] = [
325 //关联表基础名 330 //关联表基础名
326 'relationName' => $relationName, 331 'relationName' => $relationName,
  332 + //关联表类命名空间
  333 + 'relationNamespace' => $relationNamespace,
327 //关联模型名 334 //关联模型名
328 'relationModel' => $relationModel, 335 'relationModel' => $relationModel,
329 //关联文件 336 //关联文件
@@ -369,6 +376,8 @@ class Crud extends Command @@ -369,6 +376,8 @@ class Crud extends Command
369 376
370 //视图文件 377 //视图文件
371 $viewArr = $controllerArr; 378 $viewArr = $controllerArr;
  379 + $lastValue = array_pop($viewArr);
  380 + $viewArr[] = Loader::parseName($lastValue, 0);
372 array_unshift($viewArr, 'view'); 381 array_unshift($viewArr, 'view');
373 $viewDir = $adminPath . strtolower(implode(DS, $viewArr)) . DS; 382 $viewDir = $adminPath . strtolower(implode(DS, $viewArr)) . DS;
374 383
@@ -383,7 +392,7 @@ class Crud extends Command @@ -383,7 +392,7 @@ class Crud extends Command
383 //是否为删除模式 392 //是否为删除模式
384 $delete = $input->getOption('delete'); 393 $delete = $input->getOption('delete');
385 if ($delete) { 394 if ($delete) {
386 - $readyFiles = [$controllerFile, $modelFile, $validateFile, $addFile, $editFile, $indexFile, $langFile, $javascriptFile]; 395 + $readyFiles = [$controllerFile, $modelFile, $validateFile, $addFile, $editFile, $indexFile, $recyclebinFile, $langFile, $javascriptFile];
387 foreach ($readyFiles as $k => $v) { 396 foreach ($readyFiles as $k => $v) {
388 $output->warning($v); 397 $output->warning($v);
389 } 398 }
@@ -409,6 +418,7 @@ class Crud extends Command @@ -409,6 +418,7 @@ class Crud extends Command
409 case $addFile: 418 case $addFile:
410 case $editFile: 419 case $editFile:
411 case $indexFile: 420 case $indexFile:
  421 + case $recyclebinFile:
412 $this->removeEmptyBaseDir($v, $viewArr); 422 $this->removeEmptyBaseDir($v, $viewArr);
413 break; 423 break;
414 default: 424 default:
@@ -522,6 +532,7 @@ class Crud extends Command @@ -522,6 +532,7 @@ class Crud extends Command
522 } 532 }
523 $relation['relationForeignKey'] = $relationForeignKey; 533 $relation['relationForeignKey'] = $relationForeignKey;
524 $relation['relationPrimaryKey'] = $relationPrimaryKey; 534 $relation['relationPrimaryKey'] = $relationPrimaryKey;
  535 + $relation['relationClassName'] = $modelNamespace != $relation['relationNamespace'] ? $relation['relationNamespace'] . '\\' . $relation['relationName'] : $relation['relationName'];
525 } 536 }
526 unset($relation); 537 unset($relation);
527 538
@@ -629,9 +640,8 @@ class Crud extends Command @@ -629,9 +640,8 @@ class Crud extends Command
629 $defaultDateTime = "{:date('{$phpFormat}')}"; 640 $defaultDateTime = "{:date('{$phpFormat}')}";
630 $attrArr['data-date-format'] = $format; 641 $attrArr['data-date-format'] = $format;
631 $attrArr['data-use-current'] = "true"; 642 $attrArr['data-use-current'] = "true";
632 - $fieldFunc = $fieldFunc ? "|{$fieldFunc}" : "";  
633 $formAddElement = Form::text($fieldName, $defaultDateTime, $attrArr); 643 $formAddElement = Form::text($fieldName, $defaultDateTime, $attrArr);
634 - $formEditElement = Form::text($fieldName, "{\$row.{$field}{$fieldFunc}}", $attrArr); 644 + $formEditElement = Form::text($fieldName, ($fieldFunc ? "{:\$row.{$field}?{$fieldFunc}(\$row.{$field}):''}" : "{\$row.{$field}{$fieldFunc}}"), $attrArr);
635 } elseif ($inputType == 'checkbox' || $inputType == 'radio') { 645 } elseif ($inputType == 'checkbox' || $inputType == 'radio') {
636 unset($attrArr['data-rule']); 646 unset($attrArr['data-rule']);
637 $fieldName = $inputType == 'checkbox' ? $fieldName .= "[]" : $fieldName; 647 $fieldName = $inputType == 'checkbox' ? $fieldName .= "[]" : $fieldName;
@@ -928,7 +938,7 @@ class Crud extends Command @@ -928,7 +938,7 @@ class Crud extends Command
928 if ($langList) { 938 if ($langList) {
929 $this->writeToFile('lang', $data, $langFile); 939 $this->writeToFile('lang', $data, $langFile);
930 } 940 }
931 - } catch (think\exception\ErrorException $e) { 941 + } catch (ErrorException $e) {
932 throw new Exception("Code: " . $e->getCode() . "\nLine: " . $e->getLine() . "\nMessage: " . $e->getMessage() . "\nFile: " . $e->getFile()); 942 throw new Exception("Code: " . $e->getCode() . "\nLine: " . $e->getLine() . "\nMessage: " . $e->getMessage() . "\nFile: " . $e->getFile());
933 } 943 }
934 944
@@ -956,7 +966,7 @@ class Crud extends Command @@ -956,7 +966,7 @@ class Crud extends Command
956 public function {$methodName}() 966 public function {$methodName}()
957 { 967 {
958 return [{$itemString}]; 968 return [{$itemString}];
959 - } 969 + }
960 EOD; 970 EOD;
961 $controllerAssignList[] = <<<EOD 971 $controllerAssignList[] = <<<EOD
962 \$this->view->assign("{$fieldList}", \$this->model->{$methodName}()); 972 \$this->view->assign("{$fieldList}", \$this->model->{$methodName}());
@@ -980,7 +990,7 @@ EOD; @@ -980,7 +990,7 @@ EOD;
980 $attrField = ucfirst($this->getCamelizeName($field)); 990 $attrField = ucfirst($this->getCamelizeName($field));
981 if ($inputType == 'datetime') { 991 if ($inputType == 'datetime') {
982 $return = <<<EOD 992 $return = <<<EOD
983 -return \$value && !is_numeric(\$value) ? strtotime(\$value) : \$value; 993 +return \$value === '' ? null : (\$value && !is_numeric(\$value) ? strtotime(\$value) : \$value);
984 EOD; 994 EOD;
985 } elseif (in_array($inputType, ['checkbox', 'select'])) { 995 } elseif (in_array($inputType, ['checkbox', 'select'])) {
986 $return = <<<EOD 996 $return = <<<EOD
@@ -1069,9 +1079,9 @@ EOD; @@ -1069,9 +1079,9 @@ EOD;
1069 /** 1079 /**
1070 * 获取已解析相关信息 1080 * 获取已解析相关信息
1071 * @param string $module 模块名称 1081 * @param string $module 模块名称
1072 - * @param string $name 自定义名称  
1073 - * @param string $table 数据表名  
1074 - * @param string $type 解析类型,本例中为controller、model、validate 1082 + * @param string $name 自定义名称
  1083 + * @param string $table 数据表名
  1084 + * @param string $type 解析类型,本例中为controller、model、validate
1075 * @return array 1085 * @return array
1076 */ 1086 */
1077 protected function getParseNameData($module, $name, $table, $type) 1087 protected function getParseNameData($module, $name, $table, $type)
@@ -1087,6 +1097,10 @@ EOD; @@ -1087,6 +1097,10 @@ EOD;
1087 $parseArr = $arr; 1097 $parseArr = $arr;
1088 array_push($parseArr, $parseName); 1098 array_push($parseArr, $parseName);
1089 } 1099 }
  1100 + //类名不能为内部关键字
  1101 + if (in_array(strtolower($parseName), $this->internalKeywords)) {
  1102 + throw new Exception('Unable to use internal variable:' . $parseName);
  1103 + }
1090 $appNamespace = Config::get('app_namespace'); 1104 $appNamespace = Config::get('app_namespace');
1091 $parseNamespace = "{$appNamespace}\\{$module}\\{$type}" . ($arr ? "\\" . implode("\\", $arr) : ""); 1105 $parseNamespace = "{$appNamespace}\\{$module}\\{$type}" . ($arr ? "\\" . implode("\\", $arr) : "");
1092 $moduleDir = APP_PATH . $module . DS; 1106 $moduleDir = APP_PATH . $module . DS;
@@ -1097,7 +1111,7 @@ EOD; @@ -1097,7 +1111,7 @@ EOD;
1097 /** 1111 /**
1098 * 写入到文件 1112 * 写入到文件
1099 * @param string $name 1113 * @param string $name
1100 - * @param array $data 1114 + * @param array $data
1101 * @param string $pathname 1115 * @param string $pathname
1102 * @return mixed 1116 * @return mixed
1103 */ 1117 */
@@ -1118,7 +1132,7 @@ EOD; @@ -1118,7 +1132,7 @@ EOD;
1118 /** 1132 /**
1119 * 获取替换后的数据 1133 * 获取替换后的数据
1120 * @param string $name 1134 * @param string $name
1121 - * @param array $data 1135 + * @param array $data
1122 * @return string 1136 * @return string
1123 */ 1137 */
1124 protected function getReplacedStub($name, $data) 1138 protected function getReplacedStub($name, $data)
@@ -1183,7 +1197,7 @@ EOD; @@ -1183,7 +1197,7 @@ EOD;
1183 1197
1184 /** 1198 /**
1185 * 读取数据和语言数组列表 1199 * 读取数据和语言数组列表
1186 - * @param array $arr 1200 + * @param array $arr
1187 * @param boolean $withTpl 1201 * @param boolean $withTpl
1188 * @return array 1202 * @return array
1189 */ 1203 */
@@ -1303,8 +1317,8 @@ EOD; @@ -1303,8 +1317,8 @@ EOD;
1303 1317
1304 /** 1318 /**
1305 * 判断是否符合指定后缀 1319 * 判断是否符合指定后缀
1306 - * @param string $field 字段名称  
1307 - * @param mixed $suffixArr 后缀 1320 + * @param string $field 字段名称
  1321 + * @param mixed $suffixArr 后缀
1308 * @return boolean 1322 * @return boolean
1309 */ 1323 */
1310 protected function isMatchSuffix($field, $suffixArr) 1324 protected function isMatchSuffix($field, $suffixArr)
@@ -1371,7 +1385,7 @@ EOD; @@ -1371,7 +1385,7 @@ EOD;
1371 * @param string $field 1385 * @param string $field
1372 * @param string $datatype 1386 * @param string $datatype
1373 * @param string $extend 1387 * @param string $extend
1374 - * @param array $itemArr 1388 + * @param array $itemArr
1375 * @return string 1389 * @return string
1376 */ 1390 */
1377 protected function getJsColumn($field, $datatype = '', $extend = '', $itemArr = []) 1391 protected function getJsColumn($field, $datatype = '', $extend = '', $itemArr = [])
@@ -5,7 +5,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin @@ -5,7 +5,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
5 // 初始化表格参数配置 5 // 初始化表格参数配置
6 Table.api.init({ 6 Table.api.init({
7 extend: { 7 extend: {
8 - index_url: '{%controllerUrl%}/index', 8 + index_url: '{%controllerUrl%}/index' + location.search,
9 add_url: '{%controllerUrl%}/add', 9 add_url: '{%controllerUrl%}/add',
10 edit_url: '{%controllerUrl%}/edit', 10 edit_url: '{%controllerUrl%}/edit',
11 del_url: '{%controllerUrl%}/del', 11 del_url: '{%controllerUrl%}/del',
1 1
2 public function {%relationMethod%}() 2 public function {%relationMethod%}()
3 { 3 {
4 - return $this->{%relationMode%}('{%relationName%}', '{%relationForeignKey%}', '{%relationPrimaryKey%}', [], 'LEFT')->setEagerlyType(0); 4 + return $this->{%relationMode%}('{%relationClassName%}', '{%relationForeignKey%}', '{%relationPrimaryKey%}', [], 'LEFT')->setEagerlyType(0);
5 } 5 }
1 1
2 public function {%methodName%}($value, $data) 2 public function {%methodName%}($value, $data)
3 - { 3 + {
4 $value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : ''); 4 $value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
5 $list = $this->{%listMethodName%}(); 5 $list = $this->{%listMethodName%}();
6 return isset($list[$value]) ? $list[$value] : ''; 6 return isset($list[$value]) ? $list[$value] : '';
1 1
2 public function {%methodName%}($value, $data) 2 public function {%methodName%}($value, $data)
3 - { 3 + {
4 $value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : ''); 4 $value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
5 $list = $this->{%listMethodName%}(); 5 $list = $this->{%listMethodName%}();
6 return isset($list[$value]) ? $list[$value] : ''; 6 return isset($list[$value]) ? $list[$value] : '';
@@ -12,6 +12,7 @@ use think\console\Input; @@ -12,6 +12,7 @@ use think\console\Input;
12 use think\console\input\Option; 12 use think\console\input\Option;
13 use think\console\Output; 13 use think\console\Output;
14 use think\Exception; 14 use think\Exception;
  15 +use think\Loader;
15 16
16 class Menu extends Command 17 class Menu extends Command
17 { 18 {
@@ -52,6 +53,18 @@ class Menu extends Command @@ -52,6 +53,18 @@ class Menu extends Command
52 $ids = []; 53 $ids = [];
53 $list = $this->model->where(function ($query) use ($controller, $equal) { 54 $list = $this->model->where(function ($query) use ($controller, $equal) {
54 foreach ($controller as $index => $item) { 55 foreach ($controller as $index => $item) {
  56 + if (stripos($item, '_') !== false) {
  57 + $item = Loader::parseName($item, 1);
  58 + }
  59 + if (stripos($item, '/') !== false) {
  60 + $controllerArr = explode('/', $item);
  61 + end($controllerArr);
  62 + $key = key($controllerArr);
  63 + $controllerArr[$key] = Loader::parseName($controllerArr[$key]);
  64 + } else {
  65 + $controllerArr = [Loader::parseName($item)];
  66 + }
  67 + $item = str_replace('_', '\_', implode('/', $controllerArr));
55 if ($equal) { 68 if ($equal) {
56 $query->whereOr('name', 'eq', $item); 69 $query->whereOr('name', 'eq', $item);
57 } else { 70 } else {
@@ -82,10 +95,17 @@ class Menu extends Command @@ -82,10 +95,17 @@ class Menu extends Command
82 95
83 if (!in_array('all-controller', $controller)) { 96 if (!in_array('all-controller', $controller)) {
84 foreach ($controller as $index => $item) { 97 foreach ($controller as $index => $item) {
85 - $controllerArr = explode('/', $item);  
86 - end($controllerArr);  
87 - $key = key($controllerArr);  
88 - $controllerArr[$key] = ucfirst($controllerArr[$key]); 98 + if (stripos($item, '_') !== false) {
  99 + $item = Loader::parseName($item, 1);
  100 + }
  101 + if (stripos($item, '/') !== false) {
  102 + $controllerArr = explode('/', $item);
  103 + end($controllerArr);
  104 + $key = key($controllerArr);
  105 + $controllerArr[$key] = ucfirst($controllerArr[$key]);
  106 + } else {
  107 + $controllerArr = [ucfirst($item)];
  108 + }
89 $adminPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php'; 109 $adminPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php';
90 if (!is_file($adminPath)) { 110 if (!is_file($adminPath)) {
91 $output->error("controller not found"); 111 $output->error("controller not found");
@@ -159,10 +179,15 @@ class Menu extends Command @@ -159,10 +179,15 @@ class Menu extends Command
159 protected function importRule($controller) 179 protected function importRule($controller)
160 { 180 {
161 $controller = str_replace('\\', '/', $controller); 181 $controller = str_replace('\\', '/', $controller);
162 - $controllerArr = explode('/', $controller);  
163 - end($controllerArr);  
164 - $key = key($controllerArr);  
165 - $controllerArr[$key] = ucfirst($controllerArr[$key]); 182 + if (stripos($controller, '/') !== false) {
  183 + $controllerArr = explode('/', $controller);
  184 + end($controllerArr);
  185 + $key = key($controllerArr);
  186 + $controllerArr[$key] = ucfirst($controllerArr[$key]);
  187 + } else {
  188 + $key = 0;
  189 + $controllerArr = [ucfirst($controller)];
  190 + }
166 $classSuffix = Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; 191 $classSuffix = Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
167 $className = "\\app\\admin\\controller\\" . implode("\\", $controllerArr) . $classSuffix; 192 $className = "\\app\\admin\\controller\\" . implode("\\", $controllerArr) . $classSuffix;
168 193
@@ -479,14 +479,20 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table @@ -479,14 +479,20 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
479 var color = typeof this.color !== 'undefined' ? this.color : 'success'; 479 var color = typeof this.color !== 'undefined' ? this.color : 'success';
480 var yes = typeof this.yes !== 'undefined' ? this.yes : 1; 480 var yes = typeof this.yes !== 'undefined' ? this.yes : 1;
481 var no = typeof this.no !== 'undefined' ? this.no : 0; 481 var no = typeof this.no !== 'undefined' ? this.no : 0;
  482 + var url = typeof this.url !== 'undefined' ? this.url : '';
482 return "<a href='javascript:;' data-toggle='tooltip' title='" + __('Click to toggle') + "' class='btn-change' data-id='" 483 return "<a href='javascript:;' data-toggle='tooltip' title='" + __('Click to toggle') + "' class='btn-change' data-id='"
483 - + row.id + "' data-params='" + this.field + "=" + (value == yes ? no : yes) + "'><i class='fa fa-toggle-on " + (value == yes ? 'text-' + color : 'fa-flip-horizontal text-gray') + " fa-2x'></i></a>"; 484 + + row.id + "' " + (url ? "data-url='" + url + "'" : "") + " data-params='" + this.field + "=" + (value == yes ? no : yes) + "'><i class='fa fa-toggle-on " + (value == yes ? 'text-' + color : 'fa-flip-horizontal text-gray') + " fa-2x'></i></a>";
484 }, 485 },
485 url: function (value, row, index) { 486 url: function (value, row, index) {
486 return '<div class="input-group input-group-sm" style="width:250px;margin:0 auto;"><input type="text" class="form-control input-sm" value="' + value + '"><span class="input-group-btn input-group-sm"><a href="' + value + '" target="_blank" class="btn btn-default btn-sm"><i class="fa fa-link"></i></a></span></div>'; 487 return '<div class="input-group input-group-sm" style="width:250px;margin:0 auto;"><input type="text" class="form-control input-sm" value="' + value + '"><span class="input-group-btn input-group-sm"><a href="' + value + '" target="_blank" class="btn btn-default btn-sm"><i class="fa fa-link"></i></a></span></div>';
487 }, 488 },
488 search: function (value, row, index) { 489 search: function (value, row, index) {
489 - return '<a href="javascript:;" class="searchit" data-toggle="tooltip" title="' + __('Click to search %s', value) + '" data-field="' + this.field + '" data-value="' + value + '">' + value + '</a>'; 490 + var field = this.field;
  491 + if (typeof this.customField !== 'undefined' && typeof row[this.customField] !== 'undefined') {
  492 + value = row[this.customField];
  493 + field = this.customField;
  494 + }
  495 + return '<a href="javascript:;" class="searchit" data-toggle="tooltip" title="' + __('Click to search %s', value) + '" data-field="' + field + '" data-value="' + value + '">' + value + '</a>';
490 }, 496 },
491 addtabs: function (value, row, index) { 497 addtabs: function (value, row, index) {
492 var url = Table.api.replaceurl(this.url, row, this.table); 498 var url = Table.api.replaceurl(this.url, row, this.table);