worsoft-frontend-codegen-local-mcp 0.1.69 → 0.1.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,11 +38,11 @@
38
38
 
39
39
  <script setup lang="ts" name="{{CLASS_NAME}}Form">
40
40
  // 标签页关闭总线
41
- import mittBus from '/@/utils/mitt';
42
41
  // 本地会话存储
43
42
  import { Session } from '/@/utils/storage';
44
43
  // 通用消息提示
45
44
  import { useMessage } from '/@/hooks/message';
45
+ import { useCloseCurrentPage } from '/@/hooks/useCloseCurrentPage';
46
46
  // 表单数据接口
47
47
  import { getObj, addObj, putObj } from '/@/api/{{API_MODULE_PATH}}';
48
48
  // 字典数据加载
@@ -51,6 +51,7 @@ import { useDict } from '/@/hooks/dict';
51
51
  import { useCrudPageMeta } from '/@/hooks/useCrudPageMeta';
52
52
  // 国际化能力
53
53
  import { useI18n } from 'vue-i18n';
54
+ {{BUSINESS_FORM_STATUS_IMPORTS}}
54
55
  // 当前页面的字段配置
55
56
  import { allDictTypes, childFieldGroups, dataMasterEntity } from './options';
56
57
 
@@ -65,6 +66,7 @@ const scFormTable = defineAsyncComponent(() => import('/@/components/FormTable/i
65
66
  const route = useRoute();
66
67
  // 路由跳转能力
67
68
  const router = useRouter();
69
+ const { closeCurrentPage } = useCloseCurrentPage();
68
70
  // 当前页面的国际化命名空间
69
71
  const pageI18nKey = '{{I18N_NAMESPACE}}';
70
72
 
@@ -146,14 +148,9 @@ const initPage = async () => {
146
148
  }
147
149
  };
148
150
 
149
- // 关闭当前标签页
150
- const closeCurrentPage = () => {
151
- mittBus.emit('onCurrentContextmenuClick', { contextMenuClickId: 1, ...route });
152
- };
153
-
154
151
  // 返回上一页
155
152
  const handleBack = () => {
156
- router.back();
153
+ closeCurrentPage();
157
154
  };
158
155
 
159
156
  // 保存、提交和流转共用同一套提交逻辑
@@ -169,7 +166,8 @@ const onSubmit = async (actionType?: string) => {
169
166
  }
170
167
 
171
168
  try {
172
- form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
169
+ const submitData = Object.assign({}, form{{BUSINESS_SUBMIT_STATUS_ASSIGNMENT}});
170
+ form.{{PK_ATTR}} ? await putObj(submitData) : await addObj(submitData);
173
171
 
174
172
  let msg = form.{{PK_ATTR}} ? t('common.editSuccessText') : t('common.addSuccessText');
175
173
  if (actionType === 'submit') msg = t('common.messages.quickSubmitSuccess');
@@ -37,7 +37,7 @@
37
37
  <!-- 流转{{FEATURE_TITLE}} -->
38
38
  <el-button text type="success" icon="position" @click="handleQuickAction(row, 'flow')">{{ commonActionLabel('flow') }}</el-button>
39
39
  <!-- 删除{{FEATURE_TITLE}} -->
40
- <el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([row.{{PK_ATTR}}])">{{ t('common.delBtn') }}</el-button>
40
+ <el-button{{BUSINESS_DELETE_IF}} icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([row.{{PK_ATTR}}]{{BUSINESS_DELETE_ROW_ARG}})">{{ t('common.delBtn') }}</el-button>
41
41
  </template>
42
42
  </SchemaListTable>
43
43
  </div>
@@ -80,6 +80,7 @@ const router = useRouter();
80
80
  const excelUploadRef = ref();
81
81
  // 列表勾选主键集合
82
82
  const selectObjs = ref<string[]>([]);
83
+ {{BUSINESS_SELECTED_ROWS_DECLARATION}}
83
84
  // 批量删除按钮状态
84
85
  const multiple = ref(true);
85
86
 
@@ -120,7 +121,7 @@ const toolbarProps = computed(() => ({
120
121
  queryModel: state.queryForm,
121
122
  customQueryFields: queryableDictOptions.value,
122
123
  selectedIds: selectObjs.value,
123
- deleteDisabled: multiple.value,
124
+ deleteDisabled: multiple.value{{BUSINESS_DELETE_DISABLED_EXPR}},
124
125
  exportPermission: '{{PERMISSION_PREFIX}}_export',
125
126
  }));
126
127
 
@@ -195,7 +196,8 @@ const resetQuery = () => {
195
196
  };
196
197
 
197
198
  // 删除列表数据
198
- const handleDelete = async (ids: string[]) => {
199
+ const handleDelete = async (ids: string[]{{BUSINESS_DELETE_ROWS_PARAM}}) => {
200
+ {{BUSINESS_DELETE_GUARD}}
199
201
  try {
200
202
  await useMessageBox().confirm(t('common.delConfirmText'));
201
203
  } catch {
@@ -223,7 +225,7 @@ const handleToolbarImport = () => {
223
225
 
224
226
  // 工具栏批量删除事件
225
227
  const handleToolbarDelete = () => {
226
- handleDelete(selectObjs.value);
228
+ handleDelete(selectObjs.value{{BUSINESS_SELECTED_ROWS_ARG}});
227
229
  };
228
230
 
229
231
  // 工具栏查询事件
@@ -252,7 +254,8 @@ const handleToolbarRefresh = () => {
252
254
  };
253
255
 
254
256
  // 表格勾选变化事件
255
- const handleTableSelectionChange = (payload: { ids: Array<string | number> }) => {
257
+ const handleTableSelectionChange = (payload: { rows: any[]; ids: Array<string | number> }) => {
258
+ {{BUSINESS_SELECTED_ROWS_ASSIGNMENT}}
256
259
  selectObjs.value = payload.ids.map((id) => String(id));
257
260
  multiple.value = !payload.ids.length;
258
261
  };
@@ -34,11 +34,11 @@
34
34
 
35
35
  <script setup lang="ts" name="{{CLASS_NAME}}Form">
36
36
  // 标签页关闭总线
37
- import mittBus from '/@/utils/mitt';
38
37
  // 本地会话存储
39
38
  import { Session } from '/@/utils/storage';
40
39
  // 通用消息提示
41
40
  import { useMessage } from '/@/hooks/message';
41
+ import { useCloseCurrentPage } from '/@/hooks/useCloseCurrentPage';
42
42
  // 表单数据接口
43
43
  import { getObj, addObj, putObj } from '/@/api/{{API_MODULE_PATH}}';
44
44
  // 字典数据加载
@@ -47,6 +47,7 @@ import { useDict } from '/@/hooks/dict';
47
47
  import { useCrudPageMeta } from '/@/hooks/useCrudPageMeta';
48
48
  // 国际化能力
49
49
  import { useI18n } from 'vue-i18n';
50
+ {{BUSINESS_FORM_STATUS_IMPORTS}}
50
51
  // 当前页面的字段配置
51
52
  import { allDictTypes, dataMasterEntity } from './options';
52
53
 
@@ -58,6 +59,7 @@ const { t } = useI18n();
58
59
  const route = useRoute();
59
60
  // 路由跳转能力
60
61
  const router = useRouter();
62
+ const { closeCurrentPage } = useCloseCurrentPage();
61
63
 
62
64
  // 表单引用
63
65
  const dataFormRef = ref();
@@ -128,14 +130,9 @@ const initPage = async () => {
128
130
  }
129
131
  };
130
132
 
131
- // 关闭当前标签页
132
- const closeCurrentPage = () => {
133
- mittBus.emit('onCurrentContextmenuClick', { contextMenuClickId: 1, ...route });
134
- };
135
-
136
133
  // 返回上一页
137
134
  const handleBack = () => {
138
- router.back();
135
+ closeCurrentPage();
139
136
  };
140
137
 
141
138
  // 保存、提交和流转共用同一套提交逻辑
@@ -151,7 +148,8 @@ const onSubmit = async (actionType?: string) => {
151
148
  }
152
149
 
153
150
  try {
154
- form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
151
+ const submitData = Object.assign({}, form{{BUSINESS_SUBMIT_STATUS_ASSIGNMENT}});
152
+ form.{{PK_ATTR}} ? await putObj(submitData) : await addObj(submitData);
155
153
 
156
154
  let msg = form.{{PK_ATTR}} ? t('common.editSuccessText') : t('common.addSuccessText');
157
155
  if (actionType === 'submit') msg = t('common.messages.quickSubmitSuccess');
@@ -34,7 +34,7 @@
34
34
  <!-- 编辑{{FEATURE_TITLE}}:业务单据编辑态控制按需生效 -->
35
35
  <el-button{{BUSINESS_EDIT_IF}} icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="handleEdit(row.{{PK_ATTR}})">{{ t('common.editBtn') }}</el-button>
36
36
  <!-- 删除{{FEATURE_TITLE}} -->
37
- <el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([row.{{PK_ATTR}}])">{{ t('common.delBtn') }}</el-button>
37
+ <el-button{{BUSINESS_DELETE_IF}} icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([row.{{PK_ATTR}}]{{BUSINESS_DELETE_ROW_ARG}})">{{ t('common.delBtn') }}</el-button>
38
38
  </template>
39
39
  </SchemaListTable>
40
40
  </div>
@@ -77,6 +77,7 @@ const router = useRouter();
77
77
  const excelUploadRef = ref();
78
78
  // 列表勾选主键集合
79
79
  const selectObjs = ref<string[]>([]);
80
+ {{BUSINESS_SELECTED_ROWS_DECLARATION}}
80
81
  // 批量删除按钮状态
81
82
  const multiple = ref(true);
82
83
 
@@ -117,7 +118,7 @@ const toolbarProps = computed(() => ({
117
118
  queryModel: state.queryForm,
118
119
  customQueryFields: queryableDictOptions.value,
119
120
  selectedIds: selectObjs.value,
120
- deleteDisabled: multiple.value,
121
+ deleteDisabled: multiple.value{{BUSINESS_DELETE_DISABLED_EXPR}},
121
122
  addPermission: '{{PERMISSION_PREFIX}}_add',
122
123
  importPermission: '{{PERMISSION_PREFIX}}_add',
123
124
  deletePermission: '{{PERMISSION_PREFIX}}_del',
@@ -179,7 +180,8 @@ const resetQuery = () => {
179
180
  };
180
181
 
181
182
  // 删除列表数据
182
- const handleDelete = async (ids: string[]) => {
183
+ const handleDelete = async (ids: string[]{{BUSINESS_DELETE_ROWS_PARAM}}) => {
184
+ {{BUSINESS_DELETE_GUARD}}
183
185
  try {
184
186
  await useMessageBox().confirm(t('common.delConfirmText'));
185
187
  } catch {
@@ -207,7 +209,7 @@ const handleToolbarImport = () => {
207
209
 
208
210
  // 工具栏批量删除事件
209
211
  const handleToolbarDelete = () => {
210
- handleDelete(selectObjs.value);
212
+ handleDelete(selectObjs.value{{BUSINESS_SELECTED_ROWS_ARG}});
211
213
  };
212
214
 
213
215
  // 工具栏查询事件
@@ -236,7 +238,8 @@ const handleToolbarRefresh = () => {
236
238
  };
237
239
 
238
240
  // 表格勾选变化事件
239
- const handleTableSelectionChange = (payload: { ids: Array<string | number> }) => {
241
+ const handleTableSelectionChange = (payload: { rows: any[]; ids: Array<string | number> }) => {
242
+ {{BUSINESS_SELECTED_ROWS_ASSIGNMENT}}
240
243
  selectObjs.value = payload.ids.map((id) => String(id));
241
244
  multiple.value = !payload.ids.length;
242
245
  };
package/mcp_server.js CHANGED
@@ -5,7 +5,7 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
7
  const SERVER_NAME = 'worsoft-codegen-local';
8
- const SERVER_VERSION = '0.1.69';
8
+ const SERVER_VERSION = '0.1.71';
9
9
  const PROTOCOL_VERSION = '2024-11-05';
10
10
  const TOOL_NAME = 'worsoft_codegen_local_generate_frontend';
11
11
  const STYLE_CATALOG_PATH = path.join(__dirname, 'assets', 'style-catalog.json');
@@ -204,6 +204,48 @@ export const createCrudApi = (baseUrl: string, overrides: CrudApiPathOverrides =
204
204
  });
205
205
  `;
206
206
 
207
+ const DEFAULT_CLOSE_CURRENT_PAGE_TEMPLATE = `import type { RouteLocationNormalizedLoaded } from 'vue-router';
208
+ import { useRoute } from 'vue-router';
209
+ import mittBus from '/@/utils/mitt';
210
+
211
+ export const closeCurrentRoutePage = (route: RouteLocationNormalizedLoaded) => {
212
+ \tmittBus.emit('onCurrentContextmenuClick', {
213
+ \t\tcontextMenuClickId: 1,
214
+ \t\tpath: route.path,
215
+ \t\tquery: { ...route.query },
216
+ \t\tparams: { ...route.params },
217
+ \t\tmeta: { ...route.meta },
218
+ \t\tname: route.name,
219
+ \t});
220
+ };
221
+
222
+ export function useCloseCurrentPage() {
223
+ \tconst route = useRoute();
224
+
225
+ \tconst closeCurrentPage = () => {
226
+ \t\tcloseCurrentRoutePage(route);
227
+ \t};
228
+
229
+ \treturn {
230
+ \t\tcloseCurrentPage,
231
+ \t};
232
+ }
233
+ `;
234
+
235
+ const DEFAULT_DICT_SEMANTIC_TEMPLATE = `// This file is hand-maintained. Do NOT overwrite during dict-registry sync.
236
+ // Add semantic keys here for dictionaries that have meaningful state values.
237
+
238
+ export const DictSemanticValues = {
239
+ billState: {
240
+ editing: '0',
241
+ processing: '1',
242
+ paused: '2',
243
+ terminated: '3',
244
+ completed: '4',
245
+ },
246
+ } as const;
247
+ `;
248
+
207
249
  const TOOL_SCHEMA = {
208
250
  type: 'object',
209
251
  properties: {
@@ -562,22 +604,9 @@ function renderDictRegistryContent(entries) {
562
604
  '',
563
605
  'export type DictRegistryKey = keyof typeof DictRegistry;',
564
606
  'export type DictType = (typeof DictRegistry)[DictRegistryKey];',
565
- '',
566
- 'export const DictSemanticValues = {',
567
- ' billState: {',
568
- " editing: '0',",
569
- " processing: '1',",
570
- " paused: '2',",
571
- " terminated: '3',",
572
- " completed: '4',",
573
- ' },',
574
- '} as const;',
575
- '',
576
- 'export const isBillStateEditing = (value: unknown): boolean =>',
577
- " String(value ?? '') === DictSemanticValues.billState.editing;",
578
- '',
579
- ].join('\n');
580
- }
607
+ '',
608
+ ].join('\n');
609
+ }
581
610
 
582
611
  function patchDictRegistryContent(fileContent, entriesToAdd) {
583
612
  const source = String(fileContent || '');
@@ -598,28 +627,9 @@ function patchDictRegistryContent(fileContent, entriesToAdd) {
598
627
  return source.replace(block, nextBlock);
599
628
  }
600
629
 
601
- const DICT_REGISTRY_SEMANTIC_HELPERS = [
602
- '',
603
- 'export const DictSemanticValues = {',
604
- ' billState: {',
605
- " editing: '0',",
606
- " processing: '1',",
607
- " paused: '2',",
608
- " terminated: '3',",
609
- " completed: '4',",
610
- ' },',
611
- '} as const;',
612
- '',
613
- 'export const isBillStateEditing = (value: unknown): boolean =>',
614
- " String(value ?? '') === DictSemanticValues.billState.editing;",
615
- '',
616
- ].join('\n');
617
-
618
- function ensureDictRegistrySemanticHelpers(fileContent) {
619
- const source = String(fileContent || '').replace(/\s*$/, '\n');
620
- if (source.includes('export const isBillStateEditing')) return source;
621
- return `${source}${DICT_REGISTRY_SEMANTIC_HELPERS}`;
622
- }
630
+ function ensureDictRegistrySemanticHelpers(fileContent) {
631
+ return String(fileContent || '').replace(/\s*$/, '\n');
632
+ }
623
633
 
624
634
  function isPlainObject(value) {
625
635
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
@@ -779,12 +789,18 @@ function buildLocaleLeaf(model) {
779
789
  };
780
790
  }
781
791
 
782
- const leaf = {
783
- title: model.tableComment,
784
- fields: Object.fromEntries(model.optionFields.map((field) => [field.attrName, stripDictAnnotation(field.comment)])),
785
- };
786
-
787
- if (model.children.length) {
792
+ const leaf = {
793
+ title: model.tableComment,
794
+ fields: Object.fromEntries(model.optionFields.map((field) => [field.attrName, stripDictAnnotation(field.comment)])),
795
+ };
796
+
797
+ if (hasBusinessBillStateEditControl(model)) {
798
+ leaf.messages = {
799
+ completedCannotDelete: '单据状态为已完成的数据不能删除',
800
+ };
801
+ }
802
+
803
+ if (model.children.length) {
788
804
  leaf.children = Object.fromEntries(
789
805
  model.children.map((childModel) => [
790
806
  childModel.listName,
@@ -925,9 +941,39 @@ function ensureCrudFactorySupportFile(frontendPath) {
925
941
  };
926
942
  }
927
943
 
928
- function ensureDirectory(filePath) {
929
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
930
- }
944
+ function ensureCloseCurrentPageSupportFile(frontendPath) {
945
+ const hookPath = path.join(frontendPath, 'src', 'hooks', 'useCloseCurrentPage.ts');
946
+ const exists = fs.existsSync(hookPath);
947
+ const currentContent = exists ? readUtf8File(hookPath) : '';
948
+ const isCompatible = !exists || currentContent.includes('export function useCloseCurrentPage');
949
+
950
+ return {
951
+ path: hookPath,
952
+ content: DEFAULT_CLOSE_CURRENT_PAGE_TEMPLATE,
953
+ exists,
954
+ isCompatible,
955
+ needsWrite: !exists,
956
+ };
957
+ }
958
+
959
+ function ensureDictSemanticSupportFile(frontendPath) {
960
+ const semanticPath = path.join(frontendPath, 'src', 'enums', 'dict-semantic.ts');
961
+ const exists = fs.existsSync(semanticPath);
962
+ const currentContent = exists ? readUtf8File(semanticPath) : '';
963
+ const isCompatible = !exists || currentContent.includes('export const DictSemanticValues');
964
+
965
+ return {
966
+ path: semanticPath,
967
+ content: DEFAULT_DICT_SEMANTIC_TEMPLATE,
968
+ exists,
969
+ isCompatible,
970
+ needsWrite: !exists,
971
+ };
972
+ }
973
+
974
+ function ensureDirectory(filePath) {
975
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
976
+ }
931
977
 
932
978
  function writeSupportFile(filePath, content) {
933
979
  ensureDirectory(filePath);
@@ -971,13 +1017,37 @@ function prepareSharedSupport(frontendPath, dictTypes, writeSupportFiles) {
971
1017
  needsWrite: false,
972
1018
  writeEnabled: false,
973
1019
  };
974
- return {
975
- dictRegistry,
976
- crudSchema,
977
- crudFactory,
978
- writeEnabled: Boolean(writeSupportFiles),
979
- };
980
- }
1020
+ const closeCurrentPagePath = path.join(frontendPath, 'src', 'hooks', 'useCloseCurrentPage.ts');
1021
+ const closeCurrentPage = writeSupportFiles
1022
+ ? ensureCloseCurrentPageSupportFile(frontendPath)
1023
+ : {
1024
+ path: closeCurrentPagePath,
1025
+ content: DEFAULT_CLOSE_CURRENT_PAGE_TEMPLATE,
1026
+ exists: fs.existsSync(closeCurrentPagePath),
1027
+ isCompatible: true,
1028
+ needsWrite: false,
1029
+ writeEnabled: false,
1030
+ };
1031
+ const dictSemanticPath = path.join(frontendPath, 'src', 'enums', 'dict-semantic.ts');
1032
+ const dictSemantic = writeSupportFiles
1033
+ ? ensureDictSemanticSupportFile(frontendPath)
1034
+ : {
1035
+ path: dictSemanticPath,
1036
+ content: DEFAULT_DICT_SEMANTIC_TEMPLATE,
1037
+ exists: fs.existsSync(dictSemanticPath),
1038
+ isCompatible: true,
1039
+ needsWrite: false,
1040
+ writeEnabled: false,
1041
+ };
1042
+ return {
1043
+ dictRegistry,
1044
+ crudSchema,
1045
+ crudFactory,
1046
+ closeCurrentPage,
1047
+ dictSemantic,
1048
+ writeEnabled: Boolean(writeSupportFiles),
1049
+ };
1050
+ }
981
1051
 
982
1052
  function maybeWriteSharedSupport(sharedSupport, writeToDisk) {
983
1053
  if (!writeToDisk || !sharedSupport.writeEnabled) return;
@@ -1005,16 +1075,36 @@ function maybeWriteSharedSupport(sharedSupport, writeToDisk) {
1005
1075
  }
1006
1076
  writeSupportFile(sharedSupport.dictRegistry.path, sharedSupport.dictRegistry.content);
1007
1077
  }
1008
- }
1078
+
1079
+ if (sharedSupport.closeCurrentPage.needsWrite) {
1080
+ if (!sharedSupport.closeCurrentPage.isCompatible) {
1081
+ throw new Error(
1082
+ 'Detected an existing src/hooks/useCloseCurrentPage.ts that MCP could not merge safely. ' +
1083
+ 'Please align the useCloseCurrentPage export manually before enabling writeSupportFiles.'
1084
+ );
1085
+ }
1086
+ writeSupportFile(sharedSupport.closeCurrentPage.path, sharedSupport.closeCurrentPage.content);
1087
+ }
1088
+
1089
+ if (sharedSupport.dictSemantic.needsWrite) {
1090
+ if (!sharedSupport.dictSemantic.isCompatible) {
1091
+ throw new Error(
1092
+ 'Detected an existing src/enums/dict-semantic.ts that MCP could not merge safely. ' +
1093
+ 'Please align the DictSemanticValues export manually before enabling writeSupportFiles.'
1094
+ );
1095
+ }
1096
+ writeSupportFile(sharedSupport.dictSemantic.path, sharedSupport.dictSemantic.content);
1097
+ }
1098
+ }
1009
1099
 
1010
1100
  function buildSupportNote(sharedSupport, localeZhSupport) {
1011
1101
  const notes = [];
1012
1102
 
1013
- if (!sharedSupport.writeEnabled) {
1014
- notes.push(
1015
- 'Shared support file writing is disabled. Generated code still references src/utils/crudSchema.ts, src/api/common/crudFactory.ts and may reference src/enums/dict-registry.ts, so those helpers must already exist in the target project.'
1016
- );
1017
- }
1103
+ if (!sharedSupport.writeEnabled) {
1104
+ notes.push(
1105
+ 'Shared support file writing is disabled. Generated code still references src/utils/crudSchema.ts, src/api/common/crudFactory.ts, src/hooks/useCloseCurrentPage.ts, src/enums/dict-semantic.ts and may reference src/enums/dict-registry.ts, so those helpers must already exist in the target project.'
1106
+ );
1107
+ }
1018
1108
 
1019
1109
  if (sharedSupport.crudSchema.exists && !sharedSupport.crudSchema.isCompatible) {
1020
1110
  notes.push(
@@ -1030,12 +1120,19 @@ function buildSupportNote(sharedSupport, localeZhSupport) {
1030
1120
  );
1031
1121
  }
1032
1122
 
1033
- if (sharedSupport.dictRegistry.exists && !sharedSupport.dictRegistry.isCompatible) {
1034
- notes.push(
1035
- 'Detected an existing src/enums/dict-registry.ts that MCP could not merge safely. ' +
1036
- 'MCP preserved the existing file and did not overwrite it.'
1037
- );
1038
- }
1123
+ if (sharedSupport.dictRegistry.exists && !sharedSupport.dictRegistry.isCompatible) {
1124
+ notes.push(
1125
+ 'Detected an existing src/enums/dict-registry.ts that MCP could not merge safely. ' +
1126
+ 'MCP preserved the existing file and did not overwrite it.'
1127
+ );
1128
+ }
1129
+
1130
+ if (sharedSupport.dictSemantic.exists && !sharedSupport.dictSemantic.isCompatible) {
1131
+ notes.push(
1132
+ 'Detected an existing src/enums/dict-semantic.ts that does not expose DictSemanticValues. ' +
1133
+ 'MCP preserved the existing file and did not overwrite it.'
1134
+ );
1135
+ }
1039
1136
 
1040
1137
  if (localeZhSupport.exists && !localeZhSupport.isCompatible) {
1041
1138
  notes.push(
@@ -1306,7 +1403,7 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1306
1403
  throw new Error(contextLabel + '[' + index + '] is missing required field: type');
1307
1404
  }
1308
1405
 
1309
- const { length, scale } = normalizeStructuredLengthAndScale(inputField.length, inputField.scale);
1406
+ const { length, scale } = normalizeStructuredLengthAndScale(inputField.length ?? inputField.maxLength, inputField.scale);
1310
1407
  const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
1311
1408
  const formType = type === 'DATE' && explicitFormType === 'datetime' ? 'date' : explicitFormType;
1312
1409
  const explicitQueryType =
@@ -1968,9 +2065,13 @@ function renderChildTableColumn(field, childListName) {
1968
2065
  const selectPlaceholderExpr = `formSelectPlaceholder(${labelExpr}, ${renderDisabledBoolV2(field)})`;
1969
2066
  const disabledAttr = renderDisabledAttrV2(field);
1970
2067
 
1971
- let control = ` <el-input v-model="row.${field.attrName}" :placeholder="${inputPlaceholderExpr}"${disabledAttr} />`;
2068
+ let control = ` <el-input v-model="row.${field.attrName}"${renderTextMaxlengthAttrV2(field)} :placeholder="${inputPlaceholderExpr}"${disabledAttr} />`;
1972
2069
  if (field.formType === 'upload') {
1973
2070
  control = ` <UploadFile v-model="row.${field.attrName}"${disabledAttr} />`;
2071
+ } else if (field.formType === 'datetime' || field.formType === 'date') {
2072
+ const pickerType = field.formType === 'datetime' ? 'datetime' : 'date';
2073
+ const formatName = field.formType === 'datetime' ? 'dateTimeStr' : 'dateStr';
2074
+ control = ` <el-date-picker type="${pickerType}" :placeholder="${inputPlaceholderExpr}" v-model="row.${field.attrName}" :value-format="${formatName}" style="width: 100%"${disabledAttr}></el-date-picker>`;
1974
2075
  } else if (field.formType === 'select' && field.dictType) {
1975
2076
  control = [
1976
2077
  ` <el-select v-model="row.${field.attrName}" :placeholder="${selectPlaceholderExpr}" style="width: 100%"${disabledAttr}>`,
@@ -2116,7 +2217,7 @@ function renderOptionFieldV2(field, labelKey, dictRegistryRefs, indent = ' ')
2116
2217
  const prdWidth = field.width ? field.width.replace('px', '') : null;
2117
2218
  const width = prdWidth || getDefaultOptionFieldWidthV2(field);
2118
2219
 
2119
- if (width !== '120') {
2220
+ if (width !== '100') {
2120
2221
  parts.push(`width: '${width}'`);
2121
2222
  }
2122
2223
 
@@ -3234,24 +3335,45 @@ function renderSingleTableDialogDictApiFunctions(model) {
3234
3335
  ].join('\n');
3235
3336
  }
3236
3337
 
3237
- function hasBusinessBillStateEditControl(model) {
3238
- return model.pageType === 'business' && !!model.statusField && model.statusDictType === 'bill_state';
3239
- }
3240
-
3241
- function renderBusinessStatusImports(model) {
3242
- return hasBusinessBillStateEditControl(model)
3243
- ? "// 业务单据编辑态判断\nimport { isBillStateEditing } from '/@/enums/dict-registry';"
3244
- : '';
3245
- }
3246
-
3247
- function renderBusinessStatusHelpers(model) {
3248
- if (!hasBusinessBillStateEditControl(model)) return '';
3249
- const statusAttr = toCamelCase(model.statusField);
3250
- return [
3251
- '// 控制业务单据编辑按钮显隐',
3252
- `const showEditAction = (row: any): boolean => isBillStateEditing(row?.${statusAttr});`,
3253
- ].join('\n');
3254
- }
3338
+ function hasBusinessBillStateEditControl(model) {
3339
+ return model.pageType === 'business' && !!model.statusField && model.statusDictType === 'bill_state';
3340
+ }
3341
+
3342
+ function renderBusinessStatusImports(model) {
3343
+ return hasBusinessBillStateEditControl(model)
3344
+ ? "// 业务单据状态枚举\nimport { DictSemanticValues } from '/@/enums/dict-semantic';"
3345
+ : '';
3346
+ }
3347
+
3348
+ function renderBusinessStatusHelpers(model) {
3349
+ if (!hasBusinessBillStateEditControl(model)) return '';
3350
+ const statusAttr = toCamelCase(model.statusField);
3351
+ return [
3352
+ '// 控制业务单据编辑按钮显隐:仅编辑中允许编辑',
3353
+ `const showEditAction = (row: any): boolean => String(row?.${statusAttr} ?? '') === DictSemanticValues.billState.editing;`,
3354
+ '',
3355
+ '// 控制业务单据删除按钮显隐:已完成不允许删除',
3356
+ `const showDeleteAction = (row: any): boolean => String(row?.${statusAttr} ?? '') !== DictSemanticValues.billState.completed;`,
3357
+ ].join('\n');
3358
+ }
3359
+
3360
+ function renderBusinessDeleteGuard(model) {
3361
+ if (!hasBusinessBillStateEditControl(model)) return '';
3362
+ const i18nNamespace = buildI18nNamespace(model);
3363
+ return [
3364
+ ' if (rows.some((row) => !showDeleteAction(row))) {',
3365
+ ` useMessage().warning(t('${i18nNamespace}.messages.completedCannotDelete'));`,
3366
+ ' return;',
3367
+ ' }',
3368
+ '',
3369
+ ].join('\n');
3370
+ }
3371
+
3372
+ function renderBusinessSubmitStatusAssignment(model) {
3373
+ if (!hasBusinessBillStateEditControl(model)) return '';
3374
+ const statusAttr = toCamelCase(model.statusField);
3375
+ return `, actionType === 'submit' ? { ${statusAttr}: DictSemanticValues.billState.completed } : {}`;
3376
+ }
3255
3377
 
3256
3378
  function sanitizeComment(value) {
3257
3379
  return String(value || '').replace(/\*\//g, '* /').replace(/\r?\n/g, ' ').trim();
@@ -3348,11 +3470,21 @@ function buildReplacements(model, sharedSupport) {
3348
3470
  LIST_ACTIONS: renderSingleTableDialogActions(model, permissionPrefix),
3349
3471
  DICT_API_IMPORTS: model.pageType === 'dict' && model.statusField ? ', enableObj, disableObj' : '',
3350
3472
  DICT_LIST_HELPERS: renderSingleTableDialogDictHelpers(model),
3351
- DICT_API_FUNCTIONS: renderSingleTableDialogDictApiFunctions(model),
3352
- BUSINESS_STATUS_IMPORTS: renderBusinessStatusImports(model),
3353
- BUSINESS_EDIT_IF: hasBusinessBillStateEditControl(model) ? ' v-if="showEditAction(row)"' : '',
3354
- BUSINESS_STATUS_HELPERS: renderBusinessStatusHelpers(model),
3355
- API_REQUEST_IMPORT: renderApiRequestImport(model),
3473
+ DICT_API_FUNCTIONS: renderSingleTableDialogDictApiFunctions(model),
3474
+ BUSINESS_STATUS_IMPORTS: renderBusinessStatusImports(model),
3475
+ BUSINESS_FORM_STATUS_IMPORTS: renderBusinessStatusImports(model),
3476
+ BUSINESS_EDIT_IF: hasBusinessBillStateEditControl(model) ? ' v-if="showEditAction(row)"' : '',
3477
+ BUSINESS_DELETE_IF: hasBusinessBillStateEditControl(model) ? ' v-if="showDeleteAction(row)"' : '',
3478
+ BUSINESS_DELETE_ROW_ARG: hasBusinessBillStateEditControl(model) ? ', [row]' : '',
3479
+ BUSINESS_SELECTED_ROWS_DECLARATION: hasBusinessBillStateEditControl(model) ? 'const selectedRows = ref<any[]>([]);' : '',
3480
+ BUSINESS_DELETE_DISABLED_EXPR: hasBusinessBillStateEditControl(model) ? ' || selectedRows.value.some((row) => !showDeleteAction(row))' : '',
3481
+ BUSINESS_DELETE_ROWS_PARAM: hasBusinessBillStateEditControl(model) ? ', rows: any[] = []' : '',
3482
+ BUSINESS_DELETE_GUARD: renderBusinessDeleteGuard(model),
3483
+ BUSINESS_SELECTED_ROWS_ARG: hasBusinessBillStateEditControl(model) ? ', selectedRows.value' : '',
3484
+ BUSINESS_SELECTED_ROWS_ASSIGNMENT: hasBusinessBillStateEditControl(model) ? ' selectedRows.value = payload.rows;' : '',
3485
+ BUSINESS_STATUS_HELPERS: renderBusinessStatusHelpers(model),
3486
+ BUSINESS_SUBMIT_STATUS_ASSIGNMENT: renderBusinessSubmitStatusAssignment(model),
3487
+ API_REQUEST_IMPORT: renderApiRequestImport(model),
3356
3488
  EXTRA_API_FUNCTIONS: renderExtraApiFunctionsV2(model),
3357
3489
  CUSTOM_QUERY_FIELDS_EXPR: model.pageType === 'dict' ? '[]' : 'queryableDictOptions.value',
3358
3490
  SHOW_RIGHT_TOOLS: model.pageType === 'dict' ? 'false' : 'true',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",