worsoft-frontend-codegen-local-mcp 0.1.67 → 0.1.68

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.
Files changed (2) hide show
  1. package/mcp_server.js +1463 -1462
  2. package/package.json +1 -1
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.67';
8
+ const SERVER_VERSION = '0.1.68';
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');
@@ -15,12 +15,12 @@ const DEFAULT_DICT_REGISTRY_KEYS = {
15
15
  trade_standard_type: 'TRADE_STANDARD_TYPE',
16
16
  trade_score_standard: 'TRADE_SCORE_STANDARD',
17
17
  };
18
- const DEFAULT_CRUD_SCHEMA_TEMPLATE = `export interface FieldMeta {
19
- show: boolean;
20
- listShow: boolean;
21
- formShow: boolean;
22
- alwaysHide: boolean;
23
- smart: boolean;
18
+ const DEFAULT_CRUD_SCHEMA_TEMPLATE = `export interface FieldMeta {
19
+ show: boolean;
20
+ listShow: boolean;
21
+ formShow: boolean;
22
+ alwaysHide: boolean;
23
+ smart: boolean;
24
24
  queryType?: number;
25
25
  labelKey: string;
26
26
  label?: string;
@@ -28,16 +28,16 @@ const DEFAULT_CRUD_SCHEMA_TEMPLATE = `export interface FieldMeta {
28
28
  dictType?: string;
29
29
  }
30
30
 
31
- export interface FieldConfig {
32
- key: string;
33
- labelKey?: string;
34
- label?: string;
35
- width?: string;
36
- show?: boolean;
37
- listShow?: boolean;
38
- formShow?: boolean;
39
- alwaysHide?: boolean;
40
- smart?: boolean;
31
+ export interface FieldConfig {
32
+ key: string;
33
+ labelKey?: string;
34
+ label?: string;
35
+ width?: string;
36
+ show?: boolean;
37
+ listShow?: boolean;
38
+ formShow?: boolean;
39
+ alwaysHide?: boolean;
40
+ smart?: boolean;
41
41
  queryType?: number;
42
42
  dictType?: string;
43
43
  }
@@ -59,12 +59,12 @@ export interface CrudSchema {
59
59
  const DEFAULT_WIDTH = '120';
60
60
  const DEFAULT_DICT_QUERY_TYPE = 30;
61
61
 
62
- export const field = (labelKey: string, width = DEFAULT_WIDTH): FieldMeta => ({
63
- show: true,
64
- listShow: true,
65
- formShow: true,
66
- alwaysHide: false,
67
- smart: false,
62
+ export const field = (labelKey: string, width = DEFAULT_WIDTH): FieldMeta => ({
63
+ show: true,
64
+ listShow: true,
65
+ formShow: true,
66
+ alwaysHide: false,
67
+ smart: false,
68
68
  labelKey,
69
69
  width,
70
70
  });
@@ -97,11 +97,11 @@ export const collectDictTypes = (...groups: Array<Record<string, FieldMeta>>) =>
97
97
  )
98
98
  );
99
99
 
100
- const normalizeField = (item: FieldConfig): FieldMeta => ({
101
- show: item.show ?? true,
102
- listShow: item.listShow ?? item.show ?? true,
103
- formShow: item.formShow ?? item.show ?? true,
104
- alwaysHide: item.alwaysHide ?? false,
100
+ const normalizeField = (item: FieldConfig): FieldMeta => ({
101
+ show: item.show ?? true,
102
+ listShow: item.listShow ?? item.show ?? true,
103
+ formShow: item.formShow ?? item.show ?? true,
104
+ alwaysHide: item.alwaysHide ?? false,
105
105
  smart: item.smart ?? false,
106
106
  ...(typeof item.queryType === 'number'
107
107
  ? { queryType: item.queryType }
@@ -146,65 +146,65 @@ export function createCrudSchema(
146
146
  }
147
147
 
148
148
  return buildSchema(masterOrDefinition as Record<string, FieldMeta>, children);
149
- }
150
- `;
151
-
152
- const DEFAULT_CRUD_FACTORY_TEMPLATE = `import request from '/@/utils/request';
153
-
154
- export interface CrudApiPathOverrides {
155
- page?: string;
156
- save?: string;
157
- detail?: string;
158
- remove?: string;
159
- update?: string;
160
- }
161
-
162
- const joinApiPath = (baseUrl: string, suffix: string) => {
163
- const normalizedBase = String(baseUrl || '').replace(/\\/+$/, '');
164
- const normalizedSuffix = String(suffix || '').replace(/^\\/+/, '');
165
- return normalizedSuffix ? \`\${normalizedBase}/\${normalizedSuffix}\` : normalizedBase;
166
- };
167
-
168
- // 生成标准业务功能 CRUD 接口:分页、新增、详情、删除、更新。
169
- export const createCrudApi = (baseUrl: string, overrides: CrudApiPathOverrides = {}) => ({
170
- fetchList: (query?: object) =>
171
- request({
172
- url: overrides.page || joinApiPath(baseUrl, 'page'),
173
- method: 'get',
174
- params: query,
175
- }),
176
-
177
- addObj: (obj?: object) =>
178
- request({
179
- url: overrides.save || joinApiPath(baseUrl, 'save'),
180
- method: 'post',
181
- data: obj,
182
- }),
183
-
184
- getObj: (obj?: object) =>
185
- request({
186
- url: overrides.detail || joinApiPath(baseUrl, 'getById'),
187
- method: 'get',
188
- params: obj,
189
- }),
190
-
191
- delObjs: (ids?: object) =>
192
- request({
193
- url: overrides.remove || joinApiPath(baseUrl, 'removeByIds'),
194
- method: 'post',
195
- data: ids,
196
- }),
197
-
198
- putObj: (obj?: object) =>
199
- request({
200
- url: overrides.update || joinApiPath(baseUrl, 'updateById'),
201
- method: 'post',
202
- data: obj,
203
- }),
204
- });
205
- `;
206
-
207
- const TOOL_SCHEMA = {
149
+ }
150
+ `;
151
+
152
+ const DEFAULT_CRUD_FACTORY_TEMPLATE = `import request from '/@/utils/request';
153
+
154
+ export interface CrudApiPathOverrides {
155
+ page?: string;
156
+ save?: string;
157
+ detail?: string;
158
+ remove?: string;
159
+ update?: string;
160
+ }
161
+
162
+ const joinApiPath = (baseUrl: string, suffix: string) => {
163
+ const normalizedBase = String(baseUrl || '').replace(/\\/+$/, '');
164
+ const normalizedSuffix = String(suffix || '').replace(/^\\/+/, '');
165
+ return normalizedSuffix ? \`\${normalizedBase}/\${normalizedSuffix}\` : normalizedBase;
166
+ };
167
+
168
+ // 生成标准业务功能 CRUD 接口:分页、新增、详情、删除、更新。
169
+ export const createCrudApi = (baseUrl: string, overrides: CrudApiPathOverrides = {}) => ({
170
+ fetchList: (query?: object) =>
171
+ request({
172
+ url: overrides.page || joinApiPath(baseUrl, 'page'),
173
+ method: 'get',
174
+ params: query,
175
+ }),
176
+
177
+ addObj: (obj?: object) =>
178
+ request({
179
+ url: overrides.save || joinApiPath(baseUrl, 'save'),
180
+ method: 'post',
181
+ data: obj,
182
+ }),
183
+
184
+ getObj: (obj?: object) =>
185
+ request({
186
+ url: overrides.detail || joinApiPath(baseUrl, 'getById'),
187
+ method: 'get',
188
+ params: obj,
189
+ }),
190
+
191
+ delObjs: (ids?: object) =>
192
+ request({
193
+ url: overrides.remove || joinApiPath(baseUrl, 'removeByIds'),
194
+ method: 'post',
195
+ data: ids,
196
+ }),
197
+
198
+ putObj: (obj?: object) =>
199
+ request({
200
+ url: overrides.update || joinApiPath(baseUrl, 'updateById'),
201
+ method: 'post',
202
+ data: obj,
203
+ }),
204
+ });
205
+ `;
206
+
207
+ const TOOL_SCHEMA = {
208
208
  type: 'object',
209
209
  properties: {
210
210
  featureTitle: { type: 'string', description: 'Feature title from pre-parsed structured metadata.' },
@@ -230,16 +230,16 @@ const TOOL_SCHEMA = {
230
230
  scale: { type: ['string', 'number'], description: 'Optional decimal scale.' },
231
231
  required: { type: ['boolean', 'string'], description: 'Whether the field is required.' },
232
232
  readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
233
- show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
234
- listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
235
- formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
236
- formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
237
- smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
233
+ show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
234
+ listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
235
+ formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
236
+ formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
237
+ smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
238
238
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
239
239
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
240
240
  defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
241
241
  description: { type: 'string', description: 'Field description or notes.' },
242
- componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number, date or datetime.' },
242
+ componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number, date or datetime.' },
243
243
  formType: { type: 'string', description: 'Explicit form control type translated by the caller. MCP consumes this value first and only falls back to legacy heuristics when omitted.' },
244
244
  sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
245
245
  primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
@@ -273,16 +273,16 @@ const TOOL_SCHEMA = {
273
273
  scale: { type: ['string', 'number'], description: 'Optional decimal scale.' },
274
274
  required: { type: ['boolean', 'string'], description: 'Whether the field is required.' },
275
275
  readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
276
- show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
277
- listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
278
- formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
279
- formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
280
- smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
276
+ show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
277
+ listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
278
+ formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
279
+ formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
280
+ smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
281
281
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
282
282
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
283
283
  defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
284
284
  description: { type: 'string', description: 'Field description or notes.' },
285
- componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number, date or datetime.' },
285
+ componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number, date or datetime.' },
286
286
  formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
287
287
  sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
288
288
  primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
@@ -332,16 +332,16 @@ const TOOL_SCHEMA = {
332
332
  scale: { type: ['string', 'number'], description: 'Optional decimal scale.' },
333
333
  required: { type: ['boolean', 'string'], description: 'Whether the field is required.' },
334
334
  readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
335
- show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
336
- listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
337
- formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
338
- formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
339
- smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
335
+ show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
336
+ listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
337
+ formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
338
+ formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
339
+ smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
340
340
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
341
341
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
342
342
  defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
343
343
  description: { type: 'string', description: 'Field description or notes.' },
344
- componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number, date or datetime.' },
344
+ componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number, date or datetime.' },
345
345
  formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
346
346
  sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
347
347
  primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' }
@@ -370,36 +370,36 @@ const TOOL_SCHEMA = {
370
370
  type: 'string',
371
371
  description: 'Explicit relative api module path under src/api without extension, for example admin/iwmSysTrade. When provided, MCP writes the api file directly to this target path.',
372
372
  },
373
- targetI18nKey: {
374
- type: 'string',
375
- description: 'Explicit zh-cn i18n namespace, for example admin.iwmSysTrade. When provided, MCP writes zh-cn content to the matching file path and namespace instead of deriving them from moduleName and functionName.',
376
- },
377
- extraApis: {
378
- type: 'array',
379
- description: 'Additional API methods parsed from the API document and belonging to the current targetApiModule. Each method is rendered to api.ts with a purpose comment for later task-split usage.',
380
- items: {
381
- type: 'object',
382
- properties: {
383
- functionName: { type: 'string', description: 'Exported frontend API function name, for example getCurrentCountByTradeDictId.' },
384
- description: { type: 'string', description: 'API purpose comment. It must exactly match the API document interface title text after removing the section number and "接口N:" prefix; do not summarize or rewrite it.' },
385
- url: { type: 'string', description: 'Request URL. Absolute backend path is preferred, for example /admin/xxx/count.' },
386
- method: { type: 'string', enum: ['get', 'post', 'put', 'delete'], description: 'HTTP method.' },
387
- requestType: { type: 'string', enum: ['params', 'data'], description: 'Whether arguments are sent as query params or request body.' },
388
- targetApiModule: { type: 'string', description: 'Optional target api module. If provided, it must equal the current targetApiModule.' },
389
- source: { type: 'string', description: 'Source note from API doc or PRD.' },
390
- usedBy: { type: 'string', description: 'Optional downstream usage hint for task-split.' },
391
- },
392
- required: ['functionName', 'description', 'url', 'method'],
393
- additionalProperties: false,
394
- },
395
- },
396
- writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
373
+ targetI18nKey: {
374
+ type: 'string',
375
+ description: 'Explicit zh-cn i18n namespace, for example admin.iwmSysTrade. When provided, MCP writes zh-cn content to the matching file path and namespace instead of deriving them from moduleName and functionName.',
376
+ },
377
+ extraApis: {
378
+ type: 'array',
379
+ description: 'Additional API methods parsed from the API document and belonging to the current targetApiModule. Each method is rendered to api.ts with a purpose comment for later task-split usage.',
380
+ items: {
381
+ type: 'object',
382
+ properties: {
383
+ functionName: { type: 'string', description: 'Exported frontend API function name, for example getCurrentCountByTradeDictId.' },
384
+ description: { type: 'string', description: 'API purpose comment. It must exactly match the API document interface title text after removing the section number and "接口N:" prefix; do not summarize or rewrite it.' },
385
+ url: { type: 'string', description: 'Request URL. Absolute backend path is preferred, for example /admin/xxx/count.' },
386
+ method: { type: 'string', enum: ['get', 'post', 'put', 'delete'], description: 'HTTP method.' },
387
+ requestType: { type: 'string', enum: ['params', 'data'], description: 'Whether arguments are sent as query params or request body.' },
388
+ targetApiModule: { type: 'string', description: 'Optional target api module. If provided, it must equal the current targetApiModule.' },
389
+ source: { type: 'string', description: 'Source note from API doc or PRD.' },
390
+ usedBy: { type: 'string', description: 'Optional downstream usage hint for task-split.' },
391
+ },
392
+ required: ['functionName', 'description', 'url', 'method'],
393
+ additionalProperties: false,
394
+ },
395
+ },
396
+ writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
397
397
  overwrite: { type: 'boolean', default: true, description: 'Whether to overwrite existing files. If false, existing files are skipped.' },
398
- writeSupportFiles: {
399
- type: 'boolean',
400
- default: true,
401
- description: 'Whether to write project support files such as src/enums/dict-registry.ts, src/utils/crudSchema.ts and src/api/common/crudFactory.ts.'
402
- },
398
+ writeSupportFiles: {
399
+ type: 'boolean',
400
+ default: true,
401
+ description: 'Whether to write project support files such as src/enums/dict-registry.ts, src/utils/crudSchema.ts and src/api/common/crudFactory.ts.'
402
+ },
403
403
  mergeI18nZh: {
404
404
  type: 'boolean',
405
405
  default: true,
@@ -539,9 +539,9 @@ function toConstantCase(value) {
539
539
  .toUpperCase();
540
540
  }
541
541
 
542
- function parseDictRegistryEntries(fileContent) {
543
- const blockMatch = String(fileContent || '').match(/export const DictRegistry\s*=\s*{([\s\S]*?)}\s*as const;/);
544
- if (!blockMatch) return [];
542
+ function parseDictRegistryEntries(fileContent) {
543
+ const blockMatch = String(fileContent || '').match(/export const DictRegistry\s*=\s*{([\s\S]*?)}\s*as const;/);
544
+ if (!blockMatch) return [];
545
545
 
546
546
  const entries = [];
547
547
  const entryRegex = /^\s*([A-Z0-9_]+)\s*:\s*['"]([^'"]+)['"]\s*,?\s*$/gm;
@@ -550,78 +550,78 @@ function parseDictRegistryEntries(fileContent) {
550
550
  entries.push({ key: match[1], value: match[2] });
551
551
  match = entryRegex.exec(blockMatch[1]);
552
552
  }
553
- return entries;
554
- }
555
-
556
- function renderDictRegistryContent(entries) {
557
- const lines = entries.map((entry) => ` ${entry.key}: '${entry.value}',`);
558
- return [
559
- 'export const DictRegistry = {',
560
- ...lines,
561
- '} as const;',
562
- '',
563
- 'export type DictRegistryKey = keyof typeof DictRegistry;',
564
- '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
- }
581
-
582
- function patchDictRegistryContent(fileContent, entriesToAdd) {
583
- const source = String(fileContent || '');
584
- const blockMatch = source.match(/export const DictRegistry\s*=\s*{([\s\S]*?)}\s*as const;/);
585
- if (!blockMatch) return null;
586
-
587
- const block = blockMatch[0];
588
- const insertAt = block.lastIndexOf('}');
589
- if (insertAt < 0) return null;
590
-
591
- const linesToAdd = entriesToAdd.map((entry) => ` ${entry.key}: '${entry.value}',`).join('\n');
592
- if (!linesToAdd) return source;
593
-
594
- const prefix = block.slice(0, insertAt).replace(/\s*$/, '');
595
- const suffix = block.slice(insertAt);
596
- const nextBlock = `${prefix}\n${linesToAdd}\n${suffix}`;
597
-
598
- return source.replace(block, nextBlock);
599
- }
600
-
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
- }
623
-
624
- function isPlainObject(value) {
553
+ return entries;
554
+ }
555
+
556
+ function renderDictRegistryContent(entries) {
557
+ const lines = entries.map((entry) => ` ${entry.key}: '${entry.value}',`);
558
+ return [
559
+ 'export const DictRegistry = {',
560
+ ...lines,
561
+ '} as const;',
562
+ '',
563
+ 'export type DictRegistryKey = keyof typeof DictRegistry;',
564
+ '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
+ }
581
+
582
+ function patchDictRegistryContent(fileContent, entriesToAdd) {
583
+ const source = String(fileContent || '');
584
+ const blockMatch = source.match(/export const DictRegistry\s*=\s*{([\s\S]*?)}\s*as const;/);
585
+ if (!blockMatch) return null;
586
+
587
+ const block = blockMatch[0];
588
+ const insertAt = block.lastIndexOf('}');
589
+ if (insertAt < 0) return null;
590
+
591
+ const linesToAdd = entriesToAdd.map((entry) => ` ${entry.key}: '${entry.value}',`).join('\n');
592
+ if (!linesToAdd) return source;
593
+
594
+ const prefix = block.slice(0, insertAt).replace(/\s*$/, '');
595
+ const suffix = block.slice(insertAt);
596
+ const nextBlock = `${prefix}\n${linesToAdd}\n${suffix}`;
597
+
598
+ return source.replace(block, nextBlock);
599
+ }
600
+
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
+ }
623
+
624
+ function isPlainObject(value) {
625
625
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
626
626
  }
627
627
 
@@ -852,45 +852,45 @@ function prepareZhCnLocaleFile(model, mergeExisting) {
852
852
  };
853
853
  }
854
854
 
855
- function prepareDictRegistry(frontendPath, dictTypes) {
856
- const registryPath = path.join(frontendPath, 'src', 'enums', 'dict-registry.ts');
857
- const exists = fs.existsSync(registryPath);
858
- const currentContent = exists ? readUtf8File(registryPath) : '';
859
- const existingEntries = exists ? parseDictRegistryEntries(currentContent) : [];
860
- const entries = existingEntries.map((entry) => ({ ...entry }));
861
- const keyByValue = new Map(entries.map((entry) => [entry.value, entry.key]));
862
- const existingByKey = new Map(entries.map((entry) => [entry.key, entry.value]));
863
- const usedKeys = new Set(entries.map((entry) => entry.key));
864
- const entriesToAdd = [];
865
- let changed = !exists;
866
-
867
- for (const dictType of dictTypes) {
868
- if (!dictType || keyByValue.has(dictType)) continue;
869
- const key = buildUniqueDictRegistryKey(dictType, usedKeys, existingByKey);
870
- const entry = { key, value: dictType };
871
- entries.push(entry);
872
- entriesToAdd.push(entry);
873
- keyByValue.set(dictType, key);
874
- existingByKey.set(key, dictType);
875
- usedKeys.add(key);
876
- changed = true;
877
- }
878
-
879
- const patchedContent = exists ? patchDictRegistryContent(currentContent, entriesToAdd) : renderDictRegistryContent(entries);
880
- const content = patchedContent ? ensureDictRegistrySemanticHelpers(patchedContent) : null;
881
- const needsSemanticHelpers = exists && Boolean(content) && content !== currentContent;
882
-
883
- return {
884
- path: registryPath,
885
- entries,
886
- keyByValue,
887
- content,
888
- isCompatible: !exists || Boolean(content),
889
- needsWrite: changed || needsSemanticHelpers,
890
- };
891
- }
892
-
893
- function ensureCrudSchemaSupportFile(frontendPath) {
855
+ function prepareDictRegistry(frontendPath, dictTypes) {
856
+ const registryPath = path.join(frontendPath, 'src', 'enums', 'dict-registry.ts');
857
+ const exists = fs.existsSync(registryPath);
858
+ const currentContent = exists ? readUtf8File(registryPath) : '';
859
+ const existingEntries = exists ? parseDictRegistryEntries(currentContent) : [];
860
+ const entries = existingEntries.map((entry) => ({ ...entry }));
861
+ const keyByValue = new Map(entries.map((entry) => [entry.value, entry.key]));
862
+ const existingByKey = new Map(entries.map((entry) => [entry.key, entry.value]));
863
+ const usedKeys = new Set(entries.map((entry) => entry.key));
864
+ const entriesToAdd = [];
865
+ let changed = !exists;
866
+
867
+ for (const dictType of dictTypes) {
868
+ if (!dictType || keyByValue.has(dictType)) continue;
869
+ const key = buildUniqueDictRegistryKey(dictType, usedKeys, existingByKey);
870
+ const entry = { key, value: dictType };
871
+ entries.push(entry);
872
+ entriesToAdd.push(entry);
873
+ keyByValue.set(dictType, key);
874
+ existingByKey.set(key, dictType);
875
+ usedKeys.add(key);
876
+ changed = true;
877
+ }
878
+
879
+ const patchedContent = exists ? patchDictRegistryContent(currentContent, entriesToAdd) : renderDictRegistryContent(entries);
880
+ const content = patchedContent ? ensureDictRegistrySemanticHelpers(patchedContent) : null;
881
+ const needsSemanticHelpers = exists && Boolean(content) && content !== currentContent;
882
+
883
+ return {
884
+ path: registryPath,
885
+ entries,
886
+ keyByValue,
887
+ content,
888
+ isCompatible: !exists || Boolean(content),
889
+ needsWrite: changed || needsSemanticHelpers,
890
+ };
891
+ }
892
+
893
+ function ensureCrudSchemaSupportFile(frontendPath) {
894
894
  const schemaPath = path.join(frontendPath, 'src', 'utils', 'crudSchema.ts');
895
895
  const exists = fs.existsSync(schemaPath);
896
896
  const currentContent = exists ? readUtf8File(schemaPath) : '';
@@ -907,27 +907,27 @@ function ensureCrudSchemaSupportFile(frontendPath) {
907
907
  exists,
908
908
  isCompatible: hasCoreShape && supportsLabelKey,
909
909
  needsWrite: !exists || shouldUpgradeLegacy,
910
- };
911
- }
912
-
913
- function ensureCrudFactorySupportFile(frontendPath) {
914
- const factoryPath = path.join(frontendPath, 'src', 'api', 'common', 'crudFactory.ts');
915
- const exists = fs.existsSync(factoryPath);
916
- const currentContent = exists ? readUtf8File(factoryPath) : '';
917
- const isCompatible = !exists || currentContent.includes('export const createCrudApi');
918
-
919
- return {
920
- path: factoryPath,
921
- content: DEFAULT_CRUD_FACTORY_TEMPLATE,
922
- exists,
923
- isCompatible,
924
- needsWrite: !exists,
925
- };
926
- }
927
-
928
- function ensureDirectory(filePath) {
929
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
930
- }
910
+ };
911
+ }
912
+
913
+ function ensureCrudFactorySupportFile(frontendPath) {
914
+ const factoryPath = path.join(frontendPath, 'src', 'api', 'common', 'crudFactory.ts');
915
+ const exists = fs.existsSync(factoryPath);
916
+ const currentContent = exists ? readUtf8File(factoryPath) : '';
917
+ const isCompatible = !exists || currentContent.includes('export const createCrudApi');
918
+
919
+ return {
920
+ path: factoryPath,
921
+ content: DEFAULT_CRUD_FACTORY_TEMPLATE,
922
+ exists,
923
+ isCompatible,
924
+ needsWrite: !exists,
925
+ };
926
+ }
927
+
928
+ function ensureDirectory(filePath) {
929
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
930
+ }
931
931
 
932
932
  function writeSupportFile(filePath, content) {
933
933
  ensureDirectory(filePath);
@@ -950,94 +950,94 @@ function prepareSharedSupport(frontendPath, dictTypes, writeSupportFiles) {
950
950
  const normalizedDictTypes = [...new Set((dictTypes || []).filter(Boolean))];
951
951
  const dictRegistry = writeSupportFiles ? prepareDictRegistry(frontendPath, normalizedDictTypes) : buildVirtualDictRegistry(normalizedDictTypes);
952
952
  const crudSchemaPath = path.join(frontendPath, 'src', 'utils', 'crudSchema.ts');
953
- const crudSchema = writeSupportFiles
954
- ? ensureCrudSchemaSupportFile(frontendPath)
955
- : {
956
- path: crudSchemaPath,
953
+ const crudSchema = writeSupportFiles
954
+ ? ensureCrudSchemaSupportFile(frontendPath)
955
+ : {
956
+ path: crudSchemaPath,
957
957
  content: DEFAULT_CRUD_SCHEMA_TEMPLATE,
958
958
  exists: fs.existsSync(crudSchemaPath),
959
959
  isCompatible: true,
960
- needsWrite: false,
961
- writeEnabled: false,
962
- };
963
- const crudFactoryPath = path.join(frontendPath, 'src', 'api', 'common', 'crudFactory.ts');
964
- const crudFactory = writeSupportFiles
965
- ? ensureCrudFactorySupportFile(frontendPath)
966
- : {
967
- path: crudFactoryPath,
968
- content: DEFAULT_CRUD_FACTORY_TEMPLATE,
969
- exists: fs.existsSync(crudFactoryPath),
970
- isCompatible: true,
971
- needsWrite: false,
972
- writeEnabled: false,
973
- };
974
- return {
975
- dictRegistry,
976
- crudSchema,
977
- crudFactory,
978
- writeEnabled: Boolean(writeSupportFiles),
979
- };
980
- }
960
+ needsWrite: false,
961
+ writeEnabled: false,
962
+ };
963
+ const crudFactoryPath = path.join(frontendPath, 'src', 'api', 'common', 'crudFactory.ts');
964
+ const crudFactory = writeSupportFiles
965
+ ? ensureCrudFactorySupportFile(frontendPath)
966
+ : {
967
+ path: crudFactoryPath,
968
+ content: DEFAULT_CRUD_FACTORY_TEMPLATE,
969
+ exists: fs.existsSync(crudFactoryPath),
970
+ isCompatible: true,
971
+ needsWrite: false,
972
+ writeEnabled: false,
973
+ };
974
+ return {
975
+ dictRegistry,
976
+ crudSchema,
977
+ crudFactory,
978
+ writeEnabled: Boolean(writeSupportFiles),
979
+ };
980
+ }
981
981
 
982
982
  function maybeWriteSharedSupport(sharedSupport, writeToDisk) {
983
983
  if (!writeToDisk || !sharedSupport.writeEnabled) return;
984
984
 
985
- if (sharedSupport.crudSchema.needsWrite) {
986
- writeSupportFile(sharedSupport.crudSchema.path, sharedSupport.crudSchema.content);
987
- }
988
-
989
- if (sharedSupport.crudFactory.needsWrite) {
990
- if (!sharedSupport.crudFactory.isCompatible) {
991
- throw new Error(
992
- 'Detected an existing src/api/common/crudFactory.ts that MCP could not merge safely. ' +
993
- 'Please align the createCrudApi export manually before enabling writeSupportFiles.'
994
- );
995
- }
996
- writeSupportFile(sharedSupport.crudFactory.path, sharedSupport.crudFactory.content);
997
- }
998
-
999
- if (sharedSupport.dictRegistry.needsWrite) {
1000
- if (!sharedSupport.dictRegistry.isCompatible) {
1001
- throw new Error(
1002
- 'Detected an existing src/enums/dict-registry.ts that MCP could not merge safely. ' +
1003
- 'Please align the DictRegistry export shape manually before enabling writeSupportFiles.'
1004
- );
1005
- }
1006
- writeSupportFile(sharedSupport.dictRegistry.path, sharedSupport.dictRegistry.content);
1007
- }
1008
- }
985
+ if (sharedSupport.crudSchema.needsWrite) {
986
+ writeSupportFile(sharedSupport.crudSchema.path, sharedSupport.crudSchema.content);
987
+ }
988
+
989
+ if (sharedSupport.crudFactory.needsWrite) {
990
+ if (!sharedSupport.crudFactory.isCompatible) {
991
+ throw new Error(
992
+ 'Detected an existing src/api/common/crudFactory.ts that MCP could not merge safely. ' +
993
+ 'Please align the createCrudApi export manually before enabling writeSupportFiles.'
994
+ );
995
+ }
996
+ writeSupportFile(sharedSupport.crudFactory.path, sharedSupport.crudFactory.content);
997
+ }
998
+
999
+ if (sharedSupport.dictRegistry.needsWrite) {
1000
+ if (!sharedSupport.dictRegistry.isCompatible) {
1001
+ throw new Error(
1002
+ 'Detected an existing src/enums/dict-registry.ts that MCP could not merge safely. ' +
1003
+ 'Please align the DictRegistry export shape manually before enabling writeSupportFiles.'
1004
+ );
1005
+ }
1006
+ writeSupportFile(sharedSupport.dictRegistry.path, sharedSupport.dictRegistry.content);
1007
+ }
1008
+ }
1009
1009
 
1010
1010
  function buildSupportNote(sharedSupport, localeZhSupport) {
1011
1011
  const notes = [];
1012
1012
 
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
- }
1018
-
1019
- if (sharedSupport.crudSchema.exists && !sharedSupport.crudSchema.isCompatible) {
1020
- notes.push(
1021
- 'Detected an existing src/utils/crudSchema.ts that does not match the expected helper signature. ' +
1022
- 'MCP preserved the existing file and did not overwrite it. Generated pages now depend on that file being manually aligned.'
1023
- );
1024
- }
1025
-
1026
- if (sharedSupport.crudFactory.exists && !sharedSupport.crudFactory.isCompatible) {
1027
- notes.push(
1028
- 'Detected an existing src/api/common/crudFactory.ts that does not export createCrudApi. ' +
1029
- 'MCP preserved the existing file and did not overwrite it. Generated API modules now depend on that file being manually aligned.'
1030
- );
1031
- }
1032
-
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
- }
1039
-
1040
- if (localeZhSupport.exists && !localeZhSupport.isCompatible) {
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
+ }
1018
+
1019
+ if (sharedSupport.crudSchema.exists && !sharedSupport.crudSchema.isCompatible) {
1020
+ notes.push(
1021
+ 'Detected an existing src/utils/crudSchema.ts that does not match the expected helper signature. ' +
1022
+ 'MCP preserved the existing file and did not overwrite it. Generated pages now depend on that file being manually aligned.'
1023
+ );
1024
+ }
1025
+
1026
+ if (sharedSupport.crudFactory.exists && !sharedSupport.crudFactory.isCompatible) {
1027
+ notes.push(
1028
+ 'Detected an existing src/api/common/crudFactory.ts that does not export createCrudApi. ' +
1029
+ 'MCP preserved the existing file and did not overwrite it. Generated API modules now depend on that file being manually aligned.'
1030
+ );
1031
+ }
1032
+
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
+ }
1039
+
1040
+ if (localeZhSupport.exists && !localeZhSupport.isCompatible) {
1041
1041
  notes.push(
1042
1042
  `Detected an existing ${path.relative(localeZhSupport.frontendPath, localeZhSupport.path).replace(/\\/g, '/')} that MCP could not parse. ` +
1043
1043
  'The file was preserved and not updated, so new Chinese i18n keys may need to be merged manually.'
@@ -1077,21 +1077,21 @@ function mapFieldType(field) {
1077
1077
  return 'text';
1078
1078
  }
1079
1079
 
1080
- function normalizeStructuredFormType(value) {
1081
- const normalized = String(value || '').trim().toLowerCase();
1082
- if (!normalized) return '';
1083
- if (normalized === 'date') return 'date';
1084
- if (normalized === 'datetime') return 'datetime';
1085
- if (normalized === 'microme-operator') return 'microme-operator';
1086
- if (normalized === 'upload') {
1087
- return 'upload';
1088
- }
1089
- if (normalized === 'picker') {
1090
- return 'text';
1091
- }
1092
- if (['text', 'select', 'textarea', 'number'].includes(normalized)) {
1093
- return normalized;
1094
- }
1080
+ function normalizeStructuredFormType(value) {
1081
+ const normalized = String(value || '').trim().toLowerCase();
1082
+ if (!normalized) return '';
1083
+ if (normalized === 'date') return 'date';
1084
+ if (normalized === 'datetime') return 'datetime';
1085
+ if (normalized === 'microme-operator') return 'microme-operator';
1086
+ if (normalized === 'upload') {
1087
+ return 'upload';
1088
+ }
1089
+ if (normalized === 'picker') {
1090
+ return 'text';
1091
+ }
1092
+ if (['text', 'select', 'textarea', 'number'].includes(normalized)) {
1093
+ return normalized;
1094
+ }
1095
1095
  throw new Error('Unsupported explicit component/form type: ' + normalized);
1096
1096
  }
1097
1097
 
@@ -1241,53 +1241,53 @@ function normalizeStructuredSqlType(value) {
1241
1241
  if (['LOCALDATETIME', 'DATETIME', 'TIMESTAMP'].includes(upper) || ['\u65e5\u671f\u65f6\u95f4', '\u65f6\u95f4\u6233'].includes(normalized)) return 'DATETIME';
1242
1242
  return upper;
1243
1243
  }
1244
- function normalizeStructuredLengthAndScale(lengthValue, scaleValue) {
1245
- const parsed = splitLength(lengthValue);
1246
- const normalizedScale = scaleValue === undefined || scaleValue === null || scaleValue === '' ? parsed.scale : String(scaleValue).trim();
1247
- return {
1248
- length: parsed.length,
1249
- scale: normalizedScale || '',
1250
- };
1251
- }
1252
-
1253
- function parseOptionalOrder(value, label) {
1254
- if (value === undefined || value === null || value === '') return undefined;
1255
- const order = Number.parseInt(String(value), 10);
1256
- if (Number.isNaN(order) || order < 0) {
1257
- throw new Error(`${label} must be a positive integer when provided, or 0/empty when unspecified`);
1258
- }
1259
- if (order === 0) return undefined;
1260
- return order;
1261
- }
1262
-
1263
- function sortFieldsForForm(fields) {
1264
- const seenOrders = new Map();
1265
- fields
1266
- .filter((field) => field.formShow !== false && !field.primary)
1267
- .forEach((field) => {
1268
- if (field.formOrder === undefined) return;
1269
- const existing = seenOrders.get(field.formOrder);
1270
- if (existing) {
1271
- throw new Error(`Duplicate formOrder ${field.formOrder} on fields ${existing} and ${field.fieldName}`);
1272
- }
1273
- seenOrders.set(field.formOrder, field.fieldName);
1274
- });
1275
- return fields
1276
- .map((field, index) => ({ field, index }))
1277
- .sort((left, right) => {
1278
- const leftOrder = left.field.formOrder;
1279
- const rightOrder = right.field.formOrder;
1280
- if (leftOrder !== undefined && rightOrder !== undefined && leftOrder !== rightOrder) {
1281
- return leftOrder - rightOrder;
1282
- }
1283
- if (leftOrder !== undefined && rightOrder === undefined) return -1;
1284
- if (leftOrder === undefined && rightOrder !== undefined) return 1;
1285
- return left.index - right.index;
1286
- })
1287
- .map((item) => item.field);
1288
- }
1289
-
1290
- function normalizeStructuredField(inputField, index, contextLabel) {
1244
+ function normalizeStructuredLengthAndScale(lengthValue, scaleValue) {
1245
+ const parsed = splitLength(lengthValue);
1246
+ const normalizedScale = scaleValue === undefined || scaleValue === null || scaleValue === '' ? parsed.scale : String(scaleValue).trim();
1247
+ return {
1248
+ length: parsed.length,
1249
+ scale: normalizedScale || '',
1250
+ };
1251
+ }
1252
+
1253
+ function parseOptionalOrder(value, label) {
1254
+ if (value === undefined || value === null || value === '') return undefined;
1255
+ const order = Number.parseInt(String(value), 10);
1256
+ if (Number.isNaN(order) || order < 0) {
1257
+ throw new Error(`${label} must be a positive integer when provided, or 0/empty when unspecified`);
1258
+ }
1259
+ if (order === 0) return undefined;
1260
+ return order;
1261
+ }
1262
+
1263
+ function sortFieldsForForm(fields) {
1264
+ const seenOrders = new Map();
1265
+ fields
1266
+ .filter((field) => field.formShow !== false && !field.primary)
1267
+ .forEach((field) => {
1268
+ if (field.formOrder === undefined) return;
1269
+ const existing = seenOrders.get(field.formOrder);
1270
+ if (existing) {
1271
+ throw new Error(`Duplicate formOrder ${field.formOrder} on fields ${existing} and ${field.fieldName}`);
1272
+ }
1273
+ seenOrders.set(field.formOrder, field.fieldName);
1274
+ });
1275
+ return fields
1276
+ .map((field, index) => ({ field, index }))
1277
+ .sort((left, right) => {
1278
+ const leftOrder = left.field.formOrder;
1279
+ const rightOrder = right.field.formOrder;
1280
+ if (leftOrder !== undefined && rightOrder !== undefined && leftOrder !== rightOrder) {
1281
+ return leftOrder - rightOrder;
1282
+ }
1283
+ if (leftOrder !== undefined && rightOrder === undefined) return -1;
1284
+ if (leftOrder === undefined && rightOrder !== undefined) return 1;
1285
+ return left.index - right.index;
1286
+ })
1287
+ .map((item) => item.field);
1288
+ }
1289
+
1290
+ function normalizeStructuredField(inputField, index, contextLabel) {
1291
1291
  if (!inputField || typeof inputField !== 'object') {
1292
1292
  throw new Error(contextLabel + '[' + index + '] must be an object');
1293
1293
  }
@@ -1307,18 +1307,18 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1307
1307
  }
1308
1308
 
1309
1309
  const { length, scale } = normalizeStructuredLengthAndScale(inputField.length, inputField.scale);
1310
- const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
1311
- const formType = type === 'DATE' && explicitFormType === 'datetime' ? 'date' : explicitFormType;
1312
- const explicitQueryType =
1313
- inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
1314
- ? undefined
1315
- : Number.parseInt(String(inputField.queryType), 10);
1316
- const show = parseBooleanLike(inputField.show, true);
1317
- const listShow = parseBooleanLike(inputField.listShow, show);
1318
- const formShow = parseBooleanLike(inputField.formShow, show);
1319
- const formOrder = parseOptionalOrder(inputField.formOrder, contextLabel + '[' + index + '].formOrder');
1320
-
1321
- return {
1310
+ const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
1311
+ const formType = type === 'DATE' && explicitFormType === 'datetime' ? 'date' : explicitFormType;
1312
+ const explicitQueryType =
1313
+ inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
1314
+ ? undefined
1315
+ : Number.parseInt(String(inputField.queryType), 10);
1316
+ const show = parseBooleanLike(inputField.show, true);
1317
+ const listShow = parseBooleanLike(inputField.listShow, show);
1318
+ const formShow = parseBooleanLike(inputField.formShow, show);
1319
+ const formOrder = parseOptionalOrder(inputField.formOrder, contextLabel + '[' + index + '].formOrder');
1320
+
1321
+ return {
1322
1322
  fieldName,
1323
1323
  attrName: toCamelCase(fieldName),
1324
1324
  sqlType: type,
@@ -1327,16 +1327,16 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1327
1327
  comment: label,
1328
1328
  label,
1329
1329
  description: String(inputField.description || '').trim(),
1330
- formType,
1330
+ formType,
1331
1331
  dictType: normalizeDictType(inputField.dictType),
1332
1332
  notNull: parseBooleanLike(inputField.required, false),
1333
1333
  defaultValue: normalizeDefaultValue(inputField.defaultValue),
1334
- readonly: parseBooleanLike(inputField.readonly ?? inputField.disabled, false),
1335
- show,
1336
- listShow,
1337
- formShow,
1338
- formOrder,
1339
- smart: parseBooleanLike(inputField.smart, false),
1334
+ readonly: parseBooleanLike(inputField.readonly ?? inputField.disabled, false),
1335
+ show,
1336
+ listShow,
1337
+ formShow,
1338
+ formOrder,
1339
+ smart: parseBooleanLike(inputField.smart, false),
1340
1340
  queryType: Number.isNaN(explicitQueryType)
1341
1341
  ? undefined
1342
1342
  : explicitQueryType !== undefined
@@ -1543,110 +1543,110 @@ function rejectSemanticStageInputs(input) {
1543
1543
  }
1544
1544
  }
1545
1545
 
1546
- function validatePageTypeAndStyle(pageType, style) {
1547
- if (!pageType) return;
1548
- if (pageType === 'non_standard') {
1549
- throw new Error('non_standard pages are not supported by worsoft_codegen_local_generate_frontend');
1550
- }
1551
- if (pageType === 'business') {
1552
- if (style === 'single_table_dialog') {
1553
- throw new Error('Business pages must use jump-based styles. pageType=business does not support style=single_table_dialog; use single_table_jump for single-table business pages or master_child_jump for master-child business pages.');
1554
- }
1555
- return;
1556
- }
1557
- if (pageType !== 'dict') return;
1558
- if (style === 'single_table_jump' || style === 'master_child_jump') {
1559
- throw new Error(`Dict pages must use dialog-based styles. pageType=dict does not support style=${style}`);
1560
- }
1561
- }
1546
+ function validatePageTypeAndStyle(pageType, style) {
1547
+ if (!pageType) return;
1548
+ if (pageType === 'non_standard') {
1549
+ throw new Error('non_standard pages are not supported by worsoft_codegen_local_generate_frontend');
1550
+ }
1551
+ if (pageType === 'business') {
1552
+ if (style === 'single_table_dialog') {
1553
+ throw new Error('Business pages must use jump-based styles. pageType=business does not support style=single_table_dialog; use single_table_jump for single-table business pages or master_child_jump for master-child business pages.');
1554
+ }
1555
+ return;
1556
+ }
1557
+ if (pageType !== 'dict') return;
1558
+ if (style === 'single_table_jump' || style === 'master_child_jump') {
1559
+ throw new Error(`Dict pages must use dialog-based styles. pageType=dict does not support style=${style}`);
1560
+ }
1561
+ }
1562
1562
 
1563
1563
  function hasRuntimeSupport(stylePreset) {
1564
1564
  return Boolean(stylePreset.runtime && stylePreset.runtime.supported && stylePreset.runtime.templateDir);
1565
1565
  }
1566
1566
 
1567
- function normalizeFields(parsed) {
1568
- return parsed.fields.map((field) => ({ ...field, formType: field.formType || mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
1569
- }
1570
-
1571
- function sanitizeIdentifier(value, label) {
1572
- const name = String(value || '').trim();
1573
- if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
1574
- throw new Error(label + ' must be a valid JavaScript identifier');
1575
- }
1576
- return name;
1577
- }
1578
-
1579
- function normalizeExtraApis(inputExtraApis, currentTargetApiModule) {
1580
- if (inputExtraApis === undefined || inputExtraApis === null) return [];
1581
- if (!Array.isArray(inputExtraApis)) {
1582
- throw new Error('extraApis must be an array');
1583
- }
1584
-
1585
- const seen = new Set();
1586
- return inputExtraApis.map((item, index) => {
1587
- if (!item || typeof item !== 'object') {
1588
- throw new Error('extraApis[' + index + '] must be an object');
1589
- }
1590
- const functionName = sanitizeIdentifier(item.functionName, 'extraApis[' + index + '].functionName');
1591
- if (seen.has(functionName)) {
1592
- throw new Error('Duplicate extra API functionName found: ' + functionName);
1593
- }
1594
- seen.add(functionName);
1595
-
1596
- const method = String(item.method || '').trim().toLowerCase();
1597
- if (!['get', 'post', 'put', 'delete'].includes(method)) {
1598
- throw new Error('extraApis[' + index + '].method must be one of get, post, put, delete');
1599
- }
1600
- const url = normalizeExtraApiUrl(String(item.url || '').trim(), currentTargetApiModule);
1601
- if (!url) {
1602
- throw new Error('extraApis[' + index + '].url is required');
1603
- }
1604
- const description = String(item.description || '').trim();
1605
- if (!description) {
1606
- throw new Error('extraApis[' + index + '].description is required');
1607
- }
1608
- const targetApiModule = item.targetApiModule ? normalizeModuleName(String(item.targetApiModule)) : '';
1609
- if (targetApiModule && currentTargetApiModule && targetApiModule !== currentTargetApiModule) {
1610
- throw new Error(
1611
- 'extraApis[' +
1612
- index +
1613
- '].targetApiModule must equal current targetApiModule. Cross-module extra API generation is blocked to avoid overwriting other API files.'
1614
- );
1615
- }
1616
- const requestType = item.requestType ? String(item.requestType).trim() : method === 'get' || method === 'delete' ? 'params' : 'data';
1617
- if (!['params', 'data'].includes(requestType)) {
1618
- throw new Error('extraApis[' + index + '].requestType must be params or data');
1619
- }
1620
-
1621
- return {
1622
- functionName,
1623
- description,
1624
- url,
1625
- method,
1626
- requestType,
1627
- targetApiModule,
1628
- source: item.source ? String(item.source).trim() : '',
1629
- usedBy: item.usedBy ? String(item.usedBy).trim() : '',
1630
- };
1631
- });
1632
- }
1633
-
1634
- function normalizeExtraApiUrl(rawUrl, currentTargetApiModule) {
1635
- if (!rawUrl) return '';
1636
- if (/^https?:\/\//i.test(rawUrl)) return rawUrl;
1637
- let url = rawUrl.startsWith('/') ? rawUrl : '/' + rawUrl;
1638
- const moduleRoot = String(currentTargetApiModule || '').split('/')[0] || '';
1639
- if (moduleRoot && !url.startsWith('/' + moduleRoot + '/')) {
1640
- url = '/' + moduleRoot + url;
1641
- }
1642
- return url;
1643
- }
1644
-
1645
- function ensureFieldExists(fields, fieldName, tableName, role) {
1646
- const field = fields.find((item) => item.fieldName === fieldName);
1647
- if (!field) throw new Error(role + ' field "' + fieldName + '" was not found on table ' + tableName);
1648
- return field;
1649
- }
1567
+ function normalizeFields(parsed) {
1568
+ return parsed.fields.map((field) => ({ ...field, formType: field.formType || mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
1569
+ }
1570
+
1571
+ function sanitizeIdentifier(value, label) {
1572
+ const name = String(value || '').trim();
1573
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
1574
+ throw new Error(label + ' must be a valid JavaScript identifier');
1575
+ }
1576
+ return name;
1577
+ }
1578
+
1579
+ function normalizeExtraApis(inputExtraApis, currentTargetApiModule) {
1580
+ if (inputExtraApis === undefined || inputExtraApis === null) return [];
1581
+ if (!Array.isArray(inputExtraApis)) {
1582
+ throw new Error('extraApis must be an array');
1583
+ }
1584
+
1585
+ const seen = new Set();
1586
+ return inputExtraApis.map((item, index) => {
1587
+ if (!item || typeof item !== 'object') {
1588
+ throw new Error('extraApis[' + index + '] must be an object');
1589
+ }
1590
+ const functionName = sanitizeIdentifier(item.functionName, 'extraApis[' + index + '].functionName');
1591
+ if (seen.has(functionName)) {
1592
+ throw new Error('Duplicate extra API functionName found: ' + functionName);
1593
+ }
1594
+ seen.add(functionName);
1595
+
1596
+ const method = String(item.method || '').trim().toLowerCase();
1597
+ if (!['get', 'post', 'put', 'delete'].includes(method)) {
1598
+ throw new Error('extraApis[' + index + '].method must be one of get, post, put, delete');
1599
+ }
1600
+ const url = normalizeExtraApiUrl(String(item.url || '').trim(), currentTargetApiModule);
1601
+ if (!url) {
1602
+ throw new Error('extraApis[' + index + '].url is required');
1603
+ }
1604
+ const description = String(item.description || '').trim();
1605
+ if (!description) {
1606
+ throw new Error('extraApis[' + index + '].description is required');
1607
+ }
1608
+ const targetApiModule = item.targetApiModule ? normalizeModuleName(String(item.targetApiModule)) : '';
1609
+ if (targetApiModule && currentTargetApiModule && targetApiModule !== currentTargetApiModule) {
1610
+ throw new Error(
1611
+ 'extraApis[' +
1612
+ index +
1613
+ '].targetApiModule must equal current targetApiModule. Cross-module extra API generation is blocked to avoid overwriting other API files.'
1614
+ );
1615
+ }
1616
+ const requestType = item.requestType ? String(item.requestType).trim() : method === 'get' || method === 'delete' ? 'params' : 'data';
1617
+ if (!['params', 'data'].includes(requestType)) {
1618
+ throw new Error('extraApis[' + index + '].requestType must be params or data');
1619
+ }
1620
+
1621
+ return {
1622
+ functionName,
1623
+ description,
1624
+ url,
1625
+ method,
1626
+ requestType,
1627
+ targetApiModule,
1628
+ source: item.source ? String(item.source).trim() : '',
1629
+ usedBy: item.usedBy ? String(item.usedBy).trim() : '',
1630
+ };
1631
+ });
1632
+ }
1633
+
1634
+ function normalizeExtraApiUrl(rawUrl, currentTargetApiModule) {
1635
+ if (!rawUrl) return '';
1636
+ if (/^https?:\/\//i.test(rawUrl)) return rawUrl;
1637
+ let url = rawUrl.startsWith('/') ? rawUrl : '/' + rawUrl;
1638
+ const moduleRoot = String(currentTargetApiModule || '').split('/')[0] || '';
1639
+ if (moduleRoot && !url.startsWith('/' + moduleRoot + '/')) {
1640
+ url = '/' + moduleRoot + url;
1641
+ }
1642
+ return url;
1643
+ }
1644
+
1645
+ function ensureFieldExists(fields, fieldName, tableName, role) {
1646
+ const field = fields.find((item) => item.fieldName === fieldName);
1647
+ if (!field) throw new Error(role + ' field "' + fieldName + '" was not found on table ' + tableName);
1648
+ return field;
1649
+ }
1650
1650
 
1651
1651
  function detectStatusField(fields, explicitName) {
1652
1652
  if (explicitName) {
@@ -1665,9 +1665,9 @@ function buildMultiLevelModule(moduleInput, levelIndex) {
1665
1665
  const normalizedPk = moduleInput.primaryKey
1666
1666
  ? ensureFieldExists(fields, moduleInput.primaryKey, moduleInput.tableName, 'Primary key')
1667
1667
  : findPrimaryKeyFromStructuredFields(fields);
1668
- const optionFields = fields.filter((field) => field.fieldName !== normalizedPk.fieldName && !field.isAudit);
1669
- const visibleFields = sortFieldsForForm(optionFields.filter((field) => field.formShow !== false));
1670
- const listFields = optionFields.filter((field) => field.listShow !== false);
1668
+ const optionFields = fields.filter((field) => field.fieldName !== normalizedPk.fieldName && !field.isAudit);
1669
+ const visibleFields = sortFieldsForForm(optionFields.filter((field) => field.formShow !== false));
1670
+ const listFields = optionFields.filter((field) => field.listShow !== false);
1671
1671
  const statusField = detectStatusField(fields, moduleInput.statusField);
1672
1672
  const apiPath = moduleInput.apiPath || toCamelCase(moduleInput.tableName);
1673
1673
  const functionName = toCamelCase(moduleInput.tableName);
@@ -1758,10 +1758,10 @@ function buildMultiLevelDictModel(safeArgs) {
1758
1758
  targetI18nKey: resolvedTargets.targetI18nKey,
1759
1759
  frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
1760
1760
  style: safeArgs.style,
1761
- levels: builtLevels,
1762
- modules: allModules,
1763
- extraApis: normalizeExtraApis(safeArgs.extraApis, resolvedTargets.targetApiModule),
1764
- dictTypes,
1761
+ levels: builtLevels,
1762
+ modules: allModules,
1763
+ extraApis: normalizeExtraApis(safeArgs.extraApis, resolvedTargets.targetApiModule),
1764
+ dictTypes,
1765
1765
  pk: parentModule.pk,
1766
1766
  fields: parentModule.fields,
1767
1767
  optionFields: parentModule.optionFields,
@@ -1789,7 +1789,7 @@ function buildChildModels(safeArgs, mainFields, mainPk) {
1789
1789
  const childOptionFields = childFields.filter(
1790
1790
  (field) => field.fieldName !== childPk.fieldName && !field.isAudit && field.fieldName !== relation.childField
1791
1791
  );
1792
- const childVisibleFields = sortFieldsForForm(childOptionFields.filter((field) => field.formShow !== false));
1792
+ const childVisibleFields = sortFieldsForForm(childOptionFields.filter((field) => field.formShow !== false));
1793
1793
  const payloadField = relation.payloadField;
1794
1794
  const listName = payloadField;
1795
1795
 
@@ -1812,31 +1812,31 @@ function buildChildModels(safeArgs, mainFields, mainPk) {
1812
1812
  });
1813
1813
  }
1814
1814
 
1815
- function buildModel(safeArgs) {
1816
- if (safeArgs.style === 'multi_level_dict') {
1817
- return buildMultiLevelDictModel(safeArgs);
1818
- }
1819
-
1820
- const pkField = findPrimaryKeyFromStructuredFields(safeArgs.fields);
1821
- const fields = normalizeFields({
1822
- fields: safeArgs.fields,
1823
- });
1824
- const optionFields = fields.filter((field) => field.fieldName !== pkField.fieldName && !field.isAudit);
1825
- const visibleFields = sortFieldsForForm(optionFields.filter((field) => field.formShow !== false));
1826
- const listFields = optionFields.filter((field) => field.listShow !== false);
1827
- const gridFields = listFields.slice(0, 8);
1828
- const statusField = detectStatusField(fields, safeArgs.statusField);
1829
- const children = buildChildModels(safeArgs, fields, pkField);
1830
- const childDictTypes = children.flatMap((child) => child.optionFields.map((field) => field.dictType).filter(Boolean));
1831
- const dictTypes = [...new Set([...optionFields.map((field) => field.dictType).filter(Boolean), ...childDictTypes])];
1815
+ function buildModel(safeArgs) {
1816
+ if (safeArgs.style === 'multi_level_dict') {
1817
+ return buildMultiLevelDictModel(safeArgs);
1818
+ }
1819
+
1820
+ const pkField = findPrimaryKeyFromStructuredFields(safeArgs.fields);
1821
+ const fields = normalizeFields({
1822
+ fields: safeArgs.fields,
1823
+ });
1824
+ const optionFields = fields.filter((field) => field.fieldName !== pkField.fieldName && !field.isAudit);
1825
+ const visibleFields = sortFieldsForForm(optionFields.filter((field) => field.formShow !== false));
1826
+ const listFields = optionFields.filter((field) => field.listShow !== false);
1827
+ const gridFields = listFields.slice(0, 8);
1828
+ const statusField = detectStatusField(fields, safeArgs.statusField);
1829
+ const children = buildChildModels(safeArgs, fields, pkField);
1830
+ const childDictTypes = children.flatMap((child) => child.optionFields.map((field) => field.dictType).filter(Boolean));
1831
+ const dictTypes = [...new Set([...optionFields.map((field) => field.dictType).filter(Boolean), ...childDictTypes])];
1832
1832
 
1833
1833
  const derivedFunctionName = toCamelCase(safeArgs.tableName);
1834
1834
  const apiPath = safeArgs.apiPath || derivedFunctionName;
1835
- const resolvedTargets = resolveGenerationTargets({
1836
- moduleName: safeArgs.moduleName,
1837
- functionName: derivedFunctionName,
1838
- apiPath,
1839
- targetViewDir: safeArgs.targetViewDir,
1835
+ const resolvedTargets = resolveGenerationTargets({
1836
+ moduleName: safeArgs.moduleName,
1837
+ functionName: derivedFunctionName,
1838
+ apiPath,
1839
+ targetViewDir: safeArgs.targetViewDir,
1840
1840
  targetApiModule: safeArgs.targetApiModule,
1841
1841
  targetI18nKey: safeArgs.targetI18nKey,
1842
1842
  });
@@ -1855,18 +1855,18 @@ function buildModel(safeArgs) {
1855
1855
  pk: pkField,
1856
1856
  fields,
1857
1857
  optionFields,
1858
- visibleFields,
1859
- listFields,
1860
- gridFields,
1861
- statusField: statusField ? statusField.fieldName : '',
1862
- statusDictType: statusField ? statusField.dictType || '' : '',
1863
- dictTypes,
1864
- frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
1865
- style: safeArgs.style,
1866
- extraApis: normalizeExtraApis(safeArgs.extraApis, resolvedTargets.targetApiModule),
1867
- children,
1868
- };
1869
- }
1858
+ visibleFields,
1859
+ listFields,
1860
+ gridFields,
1861
+ statusField: statusField ? statusField.fieldName : '',
1862
+ statusDictType: statusField ? statusField.dictType || '' : '',
1863
+ dictTypes,
1864
+ frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
1865
+ style: safeArgs.style,
1866
+ extraApis: normalizeExtraApis(safeArgs.extraApis, resolvedTargets.targetApiModule),
1867
+ children,
1868
+ };
1869
+ }
1870
1870
 
1871
1871
  function renderTemplate(templateText, replacements) {
1872
1872
  let output = templateText;
@@ -1894,29 +1894,29 @@ function renderFormField(field) {
1894
1894
  ].join('\n');
1895
1895
  }
1896
1896
 
1897
- if (field.formType === 'number') {
1898
- const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
1899
- const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
1900
- return [
1901
- ' <el-col :span="12" class="mb20">',
1897
+ if (field.formType === 'number') {
1898
+ const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
1899
+ const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
1900
+ return [
1901
+ ' <el-col :span="12" class="mb20">',
1902
1902
  ` <el-form-item label="${label}" prop="${prop}">`,
1903
1903
  ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="${inputPlaceholderExpr}" style="width: 100%" />`,
1904
1904
  ' </el-form-item>',
1905
1905
  ' </el-col>',
1906
- ].join('\n');
1907
- }
1908
-
1909
- if (field.formType === 'microme-operator') {
1910
- return [
1911
- ' <el-col :span="12" class="mb20">',
1912
- ` <el-form-item label="${label}" prop="${prop}">`,
1913
- ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="${inputPlaceholderExpr}" />`,
1914
- ' </el-form-item>',
1915
- ' </el-col>',
1916
- ].join('\n');
1917
- }
1918
-
1919
- if (field.formType === 'datetime' || field.formType === 'date') {
1906
+ ].join('\n');
1907
+ }
1908
+
1909
+ if (field.formType === 'microme-operator') {
1910
+ return [
1911
+ ' <el-col :span="12" class="mb20">',
1912
+ ` <el-form-item label="${label}" prop="${prop}">`,
1913
+ ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="${inputPlaceholderExpr}" />`,
1914
+ ' </el-form-item>',
1915
+ ' </el-col>',
1916
+ ].join('\n');
1917
+ }
1918
+
1919
+ if (field.formType === 'datetime' || field.formType === 'date') {
1920
1920
  const pickerType = field.formType === 'datetime' ? 'datetime' : 'date';
1921
1921
  const formatName = field.formType === 'datetime' ? 'dateTimeStr' : 'dateStr';
1922
1922
  return [
@@ -1961,35 +1961,35 @@ function renderTableColumn(field) {
1961
1961
  return ` <el-table-column prop="${field.attrName}" label="${label}" show-overflow-tooltip />`;
1962
1962
  }
1963
1963
 
1964
- function renderChildTableColumn(field, childListName) {
1965
- const rules = field.notNull ? ` :rules="[{ required: true, trigger: 'blur' }]"` : '';
1966
- const labelExpr = `getChildFieldLabel('${childListName}', '${field.attrName}')`;
1967
- const inputPlaceholderExpr = `formInputPlaceholder(${labelExpr}, ${renderDisabledBoolV2(field)})`;
1968
- const selectPlaceholderExpr = `formSelectPlaceholder(${labelExpr}, ${renderDisabledBoolV2(field)})`;
1969
- const disabledAttr = renderDisabledAttrV2(field);
1970
-
1971
- let control = ` <el-input v-model="row.${field.attrName}" :placeholder="${inputPlaceholderExpr}"${disabledAttr} />`;
1972
- if (field.formType === 'upload') {
1973
- control = ` <UploadFile v-model="row.${field.attrName}"${disabledAttr} />`;
1974
- } else if (field.formType === 'select' && field.dictType) {
1975
- control = [
1976
- ` <el-select v-model="row.${field.attrName}" :placeholder="${selectPlaceholderExpr}" style="width: 100%"${disabledAttr}>`,
1977
- ` <el-option v-for="item in ${field.dictType}" :key="item.value" :label="item.label" :value="item.value" />`,
1978
- ' </el-select>',
1979
- ].join('\n');
1980
- } else if (field.formType === 'number') {
1981
- const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
1982
- const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
1983
- control = ` <el-input-number v-model="row.${field.attrName}" :min="0"${max}${precision} :placeholder="${inputPlaceholderExpr}" style="width: 100%"${disabledAttr} />`;
1984
- } else if (field.formType === 'microme-operator') {
1985
- control = ` <MicromeOperator v-model="row.${field.attrName}"${renderMicromeFormatAttr(field)} :placeholder="${inputPlaceholderExpr}"${disabledAttr} />`;
1986
- }
1987
-
1988
- return [
1989
- ` <el-table-column :label="${labelExpr}" prop="${field.attrName}">`,
1990
- ' <template #default="{ row, $index }">',
1991
- ` <el-form-item :prop="\`${childListName}.\${$index}.${field.attrName}\`"${rules}>`,
1992
- control,
1964
+ function renderChildTableColumn(field, childListName) {
1965
+ const rules = field.notNull ? ` :rules="[{ required: true, trigger: 'blur' }]"` : '';
1966
+ const labelExpr = `getChildFieldLabel('${childListName}', '${field.attrName}')`;
1967
+ const inputPlaceholderExpr = `formInputPlaceholder(${labelExpr}, ${renderDisabledBoolV2(field)})`;
1968
+ const selectPlaceholderExpr = `formSelectPlaceholder(${labelExpr}, ${renderDisabledBoolV2(field)})`;
1969
+ const disabledAttr = renderDisabledAttrV2(field);
1970
+
1971
+ let control = ` <el-input v-model="row.${field.attrName}" :placeholder="${inputPlaceholderExpr}"${disabledAttr} />`;
1972
+ if (field.formType === 'upload') {
1973
+ control = ` <UploadFile v-model="row.${field.attrName}"${disabledAttr} />`;
1974
+ } else if (field.formType === 'select' && field.dictType) {
1975
+ control = [
1976
+ ` <el-select v-model="row.${field.attrName}" :placeholder="${selectPlaceholderExpr}" style="width: 100%"${disabledAttr}>`,
1977
+ ` <el-option v-for="item in getDictOptions('${field.dictType}')" :key="item.value" :label="item.label" :value="item.value" />`,
1978
+ ' </el-select>',
1979
+ ].join('\n');
1980
+ } else if (field.formType === 'number') {
1981
+ const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
1982
+ const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
1983
+ control = ` <el-input-number v-model="row.${field.attrName}" :min="0"${max}${precision} :placeholder="${inputPlaceholderExpr}" style="width: 100%"${disabledAttr} />`;
1984
+ } else if (field.formType === 'microme-operator') {
1985
+ control = ` <MicromeOperator v-model="row.${field.attrName}"${renderMicromeFormatAttr(field)} :placeholder="${inputPlaceholderExpr}"${disabledAttr} />`;
1986
+ }
1987
+
1988
+ return [
1989
+ ` <el-table-column :label="${labelExpr}" prop="${field.attrName}">`,
1990
+ ' <template #default="{ row, $index }">',
1991
+ ` <el-form-item :prop="\`${childListName}.\${$index}.${field.attrName}\`"${rules}>`,
1992
+ control,
1993
1993
  ' </el-form-item>',
1994
1994
  ' </template>',
1995
1995
  ' </el-table-column>',
@@ -2006,15 +2006,15 @@ function renderFilterType(field) {
2006
2006
  return ` ${field.attrName}: 30,`;
2007
2007
  }
2008
2008
 
2009
- function renderDefaultLine(field) {
2010
- if (field.attrName === 'createUserId') return ` ${field.attrName}: Session.getUserId(),`;
2011
- if (field.attrName === 'createUser') return ` ${field.attrName}: Session.getUsername(),`;
2012
- if (field.attrName === 'billDate') return ` ${field.attrName}: moment(new Date()).format('YYYY-MM-DD'),`;
2013
- if (field.attrName === 'billStateId' || field.fieldName === 'bill_state_id') return ` ${field.attrName}: '0',`;
2014
- if (field.attrName === 'createTime') return ` ${field.attrName}: moment(new Date()).format('YYYY-MM-DD HH:mm:ss'),`;
2015
- if (field.formType === 'number' || field.formType === 'microme-operator') return ` ${field.attrName}: 0,`;
2016
- return ` ${field.attrName}: '',`;
2017
- }
2009
+ function renderDefaultLine(field) {
2010
+ if (field.attrName === 'createUserId') return ` ${field.attrName}: Session.getUserId(),`;
2011
+ if (field.attrName === 'createUser') return ` ${field.attrName}: Session.getUsername(),`;
2012
+ if (field.attrName === 'billDate') return ` ${field.attrName}: moment(new Date()).format('YYYY-MM-DD'),`;
2013
+ if (field.attrName === 'billStateId' || field.fieldName === 'bill_state_id') return ` ${field.attrName}: '0',`;
2014
+ if (field.attrName === 'createTime') return ` ${field.attrName}: moment(new Date()).format('YYYY-MM-DD HH:mm:ss'),`;
2015
+ if (field.formType === 'number' || field.formType === 'microme-operator') return ` ${field.attrName}: 0,`;
2016
+ return ` ${field.attrName}: '',`;
2017
+ }
2018
2018
 
2019
2019
  function renderFormRulesV2(fields) {
2020
2020
  return fields
@@ -2031,39 +2031,39 @@ function renderFormDefaults(model) {
2031
2031
  return lines.join('\n');
2032
2032
  }
2033
2033
 
2034
- function renderChildTempDefaults(childModel) {
2035
- if (!childModel) return '';
2036
- const lines = childModel.fields
2037
- .filter((field) => !['version', 'tenantId'].includes(field.attrName))
2038
- .map(renderDefaultLine);
2039
- lines.push(` version: 1,`);
2040
- lines.push(` tenantId: Session.getTenant(),`);
2041
- return lines.join('\n');
2042
- }
2034
+ function renderChildTempDefaults(childModel) {
2035
+ if (!childModel) return '';
2036
+ const lines = childModel.fields
2037
+ .filter((field) => !['version', 'tenantId'].includes(field.attrName))
2038
+ .map(renderDefaultLine);
2039
+ lines.push(` version: 1,`);
2040
+ lines.push(` tenantId: Session.getTenant(),`);
2041
+ return lines.join('\n');
2042
+ }
2043
2043
 
2044
2044
  function renderChildListDefaultLine(childModel) {
2045
2045
  return ` ${childModel.listName}: [],`;
2046
2046
  }
2047
2047
 
2048
- function renderChildTempDeclaration(childModel) {
2049
- return [
2050
- `const childTemp${childModel.className} = reactive({`,
2051
- renderChildTempDefaults(childModel),
2052
- '});',
2053
- ].join('\n');
2054
- }
2055
-
2056
- function renderChildSection(childModel, childCount) {
2057
- const deleteExpression = `deleteChild(obj, '${childModel.pk.attrName}')`;
2058
-
2059
- return [
2060
- ' <el-col :span="24" class="mb20">',
2061
- ` <!-- 子表区域:${sanitizeHtmlComment(childModel.comment || childModel.tableName)} -->`,
2062
- ` <div class="mb10" style="font-weight: 600;">{{ childSectionTitle('${childModel.listName}') }}</div>`,
2063
- ' <!-- 子表编辑表格:支持新增行、删除行和行内字段编辑 -->',
2064
- ` <sc-form-table v-model="form.${childModel.listName}" :addTemplate="childTemp${childModel.className}" @delete="(obj) => ${deleteExpression}" :placeholder="t('common.noData')">`,
2065
- childModel.visibleFields.map((field) => renderChildTableColumn(field, childModel.listName)).join('\n'),
2066
- ' </sc-form-table>',
2048
+ function renderChildTempDeclaration(childModel) {
2049
+ return [
2050
+ `const childTemp${childModel.className} = reactive({`,
2051
+ renderChildTempDefaults(childModel),
2052
+ '});',
2053
+ ].join('\n');
2054
+ }
2055
+
2056
+ function renderChildSection(childModel, childCount) {
2057
+ const deleteExpression = `deleteChild(obj, '${childModel.pk.attrName}')`;
2058
+
2059
+ return [
2060
+ ' <el-col :span="24" class="mb20">',
2061
+ ` <!-- 子表区域:${sanitizeHtmlComment(childModel.comment || childModel.tableName)} -->`,
2062
+ ` <div class="mb10" style="font-weight: 600;">{{ childSectionTitle('${childModel.listName}') }}</div>`,
2063
+ ' <!-- 子表编辑表格:支持新增行、删除行和行内字段编辑 -->',
2064
+ ` <sc-form-table v-model="form.${childModel.listName}" :addTemplate="childTemp${childModel.className}" @delete="(obj) => ${deleteExpression}" :placeholder="t('common.noData')">`,
2065
+ childModel.visibleFields.map((field) => renderChildTableColumn(field, childModel.listName)).join('\n'),
2066
+ ' </sc-form-table>',
2067
2067
  ' </el-col>',
2068
2068
  ].join('\n');
2069
2069
  }
@@ -2149,31 +2149,32 @@ function getDefaultOptionFieldWidthV2(field) {
2149
2149
 
2150
2150
  return '100';
2151
2151
  }
2152
- function renderOptionFieldV2(field, labelKey, dictRegistryRefs, indent = ' ') {
2153
- const fallbackLabel = stripDictAnnotation(field.comment).replace(/'/g, "\\'");
2154
- const parts = [`key: '${field.attrName}'`, `labelKey: '${labelKey}'`, `label: '${fallbackLabel}'`];
2155
- const width = getDefaultOptionFieldWidthV2(field);
2152
+ function renderOptionFieldV2(field, labelKey, dictRegistryRefs, indent = ' ') {
2153
+ const fallbackLabel = stripDictAnnotation(field.comment).replace(/'/g, "\\'");
2154
+ const parts = [`key: '${field.attrName}'`, `labelKey: '${labelKey}'`, `label: '${fallbackLabel}'`];
2155
+ const prdWidth = field.width ? field.width.replace('px', '') : null;
2156
+ const width = prdWidth || getDefaultOptionFieldWidthV2(field);
2156
2157
 
2157
2158
  if (width !== '120') {
2158
2159
  parts.push(`width: '${width}'`);
2159
2160
  }
2160
-
2161
- if (field.dictType) {
2162
- parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
2163
- parts.push('queryType: 30');
2164
- }
2165
- if (field.smart) {
2166
- parts.push('smart: true');
2167
- }
2168
- if (field.show === false) {
2169
- parts.push('show: false');
2170
- }
2171
- if (field.listShow !== field.show) {
2172
- parts.push(`listShow: ${field.listShow ? 'true' : 'false'}`);
2173
- }
2174
- if (field.formShow !== field.show) {
2175
- parts.push(`formShow: ${field.formShow ? 'true' : 'false'}`);
2176
- }
2161
+
2162
+ if (field.dictType) {
2163
+ parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
2164
+ parts.push('queryType: 30');
2165
+ }
2166
+ if (field.smart) {
2167
+ parts.push('smart: true');
2168
+ }
2169
+ if (field.show === false) {
2170
+ parts.push('show: false');
2171
+ }
2172
+ if (field.listShow !== field.show) {
2173
+ parts.push(`listShow: ${field.listShow ? 'true' : 'false'}`);
2174
+ }
2175
+ if (field.formShow !== field.show) {
2176
+ parts.push(`formShow: ${field.formShow ? 'true' : 'false'}`);
2177
+ }
2177
2178
 
2178
2179
  return `${indent}{ ${parts.join(', ')} },`;
2179
2180
  }
@@ -2202,23 +2203,23 @@ function renderTextareaMaxlengthAttrsV2(field) {
2202
2203
  return ` :maxlength="${field.length}" show-word-limit`;
2203
2204
  }
2204
2205
 
2205
- function renderDisabledAttrV2(field) {
2206
- return field.readonly ? ' disabled' : '';
2207
- }
2208
-
2209
- function renderDisabledBoolV2(field) {
2210
- return field.readonly ? 'true' : 'false';
2211
- }
2212
-
2213
- function renderMicromeFormatAttr(field) {
2214
- const length = Number.parseInt(String(field.length || ''), 10);
2215
- const scale = Number.parseInt(String(field.scale || ''), 10);
2216
- if (!length || Number.isNaN(length) || Number.isNaN(scale) || scale < 0) return '';
2217
- const integerLength = Math.max(length - scale, 1);
2218
- return ` format="${integerLength}-${scale}"`;
2219
- }
2220
-
2221
- function isAttachmentLikeField(field) {
2206
+ function renderDisabledAttrV2(field) {
2207
+ return field.readonly ? ' disabled' : '';
2208
+ }
2209
+
2210
+ function renderDisabledBoolV2(field) {
2211
+ return field.readonly ? 'true' : 'false';
2212
+ }
2213
+
2214
+ function renderMicromeFormatAttr(field) {
2215
+ const length = Number.parseInt(String(field.length || ''), 10);
2216
+ const scale = Number.parseInt(String(field.scale || ''), 10);
2217
+ if (!length || Number.isNaN(length) || Number.isNaN(scale) || scale < 0) return '';
2218
+ const integerLength = Math.max(length - scale, 1);
2219
+ return ` format="${integerLength}-${scale}"`;
2220
+ }
2221
+
2222
+ function isAttachmentLikeField(field) {
2222
2223
  const fieldName = String(field?.fieldName || field?.attrName || '').toLowerCase();
2223
2224
  const comment = String(field?.comment || field?.description || '').toLowerCase();
2224
2225
  return fieldName.includes('attachment') || comment.includes('\u9644\u4ef6') || comment.includes('\u4e0a\u4f20');
@@ -2229,30 +2230,30 @@ function renderFieldCommentV2(field, indent = ' ') {
2229
2230
  return indent + '<!-- ' + label + ' -->';
2230
2231
  }
2231
2232
 
2232
- function renderFormFieldV2(field) {
2233
- const prop = field.attrName;
2234
- const labelExpr = `getMasterFieldLabel('${prop}')`;
2235
- const dictExpr = `getMasterFieldMeta('${prop}')?.dictType`;
2236
- const disabledAttr = renderDisabledAttrV2(field);
2237
- const disabledBool = renderDisabledBoolV2(field);
2238
-
2239
- if (field.formType === 'upload') {
2240
- return [
2241
- renderFieldCommentV2(field),
2242
- ` <el-col :span="24" class="mb20">`,
2243
- ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2244
- ` <UploadFile v-model="form.${prop}"${disabledAttr} />`,
2245
- ' </el-form-item>',
2246
- ' </el-col>',
2247
- ].join('\n');
2248
- }
2249
-
2250
- if (field.formType === 'select') {
2251
- return [
2252
- renderFieldCommentV2(field),
2253
- ` <el-col :span="12" class="mb20">`,
2233
+ function renderFormFieldV2(field) {
2234
+ const prop = field.attrName;
2235
+ const labelExpr = `getMasterFieldLabel('${prop}')`;
2236
+ const dictExpr = `getMasterFieldMeta('${prop}')?.dictType`;
2237
+ const disabledAttr = renderDisabledAttrV2(field);
2238
+ const disabledBool = renderDisabledBoolV2(field);
2239
+
2240
+ if (field.formType === 'upload') {
2241
+ return [
2242
+ renderFieldCommentV2(field),
2243
+ ` <el-col :span="24" class="mb20">`,
2244
+ ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2245
+ ` <UploadFile v-model="form.${prop}"${disabledAttr} />`,
2246
+ ' </el-form-item>',
2247
+ ' </el-col>',
2248
+ ].join('\n');
2249
+ }
2250
+
2251
+ if (field.formType === 'select') {
2252
+ return [
2253
+ renderFieldCommentV2(field),
2254
+ ` <el-col :span="12" class="mb20">`,
2254
2255
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2255
- ` <el-select v-model="form.${prop}" :placeholder="formSelectPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr}>`,
2256
+ ` <el-select v-model="form.${prop}" :placeholder="formSelectPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr}>`,
2256
2257
  ` <el-option v-for="item in getDictOptions(${dictExpr})" :key="item.value" :label="item.label" :value="item.value" />`,
2257
2258
  ' </el-select>',
2258
2259
  ' </el-form-item>',
@@ -2260,49 +2261,49 @@ function renderFormFieldV2(field) {
2260
2261
  ].join('\n');
2261
2262
  }
2262
2263
 
2263
- if (field.formType === 'number') {
2264
- const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
2265
- const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
2266
- return [
2267
- renderFieldCommentV2(field),
2264
+ if (field.formType === 'number') {
2265
+ const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
2266
+ const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
2267
+ return [
2268
+ renderFieldCommentV2(field),
2269
+ ` <el-col :span="12" class="mb20">`,
2270
+ ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2271
+ ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr} />`,
2272
+ ' </el-form-item>',
2273
+ ' </el-col>',
2274
+ ].join('\n');
2275
+ }
2276
+
2277
+ if (field.formType === 'microme-operator') {
2278
+ return [
2279
+ renderFieldCommentV2(field),
2268
2280
  ` <el-col :span="12" class="mb20">`,
2269
2281
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2270
- ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr} />`,
2282
+ ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${disabledAttr} />`,
2271
2283
  ' </el-form-item>',
2272
2284
  ' </el-col>',
2273
- ].join('\n');
2274
- }
2275
-
2276
- if (field.formType === 'microme-operator') {
2277
- return [
2278
- renderFieldCommentV2(field),
2279
- ` <el-col :span="12" class="mb20">`,
2280
- ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2281
- ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${disabledAttr} />`,
2282
- ' </el-form-item>',
2283
- ' </el-col>',
2284
- ].join('\n');
2285
- }
2286
-
2287
- if (field.formType === 'microme-operator') {
2288
- return [
2289
- renderFieldCommentV2(field),
2290
- ' <el-col :span="12" class="mb20">',
2291
- ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2292
- ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${disabledAttr} />`,
2293
- ' </el-form-item>',
2294
- ' </el-col>',
2295
- ].join('\n');
2296
- }
2297
-
2298
- if (field.formType === 'datetime' || field.formType === 'date') {
2285
+ ].join('\n');
2286
+ }
2287
+
2288
+ if (field.formType === 'microme-operator') {
2289
+ return [
2290
+ renderFieldCommentV2(field),
2291
+ ' <el-col :span="12" class="mb20">',
2292
+ ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2293
+ ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${disabledAttr} />`,
2294
+ ' </el-form-item>',
2295
+ ' </el-col>',
2296
+ ].join('\n');
2297
+ }
2298
+
2299
+ if (field.formType === 'datetime' || field.formType === 'date') {
2299
2300
  const pickerType = field.formType === 'datetime' ? 'datetime' : 'date';
2300
2301
  const formatName = field.formType === 'datetime' ? 'dateTimeStr' : 'dateStr';
2301
2302
  return [
2302
2303
  renderFieldCommentV2(field),
2303
2304
  ` <el-col :span="12" class="mb20">`,
2304
2305
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2305
- ` <el-date-picker type="${pickerType}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"${disabledAttr}></el-date-picker>`,
2306
+ ` <el-date-picker type="${pickerType}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"${disabledAttr}></el-date-picker>`,
2306
2307
  ' </el-form-item>',
2307
2308
  ' </el-col>',
2308
2309
  ].join('\n');
@@ -2314,7 +2315,7 @@ function renderFormFieldV2(field) {
2314
2315
  renderFieldCommentV2(field),
2315
2316
  ` <el-col :span="24" class="mb20">`,
2316
2317
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2317
- ` <el-input type="textarea" v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${textareaAttrs}${disabledAttr} />`,
2318
+ ` <el-input type="textarea" v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${textareaAttrs}${disabledAttr} />`,
2318
2319
  ' </el-form-item>',
2319
2320
  ' </el-col>',
2320
2321
  ].join('\n');
@@ -2325,29 +2326,29 @@ function renderFormFieldV2(field) {
2325
2326
  renderFieldCommentV2(field),
2326
2327
  ` <el-col :span="12" class="mb20">`,
2327
2328
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2328
- ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${maxlengthAttr}${disabledAttr} />`,
2329
+ ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${maxlengthAttr}${disabledAttr} />`,
2329
2330
  ' </el-form-item>',
2330
2331
  ' </el-col>',
2331
2332
  ].join('\n');
2332
2333
  }
2333
2334
 
2334
- function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs, indent = ' ') {
2335
- const parts = [
2336
- `key: '${field.attrName}'`,
2337
- `labelKey: '${buildMultiLevelFieldLabelKey(model, moduleModel, field)}'`,
2335
+ function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs, indent = ' ') {
2336
+ const parts = [
2337
+ `key: '${field.attrName}'`,
2338
+ `labelKey: '${buildMultiLevelFieldLabelKey(model, moduleModel, field)}'`,
2338
2339
  ];
2339
- const width = getDefaultOptionFieldWidthV2(field);
2340
- if (width) parts.push(`width: '${width}'`);
2341
- if (field.show === false) parts.push('show: false');
2342
- if (field.listShow !== field.show) parts.push(`listShow: ${field.listShow ? 'true' : 'false'}`);
2343
- if (field.formShow !== field.show) parts.push(`formShow: ${field.formShow ? 'true' : 'false'}`);
2344
- if (field.smart) parts.push('smart: true');
2345
- if (field.dictType) {
2346
- parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
2347
- parts.push('queryType: 30');
2348
- }
2349
- return `${indent}{ ${parts.join(', ')} },`;
2350
- }
2340
+ const width = getDefaultOptionFieldWidthV2(field);
2341
+ if (width) parts.push(`width: '${width}'`);
2342
+ if (field.show === false) parts.push('show: false');
2343
+ if (field.listShow !== field.show) parts.push(`listShow: ${field.listShow ? 'true' : 'false'}`);
2344
+ if (field.formShow !== field.show) parts.push(`formShow: ${field.formShow ? 'true' : 'false'}`);
2345
+ if (field.smart) parts.push('smart: true');
2346
+ if (field.dictType) {
2347
+ parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
2348
+ parts.push('queryType: 30');
2349
+ }
2350
+ return `${indent}{ ${parts.join(', ')} },`;
2351
+ }
2351
2352
 
2352
2353
  function renderMultiLevelModuleDefinition(model, moduleModel, dictRegistryRefs) {
2353
2354
  const moduleApiPath = buildApiRoutePath(model.moduleName, moduleModel.apiPath);
@@ -2376,17 +2377,17 @@ function renderMultiLevelLevelConfig(level) {
2376
2377
  return ` { levelIndex: ${level.levelIndex}, position: '${level.position}', moduleKeys: [${level.modules.map((moduleModel) => `'${moduleModel.key}'`).join(', ')}] },`;
2377
2378
  }
2378
2379
 
2379
- function renderMultiLevelOptionsTs(model, dictRegistryRefs) {
2380
- return [
2381
- "// schema 字段元数据类型",
2382
- "import type { FieldMeta } from '/@/utils/crudSchema';",
2383
- "// 多层字典页面配置类型",
2384
- "import type { MultiLevelDictFieldConfig, MultiLevelDictLevelConfig, MultiLevelDictModuleConfig, MultiLevelDictModuleDefinition } from '/@/types/multiLevelDict';",
2385
- "// 字典注册表",
2386
- "import { DictRegistry } from '/@/enums/dict-registry';",
2387
- '',
2388
- '// 将字段数组转换为字段元数据映射',
2389
- 'const toFieldMetaMap = (fields: MultiLevelDictFieldConfig[]) =>',
2380
+ function renderMultiLevelOptionsTs(model, dictRegistryRefs) {
2381
+ return [
2382
+ "// schema 字段元数据类型",
2383
+ "import type { FieldMeta } from '/@/utils/crudSchema';",
2384
+ "// 多层字典页面配置类型",
2385
+ "import type { MultiLevelDictFieldConfig, MultiLevelDictLevelConfig, MultiLevelDictModuleConfig, MultiLevelDictModuleDefinition } from '/@/types/multiLevelDict';",
2386
+ "// 字典注册表",
2387
+ "import { DictRegistry } from '/@/enums/dict-registry';",
2388
+ '',
2389
+ '// 将字段数组转换为字段元数据映射',
2390
+ 'const toFieldMetaMap = (fields: MultiLevelDictFieldConfig[]) =>',
2390
2391
  ' Object.fromEntries(',
2391
2392
  ' fields.map((item) => [',
2392
2393
  ' item.key,',
@@ -2400,105 +2401,105 @@ function renderMultiLevelOptionsTs(model, dictRegistryRefs) {
2400
2401
  " ...(item.dictType ? { dictType: item.dictType } : {}),",
2401
2402
  ' } satisfies FieldMeta,',
2402
2403
  ' ])',
2403
- ' ) as Record<string, FieldMeta>;',
2404
- '',
2405
- '// 过滤有效字符串值',
2406
- 'const isString = (value: string | undefined): value is string => Boolean(value);',
2407
- '',
2408
- '// 补齐模块级运行配置',
2409
- 'const createModuleConfig = (definition: MultiLevelDictModuleDefinition): MultiLevelDictModuleConfig => ({',
2404
+ ' ) as Record<string, FieldMeta>;',
2405
+ '',
2406
+ '// 过滤有效字符串值',
2407
+ 'const isString = (value: string | undefined): value is string => Boolean(value);',
2408
+ '',
2409
+ '// 补齐模块级运行配置',
2410
+ 'const createModuleConfig = (definition: MultiLevelDictModuleDefinition): MultiLevelDictModuleConfig => ({',
2410
2411
  ' ...definition,',
2411
2412
  ' dictTypes: Array.from(new Set([...definition.fields.map((field) => field.dictType).filter(isString), definition.statusDictType].filter(isString))),',
2412
- '});',
2413
- '',
2414
- '// 模块定义集合',
2415
- 'const moduleDefinitions: Record<string, MultiLevelDictModuleDefinition> = {',
2413
+ '});',
2414
+ '',
2415
+ '// 模块定义集合',
2416
+ 'const moduleDefinitions: Record<string, MultiLevelDictModuleDefinition> = {',
2416
2417
  model.modules.map((moduleModel) => renderMultiLevelModuleDefinition(model, moduleModel, dictRegistryRefs)).join('\n'),
2417
- '};',
2418
- '',
2419
- '// 页面模块配置',
2420
- 'export const moduleConfigs = Object.fromEntries(',
2418
+ '};',
2419
+ '',
2420
+ '// 页面模块配置',
2421
+ 'export const moduleConfigs = Object.fromEntries(',
2421
2422
  ' Object.entries(moduleDefinitions).map(([key, definition]) => [key, createModuleConfig(definition)])',
2422
- ') as Record<string, MultiLevelDictModuleConfig>;',
2423
- '',
2424
- '// 多层布局配置',
2425
- 'export const levelConfigs: MultiLevelDictLevelConfig[] = [',
2423
+ ') as Record<string, MultiLevelDictModuleConfig>;',
2424
+ '',
2425
+ '// 多层布局配置',
2426
+ 'export const levelConfigs: MultiLevelDictLevelConfig[] = [',
2426
2427
  model.levels.map(renderMultiLevelLevelConfig).join('\n'),
2427
- '];',
2428
- '',
2429
- '// 各模块字段分组',
2430
- 'export const moduleFieldGroups = Object.fromEntries(',
2428
+ '];',
2429
+ '',
2430
+ '// 各模块字段分组',
2431
+ 'export const moduleFieldGroups = Object.fromEntries(',
2431
2432
  ' Object.entries(moduleConfigs).map(([key, config]) => [key, toFieldMetaMap(config.fields)])',
2432
- ') as Record<string, Record<string, FieldMeta>>;',
2433
- '',
2434
- '// 页面依赖的全部字典类型',
2435
- 'export const allDictTypes = Array.from(new Set(Object.values(moduleConfigs).flatMap((config) => config.dictTypes)));',
2436
- '',
2437
- ].join('\n');
2438
- }
2439
-
2440
- function renderMultiLevelApiFunctions(moduleModel) {
2433
+ ') as Record<string, Record<string, FieldMeta>>;',
2434
+ '',
2435
+ '// 页面依赖的全部字典类型',
2436
+ 'export const allDictTypes = Array.from(new Set(Object.values(moduleConfigs).flatMap((config) => config.dictTypes)));',
2437
+ '',
2438
+ ].join('\n');
2439
+ }
2440
+
2441
+ function renderMultiLevelApiFunctions(moduleModel) {
2441
2442
  const pkAttr = moduleModel.pk.attrName;
2442
2443
  const basePath = buildApiRoutePath(moduleModel.moduleName, moduleModel.apiPath);
2443
2444
  const enablePath = buildApiRoutePath(moduleModel.moduleName, moduleModel.enableApi);
2444
2445
  const disablePath = buildApiRoutePath(moduleModel.moduleName, moduleModel.disableApi);
2445
- return [
2446
- `// 查询${moduleModel.title}分页列表`,
2447
- `export function fetch${moduleModel.className}List(query?: any) {`,
2446
+ return [
2447
+ `// 查询${moduleModel.title}分页列表`,
2448
+ `export function fetch${moduleModel.className}List(query?: any) {`,
2448
2449
  ' return request({',
2449
2450
  ` url: '${basePath}/page',`,
2450
2451
  " method: 'get',",
2451
2452
  ' params: query,',
2452
2453
  ' });',
2453
- '}',
2454
- '',
2455
- `// 查询${moduleModel.title}详情`,
2456
- `export function get${moduleModel.className}Obj(id: string | number) {`,
2454
+ '}',
2455
+ '',
2456
+ `// 查询${moduleModel.title}详情`,
2457
+ `export function get${moduleModel.className}Obj(id: string | number) {`,
2457
2458
  ' return request({',
2458
2459
  ` url: '${basePath}/getById',`,
2459
2460
  " method: 'get',",
2460
2461
  ` params: { ${pkAttr}: id },`,
2461
2462
  ' });',
2462
- '}',
2463
- '',
2464
- `// 新增${moduleModel.title}`,
2465
- `export function add${moduleModel.className}Obj(data: any) {`,
2463
+ '}',
2464
+ '',
2465
+ `// 新增${moduleModel.title}`,
2466
+ `export function add${moduleModel.className}Obj(data: any) {`,
2466
2467
  ' return request({',
2467
2468
  ` url: '${basePath}/save',`,
2468
2469
  " method: 'post',",
2469
2470
  ' data,',
2470
2471
  ' });',
2471
- '}',
2472
- '',
2473
- `// 更新${moduleModel.title}`,
2474
- `export function put${moduleModel.className}Obj(data: any) {`,
2472
+ '}',
2473
+ '',
2474
+ `// 更新${moduleModel.title}`,
2475
+ `export function put${moduleModel.className}Obj(data: any) {`,
2475
2476
  ' return request({',
2476
2477
  ` url: '${basePath}/updateById',`,
2477
2478
  " method: 'post',",
2478
2479
  ' data,',
2479
2480
  ' });',
2480
- '}',
2481
- '',
2482
- `// 删除${moduleModel.title}`,
2483
- `export function del${moduleModel.className}Objs(data: Array<string | number>) {`,
2481
+ '}',
2482
+ '',
2483
+ `// 删除${moduleModel.title}`,
2484
+ `export function del${moduleModel.className}Objs(data: Array<string | number>) {`,
2484
2485
  ' return request({',
2485
2486
  ` url: '${basePath}/removeByIds',`,
2486
2487
  " method: 'post',",
2487
2488
  ' data,',
2488
2489
  ' });',
2489
- '}',
2490
- '',
2491
- `// 启用${moduleModel.title}`,
2492
- `export function enable${moduleModel.className}(id: string | number) {`,
2490
+ '}',
2491
+ '',
2492
+ `// 启用${moduleModel.title}`,
2493
+ `export function enable${moduleModel.className}(id: string | number) {`,
2493
2494
  ' return request({',
2494
2495
  ` url: '${enablePath}',`,
2495
2496
  " method: 'post',",
2496
2497
  ' params: { id },',
2497
2498
  ' });',
2498
- '}',
2499
- '',
2500
- `// 禁用${moduleModel.title}`,
2501
- `export function disable${moduleModel.className}(id: string | number) {`,
2499
+ '}',
2500
+ '',
2501
+ `// 禁用${moduleModel.title}`,
2502
+ `export function disable${moduleModel.className}(id: string | number) {`,
2502
2503
  ' return request({',
2503
2504
  ` url: '${disablePath}',`,
2504
2505
  " method: 'post',",
@@ -2508,22 +2509,22 @@ function renderMultiLevelApiFunctions(moduleModel) {
2508
2509
  ].join('\n');
2509
2510
  }
2510
2511
 
2511
- function renderMultiLevelApiTs(model) {
2512
- return [
2513
- "// 请求工具",
2514
- "import request from '/@/utils/request';",
2515
- '',
2516
- model.modules.map(renderMultiLevelApiFunctions).join('\n\n'),
2517
- renderExtraApiFunctions(model),
2518
- '',
2519
- ].join('\n');
2520
- }
2512
+ function renderMultiLevelApiTs(model) {
2513
+ return [
2514
+ "// 请求工具",
2515
+ "import request from '/@/utils/request';",
2516
+ '',
2517
+ model.modules.map(renderMultiLevelApiFunctions).join('\n\n'),
2518
+ renderExtraApiFunctions(model),
2519
+ '',
2520
+ ].join('\n');
2521
+ }
2521
2522
 
2522
- function renderMultiLevelFormField(field) {
2523
- const labelExpr = `getFieldLabel('${field.attrName}')`;
2524
- const prop = field.attrName;
2525
- const disabledAttr = renderDisabledAttrV2(field);
2526
- const disabledBool = renderDisabledBoolV2(field);
2523
+ function renderMultiLevelFormField(field) {
2524
+ const labelExpr = `getFieldLabel('${field.attrName}')`;
2525
+ const prop = field.attrName;
2526
+ const disabledAttr = renderDisabledAttrV2(field);
2527
+ const disabledBool = renderDisabledBoolV2(field);
2527
2528
 
2528
2529
  if (isAttachmentLikeField(field)) {
2529
2530
  return [
@@ -2541,7 +2542,7 @@ function renderMultiLevelFormField(field) {
2541
2542
  renderFieldCommentV2(field),
2542
2543
  ' <el-col :span="12" class="mb20">',
2543
2544
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2544
- ` <el-select v-model="form.${prop}" :placeholder="formSelectPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr}>`,
2545
+ ` <el-select v-model="form.${prop}" :placeholder="formSelectPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr}>`,
2545
2546
  ` <el-option v-for="item in getDictOptions(getFieldMeta('${prop}')?.dictType)" :key="item.value" :label="item.label" :value="item.value" />`,
2546
2547
  ' </el-select>',
2547
2548
  ' </el-form-item>',
@@ -2556,7 +2557,7 @@ function renderMultiLevelFormField(field) {
2556
2557
  renderFieldCommentV2(field),
2557
2558
  ' <el-col :span="12" class="mb20">',
2558
2559
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2559
- ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr} />`,
2560
+ ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr} />`,
2560
2561
  ' </el-form-item>',
2561
2562
  ' </el-col>',
2562
2563
  ].join('\n');
@@ -2569,7 +2570,7 @@ function renderMultiLevelFormField(field) {
2569
2570
  renderFieldCommentV2(field),
2570
2571
  ' <el-col :span="12" class="mb20">',
2571
2572
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2572
- ` <el-date-picker type="${pickerType}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"${disabledAttr}></el-date-picker>`,
2573
+ ` <el-date-picker type="${pickerType}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"${disabledAttr}></el-date-picker>`,
2573
2574
  ' </el-form-item>',
2574
2575
  ' </el-col>',
2575
2576
  ].join('\n');
@@ -2581,7 +2582,7 @@ function renderMultiLevelFormField(field) {
2581
2582
  renderFieldCommentV2(field),
2582
2583
  ' <el-col :span="24" class="mb20">',
2583
2584
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2584
- ` <el-input type="textarea" v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${textareaAttrs}${disabledAttr} />`,
2585
+ ` <el-input type="textarea" v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${textareaAttrs}${disabledAttr} />`,
2585
2586
  ' </el-form-item>',
2586
2587
  ' </el-col>',
2587
2588
  ].join('\n');
@@ -2591,7 +2592,7 @@ function renderMultiLevelFormField(field) {
2591
2592
  renderFieldCommentV2(field),
2592
2593
  ' <el-col :span="12" class="mb20">',
2593
2594
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2594
- ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${renderInputMaxlengthAttr(field)}${disabledAttr} />`,
2595
+ ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${renderInputMaxlengthAttr(field)}${disabledAttr} />`,
2595
2596
  ' </el-form-item>',
2596
2597
  ' </el-col>',
2597
2598
  ].join('\n');
@@ -2607,19 +2608,19 @@ function renderMultiLevelFormVue(model, moduleModel) {
2607
2608
  ` tenantId: Session.getTenant(),`,
2608
2609
  ].join('\n');
2609
2610
  const rules = renderFormRulesV2(moduleModel.visibleFields);
2610
- return `<template>
2611
- <!-- 表单弹窗:新增或编辑${sanitizeHtmlComment(moduleModel.comment || moduleModel.tableName)}数据 -->
2612
- <el-dialog v-model="visible" :title="form.${moduleModel.pk.attrName} ? t('common.editBtn') : t('common.addBtn')" :close-on-click-modal="false" draggable>
2613
- <!-- 弹窗表单:按 PRD 表单显隐和顺序渲染字段 -->
2614
- <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading">
2615
- <!-- 字典字段区:字段级注释由 MCP 根据字段名称生成 -->
2616
- <el-row :gutter="24">
2617
- ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2618
- </el-row>
2619
- </el-form>
2620
- <!-- 弹窗底部操作按钮:取消和确认提交 -->
2621
- <template #footer>
2622
- <span class="dialog-footer">
2611
+ return `<template>
2612
+ <!-- 表单弹窗:新增或编辑${sanitizeHtmlComment(moduleModel.comment || moduleModel.tableName)}数据 -->
2613
+ <el-dialog v-model="visible" :title="form.${moduleModel.pk.attrName} ? t('common.editBtn') : t('common.addBtn')" :close-on-click-modal="false" draggable>
2614
+ <!-- 弹窗表单:按 PRD 表单显隐和顺序渲染字段 -->
2615
+ <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading">
2616
+ <!-- 字典字段区:字段级注释由 MCP 根据字段名称生成 -->
2617
+ <el-row :gutter="24">
2618
+ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2619
+ </el-row>
2620
+ </el-form>
2621
+ <!-- 弹窗底部操作按钮:取消和确认提交 -->
2622
+ <template #footer>
2623
+ <span class="dialog-footer">
2623
2624
  <el-button @click="visible = false">{{ t('common.cancelButtonText') }}</el-button>
2624
2625
  <el-button type="primary" @click="onSubmit" :disabled="loading">{{ t('common.confirmButtonText') }}</el-button>
2625
2626
  </span>
@@ -2627,66 +2628,66 @@ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2627
2628
  </el-dialog>
2628
2629
  </template>
2629
2630
 
2630
- <script setup lang="ts" name="${componentName}">
2631
- // 通用消息提示
2632
- import { useMessage } from '/@/hooks/message';
2633
- // 本地会话存储
2634
- import { Session } from '/@/utils/storage';
2635
- // 字典数据加载
2636
- import { useDict } from '/@/hooks/dict';
2637
- // 表单字段元数据能力
2638
- import { useCrudPageMeta } from '/@/hooks/useCrudPageMeta';
2639
- // 国际化能力
2640
- import { useI18n } from 'vue-i18n';
2641
- // 模块配置
2642
- import { moduleConfigs, moduleFieldGroups } from './options';
2643
- // 当前模块接口
2644
- import { get${moduleModel.className}Obj, add${moduleModel.className}Obj, put${moduleModel.className}Obj } from '/@/api/${model.moduleName}/${model.functionName}';
2645
-
2646
- // 当前模块配置
2647
- const moduleConfig = moduleConfigs.${moduleModel.key};
2648
- // 当前模块字典引用
2649
- const dictRefs = useDict(...moduleConfig.dictTypes);
2650
- // 国际化方法
2651
- const { t } = useI18n();
2652
- // 弹窗刷新事件
2653
- const emit = defineEmits(['refresh']);
2654
-
2655
- // 表单引用
2656
- const dataFormRef = ref();
2657
- // 弹窗显示状态
2658
- const visible = ref(false);
2659
- // 提交加载状态
2660
- const loading = ref(false);
2661
-
2662
- // 字段标签、字典和校验提示
2663
- const { getFieldMeta, getFieldLabel, getDictOptions, inputPlaceholder, selectPlaceholder, fieldRequiredMessage } = useCrudPageMeta(${fieldsMapExpr}, dictRefs);
2664
- const formInputPlaceholder = (label: string, disabled = false) => (disabled ? '' : inputPlaceholder(label));
2665
- const formSelectPlaceholder = (label: string, disabled = false) => (disabled ? '' : selectPlaceholder(label));
2666
-
2667
- // 统一维护表单默认值
2668
- const createDefaultFormState = () => ({
2669
- ${defaultLines}
2670
- });
2671
-
2672
- // 表单响应式数据
2673
- const form = reactive(createDefaultFormState());
2674
-
2675
- // 设置父级关联字段
2676
- const setParentFieldValue = (parentId: string | number) => {
2631
+ <script setup lang="ts" name="${componentName}">
2632
+ // 通用消息提示
2633
+ import { useMessage } from '/@/hooks/message';
2634
+ // 本地会话存储
2635
+ import { Session } from '/@/utils/storage';
2636
+ // 字典数据加载
2637
+ import { useDict } from '/@/hooks/dict';
2638
+ // 表单字段元数据能力
2639
+ import { useCrudPageMeta } from '/@/hooks/useCrudPageMeta';
2640
+ // 国际化能力
2641
+ import { useI18n } from 'vue-i18n';
2642
+ // 模块配置
2643
+ import { moduleConfigs, moduleFieldGroups } from './options';
2644
+ // 当前模块接口
2645
+ import { get${moduleModel.className}Obj, add${moduleModel.className}Obj, put${moduleModel.className}Obj } from '/@/api/${model.moduleName}/${model.functionName}';
2646
+
2647
+ // 当前模块配置
2648
+ const moduleConfig = moduleConfigs.${moduleModel.key};
2649
+ // 当前模块字典引用
2650
+ const dictRefs = useDict(...moduleConfig.dictTypes);
2651
+ // 国际化方法
2652
+ const { t } = useI18n();
2653
+ // 弹窗刷新事件
2654
+ const emit = defineEmits(['refresh']);
2655
+
2656
+ // 表单引用
2657
+ const dataFormRef = ref();
2658
+ // 弹窗显示状态
2659
+ const visible = ref(false);
2660
+ // 提交加载状态
2661
+ const loading = ref(false);
2662
+
2663
+ // 字段标签、字典和校验提示
2664
+ const { getFieldMeta, getFieldLabel, getDictOptions, inputPlaceholder, selectPlaceholder, fieldRequiredMessage } = useCrudPageMeta(${fieldsMapExpr}, dictRefs);
2665
+ const formInputPlaceholder = (label: string, disabled = false) => (disabled ? '' : inputPlaceholder(label));
2666
+ const formSelectPlaceholder = (label: string, disabled = false) => (disabled ? '' : selectPlaceholder(label));
2667
+
2668
+ // 统一维护表单默认值
2669
+ const createDefaultFormState = () => ({
2670
+ ${defaultLines}
2671
+ });
2672
+
2673
+ // 表单响应式数据
2674
+ const form = reactive(createDefaultFormState());
2675
+
2676
+ // 设置父级关联字段
2677
+ const setParentFieldValue = (parentId: string | number) => {
2677
2678
  const parentField = moduleConfig.queryParentField as keyof typeof form | undefined;
2678
2679
  if (!parentField) return;
2679
2680
  const currentValue = form[parentField];
2680
2681
  (form as Record<string, string | number>)[parentField as string] = typeof currentValue === 'number' ? Number(parentId) : String(parentId);
2681
- };
2682
-
2683
- // 表单校验规则
2684
- const dataRules = ref({
2685
- ${rules}
2686
- });
2687
-
2688
- // 加载详情数据
2689
- const getData = async (id: string | number) => {
2682
+ };
2683
+
2684
+ // 表单校验规则
2685
+ const dataRules = ref({
2686
+ ${rules}
2687
+ });
2688
+
2689
+ // 加载详情数据
2690
+ const getData = async (id: string | number) => {
2690
2691
  try {
2691
2692
  loading.value = true;
2692
2693
  const { data } = await get${moduleModel.className}Obj(id);
@@ -2696,18 +2697,18 @@ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2696
2697
  } finally {
2697
2698
  loading.value = false;
2698
2699
  }
2699
- };
2700
-
2701
- // 重置表单为初始状态
2702
- const resetFormState = () => {
2700
+ };
2701
+
2702
+ // 重置表单为初始状态
2703
+ const resetFormState = () => {
2703
2704
  Object.assign(form, createDefaultFormState());
2704
2705
  nextTick(() => {
2705
2706
  dataFormRef.value?.resetFields();
2706
2707
  });
2707
- };
2708
-
2709
- // 打开弹窗并按需回填父子层级关系
2710
- const openDialog = async (id?: string | number, parentId?: string | number) => {
2708
+ };
2709
+
2710
+ // 打开弹窗并按需回填父子层级关系
2711
+ const openDialog = async (id?: string | number, parentId?: string | number) => {
2711
2712
  visible.value = true;
2712
2713
  resetFormState();
2713
2714
  if (moduleConfig.queryParentField && parentId !== undefined && parentId !== null && parentId !== '') {
@@ -2717,10 +2718,10 @@ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2717
2718
  form.${moduleModel.pk.attrName} = String(id);
2718
2719
  await getData(id);
2719
2720
  }
2720
- };
2721
-
2722
- // 提交弹窗表单
2723
- const onSubmit = async () => {
2721
+ };
2722
+
2723
+ // 提交弹窗表单
2724
+ const onSubmit = async () => {
2724
2725
  loading.value = true;
2725
2726
  const valid = await dataFormRef.value.validate().catch(() => {});
2726
2727
  if (!valid) {
@@ -2738,126 +2739,126 @@ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2738
2739
  } finally {
2739
2740
  loading.value = false;
2740
2741
  }
2741
- };
2742
-
2743
- // 向父组件暴露打开弹窗方法
2744
- defineExpose({
2745
- openDialog,
2746
- });
2742
+ };
2743
+
2744
+ // 向父组件暴露打开弹窗方法
2745
+ defineExpose({
2746
+ openDialog,
2747
+ });
2747
2748
  </script>
2748
2749
  `;
2749
2750
  }
2750
- function renderMultiLevelSchemaListSlot(levelVarName, activeKeyVarName, activeModuleVarName) {
2751
- return [
2752
- ` <div class="multi-level-slot" v-if="${levelVarName}">`,
2753
- ' <!-- 多层字典页签:切换当前层级下的字典模块 -->',
2754
- ' <el-tabs class="multi-level-tabs" v-if="' + `${levelVarName}.moduleKeys.length > 1` + `" v-model="${activeKeyVarName}">`,
2755
- ` <el-tab-pane v-for="moduleKey in ${levelVarName}.moduleKeys" :key="moduleKey" :label="resolveModuleTitle(moduleConfigs[moduleKey])" :name="moduleKey" />`,
2756
- ' </el-tabs>',
2757
- ' <!-- 多层字典列表面板:承载当前层级的工具栏和表格 -->',
2758
- ' <div class="multi-level-panel" v-if="' + `${activeModuleVarName}` + `">`,
2759
- ' <div class="layout-padding-auto layout-padding-view flex h-full flex-col">',
2760
- ' <!-- 当前层级工具栏:搜索、新增和重置 -->',
2761
- ` <SchemaListToolbar`,
2762
- ` v-bind="getPanelToolbarProps(${activeModuleVarName}.key)"`,
2763
- ` @update:keyword="handlePanelKeywordChange(${activeModuleVarName}.key, $event)"`,
2764
- ` @add="openCreate(${activeModuleVarName}.key)"`,
2765
- ` @query="handlePanelQuery(${activeModuleVarName}.key)"`,
2766
- ` @reset="handlePanelReset(${activeModuleVarName}.key)"`,
2767
- ` />`,
2768
- ' <!-- 当前层级表格:展示字典数据、分页和行操作 -->',
2769
- ` <SchemaListTable`,
2770
- ` v-bind="getPanelTableProps(${activeModuleVarName}.key)"`,
2771
- ` @row-current-change="handlePanelCurrentChange(${activeModuleVarName}.key, $event.row)"`,
2772
- ` @current-change="handleCurrentPageChange(${activeModuleVarName}.key, $event.current)"`,
2773
- ` @size-change="handlePageSizeChange(${activeModuleVarName}.key, $event.size)"`,
2774
- ` >`,
2775
- ' <!-- 行操作按钮:编辑、删除、启用、禁用 -->',
2776
- ` <template #actions="{ row }">`,
2777
- ' <!-- 编辑当前字典数据 -->',
2778
- ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'edit', row)" icon="edit-pen" text type="primary" @click="openEdit(${activeModuleVarName}.key, row)">{{ t('common.editBtn') }}</el-button>`,
2779
- ' <!-- 删除当前字典数据 -->',
2780
- ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'delete', row)" icon="delete" text type="primary" @click="handleDelete(${activeModuleVarName}.key, row)">{{ t('common.delBtn') }}</el-button>`,
2781
- ' <!-- 启用当前字典数据 -->',
2782
- ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'enable', row)" icon="circle-check" text type="primary" @click="handleEnable(${activeModuleVarName}.key, row)">闁告凹鍨抽弫?/el-button>`,
2783
- ' <!-- 禁用当前字典数据 -->',
2784
- ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'disable', row)" icon="remove" text type="primary" @click="handleDisable(${activeModuleVarName}.key, row)">缂佸倷鑳堕弫?/el-button>`,
2785
- ` </template>`,
2786
- ` </SchemaListTable>`,
2787
- ' </div>',
2788
- ' </div>',
2789
- ` </div>`,
2790
- ].join('\n');
2791
- }
2792
-
2793
- function renderMultiLevelLevelSlot(levelVarName, activeKeyVarName, activeModuleVarName) {
2794
- return [
2795
- ` <div class="multi-level-slot" v-if="${levelVarName}">`,
2796
- ' <!-- 多层字典页签:切换当前层级下的字典模块 -->',
2797
- ' <el-tabs class="multi-level-tabs" v-if="' + `${levelVarName}.moduleKeys.length > 1` + `" v-model="${activeKeyVarName}">`,
2798
- ` <el-tab-pane v-for="moduleKey in ${levelVarName}.moduleKeys" :key="moduleKey" :label="resolveModuleTitle(moduleConfigs[moduleKey])" :name="moduleKey" />`,
2799
- ' </el-tabs>',
2800
- ' <!-- 多层字典列表面板:承载当前层级的新增按钮、表格和分页 -->',
2801
- ' <div class="multi-level-panel" v-if="' + `${activeModuleVarName}` + `">`,
2802
- ' <div class="layout-padding-auto layout-padding-view flex h-full flex-col">',
2803
- ' <!-- 当前层级新增按钮:受父级状态约束控制 -->',
2804
- ' <div class="mb8" style="width: 100%">',
2805
- ` <el-button icon="folder-add" type="primary" class="ml10" :disabled="isAddDisabled(${activeModuleVarName}.key)" @click="openCreate(${activeModuleVarName}.key)">{{ t('common.addBtn') }}</el-button>`,
2806
- ' </div>',
2807
- ' <!-- 当前层级表格:展示字典数据并处理行选中联动 -->',
2808
- ' <el-table',
2809
- ` :data="moduleStateMap[${activeModuleVarName}.key].dataList"`,
2810
- ` v-loading="moduleStateMap[${activeModuleVarName}.key].loading"`,
2811
- ' border',
2812
- ' height="100%"',
2813
- ' highlight-current-row',
2814
- ` @current-change="handlePanelCurrentChange(${activeModuleVarName}.key, $event)"`,
2815
- ' >',
2816
- ' <!-- 序号列 -->',
2817
- ` <el-table-column type="index" :label="t('common.serial')" width="60" />`,
2818
- ' <!-- 字典业务字段列 -->',
2819
- ` <el-table-column`,
2820
- ` v-for="column in getListFields(${activeModuleVarName}.key)"`,
2821
- ' :key="column.key"',
2822
- ' :prop="column.key"',
2823
- ` :label="resolveLabel(column.labelKey, column.key || '')"`,
2824
- ` :min-width="column.width || '120'"`,
2825
- ' show-overflow-tooltip',
2826
- ' >',
2827
- ' <template #default="scope">',
2828
- ' <dict-tag v-if="column.dictType" :options="getDictOptions(column.dictType)" :value="scope.row[column.key]" />',
2829
- ' <span v-else>{{ scope.row[column.key] }}</span>',
2830
- ' </template>',
2831
- ' </el-table-column>',
2832
- ' <!-- 行操作按钮:编辑、删除、启用、禁用 -->',
2833
- ` <el-table-column :label="t('common.action')" width="260">`,
2834
- ' <template #default="{ row }">',
2835
- ' <!-- 编辑当前字典数据 -->',
2836
- ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'edit', row)" icon="edit-pen" text type="primary" @click="openEdit(${activeModuleVarName}.key, row)">{{ t('common.editBtn') }}</el-button>`,
2837
- ' <!-- 删除当前字典数据 -->',
2838
- ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'delete', row)" icon="delete" text type="primary" @click="handleDelete(${activeModuleVarName}.key, row)">{{ t('common.delBtn') }}</el-button>`,
2839
- ' <!-- 启用当前字典数据 -->',
2840
- ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'enable', row)" icon="circle-check" text type="primary" @click="handleEnable(${activeModuleVarName}.key, row)">{{ t('common.actions.enable') }}</el-button>`,
2841
- ' <!-- 禁用当前字典数据 -->',
2842
- ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'disable', row)" icon="remove" text type="primary" @click="handleDisable(${activeModuleVarName}.key, row)">{{ t('common.actions.disable') }}</el-button>`,
2843
- ' </template>',
2844
- ' </el-table-column>',
2845
- ' </el-table>',
2846
- ' <!-- 当前层级分页组件 -->',
2847
- ' <div class="mt-2.5 flex shrink-0 justify-end">',
2848
- ' <pagination',
2849
- ` :current-page="moduleStateMap[${activeModuleVarName}.key].currentPage"`,
2850
- ` :page-size="moduleStateMap[${activeModuleVarName}.key].pageSize"`,
2851
- ` :total="moduleStateMap[${activeModuleVarName}.key].total"`,
2852
- ` @current-change="handleCurrentPageChange(${activeModuleVarName}.key, $event)"`,
2853
- ` @size-change="handlePageSizeChange(${activeModuleVarName}.key, $event)"`,
2854
- ' />',
2855
- ' </div>',
2856
- ' </div>',
2857
- ' </div>',
2858
- ` </div>`,
2859
- ].join('\n');
2860
- }
2751
+ function renderMultiLevelSchemaListSlot(levelVarName, activeKeyVarName, activeModuleVarName) {
2752
+ return [
2753
+ ` <div class="multi-level-slot" v-if="${levelVarName}">`,
2754
+ ' <!-- 多层字典页签:切换当前层级下的字典模块 -->',
2755
+ ' <el-tabs class="multi-level-tabs" v-if="' + `${levelVarName}.moduleKeys.length > 1` + `" v-model="${activeKeyVarName}">`,
2756
+ ` <el-tab-pane v-for="moduleKey in ${levelVarName}.moduleKeys" :key="moduleKey" :label="resolveModuleTitle(moduleConfigs[moduleKey])" :name="moduleKey" />`,
2757
+ ' </el-tabs>',
2758
+ ' <!-- 多层字典列表面板:承载当前层级的工具栏和表格 -->',
2759
+ ' <div class="multi-level-panel" v-if="' + `${activeModuleVarName}` + `">`,
2760
+ ' <div class="layout-padding-auto layout-padding-view flex h-full flex-col">',
2761
+ ' <!-- 当前层级工具栏:搜索、新增和重置 -->',
2762
+ ` <SchemaListToolbar`,
2763
+ ` v-bind="getPanelToolbarProps(${activeModuleVarName}.key)"`,
2764
+ ` @update:keyword="handlePanelKeywordChange(${activeModuleVarName}.key, $event)"`,
2765
+ ` @add="openCreate(${activeModuleVarName}.key)"`,
2766
+ ` @query="handlePanelQuery(${activeModuleVarName}.key)"`,
2767
+ ` @reset="handlePanelReset(${activeModuleVarName}.key)"`,
2768
+ ` />`,
2769
+ ' <!-- 当前层级表格:展示字典数据、分页和行操作 -->',
2770
+ ` <SchemaListTable`,
2771
+ ` v-bind="getPanelTableProps(${activeModuleVarName}.key)"`,
2772
+ ` @row-current-change="handlePanelCurrentChange(${activeModuleVarName}.key, $event.row)"`,
2773
+ ` @current-change="handleCurrentPageChange(${activeModuleVarName}.key, $event.current)"`,
2774
+ ` @size-change="handlePageSizeChange(${activeModuleVarName}.key, $event.size)"`,
2775
+ ` >`,
2776
+ ' <!-- 行操作按钮:编辑、删除、启用、禁用 -->',
2777
+ ` <template #actions="{ row }">`,
2778
+ ' <!-- 编辑当前字典数据 -->',
2779
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'edit', row)" icon="edit-pen" text type="primary" @click="openEdit(${activeModuleVarName}.key, row)">{{ t('common.editBtn') }}</el-button>`,
2780
+ ' <!-- 删除当前字典数据 -->',
2781
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'delete', row)" icon="delete" text type="primary" @click="handleDelete(${activeModuleVarName}.key, row)">{{ t('common.delBtn') }}</el-button>`,
2782
+ ' <!-- 启用当前字典数据 -->',
2783
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'enable', row)" icon="circle-check" text type="primary" @click="handleEnable(${activeModuleVarName}.key, row)">闁告凹鍨抽弫?/el-button>`,
2784
+ ' <!-- 禁用当前字典数据 -->',
2785
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'disable', row)" icon="remove" text type="primary" @click="handleDisable(${activeModuleVarName}.key, row)">缂佸倷鑳堕弫?/el-button>`,
2786
+ ` </template>`,
2787
+ ` </SchemaListTable>`,
2788
+ ' </div>',
2789
+ ' </div>',
2790
+ ` </div>`,
2791
+ ].join('\n');
2792
+ }
2793
+
2794
+ function renderMultiLevelLevelSlot(levelVarName, activeKeyVarName, activeModuleVarName) {
2795
+ return [
2796
+ ` <div class="multi-level-slot" v-if="${levelVarName}">`,
2797
+ ' <!-- 多层字典页签:切换当前层级下的字典模块 -->',
2798
+ ' <el-tabs class="multi-level-tabs" v-if="' + `${levelVarName}.moduleKeys.length > 1` + `" v-model="${activeKeyVarName}">`,
2799
+ ` <el-tab-pane v-for="moduleKey in ${levelVarName}.moduleKeys" :key="moduleKey" :label="resolveModuleTitle(moduleConfigs[moduleKey])" :name="moduleKey" />`,
2800
+ ' </el-tabs>',
2801
+ ' <!-- 多层字典列表面板:承载当前层级的新增按钮、表格和分页 -->',
2802
+ ' <div class="multi-level-panel" v-if="' + `${activeModuleVarName}` + `">`,
2803
+ ' <div class="layout-padding-auto layout-padding-view flex h-full flex-col">',
2804
+ ' <!-- 当前层级新增按钮:受父级状态约束控制 -->',
2805
+ ' <div class="mb8" style="width: 100%">',
2806
+ ` <el-button icon="folder-add" type="primary" class="ml10" :disabled="isAddDisabled(${activeModuleVarName}.key)" @click="openCreate(${activeModuleVarName}.key)">{{ t('common.addBtn') }}</el-button>`,
2807
+ ' </div>',
2808
+ ' <!-- 当前层级表格:展示字典数据并处理行选中联动 -->',
2809
+ ' <el-table',
2810
+ ` :data="moduleStateMap[${activeModuleVarName}.key].dataList"`,
2811
+ ` v-loading="moduleStateMap[${activeModuleVarName}.key].loading"`,
2812
+ ' border',
2813
+ ' height="100%"',
2814
+ ' highlight-current-row',
2815
+ ` @current-change="handlePanelCurrentChange(${activeModuleVarName}.key, $event)"`,
2816
+ ' >',
2817
+ ' <!-- 序号列 -->',
2818
+ ` <el-table-column type="index" :label="t('common.serial')" width="60" />`,
2819
+ ' <!-- 字典业务字段列 -->',
2820
+ ` <el-table-column`,
2821
+ ` v-for="column in getListFields(${activeModuleVarName}.key)"`,
2822
+ ' :key="column.key"',
2823
+ ' :prop="column.key"',
2824
+ ` :label="resolveLabel(column.labelKey, column.key || '')"`,
2825
+ ` :min-width="column.width || '120'"`,
2826
+ ' show-overflow-tooltip',
2827
+ ' >',
2828
+ ' <template #default="scope">',
2829
+ ' <dict-tag v-if="column.dictType" :options="getDictOptions(column.dictType)" :value="scope.row[column.key]" />',
2830
+ ' <span v-else>{{ scope.row[column.key] }}</span>',
2831
+ ' </template>',
2832
+ ' </el-table-column>',
2833
+ ' <!-- 行操作按钮:编辑、删除、启用、禁用 -->',
2834
+ ` <el-table-column :label="t('common.action')" width="260">`,
2835
+ ' <template #default="{ row }">',
2836
+ ' <!-- 编辑当前字典数据 -->',
2837
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'edit', row)" icon="edit-pen" text type="primary" @click="openEdit(${activeModuleVarName}.key, row)">{{ t('common.editBtn') }}</el-button>`,
2838
+ ' <!-- 删除当前字典数据 -->',
2839
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'delete', row)" icon="delete" text type="primary" @click="handleDelete(${activeModuleVarName}.key, row)">{{ t('common.delBtn') }}</el-button>`,
2840
+ ' <!-- 启用当前字典数据 -->',
2841
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'enable', row)" icon="circle-check" text type="primary" @click="handleEnable(${activeModuleVarName}.key, row)">{{ t('common.actions.enable') }}</el-button>`,
2842
+ ' <!-- 禁用当前字典数据 -->',
2843
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'disable', row)" icon="remove" text type="primary" @click="handleDisable(${activeModuleVarName}.key, row)">{{ t('common.actions.disable') }}</el-button>`,
2844
+ ' </template>',
2845
+ ' </el-table-column>',
2846
+ ' </el-table>',
2847
+ ' <!-- 当前层级分页组件 -->',
2848
+ ' <div class="mt-2.5 flex shrink-0 justify-end">',
2849
+ ' <pagination',
2850
+ ` :current-page="moduleStateMap[${activeModuleVarName}.key].currentPage"`,
2851
+ ` :page-size="moduleStateMap[${activeModuleVarName}.key].pageSize"`,
2852
+ ` :total="moduleStateMap[${activeModuleVarName}.key].total"`,
2853
+ ` @current-change="handleCurrentPageChange(${activeModuleVarName}.key, $event)"`,
2854
+ ` @size-change="handlePageSizeChange(${activeModuleVarName}.key, $event)"`,
2855
+ ' />',
2856
+ ' </div>',
2857
+ ' </div>',
2858
+ ' </div>',
2859
+ ` </div>`,
2860
+ ].join('\n');
2861
+ }
2861
2862
 
2862
2863
  function renderMultiLevelIndexVue(model) {
2863
2864
  const level2 = model.levels.find((level) => level.levelIndex === 2);
@@ -2893,71 +2894,71 @@ function renderMultiLevelIndexVue(model) {
2893
2894
  )
2894
2895
  .join('\n');
2895
2896
 
2896
- return `<!-- 功能名称:${sanitizeHtmlComment(model.featureTitle)} -->
2897
- <template>
2898
- <!-- 页面布局:${sanitizeHtmlComment(model.featureTitle)}多层字典列表页 -->
2899
- <div class="layout-padding">
2900
- <!-- 多层字典主布局:左侧一级字典,右侧二级和三级字典联动 -->
2901
- <div class="multi-level-dict-layout">
2902
- <!-- 一级字典区域 -->
2903
- <div class="multi-level-left">
2904
- ${renderMultiLevelSchemaListSlot('level1Config', 'activeLevel1Key', 'activeLevel1Module')}
2905
- </div>
2906
- <!-- 二级和三级字典区域 -->
2907
- <div class="multi-level-right">
2908
- <!-- 二级字典区域 -->
2909
- <div class="multi-level-right-top">
2910
- ${level2 ? renderMultiLevelSchemaListSlot('level2Config', 'activeLevel2Key', 'activeLevel2Module') : ''}
2911
- </div>
2912
- <!-- 三级字典区域 -->
2913
- <div v-if="level3Config" class="multi-level-right-bottom">
2914
- ${level3 ? renderMultiLevelSchemaListSlot('level3Config', 'activeLevel3Key', 'activeLevel3Module') : ''}
2915
- </div>
2916
- </div>
2917
- </div>
2918
-
2919
- <!-- 多层字典表单弹窗:按模块异步挂载 -->
2920
- ${formComponents}
2921
- </div>
2922
- </template>
2923
-
2924
- <script setup lang="ts" name="system${model.className}">
2925
- // 通用消息与确认弹窗
2926
- import { useMessage, useMessageBox } from '/@/hooks/message';
2927
- // 字典数据加载
2928
- import { useDict } from '/@/hooks/dict';
2929
- // 多层字典元数据能力
2930
- import { useMultiLevelDictMeta } from '/@/hooks/useMultiLevelDictMeta';
2931
- // 多层字典页面状态能力
2932
- import { useMultiLevelDictPage } from '/@/hooks/useMultiLevelDictPage';
2933
- // 统一列表工具栏组件
2934
- import SchemaListToolbar from '/@/components/schema-list/SchemaListToolbar.vue';
2935
- // 统一列表表格组件
2936
- import SchemaListTable from '/@/components/schema-list/SchemaListTable.vue';
2937
- // 国际化能力
2938
- import { useI18n } from 'vue-i18n';
2939
- // 页面配置
2940
- import { allDictTypes, levelConfigs, moduleConfigs } from './options';
2941
- // 模块接口集合
2942
- import { ${apiImports} } from '/@/api/${model.moduleName}/${model.functionName}';
2943
-
2944
- // 各层级表单组件
2945
- ${asyncImports}
2946
-
2947
- // 各层级表单引用
2948
- ${formRefs}
2949
- // 模块与表单引用映射
2950
- ${formRefMap}
2951
- // 模块与接口处理器映射
2952
- ${apiHandlerMap}
2953
-
2954
- // 国际化方法
2955
- const { t } = useI18n();
2956
- // 页面所需的字典引用
2957
- const dictRefs = useDict(...allDictTypes);
2958
-
2959
- const {
2960
- level1Config,
2897
+ return `<!-- 功能名称:${sanitizeHtmlComment(model.featureTitle)} -->
2898
+ <template>
2899
+ <!-- 页面布局:${sanitizeHtmlComment(model.featureTitle)}多层字典列表页 -->
2900
+ <div class="layout-padding">
2901
+ <!-- 多层字典主布局:左侧一级字典,右侧二级和三级字典联动 -->
2902
+ <div class="multi-level-dict-layout">
2903
+ <!-- 一级字典区域 -->
2904
+ <div class="multi-level-left">
2905
+ ${renderMultiLevelSchemaListSlot('level1Config', 'activeLevel1Key', 'activeLevel1Module')}
2906
+ </div>
2907
+ <!-- 二级和三级字典区域 -->
2908
+ <div class="multi-level-right">
2909
+ <!-- 二级字典区域 -->
2910
+ <div class="multi-level-right-top">
2911
+ ${level2 ? renderMultiLevelSchemaListSlot('level2Config', 'activeLevel2Key', 'activeLevel2Module') : ''}
2912
+ </div>
2913
+ <!-- 三级字典区域 -->
2914
+ <div v-if="level3Config" class="multi-level-right-bottom">
2915
+ ${level3 ? renderMultiLevelSchemaListSlot('level3Config', 'activeLevel3Key', 'activeLevel3Module') : ''}
2916
+ </div>
2917
+ </div>
2918
+ </div>
2919
+
2920
+ <!-- 多层字典表单弹窗:按模块异步挂载 -->
2921
+ ${formComponents}
2922
+ </div>
2923
+ </template>
2924
+
2925
+ <script setup lang="ts" name="system${model.className}">
2926
+ // 通用消息与确认弹窗
2927
+ import { useMessage, useMessageBox } from '/@/hooks/message';
2928
+ // 字典数据加载
2929
+ import { useDict } from '/@/hooks/dict';
2930
+ // 多层字典元数据能力
2931
+ import { useMultiLevelDictMeta } from '/@/hooks/useMultiLevelDictMeta';
2932
+ // 多层字典页面状态能力
2933
+ import { useMultiLevelDictPage } from '/@/hooks/useMultiLevelDictPage';
2934
+ // 统一列表工具栏组件
2935
+ import SchemaListToolbar from '/@/components/schema-list/SchemaListToolbar.vue';
2936
+ // 统一列表表格组件
2937
+ import SchemaListTable from '/@/components/schema-list/SchemaListTable.vue';
2938
+ // 国际化能力
2939
+ import { useI18n } from 'vue-i18n';
2940
+ // 页面配置
2941
+ import { allDictTypes, levelConfigs, moduleConfigs } from './options';
2942
+ // 模块接口集合
2943
+ import { ${apiImports} } from '/@/api/${model.moduleName}/${model.functionName}';
2944
+
2945
+ // 各层级表单组件
2946
+ ${asyncImports}
2947
+
2948
+ // 各层级表单引用
2949
+ ${formRefs}
2950
+ // 模块与表单引用映射
2951
+ ${formRefMap}
2952
+ // 模块与接口处理器映射
2953
+ ${apiHandlerMap}
2954
+
2955
+ // 国际化方法
2956
+ const { t } = useI18n();
2957
+ // 页面所需的字典引用
2958
+ const dictRefs = useDict(...allDictTypes);
2959
+
2960
+ const {
2961
+ level1Config,
2961
2962
  level2Config,
2962
2963
  level3Config,
2963
2964
  activeLevel1Key,
@@ -2969,46 +2970,46 @@ const {
2969
2970
  getModuleLevel,
2970
2971
  } = useMultiLevelDictMeta(levelConfigs, moduleConfigs);
2971
2972
 
2972
- // 解析字段双语标签
2973
- const resolveLabel = (labelKey?: string, fallback = '') => {
2973
+ // 解析字段双语标签
2974
+ const resolveLabel = (labelKey?: string, fallback = '') => {
2974
2975
  if (!labelKey) return fallback;
2975
2976
  const translated = t(labelKey);
2976
2977
  return translated === labelKey ? fallback : translated;
2977
- };
2978
-
2979
- // 解析模块标题
2980
- const resolveModuleTitle = (moduleConfig: any) => resolveLabel(moduleConfig?.titleKey, moduleConfig?.key || '');
2981
- // 读取字典选项
2982
- const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
2983
- // 判断状态是否为启用
2984
- const isStatusEnabled = (value: any) => ['1', 1, true, 'true', 'enable', 'enabled'].includes(value);
2985
- const isStatusNew = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?].includes(value);
2986
- const isStatusNewOrDisabled = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?, '2', '缁備胶鏁?, 'disabled'].includes(value);
2987
- // 控制行内动作按钮显隐
2988
- const showModuleAction = (moduleKey: string, action: 'edit' | 'delete' | 'enable' | 'disable', row: any): boolean => {
2989
- const moduleConfig = moduleConfigs[moduleKey];
2990
- const statusValue = moduleConfig?.statusField ? row?.[moduleConfig.statusField] : undefined;
2991
- if (action === 'edit') return statusValue === undefined || isStatusNewOrDisabled(statusValue);
2992
- if (action === 'delete') return statusValue === undefined || isStatusNew(statusValue);
2993
- if (action === 'enable') return !!moduleConfig?.statusField && isStatusNewOrDisabled(statusValue);
2994
- if (action === 'disable') return !!moduleConfig?.statusField && isStatusEnabled(statusValue);
2995
- return false;
2996
- };
2997
-
2998
- const {
2999
- moduleStateMap,
3000
- tableStyle,
3001
- getParentIdForModule,
3002
- getListFields,
3003
- getSmartFields,
3004
- isAddDisabled,
3005
- loadModuleData,
3006
- handleKeywordChange,
3007
- handleQuery,
3008
- handleResetQuery,
3009
- handleSelectRow,
3010
- handleCurrentPageChange,
3011
- handlePageSizeChange,
2978
+ };
2979
+
2980
+ // 解析模块标题
2981
+ const resolveModuleTitle = (moduleConfig: any) => resolveLabel(moduleConfig?.titleKey, moduleConfig?.key || '');
2982
+ // 读取字典选项
2983
+ const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
2984
+ // 判断状态是否为启用
2985
+ const isStatusEnabled = (value: any) => ['1', 1, true, 'true', 'enable', 'enabled'].includes(value);
2986
+ const isStatusNew = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?].includes(value);
2987
+ const isStatusNewOrDisabled = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?, '2', '缁備胶鏁?, 'disabled'].includes(value);
2988
+ // 控制行内动作按钮显隐
2989
+ const showModuleAction = (moduleKey: string, action: 'edit' | 'delete' | 'enable' | 'disable', row: any): boolean => {
2990
+ const moduleConfig = moduleConfigs[moduleKey];
2991
+ const statusValue = moduleConfig?.statusField ? row?.[moduleConfig.statusField] : undefined;
2992
+ if (action === 'edit') return statusValue === undefined || isStatusNewOrDisabled(statusValue);
2993
+ if (action === 'delete') return statusValue === undefined || isStatusNew(statusValue);
2994
+ if (action === 'enable') return !!moduleConfig?.statusField && isStatusNewOrDisabled(statusValue);
2995
+ if (action === 'disable') return !!moduleConfig?.statusField && isStatusEnabled(statusValue);
2996
+ return false;
2997
+ };
2998
+
2999
+ const {
3000
+ moduleStateMap,
3001
+ tableStyle,
3002
+ getParentIdForModule,
3003
+ getListFields,
3004
+ getSmartFields,
3005
+ isAddDisabled,
3006
+ loadModuleData,
3007
+ handleKeywordChange,
3008
+ handleQuery,
3009
+ handleResetQuery,
3010
+ handleSelectRow,
3011
+ handleCurrentPageChange,
3012
+ handlePageSizeChange,
3012
3013
  handleFormRefresh,
3013
3014
  } = useMultiLevelDictPage(
3014
3015
  levelConfigs,
@@ -3020,91 +3021,91 @@ const {
3020
3021
  activeLevel2Module,
3021
3022
  activeLevel3Module,
3022
3023
  getModuleLevel,
3023
- apiHandlerMap
3024
- );
3025
-
3026
- const getPanelColumns = (moduleKey: string) =>
3027
- getListFields(moduleKey).map((column: any) => ({
3028
- prop: column.key,
3029
- label: resolveLabel(column.labelKey, column.key || ''),
3030
- width: column.width || '120',
3031
- dictType: column.dictType,
3032
- options: column.dictType ? getDictOptions(column.dictType) : [],
3033
- }));
3034
-
3035
- const getPanelSearchPlaceholder = (moduleKey: string) => {
3036
- const labels = getSmartFields(moduleKey)
3037
- .map((field: any) => resolveLabel(field.labelKey, field.key || ''))
3038
- .filter(Boolean);
3039
- return t('common.placeholders.searchKeywords', {
3040
- label: labels.join(' / ') || 'keyword',
3041
- });
3042
- };
3043
-
3044
- // 生成面板工具栏透传属性
3045
- const getPanelToolbarProps = (moduleKey: string) => ({
3046
- keyword: moduleStateMap[moduleKey].queryForm.smartVal,
3047
- searchPlaceholder: getPanelSearchPlaceholder(moduleKey),
3048
- queryModel: moduleStateMap[moduleKey].queryForm,
3049
- selectedIds: [],
3050
- showImport: false,
3051
- showDelete: false,
3052
- showQueryTools: true,
3053
- showRightTools: false,
3054
- deleteDisabled: true,
3055
- addText: t('common.addBtn'),
3056
- });
3057
-
3058
- // 生成面板表格透传属性
3059
- const getPanelTableProps = (moduleKey: string) => ({
3060
- data: moduleStateMap[moduleKey].dataList,
3061
- loading: !!moduleStateMap[moduleKey].loading,
3062
- columns: getPanelColumns(moduleKey),
3063
- pagination: {
3064
- current: moduleStateMap[moduleKey].currentPage,
3065
- size: moduleStateMap[moduleKey].pageSize,
3066
- total: moduleStateMap[moduleKey].total,
3067
- },
3068
- tableStyle,
3069
- rowIdKey: moduleConfigs[moduleKey].primaryKey,
3070
- showSelection: false,
3071
- highlightCurrentRow: true,
3072
- actionColumnWidth: 260,
3073
- });
3074
-
3075
- // 打开新增弹窗
3076
- const openCreate = (moduleKey: string) => {
3024
+ apiHandlerMap
3025
+ );
3026
+
3027
+ const getPanelColumns = (moduleKey: string) =>
3028
+ getListFields(moduleKey).map((column: any) => ({
3029
+ prop: column.key,
3030
+ label: resolveLabel(column.labelKey, column.key || ''),
3031
+ width: column.width || '120',
3032
+ dictType: column.dictType,
3033
+ options: column.dictType ? getDictOptions(column.dictType) : [],
3034
+ }));
3035
+
3036
+ const getPanelSearchPlaceholder = (moduleKey: string) => {
3037
+ const labels = getSmartFields(moduleKey)
3038
+ .map((field: any) => resolveLabel(field.labelKey, field.key || ''))
3039
+ .filter(Boolean);
3040
+ return t('common.placeholders.searchKeywords', {
3041
+ label: labels.join(' / ') || 'keyword',
3042
+ });
3043
+ };
3044
+
3045
+ // 生成面板工具栏透传属性
3046
+ const getPanelToolbarProps = (moduleKey: string) => ({
3047
+ keyword: moduleStateMap[moduleKey].queryForm.smartVal,
3048
+ searchPlaceholder: getPanelSearchPlaceholder(moduleKey),
3049
+ queryModel: moduleStateMap[moduleKey].queryForm,
3050
+ selectedIds: [],
3051
+ showImport: false,
3052
+ showDelete: false,
3053
+ showQueryTools: true,
3054
+ showRightTools: false,
3055
+ deleteDisabled: true,
3056
+ addText: t('common.addBtn'),
3057
+ });
3058
+
3059
+ // 生成面板表格透传属性
3060
+ const getPanelTableProps = (moduleKey: string) => ({
3061
+ data: moduleStateMap[moduleKey].dataList,
3062
+ loading: !!moduleStateMap[moduleKey].loading,
3063
+ columns: getPanelColumns(moduleKey),
3064
+ pagination: {
3065
+ current: moduleStateMap[moduleKey].currentPage,
3066
+ size: moduleStateMap[moduleKey].pageSize,
3067
+ total: moduleStateMap[moduleKey].total,
3068
+ },
3069
+ tableStyle,
3070
+ rowIdKey: moduleConfigs[moduleKey].primaryKey,
3071
+ showSelection: false,
3072
+ highlightCurrentRow: true,
3073
+ actionColumnWidth: 260,
3074
+ });
3075
+
3076
+ // 打开新增弹窗
3077
+ const openCreate = (moduleKey: string) => {
3077
3078
  formRefMap[moduleKey]?.value?.openDialog(undefined, getParentIdForModule(moduleKey));
3078
3079
  };
3079
3080
 
3080
- // 打开编辑弹窗
3081
- const openEdit = (moduleKey: string, row: any) => {
3082
- const primaryKey = moduleConfigs[moduleKey].primaryKey;
3083
- formRefMap[moduleKey]?.value?.openDialog(row?.[primaryKey], getParentIdForModule(moduleKey));
3084
- };
3085
-
3086
- // 面板当前行变化处理
3087
- const handlePanelCurrentChange = (moduleKey: string, row: any) => {
3088
- if (row) handleSelectRow(moduleKey, row);
3089
- };
3090
-
3091
- // 面板关键字变更处理
3092
- const handlePanelKeywordChange = (moduleKey: string, keyword: string) => {
3093
- handleKeywordChange(moduleKey, keyword);
3094
- };
3095
-
3096
- // 面板查询处理
3097
- const handlePanelQuery = (moduleKey: string) => {
3098
- handleQuery(moduleKey);
3099
- };
3100
-
3101
- // 面板重置处理
3102
- const handlePanelReset = (moduleKey: string) => {
3103
- handleResetQuery(moduleKey);
3104
- };
3105
-
3106
- // 删除当前模块数据
3107
- const handleDelete = async (moduleKey: string, row: any) => {
3081
+ // 打开编辑弹窗
3082
+ const openEdit = (moduleKey: string, row: any) => {
3083
+ const primaryKey = moduleConfigs[moduleKey].primaryKey;
3084
+ formRefMap[moduleKey]?.value?.openDialog(row?.[primaryKey], getParentIdForModule(moduleKey));
3085
+ };
3086
+
3087
+ // 面板当前行变化处理
3088
+ const handlePanelCurrentChange = (moduleKey: string, row: any) => {
3089
+ if (row) handleSelectRow(moduleKey, row);
3090
+ };
3091
+
3092
+ // 面板关键字变更处理
3093
+ const handlePanelKeywordChange = (moduleKey: string, keyword: string) => {
3094
+ handleKeywordChange(moduleKey, keyword);
3095
+ };
3096
+
3097
+ // 面板查询处理
3098
+ const handlePanelQuery = (moduleKey: string) => {
3099
+ handleQuery(moduleKey);
3100
+ };
3101
+
3102
+ // 面板重置处理
3103
+ const handlePanelReset = (moduleKey: string) => {
3104
+ handleResetQuery(moduleKey);
3105
+ };
3106
+
3107
+ // 删除当前模块数据
3108
+ const handleDelete = async (moduleKey: string, row: any) => {
3108
3109
  try {
3109
3110
  await useMessageBox().confirm(t('common.delConfirmText'));
3110
3111
  } catch {
@@ -3121,8 +3122,8 @@ const getPanelSearchPlaceholder = (moduleKey: string) => {
3121
3122
  }
3122
3123
  };
3123
3124
 
3124
- // 启用当前模块数据
3125
- const handleEnable = async (moduleKey: string, row: any) => {
3125
+ // 启用当前模块数据
3126
+ const handleEnable = async (moduleKey: string, row: any) => {
3126
3127
  try {
3127
3128
  const primaryKey = moduleConfigs[moduleKey].primaryKey;
3128
3129
  await apiHandlerMap[moduleKey].enable(row?.[primaryKey]);
@@ -3133,8 +3134,8 @@ const getPanelSearchPlaceholder = (moduleKey: string) => {
3133
3134
  }
3134
3135
  };
3135
3136
 
3136
- // 禁用当前模块数据
3137
- const handleDisable = async (moduleKey: string, row: any) => {
3137
+ // 禁用当前模块数据
3138
+ const handleDisable = async (moduleKey: string, row: any) => {
3138
3139
  try {
3139
3140
  const primaryKey = moduleConfigs[moduleKey].primaryKey;
3140
3141
  await apiHandlerMap[moduleKey].disable(row?.[primaryKey]);
@@ -3157,10 +3158,10 @@ function renderMultiLevelMenuSql(model) {
3157
3158
  ].join('\n');
3158
3159
  }
3159
3160
 
3160
- function renderMultiLevelFiles(model, sharedSupport, localeZhSupport) {
3161
- const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
3162
- const viewRoot = buildViewRoot(model);
3163
- const apiFilePath = buildApiFilePath(model);
3161
+ function renderMultiLevelFiles(model, sharedSupport, localeZhSupport) {
3162
+ const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
3163
+ const viewRoot = buildViewRoot(model);
3164
+ const apiFilePath = buildApiFilePath(model);
3164
3165
  const menuRoot = path.join(model.frontendPath, 'menu');
3165
3166
  const files = [
3166
3167
  { type: 'list', path: path.join(viewRoot, 'index.vue'), content: renderMultiLevelIndexVue(model) },
@@ -3183,188 +3184,188 @@ function renderMultiLevelFiles(model, sharedSupport, localeZhSupport) {
3183
3184
  content: renderMultiLevelFormVue(model, moduleModel),
3184
3185
  });
3185
3186
  });
3186
-
3187
- return files;
3188
- }
3189
-
3190
- function renderSingleTableDialogActions(model, permissionPrefix) {
3191
- const pkAttr = model.pk.attrName;
3192
- const isDictWithStatus = model.pageType === 'dict' && model.statusField;
3193
- const lines = [
3194
- ` <!-- 编辑${sanitizeHtmlComment(model.featureTitle)} -->`,
3195
- ` <el-button${isDictWithStatus ? ` v-if="showDictAction('edit', row)"` : ''} icon="edit-pen" text type="primary" v-auth="'${permissionPrefix}_edit'" @click="formDialogRef.openDialog(row.${pkAttr})">{{ t('common.editBtn') }}</el-button>`,
3196
- ` <!-- 删除${sanitizeHtmlComment(model.featureTitle)} -->`,
3197
- ` <el-button${isDictWithStatus ? ` v-if="showDictAction('delete', row)"` : ''} icon="delete" text type="primary" v-auth="'${permissionPrefix}_del'" @click="handleDelete([row.${pkAttr}])">{{ t('common.delBtn') }}</el-button>`,
3198
- ];
3199
- if (isDictWithStatus) {
3200
- lines.push(` <!-- 启用${sanitizeHtmlComment(model.featureTitle)} -->`);
3201
- lines.push(` <el-button v-if="showDictAction('enable', row)" icon="circle-check" text type="primary" @click="handleEnable(row.${pkAttr})">{{ t('common.actions.enable') }}</el-button>`);
3202
- lines.push(` <!-- 禁用${sanitizeHtmlComment(model.featureTitle)} -->`);
3203
- lines.push(` <el-button v-if="showDictAction('disable', row)" icon="remove" text type="primary" @click="handleDisable(row.${pkAttr})">{{ t('common.actions.disable') }}</el-button>`);
3204
- }
3205
- return lines.join('\n');
3206
- }
3207
-
3208
- function renderSingleTableDialogDictHelpers(model) {
3209
- if (model.pageType !== 'dict' || !model.statusField) return '';
3210
- const statusField = model.statusField;
3211
- return [
3212
- "// 判断状态是否为启用",
3213
- "const isStatusEnabled = (value: any) => ['1', 1, true, 'true', 'enable', 'enabled'].includes(value);",
3214
- "const isStatusNew = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?].includes(value);",
3215
- "const isStatusNewOrDisabled = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?, '2', '缁備胶鏁?, 'disabled'].includes(value);",
3216
- '',
3217
- "// 控制字典列表动作按钮显隐",
3218
- "const showDictAction = (action: 'edit' | 'delete' | 'enable' | 'disable', row: any): boolean => {",
3219
- ` const status = row?.${statusField};`,
3220
- " if (action === 'edit') return isStatusNewOrDisabled(status);",
3221
- " if (action === 'delete') return isStatusNew(status);",
3222
- " if (action === 'enable') return isStatusNewOrDisabled(status);",
3223
- " if (action === 'disable') return isStatusEnabled(status);",
3224
- ' return false;',
3225
- '};',
3226
- '',
3227
- '// 启用当前字典数据',
3228
- 'const handleEnable = async (id: string | number) => {',
3229
- ' try {',
3230
- ' await enableObj(id);',
3231
- ' getDataList();',
3232
- " useMessage().success(t('common.messages.enableSuccess'));",
3233
- ' } catch (err: any) {',
3234
- " useMessage().error(err.msg || t('common.messages.enableError'));",
3235
- ' }',
3236
- '};',
3237
- '',
3238
- '// 禁用当前字典数据',
3239
- 'const handleDisable = async (id: string | number) => {',
3240
- ' try {',
3241
- ' await disableObj(id);',
3242
- ' getDataList();',
3243
- " useMessage().success(t('common.messages.disableSuccess'));",
3244
- ' } catch (err: any) {',
3245
- " useMessage().error(err.msg || t('common.messages.disableError'));",
3246
- ' }',
3247
- '};',
3248
- ].join('\n');
3249
- }
3250
-
3251
- function renderSingleTableDialogDictApiFunctions(model) {
3252
- if (model.pageType !== 'dict' || !model.statusField) return '';
3253
- const apiRoutePath = buildApiRoutePath(model.moduleName, model.apiPath || model.functionName).replace(/^\/+/, '');
3254
- return [
3255
- '// 启用当前字典数据',
3256
- "export function enableObj(id: string | number) {",
3257
- ' return request({',
3258
- ` url: '/${apiRoutePath}/enable',`,
3259
- " method: 'post',",
3260
- ' params: { id },',
3261
- ' });',
3262
- '}',
3263
- '',
3264
- '// 禁用当前字典数据',
3265
- "export function disableObj(id: string | number) {",
3266
- ' return request({',
3267
- ` url: '/${apiRoutePath}/disable',`,
3268
- " method: 'post',",
3269
- ' params: { id },',
3270
- ' });',
3271
- '}',
3272
- ].join('\n');
3273
- }
3274
-
3275
- function hasBusinessBillStateEditControl(model) {
3276
- return model.pageType === 'business' && !!model.statusField && model.statusDictType === 'bill_state';
3277
- }
3278
-
3279
- function renderBusinessStatusImports(model) {
3280
- return hasBusinessBillStateEditControl(model)
3281
- ? "// 业务单据编辑态判断\nimport { isBillStateEditing } from '/@/enums/dict-registry';"
3282
- : '';
3283
- }
3284
-
3285
- function renderBusinessStatusHelpers(model) {
3286
- if (!hasBusinessBillStateEditControl(model)) return '';
3287
- const statusAttr = toCamelCase(model.statusField);
3288
- return [
3289
- '// 控制业务单据编辑按钮显隐',
3290
- `const showEditAction = (row: any): boolean => isBillStateEditing(row?.${statusAttr});`,
3291
- ].join('\n');
3292
- }
3293
-
3294
- function sanitizeComment(value) {
3295
- return String(value || '').replace(/\*\//g, '* /').replace(/\r?\n/g, ' ').trim();
3296
- }
3297
-
3298
- function sanitizeHtmlComment(value) {
3299
- return String(value || '').replace(/--/g, '').replace(/\r?\n/g, ' ').trim();
3300
- }
3301
-
3302
- function renderExtraApiFunctions(model) {
3303
- if (!Array.isArray(model.extraApis) || !model.extraApis.length) return '';
3304
- return model.extraApis
3305
- .map((api) => {
3306
- const requestField = api.requestType === 'data' ? 'data' : 'params';
3307
- const lines = [
3308
- '',
3309
- `// 额外接口:${sanitizeComment(api.description)}`,
3310
- ];
3311
- if (api.usedBy) {
3312
- lines.push(`// 使用场景:${sanitizeComment(api.usedBy)}`);
3313
- }
3314
- if (api.source) {
3315
- lines.push(`// 来源说明:${sanitizeComment(api.source)}`);
3316
- }
3317
- lines.push(
3318
- `export function ${api.functionName}(payload?: any) {`,
3319
- ' return request({',
3320
- ` url: '${api.url}',`,
3321
- ` method: '${api.method}',`,
3322
- ` ${requestField}: payload,`,
3323
- ' });',
3324
- '}'
3325
- );
3326
- return lines.join('\n');
3327
- })
3328
- .join('\n');
3329
- }
3330
-
3331
- function renderApiRequestImport(model) {
3332
- const hasExtraApis = Array.isArray(model.extraApis) && model.extraApis.length > 0;
3333
- const hasDictStatusApis = model.pageType === 'dict' && !!model.statusField;
3334
- return hasExtraApis || hasDictStatusApis ? "import request from '/@/utils/request';" : '';
3335
- }
3336
-
3337
- function renderExtraApiFunctionsV2(model) {
3338
- if (!Array.isArray(model.extraApis) || !model.extraApis.length) return '';
3339
- return model.extraApis
3340
- .map((api) => {
3341
- const requestField = api.requestType === 'data' ? 'data' : 'params';
3342
- const lines = ['', `// 额外接口:${sanitizeComment(api.description)}`];
3343
- if (api.usedBy) {
3344
- lines.push(`// 使用场景:${sanitizeComment(api.usedBy)}`);
3345
- }
3346
- if (api.source) {
3347
- lines.push(`// 来源说明:${sanitizeComment(api.source)}`);
3348
- }
3349
- lines.push(
3350
- `export function ${api.functionName}(payload?: any) {`,
3351
- ' return request({',
3352
- ` url: '${api.url}',`,
3353
- ` method: '${api.method}',`,
3354
- ` ${requestField}: payload,`,
3355
- ' });',
3356
- '}'
3357
- );
3358
- return lines.join('\n');
3359
- })
3360
- .join('\n');
3361
- }
3362
-
3363
- function buildReplacements(model, sharedSupport) {
3364
- const menuBaseId = Date.now();
3365
- const apiModulePath = model.targetApiModule || `${model.moduleName}/${model.functionName}`;
3366
- const apiRoutePath = buildApiRoutePath(model.moduleName, model.apiPath || model.functionName).replace(/^\/+/, '');
3367
- const routePath = `${model.moduleName}/${model.functionName}`;
3187
+
3188
+ return files;
3189
+ }
3190
+
3191
+ function renderSingleTableDialogActions(model, permissionPrefix) {
3192
+ const pkAttr = model.pk.attrName;
3193
+ const isDictWithStatus = model.pageType === 'dict' && model.statusField;
3194
+ const lines = [
3195
+ ` <!-- 编辑${sanitizeHtmlComment(model.featureTitle)} -->`,
3196
+ ` <el-button${isDictWithStatus ? ` v-if="showDictAction('edit', row)"` : ''} icon="edit-pen" text type="primary" v-auth="'${permissionPrefix}_edit'" @click="formDialogRef.openDialog(row.${pkAttr})">{{ t('common.editBtn') }}</el-button>`,
3197
+ ` <!-- 删除${sanitizeHtmlComment(model.featureTitle)} -->`,
3198
+ ` <el-button${isDictWithStatus ? ` v-if="showDictAction('delete', row)"` : ''} icon="delete" text type="primary" v-auth="'${permissionPrefix}_del'" @click="handleDelete([row.${pkAttr}])">{{ t('common.delBtn') }}</el-button>`,
3199
+ ];
3200
+ if (isDictWithStatus) {
3201
+ lines.push(` <!-- 启用${sanitizeHtmlComment(model.featureTitle)} -->`);
3202
+ lines.push(` <el-button v-if="showDictAction('enable', row)" icon="circle-check" text type="primary" @click="handleEnable(row.${pkAttr})">{{ t('common.actions.enable') }}</el-button>`);
3203
+ lines.push(` <!-- 禁用${sanitizeHtmlComment(model.featureTitle)} -->`);
3204
+ lines.push(` <el-button v-if="showDictAction('disable', row)" icon="remove" text type="primary" @click="handleDisable(row.${pkAttr})">{{ t('common.actions.disable') }}</el-button>`);
3205
+ }
3206
+ return lines.join('\n');
3207
+ }
3208
+
3209
+ function renderSingleTableDialogDictHelpers(model) {
3210
+ if (model.pageType !== 'dict' || !model.statusField) return '';
3211
+ const statusField = model.statusField;
3212
+ return [
3213
+ "// 判断状态是否为启用",
3214
+ "const isStatusEnabled = (value: any) => ['1', 1, true, 'true', 'enable', 'enabled'].includes(value);",
3215
+ "const isStatusNew = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?].includes(value);",
3216
+ "const isStatusNewOrDisabled = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?, '2', '缁備胶鏁?, 'disabled'].includes(value);",
3217
+ '',
3218
+ "// 控制字典列表动作按钮显隐",
3219
+ "const showDictAction = (action: 'edit' | 'delete' | 'enable' | 'disable', row: any): boolean => {",
3220
+ ` const status = row?.${statusField};`,
3221
+ " if (action === 'edit') return isStatusNewOrDisabled(status);",
3222
+ " if (action === 'delete') return isStatusNew(status);",
3223
+ " if (action === 'enable') return isStatusNewOrDisabled(status);",
3224
+ " if (action === 'disable') return isStatusEnabled(status);",
3225
+ ' return false;',
3226
+ '};',
3227
+ '',
3228
+ '// 启用当前字典数据',
3229
+ 'const handleEnable = async (id: string | number) => {',
3230
+ ' try {',
3231
+ ' await enableObj(id);',
3232
+ ' getDataList();',
3233
+ " useMessage().success(t('common.messages.enableSuccess'));",
3234
+ ' } catch (err: any) {',
3235
+ " useMessage().error(err.msg || t('common.messages.enableError'));",
3236
+ ' }',
3237
+ '};',
3238
+ '',
3239
+ '// 禁用当前字典数据',
3240
+ 'const handleDisable = async (id: string | number) => {',
3241
+ ' try {',
3242
+ ' await disableObj(id);',
3243
+ ' getDataList();',
3244
+ " useMessage().success(t('common.messages.disableSuccess'));",
3245
+ ' } catch (err: any) {',
3246
+ " useMessage().error(err.msg || t('common.messages.disableError'));",
3247
+ ' }',
3248
+ '};',
3249
+ ].join('\n');
3250
+ }
3251
+
3252
+ function renderSingleTableDialogDictApiFunctions(model) {
3253
+ if (model.pageType !== 'dict' || !model.statusField) return '';
3254
+ const apiRoutePath = buildApiRoutePath(model.moduleName, model.apiPath || model.functionName).replace(/^\/+/, '');
3255
+ return [
3256
+ '// 启用当前字典数据',
3257
+ "export function enableObj(id: string | number) {",
3258
+ ' return request({',
3259
+ ` url: '/${apiRoutePath}/enable',`,
3260
+ " method: 'post',",
3261
+ ' params: { id },',
3262
+ ' });',
3263
+ '}',
3264
+ '',
3265
+ '// 禁用当前字典数据',
3266
+ "export function disableObj(id: string | number) {",
3267
+ ' return request({',
3268
+ ` url: '/${apiRoutePath}/disable',`,
3269
+ " method: 'post',",
3270
+ ' params: { id },',
3271
+ ' });',
3272
+ '}',
3273
+ ].join('\n');
3274
+ }
3275
+
3276
+ function hasBusinessBillStateEditControl(model) {
3277
+ return model.pageType === 'business' && !!model.statusField && model.statusDictType === 'bill_state';
3278
+ }
3279
+
3280
+ function renderBusinessStatusImports(model) {
3281
+ return hasBusinessBillStateEditControl(model)
3282
+ ? "// 业务单据编辑态判断\nimport { isBillStateEditing } from '/@/enums/dict-registry';"
3283
+ : '';
3284
+ }
3285
+
3286
+ function renderBusinessStatusHelpers(model) {
3287
+ if (!hasBusinessBillStateEditControl(model)) return '';
3288
+ const statusAttr = toCamelCase(model.statusField);
3289
+ return [
3290
+ '// 控制业务单据编辑按钮显隐',
3291
+ `const showEditAction = (row: any): boolean => isBillStateEditing(row?.${statusAttr});`,
3292
+ ].join('\n');
3293
+ }
3294
+
3295
+ function sanitizeComment(value) {
3296
+ return String(value || '').replace(/\*\//g, '* /').replace(/\r?\n/g, ' ').trim();
3297
+ }
3298
+
3299
+ function sanitizeHtmlComment(value) {
3300
+ return String(value || '').replace(/--/g, '').replace(/\r?\n/g, ' ').trim();
3301
+ }
3302
+
3303
+ function renderExtraApiFunctions(model) {
3304
+ if (!Array.isArray(model.extraApis) || !model.extraApis.length) return '';
3305
+ return model.extraApis
3306
+ .map((api) => {
3307
+ const requestField = api.requestType === 'data' ? 'data' : 'params';
3308
+ const lines = [
3309
+ '',
3310
+ `// 额外接口:${sanitizeComment(api.description)}`,
3311
+ ];
3312
+ if (api.usedBy) {
3313
+ lines.push(`// 使用场景:${sanitizeComment(api.usedBy)}`);
3314
+ }
3315
+ if (api.source) {
3316
+ lines.push(`// 来源说明:${sanitizeComment(api.source)}`);
3317
+ }
3318
+ lines.push(
3319
+ `export function ${api.functionName}(payload?: any) {`,
3320
+ ' return request({',
3321
+ ` url: '${api.url}',`,
3322
+ ` method: '${api.method}',`,
3323
+ ` ${requestField}: payload,`,
3324
+ ' });',
3325
+ '}'
3326
+ );
3327
+ return lines.join('\n');
3328
+ })
3329
+ .join('\n');
3330
+ }
3331
+
3332
+ function renderApiRequestImport(model) {
3333
+ const hasExtraApis = Array.isArray(model.extraApis) && model.extraApis.length > 0;
3334
+ const hasDictStatusApis = model.pageType === 'dict' && !!model.statusField;
3335
+ return hasExtraApis || hasDictStatusApis ? "import request from '/@/utils/request';" : '';
3336
+ }
3337
+
3338
+ function renderExtraApiFunctionsV2(model) {
3339
+ if (!Array.isArray(model.extraApis) || !model.extraApis.length) return '';
3340
+ return model.extraApis
3341
+ .map((api) => {
3342
+ const requestField = api.requestType === 'data' ? 'data' : 'params';
3343
+ const lines = ['', `// 额外接口:${sanitizeComment(api.description)}`];
3344
+ if (api.usedBy) {
3345
+ lines.push(`// 使用场景:${sanitizeComment(api.usedBy)}`);
3346
+ }
3347
+ if (api.source) {
3348
+ lines.push(`// 来源说明:${sanitizeComment(api.source)}`);
3349
+ }
3350
+ lines.push(
3351
+ `export function ${api.functionName}(payload?: any) {`,
3352
+ ' return request({',
3353
+ ` url: '${api.url}',`,
3354
+ ` method: '${api.method}',`,
3355
+ ` ${requestField}: payload,`,
3356
+ ' });',
3357
+ '}'
3358
+ );
3359
+ return lines.join('\n');
3360
+ })
3361
+ .join('\n');
3362
+ }
3363
+
3364
+ function buildReplacements(model, sharedSupport) {
3365
+ const menuBaseId = Date.now();
3366
+ const apiModulePath = model.targetApiModule || `${model.moduleName}/${model.functionName}`;
3367
+ const apiRoutePath = buildApiRoutePath(model.moduleName, model.apiPath || model.functionName).replace(/^\/+/, '');
3368
+ const routePath = `${model.moduleName}/${model.functionName}`;
3368
3369
  const permissionPrefix = `${model.moduleName}/${model.functionName}`.replace(/\//g, '_');
3369
3370
  const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
3370
3371
  const i18nNamespace = buildI18nNamespace(model);
@@ -3379,29 +3380,29 @@ function buildReplacements(model, sharedSupport) {
3379
3380
  API_MODULE_PATH: apiModulePath,
3380
3381
  API_PATH: apiRoutePath,
3381
3382
  VIEW_MODULE_PATH: routePath,
3382
- MENU_ROUTE_PATH: routePath,
3383
- I18N_NAMESPACE: i18nNamespace,
3384
- PERMISSION_PREFIX: permissionPrefix,
3385
- ACTION_COLUMN_WIDTH: model.pageType === 'dict' && model.statusField ? 300 : 180,
3386
- LIST_ACTIONS: renderSingleTableDialogActions(model, permissionPrefix),
3387
- DICT_API_IMPORTS: model.pageType === 'dict' && model.statusField ? ', enableObj, disableObj' : '',
3388
- DICT_LIST_HELPERS: renderSingleTableDialogDictHelpers(model),
3389
- DICT_API_FUNCTIONS: renderSingleTableDialogDictApiFunctions(model),
3390
- BUSINESS_STATUS_IMPORTS: renderBusinessStatusImports(model),
3391
- BUSINESS_EDIT_IF: hasBusinessBillStateEditControl(model) ? ' v-if="showEditAction(row)"' : '',
3392
- BUSINESS_STATUS_HELPERS: renderBusinessStatusHelpers(model),
3393
- API_REQUEST_IMPORT: renderApiRequestImport(model),
3394
- EXTRA_API_FUNCTIONS: renderExtraApiFunctionsV2(model),
3395
- CUSTOM_QUERY_FIELDS_EXPR: model.pageType === 'dict' ? '[]' : 'queryableDictOptions.value',
3396
- SHOW_RIGHT_TOOLS: model.pageType === 'dict' ? 'false' : 'true',
3397
- MENU_BASE_ID: menuBaseId,
3398
- MENU_BASE_ID_PLUS_1: menuBaseId + 1,
3399
- MENU_BASE_ID_PLUS_2: menuBaseId + 2,
3400
- MENU_BASE_ID_PLUS_3: menuBaseId + 3,
3401
- MENU_BASE_ID_PLUS_4: menuBaseId + 4,
3402
- MENU_BASE_ID_PLUS_5: menuBaseId + 5,
3403
- MENU_BASE_ID_PLUS_6: menuBaseId + 6,
3404
- GENERATED_AT: new Date().toISOString(),
3383
+ MENU_ROUTE_PATH: routePath,
3384
+ I18N_NAMESPACE: i18nNamespace,
3385
+ PERMISSION_PREFIX: permissionPrefix,
3386
+ ACTION_COLUMN_WIDTH: model.pageType === 'dict' && model.statusField ? 300 : 180,
3387
+ LIST_ACTIONS: renderSingleTableDialogActions(model, permissionPrefix),
3388
+ DICT_API_IMPORTS: model.pageType === 'dict' && model.statusField ? ', enableObj, disableObj' : '',
3389
+ DICT_LIST_HELPERS: renderSingleTableDialogDictHelpers(model),
3390
+ DICT_API_FUNCTIONS: renderSingleTableDialogDictApiFunctions(model),
3391
+ BUSINESS_STATUS_IMPORTS: renderBusinessStatusImports(model),
3392
+ BUSINESS_EDIT_IF: hasBusinessBillStateEditControl(model) ? ' v-if="showEditAction(row)"' : '',
3393
+ BUSINESS_STATUS_HELPERS: renderBusinessStatusHelpers(model),
3394
+ API_REQUEST_IMPORT: renderApiRequestImport(model),
3395
+ EXTRA_API_FUNCTIONS: renderExtraApiFunctionsV2(model),
3396
+ CUSTOM_QUERY_FIELDS_EXPR: model.pageType === 'dict' ? '[]' : 'queryableDictOptions.value',
3397
+ SHOW_RIGHT_TOOLS: model.pageType === 'dict' ? 'false' : 'true',
3398
+ MENU_BASE_ID: menuBaseId,
3399
+ MENU_BASE_ID_PLUS_1: menuBaseId + 1,
3400
+ MENU_BASE_ID_PLUS_2: menuBaseId + 2,
3401
+ MENU_BASE_ID_PLUS_3: menuBaseId + 3,
3402
+ MENU_BASE_ID_PLUS_4: menuBaseId + 4,
3403
+ MENU_BASE_ID_PLUS_5: menuBaseId + 5,
3404
+ MENU_BASE_ID_PLUS_6: menuBaseId + 6,
3405
+ GENERATED_AT: new Date().toISOString(),
3405
3406
  FORM_FIELDS: model.visibleFields.map(renderFormFieldV2).join('\n'),
3406
3407
  TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumn(field, dictRegistryRefs)).join('\n'),
3407
3408
  FORM_DEFAULTS: renderFormDefaults(model),
@@ -3492,9 +3493,9 @@ function ensureArguments(input) {
3492
3493
  moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
3493
3494
  targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
3494
3495
  targetApiModule: input.targetApiModule ? String(input.targetApiModule) : '',
3495
- targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
3496
- extraApis: input.extraApis === undefined ? [] : input.extraApis,
3497
- writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
3496
+ targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
3497
+ extraApis: input.extraApis === undefined ? [] : input.extraApis,
3498
+ writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
3498
3499
  overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
3499
3500
  writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
3500
3501
  mergeI18nZh: input.mergeI18nZh === undefined ? true : Boolean(input.mergeI18nZh),
@@ -3559,12 +3560,12 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
3559
3560
  disableApi: buildApiRoutePath(model.moduleName, moduleModel.disableApi).replace(/^\/+/, ''),
3560
3561
  })),
3561
3562
  })),
3562
- summary: {
3563
- totalLevels: model.levels.length,
3564
- totalModules: model.modules.length,
3565
- extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
3566
- dictFields: model.modules.flatMap((moduleModel) => moduleModel.optionFields.filter((field) => field.dictType).map((field) => `${moduleModel.key}.${field.attrName}`)),
3567
- },
3563
+ summary: {
3564
+ totalLevels: model.levels.length,
3565
+ totalModules: model.modules.length,
3566
+ extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
3567
+ dictFields: model.modules.flatMap((moduleModel) => moduleModel.optionFields.filter((field) => field.dictType).map((field) => `${moduleModel.key}.${field.attrName}`)),
3568
+ },
3568
3569
  note,
3569
3570
  };
3570
3571
  }
@@ -3625,9 +3626,9 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
3625
3626
  listVisibleFields: model.listFields.length,
3626
3627
  dictFields: model.optionFields.filter((field) => field.dictType).map((field) => field.attrName),
3627
3628
  skippedAuditFields: model.fields.filter((field) => field.isAudit).map((field) => field.fieldName),
3628
- childCount: model.children.length,
3629
- extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
3630
- childTables: model.children.map((childModel) => childModel.tableName),
3629
+ childCount: model.children.length,
3630
+ extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
3631
+ childTables: model.children.map((childModel) => childModel.tableName),
3631
3632
  childPayloadFields: model.children.map((childModel) => ({ childTableName: childModel.tableName, payloadField: childModel.payloadField || childModel.listName })),
3632
3633
  childVisibleFields: model.children.reduce((sum, childModel) => sum + childModel.visibleFields.length, 0),
3633
3634
  },