Crud.php 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079
  1. <?php
  2. namespace app\admin\command;
  3. use fast\Form;
  4. use think\Config;
  5. use think\console\Command;
  6. use think\console\Input;
  7. use think\console\input\Option;
  8. use think\console\Output;
  9. use think\Db;
  10. use think\Exception;
  11. use think\Lang;
  12. class Crud extends Command
  13. {
  14. protected $stubList = [];
  15. /**
  16. * Selectpage搜索字段关联
  17. */
  18. protected $fieldSelectpageMap = [
  19. 'nickname' => ['user_id', 'user_ids', 'admin_id', 'admin_ids']
  20. ];
  21. /**
  22. * Enum类型识别为单选框的结尾字符,默认会识别为单选下拉列表
  23. */
  24. protected $enumRadioSuffix = 'data';
  25. /**
  26. * Set类型识别为复选框的结尾字符,默认会识别为多选下拉列表
  27. */
  28. protected $setCheckboxSuffix = 'data';
  29. /**
  30. * Int类型识别为日期时间的结尾字符,默认会识别为数字文本框
  31. */
  32. protected $intDateSuffix = 'time';
  33. /**
  34. * 开头后缀
  35. */
  36. protected $switchSuffix = 'switch';
  37. /**
  38. * 以指定字符结尾的字段格式化函数
  39. */
  40. protected $fieldFormatterSuffix = [
  41. 'status' => 'status',
  42. 'icon' => 'icon',
  43. 'flag' => 'flag',
  44. 'url' => 'url',
  45. 'url' => 'url',
  46. 'image' => 'image',
  47. 'images' => 'images',
  48. 'time' => ['type' => ['int', 'timestamp'], 'name' => 'datetime']
  49. ];
  50. /**
  51. * 识别为图片字段
  52. */
  53. protected $imageField = ['image', 'images', 'avatar', 'avatars'];
  54. /**
  55. * 识别为文件字段
  56. */
  57. protected $fileField = ['file', 'files'];
  58. /**
  59. * 保留字段
  60. */
  61. protected $reservedField = ['createtime', 'updatetime'];
  62. /**
  63. * 排序字段
  64. */
  65. protected $sortField = 'weigh';
  66. /**
  67. * 编辑器的Class
  68. */
  69. protected $editorClass = 'summernote';
  70. protected function configure()
  71. {
  72. $this
  73. ->setName('crud')
  74. ->addOption('table', 't', Option::VALUE_REQUIRED, 'table name without prefix', null)
  75. ->addOption('controller', 'c', Option::VALUE_OPTIONAL, 'controller name', null)
  76. ->addOption('model', 'm', Option::VALUE_OPTIONAL, 'model name', null)
  77. ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', null)
  78. ->addOption('local', 'l', Option::VALUE_OPTIONAL, 'local model', 1)
  79. ->addOption('relation', 'r', Option::VALUE_OPTIONAL, 'relation table name without prefix', null)
  80. ->addOption('relationmodel', 'e', Option::VALUE_OPTIONAL, 'relation model name', null)
  81. ->addOption('relationforeignkey', 'k', Option::VALUE_OPTIONAL, 'relation foreign key', null)
  82. ->addOption('relationprimarykey', 'p', Option::VALUE_OPTIONAL, 'relation primary key', null)
  83. ->addOption('mode', 'o', Option::VALUE_OPTIONAL, 'relation table mode,hasone or belongsto', 'belongsto')
  84. ->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete all files generated by CRUD', null)
  85. ->setDescription('Build CRUD controller and model from table');
  86. }
  87. protected function execute(Input $input, Output $output)
  88. {
  89. $adminPath = dirname(__DIR__) . DS;
  90. //表名
  91. $table = $input->getOption('table') ?: '';
  92. //自定义控制器
  93. $controller = $input->getOption('controller');
  94. //自定义模型
  95. $model = $input->getOption('model');
  96. //强制覆盖
  97. $force = $input->getOption('force');
  98. //是否为本地model,为0时表示为全局model将会把model放在app/common/model中
  99. $local = $input->getOption('local');
  100. if (!$table)
  101. {
  102. throw new Exception('table name can\'t empty');
  103. }
  104. //关联表
  105. $relation = $input->getOption('relation');
  106. //自定义关联表模型
  107. $relationModel = $input->getOption('relationmodel');
  108. //模式
  109. $mode = $input->getOption('mode');
  110. //外键
  111. $relationForeignKey = $input->getOption('relationforeignkey');
  112. //主键
  113. $relationPrimaryKey = $input->getOption('relationprimarykey');
  114. //如果有启用关联模式
  115. if ($relation && !in_array($mode, ['hasone', 'belongsto']))
  116. {
  117. throw new Exception("relation table only work in hasone or belongsto mode");
  118. }
  119. $dbname = Config::get('database.database');
  120. $prefix = Config::get('database.prefix');
  121. //检查主表
  122. $tableName = $prefix . $table;
  123. $tableInfo = Db::query("SHOW TABLE STATUS LIKE '{$tableName}'", [], TRUE);
  124. if (!$tableInfo)
  125. {
  126. throw new Exception("table not found");
  127. }
  128. $tableInfo = $tableInfo[0];
  129. //检查关联表
  130. if ($relation)
  131. {
  132. $relationTableName = $prefix . $relation;
  133. $relationTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$relationTableName}'", [], TRUE);
  134. if (!$relationTableInfo)
  135. {
  136. throw new Exception("relation table not found");
  137. }
  138. }
  139. //根据表名匹配对应的Fontawesome图标
  140. $iconPath = ROOT_PATH . str_replace('/', DS, '/public/assets/libs/font-awesome/less/variables.less');
  141. $iconName = is_file($iconPath) && stripos(file_get_contents($iconPath), '@fa-var-' . $table . ':') ? $table : 'fa fa-circle-o';
  142. //控制器默认以表名进行处理,以下划线进行分隔,如果需要自定义则需要传入controller,格式为目录层级
  143. $controllerArr = !$controller ? explode('_', strtolower($table)) : explode('/', strtolower($controller));
  144. $controllerUrl = implode('/', $controllerArr);
  145. $controllerName = ucfirst(array_pop($controllerArr));
  146. $controllerDir = implode(DS, $controllerArr);
  147. $controllerFile = ($controllerDir ? $controllerDir . DS : '') . $controllerName . '.php';
  148. $viewDir = $adminPath . 'view' . DS . $controllerUrl . DS;
  149. //最终将生成的文件路径
  150. $controllerFile = $adminPath . 'controller' . DS . $controllerFile;
  151. $javascriptFile = ROOT_PATH . 'public' . DS . 'assets' . DS . 'js' . DS . 'backend' . DS . $controllerUrl . '.js';
  152. $addFile = $viewDir . 'add.html';
  153. $editFile = $viewDir . 'edit.html';
  154. $indexFile = $viewDir . 'index.html';
  155. $langFile = $adminPath . 'lang' . DS . Lang::detect() . DS . $controllerUrl . '.php';
  156. //模型默认以表名进行处理,以下划线进行分隔,如果需要自定义则需要传入model,不支持目录层级
  157. $modelName = $this->getModelName($model, $table);
  158. $modelFile = ($local ? $adminPath : APP_PATH . 'common' . DS) . 'model' . DS . $modelName . '.php';
  159. $validateFile = $adminPath . 'validate' . DS . $modelName . '.php';
  160. //关联模型默认以表名进行处理,以下划线进行分隔,如果需要自定义则需要传入relationmodel,不支持目录层级
  161. $relationModelName = $this->getModelName($relationModel, $relation);
  162. $relationModelFile = ($local ? $adminPath : APP_PATH . 'common' . DS) . 'model' . DS . $relationModelName . '.php';
  163. //是否为删除模式
  164. $delete = $input->getOption('delete');
  165. if ($delete)
  166. {
  167. $readyFiles = [$controllerFile, $modelFile, $validateFile, $addFile, $editFile, $indexFile, $langFile, $javascriptFile];
  168. foreach ($readyFiles as $k => $v)
  169. {
  170. $output->warning($v);
  171. }
  172. $output->info("Are you sure you want to delete all those files? Type 'yes' to continue: ");
  173. $line = fgets(STDIN);
  174. if (trim($line) != 'yes')
  175. {
  176. throw new Exception("Operation is aborted!");
  177. }
  178. foreach ($readyFiles as $k => $v)
  179. {
  180. unlink($v);
  181. }
  182. $output->info("Delete Successed");
  183. return;
  184. }
  185. //非覆盖模式时如果存在控制器文件则报错
  186. if (is_file($controllerFile) && !$force)
  187. {
  188. throw new Exception("controller already exists!\nIf you need to rebuild again, use the parameter --force=true ");
  189. }
  190. //非覆盖模式时如果存在模型文件则报错
  191. if (is_file($modelFile) && !$force)
  192. {
  193. throw new Exception("model already exists!\nIf you need to rebuild again, use the parameter --force=true ");
  194. }
  195. //非覆盖模式时如果存在验证文件则报错
  196. if (is_file($validateFile) && !$force)
  197. {
  198. throw new Exception("validate already exists!\nIf you need to rebuild again, use the parameter --force=true ");
  199. }
  200. require $adminPath . 'common.php';
  201. //从数据库中获取表字段信息
  202. $sql = "SELECT * FROM `information_schema`.`columns` "
  203. . "WHERE TABLE_SCHEMA = ? AND table_name = ? "
  204. . "ORDER BY ORDINAL_POSITION";
  205. $columnList = Db::query($sql, [$dbname, $tableName]);
  206. $relationColumnList = [];
  207. if ($relation)
  208. {
  209. $relationColumnList = Db::query($sql, [$dbname, $relationTableName]);
  210. }
  211. $fieldArr = [];
  212. foreach ($columnList as $k => $v)
  213. {
  214. $fieldArr[] = $v['COLUMN_NAME'];
  215. }
  216. $relationFieldArr = [];
  217. foreach ($relationColumnList as $k => $v)
  218. {
  219. $relationFieldArr[] = $v['COLUMN_NAME'];
  220. }
  221. $addList = [];
  222. $editList = [];
  223. $javascriptList = [];
  224. $langList = [];
  225. $field = 'id';
  226. $order = 'id';
  227. $priDefined = FALSE;
  228. $priKey = '';
  229. $relationPriKey = '';
  230. foreach ($columnList as $k => $v)
  231. {
  232. if ($v['COLUMN_KEY'] == 'PRI')
  233. {
  234. $priKey = $v['COLUMN_NAME'];
  235. break;
  236. }
  237. }
  238. if (!$priKey)
  239. {
  240. throw new Exception('Primary key not found!');
  241. }
  242. if ($relation)
  243. {
  244. foreach ($relationColumnList as $k => $v)
  245. {
  246. if ($v['COLUMN_KEY'] == 'PRI')
  247. {
  248. $relationPriKey = $v['COLUMN_NAME'];
  249. break;
  250. }
  251. }
  252. if (!$relationPriKey)
  253. {
  254. throw new Exception('Relation Primary key not found!');
  255. }
  256. }
  257. $order = $priKey;
  258. //如果是关联模型
  259. if ($relation)
  260. {
  261. if ($mode == 'hasone')
  262. {
  263. $relationForeignKey = $relationForeignKey ? $relationForeignKey : $table . "_id";
  264. $relationPrimaryKey = $relationPrimaryKey ? $relationPrimaryKey : $priKey;
  265. if (!in_array($relationForeignKey, $relationFieldArr))
  266. {
  267. throw new Exception('relation table must be contain field:' . $relationForeignKey);
  268. }
  269. if (!in_array($relationPrimaryKey, $fieldArr))
  270. {
  271. throw new Exception('table must be contain field:' . $relationPrimaryKey);
  272. }
  273. }
  274. else
  275. {
  276. $relationForeignKey = $relationForeignKey ? $relationForeignKey : $relation . "_id";
  277. $relationPrimaryKey = $relationPrimaryKey ? $relationPrimaryKey : $relationPriKey;
  278. if (!in_array($relationForeignKey, $fieldArr))
  279. {
  280. throw new Exception('table must be contain field:' . $relationForeignKey);
  281. }
  282. if (!in_array($relationPrimaryKey, $relationFieldArr))
  283. {
  284. throw new Exception('relation table must be contain field:' . $relationPrimaryKey);
  285. }
  286. }
  287. }
  288. try
  289. {
  290. Form::setEscapeHtml(false);
  291. $setAttrArr = [];
  292. $getAttrArr = [];
  293. $getEnumArr = [];
  294. $appendAttrList = [];
  295. $controllerAssignList = [];
  296. //循环所有字段,开始构造视图的HTML和JS信息
  297. foreach ($columnList as $k => $v)
  298. {
  299. $field = $v['COLUMN_NAME'];
  300. $itemArr = [];
  301. // 这里构建Enum和Set类型的列表数据
  302. if (in_array($v['DATA_TYPE'], ['enum', 'set']))
  303. {
  304. $itemArr = substr($v['COLUMN_TYPE'], strlen($v['DATA_TYPE']) + 1, -1);
  305. $itemArr = explode(',', str_replace("'", '', $itemArr));
  306. $itemArr = $this->getItemArray($itemArr, $field, $v['COLUMN_COMMENT']);
  307. }
  308. // 语言列表
  309. if ($v['COLUMN_COMMENT'] != '')
  310. {
  311. $langList[] = $this->getLangItem($field, $v['COLUMN_COMMENT']);
  312. }
  313. $inputType = '';
  314. //createtime和updatetime是保留字段不能修改和添加
  315. if ($v['COLUMN_KEY'] != 'PRI' && !in_array($field, $this->reservedField))
  316. {
  317. $inputType = $this->getFieldType($v);
  318. // 如果是number类型时增加一个步长
  319. $step = $inputType == 'number' && $v['NUMERIC_SCALE'] > 0 ? "0." . str_repeat(0, $v['NUMERIC_SCALE'] - 1) . "1" : 0;
  320. $attrArr = ['id' => "c-{$field}"];
  321. $cssClassArr = ['form-control'];
  322. $fieldName = "row[{$field}]";
  323. $defaultValue = $v['COLUMN_DEFAULT'];
  324. $editValue = "{\$row.{$field}}";
  325. // 如果默认值为空,则是一个必选项
  326. if ($v['COLUMN_DEFAULT'] == '')
  327. {
  328. $attrArr['data-rule'] = 'required';
  329. }
  330. if ($inputType == 'select')
  331. {
  332. $cssClassArr[] = 'selectpicker';
  333. $attrArr['class'] = implode(' ', $cssClassArr);
  334. if ($v['DATA_TYPE'] == 'set')
  335. {
  336. $attrArr['multiple'] = '';
  337. $fieldName .= "[]";
  338. }
  339. $attrArr['name'] = $fieldName;
  340. $this->getEnum($getEnumArr, $controllerAssignList, $field, $itemArr, $v['DATA_TYPE'] == 'set' ? 'multiple' : 'select');
  341. $itemArr = $this->getLangArray($itemArr, FALSE);
  342. //添加一个获取器
  343. $this->getAttr($getAttrArr, $field, $v['DATA_TYPE'] == 'set' ? 'multiple' : 'select');
  344. $this->appendAttr($appendAttrList, $field);
  345. $formAddElement = $this->getReplacedStub('html/select', ['field' => $field, 'fieldName' => $fieldName, 'fieldList' => $this->getFieldListName($field), 'attrStr' => Form::attributes($attrArr), 'selectedValue' => $defaultValue]);
  346. $formEditElement = $this->getReplacedStub('html/select', ['field' => $field, 'fieldName' => $fieldName, 'fieldList' => $this->getFieldListName($field), 'attrStr' => Form::attributes($attrArr), 'selectedValue' => "\$row.{$field}"]);
  347. }
  348. else if ($inputType == 'datetime')
  349. {
  350. $cssClassArr[] = 'datetimepicker';
  351. $attrArr['class'] = implode(' ', $cssClassArr);
  352. $format = "YYYY-MM-DD HH:mm:ss";
  353. $phpFormat = "Y-m-d H:i:s";
  354. $fieldFunc = '';
  355. switch ($v['DATA_TYPE'])
  356. {
  357. case 'year';
  358. $format = "YYYY";
  359. $phpFormat = 'Y';
  360. break;
  361. case 'date';
  362. $format = "YYYY-MM-DD";
  363. $phpFormat = 'Y-m-d';
  364. break;
  365. case 'time';
  366. $format = "HH:mm:ss";
  367. $phpFormat = 'H:i:s';
  368. break;
  369. case 'timestamp';
  370. $fieldFunc = 'datetime';
  371. case 'datetime';
  372. $format = "YYYY-MM-DD HH:mm:ss";
  373. $phpFormat = 'Y-m-d H:i:s';
  374. break;
  375. default:
  376. $fieldFunc = 'datetime';
  377. $this->getAttr($getAttrArr, $field, $inputType);
  378. $this->setAttr($setAttrArr, $field, $inputType);
  379. $this->appendAttr($appendAttrList, $field);
  380. break;
  381. }
  382. $defaultDateTime = "{:date('{$phpFormat}')}";
  383. $attrArr['data-date-format'] = $format;
  384. $attrArr['data-use-current'] = "true";
  385. $fieldFunc = $fieldFunc ? "|{$fieldFunc}" : "";
  386. $formAddElement = Form::text($fieldName, $defaultDateTime, $attrArr);
  387. $formEditElement = Form::text($fieldName, "{\$row.{$field}{$fieldFunc}}", $attrArr);
  388. }
  389. else if ($inputType == 'checkbox' || $inputType == 'radio')
  390. {
  391. $fieldName = $inputType == 'checkbox' ? $fieldName .= "[]" : $fieldName;
  392. $attrArr['name'] = "row[{$fieldName}]";
  393. $itemArr = $this->getLangArray($itemArr, FALSE);
  394. $this->getEnum($getEnumArr, $controllerAssignList, $field, $itemArr, $inputType);
  395. //添加一个获取器
  396. $this->getAttr($getAttrArr, $field, $inputType);
  397. $this->appendAttr($appendAttrList, $field);
  398. $defaultValue = $inputType == 'radio' && !$defaultValue ? key($itemArr) : $defaultValue;
  399. $formAddElement = $this->getReplacedStub('html/' . $inputType, ['field' => $field, 'fieldName' => $fieldName, 'fieldList' => $this->getFieldListName($field), 'attrStr' => Form::attributes($attrArr), 'selectedValue' => $defaultValue]);
  400. $formEditElement = $this->getReplacedStub('html/' . $inputType, ['field' => $field, 'fieldName' => $fieldName, 'fieldList' => $this->getFieldListName($field), 'attrStr' => Form::attributes($attrArr), 'selectedValue' => "\$row.{$field}"]);
  401. }
  402. else if ($inputType == 'textarea')
  403. {
  404. $cssClassArr[] = substr($field, -7) == 'content' ? $this->editorClass : '';
  405. $attrArr['class'] = implode(' ', $cssClassArr);
  406. $attrArr['rows'] = 5;
  407. $formAddElement = Form::textarea($fieldName, $defaultValue, $attrArr);
  408. $formEditElement = Form::textarea($fieldName, $editValue, $attrArr);
  409. }
  410. else if ($inputType == 'switch')
  411. {
  412. if ($defaultValue === '1' || $defaultValue === 'Y')
  413. {
  414. $yes = $defaultValue;
  415. $no = $defaultValue === '1' ? '0' : 'N';
  416. }
  417. else
  418. {
  419. $no = $defaultValue;
  420. $yes = $defaultValue === '0' ? '1' : 'Y';
  421. }
  422. $formAddElement = $formEditElement = Form::hidden($fieldName, $no, array_merge(['checked' => ''], $attrArr));
  423. $attrArr['id'] = $fieldName . "-switch";
  424. $formAddElement .= sprintf(Form::label("{$attrArr['id']}", "%s abcdefg"), Form::checkbox($fieldName, $yes, $defaultValue === $yes, $attrArr));
  425. $formEditElement .= sprintf(Form::label("{$attrArr['id']}", "%s abcdefg"), Form::checkbox($fieldName, $yes, 0, $attrArr));
  426. $formEditElement = str_replace('type="checkbox"', 'type="checkbox" {in name="' . "\$row.{$field}" . '" value="' . $yes . '"}checked{/in}', $formEditElement);
  427. }
  428. else
  429. {
  430. $search = $replace = '';
  431. //特殊字段为关联搜索
  432. if (substr($field, -3) == '_id' || substr($field, -4) == '_ids')
  433. {
  434. $inputType = 'text';
  435. $defaultValue = '';
  436. $attrArr['data-rule'] = 'required';
  437. $cssClassArr[] = 'selectpage';
  438. $attrArr['data-db-table'] = substr($field, 0, strripos($field, '_'));
  439. if ($attrArr['data-db-table'] == 'category')
  440. {
  441. $attrArr['data-params'] = '##replacetext##';
  442. $search = '"##replacetext##"';
  443. $replace = '\'{"custom[type]":"' . $table . '"}\'';
  444. }
  445. if (substr($field, -4) == '_ids')
  446. {
  447. $attrArr['data-multiple'] = 'true';
  448. }
  449. foreach ($this->fieldSelectpageMap as $m => $n)
  450. {
  451. if (in_array($field, $n))
  452. {
  453. $attrArr['data-field'] = $m;
  454. break;
  455. }
  456. }
  457. }
  458. //因为有自动完成可输入其它内容
  459. $step = array_intersect($cssClassArr, ['selectpage']) ? 0 : $step;
  460. $attrArr['class'] = implode(' ', $cssClassArr);
  461. $isUpload = false;
  462. foreach (array_merge($this->imageField, $this->fileField) as $m => $n)
  463. {
  464. if (preg_match("/{$n}$/i", $field))
  465. {
  466. $isUpload = true;
  467. break;
  468. }
  469. }
  470. //如果是步长则加上步长
  471. if ($step)
  472. {
  473. $attrArr['step'] = $step;
  474. }
  475. //如果是图片加上个size
  476. if ($isUpload)
  477. {
  478. $attrArr['size'] = 50;
  479. }
  480. $formAddElement = Form::input($inputType, $fieldName, $defaultValue, $attrArr);
  481. $formEditElement = Form::input($inputType, $fieldName, $editValue, $attrArr);
  482. if ($search && $replace)
  483. {
  484. $formAddElement = str_replace($search, $replace, $formAddElement);
  485. $formEditElement = str_replace($search, $replace, $formEditElement);
  486. }
  487. //如果是图片或文件
  488. if ($isUpload)
  489. {
  490. $formAddElement = $this->getImageUpload($field, $formAddElement);
  491. $formEditElement = $this->getImageUpload($field, $formEditElement);
  492. }
  493. }
  494. //构造添加和编辑HTML信息
  495. $addList[] = $this->getFormGroup($field, $formAddElement);
  496. $editList[] = $this->getFormGroup($field, $formEditElement);
  497. }
  498. //过滤text类型字段
  499. if ($v['DATA_TYPE'] != 'text')
  500. {
  501. //主键
  502. if ($v['COLUMN_KEY'] == 'PRI' && !$priDefined)
  503. {
  504. $priDefined = TRUE;
  505. $javascriptList[] = "{field: 'state', checkbox: true}";
  506. }
  507. //构造JS列信息
  508. $javascriptList[] = $this->getJsColumn($field, $v['DATA_TYPE']);
  509. if ($inputType && in_array($inputType, ['select', 'checkbox', 'radio']))
  510. {
  511. $javascriptList[] = $this->getJsColumn($field, $v['DATA_TYPE'], '_text');
  512. }
  513. //排序方式,如果有指定排序字段,否则按主键排序
  514. $order = $field == $this->sortField ? $this->sortField : $order;
  515. }
  516. }
  517. $relationPriKey = 'id';
  518. $relationFieldArr = [];
  519. foreach ($relationColumnList as $k => $v)
  520. {
  521. $relationField = $v['COLUMN_NAME'];
  522. $relationFieldArr[] = $field;
  523. $relationField = strtolower($relationModelName) . "." . $relationField;
  524. // 语言列表
  525. if ($v['COLUMN_COMMENT'] != '')
  526. {
  527. $langList[] = $this->getLangItem($relationField, $v['COLUMN_COMMENT']);
  528. }
  529. //过滤text类型字段
  530. if ($v['DATA_TYPE'] != 'text')
  531. {
  532. //构造JS列信息
  533. $javascriptList[] = $this->getJsColumn($relationField, $v['DATA_TYPE']);
  534. }
  535. }
  536. //JS最后一列加上操作列
  537. $javascriptList[] = str_repeat(" ", 24) . "{field: 'operate', title: __('Operate'), events: Table.api.events.operate, formatter: Table.api.formatter.operate}";
  538. $addList = implode("\n", array_filter($addList));
  539. $editList = implode("\n", array_filter($editList));
  540. $javascriptList = implode(",\n", array_filter($javascriptList));
  541. $langList = implode(",\n", array_filter($langList));
  542. //表注释
  543. $tableComment = $tableInfo['Comment'];
  544. $tableComment = mb_substr($tableComment, -1) == '表' ? mb_substr($tableComment, 0, -1) . '管理' : $tableComment;
  545. $appNamespace = Config::get('app_namespace');
  546. $moduleName = 'admin';
  547. $controllerNamespace = "{$appNamespace}\\{$moduleName}\\controller" . ($controllerDir ? "\\" : "") . str_replace('/', "\\", $controllerDir);
  548. $modelNamespace = "{$appNamespace}\\" . ($local ? $moduleName : "common") . "\\model";
  549. $validateNamespace = "{$appNamespace}\\" . $moduleName . "\\validate";
  550. $validateName = $modelName;
  551. $data = [
  552. 'controllerNamespace' => $controllerNamespace,
  553. 'modelNamespace' => $modelNamespace,
  554. 'validateNamespace' => $validateNamespace,
  555. 'controllerUrl' => $controllerUrl,
  556. 'controllerDir' => $controllerDir,
  557. 'controllerName' => $controllerName,
  558. 'controllerAssignList' => implode("\n", $controllerAssignList),
  559. 'modelName' => $modelName,
  560. 'validateName' => $validateName,
  561. 'tableComment' => $tableComment,
  562. 'iconName' => $iconName,
  563. 'pk' => $priKey,
  564. 'order' => $order,
  565. 'table' => $table,
  566. 'tableName' => $tableName,
  567. 'addList' => $addList,
  568. 'editList' => $editList,
  569. 'javascriptList' => $javascriptList,
  570. 'langList' => $langList,
  571. 'modelAutoWriteTimestamp' => in_array('createtime', $fieldArr) || in_array('updatetime', $fieldArr) ? "'int'" : 'false',
  572. 'createTime' => in_array('createtime', $fieldArr) ? "'createtime'" : 'false',
  573. 'updateTime' => in_array('updatetime', $fieldArr) ? "'updatetime'" : 'false',
  574. 'modelTableName' => $table,
  575. 'relationModelTableName' => $relation,
  576. 'relationModelName' => $relationModelName,
  577. 'relationWith' => '',
  578. 'relationMethod' => '',
  579. 'relationModel' => '',
  580. 'relationForeignKey' => '',
  581. 'relationPrimaryKey' => '',
  582. 'relationSearch' => $relation ? 'true' : 'false',
  583. 'controllerIndex' => '',
  584. 'appendAttrList' => implode(",\n", $appendAttrList),
  585. 'getEnumList' => implode("\n\n", $getEnumArr),
  586. 'getAttrList' => implode("\n\n", $getAttrArr),
  587. 'setAttrList' => implode("\n\n", $setAttrArr),
  588. 'modelMethod' => '',
  589. ];
  590. //如果使用关联模型
  591. if ($relation)
  592. {
  593. //需要构造关联的方法
  594. $data['relationMethod'] = strtolower($relationModelName);
  595. //预载入的方法
  596. $data['relationWith'] = "->with('{$data['relationMethod']}')";
  597. //需要重写index方法
  598. $data['controllerIndex'] = $this->getReplacedStub('controllerindex', $data);
  599. //关联的模式
  600. $data['relationMode'] = $mode == 'hasone' ? 'hasOne' : 'belongsTo';
  601. //关联字段
  602. $data['relationForeignKey'] = $relationForeignKey;
  603. $data['relationPrimaryKey'] = $relationPrimaryKey ? $relationPrimaryKey : $priKey;
  604. //构造关联模型的方法
  605. $data['modelMethod'] = $this->getReplacedStub('modelmethod', $data);
  606. }
  607. // 生成控制器文件
  608. $result = $this->writeToFile('controller', $data, $controllerFile);
  609. // 生成模型文件
  610. $result = $this->writeToFile('model', $data, $modelFile);
  611. if ($relation && !is_file($relationModelFile))
  612. {
  613. // 生成关联模型文件
  614. $result = $this->writeToFile('relationmodel', $data, $relationModelFile);
  615. }
  616. // 生成验证文件
  617. $result = $this->writeToFile('validate', $data, $validateFile);
  618. // 生成视图文件
  619. $result = $this->writeToFile('add', $data, $addFile);
  620. $result = $this->writeToFile('edit', $data, $editFile);
  621. $result = $this->writeToFile('index', $data, $indexFile);
  622. // 生成JS文件
  623. $result = $this->writeToFile('javascript', $data, $javascriptFile);
  624. // 生成语言文件
  625. if ($langList)
  626. {
  627. $result = $this->writeToFile('lang', $data, $langFile);
  628. }
  629. }
  630. catch (\think\exception\ErrorException $e)
  631. {
  632. throw new Exception("Code: " . $e->getCode() . "\nLine: " . $e->getLine() . "\nMessage: " . $e->getMessage() . "\nFile: " . $e->getFile());
  633. }
  634. $output->info("Build Successed");
  635. }
  636. protected function getEnum(&$getEnum, &$controllerAssignList, $field, $itemArr = '', $inputType = '')
  637. {
  638. if (!in_array($inputType, ['datetime', 'select', 'multiple', 'checkbox', 'radio']))
  639. return;
  640. $fieldList = $this->getFieldListName($field);
  641. $methodName = 'get' . ucfirst($fieldList);
  642. unset($v);
  643. foreach ($itemArr as $k => &$v)
  644. {
  645. $v = "__('" . ucfirst($v) . "')";
  646. }
  647. unset($v);
  648. $itemString = $this->getArrayString($itemArr);
  649. $getEnum[] = <<<EOD
  650. public function {$methodName}()
  651. {
  652. return [{$itemString}];
  653. }
  654. EOD;
  655. $controllerAssignList[] = <<<EOD
  656. \$this->view->assign("{$fieldList}", \$this->model->{$methodName}());
  657. EOD;
  658. }
  659. protected function getAttr(&$getAttr, $field, $inputType = '')
  660. {
  661. if (!in_array($inputType, ['datetime', 'select', 'multiple', 'checkbox', 'radio']))
  662. return;
  663. $attrField = ucfirst($this->getCamelizeName($field));
  664. $getAttr[] = $this->getReplacedStub("mixins" . DS . $inputType, ['field' => $field, 'methodName' => "get{$attrField}TextAttr", 'listMethodName' => "get{$attrField}List"]);
  665. }
  666. protected function setAttr(&$setAttr, $field, $inputType = '')
  667. {
  668. if ($inputType != 'datetime')
  669. return;
  670. $attrField = ucfirst($this->getCamelizeName($field));
  671. if ($inputType == 'datetime')
  672. {
  673. $return = <<<EOD
  674. return \$value && !is_numeric(\$value) ? strtotime(\$value) : \$value;
  675. EOD;
  676. }
  677. $setAttr[] = <<<EOD
  678. protected function set{$attrField}Attr(\$value)
  679. {
  680. $return
  681. }
  682. EOD;
  683. }
  684. protected function appendAttr(&$appendAttrList, $field)
  685. {
  686. $appendAttrList[] = <<<EOD
  687. '{$field}_text'
  688. EOD;
  689. }
  690. protected function getModelName($model, $table)
  691. {
  692. if (!$model)
  693. {
  694. $modelarr = explode('_', strtolower($table));
  695. foreach ($modelarr as $k => &$v)
  696. $v = ucfirst($v);
  697. unset($v);
  698. $modelName = implode('', $modelarr);
  699. }
  700. else
  701. {
  702. $modelName = ucfirst($model);
  703. }
  704. return $modelName;
  705. }
  706. /**
  707. * 写入到文件
  708. * @param string $name
  709. * @param array $data
  710. * @param string $pathname
  711. * @return mixed
  712. */
  713. protected function writeToFile($name, $data, $pathname)
  714. {
  715. $content = $this->getReplacedStub($name, $data);
  716. if (!is_dir(dirname($pathname)))
  717. {
  718. mkdir(strtolower(dirname($pathname)), 0755, true);
  719. }
  720. return file_put_contents($pathname, $content);
  721. }
  722. /**
  723. * 获取替换后的数据
  724. * @param string $name
  725. * @param array $data
  726. * @return string
  727. */
  728. protected function getReplacedStub($name, $data)
  729. {
  730. $search = $replace = [];
  731. foreach ($data as $k => $v)
  732. {
  733. $search[] = "{%{$k}%}";
  734. $replace[] = $v;
  735. }
  736. $stubname = $this->getStub($name);
  737. if (isset($this->stubList[$stubname]))
  738. {
  739. $stub = $this->stubList[$stubname];
  740. }
  741. else
  742. {
  743. $this->stubList[$stubname] = $stub = file_get_contents($stubname);
  744. }
  745. $content = str_replace($search, $replace, $stub);
  746. return $content;
  747. }
  748. /**
  749. * 获取基础模板
  750. * @param string $name
  751. * @return string
  752. */
  753. protected function getStub($name)
  754. {
  755. return __DIR__ . DS . 'Crud' . DS . 'stubs' . DS . $name . '.stub';
  756. }
  757. protected function getLangItem($field, $content)
  758. {
  759. if ($content || !Lang::has($field))
  760. {
  761. $itemArr = [];
  762. if (stripos($content, ':') !== false && stripos($content, ',') && stripos($content, '=') !== false)
  763. {
  764. list($fieldLang, $item) = explode(':', $content);
  765. $itemArr = [$field => $fieldLang];
  766. foreach (explode(',', $item) as $k => $v)
  767. {
  768. list($key, $value) = explode('=', $v);
  769. $itemArr[$field . ' ' . $key] = $value;
  770. }
  771. }
  772. else
  773. {
  774. $itemArr = [$field => $content];
  775. }
  776. $resultArr = [];
  777. foreach ($itemArr as $k => $v)
  778. {
  779. $resultArr[] = " '" . ucfirst($k) . "' => '{$v}'";
  780. }
  781. return implode(",\n", $resultArr);
  782. }
  783. else
  784. {
  785. return '';
  786. }
  787. }
  788. /**
  789. * 读取数据和语言数组列表
  790. * @param array $arr
  791. * @return array
  792. */
  793. protected function getLangArray($arr, $withTpl = TRUE)
  794. {
  795. $langArr = [];
  796. foreach ($arr as $k => $v)
  797. {
  798. $langArr[(is_numeric($k) ? $v : $k)] = is_numeric($k) ? ($withTpl ? "{:" : "") . "__('" . ucfirst($v) . "')" . ($withTpl ? "}" : "") : $v;
  799. }
  800. return $langArr;
  801. }
  802. /**
  803. * 将数据转换成带字符串
  804. * @param array $arr
  805. * @return string
  806. */
  807. protected function getArrayString($arr)
  808. {
  809. if (!is_array($arr))
  810. return $arr;
  811. $stringArr = [];
  812. foreach ($arr as $k => $v)
  813. {
  814. $is_var = in_array(substr($v, 0, 1), ['$', '_']);
  815. if (!$is_var)
  816. {
  817. $v = str_replace("'", "\'", $v);
  818. $k = str_replace("'", "\'", $k);
  819. }
  820. $stringArr[] = "'" . $k . "' => " . ($is_var ? $v : "'{$v}'");
  821. }
  822. return implode(",", $stringArr);
  823. }
  824. protected function getItemArray($item, $field, $comment)
  825. {
  826. $itemArr = [];
  827. if (stripos($comment, ':') !== false && stripos($comment, ',') && stripos($comment, '=') !== false)
  828. {
  829. list($fieldLang, $item) = explode(':', $comment);
  830. $itemArr = [];
  831. foreach (explode(',', $item) as $k => $v)
  832. {
  833. list($key, $value) = explode('=', $v);
  834. $itemArr[$key] = $field . ' ' . $key;
  835. }
  836. }
  837. else
  838. {
  839. foreach ($item as $k => $v)
  840. {
  841. $itemArr[$v] = is_numeric($v) ? $field . ' ' . $v : $v;
  842. }
  843. }
  844. return $itemArr;
  845. }
  846. protected function getFieldType(& $v)
  847. {
  848. $inputType = 'text';
  849. switch ($v['DATA_TYPE'])
  850. {
  851. case 'bigint':
  852. case 'int':
  853. case 'mediumint':
  854. case 'smallint':
  855. case 'tinyint':
  856. $inputType = 'number';
  857. break;
  858. case 'enum':
  859. case 'set':
  860. $inputType = 'select';
  861. break;
  862. case 'decimal':
  863. case 'double':
  864. case 'float':
  865. $inputType = 'number';
  866. break;
  867. case 'longtext':
  868. case 'text':
  869. case 'mediumtext':
  870. case 'smalltext':
  871. case 'tinytext':
  872. $inputType = 'textarea';
  873. break;
  874. case 'year';
  875. case 'date';
  876. case 'time';
  877. case 'datetime';
  878. case 'timestamp';
  879. $inputType = 'datetime';
  880. break;
  881. default:
  882. break;
  883. }
  884. $fieldsName = $v['COLUMN_NAME'];
  885. // 指定后缀说明也是个时间字段
  886. if (preg_match("/{$this->intDateSuffix}$/i", $fieldsName))
  887. {
  888. $inputType = 'datetime';
  889. }
  890. // 指定后缀结尾且类型为enum,说明是个单选框
  891. if (preg_match("/{$this->enumRadioSuffix}$/i", $fieldsName) && $v['DATA_TYPE'] == 'enum')
  892. {
  893. $inputType = "radio";
  894. }
  895. // 指定后缀结尾且类型为set,说明是个复选框
  896. if (preg_match("/{$this->setCheckboxSuffix}$/i", $fieldsName) && $v['DATA_TYPE'] == 'set')
  897. {
  898. $inputType = "checkbox";
  899. }
  900. // 指定后缀结尾且类型为char或tinyint且长度为1,说明是个Switch复选框
  901. if (preg_match("/{$this->switchSuffix}$/i", $fieldsName) && ($v['COLUMN_TYPE'] == 'tinyint(1)' || $v['COLUMN_TYPE'] == 'char(1)') && $v['COLUMN_DEFAULT'] !== '' && $v['COLUMN_DEFAULT'] !== null)
  902. {
  903. $inputType = "switch";
  904. }
  905. return $inputType;
  906. }
  907. /**
  908. * 获取表单分组数据
  909. * @param string $field
  910. * @param string $content
  911. * @return string
  912. */
  913. protected function getFormGroup($field, $content)
  914. {
  915. $langField = ucfirst($field);
  916. return<<<EOD
  917. <div class="form-group">
  918. <label for="c-{$field}" class="control-label col-xs-12 col-sm-2">{:__('{$langField}')}:</label>
  919. <div class="col-xs-12 col-sm-8">
  920. {$content}
  921. </div>
  922. </div>
  923. EOD;
  924. }
  925. /**
  926. * 获取图片模板数据
  927. * @param string $field
  928. * @param string $content
  929. * @return array
  930. */
  931. protected function getImageUpload($field, $content)
  932. {
  933. $filter = '';
  934. foreach ($this->imageField as $k => $v)
  935. {
  936. if (preg_match("/{$v}$/i", $field))
  937. {
  938. $filter = ' data-mimetype="image/*"';
  939. break;
  940. }
  941. }
  942. $multiple = substr($field, -1) == 's' ? ' data-multiple="true"' : ' data-multiple="false"';
  943. $preview = $filter ? ' data-preview-id="p-' . $field . '"' : '';
  944. $previewcontainer = $preview ? '<ul class="row list-inline plupload-preview" id="p-' . $field . '"></ul>' : '';
  945. return <<<EOD
  946. <div class="form-inline">
  947. {$content}
  948. <span><button type="button" id="plupload-{$field}" class="btn btn-danger plupload" data-input-id="c-{$field}"{$filter}{$multiple}{$preview}><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
  949. <span><button type="button" id="fachoose-{$field}" class="btn btn-primary fachoose" data-input-id="c-{$field}"{$filter}{$multiple}><i class="fa fa-list"></i> {:__('Choose')}</button></span>
  950. {$previewcontainer}
  951. </div>
  952. EOD;
  953. }
  954. /**
  955. * 获取JS列数据
  956. * @param string $field
  957. * @return string
  958. */
  959. protected function getJsColumn($field, $datatype = '', $extend = '')
  960. {
  961. $lang = ucfirst($field);
  962. $html = str_repeat(" ", 24) . "{field: '{$field}{$extend}', title: __('{$lang}')";
  963. $formatter = '';
  964. foreach ($this->fieldFormatterSuffix as $k => $v)
  965. {
  966. if (preg_match("/{$k}$/i", $field))
  967. {
  968. if (is_array($v))
  969. {
  970. if (in_array($datatype, $v['type']))
  971. {
  972. $formatter = $v['name'];
  973. break;
  974. }
  975. }
  976. else
  977. {
  978. $formatter = $v;
  979. break;
  980. }
  981. }
  982. }
  983. if ($extend)
  984. $html .= ", operate:false";
  985. if ($formatter && !$extend)
  986. $html .= ", formatter: Table.api.formatter." . $formatter . "}";
  987. else
  988. $html .= "}";
  989. return $html;
  990. }
  991. protected function getCamelizeName($uncamelized_words, $separator = '_')
  992. {
  993. $uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words));
  994. return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator);
  995. }
  996. protected function getFieldListName($field)
  997. {
  998. return $this->getCamelizeName($field) . 'List';
  999. }
  1000. }