Crud.php 38 KB

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