Crud.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. <?php
  2. namespace app\admin\controller\crud;
  3. use Throwable;
  4. use ba\Exception;
  5. use ba\Filesystem;
  6. use think\facade\Db;
  7. use ba\TableManager;
  8. use app\admin\model\CrudLog;
  9. use app\common\library\Menu;
  10. use app\admin\model\AdminLog;
  11. use app\common\controller\Backend;
  12. use app\admin\library\crud\Helper;
  13. class Crud extends Backend
  14. {
  15. /**
  16. * 模型文件数据
  17. * @var array
  18. */
  19. protected array $modelData = [];
  20. /**
  21. * 控制器文件数据
  22. * @var array
  23. */
  24. protected array $controllerData = [];
  25. /**
  26. * index.vue文件数据
  27. * @var array
  28. */
  29. protected array $indexVueData = [];
  30. /**
  31. * form.vue文件数据
  32. * @var array
  33. */
  34. protected array $formVueData = [];
  35. /**
  36. * 语言翻译前缀
  37. * @var string
  38. */
  39. protected string $webTranslate = '';
  40. /**
  41. * 语言包数据
  42. * @var array
  43. */
  44. protected array $langTsData = [];
  45. /**
  46. * 当designType为以下值时:
  47. * 1. 出入库字符串到数组转换
  48. * 2. 默认值转数组
  49. * @var array
  50. */
  51. protected array $dtStringToArray = ['checkbox', 'selects', 'remoteSelects', 'city', 'images', 'files'];
  52. protected array $noNeedPermission = ['logStart', 'getFileData', 'parseFieldData', 'generateCheck', 'databaseList'];
  53. public function initialize(): void
  54. {
  55. parent::initialize();
  56. }
  57. /**
  58. * 开始生成
  59. * @throws Throwable
  60. */
  61. public function generate(): void
  62. {
  63. $type = $this->request->post('type', '');
  64. $table = $this->request->post('table', []);
  65. $fields = $this->request->post('fields', [], 'clean_xss,htmlspecialchars_decode_improve');
  66. if (!$table || !$fields || !isset($table['name']) || !$table['name']) {
  67. $this->error(__('Parameter error'));
  68. }
  69. try {
  70. // 记录日志
  71. $crudLogId = Helper::recordCrudStatus([
  72. 'table' => $table,
  73. 'fields' => $fields,
  74. 'status' => 'start',
  75. ]);
  76. if ($type == 'create' || $table['rebuild'] == 'Yes') {
  77. // 数据表存在则删除
  78. Helper::delTable($table['name']);
  79. }
  80. // 处理表设计
  81. [$tablePk] = Helper::handleTableDesign($table, $fields);
  82. // 表名称
  83. $tableName = TableManager::tableName($table['name'], false);
  84. // 表注释
  85. $tableComment = mb_substr($table['comment'], -1) == '表' ? mb_substr($table['comment'], 0, -1) . '管理' : $table['comment'];
  86. // 生成文件信息解析
  87. $modelFile = Helper::parseNameData($table['isCommonModel'] ? 'common' : 'admin', $tableName, 'model', $table['modelFile']);
  88. $validateFile = Helper::parseNameData('admin', $tableName, 'validate', $table['validateFile']);
  89. $controllerFile = Helper::parseNameData('admin', $tableName, 'controller', $table['controllerFile']);
  90. $webViewsDir = Helper::parseWebDirNameData($tableName, 'views', $table['webViewsDir']);
  91. $webLangDir = Helper::parseWebDirNameData($tableName, 'lang', $table['webViewsDir']);
  92. // 语言翻译前缀
  93. $this->webTranslate = implode('.', $webLangDir['lang']) . '.';
  94. // 快速搜索字段
  95. if (!in_array($tablePk, $table['quickSearchField'])) {
  96. $table['quickSearchField'][] = $tablePk;
  97. }
  98. $quickSearchFieldZhCnTitle = [];
  99. // 模型数据
  100. $this->modelData['append'] = [];
  101. $this->modelData['methods'] = [];
  102. $this->modelData['fieldType'] = [];
  103. $this->modelData['createTime'] = '';
  104. $this->modelData['updateTime'] = '';
  105. $this->modelData['beforeInsertMixins'] = [];
  106. $this->modelData['beforeInsert'] = '';
  107. $this->modelData['afterInsert'] = '';
  108. $this->modelData['name'] = $tableName;
  109. $this->modelData['className'] = $modelFile['lastName'];
  110. $this->modelData['namespace'] = $modelFile['namespace'];
  111. $this->modelData['relationMethodList'] = [];
  112. // 控制器数据
  113. $this->controllerData['use'] = [];
  114. $this->controllerData['attr'] = [];
  115. $this->controllerData['methods'] = [];
  116. $this->controllerData['filterRule'] = '';
  117. $this->controllerData['className'] = $controllerFile['lastName'];
  118. $this->controllerData['namespace'] = $controllerFile['namespace'];
  119. $this->controllerData['tableComment'] = $tableComment;
  120. $this->controllerData['modelName'] = $modelFile['lastName'];
  121. $this->controllerData['modelNamespace'] = $modelFile['namespace'];
  122. // index.vue数据
  123. $this->indexVueData['enableDragSort'] = false;
  124. $this->indexVueData['defaultItems'] = [];
  125. $this->indexVueData['tableColumn'] = [
  126. [
  127. 'type' => 'selection',
  128. 'align' => 'center',
  129. 'operator' => 'false',
  130. ],
  131. ];
  132. $this->indexVueData['dblClickNotEditColumn'] = ['undefined'];
  133. $this->indexVueData['optButtons'] = ['edit', 'delete'];
  134. $this->indexVueData['defaultOrder'] = '';
  135. // form.vue数据
  136. $this->formVueData['bigDialog'] = 'false';
  137. $this->formVueData['formFields'] = [];
  138. // 语言包数据
  139. $this->langTsData = [
  140. 'en' => [],
  141. 'zh-cn' => [],
  142. ];
  143. // 简化的字段数据
  144. $fieldsMap = [];
  145. foreach ($fields as $key => $field) {
  146. $fieldsMap[$field['name']] = $field['designType'];
  147. // 分析字段
  148. Helper::analyseField($field);
  149. Helper::getDictData($this->langTsData['en'], $field, 'en');
  150. Helper::getDictData($this->langTsData['zh-cn'], $field, 'zh-cn');
  151. // 快速搜索字段
  152. if (in_array($field['name'], $table['quickSearchField'])) {
  153. $quickSearchFieldZhCnTitle[] = $this->langTsData['zh-cn'][$field['name']] ?? $field['name'];
  154. }
  155. // 不允许双击编辑的字段
  156. if ($field['designType'] == 'switch') {
  157. $this->indexVueData['dblClickNotEditColumn'][] = $field['name'];
  158. }
  159. // 列字典数据
  160. $columnDict = $this->getColumnDict($field);
  161. // 表单项
  162. if (in_array($field['name'], $table['formFields'])) {
  163. $this->formVueData['formFields'][] = $this->getFormField($field, $columnDict);
  164. }
  165. // 表格列
  166. if (in_array($field['name'], $table['columnFields'])) {
  167. $this->indexVueData['tableColumn'][] = $this->getTableColumn($field, $columnDict);
  168. }
  169. // 关联表数据解析
  170. if (in_array($field['designType'], ['remoteSelect', 'remoteSelects'])) {
  171. $this->parseJoinData($field);
  172. }
  173. // 模型方法
  174. $this->parseModelMethods($field, $this->modelData);
  175. // 控制器/模型等文件的一些杂项属性解析
  176. $this->parseSundryData($field, $table);
  177. if (!in_array($field['name'], $table['formFields'])) {
  178. $this->controllerData['attr']['preExcludeFields'][] = $field['name'];
  179. }
  180. }
  181. // 快速搜索提示
  182. $this->langTsData['en']['quick Search Fields'] = implode(',', $table['quickSearchField']);
  183. $this->langTsData['zh-cn']['quick Search Fields'] = implode('、', $quickSearchFieldZhCnTitle);
  184. $this->controllerData['attr']['quickSearchField'] = $table['quickSearchField'];
  185. // 开启字段排序
  186. $weighKey = array_search('weigh', $fieldsMap);
  187. if ($weighKey !== false) {
  188. $this->indexVueData['enableDragSort'] = true;
  189. $this->modelData['afterInsert'] = Helper::assembleStub('mixins/model/afterInsert', [
  190. 'field' => $weighKey
  191. ]);
  192. }
  193. // 表格的操作列
  194. $this->indexVueData['tableColumn'][] = [
  195. 'label' => "t('Operate')",
  196. 'align' => 'center',
  197. 'width' => $this->indexVueData['enableDragSort'] ? 140 : 100,
  198. 'render' => 'buttons',
  199. 'buttons' => 'optButtons',
  200. 'operator' => 'false',
  201. ];
  202. if ($this->indexVueData['enableDragSort']) {
  203. array_unshift($this->indexVueData['optButtons'], 'weigh-sort');
  204. }
  205. // 写入语言包代码
  206. Helper::writeWebLangFile($this->langTsData, $webLangDir);
  207. // 写入模型代码
  208. Helper::writeModelFile($tablePk, $fieldsMap, $this->modelData, $modelFile);
  209. // 写入控制器代码
  210. Helper::writeControllerFile($this->controllerData, $controllerFile);
  211. // 写入验证器代码
  212. $validateContent = Helper::assembleStub('mixins/validate/validate', [
  213. 'namespace' => $validateFile['namespace'],
  214. 'className' => $validateFile['lastName'],
  215. ]);
  216. Helper::writeFile($validateFile['parseFile'], $validateContent);
  217. // 写入index.vue代码
  218. $this->indexVueData['tablePk'] = $tablePk;
  219. $this->indexVueData['webTranslate'] = $this->webTranslate;
  220. Helper::writeIndexFile($this->indexVueData, $webViewsDir, $controllerFile);
  221. // 写入form.vue代码
  222. Helper::writeFormFile($this->formVueData, $webViewsDir, $fields, $this->webTranslate);
  223. // 生成菜单
  224. Helper::createMenu($webViewsDir, $tableComment);
  225. Helper::recordCrudStatus([
  226. 'id' => $crudLogId,
  227. 'status' => 'success',
  228. ]);
  229. } catch (Exception $e) {
  230. Helper::recordCrudStatus([
  231. 'id' => $crudLogId ?? 0,
  232. 'status' => 'error',
  233. ]);
  234. $this->error($e->getMessage());
  235. } catch (Throwable $e) {
  236. Helper::recordCrudStatus([
  237. 'id' => $crudLogId ?? 0,
  238. 'status' => 'error',
  239. ]);
  240. if (env('app_debug', false)) throw $e;
  241. $this->error($e->getMessage());
  242. }
  243. $this->success();
  244. }
  245. /**
  246. * 从log开始
  247. * @throws Throwable
  248. */
  249. public function logStart(): void
  250. {
  251. $id = $this->request->post('id');
  252. $info = CrudLog::find($id)->toArray();
  253. if (!$info) {
  254. $this->error(__('Record not found'));
  255. }
  256. // 数据表是否有数据
  257. $adapter = TableManager::adapter();
  258. if ($adapter->hasTable($info['table']['name'])) {
  259. $info['table']['empty'] = Db::name($info['table']['name'])->limit(1)->select()->isEmpty();
  260. } else {
  261. $info['table']['empty'] = true;
  262. }
  263. AdminLog::setTitle(__('Log start'));
  264. $this->success('', [
  265. 'table' => $info['table'],
  266. 'fields' => $info['fields'],
  267. ]);
  268. }
  269. /**
  270. * 删除CRUD记录和生成的文件
  271. * @throws Throwable
  272. */
  273. public function delete(): void
  274. {
  275. $id = $this->request->post('id');
  276. $info = CrudLog::find($id)->toArray();
  277. if (!$info) {
  278. $this->error(__('Record not found'));
  279. }
  280. $webLangDir = Helper::parseWebDirNameData($info['table']['name'], 'lang', $info['table']['webViewsDir']);
  281. $files = [
  282. $webLangDir['en'] . '.ts',
  283. $webLangDir['zh-cn'] . '.ts',
  284. $info['table']['webViewsDir'] . '/' . 'index.vue',
  285. $info['table']['webViewsDir'] . '/' . 'popupForm.vue',
  286. $info['table']['controllerFile'],
  287. $info['table']['modelFile'],
  288. $info['table']['validateFile'],
  289. ];
  290. try {
  291. foreach ($files as &$file) {
  292. $file = Filesystem::fsFit(root_path() . $file);
  293. if (file_exists($file)) {
  294. unlink($file);
  295. }
  296. Filesystem::delEmptyDir(dirname($file));
  297. }
  298. // 删除菜单
  299. Menu::delete(Helper::getMenuName($webLangDir), true);
  300. Helper::recordCrudStatus([
  301. 'id' => $id,
  302. 'status' => 'delete',
  303. ]);
  304. } catch (Throwable $e) {
  305. $this->error($e->getMessage());
  306. }
  307. $this->success(__('Deleted successfully'));
  308. }
  309. /**
  310. * 获取文件路径数据
  311. * @throws Throwable
  312. */
  313. public function getFileData(): void
  314. {
  315. $table = $this->request->get('table');
  316. $commonModel = $this->request->get('commonModel/b');
  317. if (!$table) {
  318. $this->error(__('Parameter error'));
  319. }
  320. try {
  321. $modelFile = Helper::parseNameData($commonModel ? 'common' : 'admin', $table, 'model');
  322. $validateFile = Helper::parseNameData('admin', $table, 'validate');
  323. $controllerFile = Helper::parseNameData('admin', $table, 'controller');
  324. $webViewsDir = Helper::parseWebDirNameData($table, 'views');
  325. } catch (Throwable $e) {
  326. $this->error($e->getMessage());
  327. }
  328. // 模型和控制器文件和文件列表
  329. $adminModelFiles = Filesystem::getDirFiles(root_path() . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . 'model' . DIRECTORY_SEPARATOR);
  330. $commonModelFiles = Filesystem::getDirFiles(root_path() . 'app' . DIRECTORY_SEPARATOR . 'common' . DIRECTORY_SEPARATOR . 'model' . DIRECTORY_SEPARATOR);
  331. $adminControllerFiles = get_controller_list();
  332. $modelFileList = [];
  333. $controllerFiles = [];
  334. foreach ($adminModelFiles as $item) {
  335. $item = Filesystem::fsFit('app/admin/model/' . $item);
  336. $modelFileList[$item] = $item;
  337. }
  338. foreach ($commonModelFiles as $item) {
  339. $item = Filesystem::fsFit('app/common/model/' . $item);
  340. $modelFileList[$item] = $item;
  341. }
  342. $outExcludeController = [
  343. 'Addon.php',
  344. 'Ajax.php',
  345. 'Dashboard.php',
  346. 'Index.php',
  347. 'Module.php',
  348. 'Terminal.php',
  349. 'routine/AdminInfo.php',
  350. 'routine/Config.php',
  351. ];
  352. foreach ($adminControllerFiles as $item) {
  353. if (in_array($item, $outExcludeController)) {
  354. continue;
  355. }
  356. $item = Filesystem::fsFit('app/admin/controller/' . $item);
  357. $controllerFiles[$item] = $item;
  358. }
  359. $this->success('', [
  360. 'modelFile' => $modelFile['rootFileName'],
  361. 'controllerFile' => $controllerFile['rootFileName'],
  362. 'validateFile' => $validateFile['rootFileName'],
  363. 'controllerFileList' => $controllerFiles,
  364. 'modelFileList' => $modelFileList,
  365. 'webViewsDir' => $webViewsDir['views'],
  366. ]);
  367. }
  368. /**
  369. * 检查是否已有CRUD记录
  370. * @throws Throwable
  371. */
  372. public function checkCrudLog(): void
  373. {
  374. $table = $this->request->get('table');
  375. $crudLog = Db::name('crud_log')
  376. ->where('table_name', $table)
  377. ->order('create_time desc')
  378. ->find();
  379. $this->success('', [
  380. 'id' => ($crudLog && $crudLog['status'] == 'success') ? $crudLog['id'] : 0,
  381. ]);
  382. }
  383. /**
  384. * 解析字段数据
  385. * @throws Throwable
  386. */
  387. public function parseFieldData(): void
  388. {
  389. AdminLog::setTitle(__('Parse field data'));
  390. $type = $this->request->post('type');
  391. $table = $this->request->post('table');
  392. $table = TableManager::tableName($table);
  393. if ($type == 'db') {
  394. $sql = 'SELECT * FROM `information_schema`.`tables` '
  395. . 'WHERE TABLE_SCHEMA = ? AND table_name = ?';
  396. $tableInfo = Db::query($sql, [config('database.connections.mysql.database'), $table]);
  397. if (!$tableInfo) {
  398. $this->error(__('Record not found'));
  399. }
  400. // 数据表是否有数据
  401. $adapter = TableManager::adapter(false);
  402. if ($adapter->hasTable($table)) {
  403. $empty = Db::table($table)->limit(1)->select()->isEmpty();
  404. } else {
  405. $empty = true;
  406. }
  407. $this->success('', [
  408. 'columns' => Helper::parseTableColumns($table),
  409. 'comment' => $tableInfo[0]['TABLE_COMMENT'] ?? '',
  410. 'empty' => $empty,
  411. ]);
  412. }
  413. }
  414. /**
  415. * 生成前检查
  416. * @throws Throwable
  417. */
  418. public function generateCheck(): void
  419. {
  420. $table = $this->request->post('table');
  421. $controllerFile = $this->request->post('controllerFile', '');
  422. if (!$table) {
  423. $this->error(__('Parameter error'));
  424. }
  425. AdminLog::setTitle(__('Generate check'));
  426. try {
  427. if (!$controllerFile) {
  428. $controllerFile = Helper::parseNameData('admin', $table, 'controller')['rootFileName'];
  429. }
  430. } catch (Throwable $e) {
  431. $this->error($e->getMessage());
  432. }
  433. $tableList = get_table_list();
  434. $tableExist = array_key_exists(TableManager::tableName($table), $tableList);
  435. $controllerExist = file_exists(root_path() . $controllerFile);
  436. if ($controllerExist || $tableExist) {
  437. $this->error('', [
  438. 'table' => $tableExist,
  439. 'controller' => $controllerExist,
  440. ], -1);
  441. }
  442. $this->success();
  443. }
  444. public function databaseList(): void
  445. {
  446. $tablePrefix = config('database.connections.mysql.prefix');
  447. $outExcludeTable = [
  448. // 功能表
  449. 'area',
  450. 'token',
  451. 'captcha',
  452. 'admin_group_access',
  453. 'config',
  454. 'admin_log',
  455. // 不建议生成crud的表
  456. 'user_money_log',
  457. 'user_score_log',
  458. ];
  459. $outTables = [];
  460. $tables = get_table_list();
  461. $pattern = '/^' . $tablePrefix . '/i';
  462. foreach ($tables as $table => $tableComment) {
  463. if (!preg_match($pattern, $table)) continue;
  464. $table = preg_replace($pattern, '', $table);
  465. if (!in_array($table, $outExcludeTable)) {
  466. $outTables[$table] = $tableComment;
  467. }
  468. }
  469. $this->success('', [
  470. 'dbs' => $outTables,
  471. ]);
  472. }
  473. /**
  474. * 关联表数据解析
  475. * @param $field
  476. * @throws Throwable
  477. */
  478. private function parseJoinData($field): void
  479. {
  480. $dictEn = [];
  481. $dictZhCn = [];
  482. if ($field['form']['relation-fields'] && $field['form']['remote-table']) {
  483. $columns = Helper::parseTableColumns($field['form']['remote-table'], true);
  484. $relationFields = explode(',', $field['form']['relation-fields']);
  485. $tableName = TableManager::tableName($field['form']['remote-table'], false);
  486. $rnPattern = '/(.*)(_ids|_id)$/';
  487. if (preg_match($rnPattern, $field['name'])) {
  488. $relationName = parse_name(preg_replace($rnPattern, '$1', $field['name']), 1, false);
  489. } else {
  490. $relationName = parse_name($field['name'] . '_table', 1, false);
  491. }
  492. // 建立关联模型代码文件
  493. if (!$field['form']['remote-model'] || !file_exists(root_path() . $field['form']['remote-model'])) {
  494. $joinModelFile = Helper::parseNameData('admin', $tableName, 'model', $field['form']['remote-model']);
  495. if (!file_exists(root_path() . $joinModelFile['rootFileName'])) {
  496. $joinModelData['append'] = [];
  497. $joinModelData['methods'] = [];
  498. $joinModelData['fieldType'] = [];
  499. $joinModelData['createTime'] = '';
  500. $joinModelData['updateTime'] = '';
  501. $joinModelData['beforeInsertMixins'] = [];
  502. $joinModelData['beforeInsert'] = '';
  503. $joinModelData['afterInsert'] = '';
  504. $joinModelData['name'] = $tableName;
  505. $joinModelData['className'] = $joinModelFile['lastName'];
  506. $joinModelData['namespace'] = $joinModelFile['namespace'];
  507. $joinTablePk = 'id';
  508. $joinFieldsMap = [];
  509. foreach ($columns as $column) {
  510. $joinFieldsMap[$column['name']] = $column['designType'];
  511. $this->parseModelMethods($column, $joinModelData);
  512. if ($column['primaryKey']) $joinTablePk = $column['name'];
  513. }
  514. $weighKey = array_search('weigh', $joinFieldsMap);
  515. if ($weighKey !== false) {
  516. $joinModelData['afterInsert'] = Helper::assembleStub('mixins/model/afterInsert', [
  517. 'field' => $joinFieldsMap[$weighKey]
  518. ]);
  519. }
  520. Helper::writeModelFile($joinTablePk, $joinFieldsMap, $joinModelData, $joinModelFile);
  521. }
  522. $field['form']['remote-model'] = $joinModelFile['rootFileName'];
  523. }
  524. if ($field['designType'] == 'remoteSelect') {
  525. // 关联预载入方法
  526. $this->controllerData['attr']['withJoinTable'][$relationName] = $relationName;
  527. // 模型方法代码
  528. $relationData = [
  529. 'relationMethod' => $relationName,
  530. 'relationMode' => 'belongsTo',
  531. 'relationPrimaryKey' => $field['form']['remote-pk'] ?? 'id',
  532. 'relationForeignKey' => $field['name'],
  533. 'relationClassName' => str_replace(['.php', '/'], ['', '\\'], '\\' . $field['form']['remote-model']) . "::class",
  534. ];
  535. $this->modelData['relationMethodList'][$relationName] = Helper::assembleStub('mixins/model/belongsTo', $relationData);
  536. // 查询时显示的字段
  537. if ($relationFields) {
  538. $this->controllerData['relationVisibleFieldList'][$relationData['relationMethod']] = $relationFields;
  539. }
  540. } elseif ($field['designType'] == 'remoteSelects') {
  541. $this->modelData['append'][] = $relationName;
  542. $this->modelData['methods'][] = Helper::assembleStub('mixins/model/getters/remoteSelectLabels', [
  543. 'field' => parse_name($relationName, 1),
  544. 'className' => str_replace(['.php', '/'], ['', '\\'], '\\' . $field['form']['remote-model']),
  545. 'primaryKey' => $field['form']['remote-pk'] ?? 'id',
  546. 'foreignKey' => $field['name'],
  547. 'labelFieldName' => $field['form']['remote-field'] ?? 'name',
  548. ]);
  549. }
  550. foreach ($relationFields as $relationField) {
  551. if (!array_key_exists($relationField, $columns)) continue;
  552. $relationFieldPrefix = $relationName . '.';
  553. $relationFieldLangPrefix = strtolower($relationName) . '__';
  554. Helper::getDictData($dictEn, $columns[$relationField], 'en', $relationFieldLangPrefix);
  555. Helper::getDictData($dictZhCn, $columns[$relationField], 'zh-cn', $relationFieldLangPrefix);
  556. // 不允许双击编辑的字段
  557. if ($columns[$relationField]['designType'] == 'switch') {
  558. $this->indexVueData['dblClickNotEditColumn'][] = $field['name'];
  559. }
  560. // 列字典数据
  561. $columnDict = $this->getColumnDict($columns[$relationField], $relationFieldLangPrefix);
  562. // 表格列
  563. $columns[$relationField]['designType'] = $field['designType'];
  564. $columns[$relationField]['table']['render'] = 'tags';
  565. if ($field['designType'] == 'remoteSelects') {
  566. $columns[$relationField]['table']['operator'] = 'false';
  567. $this->indexVueData['tableColumn'][] = $this->getTableColumn($columns[$relationField], $columnDict, $relationFieldPrefix, $relationFieldLangPrefix);
  568. // 额外生成一个公共搜索,渲染为远程下拉的列
  569. unset($columns[$relationField]['table']['render']);
  570. $columns[$relationField]['table']['label'] = "t('" . $this->webTranslate . $relationFieldLangPrefix . $columns[$relationField]['name'] . "')";
  571. $columns[$relationField]['name'] = $field['name'];
  572. $columns[$relationField]['table']['show'] = 'false';
  573. $columns[$relationField]['table']['operator'] = 'FIND_IN_SET';
  574. $columns[$relationField]['table']['comSearchRender'] = 'remoteSelect';
  575. $columns[$relationField]['table']['remote'] = [
  576. 'pk' => TableManager::tableName($field['form']['remote-table']) . '.' . ($field['form']['remote-pk'] ?? 'id'),
  577. 'field' => $field['form']['remote-field'] ?? 'name',
  578. 'remoteUrl' => $this->getRemoteSelectUrl($field),
  579. 'multiple' => 'true',
  580. ];
  581. $this->indexVueData['tableColumn'][] = $this->getTableColumn($columns[$relationField], $columnDict, '', $relationFieldLangPrefix);
  582. } else {
  583. $columns[$relationField]['table']['operator'] = 'LIKE';
  584. $this->indexVueData['tableColumn'][] = $this->getTableColumn($columns[$relationField], $columnDict, $relationFieldPrefix, $relationFieldLangPrefix);
  585. }
  586. }
  587. }
  588. $this->langTsData['en'] = array_merge($this->langTsData['en'], $dictEn);
  589. $this->langTsData['zh-cn'] = array_merge($this->langTsData['zh-cn'], $dictZhCn);
  590. }
  591. /**
  592. * 解析模型方法(设置器、获取器等)
  593. */
  594. private function parseModelMethods($field, &$modelData): void
  595. {
  596. // fieldType
  597. if ($field['designType'] == 'array') {
  598. $modelData['fieldType'][$field['name']] = 'json';
  599. } elseif (!in_array($field['name'], ['create_time', 'update_time', 'updatetime', 'createtime']) && $field['designType'] == 'datetime' && (in_array($field['type'], ['int', 'bigint']))) {
  600. $modelData['fieldType'][$field['name']] = 'timestamp:Y-m-d H:i:s';
  601. }
  602. // beforeInsertMixins
  603. if ($field['designType'] == 'spk') {
  604. $modelData['beforeInsertMixins']['snowflake'] = Helper::assembleStub('mixins/model/mixins/beforeInsertWithSnowflake', []);
  605. }
  606. // methods
  607. $fieldName = parse_name($field['name'], 1);
  608. if (in_array($field['designType'], $this->dtStringToArray)) {
  609. $modelData['methods'][] = Helper::assembleStub('mixins/model/getters/stringToArray', [
  610. 'field' => $fieldName
  611. ]);
  612. $modelData['methods'][] = Helper::assembleStub('mixins/model/setters/arrayToString', [
  613. 'field' => $fieldName
  614. ]);
  615. } elseif ($field['designType'] == 'array') {
  616. $modelData['methods'][] = Helper::assembleStub('mixins/model/getters/jsonDecode', [
  617. 'field' => $fieldName
  618. ]);
  619. } elseif ($field['designType'] == 'time') {
  620. $modelData['methods'][] = Helper::assembleStub('mixins/model/setters/time', [
  621. 'field' => $fieldName
  622. ]);
  623. } elseif ($field['designType'] == 'editor') {
  624. $modelData['methods'][] = Helper::assembleStub('mixins/model/getters/htmlDecode', [
  625. 'field' => $fieldName
  626. ]);
  627. } elseif ($field['designType'] == 'spk') {
  628. $modelData['methods'][] = Helper::assembleStub('mixins/model/getters/string', [
  629. 'field' => $fieldName
  630. ]);
  631. } elseif ($field['originalDesignType'] == 'float') {
  632. $modelData['methods'][] = Helper::assembleStub('mixins/model/getters/float', [
  633. 'field' => $fieldName
  634. ]);
  635. }
  636. if ($field['designType'] == 'city') {
  637. $modelData['append'][] = $field['name'] . '_text';
  638. $modelData['methods'][] = Helper::assembleStub('mixins/model/getters/cityNames', [
  639. 'field' => $fieldName . 'Text',
  640. 'originalFieldName' => $field['name'],
  641. ]);
  642. }
  643. }
  644. /**
  645. * 控制器/模型等文件的一些杂项属性解析
  646. */
  647. private function parseSundryData($field, $table): void
  648. {
  649. if ($field['designType'] == 'editor') {
  650. $this->formVueData['bigDialog'] = 'true'; // form 使用较宽的 Dialog
  651. $this->controllerData['filterRule'] = "\n" . Helper::tab(2) . '$this->request->filter(\'clean_xss\');';// 修改变量过滤规则
  652. }
  653. // 默认排序字段
  654. if ($table['defaultSortField'] && $table['defaultSortType']) {
  655. $defaultSortField = "{$table['defaultSortField']},{$table['defaultSortType']}";
  656. if ($defaultSortField == 'id,desc') {
  657. $this->controllerData['attr']['defaultSortField'] = '';
  658. } else {
  659. $this->controllerData['attr']['defaultSortField'] = $defaultSortField;
  660. $this->indexVueData['defaultOrder'] = Helper::buildDefaultOrder($table['defaultSortField'], $table['defaultSortType']);
  661. }
  662. }
  663. }
  664. private function getFormField($field, $columnDict): array
  665. {
  666. // 表单项属性
  667. $formField = [
  668. ':label' => 't(\'' . $this->webTranslate . $field['name'] . '\')',
  669. 'type' => $field['designType'],
  670. 'v-model' => 'baTable.form.items!.' . $field['name'],
  671. 'prop' => $field['name'],
  672. ];
  673. // 不同输入框的属性处理
  674. if ($columnDict || in_array($field['designType'], ['radio', 'checkbox', 'select', 'selects'])) {
  675. $formField[':data'] = [
  676. 'content' => $columnDict,
  677. ];
  678. } elseif ($field['designType'] == 'textarea') {
  679. $formField[':input-attr']['rows'] = (int)($field['form']['rows'] ?? 3);
  680. $formField['@keyup.enter.stop'] = '';
  681. $formField['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)';
  682. } elseif ($field['designType'] == 'remoteSelect' || $field['designType'] == 'remoteSelects') {
  683. $formField[':input-attr']['pk'] = TableManager::tableName($field['form']['remote-table']) . '.' . ($field['form']['remote-pk'] ?? 'id');
  684. $formField[':input-attr']['field'] = $field['form']['remote-field'] ?? 'name';
  685. $formField[':input-attr']['remote-url'] = $this->getRemoteSelectUrl($field);
  686. } elseif ($field['designType'] == 'number') {
  687. $formField[':input-attr']['step'] = (int)($field['form']['step'] ?? 1);
  688. $formField['v-model.number'] = $formField['v-model'];
  689. unset($formField['v-model']);
  690. } elseif ($field['designType'] == 'icon') {
  691. $formField[':input-attr']['placement'] = 'top';
  692. } elseif ($field['designType'] == 'editor') {
  693. $formField['@keyup.enter.stop'] = '';
  694. $formField['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)';
  695. }
  696. // placeholder
  697. if (!in_array($field['designType'], ['image', 'images', 'file', 'files', 'switch'])) {
  698. if (in_array($field['designType'], ['radio', 'checkbox', 'datetime', 'year', 'date', 'time', 'select', 'selects', 'remoteSelect', 'remoteSelects', 'city', 'icon'])) {
  699. $formField[':placeholder'] = "t('Please select field', { field: t('" . $this->webTranslate . $field['name'] . "') })";
  700. } else {
  701. $formField[':placeholder'] = "t('Please input field', { field: t('" . $this->webTranslate . $field['name'] . "') })";
  702. }
  703. }
  704. // 默认值
  705. if ($field['default'] && $field['default'] != 'empty string') {
  706. $this->indexVueData['defaultItems'][$field['name']] = $field['default'];
  707. }
  708. if ($field['default'] == 'null') {
  709. $this->indexVueData['defaultItems'][$field['name']] = $field['designType'] == 'editor' ? '' : null;
  710. } elseif ($field['default'] == '0' && in_array($field['designType'], ['radio', 'checkbox', 'select', 'selects'])) {
  711. // 防止为`0`时无法设置上默认值
  712. $this->indexVueData['defaultItems'][$field['name']] = '0';
  713. }
  714. if ($field['designType'] == 'array') {
  715. $this->indexVueData['defaultItems'][$field['name']] = "[]";
  716. } elseif (in_array($field['designType'], $this->dtStringToArray) && $field['default'] !== null && stripos($field['default'], ',') !== false) {
  717. $this->indexVueData['defaultItems'][$field['name']] = Helper::buildSimpleArray(explode(',', $field['default']));
  718. } elseif (in_array($field['designType'], ['weigh', 'number', 'float'])) {
  719. $this->indexVueData['defaultItems'][$field['name']] = (float)$field['default'];
  720. }
  721. return $formField;
  722. }
  723. private function getRemoteSelectUrl($field): string
  724. {
  725. if ($field['form']['remote-url']) return $field['form']['remote-url'];
  726. $url = '';
  727. if ($field['form']['remote-controller']) {
  728. $pathArr = [];
  729. $controller = explode(DIRECTORY_SEPARATOR, $field['form']['remote-controller']);
  730. $controller = str_replace('.php', '', $controller);
  731. $redundantDir = [
  732. 'app' => 0,
  733. 'admin' => 1,
  734. 'controller' => 2,
  735. ];
  736. foreach ($controller as $key => $item) {
  737. if (!array_key_exists($item, $redundantDir) || $key !== $redundantDir[$item]) {
  738. $pathArr[] = $item;
  739. }
  740. }
  741. $url = count($pathArr) > 1 ? implode('.', $pathArr) : $pathArr[0];
  742. $url = '/admin/' . $url . '/index';
  743. }
  744. return $url;
  745. }
  746. private function getTableColumn($field, $columnDict, $fieldNamePrefix = '', $translationPrefix = ''): array
  747. {
  748. $column = [
  749. 'label' => "t('" . $this->webTranslate . $translationPrefix . $field['name'] . "')",
  750. 'prop' => $fieldNamePrefix . $field['name'] . ($field['designType'] == 'city' ? '_text' : ''),
  751. 'align' => 'center',
  752. ];
  753. // 模糊搜索增加一个placeholder
  754. if (isset($field['table']['operator']) && $field['table']['operator'] == 'LIKE') {
  755. $column['operatorPlaceholder'] = "t('Fuzzy query')";
  756. }
  757. // 合并前端预设的字段表格属性
  758. if (isset($field['table']) && $field['table']) {
  759. $column = array_merge($column, $field['table']);
  760. }
  761. // 需要值替换的渲染类型
  762. $columnReplaceValue = ['tag', 'tags', 'switch'];
  763. if (!in_array($field['designType'], ['remoteSelect', 'remoteSelects']) && ($columnDict || (isset($field['table']['render']) && in_array($field['table']['render'], $columnReplaceValue)))) {
  764. $column['replaceValue'] = $columnDict;
  765. }
  766. if (isset($column['render']) && $column['render'] == 'none') {
  767. unset($column['render']);
  768. }
  769. return $column;
  770. }
  771. private function getColumnDict($column, $translationPrefix = ''): array
  772. {
  773. $dict = [];
  774. // 确保字典中无翻译也可以识别到该值
  775. if (in_array($column['type'], ['enum', 'set'])) {
  776. $dataType = str_replace(' ', '', $column['dataType']);
  777. $columnData = substr($dataType, stripos($dataType, '(') + 1, -1);
  778. $columnData = explode(',', str_replace(["'", '"'], '', $columnData));
  779. foreach ($columnData as $columnDatum) {
  780. $dict[$columnDatum] = $column['name'] . ' ' . $columnDatum;
  781. }
  782. }
  783. $dictData = [];
  784. Helper::getDictData($dictData, $column, 'zh-cn', $translationPrefix);
  785. if ($dictData) {
  786. unset($dictData[$translationPrefix . $column['name']]);
  787. foreach ($dictData as $key => $item) {
  788. $keyName = str_replace($translationPrefix . $column['name'] . ' ', '', $key);
  789. $dict[$keyName] = "t('" . $this->webTranslate . $key . "')";
  790. }
  791. }
  792. return $dict;
  793. }
  794. }