worsoft-frontend-codegen-local-mcp 0.1.85 → 0.1.89

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.
package/README.md CHANGED
@@ -15,6 +15,18 @@ This MCP generates Worsoft frontend files from structured JSON metadata.
15
15
 
16
16
  ## Current Inputs
17
17
 
18
+ Preferred input:
19
+
20
+ - `parseResultPath`
21
+ - `writeToDisk`
22
+ - `overwrite`
23
+ - `writeSupportFiles`
24
+ - `mergeI18nZh`
25
+
26
+ When `parseResultPath` is provided, MCP reads `parseResult.json` locally and uses `parseResult.mcpPayload` as the generation input. The caller should not expand `mcpPayload` in the model context.
27
+
28
+ Legacy-compatible low-level inputs:
29
+
18
30
  - `featureTitle`
19
31
  - `tableName`
20
32
  - `tableComment`
@@ -35,7 +47,28 @@ If the caller accidentally passes the `src` directory, MCP will normalize it bac
35
47
 
36
48
  ## Recommended MCP Arguments
37
49
 
38
- Single table:
50
+ Preferred:
51
+
52
+ ```json
53
+ {
54
+ "parseResultPath": "E:/own-worker-platform/trunk/docs/03-modules/.../parseResult.json",
55
+ "writeToDisk": true,
56
+ "overwrite": true
57
+ }
58
+ ```
59
+
60
+ If `parseResult.mcpPayload` does not contain `frontendPath`, pass it as a non-semantic runtime argument:
61
+
62
+ ```json
63
+ {
64
+ "parseResultPath": "E:/own-worker-platform/trunk/docs/03-modules/.../parseResult.json",
65
+ "frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
66
+ "writeToDisk": true,
67
+ "overwrite": true
68
+ }
69
+ ```
70
+
71
+ Legacy low-level single table:
39
72
 
40
73
  ```json
41
74
  {
@@ -56,7 +89,7 @@ Single table:
56
89
  }
57
90
  ```
58
91
 
59
- Master-child:
92
+ Legacy low-level master-child:
60
93
 
61
94
  ```json
62
95
  {
@@ -105,6 +138,7 @@ Node:
105
138
 
106
139
  ```bash
107
140
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single
141
+ node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario parse_result_path
108
142
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single_dialog
109
143
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario dict_single
110
144
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario master
@@ -134,9 +168,9 @@ Optional overrides:
134
168
  - `mergeI18nZh=false`:
135
169
  MCP renders `zh-cn.ts` from current metadata only and does not parse the existing file
136
170
 
137
- ## Master-Child Contract
171
+ ## Legacy Low-Level Master-Child Contract
138
172
 
139
- For `master_child_jump`, MCP does not infer relations.
173
+ For low-level `master_child_jump` calls, MCP does not infer relations. `parseResultPath` callers should let `doc-parse` populate `parseResult.mcpPayload.children`.
140
174
 
141
175
  The caller must provide explicit `children[]`, and each child must include:
142
176
 
@@ -149,9 +183,9 @@ The caller must provide explicit `children[]`, and each child must include:
149
183
 
150
184
  If these fields are missing, MCP returns a structured validation error.
151
185
 
152
- ## Multi-Level Dictionary Contract
186
+ ## Legacy Low-Level Multi-Level Dictionary Contract
153
187
 
154
- For `multi_level_dict`, MCP does not infer hierarchy.
188
+ For low-level `multi_level_dict`, MCP does not infer hierarchy. `parseResultPath` callers should let `doc-parse` populate `parseResult.mcpPayload.levels`.
155
189
 
156
190
  The caller must provide explicit `levels[]`. Each level must include:
157
191
 
@@ -182,14 +216,7 @@ When the same physical table is split into different logical regions, pass `modu
182
216
  - Human guide: `STYLE_TEMPLATE_GUIDE.md`
183
217
  - Machine source: `assets/style-catalog.json`
184
218
 
185
- Declared styles:
186
-
187
- - `single_table_jump`
188
- - `single_table_dialog`
189
- - `master_child_jump`
190
- - `multi_level_dict`
191
- - `single_tree_table`
192
- - `tree_left_list`
219
+ Declared styles are defined by `assets/style-catalog.json`; README intentionally does not duplicate the full enum as a second source.
193
220
 
194
221
  ## Notes
195
222
 
@@ -2,6 +2,6 @@
2
2
  import { createCrudApi } from '/@/api/common/crudFactory';
3
3
  {{API_REQUEST_IMPORT}}
4
4
 
5
- // {{FEATURE_TITLE}}主子表台账只读接口:分页查询、详情查询
6
- export const { fetchList, getObj } = createCrudApi('/{{API_PATH}}');
5
+ // {{FEATURE_TITLE}}主子表台账只读接口:分页查询、详情查询、导出
6
+ export const { fetchList, getObj, exportObj } = createCrudApi('/{{API_PATH}}');
7
7
  {{EXTRA_API_FUNCTIONS}}
@@ -36,7 +36,7 @@
36
36
 
37
37
  <script setup lang="ts" name="system{{CLASS_NAME}}">
38
38
  // 列表接口:台账页只消费分页查询能力
39
- import { fetchList } from '/@/api/{{API_MODULE_PATH}}';
39
+ import { fetchList, exportObj } from '/@/api/{{API_MODULE_PATH}}';
40
40
  // 字典数据加载
41
41
  import { useDict } from '/@/hooks/dict';
42
42
  // 列表页字段元数据能力
@@ -66,8 +66,9 @@ const router = useRouter();
66
66
  const { state, getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle, exportExcel: runExportExcel, resetQueryForm } = useSchemaListQuery({
67
67
  schema: crudSchema,
68
68
  pageList: fetchList,
69
- exportUrl: '/{{API_PATH}}/export',
70
- exportFileName: '{{FUNCTION_NAME}}.xlsx',
69
+ exportApi: exportObj,
70
+ exportFileName: '{{FEATURE_TITLE}}.xlsx',
71
+ exportColumns: () => tableColumns.value,
71
72
  });
72
73
 
73
74
  // 统一处理台账列表加载后的首行选中,以及刷新后的当前行保持
@@ -2,6 +2,6 @@
2
2
  import { createCrudApi } from '/@/api/common/crudFactory';
3
3
  {{API_REQUEST_IMPORT}}
4
4
 
5
- // {{FEATURE_TITLE}}台账只读接口:分页查询、详情查询
6
- export const { fetchList, getObj } = createCrudApi('/{{API_PATH}}');
5
+ // {{FEATURE_TITLE}}台账只读接口:分页查询、详情查询、导出
6
+ export const { fetchList, getObj, exportObj } = createCrudApi('/{{API_PATH}}');
7
7
  {{EXTRA_API_FUNCTIONS}}
@@ -36,7 +36,7 @@
36
36
 
37
37
  <script setup lang="ts" name="system{{CLASS_NAME}}">
38
38
  // 列表接口:台账页只消费分页查询能力
39
- import { fetchList } from '/@/api/{{API_MODULE_PATH}}';
39
+ import { fetchList, exportObj } from '/@/api/{{API_MODULE_PATH}}';
40
40
  // 字典数据加载
41
41
  import { useDict } from '/@/hooks/dict';
42
42
  // 列表页字段元数据能力
@@ -66,8 +66,9 @@ const router = useRouter();
66
66
  const { state, getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle, exportExcel: runExportExcel, resetQueryForm } = useSchemaListQuery({
67
67
  schema: crudSchema,
68
68
  pageList: fetchList,
69
- exportUrl: '/{{API_PATH}}/export',
70
- exportFileName: '{{FUNCTION_NAME}}.xlsx',
69
+ exportApi: exportObj,
70
+ exportFileName: '{{FEATURE_TITLE}}.xlsx',
71
+ exportColumns: () => tableColumns.value,
71
72
  });
72
73
 
73
74
  // 统一处理台账列表加载后的首行选中,以及刷新后的当前行保持
@@ -22,15 +22,11 @@
22
22
  </div>
23
23
  </template>
24
24
  <!-- 主子表表单:按 PRD 表单显隐和顺序渲染字段 -->
25
- <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
26
- <!-- 主表字段区:字段级注释由 MCP 根据字段名称生成 -->
27
- <el-row :gutter="24">
28
- {{FORM_FIELDS}}
29
- </el-row>
30
- <!-- 子表明细区:按 children 配置渲染多个子表 -->
31
- <el-row :gutter="24">
25
+ <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading" label-width="120px">
26
+ <el-collapse v-model="activeCollapseNames">
27
+ {{FORM_COLLAPSE_SECTIONS}}
32
28
  {{CHILD_SECTIONS}}
33
- </el-row>
29
+ </el-collapse>
34
30
  </el-form>
35
31
  </el-card>
36
32
  </div>
@@ -72,6 +68,7 @@ const pageI18nKey = '{{I18N_NAMESPACE}}';
72
68
 
73
69
  // 表单引用
74
70
  const dataFormRef = ref();
71
+ const activeCollapseNames = ref({{ACTIVE_COLLAPSE_NAMES}});
75
72
  // 页面加载状态
76
73
  const loading = ref(false);
77
74
  // 是否详情模式
@@ -50,7 +50,7 @@
50
50
 
51
51
  <script setup lang="ts" name="system{{CLASS_NAME}}">
52
52
  // 列表接口
53
- import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
53
+ import { fetchList, delObjs, exportObj } from '/@/api/{{API_MODULE_PATH}}';
54
54
  // 通用消息与确认弹窗
55
55
  import { useMessage, useMessageBox } from '/@/hooks/message';
56
56
  // 字典数据加载
@@ -91,8 +91,9 @@ const multiple = ref(true);
91
91
  const { state, getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle, exportExcel: runExportExcel, resetQueryForm } = useSchemaListQuery({
92
92
  schema: crudSchema,
93
93
  pageList: fetchList,
94
- exportUrl: '/{{API_PATH}}/export',
95
- exportFileName: '{{FUNCTION_NAME}}.xlsx',
94
+ exportApi: exportObj,
95
+ exportFileName: '{{FEATURE_TITLE}}.xlsx',
96
+ exportColumns: () => tableColumns.value,
96
97
  });
97
98
 
98
99
  // 统一处理列表加载后的首行选中,以及刷新后的当前行保持
@@ -44,7 +44,7 @@
44
44
 
45
45
  <script setup lang="ts" name="system{{CLASS_NAME}}">
46
46
  // 列表接口
47
- import { fetchList, delObjs{{DICT_API_IMPORTS}} } from '/@/api/{{API_MODULE_PATH}}';
47
+ import { fetchList, delObjs, exportObj{{DICT_API_IMPORTS}} } from '/@/api/{{API_MODULE_PATH}}';
48
48
  // 通用消息与确认弹窗
49
49
  import { useMessage, useMessageBox } from '/@/hooks/message';
50
50
  // 字典数据加载
@@ -85,8 +85,9 @@ const multiple = ref(true);
85
85
  const { state, getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle, exportExcel: runExportExcel, resetQueryForm } = useSchemaListQuery({
86
86
  schema: crudSchema,
87
87
  pageList: fetchList,
88
- exportUrl: '/{{API_PATH}}/export',
89
- exportFileName: '{{FUNCTION_NAME}}.xlsx',
88
+ exportApi: exportObj,
89
+ exportFileName: '{{FEATURE_TITLE}}.xlsx',
90
+ exportColumns: () => tableColumns.value,
90
91
  });
91
92
 
92
93
  // 统一处理列表加载后的首行选中,以及刷新后的当前行保持
@@ -22,11 +22,10 @@
22
22
  </div>
23
23
  </template>
24
24
  <!-- 主表表单:按 PRD 表单显隐和顺序渲染字段 -->
25
- <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
26
- <!-- 主表字段区:字段级注释由 MCP 根据字段名称生成 -->
27
- <el-row :gutter="24">
28
- {{FORM_FIELDS}}
29
- </el-row>
25
+ <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading" label-width="120px">
26
+ <el-collapse v-model="activeCollapseNames">
27
+ {{FORM_COLLAPSE_SECTIONS}}
28
+ </el-collapse>
30
29
  </el-form>
31
30
  </el-card>
32
31
  </div>
@@ -63,6 +62,7 @@ const { closeCurrentPage } = useCloseCurrentPage();
63
62
 
64
63
  // 表单引用
65
64
  const dataFormRef = ref();
65
+ const activeCollapseNames = ref({{ACTIVE_COLLAPSE_NAMES}});
66
66
  // 页面加载状态
67
67
  const loading = ref(false);
68
68
  // 是否详情模式
@@ -51,7 +51,7 @@
51
51
 
52
52
  <script setup lang="ts" name="system{{CLASS_NAME}}">
53
53
  // 列表接口
54
- import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
54
+ import { fetchList, delObjs, exportObj } from '/@/api/{{API_MODULE_PATH}}';
55
55
  // 通用消息与确认弹窗
56
56
  import { useMessage, useMessageBox } from '/@/hooks/message';
57
57
  // 字典数据加载
@@ -92,8 +92,9 @@ const multiple = ref(true);
92
92
  const { state, getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle, exportExcel: runExportExcel, resetQueryForm } = useSchemaListQuery({
93
93
  schema: crudSchema,
94
94
  pageList: fetchList,
95
- exportUrl: '/{{API_PATH}}/export',
96
- exportFileName: '{{FUNCTION_NAME}}.xlsx',
95
+ exportApi: exportObj,
96
+ exportFileName: '{{FEATURE_TITLE}}.xlsx',
97
+ exportColumns: () => tableColumns.value,
97
98
  });
98
99
 
99
100
  // 统一处理列表加载后的首行选中,以及刷新后的当前行保持
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.85';
8
+ const SERVER_VERSION = '0.1.89';
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');
@@ -203,15 +203,15 @@ export const createCrudApi = (baseUrl: string, overrides: CrudApiPathOverrides =
203
203
  data: obj,
204
204
  }),
205
205
 
206
- exportObj: (query?: object) =>
207
- request({
208
- url: overrides.export || joinApiPath(baseUrl, 'export'),
209
- method: 'get',
210
- params: query,
211
- responseType: 'blob',
212
- }),
213
- });
214
- `;
206
+ exportObj: (query?: object) =>
207
+ request({
208
+ url: overrides.export || joinApiPath(baseUrl, 'export'),
209
+ method: 'post',
210
+ data: query,
211
+ responseType: 'blob',
212
+ }),
213
+ });
214
+ `;
215
215
 
216
216
  const DEFAULT_CLOSE_CURRENT_PAGE_TEMPLATE = `import type { RouteLocationNormalizedLoaded } from 'vue-router';
217
217
  import { useRoute } from 'vue-router';
@@ -260,9 +260,13 @@ export const DictSemanticValues = {
260
260
  } as const;
261
261
  `;
262
262
 
263
- const TOOL_SCHEMA = {
264
- type: 'object',
265
- properties: {
263
+ const TOOL_SCHEMA = {
264
+ type: 'object',
265
+ properties: {
266
+ parseResultPath: {
267
+ type: 'string',
268
+ description: 'Recommended local parseResult.json path. When provided, MCP reads parseResult.mcpPayload locally and uses it as generation input. Direct low-level arguments are legacy compatibility only.',
269
+ },
266
270
  featureTitle: { type: 'string', description: 'Feature title from pre-parsed structured metadata.' },
267
271
  billCode: { type: 'string', description: 'Stable menu bill_code generated from the Chinese menu title initials.' },
268
272
  tableName: { type: 'string', description: 'Canonical main table name from PRD-aligned structured metadata.' },
@@ -273,10 +277,10 @@ const TOOL_SCHEMA = {
273
277
  enum: ['business', 'dict', 'ledger', 'non_standard'],
274
278
  description: 'Structured page type from parseResult. MCP consumes this value but does not derive it. Dict pages are restricted to dialog-based templates. Ledger pages are read-only jump-based templates.',
275
279
  },
276
- style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Final style id from parseResult or translated mcpPayload. MCP validates it but does not infer it.' },
280
+ style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Legacy low-level final style id from parseResult.mcpPayload. MCP validates it but does not infer it.' },
277
281
  fields: {
278
282
  type: 'array',
279
- description: 'Structured main-table field metadata already translated by the caller. MCP only consumes these low-level generation parameters.',
283
+ description: 'Legacy low-level structured main-table field metadata. Prefer parseResultPath so MCP reads parseResult.mcpPayload locally.',
280
284
  items: {
281
285
  type: 'object',
282
286
  properties: {
@@ -290,7 +294,7 @@ const TOOL_SCHEMA = {
290
294
  show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
291
295
  listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
292
296
  formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
293
- formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
297
+ formOrder: { type: ['number', 'string'], description: 'Generation-only 1-based form field order from PRD detail/form table. This is not emitted to options.ts.' },
294
298
  smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
295
299
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
296
300
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
@@ -333,7 +337,7 @@ const TOOL_SCHEMA = {
333
337
  show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
334
338
  listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
335
339
  formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
336
- formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
340
+ formOrder: { type: ['number', 'string'], description: 'Generation-only 1-based form field order from PRD detail/form table. This is not emitted to options.ts.' },
337
341
  smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
338
342
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
339
343
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
@@ -394,7 +398,7 @@ const TOOL_SCHEMA = {
394
398
  show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
395
399
  listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
396
400
  formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
397
- formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
401
+ formOrder: { type: ['number', 'string'], description: 'Generation-only 1-based form field order from PRD detail/form table. This is not emitted to options.ts.' },
398
402
  smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
399
403
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
400
404
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
@@ -465,9 +469,9 @@ const TOOL_SCHEMA = {
465
469
  description: 'Whether to merge generated zh-cn content into an existing i18n file. If false, MCP renders zh-cn.ts from current metadata only.'
466
470
  },
467
471
  },
468
- required: ['tableName', 'style', 'frontendPath'],
469
- additionalProperties: false,
470
- };
472
+ required: [],
473
+ additionalProperties: false,
474
+ };
471
475
 
472
476
  function loadStyleCatalog() {
473
477
  return JSON.parse(fs.readFileSync(STYLE_CATALOG_PATH, 'utf8'));
@@ -1224,13 +1228,19 @@ function readUtf8File(filePath) {
1224
1228
  return stripBom(fs.readFileSync(filePath, 'utf8'));
1225
1229
  }
1226
1230
 
1227
- function normalizeDictType(value) {
1228
- const normalized = String(value || '').trim();
1229
- if (!normalized || normalized === '-' || normalized === '/') {
1230
- return null;
1231
- }
1232
- return normalized.replace(/^['"`]+|['"`]+$/g, '');
1233
- }
1231
+ function normalizeDictType(value) {
1232
+ const normalized = String(value || '').trim();
1233
+ if (!normalized || normalized === '-' || normalized === '/') {
1234
+ return null;
1235
+ }
1236
+ return normalized.replace(/^['"`]+|['"`]+$/g, '');
1237
+ }
1238
+
1239
+ function normalizeDictTypeFromSourceLabel(value) {
1240
+ const normalized = normalizeDictType(value);
1241
+ if (!normalized) return null;
1242
+ return /^[a-z][a-z0-9_]*$/.test(normalized) ? normalized : null;
1243
+ }
1234
1244
 
1235
1245
  function stripDictAnnotation(label) {
1236
1246
  const text = String(label || '').trim();
@@ -1370,9 +1380,9 @@ function normalizeStructuredLengthAndScale(lengthValue, scaleValue) {
1370
1380
  function parseOptionalOrder(value, label) {
1371
1381
  if (value === undefined || value === null || value === '') return undefined;
1372
1382
  const order = Number.parseInt(String(value), 10);
1373
- if (Number.isNaN(order) || order < 0) {
1374
- throw new Error(`${label} must be a non-negative integer when provided, or empty when unspecified`);
1375
- }
1383
+ if (Number.isNaN(order) || order < 1) {
1384
+ throw new Error(`${label} must be a positive integer starting from 1 when provided, or empty when unspecified`);
1385
+ }
1376
1386
  return order;
1377
1387
  }
1378
1388
 
@@ -1429,29 +1439,33 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1429
1439
  throw new Error(contextLabel + '[' + index + '] is missing required field: type');
1430
1440
  }
1431
1441
 
1432
- const { length, scale } = normalizeStructuredLengthAndScale(inputField.length ?? inputField.maxLength, inputField.scale);
1433
- const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
1434
- const formType = explicitFormType;
1435
- const explicitQueryType =
1436
- inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
1437
- ? undefined
1442
+ const { length, scale } = normalizeStructuredLengthAndScale(inputField.length ?? inputField.maxLength, inputField.scale);
1443
+ const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
1444
+ const formType = explicitFormType;
1445
+ const dictType = normalizeDictType(inputField.dictType) || normalizeDictTypeFromSourceLabel(inputField.sourceDictLabel);
1446
+ const explicitQueryType =
1447
+ inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
1448
+ ? undefined
1438
1449
  : Number.parseInt(String(inputField.queryType), 10);
1439
1450
  const show = parseBooleanLike(inputField.show, true);
1440
- const listShow = parseBooleanLike(inputField.listShow, show);
1441
- const formShow = parseBooleanLike(inputField.formShow, show);
1442
- const formOrder = parseOptionalOrder(inputField.formOrder, contextLabel + '[' + index + '].formOrder');
1443
-
1444
- return {
1451
+ const listShow = parseBooleanLike(inputField.listShow, show);
1452
+ const formShow = parseBooleanLike(inputField.formShow, show);
1453
+ const formOrder = parseOptionalOrder(inputField.formOrder, contextLabel + '[' + index + '].formOrder');
1454
+ if (formShow === false && formOrder !== undefined) {
1455
+ throw new Error(`${contextLabel}[${index}].formOrder must be empty when formShow=false`);
1456
+ }
1457
+
1458
+ return {
1445
1459
  fieldName,
1446
1460
  attrName: toCamelCase(fieldName),
1447
1461
  sqlType: type,
1448
1462
  length,
1449
1463
  scale,
1450
1464
  comment: label,
1451
- label,
1452
- description: String(inputField.description || '').trim(),
1453
- formType,
1454
- dictType: normalizeDictType(inputField.dictType),
1465
+ label,
1466
+ description: String(inputField.description || '').trim(),
1467
+ formType,
1468
+ dictType,
1455
1469
  notNull: parseBooleanLike(inputField.required, false),
1456
1470
  defaultValue: normalizeDefaultValue(inputField.defaultValue),
1457
1471
  readonly: parseBooleanLike(inputField.readonly, false),
@@ -1461,13 +1475,13 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1461
1475
  formOrder,
1462
1476
  width: normalizeOptionWidth(inputField.width),
1463
1477
  smart: parseBooleanLike(inputField.smart, false),
1464
- queryType: Number.isNaN(explicitQueryType)
1465
- ? undefined
1466
- : explicitQueryType !== undefined
1467
- ? explicitQueryType
1468
- : normalizeDictType(inputField.dictType) && listShow
1469
- ? 30
1470
- : undefined,
1478
+ queryType: Number.isNaN(explicitQueryType)
1479
+ ? undefined
1480
+ : explicitQueryType !== undefined
1481
+ ? explicitQueryType
1482
+ : dictType && listShow
1483
+ ? 30
1484
+ : undefined,
1471
1485
  sourceKind: normalizeStructuredSourceKind(inputField.sourceKind),
1472
1486
  primary: parseBooleanLike(inputField.primary, fieldName === 'id'),
1473
1487
  };
@@ -1659,7 +1673,7 @@ function normalizePageTypeInput(pageType) {
1659
1673
  throw new Error(`Unsupported pageType: ${normalized}. Allowed values are dict, business, ledger, non_standard.`);
1660
1674
  }
1661
1675
 
1662
- function rejectSemanticStageInputs(input) {
1676
+ function rejectSemanticStageInputs(input) {
1663
1677
  const forbiddenKeys = [
1664
1678
  'parseResult',
1665
1679
  'prdFile',
@@ -1670,10 +1684,154 @@ function rejectSemanticStageInputs(input) {
1670
1684
  'fieldUiMeta',
1671
1685
  ];
1672
1686
  const present = forbiddenKeys.filter((key) => Object.prototype.hasOwnProperty.call(input, key));
1673
- if (present.length) {
1674
- throw new Error(`worsoft_codegen_local_generate_frontend only accepts translated low-level generation parameters. Unsupported semantic-stage keys: ${present.join(', ')}`);
1675
- }
1676
- }
1687
+ if (present.length) {
1688
+ throw new Error(`worsoft_codegen_local_generate_frontend only accepts translated low-level generation parameters. Unsupported semantic-stage keys: ${present.join(', ')}`);
1689
+ }
1690
+ }
1691
+
1692
+ function readJsonFile(filePath, label) {
1693
+ if (!filePath || typeof filePath !== 'string') {
1694
+ throw new Error(`${label} must be a non-empty string`);
1695
+ }
1696
+ const resolvedPath = path.resolve(filePath);
1697
+ if (!fs.existsSync(resolvedPath)) {
1698
+ throw new Error(`${label} not found: ${resolvedPath}`);
1699
+ }
1700
+ try {
1701
+ return { path: resolvedPath, value: JSON.parse(fs.readFileSync(resolvedPath, 'utf8')) };
1702
+ } catch (error) {
1703
+ throw new Error(`${label} is not valid JSON: ${resolvedPath}. ${error.message}`);
1704
+ }
1705
+ }
1706
+
1707
+ function copyIfPresent(target, source, keys) {
1708
+ if (!source || typeof source !== 'object') return;
1709
+ for (const key of keys) {
1710
+ if (target[key] === undefined && source[key] !== undefined && source[key] !== null && source[key] !== '') {
1711
+ target[key] = source[key];
1712
+ }
1713
+ }
1714
+ }
1715
+
1716
+ function assertParseResultPayloadConsistency(parseResult, mcpPayload, parseResultPath) {
1717
+ const keys = ['pageType', 'style', 'targetViewDir', 'targetApiModule', 'targetI18nKey', 'billCode'];
1718
+ for (const key of keys) {
1719
+ if (parseResult[key] === undefined || parseResult[key] === null || parseResult[key] === '') continue;
1720
+ if (mcpPayload[key] === undefined || mcpPayload[key] === null || mcpPayload[key] === '') continue;
1721
+ if (String(parseResult[key]) !== String(mcpPayload[key])) {
1722
+ throw new Error(`parseResult.mcpPayload.${key} does not match parseResult.${key} in ${parseResultPath}`);
1723
+ }
1724
+ }
1725
+ }
1726
+
1727
+ function collectPayloadFieldGroups(mcpPayload) {
1728
+ const groups = [];
1729
+ if (Array.isArray(mcpPayload.fields)) {
1730
+ groups.push({ path: 'mcpPayload.fields', fields: mcpPayload.fields });
1731
+ }
1732
+ if (Array.isArray(mcpPayload.children)) {
1733
+ mcpPayload.children.forEach((child, childIndex) => {
1734
+ if (Array.isArray(child.fields)) {
1735
+ groups.push({ path: `mcpPayload.children[${childIndex}].fields`, fields: child.fields });
1736
+ }
1737
+ });
1738
+ }
1739
+ if (Array.isArray(mcpPayload.levels)) {
1740
+ mcpPayload.levels.forEach((level, levelIndex) => {
1741
+ if (!Array.isArray(level.modules)) return;
1742
+ level.modules.forEach((module, moduleIndex) => {
1743
+ if (Array.isArray(module.fields)) {
1744
+ groups.push({ path: `mcpPayload.levels[${levelIndex}].modules[${moduleIndex}].fields`, fields: module.fields });
1745
+ }
1746
+ });
1747
+ });
1748
+ }
1749
+ return groups;
1750
+ }
1751
+
1752
+ function normalizeFieldSet(values) {
1753
+ if (!Array.isArray(values)) return new Set();
1754
+ return new Set(
1755
+ values
1756
+ .map((item) => (typeof item === 'string' ? item : item && (item.fieldName || item.name)))
1757
+ .filter(Boolean)
1758
+ .map((item) => String(item).trim())
1759
+ );
1760
+ }
1761
+
1762
+ function assertVisibilityMatrixConsistency(parseResult, mcpPayload, parseResultPath) {
1763
+ if (!parseResult.visibilityMatrix || typeof parseResult.visibilityMatrix !== 'object') return;
1764
+ const matrixGroups = Array.isArray(parseResult.visibilityMatrix.groups) ? parseResult.visibilityMatrix.groups : [];
1765
+ const fallbackVisibleFields = normalizeFieldSet(parseResult.visibilityMatrix.visibleFormFields);
1766
+ const fallbackHiddenFields = normalizeFieldSet(parseResult.visibilityMatrix.explicitHiddenFormFields || parseResult.visibilityMatrix.hiddenFormFields);
1767
+ if (!matrixGroups.length && !fallbackVisibleFields.size && !fallbackHiddenFields.size) return;
1768
+
1769
+ const issues = [];
1770
+ for (const group of collectPayloadFieldGroups(mcpPayload)) {
1771
+ const matrixGroup = matrixGroups.find((item) => item && item.path === group.path);
1772
+ const visibleFields = matrixGroup ? normalizeFieldSet(matrixGroup.visibleFormFields) : fallbackVisibleFields;
1773
+ const hiddenFields = matrixGroup ? normalizeFieldSet(matrixGroup.explicitHiddenFormFields || matrixGroup.hiddenFormFields) : fallbackHiddenFields;
1774
+ if (!visibleFields.size && !hiddenFields.size) continue;
1775
+
1776
+ for (const field of group.fields) {
1777
+ if (!field || typeof field !== 'object' || !field.fieldName) continue;
1778
+ const fieldName = String(field.fieldName);
1779
+ const expectedFormShow = visibleFields.has(fieldName) && !hiddenFields.has(fieldName);
1780
+ if (Boolean(field.formShow) !== expectedFormShow) {
1781
+ issues.push(`${group.path}.${fieldName}.formShow expected ${expectedFormShow} from visibilityMatrix, got ${field.formShow}`);
1782
+ }
1783
+ if (!expectedFormShow && field.formOrder !== undefined && field.formOrder !== null && field.formOrder !== '') {
1784
+ issues.push(`${group.path}.${fieldName}.formOrder must be empty when visibilityMatrix marks it hidden`);
1785
+ }
1786
+ }
1787
+ }
1788
+
1789
+ if (issues.length) {
1790
+ throw new Error(`parseResult visibilityMatrix conflicts with mcpPayload in ${parseResultPath}: ${issues.join('; ')}`);
1791
+ }
1792
+ }
1793
+
1794
+ function resolveGenerationInput(input) {
1795
+ if (!input.parseResultPath) {
1796
+ return { generationInput: input, parseResultPath: '' };
1797
+ }
1798
+
1799
+ const parsed = readJsonFile(input.parseResultPath, 'parseResultPath');
1800
+ const parseResult = parsed.value;
1801
+ if (!parseResult || typeof parseResult !== 'object' || Array.isArray(parseResult)) {
1802
+ throw new Error(`parseResultPath must contain a JSON object: ${parsed.path}`);
1803
+ }
1804
+ if (!parseResult.mcpPayload || typeof parseResult.mcpPayload !== 'object' || Array.isArray(parseResult.mcpPayload)) {
1805
+ throw new Error(`parseResult.mcpPayload missing in ${parsed.path}`);
1806
+ }
1807
+
1808
+ const generationInput = { ...parseResult.mcpPayload };
1809
+ copyIfPresent(generationInput, parseResult, [
1810
+ 'featureTitle',
1811
+ 'billCode',
1812
+ 'tableName',
1813
+ 'tableComment',
1814
+ 'apiPath',
1815
+ 'pageType',
1816
+ 'style',
1817
+ 'targetViewDir',
1818
+ 'targetApiModule',
1819
+ 'targetI18nKey',
1820
+ 'frontendPath',
1821
+ 'moduleName',
1822
+ ]);
1823
+ assertParseResultPayloadConsistency(parseResult, generationInput, parsed.path);
1824
+ assertVisibilityMatrixConsistency(parseResult, generationInput, parsed.path);
1825
+
1826
+ for (const key of ['frontendPath', 'moduleName', 'writeToDisk', 'overwrite', 'writeSupportFiles', 'mergeI18nZh']) {
1827
+ if (Object.prototype.hasOwnProperty.call(input, key)) {
1828
+ generationInput[key] = input[key];
1829
+ }
1830
+ }
1831
+
1832
+ generationInput.parseResultPath = parsed.path;
1833
+ return { generationInput, parseResultPath: parsed.path };
1834
+ }
1677
1835
 
1678
1836
  function validatePageTypeAndStyle(pageType, style) {
1679
1837
  if (!pageType) return;
@@ -2264,23 +2422,24 @@ function renderChildSection(childModel, childCount) {
2264
2422
  const deleteExpression = `deleteChild(obj, '${childModel.pk.attrName}')`;
2265
2423
 
2266
2424
  return [
2267
- ' <el-col :span="24" class="mb20">',
2268
- ` <!-- 子表区域:${sanitizeHtmlComment(childModel.comment || childModel.tableName)} -->`,
2269
- ' <div class="mb10" style="display:flex;justify-content:space-between;align-items:center;">',
2270
- ` <span style="font-weight: 600;">{{ childSectionTitle('${childModel.listName}') }}</span>`,
2271
- ' <div style="display:flex;align-items:center;gap:8px;">',
2425
+ ` <el-collapse-item :title="childSectionTitle('${childModel.listName}')" name="child-${childModel.listName}">`,
2426
+ ' <el-row :gutter="24">',
2427
+ ' <el-col :span="24" class="mb20">',
2428
+ ` <!-- 子表区域:${sanitizeHtmlComment(childModel.comment || childModel.tableName)} -->`,
2429
+ ' <div class="mb10" style="display:flex;justify-content:flex-end;align-items:center;gap:8px;">',
2272
2430
  ` <el-button icon="Plus" type="primary" :disabled="detail" @click="handleAddChild('${childModel.listName}')">{{ t('common.addBtn') }}</el-button>`,
2273
2431
  ` <el-button icon="Delete" type="danger" plain :disabled="detail || !getSelectedChildRows('${childModel.listName}').length" @click="handleDeleteSelectedChild('${childModel.listName}', '${childModel.pk.attrName}')">{{ t('common.delBtn') }}</el-button>`,
2274
2432
  ' </div>',
2275
- ' </div>',
2276
- ' <!-- 子表编辑表格:新增/删除按钮外置,表格内部隐藏新增、删除和序号控制列 -->',
2277
- ` <sc-form-table :ref="(el) => setChildTableRef('${childModel.listName}', el)" v-model="form.${childModel.listName}" :addTemplate="childTemp${childModel.className}" hide-index hide-add hide-delete @delete="(obj) => ${deleteExpression}" :placeholder="t('common.noData')" @selection-change="(rows) => handleChildSelectionChange('${childModel.listName}', rows)">`,
2278
- ' <el-table-column type="selection" width="55" :selectable="() => !detail" />',
2433
+ ' <!-- 子表编辑表格:新增/删除按钮外置,表格内部隐藏新增、删除和序号控制列 -->',
2434
+ ` <sc-form-table :ref="(el) => setChildTableRef('${childModel.listName}', el)" v-model="form.${childModel.listName}" :addTemplate="childTemp${childModel.className}" hide-index hide-add hide-delete @delete="(obj) => ${deleteExpression}" :placeholder="t('common.noData')" @selection-change="(rows) => handleChildSelectionChange('${childModel.listName}', rows)">`,
2435
+ ' <el-table-column type="selection" width="55" :selectable="() => !detail" />',
2279
2436
  childModel.visibleFields.map((field) => renderChildTableColumn(field, childModel.listName)).join('\n'),
2280
- ' </sc-form-table>',
2281
- ' </el-col>',
2282
- ].join('\n');
2283
- }
2437
+ ' </sc-form-table>',
2438
+ ' </el-col>',
2439
+ ' </el-row>',
2440
+ ' </el-collapse-item>',
2441
+ ].join('\n');
2442
+ }
2284
2443
 
2285
2444
  function renderChildFormListDefaults(children) {
2286
2445
  if (!children.length) return '';
@@ -2405,7 +2564,7 @@ function renderFieldCommentV2(field, indent = ' ') {
2405
2564
  return indent + '<!-- ' + label + ' -->';
2406
2565
  }
2407
2566
 
2408
- function renderFormFieldV2(field) {
2567
+ function renderFormFieldV2(field) {
2409
2568
  const prop = field.attrName;
2410
2569
  const labelExpr = `getMasterFieldLabel('${prop}')`;
2411
2570
  const dictExpr = `getMasterFieldMeta('${prop}')?.dictType`;
@@ -2504,10 +2663,58 @@ function renderFormFieldV2(field) {
2504
2663
  ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${maxlengthAttr}${disabledAttr} />`,
2505
2664
  ' </el-form-item>',
2506
2665
  ' </el-col>',
2507
- ].join('\n');
2508
- }
2509
-
2510
- function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs, indent = ' ') {
2666
+ ].join('\n');
2667
+ }
2668
+
2669
+ const FILL_REPORT_FIELD_NAMES = new Set(['createBy', 'createTime', 'finishedTime']);
2670
+ const FILL_REPORT_FIELD_ORDER = ['createBy', 'createTime', 'finishedTime'];
2671
+
2672
+ function isFillReportField(field) {
2673
+ return FILL_REPORT_FIELD_NAMES.has(String(field?.attrName || field?.fieldName || ''));
2674
+ }
2675
+
2676
+ function renderBusinessFormCollapseItem(title, name, fields) {
2677
+ return [
2678
+ ` <el-collapse-item title="${title}" name="${name}">`,
2679
+ ' <el-row :gutter="24">',
2680
+ fields.map(renderFormFieldV2).join('\n'),
2681
+ ' </el-row>',
2682
+ ' </el-collapse-item>',
2683
+ ].join('\n');
2684
+ }
2685
+
2686
+ function splitBusinessFormFields(fields) {
2687
+ const fillReportFields = fields
2688
+ .filter(isFillReportField)
2689
+ .sort((left, right) => FILL_REPORT_FIELD_ORDER.indexOf(left.attrName) - FILL_REPORT_FIELD_ORDER.indexOf(right.attrName));
2690
+ if (!fillReportFields.length) {
2691
+ return { basicFields: fields, fillReportFields: [] };
2692
+ }
2693
+ return {
2694
+ basicFields: fields.filter((field) => !isFillReportField(field)),
2695
+ fillReportFields,
2696
+ };
2697
+ }
2698
+
2699
+ function renderBusinessFormCollapseSections(model) {
2700
+ const fields = model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields;
2701
+ const { basicFields, fillReportFields } = splitBusinessFormFields(fields);
2702
+ const sections = [renderBusinessFormCollapseItem('基本信息', '0', basicFields)];
2703
+ if (fillReportFields.length) {
2704
+ sections.push(renderBusinessFormCollapseItem('填报信息', '1', fillReportFields));
2705
+ }
2706
+ return sections.join('\n');
2707
+ }
2708
+
2709
+ function renderActiveCollapseNames(model) {
2710
+ const fields = model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields;
2711
+ const names = ['0'];
2712
+ if (fields.some(isFillReportField)) names.push('1');
2713
+ model.children.forEach((childModel) => names.push(`child-${childModel.listName}`));
2714
+ return `[${names.map((name) => `'${name}'`).join(', ')}]`;
2715
+ }
2716
+
2717
+ function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs, indent = ' ') {
2511
2718
  const parts = [
2512
2719
  `key: '${field.attrName}'`,
2513
2720
  `labelKey: '${buildMultiLevelFieldLabelKey(model, moduleModel, field)}'`,
@@ -2670,15 +2877,15 @@ function renderMultiLevelApiFunctions(moduleModel) {
2670
2877
  ' });',
2671
2878
  '}',
2672
2879
  '',
2673
- `// 导出${title}`,
2674
- `export function export${moduleModel.className}Obj(query?: any) {`,
2675
- ' return request({',
2676
- ` url: '${basePath}/export',`,
2677
- " method: 'get',",
2678
- ' params: query,',
2679
- " responseType: 'blob',",
2680
- ' });',
2681
- '}',
2880
+ `// 导出${title}`,
2881
+ `export function export${moduleModel.className}Obj(query?: any) {`,
2882
+ ' return request({',
2883
+ ` url: '${basePath}/export',`,
2884
+ " method: 'post',",
2885
+ ' data: query,',
2886
+ " responseType: 'blob',",
2887
+ ' });',
2888
+ '}',
2682
2889
  '',
2683
2890
  `// 启用${title}`,
2684
2891
  `export function enable${moduleModel.className}(id: string | number) {`,
@@ -4170,10 +4377,12 @@ function buildReplacements(model, sharedSupport) {
4170
4377
  BUSINESS_FORM_KEEP_ALIVE_COLUMN: model.pageType === 'business' ? ', keep_alive' : '',
4171
4378
  BUSINESS_FORM_KEEP_ALIVE_VALUE: model.pageType === 'business' ? ", '1'" : '',
4172
4379
  BILL_CODE: deriveBillCode(model),
4173
- GENERATED_AT: new Date().toISOString(),
4174
- FORM_FIELDS: (model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields).map(renderFormFieldV2).join('\n'),
4175
- TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumn(field, dictRegistryRefs)).join('\n'),
4176
- FORM_DEFAULTS: renderFormDefaults(model),
4380
+ GENERATED_AT: new Date().toISOString(),
4381
+ FORM_FIELDS: (model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields).map(renderFormFieldV2).join('\n'),
4382
+ FORM_COLLAPSE_SECTIONS: renderBusinessFormCollapseSections(model),
4383
+ ACTIVE_COLLAPSE_NAMES: renderActiveCollapseNames(model),
4384
+ TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumn(field, dictRegistryRefs)).join('\n'),
4385
+ FORM_DEFAULTS: renderFormDefaults(model),
4177
4386
  DICT_REGISTRY_IMPORT_BLOCK: model.dictTypes.length ? "import { DictRegistry } from '/@/enums/dict-registry';" : '',
4178
4387
  MASTER_OPTION_FIELDS: model.optionFields.map((field) => renderOptionFieldV2(field, buildFieldLabelKey(model, field), dictRegistryRefs)).join('\n'),
4179
4388
  CHILD_OPTION_GROUPS: model.children.map((childModel) => renderChildOptionGroupV2(model, childModel, dictRegistryRefs)).join('\n'),
@@ -4231,20 +4440,22 @@ function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
4231
4440
  return files;
4232
4441
  }
4233
4442
 
4234
- function ensureArguments(input) {
4235
- if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
4236
- rejectSemanticStageInputs(input);
4237
- for (const key of TOOL_SCHEMA.required) {
4238
- if (input[key] === undefined || input[key] === null || input[key] === '') throw new Error(key + ' is required');
4239
- }
4240
-
4241
- const style = String(input.style);
4242
- const pageType = normalizePageTypeInput(input.pageType);
4243
- getStylePreset(style);
4244
- validatePageTypeAndStyle(pageType, style);
4245
- const isLevelStyle = isLevelBasedStyle(style);
4246
- const fields = isLevelStyle ? [] : normalizeStructuredFieldArray(input.fields, 'fields');
4247
- const levels = isLevelStyle ? normalizeLevelsInput(input.levels) : [];
4443
+ function ensureArguments(input) {
4444
+ if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
4445
+ rejectSemanticStageInputs(input);
4446
+ const resolved = resolveGenerationInput(input);
4447
+ const generationInput = resolved.generationInput;
4448
+ for (const key of ['tableName', 'style', 'frontendPath']) {
4449
+ if (generationInput[key] === undefined || generationInput[key] === null || generationInput[key] === '') throw new Error(key + ' is required');
4450
+ }
4451
+
4452
+ const style = String(generationInput.style);
4453
+ const pageType = normalizePageTypeInput(generationInput.pageType);
4454
+ getStylePreset(style);
4455
+ validatePageTypeAndStyle(pageType, style);
4456
+ const isLevelStyle = isLevelBasedStyle(style);
4457
+ const fields = isLevelStyle ? [] : normalizeStructuredFieldArray(generationInput.fields, 'fields');
4458
+ const levels = isLevelStyle ? normalizeLevelsInput(generationInput.levels) : [];
4248
4459
 
4249
4460
  if (isLevelStyle && !levels.length) {
4250
4461
  throw new Error(`${style} requires levels[]`);
@@ -4254,29 +4465,30 @@ function ensureArguments(input) {
4254
4465
  throw new Error('fields must be a non-empty array');
4255
4466
  }
4256
4467
 
4257
- return {
4258
- featureTitle: input.featureTitle ? String(input.featureTitle) : '',
4259
- billCode: input.billCode ? String(input.billCode).trim() : '',
4260
- tableName: String(input.tableName),
4261
- tableComment: input.tableComment ? String(input.tableComment) : '',
4262
- apiPath: normalizeApiPath(input.apiPath),
4263
- pageType,
4264
- style,
4265
- fields,
4266
- levels,
4267
- children: normalizeChildrenInput(input.children),
4268
- frontendPath: String(input.frontendPath),
4269
- moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
4270
- targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
4271
- targetApiModule: input.targetApiModule ? String(input.targetApiModule) : '',
4272
- targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
4273
- extraApis: input.extraApis === undefined ? [] : input.extraApis,
4274
- writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
4275
- overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
4276
- writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
4277
- mergeI18nZh: input.mergeI18nZh === undefined ? true : Boolean(input.mergeI18nZh),
4278
- };
4279
- }
4468
+ return {
4469
+ parseResultPath: resolved.parseResultPath,
4470
+ featureTitle: generationInput.featureTitle ? String(generationInput.featureTitle) : '',
4471
+ billCode: generationInput.billCode ? String(generationInput.billCode).trim() : '',
4472
+ tableName: String(generationInput.tableName),
4473
+ tableComment: generationInput.tableComment ? String(generationInput.tableComment) : '',
4474
+ apiPath: normalizeApiPath(generationInput.apiPath),
4475
+ pageType,
4476
+ style,
4477
+ fields,
4478
+ levels,
4479
+ children: normalizeChildrenInput(generationInput.children),
4480
+ frontendPath: String(generationInput.frontendPath),
4481
+ moduleName: generationInput.moduleName ? String(generationInput.moduleName) : 'admin/test',
4482
+ targetViewDir: generationInput.targetViewDir ? String(generationInput.targetViewDir) : '',
4483
+ targetApiModule: generationInput.targetApiModule ? String(generationInput.targetApiModule) : '',
4484
+ targetI18nKey: generationInput.targetI18nKey ? String(generationInput.targetI18nKey) : '',
4485
+ extraApis: generationInput.extraApis === undefined ? [] : generationInput.extraApis,
4486
+ writeToDisk: generationInput.writeToDisk === undefined ? true : Boolean(generationInput.writeToDisk),
4487
+ overwrite: generationInput.overwrite === undefined ? true : Boolean(generationInput.overwrite),
4488
+ writeSupportFiles: generationInput.writeSupportFiles === undefined ? true : Boolean(generationInput.writeSupportFiles),
4489
+ mergeI18nZh: generationInput.mergeI18nZh === undefined ? true : Boolean(generationInput.mergeI18nZh),
4490
+ };
4491
+ }
4280
4492
 
4281
4493
  function maybeWriteFiles(files, writeToDisk, overwrite) {
4282
4494
  if (!writeToDisk) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.85",
3
+ "version": "0.1.89",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",