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.
- package/mcp_server.js +1463 -1501
- 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.
|
|
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
|
|
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
|
-
` <
|
|
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
|
-
|
|
2280
|
-
`
|
|
2281
|
-
`
|
|
2282
|
-
'
|
|
2283
|
-
'
|
|
2284
|
-
].join('\n');
|
|
2285
|
-
}
|
|
2286
|
-
|
|
2287
|
-
if (field.formType === '
|
|
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
|
},
|