From 2524ced5d4895b67ac788b31f548809f05d40563 Mon Sep 17 00:00:00 2001 From: anchonghuang <2497471389@qq.com> Date: Wed, 2 Jul 2025 22:13:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(CRUD):=E5=8D=95=E9=80=89=E6=A1=86=E3=80=81?= =?UTF-8?q?=E5=A4=8D=E9=80=89=E6=A1=86=E3=80=81=E4=B8=8B=E6=8B=89=E6=A1=86?= =?UTF-8?q?=E3=80=81=E4=B8=8B=E6=8B=89=E6=A1=86=E5=A4=9A=E9=80=89=E5=85=B3?= =?UTF-8?q?=E8=81=94=E5=AD=97=E5=85=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/crud/Crud.php | 123 +++++++++++++++++-- app/admin/library/crud/Helper.php | 16 ++- app/admin/library/crud/stubs/html/index.stub | 2 +- config/buildadmin.php | 2 +- config/database.php | 4 +- web/src/lang/backend/en/crud/crud.ts | 10 +- web/src/lang/backend/zh-cn/crud/crud.ts | 2 + web/src/stores/interface/index.ts | 38 ++++++ web/src/views/backend/crud/design.vue | 106 +++++++++++++++- web/src/views/backend/crud/index.ts | 5 + 10 files changed, 289 insertions(+), 19 deletions(-) diff --git a/app/admin/controller/crud/Crud.php b/app/admin/controller/crud/Crud.php index 217c45d9..8afba081 100644 --- a/app/admin/controller/crud/Crud.php +++ b/app/admin/controller/crud/Crud.php @@ -144,6 +144,7 @@ class Crud extends Backend $this->controllerData['tableComment'] = $tableComment; $this->controllerData['modelName'] = $modelFile['lastName']; $this->controllerData['modelNamespace'] = $modelFile['namespace']; + $this->controllerData['hasDictField'] = false; // 标记是否包含字典字段 // index.vue数据 $this->indexVueData['enableDragSort'] = false; @@ -218,6 +219,11 @@ class Crud extends Backend // 控制器/模型等文件的一些杂项属性解析 $this->parseSundryData($field, $table); + // 检测字典字段 + if (isset($field['sysdictType']) && !empty($field['sysdictType'])) { + $this->controllerData['hasDictField'] = true; + } + if (!in_array($field['name'], $table['formFields'])) { $this->controllerData['attr']['preExcludeFields'][] = $field['name']; } @@ -253,6 +259,8 @@ class Crud extends Backend // 写入语言包代码 Helper::writeWebLangFile($this->langTsData, $webLangDir); + // 如果有字典字段,添加相关导入处理已集成到各生成方法中 + // 写入模型代码 Helper::writeModelFile($tablePk, $fieldsMap, $this->modelData, $modelFile); @@ -269,6 +277,16 @@ class Crud extends Backend // 写入index.vue代码 $this->indexVueData['tablePk'] = $tablePk; $this->indexVueData['webTranslate'] = $this->webTranslate; + + // 添加字典相关的导入 + if (isset($this->indexVueData['needDictData']) && $this->indexVueData['needDictData']) { + if (!isset($this->indexVueData['imports'])) { + $this->indexVueData['imports'] = []; + } + $this->indexVueData['imports'][] = "import { useDictData } from '/@/stores/dictData'"; + $this->indexVueData['imports'][] = "const dictData = useDictData()"; + } + Helper::writeIndexFile($this->indexVueData, $webViewsDir, $controllerFile); // 写入form.vue代码 @@ -850,7 +868,36 @@ class Crud extends Backend // 不同输入框的属性处理 if ($columnDict || in_array($field['designType'], ['radio', 'checkbox', 'select', 'selects'])) { - $formField[':input-attr']['content'] = $columnDict; + // 优先使用系统字典 + if (isset($field['sysdictType']) && !empty($field['sysdictType'])) { + $formField['type'] = $this->getDictFormType($field['designType']); + + // select/selects 使用 remote 方式(remoteSelect/remoteSelects) + if (in_array($field['designType'], ['select', 'selects'])) { + $formField[':input-attr'] = [ + 'pk' => 'key', + 'field' => 'value', + 'remoteUrl' => '/admin/dict.Data/index', + 'params' => [ + 'entryCode' => $field['sysdictType'], + ], + ]; + } + // radio/checkbox 使用 content 方式 + getDictOptionsById + else { + $formField[':input-attr']['content'] = "__JS__dictData.getDictOptionsById('{$field['sysdictType']}')"; + } + + // 为字典字段添加前端导入依赖 + if (!in_array("import { useDictData } from '/@/stores/dictData'", $this->formVueData['imports'])) { + $this->formVueData['imports'][] = "import { useDictData } from '/@/stores/dictData'"; + } + if (!in_array("const dictData = useDictData()", $this->formVueData['imports'])) { + $this->formVueData['imports'][] = "const dictData = useDictData()"; + } + } else { + $formField[':input-attr']['content'] = $columnDict; + } } elseif ($field['designType'] == 'textarea') { $formField[':input-attr']['rows'] = (int)($field['form']['rows'] ?? 3); $formField['@keyup.enter.stop'] = ''; @@ -877,6 +924,32 @@ class Crud extends Backend } } + // 默认值处理 + $this->handleDefaultValue($field); + + return $formField; + } + + /** + * 获取字典字段的表单类型 + */ + private function getDictFormType($designType): string + { + $dictFormTypeMap = [ + 'select' => 'remoteSelect', + 'selects' => 'remoteSelects', + 'radio' => 'radio', + 'checkbox' => 'checkbox', + ]; + + return $dictFormTypeMap[$designType] ?? $designType; + } + + /** + * 处理默认值 + */ + private function handleDefaultValue($field): void + { // 默认值 if ($field['defaultType'] == 'INPUT') { $this->indexVueData['defaultItems'][$field['name']] = $field['default']; @@ -897,8 +970,6 @@ class Crud extends Backend if (isset($field['default']) && in_array($field['designType'], ['switch', 'number', 'float', 'remoteSelect']) && $field['default'] == 0) { unset($this->indexVueData['defaultItems'][$field['name']]); } - - return $formField; } private function getRemoteSelectPk($field): string @@ -954,10 +1025,15 @@ class Crud extends Backend $column = array_merge($column, $field['table']); } - // 需要值替换的渲染类型 - $columnReplaceValue = ['tag', 'tags', 'switch']; - if (!in_array($field['designType'], ['remoteSelect', 'remoteSelects']) && ($columnDict || (isset($field['table']['render']) && in_array($field['table']['render'], $columnReplaceValue)))) { - $column['replaceValue'] = $columnDict; + // 字典字段的特殊处理 + if (isset($field['sysdictType']) && !empty($field['sysdictType'])) { + $column = $this->handleDictTableColumn($column, $field, $translationPrefix); + } else { + // 需要值替换的渲染类型 + $columnReplaceValue = ['tag', 'tags', 'switch']; + if (!in_array($field['designType'], ['remoteSelect', 'remoteSelects']) && ($columnDict || (isset($field['table']['render']) && in_array($field['table']['render'], $columnReplaceValue)))) { + $column['replaceValue'] = $columnDict; + } } if (isset($column['render']) && $column['render'] == 'none') { @@ -966,6 +1042,39 @@ class Crud extends Backend return $column; } + /** + * 处理字典字段的表格列配置 + */ + private function handleDictTableColumn($column, $field, $translationPrefix = ''): array + { + // 使用 formatter 方式获取字典值显示 + $column['formatter'] = "function(row, column, cellValue) { return dictData.getDictValueByKey('{$field['sysdictType']}', cellValue) }"; + + // 搜索配置统一使用 remoteSelect + $column['comSearchRender'] = 'remoteSelect'; + $column['remote'] = [ + 'pk' => 'key', + 'field' => 'value', + 'remoteUrl' => '/admin/dict.Data/index', + 'params' => [ + 'entryCode' => $field['sysdictType'], + ], + ]; + + // 为多选字段设置特殊配置 + if (in_array($field['designType'], ['selects', 'checkbox'])) { + $column['operator'] = 'FIND_IN_SET'; + $column['remote']['multiple'] = 'true'; + } else { + $column['operator'] = '='; + } + + // 添加字典数据导入标记 + $this->indexVueData['needDictData'] = true; + + return $column; + } + private function getColumnDict($column, $translationPrefix = ''): array { $dict = []; diff --git a/app/admin/library/crud/Helper.php b/app/admin/library/crud/Helper.php index ee922f66..77b41328 100644 --- a/app/admin/library/crud/Helper.php +++ b/app/admin/library/crud/Helper.php @@ -1163,6 +1163,14 @@ class Helper $controllerFile['path'][] = $controllerFile['originalLastName']; $indexVueData['controllerUrl'] = '\'/admin/' . ($controllerFile['path'] ? implode('.', $controllerFile['path']) : '') . '/\''; $indexVueData['componentName'] = ($webViewsDir['path'] ? implode('/', $webViewsDir['path']) . '/' : '') . $webViewsDir['originalLastName']; + + // 处理导入扩展 + if (isset($indexVueData['imports']) && is_array($indexVueData['imports'])) { + $indexVueData['importExpand'] = self::buildImportExpand($indexVueData['imports']); + } else { + $indexVueData['importExpand'] = ''; + } + $indexVueContent = self::assembleStub('html/index', $indexVueData); self::writeFile(root_path() . $webViewsDir['views'] . '/' . 'index.vue', $indexVueContent); } @@ -1190,10 +1198,10 @@ class Helper $itemJson .= self::buildTableColumnKey($ik, $iItem); } $itemJson = rtrim($itemJson, ','); - $itemJson .= ' }'; + $itemJson .= ' },'; } elseif ($item === 'false' || $item === 'true') { $itemJson = ' ' . $key . ': ' . $item . ','; - } elseif (in_array($key, ['label', 'width', 'buttons'], true) || str_starts_with($item, "t('") || str_starts_with($item, "t(\"")) { + } elseif (in_array($key, ['label', 'width', 'buttons', 'formatter'], true) || str_starts_with($item, "t('") || str_starts_with($item, "t(\"") || str_starts_with($item, "function(")) { $itemJson = ' ' . $key . ': ' . $item . ','; } else { $itemJson = ' ' . $key . ': \'' . $item . '\','; @@ -1277,6 +1285,10 @@ class Helper $jsonStr .= $keyStr . $item . ','; } elseif (isset($item[0]) && $item[0] == '[' && str_ends_with($item, ']')) { $jsonStr .= $keyStr . $item . ','; + } elseif (str_starts_with($item, '__JS__')) { + // 处理 JavaScript 表达式,去除 __JS__ 标记 + $jsExpression = substr($item, 6); // 去除 '__JS__' 前缀 + $jsonStr .= $keyStr . $jsExpression . ','; } else { $quote = self::getQuote($item); $jsonStr .= $keyStr . "$quote$item$quote,"; diff --git a/app/admin/library/crud/stubs/html/index.stub b/app/admin/library/crud/stubs/html/index.stub index 53ad5fe7..78dbb10b 100644 --- a/app/admin/library/crud/stubs/html/index.stub +++ b/app/admin/library/crud/stubs/html/index.stub @@ -27,7 +27,7 @@ import { baTableApi } from '/@/api/common' import { defaultOptButtons } from '/@/components/table' import TableHeader from '/@/components/table/header/index.vue' import Table from '/@/components/table/index.vue' -import baTableClass from '/@/utils/baTable' +import baTableClass from '/@/utils/baTable'{%importExpand%} defineOptions({ name: '{%componentName%}', diff --git a/config/buildadmin.php b/config/buildadmin.php index cf15b874..c5b1247e 100644 --- a/config/buildadmin.php +++ b/config/buildadmin.php @@ -42,7 +42,7 @@ return [ // 默认驱动方式 'default' => 'mysql', // 加密key - 'key' => 'tcbDgmqLVzuAdNH39o0QnhOisvSCFZ7I', + 'key' => '6fAhpdHPK7EZ3NBjs0YuDnJlFixgcvWO', // 加密方式 'algo' => 'ripemd160', // 驱动 diff --git a/config/database.php b/config/database.php index aa7e77c3..0f60bfb6 100644 --- a/config/database.php +++ b/config/database.php @@ -26,11 +26,11 @@ return [ // 服务器地址 'hostname' => env('database.hostname', '127.0.0.1'), // 数据库名 - 'database' => env('database.database', 'buildadmin_com'), + 'database' => env('database.database', 'buildadmin_crud_fork'), // 用户名 'username' => env('database.username', 'root'), // 密码 - 'password' => env('database.password', 'admin888'), + 'password' => env('database.password', 'root'), // 端口 'hostport' => env('database.hostport', '3306'), // 数据库连接参数 diff --git a/web/src/lang/backend/en/crud/crud.ts b/web/src/lang/backend/en/crud/crud.ts index 878ae274..c2cd446e 100644 --- a/web/src/lang/backend/en/crud/crud.ts +++ b/web/src/lang/backend/en/crud/crud.ts @@ -162,9 +162,9 @@ export default { 'Rename failed': 'Rename failed', 'Design remote select tips': 'The name of the cost field is automatically generated from the table name; Confirm that when the field name user_id is generated, the association method generated by the field name user_id is named user, and the association method generated by the field name developer_done_id is named developerDone. Note that the name prefix of the remote drop-down field is not the same', - 'Vite hot warning': - 'Vite Hot Update service not found, please generate code in the development environment, or click the WEB terminal in the upper right corner to republish', - 'Reset generate type attr': - 'The field generation type has been changed. Do you want to reset the field design to the preset scheme for the new type?', - 'Design efficiency': 'Determine design validity by yourself', + 'Vite hot warning': 'Vite hot update service not found, please generate code in development environment, or click WEB terminal in the upper right corner to republish', + 'Reset generate type attr': 'Field generation type has been modified, do you want to reset the field design to the new type preset scheme?', + 'Design efficiency': 'Determine design effectiveness by yourself', + 'Dictionary Type': 'Dictionary Type', + 'Select dictionary type to auto-generate field options': 'Select dictionary type, will automatically set field type to varchar, only supports radio, checkbox, select types', } diff --git a/web/src/lang/backend/zh-cn/crud/crud.ts b/web/src/lang/backend/zh-cn/crud/crud.ts index 72103eb2..b52ca811 100644 --- a/web/src/lang/backend/zh-cn/crud/crud.ts +++ b/web/src/lang/backend/zh-cn/crud/crud.ts @@ -162,4 +162,6 @@ export default { 'Vite hot warning': '未找到 Vite 热更新服务,请在开发环境生成代码,或点击右上角的WEB终端重新发布', 'Reset generate type attr': '字段生成类型已修改,是否将字段设计重置为新类型预设的方案?', 'Design efficiency': '自行确定设计有效性', + 'Dictionary Type': '字典类型', + 'Select dictionary type to auto-generate field options': '选择字典类型,将自动设置字段类型为varchar,仅支持单选框、复选框、下拉框类型', } diff --git a/web/src/stores/interface/index.ts b/web/src/stores/interface/index.ts index 5307183a..2bcff89a 100644 --- a/web/src/stores/interface/index.ts +++ b/web/src/stores/interface/index.ts @@ -201,3 +201,41 @@ export interface SiteConfig { initialize: boolean userInitialize: boolean } + +export interface DictValue { + id: number + entry_code: string + key: string + value: string + value_i18n?: Record + remark: string + weigh: number + status: number + color?: string + create_time: string + update_time: string +} + +export interface DictEntry { + id: number + type_id: number + title: string + code: string + weigh: number + status: number + create_time: string + update_time: string +} + +export interface DictData { + dictCache: Record + allDictData: { + entry: DictEntry + values: DictValue[] + }[] + lastUpdateTime: number + i18n: { + activeTab: string + langList: { name: string; value: string }[] + } +} diff --git a/web/src/views/backend/crud/design.vue b/web/src/views/backend/crud/design.vue index 3d81b9f3..1c011d39 100644 --- a/web/src/views/backend/crud/design.vue +++ b/web/src/views/backend/crud/design.vue @@ -265,6 +265,22 @@ " v-model="state.fields[state.activateField].comment" /> + {{ t('crud.crud.Field Properties') }} { onMounted(() => { loadData() + checkDictPluginInstalled() const sortable = Sortable.create(designWindowRef.value!, { group: 'design-field', animation: 200, filter: '.design-field-empty', - onAdd: (evt: SortableEvt) => { + onAdd: (evt: SortableEvt) => {console.log('state', state); const name = evt.originalEvent?.dataTransfer?.getData('name') const field = fieldItem[name as keyof typeof fieldItem] if (field && field[evt.oldIndex!]) { @@ -1819,6 +1839,90 @@ const getTableDesignTimelineType = (type: TableDesignChangeType): TimelineItemPr } return timeline as TimelineItemProps['type'] } + +/** + * 字典类型变更处理 + */ +const onDictTypeChange = (dictCode: string) => { + const field = state.fields[state.activateField] + + if (!dictCode) { + // 清除字典选择,恢复原有字段类型 + if (field.originalType) { + field.type = field.originalType + if (field.originalDataType) { + field.dataType = field.originalDataType + } else { + delete field.dataType + } + delete field.originalType + delete field.originalDataType + } + // 恢复原有默认值设置 + if (field.originalDefault !== undefined) { + field.default = field.originalDefault + field.defaultType = field.originalDefaultType + delete field.originalDefault + delete field.originalDefaultType + } + field.sysdictType = '' + onFieldAttrChange() + return + } + + // 保存原有字段类型(仅在首次选择字典时保存) + if (!field.originalType) { + field.originalType = field.type + field.originalDataType = field.dataType + // 保存原有默认值设置 + field.originalDefault = field.default + field.originalDefaultType = field.defaultType + } + + // 设置为varchar类型以存储字典键值 + field.type = 'varchar' + field.length = 100 + field.precision = 0 + delete field.dataType + + // 清理默认值设置,字典字段的默认值应该由字典配置决定 + field.default = '' + field.defaultType = 'NONE' + + // 不再自动推断设计类型,保持用户选择的类型 + onFieldAttrChange() +} + +/** + * 检测字典插件是否安装 + */ +const checkDictPluginInstalled = async () => { + try { + // 使用模块状态API检测字典插件是否安装 + const response = await getInstallState('sysdict') + console.log('sysdict install state:', response) + // 检查插件是否已安装且启用 + state.dictPluginInstalled = response.data && response.data.state === 1 + } catch (error) { + console.error('Failed to check dict plugin state:', error) + state.dictPluginInstalled = false + } +} + +/** + * 判断是否应该显示字典选择器 + * 只有在单选框、复选框、下拉框、下拉框多选时才显示 + */ +const shouldShowDictSelector = () => { + if (state.activateField === -1 || !state.fields[state.activateField]) { + return false + } + + const designType = state.fields[state.activateField].designType + const allowedTypes = ['radio', 'checkbox', 'select', 'selects'] + + return allowedTypes.includes(designType) +}