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

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 -1501
  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.69';
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
  }
@@ -2108,72 +2108,34 @@ function includesAnyLabel(label, values) {
2108
2108
  }
2109
2109
 
2110
2110
  function getDefaultOptionFieldWidthV2(field) {
2111
- const label = normalizeWidthLabel(field);
2112
- const visibleLength = getLabelVisibleLength(label);
2113
-
2114
- if (field.formType === 'textarea') return '300';
2115
-
2116
- if (matchesAnyLabel(label, ['\u72b6\u6001', '\u5355\u636e\u72b6\u6001'])) return '80';
2117
- if (label === '\u9879\u76ee\u7f16\u53f7') return '80';
2118
- if (label === '\u7edf\u4e00\u793e\u4f1a\u4fe1\u7528\u4ee3\u7801') return '160';
2119
- if (matchesAnyLabel(label, ['\u56fd\u5bb6'])) return '100';
2120
- if (matchesAnyLabel(label, ['\u7701\u4efd', '\u57ce\u5e02'])) return '120';
2121
- if (matchesAnyLabel(label, ['\u89c4\u6a21'])) return '100';
2122
- if (matchesAnyLabel(label, ['\u9879\u76ee\u7b80\u79f0', '\u8d44\u8d28\u4f7f\u7528'])) return '200';
2123
- if (matchesAnyLabel(label, ['\u4e3b\u8ddf\u8e2a\u4eba', '\u8ddf\u8e2a\u5355\u4f4d', '\u7ec4\u7ec7\u673a\u6784\u5168\u8def\u5f84'])) return '200';
2124
- if (matchesAnyLabel(label, ['\u4e1a\u4e3b\u7b80\u79f0', '\u4f9b\u5e94\u5546\u7b80\u79f0', '\u7269\u8d44\u540d\u79f0', '\u4f5c\u4e1a\u540d\u79f0'])) return '150';
2125
- if (matchesAnyLabel(label, ['\u89c4\u683c\u578b\u53f7', '\u8ba1\u91cf\u5355\u4f4d'])) return '80';
2126
- if (matchesAnyLabel(label, ['\u4e1a\u4e3b\u5355\u4f4d', '\u76d1\u7406\u5355\u4f4d', '\u8bbe\u8ba1\u5355\u4f4d', '\u5206\u5305\u5355\u4f4d', '\u4f9b\u5e94\u5546\u540d\u79f0'])) return '250';
2127
- if (matchesAnyLabel(label, ['\u6750\u6599\u7279\u5f81'])) return '200';
2128
-
2129
- if (matchesAnyLabel(label, ['\u5408\u540c\u7f16\u53f7', '\u5355\u636e\u7f16\u53f7']) || (label !== '\u9879\u76ee\u7f16\u53f7' && label.endsWith('\u7f16\u7801'))) return '200';
2130
-
2131
- if (matchesAnyLabel(label, [
2132
- '\u9879\u76ee\u540d\u79f0',
2133
- '\u9879\u76ee\u540d\u79f0\u5408\u540c',
2134
- '\u9879\u76ee\u540d\u79f0\u4e2d\u6587',
2135
- '\u62db\u6807\u9879\u76ee\u540d\u79f0',
2136
- '\u5408\u540c\u540d\u79f0',
2137
- '\u5408\u540c\u540d\u79f0\u5408\u540c',
2138
- '\u5408\u540c\u540d\u79f0\u4e2d\u6587',
2139
- ])) return '350';
2140
-
2141
- if (includesAnyLabel(label, ['\u91d1\u989d'])) return '180';
2142
- if (includesAnyLabel(label, ['\u6570\u91cf', '\u5355\u4ef7', '\u6570\u989d'])) return '140';
2143
- if (label.endsWith('\u4eba')) return '90';
2144
- if (includesAnyLabel(label, ['\u7535\u8bdd'])) return '100';
2145
-
2146
- if (field.formType === 'datetime') return '150';
2147
- if (field.formType === 'date') return visibleLength > 7 ? '170' : '90';
2148
- if (field.formType === 'select') return visibleLength > 5 ? '200' : '100';
2149
-
2150
2111
  return '100';
2151
2112
  }
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);
2113
+ function renderOptionFieldV2(field, labelKey, dictRegistryRefs, indent = ' ') {
2114
+ const fallbackLabel = stripDictAnnotation(field.comment).replace(/'/g, "\\'");
2115
+ const parts = [`key: '${field.attrName}'`, `labelKey: '${labelKey}'`, `label: '${fallbackLabel}'`];
2116
+ const prdWidth = field.width ? field.width.replace('px', '') : null;
2117
+ const width = prdWidth || getDefaultOptionFieldWidthV2(field);
2156
2118
 
2157
2119
  if (width !== '120') {
2158
2120
  parts.push(`width: '${width}'`);
2159
2121
  }
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
- }
2122
+
2123
+ if (field.dictType) {
2124
+ parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
2125
+ parts.push('queryType: 30');
2126
+ }
2127
+ if (field.smart) {
2128
+ parts.push('smart: true');
2129
+ }
2130
+ if (field.show === false) {
2131
+ parts.push('show: false');
2132
+ }
2133
+ if (field.listShow !== field.show) {
2134
+ parts.push(`listShow: ${field.listShow ? 'true' : 'false'}`);
2135
+ }
2136
+ if (field.formShow !== field.show) {
2137
+ parts.push(`formShow: ${field.formShow ? 'true' : 'false'}`);
2138
+ }
2177
2139
 
2178
2140
  return `${indent}{ ${parts.join(', ')} },`;
2179
2141
  }
@@ -2202,23 +2164,23 @@ function renderTextareaMaxlengthAttrsV2(field) {
2202
2164
  return ` :maxlength="${field.length}" show-word-limit`;
2203
2165
  }
2204
2166
 
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) {
2167
+ function renderDisabledAttrV2(field) {
2168
+ return field.readonly ? ' disabled' : '';
2169
+ }
2170
+
2171
+ function renderDisabledBoolV2(field) {
2172
+ return field.readonly ? 'true' : 'false';
2173
+ }
2174
+
2175
+ function renderMicromeFormatAttr(field) {
2176
+ const length = Number.parseInt(String(field.length || ''), 10);
2177
+ const scale = Number.parseInt(String(field.scale || ''), 10);
2178
+ if (!length || Number.isNaN(length) || Number.isNaN(scale) || scale < 0) return '';
2179
+ const integerLength = Math.max(length - scale, 1);
2180
+ return ` format="${integerLength}-${scale}"`;
2181
+ }
2182
+
2183
+ function isAttachmentLikeField(field) {
2222
2184
  const fieldName = String(field?.fieldName || field?.attrName || '').toLowerCase();
2223
2185
  const comment = String(field?.comment || field?.description || '').toLowerCase();
2224
2186
  return fieldName.includes('attachment') || comment.includes('\u9644\u4ef6') || comment.includes('\u4e0a\u4f20');
@@ -2229,30 +2191,30 @@ function renderFieldCommentV2(field, indent = ' ') {
2229
2191
  return indent + '<!-- ' + label + ' -->';
2230
2192
  }
2231
2193
 
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">`,
2194
+ function renderFormFieldV2(field) {
2195
+ const prop = field.attrName;
2196
+ const labelExpr = `getMasterFieldLabel('${prop}')`;
2197
+ const dictExpr = `getMasterFieldMeta('${prop}')?.dictType`;
2198
+ const disabledAttr = renderDisabledAttrV2(field);
2199
+ const disabledBool = renderDisabledBoolV2(field);
2200
+
2201
+ if (field.formType === 'upload') {
2202
+ return [
2203
+ renderFieldCommentV2(field),
2204
+ ` <el-col :span="24" class="mb20">`,
2205
+ ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2206
+ ` <UploadFile v-model="form.${prop}"${disabledAttr} />`,
2207
+ ' </el-form-item>',
2208
+ ' </el-col>',
2209
+ ].join('\n');
2210
+ }
2211
+
2212
+ if (field.formType === 'select') {
2213
+ return [
2214
+ renderFieldCommentV2(field),
2215
+ ` <el-col :span="12" class="mb20">`,
2254
2216
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2255
- ` <el-select v-model="form.${prop}" :placeholder="formSelectPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr}>`,
2217
+ ` <el-select v-model="form.${prop}" :placeholder="formSelectPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr}>`,
2256
2218
  ` <el-option v-for="item in getDictOptions(${dictExpr})" :key="item.value" :label="item.label" :value="item.value" />`,
2257
2219
  ' </el-select>',
2258
2220
  ' </el-form-item>',
@@ -2260,49 +2222,49 @@ function renderFormFieldV2(field) {
2260
2222
  ].join('\n');
2261
2223
  }
2262
2224
 
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),
2225
+ if (field.formType === 'number') {
2226
+ const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
2227
+ const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
2228
+ return [
2229
+ renderFieldCommentV2(field),
2230
+ ` <el-col :span="12" class="mb20">`,
2231
+ ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2232
+ ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr} />`,
2233
+ ' </el-form-item>',
2234
+ ' </el-col>',
2235
+ ].join('\n');
2236
+ }
2237
+
2238
+ if (field.formType === 'microme-operator') {
2239
+ return [
2240
+ renderFieldCommentV2(field),
2268
2241
  ` <el-col :span="12" class="mb20">`,
2269
2242
  ` <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} />`,
2243
+ ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${disabledAttr} />`,
2271
2244
  ' </el-form-item>',
2272
2245
  ' </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') {
2246
+ ].join('\n');
2247
+ }
2248
+
2249
+ if (field.formType === 'microme-operator') {
2250
+ return [
2251
+ renderFieldCommentV2(field),
2252
+ ' <el-col :span="12" class="mb20">',
2253
+ ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2254
+ ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${disabledAttr} />`,
2255
+ ' </el-form-item>',
2256
+ ' </el-col>',
2257
+ ].join('\n');
2258
+ }
2259
+
2260
+ if (field.formType === 'datetime' || field.formType === 'date') {
2299
2261
  const pickerType = field.formType === 'datetime' ? 'datetime' : 'date';
2300
2262
  const formatName = field.formType === 'datetime' ? 'dateTimeStr' : 'dateStr';
2301
2263
  return [
2302
2264
  renderFieldCommentV2(field),
2303
2265
  ` <el-col :span="12" class="mb20">`,
2304
2266
  ` <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>`,
2267
+ ` <el-date-picker type="${pickerType}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"${disabledAttr}></el-date-picker>`,
2306
2268
  ' </el-form-item>',
2307
2269
  ' </el-col>',
2308
2270
  ].join('\n');
@@ -2314,7 +2276,7 @@ function renderFormFieldV2(field) {
2314
2276
  renderFieldCommentV2(field),
2315
2277
  ` <el-col :span="24" class="mb20">`,
2316
2278
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2317
- ` <el-input type="textarea" v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${textareaAttrs}${disabledAttr} />`,
2279
+ ` <el-input type="textarea" v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${textareaAttrs}${disabledAttr} />`,
2318
2280
  ' </el-form-item>',
2319
2281
  ' </el-col>',
2320
2282
  ].join('\n');
@@ -2325,29 +2287,29 @@ function renderFormFieldV2(field) {
2325
2287
  renderFieldCommentV2(field),
2326
2288
  ` <el-col :span="12" class="mb20">`,
2327
2289
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2328
- ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${maxlengthAttr}${disabledAttr} />`,
2290
+ ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${maxlengthAttr}${disabledAttr} />`,
2329
2291
  ' </el-form-item>',
2330
2292
  ' </el-col>',
2331
2293
  ].join('\n');
2332
2294
  }
2333
2295
 
2334
- function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs, indent = ' ') {
2335
- const parts = [
2336
- `key: '${field.attrName}'`,
2337
- `labelKey: '${buildMultiLevelFieldLabelKey(model, moduleModel, field)}'`,
2296
+ function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs, indent = ' ') {
2297
+ const parts = [
2298
+ `key: '${field.attrName}'`,
2299
+ `labelKey: '${buildMultiLevelFieldLabelKey(model, moduleModel, field)}'`,
2338
2300
  ];
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
- }
2301
+ const width = getDefaultOptionFieldWidthV2(field);
2302
+ if (width) parts.push(`width: '${width}'`);
2303
+ if (field.show === false) parts.push('show: false');
2304
+ if (field.listShow !== field.show) parts.push(`listShow: ${field.listShow ? 'true' : 'false'}`);
2305
+ if (field.formShow !== field.show) parts.push(`formShow: ${field.formShow ? 'true' : 'false'}`);
2306
+ if (field.smart) parts.push('smart: true');
2307
+ if (field.dictType) {
2308
+ parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
2309
+ parts.push('queryType: 30');
2310
+ }
2311
+ return `${indent}{ ${parts.join(', ')} },`;
2312
+ }
2351
2313
 
2352
2314
  function renderMultiLevelModuleDefinition(model, moduleModel, dictRegistryRefs) {
2353
2315
  const moduleApiPath = buildApiRoutePath(model.moduleName, moduleModel.apiPath);
@@ -2376,17 +2338,17 @@ function renderMultiLevelLevelConfig(level) {
2376
2338
  return ` { levelIndex: ${level.levelIndex}, position: '${level.position}', moduleKeys: [${level.modules.map((moduleModel) => `'${moduleModel.key}'`).join(', ')}] },`;
2377
2339
  }
2378
2340
 
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[]) =>',
2341
+ function renderMultiLevelOptionsTs(model, dictRegistryRefs) {
2342
+ return [
2343
+ "// schema 字段元数据类型",
2344
+ "import type { FieldMeta } from '/@/utils/crudSchema';",
2345
+ "// 多层字典页面配置类型",
2346
+ "import type { MultiLevelDictFieldConfig, MultiLevelDictLevelConfig, MultiLevelDictModuleConfig, MultiLevelDictModuleDefinition } from '/@/types/multiLevelDict';",
2347
+ "// 字典注册表",
2348
+ "import { DictRegistry } from '/@/enums/dict-registry';",
2349
+ '',
2350
+ '// 将字段数组转换为字段元数据映射',
2351
+ 'const toFieldMetaMap = (fields: MultiLevelDictFieldConfig[]) =>',
2390
2352
  ' Object.fromEntries(',
2391
2353
  ' fields.map((item) => [',
2392
2354
  ' item.key,',
@@ -2400,105 +2362,105 @@ function renderMultiLevelOptionsTs(model, dictRegistryRefs) {
2400
2362
  " ...(item.dictType ? { dictType: item.dictType } : {}),",
2401
2363
  ' } satisfies FieldMeta,',
2402
2364
  ' ])',
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 => ({',
2365
+ ' ) as Record<string, FieldMeta>;',
2366
+ '',
2367
+ '// 过滤有效字符串值',
2368
+ 'const isString = (value: string | undefined): value is string => Boolean(value);',
2369
+ '',
2370
+ '// 补齐模块级运行配置',
2371
+ 'const createModuleConfig = (definition: MultiLevelDictModuleDefinition): MultiLevelDictModuleConfig => ({',
2410
2372
  ' ...definition,',
2411
2373
  ' 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> = {',
2374
+ '});',
2375
+ '',
2376
+ '// 模块定义集合',
2377
+ 'const moduleDefinitions: Record<string, MultiLevelDictModuleDefinition> = {',
2416
2378
  model.modules.map((moduleModel) => renderMultiLevelModuleDefinition(model, moduleModel, dictRegistryRefs)).join('\n'),
2417
- '};',
2418
- '',
2419
- '// 页面模块配置',
2420
- 'export const moduleConfigs = Object.fromEntries(',
2379
+ '};',
2380
+ '',
2381
+ '// 页面模块配置',
2382
+ 'export const moduleConfigs = Object.fromEntries(',
2421
2383
  ' Object.entries(moduleDefinitions).map(([key, definition]) => [key, createModuleConfig(definition)])',
2422
- ') as Record<string, MultiLevelDictModuleConfig>;',
2423
- '',
2424
- '// 多层布局配置',
2425
- 'export const levelConfigs: MultiLevelDictLevelConfig[] = [',
2384
+ ') as Record<string, MultiLevelDictModuleConfig>;',
2385
+ '',
2386
+ '// 多层布局配置',
2387
+ 'export const levelConfigs: MultiLevelDictLevelConfig[] = [',
2426
2388
  model.levels.map(renderMultiLevelLevelConfig).join('\n'),
2427
- '];',
2428
- '',
2429
- '// 各模块字段分组',
2430
- 'export const moduleFieldGroups = Object.fromEntries(',
2389
+ '];',
2390
+ '',
2391
+ '// 各模块字段分组',
2392
+ 'export const moduleFieldGroups = Object.fromEntries(',
2431
2393
  ' 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) {
2394
+ ') as Record<string, Record<string, FieldMeta>>;',
2395
+ '',
2396
+ '// 页面依赖的全部字典类型',
2397
+ 'export const allDictTypes = Array.from(new Set(Object.values(moduleConfigs).flatMap((config) => config.dictTypes)));',
2398
+ '',
2399
+ ].join('\n');
2400
+ }
2401
+
2402
+ function renderMultiLevelApiFunctions(moduleModel) {
2441
2403
  const pkAttr = moduleModel.pk.attrName;
2442
2404
  const basePath = buildApiRoutePath(moduleModel.moduleName, moduleModel.apiPath);
2443
2405
  const enablePath = buildApiRoutePath(moduleModel.moduleName, moduleModel.enableApi);
2444
2406
  const disablePath = buildApiRoutePath(moduleModel.moduleName, moduleModel.disableApi);
2445
- return [
2446
- `// 查询${moduleModel.title}分页列表`,
2447
- `export function fetch${moduleModel.className}List(query?: any) {`,
2407
+ return [
2408
+ `// 查询${moduleModel.title}分页列表`,
2409
+ `export function fetch${moduleModel.className}List(query?: any) {`,
2448
2410
  ' return request({',
2449
2411
  ` url: '${basePath}/page',`,
2450
2412
  " method: 'get',",
2451
2413
  ' params: query,',
2452
2414
  ' });',
2453
- '}',
2454
- '',
2455
- `// 查询${moduleModel.title}详情`,
2456
- `export function get${moduleModel.className}Obj(id: string | number) {`,
2415
+ '}',
2416
+ '',
2417
+ `// 查询${moduleModel.title}详情`,
2418
+ `export function get${moduleModel.className}Obj(id: string | number) {`,
2457
2419
  ' return request({',
2458
2420
  ` url: '${basePath}/getById',`,
2459
2421
  " method: 'get',",
2460
2422
  ` params: { ${pkAttr}: id },`,
2461
2423
  ' });',
2462
- '}',
2463
- '',
2464
- `// 新增${moduleModel.title}`,
2465
- `export function add${moduleModel.className}Obj(data: any) {`,
2424
+ '}',
2425
+ '',
2426
+ `// 新增${moduleModel.title}`,
2427
+ `export function add${moduleModel.className}Obj(data: any) {`,
2466
2428
  ' return request({',
2467
2429
  ` url: '${basePath}/save',`,
2468
2430
  " method: 'post',",
2469
2431
  ' data,',
2470
2432
  ' });',
2471
- '}',
2472
- '',
2473
- `// 更新${moduleModel.title}`,
2474
- `export function put${moduleModel.className}Obj(data: any) {`,
2433
+ '}',
2434
+ '',
2435
+ `// 更新${moduleModel.title}`,
2436
+ `export function put${moduleModel.className}Obj(data: any) {`,
2475
2437
  ' return request({',
2476
2438
  ` url: '${basePath}/updateById',`,
2477
2439
  " method: 'post',",
2478
2440
  ' data,',
2479
2441
  ' });',
2480
- '}',
2481
- '',
2482
- `// 删除${moduleModel.title}`,
2483
- `export function del${moduleModel.className}Objs(data: Array<string | number>) {`,
2442
+ '}',
2443
+ '',
2444
+ `// 删除${moduleModel.title}`,
2445
+ `export function del${moduleModel.className}Objs(data: Array<string | number>) {`,
2484
2446
  ' return request({',
2485
2447
  ` url: '${basePath}/removeByIds',`,
2486
2448
  " method: 'post',",
2487
2449
  ' data,',
2488
2450
  ' });',
2489
- '}',
2490
- '',
2491
- `// 启用${moduleModel.title}`,
2492
- `export function enable${moduleModel.className}(id: string | number) {`,
2451
+ '}',
2452
+ '',
2453
+ `// 启用${moduleModel.title}`,
2454
+ `export function enable${moduleModel.className}(id: string | number) {`,
2493
2455
  ' return request({',
2494
2456
  ` url: '${enablePath}',`,
2495
2457
  " method: 'post',",
2496
2458
  ' params: { id },',
2497
2459
  ' });',
2498
- '}',
2499
- '',
2500
- `// 禁用${moduleModel.title}`,
2501
- `export function disable${moduleModel.className}(id: string | number) {`,
2460
+ '}',
2461
+ '',
2462
+ `// 禁用${moduleModel.title}`,
2463
+ `export function disable${moduleModel.className}(id: string | number) {`,
2502
2464
  ' return request({',
2503
2465
  ` url: '${disablePath}',`,
2504
2466
  " method: 'post',",
@@ -2508,22 +2470,22 @@ function renderMultiLevelApiFunctions(moduleModel) {
2508
2470
  ].join('\n');
2509
2471
  }
2510
2472
 
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
- }
2473
+ function renderMultiLevelApiTs(model) {
2474
+ return [
2475
+ "// 请求工具",
2476
+ "import request from '/@/utils/request';",
2477
+ '',
2478
+ model.modules.map(renderMultiLevelApiFunctions).join('\n\n'),
2479
+ renderExtraApiFunctions(model),
2480
+ '',
2481
+ ].join('\n');
2482
+ }
2521
2483
 
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);
2484
+ function renderMultiLevelFormField(field) {
2485
+ const labelExpr = `getFieldLabel('${field.attrName}')`;
2486
+ const prop = field.attrName;
2487
+ const disabledAttr = renderDisabledAttrV2(field);
2488
+ const disabledBool = renderDisabledBoolV2(field);
2527
2489
 
2528
2490
  if (isAttachmentLikeField(field)) {
2529
2491
  return [
@@ -2541,7 +2503,7 @@ function renderMultiLevelFormField(field) {
2541
2503
  renderFieldCommentV2(field),
2542
2504
  ' <el-col :span="12" class="mb20">',
2543
2505
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2544
- ` <el-select v-model="form.${prop}" :placeholder="formSelectPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr}>`,
2506
+ ` <el-select v-model="form.${prop}" :placeholder="formSelectPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr}>`,
2545
2507
  ` <el-option v-for="item in getDictOptions(getFieldMeta('${prop}')?.dictType)" :key="item.value" :label="item.label" :value="item.value" />`,
2546
2508
  ' </el-select>',
2547
2509
  ' </el-form-item>',
@@ -2556,7 +2518,7 @@ function renderMultiLevelFormField(field) {
2556
2518
  renderFieldCommentV2(field),
2557
2519
  ' <el-col :span="12" class="mb20">',
2558
2520
  ` <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} />`,
2521
+ ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr} />`,
2560
2522
  ' </el-form-item>',
2561
2523
  ' </el-col>',
2562
2524
  ].join('\n');
@@ -2569,7 +2531,7 @@ function renderMultiLevelFormField(field) {
2569
2531
  renderFieldCommentV2(field),
2570
2532
  ' <el-col :span="12" class="mb20">',
2571
2533
  ` <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>`,
2534
+ ` <el-date-picker type="${pickerType}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"${disabledAttr}></el-date-picker>`,
2573
2535
  ' </el-form-item>',
2574
2536
  ' </el-col>',
2575
2537
  ].join('\n');
@@ -2581,7 +2543,7 @@ function renderMultiLevelFormField(field) {
2581
2543
  renderFieldCommentV2(field),
2582
2544
  ' <el-col :span="24" class="mb20">',
2583
2545
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2584
- ` <el-input type="textarea" v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${textareaAttrs}${disabledAttr} />`,
2546
+ ` <el-input type="textarea" v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${textareaAttrs}${disabledAttr} />`,
2585
2547
  ' </el-form-item>',
2586
2548
  ' </el-col>',
2587
2549
  ].join('\n');
@@ -2591,7 +2553,7 @@ function renderMultiLevelFormField(field) {
2591
2553
  renderFieldCommentV2(field),
2592
2554
  ' <el-col :span="12" class="mb20">',
2593
2555
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2594
- ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${renderInputMaxlengthAttr(field)}${disabledAttr} />`,
2556
+ ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${renderInputMaxlengthAttr(field)}${disabledAttr} />`,
2595
2557
  ' </el-form-item>',
2596
2558
  ' </el-col>',
2597
2559
  ].join('\n');
@@ -2607,19 +2569,19 @@ function renderMultiLevelFormVue(model, moduleModel) {
2607
2569
  ` tenantId: Session.getTenant(),`,
2608
2570
  ].join('\n');
2609
2571
  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">
2572
+ return `<template>
2573
+ <!-- 表单弹窗:新增或编辑${sanitizeHtmlComment(moduleModel.comment || moduleModel.tableName)}数据 -->
2574
+ <el-dialog v-model="visible" :title="form.${moduleModel.pk.attrName} ? t('common.editBtn') : t('common.addBtn')" :close-on-click-modal="false" draggable>
2575
+ <!-- 弹窗表单:按 PRD 表单显隐和顺序渲染字段 -->
2576
+ <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading">
2577
+ <!-- 字典字段区:字段级注释由 MCP 根据字段名称生成 -->
2578
+ <el-row :gutter="24">
2579
+ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2580
+ </el-row>
2581
+ </el-form>
2582
+ <!-- 弹窗底部操作按钮:取消和确认提交 -->
2583
+ <template #footer>
2584
+ <span class="dialog-footer">
2623
2585
  <el-button @click="visible = false">{{ t('common.cancelButtonText') }}</el-button>
2624
2586
  <el-button type="primary" @click="onSubmit" :disabled="loading">{{ t('common.confirmButtonText') }}</el-button>
2625
2587
  </span>
@@ -2627,66 +2589,66 @@ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2627
2589
  </el-dialog>
2628
2590
  </template>
2629
2591
 
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) => {
2592
+ <script setup lang="ts" name="${componentName}">
2593
+ // 通用消息提示
2594
+ import { useMessage } from '/@/hooks/message';
2595
+ // 本地会话存储
2596
+ import { Session } from '/@/utils/storage';
2597
+ // 字典数据加载
2598
+ import { useDict } from '/@/hooks/dict';
2599
+ // 表单字段元数据能力
2600
+ import { useCrudPageMeta } from '/@/hooks/useCrudPageMeta';
2601
+ // 国际化能力
2602
+ import { useI18n } from 'vue-i18n';
2603
+ // 模块配置
2604
+ import { moduleConfigs, moduleFieldGroups } from './options';
2605
+ // 当前模块接口
2606
+ import { get${moduleModel.className}Obj, add${moduleModel.className}Obj, put${moduleModel.className}Obj } from '/@/api/${model.moduleName}/${model.functionName}';
2607
+
2608
+ // 当前模块配置
2609
+ const moduleConfig = moduleConfigs.${moduleModel.key};
2610
+ // 当前模块字典引用
2611
+ const dictRefs = useDict(...moduleConfig.dictTypes);
2612
+ // 国际化方法
2613
+ const { t } = useI18n();
2614
+ // 弹窗刷新事件
2615
+ const emit = defineEmits(['refresh']);
2616
+
2617
+ // 表单引用
2618
+ const dataFormRef = ref();
2619
+ // 弹窗显示状态
2620
+ const visible = ref(false);
2621
+ // 提交加载状态
2622
+ const loading = ref(false);
2623
+
2624
+ // 字段标签、字典和校验提示
2625
+ const { getFieldMeta, getFieldLabel, getDictOptions, inputPlaceholder, selectPlaceholder, fieldRequiredMessage } = useCrudPageMeta(${fieldsMapExpr}, dictRefs);
2626
+ const formInputPlaceholder = (label: string, disabled = false) => (disabled ? '' : inputPlaceholder(label));
2627
+ const formSelectPlaceholder = (label: string, disabled = false) => (disabled ? '' : selectPlaceholder(label));
2628
+
2629
+ // 统一维护表单默认值
2630
+ const createDefaultFormState = () => ({
2631
+ ${defaultLines}
2632
+ });
2633
+
2634
+ // 表单响应式数据
2635
+ const form = reactive(createDefaultFormState());
2636
+
2637
+ // 设置父级关联字段
2638
+ const setParentFieldValue = (parentId: string | number) => {
2677
2639
  const parentField = moduleConfig.queryParentField as keyof typeof form | undefined;
2678
2640
  if (!parentField) return;
2679
2641
  const currentValue = form[parentField];
2680
2642
  (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) => {
2643
+ };
2644
+
2645
+ // 表单校验规则
2646
+ const dataRules = ref({
2647
+ ${rules}
2648
+ });
2649
+
2650
+ // 加载详情数据
2651
+ const getData = async (id: string | number) => {
2690
2652
  try {
2691
2653
  loading.value = true;
2692
2654
  const { data } = await get${moduleModel.className}Obj(id);
@@ -2696,18 +2658,18 @@ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2696
2658
  } finally {
2697
2659
  loading.value = false;
2698
2660
  }
2699
- };
2700
-
2701
- // 重置表单为初始状态
2702
- const resetFormState = () => {
2661
+ };
2662
+
2663
+ // 重置表单为初始状态
2664
+ const resetFormState = () => {
2703
2665
  Object.assign(form, createDefaultFormState());
2704
2666
  nextTick(() => {
2705
2667
  dataFormRef.value?.resetFields();
2706
2668
  });
2707
- };
2708
-
2709
- // 打开弹窗并按需回填父子层级关系
2710
- const openDialog = async (id?: string | number, parentId?: string | number) => {
2669
+ };
2670
+
2671
+ // 打开弹窗并按需回填父子层级关系
2672
+ const openDialog = async (id?: string | number, parentId?: string | number) => {
2711
2673
  visible.value = true;
2712
2674
  resetFormState();
2713
2675
  if (moduleConfig.queryParentField && parentId !== undefined && parentId !== null && parentId !== '') {
@@ -2717,10 +2679,10 @@ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2717
2679
  form.${moduleModel.pk.attrName} = String(id);
2718
2680
  await getData(id);
2719
2681
  }
2720
- };
2721
-
2722
- // 提交弹窗表单
2723
- const onSubmit = async () => {
2682
+ };
2683
+
2684
+ // 提交弹窗表单
2685
+ const onSubmit = async () => {
2724
2686
  loading.value = true;
2725
2687
  const valid = await dataFormRef.value.validate().catch(() => {});
2726
2688
  if (!valid) {
@@ -2738,126 +2700,126 @@ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
2738
2700
  } finally {
2739
2701
  loading.value = false;
2740
2702
  }
2741
- };
2742
-
2743
- // 向父组件暴露打开弹窗方法
2744
- defineExpose({
2745
- openDialog,
2746
- });
2703
+ };
2704
+
2705
+ // 向父组件暴露打开弹窗方法
2706
+ defineExpose({
2707
+ openDialog,
2708
+ });
2747
2709
  </script>
2748
2710
  `;
2749
2711
  }
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
- }
2712
+ function renderMultiLevelSchemaListSlot(levelVarName, activeKeyVarName, activeModuleVarName) {
2713
+ return [
2714
+ ` <div class="multi-level-slot" v-if="${levelVarName}">`,
2715
+ ' <!-- 多层字典页签:切换当前层级下的字典模块 -->',
2716
+ ' <el-tabs class="multi-level-tabs" v-if="' + `${levelVarName}.moduleKeys.length > 1` + `" v-model="${activeKeyVarName}">`,
2717
+ ` <el-tab-pane v-for="moduleKey in ${levelVarName}.moduleKeys" :key="moduleKey" :label="resolveModuleTitle(moduleConfigs[moduleKey])" :name="moduleKey" />`,
2718
+ ' </el-tabs>',
2719
+ ' <!-- 多层字典列表面板:承载当前层级的工具栏和表格 -->',
2720
+ ' <div class="multi-level-panel" v-if="' + `${activeModuleVarName}` + `">`,
2721
+ ' <div class="layout-padding-auto layout-padding-view flex h-full flex-col">',
2722
+ ' <!-- 当前层级工具栏:搜索、新增和重置 -->',
2723
+ ` <SchemaListToolbar`,
2724
+ ` v-bind="getPanelToolbarProps(${activeModuleVarName}.key)"`,
2725
+ ` @update:keyword="handlePanelKeywordChange(${activeModuleVarName}.key, $event)"`,
2726
+ ` @add="openCreate(${activeModuleVarName}.key)"`,
2727
+ ` @query="handlePanelQuery(${activeModuleVarName}.key)"`,
2728
+ ` @reset="handlePanelReset(${activeModuleVarName}.key)"`,
2729
+ ` />`,
2730
+ ' <!-- 当前层级表格:展示字典数据、分页和行操作 -->',
2731
+ ` <SchemaListTable`,
2732
+ ` v-bind="getPanelTableProps(${activeModuleVarName}.key)"`,
2733
+ ` @row-current-change="handlePanelCurrentChange(${activeModuleVarName}.key, $event.row)"`,
2734
+ ` @current-change="handleCurrentPageChange(${activeModuleVarName}.key, $event.current)"`,
2735
+ ` @size-change="handlePageSizeChange(${activeModuleVarName}.key, $event.size)"`,
2736
+ ` >`,
2737
+ ' <!-- 行操作按钮:编辑、删除、启用、禁用 -->',
2738
+ ` <template #actions="{ row }">`,
2739
+ ' <!-- 编辑当前字典数据 -->',
2740
+ ` <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>`,
2741
+ ' <!-- 删除当前字典数据 -->',
2742
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'delete', row)" icon="delete" text type="primary" @click="handleDelete(${activeModuleVarName}.key, row)">{{ t('common.delBtn') }}</el-button>`,
2743
+ ' <!-- 启用当前字典数据 -->',
2744
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'enable', row)" icon="circle-check" text type="primary" @click="handleEnable(${activeModuleVarName}.key, row)">闁告凹鍨抽弫?/el-button>`,
2745
+ ' <!-- 禁用当前字典数据 -->',
2746
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'disable', row)" icon="remove" text type="primary" @click="handleDisable(${activeModuleVarName}.key, row)">缂佸倷鑳堕弫?/el-button>`,
2747
+ ` </template>`,
2748
+ ` </SchemaListTable>`,
2749
+ ' </div>',
2750
+ ' </div>',
2751
+ ` </div>`,
2752
+ ].join('\n');
2753
+ }
2754
+
2755
+ function renderMultiLevelLevelSlot(levelVarName, activeKeyVarName, activeModuleVarName) {
2756
+ return [
2757
+ ` <div class="multi-level-slot" v-if="${levelVarName}">`,
2758
+ ' <!-- 多层字典页签:切换当前层级下的字典模块 -->',
2759
+ ' <el-tabs class="multi-level-tabs" v-if="' + `${levelVarName}.moduleKeys.length > 1` + `" v-model="${activeKeyVarName}">`,
2760
+ ` <el-tab-pane v-for="moduleKey in ${levelVarName}.moduleKeys" :key="moduleKey" :label="resolveModuleTitle(moduleConfigs[moduleKey])" :name="moduleKey" />`,
2761
+ ' </el-tabs>',
2762
+ ' <!-- 多层字典列表面板:承载当前层级的新增按钮、表格和分页 -->',
2763
+ ' <div class="multi-level-panel" v-if="' + `${activeModuleVarName}` + `">`,
2764
+ ' <div class="layout-padding-auto layout-padding-view flex h-full flex-col">',
2765
+ ' <!-- 当前层级新增按钮:受父级状态约束控制 -->',
2766
+ ' <div class="mb8" style="width: 100%">',
2767
+ ` <el-button icon="folder-add" type="primary" class="ml10" :disabled="isAddDisabled(${activeModuleVarName}.key)" @click="openCreate(${activeModuleVarName}.key)">{{ t('common.addBtn') }}</el-button>`,
2768
+ ' </div>',
2769
+ ' <!-- 当前层级表格:展示字典数据并处理行选中联动 -->',
2770
+ ' <el-table',
2771
+ ` :data="moduleStateMap[${activeModuleVarName}.key].dataList"`,
2772
+ ` v-loading="moduleStateMap[${activeModuleVarName}.key].loading"`,
2773
+ ' border',
2774
+ ' height="100%"',
2775
+ ' highlight-current-row',
2776
+ ` @current-change="handlePanelCurrentChange(${activeModuleVarName}.key, $event)"`,
2777
+ ' >',
2778
+ ' <!-- 序号列 -->',
2779
+ ` <el-table-column type="index" :label="t('common.serial')" width="60" />`,
2780
+ ' <!-- 字典业务字段列 -->',
2781
+ ` <el-table-column`,
2782
+ ` v-for="column in getListFields(${activeModuleVarName}.key)"`,
2783
+ ' :key="column.key"',
2784
+ ' :prop="column.key"',
2785
+ ` :label="resolveLabel(column.labelKey, column.key || '')"`,
2786
+ ` :min-width="column.width || '120'"`,
2787
+ ' show-overflow-tooltip',
2788
+ ' >',
2789
+ ' <template #default="scope">',
2790
+ ' <dict-tag v-if="column.dictType" :options="getDictOptions(column.dictType)" :value="scope.row[column.key]" />',
2791
+ ' <span v-else>{{ scope.row[column.key] }}</span>',
2792
+ ' </template>',
2793
+ ' </el-table-column>',
2794
+ ' <!-- 行操作按钮:编辑、删除、启用、禁用 -->',
2795
+ ` <el-table-column :label="t('common.action')" width="260">`,
2796
+ ' <template #default="{ row }">',
2797
+ ' <!-- 编辑当前字典数据 -->',
2798
+ ` <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>`,
2799
+ ' <!-- 删除当前字典数据 -->',
2800
+ ` <el-button v-if="showModuleAction(${activeModuleVarName}.key, 'delete', row)" icon="delete" text type="primary" @click="handleDelete(${activeModuleVarName}.key, row)">{{ t('common.delBtn') }}</el-button>`,
2801
+ ' <!-- 启用当前字典数据 -->',
2802
+ ` <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>`,
2803
+ ' <!-- 禁用当前字典数据 -->',
2804
+ ` <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>`,
2805
+ ' </template>',
2806
+ ' </el-table-column>',
2807
+ ' </el-table>',
2808
+ ' <!-- 当前层级分页组件 -->',
2809
+ ' <div class="mt-2.5 flex shrink-0 justify-end">',
2810
+ ' <pagination',
2811
+ ` :current-page="moduleStateMap[${activeModuleVarName}.key].currentPage"`,
2812
+ ` :page-size="moduleStateMap[${activeModuleVarName}.key].pageSize"`,
2813
+ ` :total="moduleStateMap[${activeModuleVarName}.key].total"`,
2814
+ ` @current-change="handleCurrentPageChange(${activeModuleVarName}.key, $event)"`,
2815
+ ` @size-change="handlePageSizeChange(${activeModuleVarName}.key, $event)"`,
2816
+ ' />',
2817
+ ' </div>',
2818
+ ' </div>',
2819
+ ' </div>',
2820
+ ` </div>`,
2821
+ ].join('\n');
2822
+ }
2861
2823
 
2862
2824
  function renderMultiLevelIndexVue(model) {
2863
2825
  const level2 = model.levels.find((level) => level.levelIndex === 2);
@@ -2893,71 +2855,71 @@ function renderMultiLevelIndexVue(model) {
2893
2855
  )
2894
2856
  .join('\n');
2895
2857
 
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,
2858
+ return `<!-- 功能名称:${sanitizeHtmlComment(model.featureTitle)} -->
2859
+ <template>
2860
+ <!-- 页面布局:${sanitizeHtmlComment(model.featureTitle)}多层字典列表页 -->
2861
+ <div class="layout-padding">
2862
+ <!-- 多层字典主布局:左侧一级字典,右侧二级和三级字典联动 -->
2863
+ <div class="multi-level-dict-layout">
2864
+ <!-- 一级字典区域 -->
2865
+ <div class="multi-level-left">
2866
+ ${renderMultiLevelSchemaListSlot('level1Config', 'activeLevel1Key', 'activeLevel1Module')}
2867
+ </div>
2868
+ <!-- 二级和三级字典区域 -->
2869
+ <div class="multi-level-right">
2870
+ <!-- 二级字典区域 -->
2871
+ <div class="multi-level-right-top">
2872
+ ${level2 ? renderMultiLevelSchemaListSlot('level2Config', 'activeLevel2Key', 'activeLevel2Module') : ''}
2873
+ </div>
2874
+ <!-- 三级字典区域 -->
2875
+ <div v-if="level3Config" class="multi-level-right-bottom">
2876
+ ${level3 ? renderMultiLevelSchemaListSlot('level3Config', 'activeLevel3Key', 'activeLevel3Module') : ''}
2877
+ </div>
2878
+ </div>
2879
+ </div>
2880
+
2881
+ <!-- 多层字典表单弹窗:按模块异步挂载 -->
2882
+ ${formComponents}
2883
+ </div>
2884
+ </template>
2885
+
2886
+ <script setup lang="ts" name="system${model.className}">
2887
+ // 通用消息与确认弹窗
2888
+ import { useMessage, useMessageBox } from '/@/hooks/message';
2889
+ // 字典数据加载
2890
+ import { useDict } from '/@/hooks/dict';
2891
+ // 多层字典元数据能力
2892
+ import { useMultiLevelDictMeta } from '/@/hooks/useMultiLevelDictMeta';
2893
+ // 多层字典页面状态能力
2894
+ import { useMultiLevelDictPage } from '/@/hooks/useMultiLevelDictPage';
2895
+ // 统一列表工具栏组件
2896
+ import SchemaListToolbar from '/@/components/schema-list/SchemaListToolbar.vue';
2897
+ // 统一列表表格组件
2898
+ import SchemaListTable from '/@/components/schema-list/SchemaListTable.vue';
2899
+ // 国际化能力
2900
+ import { useI18n } from 'vue-i18n';
2901
+ // 页面配置
2902
+ import { allDictTypes, levelConfigs, moduleConfigs } from './options';
2903
+ // 模块接口集合
2904
+ import { ${apiImports} } from '/@/api/${model.moduleName}/${model.functionName}';
2905
+
2906
+ // 各层级表单组件
2907
+ ${asyncImports}
2908
+
2909
+ // 各层级表单引用
2910
+ ${formRefs}
2911
+ // 模块与表单引用映射
2912
+ ${formRefMap}
2913
+ // 模块与接口处理器映射
2914
+ ${apiHandlerMap}
2915
+
2916
+ // 国际化方法
2917
+ const { t } = useI18n();
2918
+ // 页面所需的字典引用
2919
+ const dictRefs = useDict(...allDictTypes);
2920
+
2921
+ const {
2922
+ level1Config,
2961
2923
  level2Config,
2962
2924
  level3Config,
2963
2925
  activeLevel1Key,
@@ -2969,46 +2931,46 @@ const {
2969
2931
  getModuleLevel,
2970
2932
  } = useMultiLevelDictMeta(levelConfigs, moduleConfigs);
2971
2933
 
2972
- // 解析字段双语标签
2973
- const resolveLabel = (labelKey?: string, fallback = '') => {
2934
+ // 解析字段双语标签
2935
+ const resolveLabel = (labelKey?: string, fallback = '') => {
2974
2936
  if (!labelKey) return fallback;
2975
2937
  const translated = t(labelKey);
2976
2938
  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,
2939
+ };
2940
+
2941
+ // 解析模块标题
2942
+ const resolveModuleTitle = (moduleConfig: any) => resolveLabel(moduleConfig?.titleKey, moduleConfig?.key || '');
2943
+ // 读取字典选项
2944
+ const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
2945
+ // 判断状态是否为启用
2946
+ const isStatusEnabled = (value: any) => ['1', 1, true, 'true', 'enable', 'enabled'].includes(value);
2947
+ const isStatusNew = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?].includes(value);
2948
+ const isStatusNewOrDisabled = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?, '2', '缁備胶鏁?, 'disabled'].includes(value);
2949
+ // 控制行内动作按钮显隐
2950
+ const showModuleAction = (moduleKey: string, action: 'edit' | 'delete' | 'enable' | 'disable', row: any): boolean => {
2951
+ const moduleConfig = moduleConfigs[moduleKey];
2952
+ const statusValue = moduleConfig?.statusField ? row?.[moduleConfig.statusField] : undefined;
2953
+ if (action === 'edit') return statusValue === undefined || isStatusNewOrDisabled(statusValue);
2954
+ if (action === 'delete') return statusValue === undefined || isStatusNew(statusValue);
2955
+ if (action === 'enable') return !!moduleConfig?.statusField && isStatusNewOrDisabled(statusValue);
2956
+ if (action === 'disable') return !!moduleConfig?.statusField && isStatusEnabled(statusValue);
2957
+ return false;
2958
+ };
2959
+
2960
+ const {
2961
+ moduleStateMap,
2962
+ tableStyle,
2963
+ getParentIdForModule,
2964
+ getListFields,
2965
+ getSmartFields,
2966
+ isAddDisabled,
2967
+ loadModuleData,
2968
+ handleKeywordChange,
2969
+ handleQuery,
2970
+ handleResetQuery,
2971
+ handleSelectRow,
2972
+ handleCurrentPageChange,
2973
+ handlePageSizeChange,
3012
2974
  handleFormRefresh,
3013
2975
  } = useMultiLevelDictPage(
3014
2976
  levelConfigs,
@@ -3020,91 +2982,91 @@ const {
3020
2982
  activeLevel2Module,
3021
2983
  activeLevel3Module,
3022
2984
  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) => {
2985
+ apiHandlerMap
2986
+ );
2987
+
2988
+ const getPanelColumns = (moduleKey: string) =>
2989
+ getListFields(moduleKey).map((column: any) => ({
2990
+ prop: column.key,
2991
+ label: resolveLabel(column.labelKey, column.key || ''),
2992
+ width: column.width || '120',
2993
+ dictType: column.dictType,
2994
+ options: column.dictType ? getDictOptions(column.dictType) : [],
2995
+ }));
2996
+
2997
+ const getPanelSearchPlaceholder = (moduleKey: string) => {
2998
+ const labels = getSmartFields(moduleKey)
2999
+ .map((field: any) => resolveLabel(field.labelKey, field.key || ''))
3000
+ .filter(Boolean);
3001
+ return t('common.placeholders.searchKeywords', {
3002
+ label: labels.join(' / ') || 'keyword',
3003
+ });
3004
+ };
3005
+
3006
+ // 生成面板工具栏透传属性
3007
+ const getPanelToolbarProps = (moduleKey: string) => ({
3008
+ keyword: moduleStateMap[moduleKey].queryForm.smartVal,
3009
+ searchPlaceholder: getPanelSearchPlaceholder(moduleKey),
3010
+ queryModel: moduleStateMap[moduleKey].queryForm,
3011
+ selectedIds: [],
3012
+ showImport: false,
3013
+ showDelete: false,
3014
+ showQueryTools: true,
3015
+ showRightTools: false,
3016
+ deleteDisabled: true,
3017
+ addText: t('common.addBtn'),
3018
+ });
3019
+
3020
+ // 生成面板表格透传属性
3021
+ const getPanelTableProps = (moduleKey: string) => ({
3022
+ data: moduleStateMap[moduleKey].dataList,
3023
+ loading: !!moduleStateMap[moduleKey].loading,
3024
+ columns: getPanelColumns(moduleKey),
3025
+ pagination: {
3026
+ current: moduleStateMap[moduleKey].currentPage,
3027
+ size: moduleStateMap[moduleKey].pageSize,
3028
+ total: moduleStateMap[moduleKey].total,
3029
+ },
3030
+ tableStyle,
3031
+ rowIdKey: moduleConfigs[moduleKey].primaryKey,
3032
+ showSelection: false,
3033
+ highlightCurrentRow: true,
3034
+ actionColumnWidth: 260,
3035
+ });
3036
+
3037
+ // 打开新增弹窗
3038
+ const openCreate = (moduleKey: string) => {
3077
3039
  formRefMap[moduleKey]?.value?.openDialog(undefined, getParentIdForModule(moduleKey));
3078
3040
  };
3079
3041
 
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) => {
3042
+ // 打开编辑弹窗
3043
+ const openEdit = (moduleKey: string, row: any) => {
3044
+ const primaryKey = moduleConfigs[moduleKey].primaryKey;
3045
+ formRefMap[moduleKey]?.value?.openDialog(row?.[primaryKey], getParentIdForModule(moduleKey));
3046
+ };
3047
+
3048
+ // 面板当前行变化处理
3049
+ const handlePanelCurrentChange = (moduleKey: string, row: any) => {
3050
+ if (row) handleSelectRow(moduleKey, row);
3051
+ };
3052
+
3053
+ // 面板关键字变更处理
3054
+ const handlePanelKeywordChange = (moduleKey: string, keyword: string) => {
3055
+ handleKeywordChange(moduleKey, keyword);
3056
+ };
3057
+
3058
+ // 面板查询处理
3059
+ const handlePanelQuery = (moduleKey: string) => {
3060
+ handleQuery(moduleKey);
3061
+ };
3062
+
3063
+ // 面板重置处理
3064
+ const handlePanelReset = (moduleKey: string) => {
3065
+ handleResetQuery(moduleKey);
3066
+ };
3067
+
3068
+ // 删除当前模块数据
3069
+ const handleDelete = async (moduleKey: string, row: any) => {
3108
3070
  try {
3109
3071
  await useMessageBox().confirm(t('common.delConfirmText'));
3110
3072
  } catch {
@@ -3121,8 +3083,8 @@ const getPanelSearchPlaceholder = (moduleKey: string) => {
3121
3083
  }
3122
3084
  };
3123
3085
 
3124
- // 启用当前模块数据
3125
- const handleEnable = async (moduleKey: string, row: any) => {
3086
+ // 启用当前模块数据
3087
+ const handleEnable = async (moduleKey: string, row: any) => {
3126
3088
  try {
3127
3089
  const primaryKey = moduleConfigs[moduleKey].primaryKey;
3128
3090
  await apiHandlerMap[moduleKey].enable(row?.[primaryKey]);
@@ -3133,8 +3095,8 @@ const getPanelSearchPlaceholder = (moduleKey: string) => {
3133
3095
  }
3134
3096
  };
3135
3097
 
3136
- // 禁用当前模块数据
3137
- const handleDisable = async (moduleKey: string, row: any) => {
3098
+ // 禁用当前模块数据
3099
+ const handleDisable = async (moduleKey: string, row: any) => {
3138
3100
  try {
3139
3101
  const primaryKey = moduleConfigs[moduleKey].primaryKey;
3140
3102
  await apiHandlerMap[moduleKey].disable(row?.[primaryKey]);
@@ -3157,10 +3119,10 @@ function renderMultiLevelMenuSql(model) {
3157
3119
  ].join('\n');
3158
3120
  }
3159
3121
 
3160
- function renderMultiLevelFiles(model, sharedSupport, localeZhSupport) {
3161
- const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
3162
- const viewRoot = buildViewRoot(model);
3163
- const apiFilePath = buildApiFilePath(model);
3122
+ function renderMultiLevelFiles(model, sharedSupport, localeZhSupport) {
3123
+ const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
3124
+ const viewRoot = buildViewRoot(model);
3125
+ const apiFilePath = buildApiFilePath(model);
3164
3126
  const menuRoot = path.join(model.frontendPath, 'menu');
3165
3127
  const files = [
3166
3128
  { type: 'list', path: path.join(viewRoot, 'index.vue'), content: renderMultiLevelIndexVue(model) },
@@ -3183,188 +3145,188 @@ function renderMultiLevelFiles(model, sharedSupport, localeZhSupport) {
3183
3145
  content: renderMultiLevelFormVue(model, moduleModel),
3184
3146
  });
3185
3147
  });
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}`;
3148
+
3149
+ return files;
3150
+ }
3151
+
3152
+ function renderSingleTableDialogActions(model, permissionPrefix) {
3153
+ const pkAttr = model.pk.attrName;
3154
+ const isDictWithStatus = model.pageType === 'dict' && model.statusField;
3155
+ const lines = [
3156
+ ` <!-- 编辑${sanitizeHtmlComment(model.featureTitle)} -->`,
3157
+ ` <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>`,
3158
+ ` <!-- 删除${sanitizeHtmlComment(model.featureTitle)} -->`,
3159
+ ` <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>`,
3160
+ ];
3161
+ if (isDictWithStatus) {
3162
+ lines.push(` <!-- 启用${sanitizeHtmlComment(model.featureTitle)} -->`);
3163
+ 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>`);
3164
+ lines.push(` <!-- 禁用${sanitizeHtmlComment(model.featureTitle)} -->`);
3165
+ lines.push(` <el-button v-if="showDictAction('disable', row)" icon="remove" text type="primary" @click="handleDisable(row.${pkAttr})">{{ t('common.actions.disable') }}</el-button>`);
3166
+ }
3167
+ return lines.join('\n');
3168
+ }
3169
+
3170
+ function renderSingleTableDialogDictHelpers(model) {
3171
+ if (model.pageType !== 'dict' || !model.statusField) return '';
3172
+ const statusField = model.statusField;
3173
+ return [
3174
+ "// 判断状态是否为启用",
3175
+ "const isStatusEnabled = (value: any) => ['1', 1, true, 'true', 'enable', 'enabled'].includes(value);",
3176
+ "const isStatusNew = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?].includes(value);",
3177
+ "const isStatusNewOrDisabled = (value: any) => ['0', 0, false, 'false', 'new', '閺傛澘顤?, '2', '缁備胶鏁?, 'disabled'].includes(value);",
3178
+ '',
3179
+ "// 控制字典列表动作按钮显隐",
3180
+ "const showDictAction = (action: 'edit' | 'delete' | 'enable' | 'disable', row: any): boolean => {",
3181
+ ` const status = row?.${statusField};`,
3182
+ " if (action === 'edit') return isStatusNewOrDisabled(status);",
3183
+ " if (action === 'delete') return isStatusNew(status);",
3184
+ " if (action === 'enable') return isStatusNewOrDisabled(status);",
3185
+ " if (action === 'disable') return isStatusEnabled(status);",
3186
+ ' return false;',
3187
+ '};',
3188
+ '',
3189
+ '// 启用当前字典数据',
3190
+ 'const handleEnable = async (id: string | number) => {',
3191
+ ' try {',
3192
+ ' await enableObj(id);',
3193
+ ' getDataList();',
3194
+ " useMessage().success(t('common.messages.enableSuccess'));",
3195
+ ' } catch (err: any) {',
3196
+ " useMessage().error(err.msg || t('common.messages.enableError'));",
3197
+ ' }',
3198
+ '};',
3199
+ '',
3200
+ '// 禁用当前字典数据',
3201
+ 'const handleDisable = async (id: string | number) => {',
3202
+ ' try {',
3203
+ ' await disableObj(id);',
3204
+ ' getDataList();',
3205
+ " useMessage().success(t('common.messages.disableSuccess'));",
3206
+ ' } catch (err: any) {',
3207
+ " useMessage().error(err.msg || t('common.messages.disableError'));",
3208
+ ' }',
3209
+ '};',
3210
+ ].join('\n');
3211
+ }
3212
+
3213
+ function renderSingleTableDialogDictApiFunctions(model) {
3214
+ if (model.pageType !== 'dict' || !model.statusField) return '';
3215
+ const apiRoutePath = buildApiRoutePath(model.moduleName, model.apiPath || model.functionName).replace(/^\/+/, '');
3216
+ return [
3217
+ '// 启用当前字典数据',
3218
+ "export function enableObj(id: string | number) {",
3219
+ ' return request({',
3220
+ ` url: '/${apiRoutePath}/enable',`,
3221
+ " method: 'post',",
3222
+ ' params: { id },',
3223
+ ' });',
3224
+ '}',
3225
+ '',
3226
+ '// 禁用当前字典数据',
3227
+ "export function disableObj(id: string | number) {",
3228
+ ' return request({',
3229
+ ` url: '/${apiRoutePath}/disable',`,
3230
+ " method: 'post',",
3231
+ ' params: { id },',
3232
+ ' });',
3233
+ '}',
3234
+ ].join('\n');
3235
+ }
3236
+
3237
+ function hasBusinessBillStateEditControl(model) {
3238
+ return model.pageType === 'business' && !!model.statusField && model.statusDictType === 'bill_state';
3239
+ }
3240
+
3241
+ function renderBusinessStatusImports(model) {
3242
+ return hasBusinessBillStateEditControl(model)
3243
+ ? "// 业务单据编辑态判断\nimport { isBillStateEditing } from '/@/enums/dict-registry';"
3244
+ : '';
3245
+ }
3246
+
3247
+ function renderBusinessStatusHelpers(model) {
3248
+ if (!hasBusinessBillStateEditControl(model)) return '';
3249
+ const statusAttr = toCamelCase(model.statusField);
3250
+ return [
3251
+ '// 控制业务单据编辑按钮显隐',
3252
+ `const showEditAction = (row: any): boolean => isBillStateEditing(row?.${statusAttr});`,
3253
+ ].join('\n');
3254
+ }
3255
+
3256
+ function sanitizeComment(value) {
3257
+ return String(value || '').replace(/\*\//g, '* /').replace(/\r?\n/g, ' ').trim();
3258
+ }
3259
+
3260
+ function sanitizeHtmlComment(value) {
3261
+ return String(value || '').replace(/--/g, '').replace(/\r?\n/g, ' ').trim();
3262
+ }
3263
+
3264
+ function renderExtraApiFunctions(model) {
3265
+ if (!Array.isArray(model.extraApis) || !model.extraApis.length) return '';
3266
+ return model.extraApis
3267
+ .map((api) => {
3268
+ const requestField = api.requestType === 'data' ? 'data' : 'params';
3269
+ const lines = [
3270
+ '',
3271
+ `// 额外接口:${sanitizeComment(api.description)}`,
3272
+ ];
3273
+ if (api.usedBy) {
3274
+ lines.push(`// 使用场景:${sanitizeComment(api.usedBy)}`);
3275
+ }
3276
+ if (api.source) {
3277
+ lines.push(`// 来源说明:${sanitizeComment(api.source)}`);
3278
+ }
3279
+ lines.push(
3280
+ `export function ${api.functionName}(payload?: any) {`,
3281
+ ' return request({',
3282
+ ` url: '${api.url}',`,
3283
+ ` method: '${api.method}',`,
3284
+ ` ${requestField}: payload,`,
3285
+ ' });',
3286
+ '}'
3287
+ );
3288
+ return lines.join('\n');
3289
+ })
3290
+ .join('\n');
3291
+ }
3292
+
3293
+ function renderApiRequestImport(model) {
3294
+ const hasExtraApis = Array.isArray(model.extraApis) && model.extraApis.length > 0;
3295
+ const hasDictStatusApis = model.pageType === 'dict' && !!model.statusField;
3296
+ return hasExtraApis || hasDictStatusApis ? "import request from '/@/utils/request';" : '';
3297
+ }
3298
+
3299
+ function renderExtraApiFunctionsV2(model) {
3300
+ if (!Array.isArray(model.extraApis) || !model.extraApis.length) return '';
3301
+ return model.extraApis
3302
+ .map((api) => {
3303
+ const requestField = api.requestType === 'data' ? 'data' : 'params';
3304
+ const lines = ['', `// 额外接口:${sanitizeComment(api.description)}`];
3305
+ if (api.usedBy) {
3306
+ lines.push(`// 使用场景:${sanitizeComment(api.usedBy)}`);
3307
+ }
3308
+ if (api.source) {
3309
+ lines.push(`// 来源说明:${sanitizeComment(api.source)}`);
3310
+ }
3311
+ lines.push(
3312
+ `export function ${api.functionName}(payload?: any) {`,
3313
+ ' return request({',
3314
+ ` url: '${api.url}',`,
3315
+ ` method: '${api.method}',`,
3316
+ ` ${requestField}: payload,`,
3317
+ ' });',
3318
+ '}'
3319
+ );
3320
+ return lines.join('\n');
3321
+ })
3322
+ .join('\n');
3323
+ }
3324
+
3325
+ function buildReplacements(model, sharedSupport) {
3326
+ const menuBaseId = Date.now();
3327
+ const apiModulePath = model.targetApiModule || `${model.moduleName}/${model.functionName}`;
3328
+ const apiRoutePath = buildApiRoutePath(model.moduleName, model.apiPath || model.functionName).replace(/^\/+/, '');
3329
+ const routePath = `${model.moduleName}/${model.functionName}`;
3368
3330
  const permissionPrefix = `${model.moduleName}/${model.functionName}`.replace(/\//g, '_');
3369
3331
  const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
3370
3332
  const i18nNamespace = buildI18nNamespace(model);
@@ -3379,29 +3341,29 @@ function buildReplacements(model, sharedSupport) {
3379
3341
  API_MODULE_PATH: apiModulePath,
3380
3342
  API_PATH: apiRoutePath,
3381
3343
  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(),
3344
+ MENU_ROUTE_PATH: routePath,
3345
+ I18N_NAMESPACE: i18nNamespace,
3346
+ PERMISSION_PREFIX: permissionPrefix,
3347
+ ACTION_COLUMN_WIDTH: model.pageType === 'dict' && model.statusField ? 300 : 180,
3348
+ LIST_ACTIONS: renderSingleTableDialogActions(model, permissionPrefix),
3349
+ DICT_API_IMPORTS: model.pageType === 'dict' && model.statusField ? ', enableObj, disableObj' : '',
3350
+ DICT_LIST_HELPERS: renderSingleTableDialogDictHelpers(model),
3351
+ DICT_API_FUNCTIONS: renderSingleTableDialogDictApiFunctions(model),
3352
+ BUSINESS_STATUS_IMPORTS: renderBusinessStatusImports(model),
3353
+ BUSINESS_EDIT_IF: hasBusinessBillStateEditControl(model) ? ' v-if="showEditAction(row)"' : '',
3354
+ BUSINESS_STATUS_HELPERS: renderBusinessStatusHelpers(model),
3355
+ API_REQUEST_IMPORT: renderApiRequestImport(model),
3356
+ EXTRA_API_FUNCTIONS: renderExtraApiFunctionsV2(model),
3357
+ CUSTOM_QUERY_FIELDS_EXPR: model.pageType === 'dict' ? '[]' : 'queryableDictOptions.value',
3358
+ SHOW_RIGHT_TOOLS: model.pageType === 'dict' ? 'false' : 'true',
3359
+ MENU_BASE_ID: menuBaseId,
3360
+ MENU_BASE_ID_PLUS_1: menuBaseId + 1,
3361
+ MENU_BASE_ID_PLUS_2: menuBaseId + 2,
3362
+ MENU_BASE_ID_PLUS_3: menuBaseId + 3,
3363
+ MENU_BASE_ID_PLUS_4: menuBaseId + 4,
3364
+ MENU_BASE_ID_PLUS_5: menuBaseId + 5,
3365
+ MENU_BASE_ID_PLUS_6: menuBaseId + 6,
3366
+ GENERATED_AT: new Date().toISOString(),
3405
3367
  FORM_FIELDS: model.visibleFields.map(renderFormFieldV2).join('\n'),
3406
3368
  TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumn(field, dictRegistryRefs)).join('\n'),
3407
3369
  FORM_DEFAULTS: renderFormDefaults(model),
@@ -3492,9 +3454,9 @@ function ensureArguments(input) {
3492
3454
  moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
3493
3455
  targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
3494
3456
  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),
3457
+ targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
3458
+ extraApis: input.extraApis === undefined ? [] : input.extraApis,
3459
+ writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
3498
3460
  overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
3499
3461
  writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
3500
3462
  mergeI18nZh: input.mergeI18nZh === undefined ? true : Boolean(input.mergeI18nZh),
@@ -3559,12 +3521,12 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
3559
3521
  disableApi: buildApiRoutePath(model.moduleName, moduleModel.disableApi).replace(/^\/+/, ''),
3560
3522
  })),
3561
3523
  })),
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
- },
3524
+ summary: {
3525
+ totalLevels: model.levels.length,
3526
+ totalModules: model.modules.length,
3527
+ extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
3528
+ dictFields: model.modules.flatMap((moduleModel) => moduleModel.optionFields.filter((field) => field.dictType).map((field) => `${moduleModel.key}.${field.attrName}`)),
3529
+ },
3568
3530
  note,
3569
3531
  };
3570
3532
  }
@@ -3625,9 +3587,9 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
3625
3587
  listVisibleFields: model.listFields.length,
3626
3588
  dictFields: model.optionFields.filter((field) => field.dictType).map((field) => field.attrName),
3627
3589
  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),
3590
+ childCount: model.children.length,
3591
+ extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
3592
+ childTables: model.children.map((childModel) => childModel.tableName),
3631
3593
  childPayloadFields: model.children.map((childModel) => ({ childTableName: childModel.tableName, payloadField: childModel.payloadField || childModel.listName })),
3632
3594
  childVisibleFields: model.children.reduce((sum, childModel) => sum + childModel.visibleFields.length, 0),
3633
3595
  },