worsoft-frontend-codegen-local-mcp 0.1.39 → 0.1.41
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
CHANGED
|
@@ -5,57 +5,57 @@ 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.41';
|
|
9
9
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
10
10
|
const TOOL_NAME = 'worsoft_codegen_local_generate_frontend';
|
|
11
|
-
const STYLE_CATALOG_PATH = path.join(__dirname, 'assets', 'style-catalog.json');
|
|
11
|
+
const STYLE_CATALOG_PATH = path.join(__dirname, 'assets', 'style-catalog.json');
|
|
12
12
|
const STYLE_CATALOG = loadStyleCatalog();
|
|
13
13
|
const DEFAULT_DICT_REGISTRY_KEYS = {
|
|
14
14
|
add_start_stop: 'COMMON_STATUS',
|
|
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
|
-
alwaysHide: boolean;
|
|
22
|
-
smart: boolean;
|
|
23
|
-
queryType?: number;
|
|
24
|
-
labelKey: string;
|
|
25
|
-
label?: string;
|
|
26
|
-
width: string;
|
|
27
|
-
dictType?: string;
|
|
28
|
-
}
|
|
18
|
+
const DEFAULT_CRUD_SCHEMA_TEMPLATE = `export interface FieldMeta {
|
|
19
|
+
show: boolean;
|
|
20
|
+
listShow: boolean;
|
|
21
|
+
alwaysHide: boolean;
|
|
22
|
+
smart: boolean;
|
|
23
|
+
queryType?: number;
|
|
24
|
+
labelKey: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
width: string;
|
|
27
|
+
dictType?: string;
|
|
28
|
+
}
|
|
29
29
|
|
|
30
30
|
export interface FieldConfig {
|
|
31
31
|
key: string;
|
|
32
32
|
labelKey?: string;
|
|
33
33
|
label?: string;
|
|
34
34
|
width?: string;
|
|
35
|
-
show?: boolean;
|
|
36
|
-
listShow?: boolean;
|
|
37
|
-
alwaysHide?: boolean;
|
|
38
|
-
smart?: boolean;
|
|
39
|
-
queryType?: number;
|
|
40
|
-
dictType?: string;
|
|
41
|
-
}
|
|
35
|
+
show?: boolean;
|
|
36
|
+
listShow?: boolean;
|
|
37
|
+
alwaysHide?: boolean;
|
|
38
|
+
smart?: boolean;
|
|
39
|
+
queryType?: number;
|
|
40
|
+
dictType?: string;
|
|
41
|
+
}
|
|
42
42
|
|
|
43
43
|
export interface CrudSchemaDefinition {
|
|
44
44
|
master: FieldConfig[];
|
|
45
45
|
children?: Record<string, FieldConfig[]>;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export interface CrudSchema {
|
|
49
|
-
master: Record<string, FieldMeta>;
|
|
50
|
-
children: Record<string, Record<string, FieldMeta>>;
|
|
51
|
-
smartNames: string[];
|
|
52
|
-
filterTypes: Record<string, number>;
|
|
53
|
-
childFilterTypes: Record<string, Record<string, number>>;
|
|
54
|
-
allDictTypes: string[];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const DEFAULT_WIDTH = '120';
|
|
58
|
-
const DEFAULT_DICT_QUERY_TYPE = 30;
|
|
48
|
+
export interface CrudSchema {
|
|
49
|
+
master: Record<string, FieldMeta>;
|
|
50
|
+
children: Record<string, Record<string, FieldMeta>>;
|
|
51
|
+
smartNames: string[];
|
|
52
|
+
filterTypes: Record<string, number>;
|
|
53
|
+
childFilterTypes: Record<string, Record<string, number>>;
|
|
54
|
+
allDictTypes: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const DEFAULT_WIDTH = '120';
|
|
58
|
+
const DEFAULT_DICT_QUERY_TYPE = 30;
|
|
59
59
|
|
|
60
60
|
export const field = (labelKey: string, width = DEFAULT_WIDTH): FieldMeta => ({
|
|
61
61
|
show: true,
|
|
@@ -71,17 +71,17 @@ export const dictField = (labelKey: string, dictType: string, width = DEFAULT_WI
|
|
|
71
71
|
dictType,
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
export const buildFilterTypes = (fields: Record<string, FieldMeta>) =>
|
|
75
|
-
Object.fromEntries(
|
|
76
|
-
Object.entries(fields)
|
|
77
|
-
.filter(([, item]) => typeof item.queryType === 'number')
|
|
78
|
-
.map(([key, item]) => [key, item.queryType as number])
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
export const buildSmartNames = (fields: Record<string, FieldMeta>) =>
|
|
82
|
-
Object.entries(fields)
|
|
83
|
-
.filter(([, item]) => item.smart)
|
|
84
|
-
.map(([key]) => key);
|
|
74
|
+
export const buildFilterTypes = (fields: Record<string, FieldMeta>) =>
|
|
75
|
+
Object.fromEntries(
|
|
76
|
+
Object.entries(fields)
|
|
77
|
+
.filter(([, item]) => typeof item.queryType === 'number')
|
|
78
|
+
.map(([key, item]) => [key, item.queryType as number])
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export const buildSmartNames = (fields: Record<string, FieldMeta>) =>
|
|
82
|
+
Object.entries(fields)
|
|
83
|
+
.filter(([, item]) => item.smart)
|
|
84
|
+
.map(([key]) => key);
|
|
85
85
|
|
|
86
86
|
export const collectDictTypes = (...groups: Array<Record<string, FieldMeta>>) =>
|
|
87
87
|
Array.from(
|
|
@@ -94,19 +94,19 @@ export const collectDictTypes = (...groups: Array<Record<string, FieldMeta>>) =>
|
|
|
94
94
|
)
|
|
95
95
|
);
|
|
96
96
|
|
|
97
|
-
const normalizeField = (item: FieldConfig): FieldMeta => ({
|
|
98
|
-
show: item.show ?? true,
|
|
99
|
-
listShow: item.listShow ?? item.show ?? true,
|
|
100
|
-
alwaysHide: item.alwaysHide ?? false,
|
|
101
|
-
smart: item.smart ?? false,
|
|
102
|
-
...(typeof item.queryType === 'number'
|
|
103
|
-
? { queryType: item.queryType }
|
|
104
|
-
: item.dictType && (item.listShow ?? item.show ?? true)
|
|
105
|
-
? { queryType: DEFAULT_DICT_QUERY_TYPE }
|
|
106
|
-
: {}),
|
|
107
|
-
labelKey: item.labelKey ?? item.label ?? item.key,
|
|
108
|
-
...(item.label ? { label: item.label } : {}),
|
|
109
|
-
width: item.width ?? DEFAULT_WIDTH,
|
|
97
|
+
const normalizeField = (item: FieldConfig): FieldMeta => ({
|
|
98
|
+
show: item.show ?? true,
|
|
99
|
+
listShow: item.listShow ?? item.show ?? true,
|
|
100
|
+
alwaysHide: item.alwaysHide ?? false,
|
|
101
|
+
smart: item.smart ?? false,
|
|
102
|
+
...(typeof item.queryType === 'number'
|
|
103
|
+
? { queryType: item.queryType }
|
|
104
|
+
: item.dictType && (item.listShow ?? item.show ?? true)
|
|
105
|
+
? { queryType: DEFAULT_DICT_QUERY_TYPE }
|
|
106
|
+
: {}),
|
|
107
|
+
labelKey: item.labelKey ?? item.label ?? item.key,
|
|
108
|
+
...(item.label ? { label: item.label } : {}),
|
|
109
|
+
width: item.width ?? DEFAULT_WIDTH,
|
|
110
110
|
...(item.dictType ? { dictType: item.dictType } : {}),
|
|
111
111
|
});
|
|
112
112
|
|
|
@@ -116,13 +116,13 @@ const toFieldMap = (fields: FieldConfig[]) =>
|
|
|
116
116
|
const buildSchema = (
|
|
117
117
|
master: Record<string, FieldMeta>,
|
|
118
118
|
children: Record<string, Record<string, FieldMeta>> = {}
|
|
119
|
-
): CrudSchema => ({
|
|
120
|
-
master,
|
|
121
|
-
children,
|
|
122
|
-
smartNames: buildSmartNames(master),
|
|
123
|
-
filterTypes: buildFilterTypes(master),
|
|
124
|
-
childFilterTypes: Object.fromEntries(
|
|
125
|
-
Object.entries(children).map(([key, fields]) => [key, buildFilterTypes(fields)])
|
|
119
|
+
): CrudSchema => ({
|
|
120
|
+
master,
|
|
121
|
+
children,
|
|
122
|
+
smartNames: buildSmartNames(master),
|
|
123
|
+
filterTypes: buildFilterTypes(master),
|
|
124
|
+
childFilterTypes: Object.fromEntries(
|
|
125
|
+
Object.entries(children).map(([key, fields]) => [key, buildFilterTypes(fields)])
|
|
126
126
|
),
|
|
127
127
|
allDictTypes: collectDictTypes(master, ...Object.values(children)),
|
|
128
128
|
});
|
|
@@ -148,19 +148,19 @@ export function createCrudSchema(
|
|
|
148
148
|
const TOOL_SCHEMA = {
|
|
149
149
|
type: 'object',
|
|
150
150
|
properties: {
|
|
151
|
-
featureTitle: { type: 'string', description: 'Feature title from pre-parsed structured metadata.' },
|
|
152
|
-
tableName: { type: 'string', description: 'Canonical main table name from PRD-aligned structured metadata.' },
|
|
153
|
-
tableComment: { type: 'string', description: 'Canonical main table comment or feature label from PRD-aligned structured metadata.' },
|
|
154
|
-
apiPath: { type: 'string', description: 'Backend API base path from pre-parsed structured metadata, for example iwmEmpOutsourcePerson.' },
|
|
155
|
-
pageType: {
|
|
156
|
-
type: 'string',
|
|
157
|
-
enum: ['business', 'dict', 'non_standard'],
|
|
158
|
-
description: 'Structured page type from parseResult. MCP consumes this value but does not derive it. Dict pages are restricted to dialog-based templates.',
|
|
159
|
-
},
|
|
160
|
-
style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Final style id from parseResult or translated mcpPayload. MCP validates it but does not infer it.' },
|
|
161
|
-
fields: {
|
|
162
|
-
type: 'array',
|
|
163
|
-
description: 'Structured main-table field metadata already translated by the caller. MCP only consumes these low-level generation parameters.',
|
|
151
|
+
featureTitle: { type: 'string', description: 'Feature title from pre-parsed structured metadata.' },
|
|
152
|
+
tableName: { type: 'string', description: 'Canonical main table name from PRD-aligned structured metadata.' },
|
|
153
|
+
tableComment: { type: 'string', description: 'Canonical main table comment or feature label from PRD-aligned structured metadata.' },
|
|
154
|
+
apiPath: { type: 'string', description: 'Backend API base path from pre-parsed structured metadata, for example iwmEmpOutsourcePerson.' },
|
|
155
|
+
pageType: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
enum: ['business', 'dict', 'non_standard'],
|
|
158
|
+
description: 'Structured page type from parseResult. MCP consumes this value but does not derive it. Dict pages are restricted to dialog-based templates.',
|
|
159
|
+
},
|
|
160
|
+
style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Final style id from parseResult or translated mcpPayload. MCP validates it but does not infer it.' },
|
|
161
|
+
fields: {
|
|
162
|
+
type: 'array',
|
|
163
|
+
description: 'Structured main-table field metadata already translated by the caller. MCP only consumes these low-level generation parameters.',
|
|
164
164
|
items: {
|
|
165
165
|
type: 'object',
|
|
166
166
|
properties: {
|
|
@@ -170,17 +170,17 @@ const TOOL_SCHEMA = {
|
|
|
170
170
|
length: { type: ['string', 'number'], description: 'Field length or precision string, for example 30 or 10,2.' },
|
|
171
171
|
scale: { type: ['string', 'number'], description: 'Optional decimal scale.' },
|
|
172
172
|
required: { type: ['boolean', 'string'], description: 'Whether the field is required.' },
|
|
173
|
-
readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
|
|
174
|
-
show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
|
|
175
|
-
listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
|
|
176
|
-
smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
|
|
177
|
-
queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
|
|
178
|
-
dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
|
|
173
|
+
readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
|
|
174
|
+
show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
|
|
175
|
+
listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
|
|
176
|
+
smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
|
|
177
|
+
queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
|
|
178
|
+
dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
|
|
179
179
|
defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
|
|
180
|
-
description: { type: 'string', description: 'Field description or notes.' },
|
|
181
|
-
componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number or datetime.' },
|
|
182
|
-
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.' },
|
|
183
|
-
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
180
|
+
description: { type: 'string', description: 'Field description or notes.' },
|
|
181
|
+
componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number or datetime.' },
|
|
182
|
+
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.' },
|
|
183
|
+
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
184
184
|
primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
|
|
185
185
|
},
|
|
186
186
|
required: ['fieldName', 'label', 'type'],
|
|
@@ -189,7 +189,7 @@ const TOOL_SCHEMA = {
|
|
|
189
189
|
},
|
|
190
190
|
children: {
|
|
191
191
|
type: 'array',
|
|
192
|
-
description: 'Optional direct child-table structures for master_child_jump. When provided, MCP renders all listed direct child tables on the same main form. The caller must determine semantic truth before passing them in.',
|
|
192
|
+
description: 'Optional direct child-table structures for master_child_jump. When provided, MCP renders all listed direct child tables on the same main form. The caller must determine semantic truth before passing them in.',
|
|
193
193
|
items: {
|
|
194
194
|
type: 'object',
|
|
195
195
|
properties: {
|
|
@@ -197,7 +197,7 @@ const TOOL_SCHEMA = {
|
|
|
197
197
|
childTableComment: { type: 'string', description: 'Child table comment or display label.' },
|
|
198
198
|
mainField: { type: 'string', description: 'Main table relation field.' },
|
|
199
199
|
childField: { type: 'string', description: 'Child table relation field.' },
|
|
200
|
-
payloadField: { type: 'string', description: 'Backend payload list field name from structured metadata, for example certificateList.' },
|
|
200
|
+
payloadField: { type: 'string', description: 'Backend payload list field name from structured metadata, for example certificateList.' },
|
|
201
201
|
relationType: { type: 'string', description: 'Optional relation type label, for example 1:N.' },
|
|
202
202
|
fields: {
|
|
203
203
|
type: 'array',
|
|
@@ -211,17 +211,17 @@ const TOOL_SCHEMA = {
|
|
|
211
211
|
length: { type: ['string', 'number'], description: 'Field length or precision string.' },
|
|
212
212
|
scale: { type: ['string', 'number'], description: 'Optional decimal scale.' },
|
|
213
213
|
required: { type: ['boolean', 'string'], description: 'Whether the field is required.' },
|
|
214
|
-
readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
|
|
215
|
-
show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
|
|
216
|
-
listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
|
|
217
|
-
smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
|
|
218
|
-
queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
|
|
219
|
-
dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
|
|
214
|
+
readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
|
|
215
|
+
show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
|
|
216
|
+
listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
|
|
217
|
+
smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
|
|
218
|
+
queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
|
|
219
|
+
dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
|
|
220
220
|
defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
|
|
221
|
-
description: { type: 'string', description: 'Field description or notes.' },
|
|
222
|
-
componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata.' },
|
|
223
|
-
formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
|
|
224
|
-
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
221
|
+
description: { type: 'string', description: 'Field description or notes.' },
|
|
222
|
+
componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata.' },
|
|
223
|
+
formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
|
|
224
|
+
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
225
225
|
primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
|
|
226
226
|
},
|
|
227
227
|
required: ['fieldName', 'label', 'type'],
|
|
@@ -247,8 +247,8 @@ const TOOL_SCHEMA = {
|
|
|
247
247
|
items: {
|
|
248
248
|
type: 'object',
|
|
249
249
|
properties: {
|
|
250
|
-
tableName: { type: 'string', description: 'Canonical module table name from PRD-aligned structured metadata.' },
|
|
251
|
-
tableComment: { type: 'string', description: 'Canonical module display label from PRD-aligned structured metadata.' },
|
|
250
|
+
tableName: { type: 'string', description: 'Canonical module table name from PRD-aligned structured metadata.' },
|
|
251
|
+
tableComment: { type: 'string', description: 'Canonical module display label from PRD-aligned structured metadata.' },
|
|
252
252
|
apiPath: { type: 'string', description: 'Backend API base path for this module.' },
|
|
253
253
|
primaryKey: { type: 'string', description: 'Primary key field name. Defaults to id when omitted.' },
|
|
254
254
|
queryParentField: { type: 'string', description: 'Direct parent foreign key field used for page queries.' },
|
|
@@ -268,17 +268,17 @@ const TOOL_SCHEMA = {
|
|
|
268
268
|
length: { type: ['string', 'number'], description: 'Field length or precision string.' },
|
|
269
269
|
scale: { type: ['string', 'number'], description: 'Optional decimal scale.' },
|
|
270
270
|
required: { type: ['boolean', 'string'], description: 'Whether the field is required.' },
|
|
271
|
-
readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
|
|
272
|
-
show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
|
|
273
|
-
listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
|
|
274
|
-
smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
|
|
275
|
-
queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
|
|
276
|
-
dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
|
|
271
|
+
readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
|
|
272
|
+
show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
|
|
273
|
+
listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
|
|
274
|
+
smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
|
|
275
|
+
queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
|
|
276
|
+
dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
|
|
277
277
|
defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
|
|
278
|
-
description: { type: 'string', description: 'Field description or notes.' },
|
|
279
|
-
componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata.' },
|
|
280
|
-
formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
|
|
281
|
-
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
278
|
+
description: { type: 'string', description: 'Field description or notes.' },
|
|
279
|
+
componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata.' },
|
|
280
|
+
formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
|
|
281
|
+
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
282
282
|
primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' }
|
|
283
283
|
},
|
|
284
284
|
required: ['fieldName', 'label', 'type'],
|
|
@@ -294,34 +294,34 @@ const TOOL_SCHEMA = {
|
|
|
294
294
|
required: ['levelIndex', 'modules'],
|
|
295
295
|
additionalProperties: false
|
|
296
296
|
}
|
|
297
|
-
},
|
|
298
|
-
frontendPath: { type: 'string', description: 'Absolute frontend output root path.' },
|
|
299
|
-
moduleName: { type: 'string', description: 'Relative frontend module path, for example admin/test.' },
|
|
300
|
-
targetViewDir: {
|
|
301
|
-
type: 'string',
|
|
302
|
-
description: 'Explicit relative target view directory under src/views, for example admin/iwmSysTrade. When provided, MCP writes directly to this directory instead of deriving the feature folder from tableName.',
|
|
303
|
-
},
|
|
304
|
-
targetApiModule: {
|
|
305
|
-
type: 'string',
|
|
306
|
-
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.',
|
|
307
|
-
},
|
|
308
|
-
targetI18nKey: {
|
|
309
|
-
type: 'string',
|
|
310
|
-
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.',
|
|
311
|
-
},
|
|
312
|
-
writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
|
|
313
|
-
overwrite: { type: 'boolean', default: true, description: 'Whether to overwrite existing files. If false, existing files are skipped.' },
|
|
314
|
-
writeSupportFiles: {
|
|
315
|
-
type: 'boolean',
|
|
316
|
-
default: true,
|
|
317
|
-
description: 'Whether to write project support files such as src/enums/dict-registry.ts and src/utils/crudSchema.ts.'
|
|
318
|
-
},
|
|
319
|
-
mergeI18nZh: {
|
|
320
|
-
type: 'boolean',
|
|
321
|
-
default: true,
|
|
322
|
-
description: 'Whether to merge generated zh-cn content into an existing i18n file. If false, MCP renders zh-cn.ts from current metadata only.'
|
|
323
|
-
},
|
|
324
|
-
},
|
|
297
|
+
},
|
|
298
|
+
frontendPath: { type: 'string', description: 'Absolute frontend output root path.' },
|
|
299
|
+
moduleName: { type: 'string', description: 'Relative frontend module path, for example admin/test.' },
|
|
300
|
+
targetViewDir: {
|
|
301
|
+
type: 'string',
|
|
302
|
+
description: 'Explicit relative target view directory under src/views, for example admin/iwmSysTrade. When provided, MCP writes directly to this directory instead of deriving the feature folder from tableName.',
|
|
303
|
+
},
|
|
304
|
+
targetApiModule: {
|
|
305
|
+
type: 'string',
|
|
306
|
+
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.',
|
|
307
|
+
},
|
|
308
|
+
targetI18nKey: {
|
|
309
|
+
type: 'string',
|
|
310
|
+
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.',
|
|
311
|
+
},
|
|
312
|
+
writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
|
|
313
|
+
overwrite: { type: 'boolean', default: true, description: 'Whether to overwrite existing files. If false, existing files are skipped.' },
|
|
314
|
+
writeSupportFiles: {
|
|
315
|
+
type: 'boolean',
|
|
316
|
+
default: true,
|
|
317
|
+
description: 'Whether to write project support files such as src/enums/dict-registry.ts and src/utils/crudSchema.ts.'
|
|
318
|
+
},
|
|
319
|
+
mergeI18nZh: {
|
|
320
|
+
type: 'boolean',
|
|
321
|
+
default: true,
|
|
322
|
+
description: 'Whether to merge generated zh-cn content into an existing i18n file. If false, MCP renders zh-cn.ts from current metadata only.'
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
325
|
required: ['tableName', 'style', 'frontendPath'],
|
|
326
326
|
additionalProperties: false,
|
|
327
327
|
};
|
|
@@ -362,9 +362,9 @@ function normalizeModuleName(moduleName) {
|
|
|
362
362
|
return moduleName.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
function normalizeModulePathForFeature(moduleName, functionName, apiPath) {
|
|
366
|
-
const normalized = normalizeModuleName(moduleName);
|
|
367
|
-
const segments = normalized.split('/').filter(Boolean);
|
|
365
|
+
function normalizeModulePathForFeature(moduleName, functionName, apiPath) {
|
|
366
|
+
const normalized = normalizeModuleName(moduleName);
|
|
367
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
368
368
|
if (segments.length <= 1) {
|
|
369
369
|
return normalized;
|
|
370
370
|
}
|
|
@@ -379,73 +379,73 @@ function normalizeModulePathForFeature(moduleName, functionName, apiPath) {
|
|
|
379
379
|
while (segments.length > 1 && duplicateNames.has(segments[segments.length - 1])) {
|
|
380
380
|
segments.pop();
|
|
381
381
|
}
|
|
382
|
-
|
|
383
|
-
return segments.join('/');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function normalizeExplicitTargetPath(targetPath, label) {
|
|
387
|
-
if (!targetPath) return '';
|
|
388
|
-
const normalized = normalizeModuleName(String(targetPath));
|
|
389
|
-
const segments = normalized.split('/').filter(Boolean);
|
|
390
|
-
if (segments.length < 2) {
|
|
391
|
-
throw new Error(`${label} must include parent path and feature name, for example admin/exampleFeature`);
|
|
392
|
-
}
|
|
393
|
-
return normalized;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function normalizeTargetI18nKey(targetI18nKey) {
|
|
397
|
-
if (!targetI18nKey) return '';
|
|
398
|
-
const normalized = String(targetI18nKey).trim().replace(/^\.|\.$/g, '');
|
|
399
|
-
const segments = normalized.split('.').filter(Boolean);
|
|
400
|
-
if (segments.length < 2) {
|
|
401
|
-
throw new Error('targetI18nKey must include parent namespace and feature name, for example admin.exampleFeature');
|
|
402
|
-
}
|
|
403
|
-
return segments.join('.');
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function resolveGenerationTargets({ moduleName, functionName, apiPath, targetViewDir, targetApiModule, targetI18nKey }) {
|
|
407
|
-
const explicitViewDir = normalizeExplicitTargetPath(targetViewDir, 'targetViewDir');
|
|
408
|
-
const explicitApiModule = normalizeExplicitTargetPath(targetApiModule, 'targetApiModule');
|
|
409
|
-
const explicitI18nKey = normalizeTargetI18nKey(targetI18nKey);
|
|
410
|
-
|
|
411
|
-
const fallbackModuleName = normalizeModulePathForFeature(moduleName, functionName, apiPath);
|
|
412
|
-
const fallbackViewDir = [fallbackModuleName, functionName].filter(Boolean).join('/');
|
|
413
|
-
const fallbackApiModule = [fallbackModuleName, functionName].filter(Boolean).join('/');
|
|
414
|
-
const fallbackI18nKey = [fallbackModuleName, functionName].filter(Boolean).join('.').replace(/\//g, '.');
|
|
415
|
-
|
|
416
|
-
const finalViewDir = explicitViewDir || fallbackViewDir;
|
|
417
|
-
const viewSegments = finalViewDir.split('/').filter(Boolean);
|
|
418
|
-
const finalFunctionName = viewSegments[viewSegments.length - 1];
|
|
419
|
-
const finalModuleName = viewSegments.slice(0, -1).join('/');
|
|
420
|
-
const finalApiModule = explicitApiModule || [finalModuleName, finalFunctionName].filter(Boolean).join('/');
|
|
421
|
-
const finalI18nKey = explicitI18nKey || [finalModuleName, finalFunctionName].filter(Boolean).join('.');
|
|
422
|
-
|
|
423
|
-
return {
|
|
424
|
-
moduleName: finalModuleName,
|
|
425
|
-
functionName: finalFunctionName,
|
|
426
|
-
targetViewDir: finalViewDir,
|
|
427
|
-
targetApiModule: finalApiModule,
|
|
428
|
-
targetI18nKey: finalI18nKey,
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function normalizeFrontendRootPath(frontendPath) {
|
|
433
|
-
const resolvedPath = path.resolve(String(frontendPath || ''));
|
|
434
|
-
const normalizedPath = resolvedPath.replace(/[\\/]+$/, '');
|
|
435
|
-
const baseName = path.basename(normalizedPath).toLowerCase();
|
|
436
|
-
if (baseName === 'src') {
|
|
437
|
-
return path.dirname(normalizedPath);
|
|
438
|
-
}
|
|
439
|
-
return normalizedPath;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function buildViewRoot(model) {
|
|
443
|
-
return path.join(model.frontendPath, 'src', 'views', ...model.targetViewDir.split('/').filter(Boolean));
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function buildApiFilePath(model) {
|
|
447
|
-
return path.join(model.frontendPath, 'src', 'api', ...model.targetApiModule.split('/').filter(Boolean)) + '.ts';
|
|
448
|
-
}
|
|
382
|
+
|
|
383
|
+
return segments.join('/');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function normalizeExplicitTargetPath(targetPath, label) {
|
|
387
|
+
if (!targetPath) return '';
|
|
388
|
+
const normalized = normalizeModuleName(String(targetPath));
|
|
389
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
390
|
+
if (segments.length < 2) {
|
|
391
|
+
throw new Error(`${label} must include parent path and feature name, for example admin/exampleFeature`);
|
|
392
|
+
}
|
|
393
|
+
return normalized;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function normalizeTargetI18nKey(targetI18nKey) {
|
|
397
|
+
if (!targetI18nKey) return '';
|
|
398
|
+
const normalized = String(targetI18nKey).trim().replace(/^\.|\.$/g, '');
|
|
399
|
+
const segments = normalized.split('.').filter(Boolean);
|
|
400
|
+
if (segments.length < 2) {
|
|
401
|
+
throw new Error('targetI18nKey must include parent namespace and feature name, for example admin.exampleFeature');
|
|
402
|
+
}
|
|
403
|
+
return segments.join('.');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function resolveGenerationTargets({ moduleName, functionName, apiPath, targetViewDir, targetApiModule, targetI18nKey }) {
|
|
407
|
+
const explicitViewDir = normalizeExplicitTargetPath(targetViewDir, 'targetViewDir');
|
|
408
|
+
const explicitApiModule = normalizeExplicitTargetPath(targetApiModule, 'targetApiModule');
|
|
409
|
+
const explicitI18nKey = normalizeTargetI18nKey(targetI18nKey);
|
|
410
|
+
|
|
411
|
+
const fallbackModuleName = normalizeModulePathForFeature(moduleName, functionName, apiPath);
|
|
412
|
+
const fallbackViewDir = [fallbackModuleName, functionName].filter(Boolean).join('/');
|
|
413
|
+
const fallbackApiModule = [fallbackModuleName, functionName].filter(Boolean).join('/');
|
|
414
|
+
const fallbackI18nKey = [fallbackModuleName, functionName].filter(Boolean).join('.').replace(/\//g, '.');
|
|
415
|
+
|
|
416
|
+
const finalViewDir = explicitViewDir || fallbackViewDir;
|
|
417
|
+
const viewSegments = finalViewDir.split('/').filter(Boolean);
|
|
418
|
+
const finalFunctionName = viewSegments[viewSegments.length - 1];
|
|
419
|
+
const finalModuleName = viewSegments.slice(0, -1).join('/');
|
|
420
|
+
const finalApiModule = explicitApiModule || [finalModuleName, finalFunctionName].filter(Boolean).join('/');
|
|
421
|
+
const finalI18nKey = explicitI18nKey || [finalModuleName, finalFunctionName].filter(Boolean).join('.');
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
moduleName: finalModuleName,
|
|
425
|
+
functionName: finalFunctionName,
|
|
426
|
+
targetViewDir: finalViewDir,
|
|
427
|
+
targetApiModule: finalApiModule,
|
|
428
|
+
targetI18nKey: finalI18nKey,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function normalizeFrontendRootPath(frontendPath) {
|
|
433
|
+
const resolvedPath = path.resolve(String(frontendPath || ''));
|
|
434
|
+
const normalizedPath = resolvedPath.replace(/[\\/]+$/, '');
|
|
435
|
+
const baseName = path.basename(normalizedPath).toLowerCase();
|
|
436
|
+
if (baseName === 'src') {
|
|
437
|
+
return path.dirname(normalizedPath);
|
|
438
|
+
}
|
|
439
|
+
return normalizedPath;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function buildViewRoot(model) {
|
|
443
|
+
return path.join(model.frontendPath, 'src', 'views', ...model.targetViewDir.split('/').filter(Boolean));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function buildApiFilePath(model) {
|
|
447
|
+
return path.join(model.frontendPath, 'src', 'api', ...model.targetApiModule.split('/').filter(Boolean)) + '.ts';
|
|
448
|
+
}
|
|
449
449
|
|
|
450
450
|
function toConstantCase(value) {
|
|
451
451
|
return String(value || '')
|
|
@@ -569,12 +569,12 @@ function buildUniqueDictRegistryKey(dictType, usedKeys, existingByKey) {
|
|
|
569
569
|
return candidate;
|
|
570
570
|
}
|
|
571
571
|
|
|
572
|
-
function buildI18nNamespaceSegments(model) {
|
|
573
|
-
if (model.targetI18nKey) {
|
|
574
|
-
return model.targetI18nKey.split('.').filter(Boolean);
|
|
575
|
-
}
|
|
576
|
-
return [...model.moduleName.split('/').filter(Boolean), model.functionName];
|
|
577
|
-
}
|
|
572
|
+
function buildI18nNamespaceSegments(model) {
|
|
573
|
+
if (model.targetI18nKey) {
|
|
574
|
+
return model.targetI18nKey.split('.').filter(Boolean);
|
|
575
|
+
}
|
|
576
|
+
return [...model.moduleName.split('/').filter(Boolean), model.functionName];
|
|
577
|
+
}
|
|
578
578
|
|
|
579
579
|
function buildI18nNamespace(model) {
|
|
580
580
|
return buildI18nNamespaceSegments(model).join('.');
|
|
@@ -675,43 +675,43 @@ function buildZhCnLocaleObject(model) {
|
|
|
675
675
|
return root;
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
-
function prepareZhCnLocaleFile(model, mergeExisting) {
|
|
679
|
-
const localeSegments = buildI18nNamespaceSegments(model);
|
|
680
|
-
const localePath = path.join(model.frontendPath, 'src', 'i18n', 'biz', ...localeSegments) + '.zh-cn.ts';
|
|
681
|
-
const exists = fs.existsSync(localePath);
|
|
682
|
-
const currentContent = exists ? readUtf8File(localePath) : '';
|
|
683
|
-
const generatedObject = buildZhCnLocaleObject(model);
|
|
684
|
-
const generatedContent = renderExportDefaultContent(generatedObject);
|
|
685
|
-
|
|
686
|
-
if (!mergeExisting) {
|
|
687
|
-
return {
|
|
688
|
-
path: localePath,
|
|
689
|
-
frontendPath: model.frontendPath,
|
|
690
|
-
exists,
|
|
691
|
-
isCompatible: true,
|
|
692
|
-
namespace: buildI18nNamespace(model),
|
|
693
|
-
content: generatedContent,
|
|
694
|
-
needsWrite: !exists || currentContent !== generatedContent,
|
|
695
|
-
mergeMode: 'replace',
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const currentObject = exists ? parseExportDefaultObject(currentContent) : null;
|
|
700
|
-
const isCompatible = !exists || isPlainObject(currentObject);
|
|
701
|
-
const sanitizedCurrentObject = isCompatible ? removeFeatureCommonLocaleSections(currentObject || {}, model) : null;
|
|
702
|
-
const mergedObject = isCompatible ? deepMergeMissing(sanitizedCurrentObject || {}, generatedObject) : null;
|
|
703
|
-
|
|
704
|
-
return {
|
|
705
|
-
path: localePath,
|
|
706
|
-
frontendPath: model.frontendPath,
|
|
707
|
-
exists,
|
|
708
|
-
isCompatible,
|
|
709
|
-
namespace: buildI18nNamespace(model),
|
|
710
|
-
content: mergedObject ? renderExportDefaultContent(mergedObject) : '',
|
|
711
|
-
needsWrite: !exists || (isCompatible && renderExportDefaultContent(currentObject || {}) !== renderExportDefaultContent(mergedObject)),
|
|
712
|
-
mergeMode: 'merge',
|
|
713
|
-
};
|
|
714
|
-
}
|
|
678
|
+
function prepareZhCnLocaleFile(model, mergeExisting) {
|
|
679
|
+
const localeSegments = buildI18nNamespaceSegments(model);
|
|
680
|
+
const localePath = path.join(model.frontendPath, 'src', 'i18n', 'biz', ...localeSegments) + '.zh-cn.ts';
|
|
681
|
+
const exists = fs.existsSync(localePath);
|
|
682
|
+
const currentContent = exists ? readUtf8File(localePath) : '';
|
|
683
|
+
const generatedObject = buildZhCnLocaleObject(model);
|
|
684
|
+
const generatedContent = renderExportDefaultContent(generatedObject);
|
|
685
|
+
|
|
686
|
+
if (!mergeExisting) {
|
|
687
|
+
return {
|
|
688
|
+
path: localePath,
|
|
689
|
+
frontendPath: model.frontendPath,
|
|
690
|
+
exists,
|
|
691
|
+
isCompatible: true,
|
|
692
|
+
namespace: buildI18nNamespace(model),
|
|
693
|
+
content: generatedContent,
|
|
694
|
+
needsWrite: !exists || currentContent !== generatedContent,
|
|
695
|
+
mergeMode: 'replace',
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const currentObject = exists ? parseExportDefaultObject(currentContent) : null;
|
|
700
|
+
const isCompatible = !exists || isPlainObject(currentObject);
|
|
701
|
+
const sanitizedCurrentObject = isCompatible ? removeFeatureCommonLocaleSections(currentObject || {}, model) : null;
|
|
702
|
+
const mergedObject = isCompatible ? deepMergeMissing(sanitizedCurrentObject || {}, generatedObject) : null;
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
path: localePath,
|
|
706
|
+
frontendPath: model.frontendPath,
|
|
707
|
+
exists,
|
|
708
|
+
isCompatible,
|
|
709
|
+
namespace: buildI18nNamespace(model),
|
|
710
|
+
content: mergedObject ? renderExportDefaultContent(mergedObject) : '',
|
|
711
|
+
needsWrite: !exists || (isCompatible && renderExportDefaultContent(currentObject || {}) !== renderExportDefaultContent(mergedObject)),
|
|
712
|
+
mergeMode: 'merge',
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
715
|
|
|
716
716
|
function prepareDictRegistry(frontendPath, dictTypes) {
|
|
717
717
|
const registryPath = path.join(frontendPath, 'src', 'enums', 'dict-registry.ts');
|
|
@@ -765,46 +765,46 @@ function ensureDirectory(filePath) {
|
|
|
765
765
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
766
766
|
}
|
|
767
767
|
|
|
768
|
-
function writeSupportFile(filePath, content) {
|
|
769
|
-
ensureDirectory(filePath);
|
|
770
|
-
fs.writeFileSync(filePath, content, 'utf8');
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function buildVirtualDictRegistry(dictTypes) {
|
|
774
|
-
const normalizedDictTypes = [...new Set((dictTypes || []).filter(Boolean))];
|
|
775
|
-
const entries = normalizedDictTypes.map((dictType) => ({ key: getPreferredDictRegistryKey(dictType), value: dictType }));
|
|
776
|
-
return {
|
|
777
|
-
path: '',
|
|
778
|
-
entries,
|
|
779
|
-
keyByValue: new Map(entries.map((entry) => [entry.value, entry.key])),
|
|
780
|
-
needsWrite: false,
|
|
781
|
-
writeEnabled: false,
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function prepareSharedSupport(frontendPath, dictTypes, writeSupportFiles) {
|
|
786
|
-
const normalizedDictTypes = [...new Set((dictTypes || []).filter(Boolean))];
|
|
787
|
-
const dictRegistry = writeSupportFiles ? prepareDictRegistry(frontendPath, normalizedDictTypes) : buildVirtualDictRegistry(normalizedDictTypes);
|
|
788
|
-
const crudSchemaPath = path.join(frontendPath, 'src', 'utils', 'crudSchema.ts');
|
|
789
|
-
const crudSchema = writeSupportFiles
|
|
790
|
-
? ensureCrudSchemaSupportFile(frontendPath)
|
|
791
|
-
: {
|
|
792
|
-
path: crudSchemaPath,
|
|
793
|
-
content: DEFAULT_CRUD_SCHEMA_TEMPLATE,
|
|
794
|
-
exists: fs.existsSync(crudSchemaPath),
|
|
795
|
-
isCompatible: true,
|
|
796
|
-
needsWrite: false,
|
|
797
|
-
writeEnabled: false,
|
|
798
|
-
};
|
|
799
|
-
return {
|
|
800
|
-
dictRegistry,
|
|
801
|
-
crudSchema,
|
|
802
|
-
writeEnabled: Boolean(writeSupportFiles),
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
function maybeWriteSharedSupport(sharedSupport, writeToDisk) {
|
|
807
|
-
if (!writeToDisk || !sharedSupport.writeEnabled) return;
|
|
768
|
+
function writeSupportFile(filePath, content) {
|
|
769
|
+
ensureDirectory(filePath);
|
|
770
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function buildVirtualDictRegistry(dictTypes) {
|
|
774
|
+
const normalizedDictTypes = [...new Set((dictTypes || []).filter(Boolean))];
|
|
775
|
+
const entries = normalizedDictTypes.map((dictType) => ({ key: getPreferredDictRegistryKey(dictType), value: dictType }));
|
|
776
|
+
return {
|
|
777
|
+
path: '',
|
|
778
|
+
entries,
|
|
779
|
+
keyByValue: new Map(entries.map((entry) => [entry.value, entry.key])),
|
|
780
|
+
needsWrite: false,
|
|
781
|
+
writeEnabled: false,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function prepareSharedSupport(frontendPath, dictTypes, writeSupportFiles) {
|
|
786
|
+
const normalizedDictTypes = [...new Set((dictTypes || []).filter(Boolean))];
|
|
787
|
+
const dictRegistry = writeSupportFiles ? prepareDictRegistry(frontendPath, normalizedDictTypes) : buildVirtualDictRegistry(normalizedDictTypes);
|
|
788
|
+
const crudSchemaPath = path.join(frontendPath, 'src', 'utils', 'crudSchema.ts');
|
|
789
|
+
const crudSchema = writeSupportFiles
|
|
790
|
+
? ensureCrudSchemaSupportFile(frontendPath)
|
|
791
|
+
: {
|
|
792
|
+
path: crudSchemaPath,
|
|
793
|
+
content: DEFAULT_CRUD_SCHEMA_TEMPLATE,
|
|
794
|
+
exists: fs.existsSync(crudSchemaPath),
|
|
795
|
+
isCompatible: true,
|
|
796
|
+
needsWrite: false,
|
|
797
|
+
writeEnabled: false,
|
|
798
|
+
};
|
|
799
|
+
return {
|
|
800
|
+
dictRegistry,
|
|
801
|
+
crudSchema,
|
|
802
|
+
writeEnabled: Boolean(writeSupportFiles),
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function maybeWriteSharedSupport(sharedSupport, writeToDisk) {
|
|
807
|
+
if (!writeToDisk || !sharedSupport.writeEnabled) return;
|
|
808
808
|
|
|
809
809
|
if (sharedSupport.crudSchema.needsWrite) {
|
|
810
810
|
writeSupportFile(sharedSupport.crudSchema.path, sharedSupport.crudSchema.content);
|
|
@@ -815,35 +815,35 @@ function maybeWriteSharedSupport(sharedSupport, writeToDisk) {
|
|
|
815
815
|
}
|
|
816
816
|
}
|
|
817
817
|
|
|
818
|
-
function buildSupportNote(sharedSupport, localeZhSupport) {
|
|
819
|
-
const notes = [];
|
|
820
|
-
|
|
821
|
-
if (!sharedSupport.writeEnabled) {
|
|
822
|
-
notes.push(
|
|
823
|
-
'Shared support file writing is disabled. Generated code still references src/utils/crudSchema.ts and may reference src/enums/dict-registry.ts, so those helpers must already exist in the target project.'
|
|
824
|
-
);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
if (sharedSupport.crudSchema.exists && !sharedSupport.crudSchema.isCompatible) {
|
|
828
|
-
notes.push(
|
|
829
|
-
'Detected an existing src/utils/crudSchema.ts that does not match the expected helper signature. ' +
|
|
830
|
-
'MCP preserved the existing file and did not overwrite it. Generated pages now depend on that file being manually aligned.'
|
|
818
|
+
function buildSupportNote(sharedSupport, localeZhSupport) {
|
|
819
|
+
const notes = [];
|
|
820
|
+
|
|
821
|
+
if (!sharedSupport.writeEnabled) {
|
|
822
|
+
notes.push(
|
|
823
|
+
'Shared support file writing is disabled. Generated code still references src/utils/crudSchema.ts and may reference src/enums/dict-registry.ts, so those helpers must already exist in the target project.'
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (sharedSupport.crudSchema.exists && !sharedSupport.crudSchema.isCompatible) {
|
|
828
|
+
notes.push(
|
|
829
|
+
'Detected an existing src/utils/crudSchema.ts that does not match the expected helper signature. ' +
|
|
830
|
+
'MCP preserved the existing file and did not overwrite it. Generated pages now depend on that file being manually aligned.'
|
|
831
831
|
);
|
|
832
832
|
}
|
|
833
833
|
|
|
834
|
-
if (localeZhSupport.exists && !localeZhSupport.isCompatible) {
|
|
835
|
-
notes.push(
|
|
836
|
-
`Detected an existing ${path.relative(localeZhSupport.frontendPath, localeZhSupport.path).replace(/\\/g, '/')} that MCP could not parse. ` +
|
|
837
|
-
'The file was preserved and not updated, so new Chinese i18n keys may need to be merged manually.'
|
|
838
|
-
);
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
if (localeZhSupport.mergeMode === 'replace') {
|
|
842
|
-
notes.push('zh-cn.ts merge is disabled. MCP rendered the Chinese locale file from current metadata only.');
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
return notes.length ? notes.join(' ') : 'Runtime template rendering completed.';
|
|
846
|
-
}
|
|
834
|
+
if (localeZhSupport.exists && !localeZhSupport.isCompatible) {
|
|
835
|
+
notes.push(
|
|
836
|
+
`Detected an existing ${path.relative(localeZhSupport.frontendPath, localeZhSupport.path).replace(/\\/g, '/')} that MCP could not parse. ` +
|
|
837
|
+
'The file was preserved and not updated, so new Chinese i18n keys may need to be merged manually.'
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (localeZhSupport.mergeMode === 'replace') {
|
|
842
|
+
notes.push('zh-cn.ts merge is disabled. MCP rendered the Chinese locale file from current metadata only.');
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return notes.length ? notes.join(' ') : 'Runtime template rendering completed.';
|
|
846
|
+
}
|
|
847
847
|
|
|
848
848
|
function getDictRegistryReference(dictType, keyByValue) {
|
|
849
849
|
if (!dictType) return '';
|
|
@@ -861,34 +861,34 @@ function findDictType(comment) {
|
|
|
861
861
|
return extractDictType(comment);
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
-
function mapFieldType(field) {
|
|
865
|
-
if (field.dictType) return 'select';
|
|
866
|
-
if (field.sqlType === 'DATETIME' || field.sqlType === 'TIMESTAMP') return 'datetime';
|
|
867
|
-
if (field.sqlType === 'DATE') return 'date';
|
|
868
|
-
if (['INT', 'BIGINT', 'DECIMAL', 'NUMERIC'].includes(field.sqlType)) return 'number';
|
|
869
|
-
if (field.sqlType === 'TEXT') return 'textarea';
|
|
870
|
-
if (field.sqlType === 'VARCHAR' && field.length && Number(field.length) > 64) return 'textarea';
|
|
871
|
-
return 'text';
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
function normalizeStructuredFormType(value) {
|
|
875
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
876
|
-
if (!normalized) return '';
|
|
877
|
-
if (normalized === 'date') return 'date';
|
|
878
|
-
if (normalized === 'datetime') return 'datetime';
|
|
879
|
-
if (normalized === 'microme-operator') return 'number';
|
|
880
|
-
if (normalized === 'upload' || normalized === 'picker') {
|
|
881
|
-
throw new Error(
|
|
882
|
-
'Explicit component/form type "' +
|
|
883
|
-
normalized +
|
|
884
|
-
'" is not yet supported by worsoft-codegen-local templates. Please keep it in parseResult for downstream handling or extend MCP template support first.'
|
|
885
|
-
);
|
|
886
|
-
}
|
|
887
|
-
if (['text', 'select', 'textarea', 'number'].includes(normalized)) {
|
|
888
|
-
return normalized;
|
|
889
|
-
}
|
|
890
|
-
throw new Error('Unsupported explicit component/form type: ' + normalized);
|
|
891
|
-
}
|
|
864
|
+
function mapFieldType(field) {
|
|
865
|
+
if (field.dictType) return 'select';
|
|
866
|
+
if (field.sqlType === 'DATETIME' || field.sqlType === 'TIMESTAMP') return 'datetime';
|
|
867
|
+
if (field.sqlType === 'DATE') return 'date';
|
|
868
|
+
if (['INT', 'BIGINT', 'DECIMAL', 'NUMERIC'].includes(field.sqlType)) return 'number';
|
|
869
|
+
if (field.sqlType === 'TEXT') return 'textarea';
|
|
870
|
+
if (field.sqlType === 'VARCHAR' && field.length && Number(field.length) > 64) return 'textarea';
|
|
871
|
+
return 'text';
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function normalizeStructuredFormType(value) {
|
|
875
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
876
|
+
if (!normalized) return '';
|
|
877
|
+
if (normalized === 'date') return 'date';
|
|
878
|
+
if (normalized === 'datetime') return 'datetime';
|
|
879
|
+
if (normalized === 'microme-operator') return 'number';
|
|
880
|
+
if (normalized === 'upload' || normalized === 'picker') {
|
|
881
|
+
throw new Error(
|
|
882
|
+
'Explicit component/form type "' +
|
|
883
|
+
normalized +
|
|
884
|
+
'" is not yet supported by worsoft-codegen-local templates. Please keep it in parseResult for downstream handling or extend MCP template support first.'
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (['text', 'select', 'textarea', 'number'].includes(normalized)) {
|
|
888
|
+
return normalized;
|
|
889
|
+
}
|
|
890
|
+
throw new Error('Unsupported explicit component/form type: ' + normalized);
|
|
891
|
+
}
|
|
892
892
|
|
|
893
893
|
function escapeForRegex(value) {
|
|
894
894
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -1000,6 +1000,28 @@ function normalizeApiPath(value) {
|
|
|
1000
1000
|
.replace(/\/+$/, '');
|
|
1001
1001
|
}
|
|
1002
1002
|
|
|
1003
|
+
function buildApiRoutePath(moduleName, apiPath) {
|
|
1004
|
+
const normalizedApiPath = normalizeApiPath(apiPath);
|
|
1005
|
+
if (!normalizedApiPath) return '';
|
|
1006
|
+
|
|
1007
|
+
const normalizedModuleName = normalizeModuleName(moduleName || '')
|
|
1008
|
+
.split('/')
|
|
1009
|
+
.filter(Boolean);
|
|
1010
|
+
const apiSegments = normalizedApiPath.split('/').filter(Boolean);
|
|
1011
|
+
|
|
1012
|
+
if (!normalizedModuleName.length) {
|
|
1013
|
+
return '/' + apiSegments.join('/');
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const modulePrefix = normalizedModuleName.join('/');
|
|
1017
|
+
const currentApiPath = apiSegments.join('/');
|
|
1018
|
+
if (currentApiPath === modulePrefix || currentApiPath.startsWith(modulePrefix + '/')) {
|
|
1019
|
+
return '/' + currentApiPath;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
return '/' + [...normalizedModuleName, ...apiSegments].join('/');
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1003
1025
|
function normalizeStructuredSqlType(value) {
|
|
1004
1026
|
const normalized = String(value || '').trim();
|
|
1005
1027
|
const upper = normalized.replace(/\s+/g, '').toUpperCase();
|
|
@@ -1023,7 +1045,7 @@ function normalizeStructuredLengthAndScale(lengthValue, scaleValue) {
|
|
|
1023
1045
|
};
|
|
1024
1046
|
}
|
|
1025
1047
|
|
|
1026
|
-
function normalizeStructuredField(inputField, index, contextLabel) {
|
|
1048
|
+
function normalizeStructuredField(inputField, index, contextLabel) {
|
|
1027
1049
|
if (!inputField || typeof inputField !== 'object') {
|
|
1028
1050
|
throw new Error(contextLabel + '[' + index + '] must be an object');
|
|
1029
1051
|
}
|
|
@@ -1042,42 +1064,42 @@ function normalizeStructuredField(inputField, index, contextLabel) {
|
|
|
1042
1064
|
throw new Error(contextLabel + '[' + index + '] is missing required field: type');
|
|
1043
1065
|
}
|
|
1044
1066
|
|
|
1045
|
-
const { length, scale } = normalizeStructuredLengthAndScale(inputField.length, inputField.scale);
|
|
1046
|
-
const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
|
|
1047
|
-
const explicitQueryType =
|
|
1048
|
-
inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
|
|
1049
|
-
? undefined
|
|
1050
|
-
: Number.parseInt(String(inputField.queryType), 10);
|
|
1051
|
-
const listShow = parseBooleanLike(inputField.listShow, parseBooleanLike(inputField.show, true));
|
|
1052
|
-
|
|
1053
|
-
return {
|
|
1054
|
-
fieldName,
|
|
1055
|
-
attrName: toCamelCase(fieldName),
|
|
1067
|
+
const { length, scale } = normalizeStructuredLengthAndScale(inputField.length, inputField.scale);
|
|
1068
|
+
const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
|
|
1069
|
+
const explicitQueryType =
|
|
1070
|
+
inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
|
|
1071
|
+
? undefined
|
|
1072
|
+
: Number.parseInt(String(inputField.queryType), 10);
|
|
1073
|
+
const listShow = parseBooleanLike(inputField.listShow, parseBooleanLike(inputField.show, true));
|
|
1074
|
+
|
|
1075
|
+
return {
|
|
1076
|
+
fieldName,
|
|
1077
|
+
attrName: toCamelCase(fieldName),
|
|
1056
1078
|
sqlType: type,
|
|
1057
1079
|
length,
|
|
1058
1080
|
scale,
|
|
1059
1081
|
comment: label,
|
|
1060
1082
|
label,
|
|
1061
|
-
description: String(inputField.description || '').trim(),
|
|
1062
|
-
formType: explicitFormType,
|
|
1063
|
-
dictType: normalizeDictType(inputField.dictType),
|
|
1064
|
-
notNull: parseBooleanLike(inputField.required, false),
|
|
1065
|
-
defaultValue: normalizeDefaultValue(inputField.defaultValue),
|
|
1066
|
-
readonly: parseBooleanLike(inputField.readonly, false),
|
|
1067
|
-
show: parseBooleanLike(inputField.show, true),
|
|
1068
|
-
listShow,
|
|
1069
|
-
smart: parseBooleanLike(inputField.smart, false),
|
|
1070
|
-
queryType: Number.isNaN(explicitQueryType)
|
|
1071
|
-
? undefined
|
|
1072
|
-
: explicitQueryType !== undefined
|
|
1073
|
-
? explicitQueryType
|
|
1074
|
-
: normalizeDictType(inputField.dictType) && listShow
|
|
1075
|
-
? 30
|
|
1076
|
-
: undefined,
|
|
1077
|
-
sourceKind: normalizeStructuredSourceKind(inputField.sourceKind),
|
|
1078
|
-
primary: parseBooleanLike(inputField.primary, fieldName === 'id'),
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1083
|
+
description: String(inputField.description || '').trim(),
|
|
1084
|
+
formType: explicitFormType,
|
|
1085
|
+
dictType: normalizeDictType(inputField.dictType),
|
|
1086
|
+
notNull: parseBooleanLike(inputField.required, false),
|
|
1087
|
+
defaultValue: normalizeDefaultValue(inputField.defaultValue),
|
|
1088
|
+
readonly: parseBooleanLike(inputField.readonly, false),
|
|
1089
|
+
show: parseBooleanLike(inputField.show, true),
|
|
1090
|
+
listShow,
|
|
1091
|
+
smart: parseBooleanLike(inputField.smart, false),
|
|
1092
|
+
queryType: Number.isNaN(explicitQueryType)
|
|
1093
|
+
? undefined
|
|
1094
|
+
: explicitQueryType !== undefined
|
|
1095
|
+
? explicitQueryType
|
|
1096
|
+
: normalizeDictType(inputField.dictType) && listShow
|
|
1097
|
+
? 30
|
|
1098
|
+
: undefined,
|
|
1099
|
+
sourceKind: normalizeStructuredSourceKind(inputField.sourceKind),
|
|
1100
|
+
primary: parseBooleanLike(inputField.primary, fieldName === 'id'),
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1081
1103
|
|
|
1082
1104
|
function normalizeStructuredFieldArray(inputFields, contextLabel) {
|
|
1083
1105
|
if (!Array.isArray(inputFields) || !inputFields.length) {
|
|
@@ -1114,14 +1136,14 @@ function buildRetryArguments(safeArgs) {
|
|
|
1114
1136
|
tableName: safeArgs.tableName,
|
|
1115
1137
|
...(safeArgs.tableComment ? { tableComment: safeArgs.tableComment } : {}),
|
|
1116
1138
|
style: safeArgs.style,
|
|
1117
|
-
frontendPath: safeArgs.frontendPath,
|
|
1118
|
-
moduleName: safeArgs.moduleName,
|
|
1119
|
-
writeToDisk: safeArgs.writeToDisk,
|
|
1120
|
-
overwrite: safeArgs.overwrite,
|
|
1121
|
-
writeSupportFiles: safeArgs.writeSupportFiles,
|
|
1122
|
-
mergeI18nZh: safeArgs.mergeI18nZh,
|
|
1123
|
-
fields: safeArgs.fields,
|
|
1124
|
-
};
|
|
1139
|
+
frontendPath: safeArgs.frontendPath,
|
|
1140
|
+
moduleName: safeArgs.moduleName,
|
|
1141
|
+
writeToDisk: safeArgs.writeToDisk,
|
|
1142
|
+
overwrite: safeArgs.overwrite,
|
|
1143
|
+
writeSupportFiles: safeArgs.writeSupportFiles,
|
|
1144
|
+
mergeI18nZh: safeArgs.mergeI18nZh,
|
|
1145
|
+
fields: safeArgs.fields,
|
|
1146
|
+
};
|
|
1125
1147
|
|
|
1126
1148
|
if (safeArgs.children && safeArgs.children.length) {
|
|
1127
1149
|
retryArguments.children = safeArgs.children;
|
|
@@ -1244,53 +1266,53 @@ function normalizeLevelsInput(inputLevels) {
|
|
|
1244
1266
|
return levels;
|
|
1245
1267
|
}
|
|
1246
1268
|
|
|
1247
|
-
function getStylePreset(styleId) {
|
|
1248
|
-
const preset = STYLE_CATALOG[styleId];
|
|
1249
|
-
if (!preset) throw new Error('Unsupported style: ' + styleId);
|
|
1250
|
-
return preset;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
function normalizePageTypeInput(pageType) {
|
|
1254
|
-
if (pageType === undefined || pageType === null || pageType === '') return '';
|
|
1255
|
-
const normalized = String(pageType).trim();
|
|
1256
|
-
if (['business', 'dict', 'non_standard'].includes(normalized)) return normalized;
|
|
1257
|
-
throw new Error(`Unsupported pageType: ${normalized}. Allowed values are dict, business, non_standard.`);
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
function rejectSemanticStageInputs(input) {
|
|
1261
|
-
const forbiddenKeys = [
|
|
1262
|
-
'parseResult',
|
|
1263
|
-
'prdFile',
|
|
1264
|
-
'apiDocFile',
|
|
1265
|
-
'fieldMappings',
|
|
1266
|
-
'dictionaryMeta',
|
|
1267
|
-
'listQueryMeta',
|
|
1268
|
-
'fieldUiMeta',
|
|
1269
|
-
];
|
|
1270
|
-
const present = forbiddenKeys.filter((key) => Object.prototype.hasOwnProperty.call(input, key));
|
|
1271
|
-
if (present.length) {
|
|
1272
|
-
throw new Error(`worsoft_codegen_local_generate_frontend only accepts translated low-level generation parameters. Unsupported semantic-stage keys: ${present.join(', ')}`);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
function validatePageTypeAndStyle(pageType, style) {
|
|
1277
|
-
if (!pageType) return;
|
|
1278
|
-
if (pageType === 'non_standard') {
|
|
1279
|
-
throw new Error('non_standard pages are not supported by worsoft_codegen_local_generate_frontend');
|
|
1280
|
-
}
|
|
1281
|
-
if (pageType !== 'dict') return;
|
|
1282
|
-
if (style === 'single_table_jump' || style === 'master_child_jump') {
|
|
1283
|
-
throw new Error(`Dict pages must use dialog-based styles. pageType=dict does not support style=${style}`);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
function hasRuntimeSupport(stylePreset) {
|
|
1288
|
-
return Boolean(stylePreset.runtime && stylePreset.runtime.supported && stylePreset.runtime.templateDir);
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
function normalizeFields(parsed) {
|
|
1292
|
-
return parsed.fields.map((field) => ({ ...field, formType: field.formType || mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
|
|
1293
|
-
}
|
|
1269
|
+
function getStylePreset(styleId) {
|
|
1270
|
+
const preset = STYLE_CATALOG[styleId];
|
|
1271
|
+
if (!preset) throw new Error('Unsupported style: ' + styleId);
|
|
1272
|
+
return preset;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function normalizePageTypeInput(pageType) {
|
|
1276
|
+
if (pageType === undefined || pageType === null || pageType === '') return '';
|
|
1277
|
+
const normalized = String(pageType).trim();
|
|
1278
|
+
if (['business', 'dict', 'non_standard'].includes(normalized)) return normalized;
|
|
1279
|
+
throw new Error(`Unsupported pageType: ${normalized}. Allowed values are dict, business, non_standard.`);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function rejectSemanticStageInputs(input) {
|
|
1283
|
+
const forbiddenKeys = [
|
|
1284
|
+
'parseResult',
|
|
1285
|
+
'prdFile',
|
|
1286
|
+
'apiDocFile',
|
|
1287
|
+
'fieldMappings',
|
|
1288
|
+
'dictionaryMeta',
|
|
1289
|
+
'listQueryMeta',
|
|
1290
|
+
'fieldUiMeta',
|
|
1291
|
+
];
|
|
1292
|
+
const present = forbiddenKeys.filter((key) => Object.prototype.hasOwnProperty.call(input, key));
|
|
1293
|
+
if (present.length) {
|
|
1294
|
+
throw new Error(`worsoft_codegen_local_generate_frontend only accepts translated low-level generation parameters. Unsupported semantic-stage keys: ${present.join(', ')}`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function validatePageTypeAndStyle(pageType, style) {
|
|
1299
|
+
if (!pageType) return;
|
|
1300
|
+
if (pageType === 'non_standard') {
|
|
1301
|
+
throw new Error('non_standard pages are not supported by worsoft_codegen_local_generate_frontend');
|
|
1302
|
+
}
|
|
1303
|
+
if (pageType !== 'dict') return;
|
|
1304
|
+
if (style === 'single_table_jump' || style === 'master_child_jump') {
|
|
1305
|
+
throw new Error(`Dict pages must use dialog-based styles. pageType=dict does not support style=${style}`);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function hasRuntimeSupport(stylePreset) {
|
|
1310
|
+
return Boolean(stylePreset.runtime && stylePreset.runtime.supported && stylePreset.runtime.templateDir);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function normalizeFields(parsed) {
|
|
1314
|
+
return parsed.fields.map((field) => ({ ...field, formType: field.formType || mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
|
|
1315
|
+
}
|
|
1294
1316
|
|
|
1295
1317
|
function ensureFieldExists(fields, fieldName, tableName, role) {
|
|
1296
1318
|
const field = fields.find((item) => item.fieldName === fieldName);
|
|
@@ -1378,33 +1400,36 @@ function buildMultiLevelDictModel(safeArgs) {
|
|
|
1378
1400
|
throw new Error('multi_level_dict requires level 1 with at least one parent module');
|
|
1379
1401
|
}
|
|
1380
1402
|
|
|
1381
|
-
const parentModule = parentLevel.modules[0];
|
|
1382
|
-
const derivedFunctionName = toCamelCase(safeArgs.tableName || parentModule.tableName);
|
|
1383
|
-
const resolvedTargets = resolveGenerationTargets({
|
|
1384
|
-
moduleName: safeArgs.moduleName,
|
|
1385
|
-
functionName: derivedFunctionName,
|
|
1386
|
-
apiPath: safeArgs.apiPath || parentModule.apiPath,
|
|
1387
|
-
targetViewDir: safeArgs.targetViewDir,
|
|
1388
|
-
targetApiModule: safeArgs.targetApiModule,
|
|
1389
|
-
targetI18nKey: safeArgs.targetI18nKey,
|
|
1390
|
-
});
|
|
1391
|
-
const allModules = builtLevels.flatMap((level) => level.modules);
|
|
1392
|
-
const dictTypes = [...new Set(allModules.flatMap((module) => module.optionFields.map((field) => field.dictType).filter(Boolean)))];
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1403
|
+
const parentModule = parentLevel.modules[0];
|
|
1404
|
+
const derivedFunctionName = toCamelCase(safeArgs.tableName || parentModule.tableName);
|
|
1405
|
+
const resolvedTargets = resolveGenerationTargets({
|
|
1406
|
+
moduleName: safeArgs.moduleName,
|
|
1407
|
+
functionName: derivedFunctionName,
|
|
1408
|
+
apiPath: safeArgs.apiPath || parentModule.apiPath,
|
|
1409
|
+
targetViewDir: safeArgs.targetViewDir,
|
|
1410
|
+
targetApiModule: safeArgs.targetApiModule,
|
|
1411
|
+
targetI18nKey: safeArgs.targetI18nKey,
|
|
1412
|
+
});
|
|
1413
|
+
const allModules = builtLevels.flatMap((level) => level.modules);
|
|
1414
|
+
const dictTypes = [...new Set(allModules.flatMap((module) => module.optionFields.map((field) => field.dictType).filter(Boolean)))];
|
|
1415
|
+
allModules.forEach((moduleModel) => {
|
|
1416
|
+
moduleModel.moduleName = resolvedTargets.moduleName;
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
return {
|
|
1420
|
+
featureTitle: safeArgs.featureTitle || safeArgs.tableComment || parentModule.tableComment,
|
|
1421
|
+
tableName: safeArgs.tableName,
|
|
1422
|
+
tableComment: safeArgs.tableComment || parentModule.tableComment,
|
|
1423
|
+
apiPath: safeArgs.apiPath || parentModule.apiPath,
|
|
1424
|
+
pageType: safeArgs.pageType || 'dict',
|
|
1425
|
+
className: toPascalCase(safeArgs.tableName || parentModule.tableName),
|
|
1426
|
+
functionName: resolvedTargets.functionName,
|
|
1427
|
+
moduleName: resolvedTargets.moduleName,
|
|
1428
|
+
targetViewDir: resolvedTargets.targetViewDir,
|
|
1429
|
+
targetApiModule: resolvedTargets.targetApiModule,
|
|
1430
|
+
targetI18nKey: resolvedTargets.targetI18nKey,
|
|
1431
|
+
frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
|
|
1432
|
+
style: safeArgs.style,
|
|
1408
1433
|
levels: builtLevels,
|
|
1409
1434
|
modules: allModules,
|
|
1410
1435
|
dictTypes,
|
|
@@ -1475,36 +1500,36 @@ function buildModel(safeArgs) {
|
|
|
1475
1500
|
const childDictTypes = children.flatMap((child) => child.optionFields.map((field) => field.dictType).filter(Boolean));
|
|
1476
1501
|
const dictTypes = [...new Set([...optionFields.map((field) => field.dictType).filter(Boolean), ...childDictTypes])];
|
|
1477
1502
|
|
|
1478
|
-
const derivedFunctionName = toCamelCase(safeArgs.tableName);
|
|
1479
|
-
const apiPath = safeArgs.apiPath || derivedFunctionName;
|
|
1480
|
-
const resolvedTargets = resolveGenerationTargets({
|
|
1481
|
-
moduleName: safeArgs.moduleName,
|
|
1482
|
-
functionName: derivedFunctionName,
|
|
1483
|
-
apiPath,
|
|
1484
|
-
targetViewDir: safeArgs.targetViewDir,
|
|
1485
|
-
targetApiModule: safeArgs.targetApiModule,
|
|
1486
|
-
targetI18nKey: safeArgs.targetI18nKey,
|
|
1487
|
-
});
|
|
1488
|
-
return {
|
|
1489
|
-
featureTitle: safeArgs.featureTitle || safeArgs.tableComment || safeArgs.tableName,
|
|
1490
|
-
tableName: safeArgs.tableName,
|
|
1491
|
-
tableComment: safeArgs.tableComment || safeArgs.featureTitle || safeArgs.tableName,
|
|
1492
|
-
apiPath,
|
|
1493
|
-
pageType: safeArgs.pageType || '',
|
|
1494
|
-
className: toPascalCase(safeArgs.tableName),
|
|
1495
|
-
functionName: resolvedTargets.functionName,
|
|
1496
|
-
moduleName: resolvedTargets.moduleName,
|
|
1497
|
-
targetViewDir: resolvedTargets.targetViewDir,
|
|
1498
|
-
targetApiModule: resolvedTargets.targetApiModule,
|
|
1499
|
-
targetI18nKey: resolvedTargets.targetI18nKey,
|
|
1500
|
-
pk: pkField,
|
|
1503
|
+
const derivedFunctionName = toCamelCase(safeArgs.tableName);
|
|
1504
|
+
const apiPath = safeArgs.apiPath || derivedFunctionName;
|
|
1505
|
+
const resolvedTargets = resolveGenerationTargets({
|
|
1506
|
+
moduleName: safeArgs.moduleName,
|
|
1507
|
+
functionName: derivedFunctionName,
|
|
1508
|
+
apiPath,
|
|
1509
|
+
targetViewDir: safeArgs.targetViewDir,
|
|
1510
|
+
targetApiModule: safeArgs.targetApiModule,
|
|
1511
|
+
targetI18nKey: safeArgs.targetI18nKey,
|
|
1512
|
+
});
|
|
1513
|
+
return {
|
|
1514
|
+
featureTitle: safeArgs.featureTitle || safeArgs.tableComment || safeArgs.tableName,
|
|
1515
|
+
tableName: safeArgs.tableName,
|
|
1516
|
+
tableComment: safeArgs.tableComment || safeArgs.featureTitle || safeArgs.tableName,
|
|
1517
|
+
apiPath,
|
|
1518
|
+
pageType: safeArgs.pageType || '',
|
|
1519
|
+
className: toPascalCase(safeArgs.tableName),
|
|
1520
|
+
functionName: resolvedTargets.functionName,
|
|
1521
|
+
moduleName: resolvedTargets.moduleName,
|
|
1522
|
+
targetViewDir: resolvedTargets.targetViewDir,
|
|
1523
|
+
targetApiModule: resolvedTargets.targetApiModule,
|
|
1524
|
+
targetI18nKey: resolvedTargets.targetI18nKey,
|
|
1525
|
+
pk: pkField,
|
|
1501
1526
|
fields,
|
|
1502
1527
|
optionFields,
|
|
1503
1528
|
visibleFields,
|
|
1504
1529
|
listFields,
|
|
1505
1530
|
gridFields,
|
|
1506
1531
|
dictTypes,
|
|
1507
|
-
frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
|
|
1532
|
+
frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
|
|
1508
1533
|
style: safeArgs.style,
|
|
1509
1534
|
children,
|
|
1510
1535
|
};
|
|
@@ -1648,6 +1673,8 @@ function renderFormRulesV2(fields) {
|
|
|
1648
1673
|
function renderFormDefaults(model) {
|
|
1649
1674
|
const lines = [` ${model.pk.attrName}: '',`];
|
|
1650
1675
|
for (const field of model.fields.filter((item) => item.fieldName !== model.pk.fieldName && !item.isAudit)) lines.push(renderDefaultLine(field));
|
|
1676
|
+
lines.push(` version: 1,`);
|
|
1677
|
+
lines.push(` tenantId: Local.getTenant(),`);
|
|
1651
1678
|
return lines.join('\n');
|
|
1652
1679
|
}
|
|
1653
1680
|
|
|
@@ -1763,27 +1790,27 @@ function getDefaultOptionFieldWidthV2(field) {
|
|
|
1763
1790
|
|
|
1764
1791
|
return '100';
|
|
1765
1792
|
}
|
|
1766
|
-
function renderOptionFieldV2(field, labelKey, dictRegistryRefs, indent = ' ') {
|
|
1767
|
-
const fallbackLabel = stripDictAnnotation(field.comment).replace(/'/g, "\\'");
|
|
1768
|
-
const parts = [`key: '${field.attrName}'`, `labelKey: '${labelKey}'`, `label: '${fallbackLabel}'`];
|
|
1769
|
-
const width = getDefaultOptionFieldWidthV2(field);
|
|
1793
|
+
function renderOptionFieldV2(field, labelKey, dictRegistryRefs, indent = ' ') {
|
|
1794
|
+
const fallbackLabel = stripDictAnnotation(field.comment).replace(/'/g, "\\'");
|
|
1795
|
+
const parts = [`key: '${field.attrName}'`, `labelKey: '${labelKey}'`, `label: '${fallbackLabel}'`];
|
|
1796
|
+
const width = getDefaultOptionFieldWidthV2(field);
|
|
1770
1797
|
|
|
1771
1798
|
if (width !== '120') {
|
|
1772
1799
|
parts.push(`width: '${width}'`);
|
|
1773
1800
|
}
|
|
1774
|
-
|
|
1775
|
-
if (field.dictType) {
|
|
1776
|
-
parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
|
|
1777
|
-
}
|
|
1778
|
-
if (field.smart) {
|
|
1779
|
-
parts.push('smart: true');
|
|
1780
|
-
}
|
|
1781
|
-
if (typeof field.queryType === 'number') {
|
|
1782
|
-
parts.push(`queryType: ${field.queryType}`);
|
|
1783
|
-
}
|
|
1784
|
-
if (field.show === false) {
|
|
1785
|
-
parts.push('show: false');
|
|
1786
|
-
}
|
|
1801
|
+
|
|
1802
|
+
if (field.dictType) {
|
|
1803
|
+
parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
|
|
1804
|
+
}
|
|
1805
|
+
if (field.smart) {
|
|
1806
|
+
parts.push('smart: true');
|
|
1807
|
+
}
|
|
1808
|
+
if (typeof field.queryType === 'number') {
|
|
1809
|
+
parts.push(`queryType: ${field.queryType}`);
|
|
1810
|
+
}
|
|
1811
|
+
if (field.show === false) {
|
|
1812
|
+
parts.push('show: false');
|
|
1813
|
+
}
|
|
1787
1814
|
if (field.listShow === false) {
|
|
1788
1815
|
parts.push('listShow: false');
|
|
1789
1816
|
}
|
|
@@ -1904,28 +1931,31 @@ function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs
|
|
|
1904
1931
|
`labelKey: '${buildMultiLevelFieldLabelKey(model, moduleModel, field)}'`,
|
|
1905
1932
|
];
|
|
1906
1933
|
const width = getDefaultOptionFieldWidthV2(field);
|
|
1907
|
-
if (width) parts.push(`width: '${width}'`);
|
|
1908
|
-
if (field.show === false) parts.push('show: false');
|
|
1909
|
-
if (field.listShow === false) parts.push('listShow: false');
|
|
1910
|
-
if (field.smart) parts.push('smart: true');
|
|
1911
|
-
if (typeof field.queryType === 'number') parts.push(`queryType: ${field.queryType}`);
|
|
1912
|
-
if (field.dictType) parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
|
|
1913
|
-
return `${indent}{ ${parts.join(', ')} },`;
|
|
1914
|
-
}
|
|
1934
|
+
if (width) parts.push(`width: '${width}'`);
|
|
1935
|
+
if (field.show === false) parts.push('show: false');
|
|
1936
|
+
if (field.listShow === false) parts.push('listShow: false');
|
|
1937
|
+
if (field.smart) parts.push('smart: true');
|
|
1938
|
+
if (typeof field.queryType === 'number') parts.push(`queryType: ${field.queryType}`);
|
|
1939
|
+
if (field.dictType) parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
|
|
1940
|
+
return `${indent}{ ${parts.join(', ')} },`;
|
|
1941
|
+
}
|
|
1915
1942
|
|
|
1916
1943
|
function renderMultiLevelModuleDefinition(model, moduleModel, dictRegistryRefs) {
|
|
1944
|
+
const moduleApiPath = buildApiRoutePath(model.moduleName, moduleModel.apiPath);
|
|
1945
|
+
const moduleEnableApi = buildApiRoutePath(model.moduleName, moduleModel.enableApi);
|
|
1946
|
+
const moduleDisableApi = buildApiRoutePath(model.moduleName, moduleModel.disableApi);
|
|
1917
1947
|
const lines = [
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1948
|
+
` ${moduleModel.key}: {`,
|
|
1949
|
+
` key: '${moduleModel.key}',`,
|
|
1950
|
+
` titleKey: '${buildMultiLevelModuleTitleKey(model, moduleModel)}',`,
|
|
1951
|
+
` apiPath: '${moduleApiPath}',`,
|
|
1952
|
+
` primaryKey: '${moduleModel.pk.attrName}',`,
|
|
1953
|
+
];
|
|
1924
1954
|
if (moduleModel.queryParentField) lines.push(` queryParentField: '${moduleModel.queryParentField}',`);
|
|
1925
1955
|
if (moduleModel.statusField) lines.push(` statusField: '${moduleModel.statusField}',`);
|
|
1926
1956
|
if (moduleModel.statusDictType) lines.push(` statusDictType: ${getDictRegistryReference(moduleModel.statusDictType, dictRegistryRefs)},`);
|
|
1927
|
-
lines.push(` enableApi: '${
|
|
1928
|
-
lines.push(` disableApi: '${
|
|
1957
|
+
lines.push(` enableApi: '${moduleEnableApi}',`);
|
|
1958
|
+
lines.push(` disableApi: '${moduleDisableApi}',`);
|
|
1929
1959
|
lines.push(' fields: [');
|
|
1930
1960
|
lines.push(moduleModel.optionFields.map((field) => renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs)).join('\n'));
|
|
1931
1961
|
lines.push(' ],');
|
|
@@ -1989,9 +2019,9 @@ function renderMultiLevelOptionsTs(model, dictRegistryRefs) {
|
|
|
1989
2019
|
|
|
1990
2020
|
function renderMultiLevelApiFunctions(moduleModel) {
|
|
1991
2021
|
const pkAttr = moduleModel.pk.attrName;
|
|
1992
|
-
const basePath =
|
|
1993
|
-
const enablePath =
|
|
1994
|
-
const disablePath =
|
|
2022
|
+
const basePath = buildApiRoutePath(moduleModel.moduleName, moduleModel.apiPath);
|
|
2023
|
+
const enablePath = buildApiRoutePath(moduleModel.moduleName, moduleModel.enableApi);
|
|
2024
|
+
const disablePath = buildApiRoutePath(moduleModel.moduleName, moduleModel.disableApi);
|
|
1995
2025
|
return [
|
|
1996
2026
|
`export function fetch${moduleModel.className}List(query?: any) {`,
|
|
1997
2027
|
' return request({',
|
|
@@ -2052,13 +2082,13 @@ function renderMultiLevelApiFunctions(moduleModel) {
|
|
|
2052
2082
|
}
|
|
2053
2083
|
|
|
2054
2084
|
function renderMultiLevelApiTs(model) {
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
}
|
|
2085
|
+
return [
|
|
2086
|
+
"import request from '/@/utils/request';",
|
|
2087
|
+
'',
|
|
2088
|
+
model.modules.map(renderMultiLevelApiFunctions).join('\n\n'),
|
|
2089
|
+
'',
|
|
2090
|
+
].join('\n');
|
|
2091
|
+
}
|
|
2062
2092
|
|
|
2063
2093
|
function renderMultiLevelFormField(field) {
|
|
2064
2094
|
const labelExpr = `getFieldLabel('${field.attrName}')`;
|
|
@@ -2143,6 +2173,8 @@ function renderMultiLevelFormVue(model, moduleModel) {
|
|
|
2143
2173
|
const defaultLines = [
|
|
2144
2174
|
` ${moduleModel.pk.attrName}: '',`,
|
|
2145
2175
|
...moduleModel.optionFields.map((field) => renderDefaultLine(field)),
|
|
2176
|
+
` version: 1,`,
|
|
2177
|
+
` tenantId: Local.getTenant(),`,
|
|
2146
2178
|
].join('\n');
|
|
2147
2179
|
const rules = renderFormRulesV2(moduleModel.visibleFields);
|
|
2148
2180
|
return `<template>
|
|
@@ -2163,6 +2195,7 @@ ${moduleModel.visibleFields.map(renderMultiLevelFormField).join('\n')}
|
|
|
2163
2195
|
|
|
2164
2196
|
<script setup lang="ts" name="${componentName}">
|
|
2165
2197
|
import { useMessage } from '/@/hooks/message';
|
|
2198
|
+
import { Local } from '/@/utils/storage';
|
|
2166
2199
|
import { useDict } from '/@/hooks/dict';
|
|
2167
2200
|
import { useCrudPageMeta } from '/@/hooks/useCrudPageMeta';
|
|
2168
2201
|
import { useI18n } from 'vue-i18n';
|
|
@@ -2551,15 +2584,15 @@ function renderMultiLevelMenuSql(model) {
|
|
|
2551
2584
|
].join('\n');
|
|
2552
2585
|
}
|
|
2553
2586
|
|
|
2554
|
-
function renderMultiLevelFiles(model, sharedSupport, localeZhSupport) {
|
|
2555
|
-
const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
|
|
2556
|
-
const viewRoot = buildViewRoot(model);
|
|
2557
|
-
const apiFilePath = buildApiFilePath(model);
|
|
2558
|
-
const menuRoot = path.join(model.frontendPath, 'menu');
|
|
2559
|
-
const files = [
|
|
2560
|
-
{ type: 'list', path: path.join(viewRoot, 'index.vue'), content: renderMultiLevelIndexVue(model) },
|
|
2561
|
-
{ type: 'options', path: path.join(viewRoot, 'options.ts'), content: renderMultiLevelOptionsTs(model, dictRegistryRefs) },
|
|
2562
|
-
{ type: 'api', path: apiFilePath, content: renderMultiLevelApiTs(model) },
|
|
2587
|
+
function renderMultiLevelFiles(model, sharedSupport, localeZhSupport) {
|
|
2588
|
+
const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
|
|
2589
|
+
const viewRoot = buildViewRoot(model);
|
|
2590
|
+
const apiFilePath = buildApiFilePath(model);
|
|
2591
|
+
const menuRoot = path.join(model.frontendPath, 'menu');
|
|
2592
|
+
const files = [
|
|
2593
|
+
{ type: 'list', path: path.join(viewRoot, 'index.vue'), content: renderMultiLevelIndexVue(model) },
|
|
2594
|
+
{ type: 'options', path: path.join(viewRoot, 'options.ts'), content: renderMultiLevelOptionsTs(model, dictRegistryRefs) },
|
|
2595
|
+
{ type: 'api', path: apiFilePath, content: renderMultiLevelApiTs(model) },
|
|
2563
2596
|
{
|
|
2564
2597
|
type: 'i18nZh',
|
|
2565
2598
|
path: localeZhSupport.path,
|
|
@@ -2583,7 +2616,8 @@ function renderMultiLevelFiles(model, sharedSupport, localeZhSupport) {
|
|
|
2583
2616
|
|
|
2584
2617
|
function buildReplacements(model, sharedSupport) {
|
|
2585
2618
|
const menuBaseId = Date.now();
|
|
2586
|
-
const apiModulePath = model.
|
|
2619
|
+
const apiModulePath = model.targetApiModule || `${model.moduleName}/${model.functionName}`;
|
|
2620
|
+
const apiRoutePath = buildApiRoutePath(model.moduleName, model.apiPath || model.functionName).replace(/^\/+/, '');
|
|
2587
2621
|
const routePath = `${model.moduleName}/${model.functionName}`;
|
|
2588
2622
|
const permissionPrefix = `${model.moduleName}/${model.functionName}`.replace(/\//g, '_');
|
|
2589
2623
|
const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
|
|
@@ -2597,7 +2631,7 @@ function buildReplacements(model, sharedSupport) {
|
|
|
2597
2631
|
FUNCTION_NAME: model.functionName,
|
|
2598
2632
|
PK_ATTR: model.pk.attrName,
|
|
2599
2633
|
API_MODULE_PATH: apiModulePath,
|
|
2600
|
-
API_PATH:
|
|
2634
|
+
API_PATH: apiRoutePath,
|
|
2601
2635
|
VIEW_MODULE_PATH: routePath,
|
|
2602
2636
|
MENU_ROUTE_PATH: routePath,
|
|
2603
2637
|
I18N_NAMESPACE: i18nNamespace,
|
|
@@ -2610,7 +2644,7 @@ function buildReplacements(model, sharedSupport) {
|
|
|
2610
2644
|
MENU_BASE_ID_PLUS_5: menuBaseId + 5,
|
|
2611
2645
|
GENERATED_AT: new Date().toISOString(),
|
|
2612
2646
|
FORM_FIELDS: model.visibleFields.map(renderFormFieldV2).join('\n'),
|
|
2613
|
-
TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumn(field, dictRegistryRefs)).join('\n'),
|
|
2647
|
+
TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumn(field, dictRegistryRefs)).join('\n'),
|
|
2614
2648
|
FORM_DEFAULTS: renderFormDefaults(model),
|
|
2615
2649
|
DICT_REGISTRY_IMPORT_BLOCK: model.dictTypes.length ? "import { DictRegistry } from '/@/enums/dict-registry';" : '',
|
|
2616
2650
|
MASTER_OPTION_FIELDS: model.optionFields.map((field) => renderOptionFieldV2(field, buildFieldLabelKey(model, field), dictRegistryRefs)).join('\n'),
|
|
@@ -2619,7 +2653,7 @@ function buildReplacements(model, sharedSupport) {
|
|
|
2619
2653
|
CHILD_FORM_LIST_DEFAULTS: renderChildFormListDefaults(model.children),
|
|
2620
2654
|
CHILD_TEMP_DECLARATIONS: renderChildTempDeclarations(model.children),
|
|
2621
2655
|
CHILD_RESET_LISTS: renderChildResetListLines(model.children),
|
|
2622
|
-
CHILD_SECTIONS: model.children.map((childModel) => renderChildSection(childModel, model.children.length)).join('\n'),
|
|
2656
|
+
CHILD_SECTIONS: model.children.map((childModel) => renderChildSection(childModel, model.children.length)).join('\n'),
|
|
2623
2657
|
};
|
|
2624
2658
|
}
|
|
2625
2659
|
|
|
@@ -2638,15 +2672,15 @@ function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
|
|
|
2638
2672
|
const apiTemplate = fs.readFileSync(path.join(templateDir, runtime.files.api), 'utf8');
|
|
2639
2673
|
const menuSqlTemplate = runtime.files.menuSql ? fs.readFileSync(path.join(templateDir, runtime.files.menuSql), 'utf8') : null;
|
|
2640
2674
|
|
|
2641
|
-
const viewRoot = buildViewRoot(model);
|
|
2642
|
-
const apiFilePath = buildApiFilePath(model);
|
|
2643
|
-
const menuRoot = path.join(model.frontendPath, 'menu');
|
|
2675
|
+
const viewRoot = buildViewRoot(model);
|
|
2676
|
+
const apiFilePath = buildApiFilePath(model);
|
|
2677
|
+
const menuRoot = path.join(model.frontendPath, 'menu');
|
|
2644
2678
|
|
|
2645
2679
|
const files = [
|
|
2646
2680
|
{ type: 'form', path: path.join(viewRoot, 'form.vue'), content: renderTemplate(formTemplate, replacements) },
|
|
2647
2681
|
{ type: 'list', path: path.join(viewRoot, 'index.vue'), content: renderTemplate(listTemplate, replacements) },
|
|
2648
2682
|
{ type: 'options', path: path.join(viewRoot, 'options.ts'), content: renderTemplate(optionsTemplate, replacements) },
|
|
2649
|
-
{ type: 'api', path: apiFilePath, content: renderTemplate(apiTemplate, replacements) },
|
|
2683
|
+
{ type: 'api', path: apiFilePath, content: renderTemplate(apiTemplate, replacements) },
|
|
2650
2684
|
{
|
|
2651
2685
|
type: 'i18nZh',
|
|
2652
2686
|
path: localeZhSupport.path,
|
|
@@ -2662,18 +2696,18 @@ function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
|
|
|
2662
2696
|
return files;
|
|
2663
2697
|
}
|
|
2664
2698
|
|
|
2665
|
-
function ensureArguments(input) {
|
|
2666
|
-
if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
|
|
2667
|
-
rejectSemanticStageInputs(input);
|
|
2668
|
-
for (const key of TOOL_SCHEMA.required) {
|
|
2669
|
-
if (input[key] === undefined || input[key] === null || input[key] === '') throw new Error(key + ' is required');
|
|
2670
|
-
}
|
|
2699
|
+
function ensureArguments(input) {
|
|
2700
|
+
if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
|
|
2701
|
+
rejectSemanticStageInputs(input);
|
|
2702
|
+
for (const key of TOOL_SCHEMA.required) {
|
|
2703
|
+
if (input[key] === undefined || input[key] === null || input[key] === '') throw new Error(key + ' is required');
|
|
2704
|
+
}
|
|
2671
2705
|
|
|
2672
|
-
const style = String(input.style);
|
|
2673
|
-
const pageType = normalizePageTypeInput(input.pageType);
|
|
2674
|
-
getStylePreset(style);
|
|
2675
|
-
validatePageTypeAndStyle(pageType, style);
|
|
2676
|
-
const isMultiLevelDict = style === 'multi_level_dict';
|
|
2706
|
+
const style = String(input.style);
|
|
2707
|
+
const pageType = normalizePageTypeInput(input.pageType);
|
|
2708
|
+
getStylePreset(style);
|
|
2709
|
+
validatePageTypeAndStyle(pageType, style);
|
|
2710
|
+
const isMultiLevelDict = style === 'multi_level_dict';
|
|
2677
2711
|
const fields = isMultiLevelDict ? [] : normalizeStructuredFieldArray(input.fields, 'fields');
|
|
2678
2712
|
const levels = isMultiLevelDict ? normalizeLevelsInput(input.levels) : [];
|
|
2679
2713
|
|
|
@@ -2685,27 +2719,27 @@ function ensureArguments(input) {
|
|
|
2685
2719
|
throw new Error('fields must be a non-empty array');
|
|
2686
2720
|
}
|
|
2687
2721
|
|
|
2688
|
-
return {
|
|
2689
|
-
featureTitle: input.featureTitle ? String(input.featureTitle) : '',
|
|
2690
|
-
tableName: String(input.tableName),
|
|
2691
|
-
tableComment: input.tableComment ? String(input.tableComment) : '',
|
|
2692
|
-
apiPath: normalizeApiPath(input.apiPath),
|
|
2693
|
-
pageType,
|
|
2694
|
-
style,
|
|
2722
|
+
return {
|
|
2723
|
+
featureTitle: input.featureTitle ? String(input.featureTitle) : '',
|
|
2724
|
+
tableName: String(input.tableName),
|
|
2725
|
+
tableComment: input.tableComment ? String(input.tableComment) : '',
|
|
2726
|
+
apiPath: normalizeApiPath(input.apiPath),
|
|
2727
|
+
pageType,
|
|
2728
|
+
style,
|
|
2695
2729
|
fields,
|
|
2696
2730
|
levels,
|
|
2697
|
-
children: normalizeChildrenInput(input.children),
|
|
2698
|
-
frontendPath: String(input.frontendPath),
|
|
2699
|
-
moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
|
|
2700
|
-
targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
|
|
2701
|
-
targetApiModule: input.targetApiModule ? String(input.targetApiModule) : '',
|
|
2702
|
-
targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
|
|
2703
|
-
writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
|
|
2704
|
-
overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
|
|
2705
|
-
writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
|
|
2706
|
-
mergeI18nZh: input.mergeI18nZh === undefined ? true : Boolean(input.mergeI18nZh),
|
|
2707
|
-
};
|
|
2708
|
-
}
|
|
2731
|
+
children: normalizeChildrenInput(input.children),
|
|
2732
|
+
frontendPath: String(input.frontendPath),
|
|
2733
|
+
moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
|
|
2734
|
+
targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
|
|
2735
|
+
targetApiModule: input.targetApiModule ? String(input.targetApiModule) : '',
|
|
2736
|
+
targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
|
|
2737
|
+
writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
|
|
2738
|
+
overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
|
|
2739
|
+
writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
|
|
2740
|
+
mergeI18nZh: input.mergeI18nZh === undefined ? true : Boolean(input.mergeI18nZh),
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2709
2743
|
|
|
2710
2744
|
function maybeWriteFiles(files, writeToDisk, overwrite) {
|
|
2711
2745
|
if (!writeToDisk) return;
|
|
@@ -2728,43 +2762,43 @@ function maybeWriteFiles(files, writeToDisk, overwrite) {
|
|
|
2728
2762
|
}
|
|
2729
2763
|
}
|
|
2730
2764
|
|
|
2731
|
-
function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
2732
|
-
if (model.style === 'multi_level_dict') {
|
|
2733
|
-
return {
|
|
2734
|
-
mode: 'local-template',
|
|
2735
|
-
style: safeArgs.style,
|
|
2736
|
-
pageType: model.pageType || '',
|
|
2737
|
-
styleLabel: stylePreset.label,
|
|
2765
|
+
function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
2766
|
+
if (model.style === 'multi_level_dict') {
|
|
2767
|
+
return {
|
|
2768
|
+
mode: 'local-template',
|
|
2769
|
+
style: safeArgs.style,
|
|
2770
|
+
pageType: model.pageType || '',
|
|
2771
|
+
styleLabel: stylePreset.label,
|
|
2738
2772
|
runtimeSupported: hasRuntimeSupport(stylePreset),
|
|
2739
|
-
tableName: model.tableName,
|
|
2740
|
-
tableComment: model.tableComment,
|
|
2741
|
-
|
|
2742
|
-
moduleName: model.moduleName,
|
|
2743
|
-
targetViewDir: model.targetViewDir,
|
|
2744
|
-
targetApiModule: model.targetApiModule,
|
|
2745
|
-
targetI18nKey: model.targetI18nKey,
|
|
2746
|
-
writeToDisk: safeArgs.writeToDisk,
|
|
2747
|
-
sideEffects: {
|
|
2748
|
-
writeSupportFiles: safeArgs.writeSupportFiles,
|
|
2749
|
-
mergeI18nZh: safeArgs.mergeI18nZh,
|
|
2750
|
-
},
|
|
2751
|
-
files: files.map((file) => ({ type: file.type, path: file.path, bytes: Buffer.byteLength(file.content, 'utf8'), status: file.status || (safeArgs.writeToDisk ? 'success' : 'rendered') })),
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2773
|
+
tableName: model.tableName,
|
|
2774
|
+
tableComment: model.tableComment,
|
|
2775
|
+
apiPath: buildApiRoutePath(model.moduleName, model.apiPath).replace(/^\/+/, ''),
|
|
2776
|
+
moduleName: model.moduleName,
|
|
2777
|
+
targetViewDir: model.targetViewDir,
|
|
2778
|
+
targetApiModule: model.targetApiModule,
|
|
2779
|
+
targetI18nKey: model.targetI18nKey,
|
|
2780
|
+
writeToDisk: safeArgs.writeToDisk,
|
|
2781
|
+
sideEffects: {
|
|
2782
|
+
writeSupportFiles: safeArgs.writeSupportFiles,
|
|
2783
|
+
mergeI18nZh: safeArgs.mergeI18nZh,
|
|
2784
|
+
},
|
|
2785
|
+
files: files.map((file) => ({ type: file.type, path: file.path, bytes: Buffer.byteLength(file.content, 'utf8'), status: file.status || (safeArgs.writeToDisk ? 'success' : 'rendered') })),
|
|
2786
|
+
levels: model.levels.map((level) => ({
|
|
2787
|
+
levelIndex: level.levelIndex,
|
|
2788
|
+
position: level.position,
|
|
2789
|
+
modules: level.modules.map((moduleModel) => ({
|
|
2790
|
+
key: moduleModel.key,
|
|
2791
|
+
tableName: moduleModel.tableName,
|
|
2792
|
+
tableComment: moduleModel.tableComment,
|
|
2793
|
+
apiPath: buildApiRoutePath(model.moduleName, moduleModel.apiPath).replace(/^\/+/, ''),
|
|
2794
|
+
primaryKey: moduleModel.pk.fieldName,
|
|
2795
|
+
queryParentField: moduleModel.queryParentField,
|
|
2796
|
+
statusField: moduleModel.statusField,
|
|
2797
|
+
statusDictType: moduleModel.statusDictType,
|
|
2798
|
+
enableApi: buildApiRoutePath(model.moduleName, moduleModel.enableApi).replace(/^\/+/, ''),
|
|
2799
|
+
disableApi: buildApiRoutePath(model.moduleName, moduleModel.disableApi).replace(/^\/+/, ''),
|
|
2800
|
+
})),
|
|
2766
2801
|
})),
|
|
2767
|
-
})),
|
|
2768
2802
|
summary: {
|
|
2769
2803
|
totalLevels: model.levels.length,
|
|
2770
2804
|
totalModules: model.modules.length,
|
|
@@ -2786,36 +2820,36 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
2786
2820
|
}));
|
|
2787
2821
|
const selectedList = relations.map((relation) => formatRelationCandidate(relation));
|
|
2788
2822
|
|
|
2789
|
-
return {
|
|
2790
|
-
mode: 'local-template',
|
|
2791
|
-
style: safeArgs.style,
|
|
2792
|
-
pageType: model.pageType || '',
|
|
2793
|
-
styleLabel: stylePreset.label,
|
|
2823
|
+
return {
|
|
2824
|
+
mode: 'local-template',
|
|
2825
|
+
style: safeArgs.style,
|
|
2826
|
+
pageType: model.pageType || '',
|
|
2827
|
+
styleLabel: stylePreset.label,
|
|
2794
2828
|
runtimeSupported: hasRuntimeSupport(stylePreset),
|
|
2795
|
-
tableName: model.tableName,
|
|
2796
|
-
tableComment: model.tableComment,
|
|
2797
|
-
apiPath: model.apiPath,
|
|
2798
|
-
moduleName: model.moduleName,
|
|
2799
|
-
targetViewDir: model.targetViewDir,
|
|
2800
|
-
targetApiModule: model.targetApiModule,
|
|
2801
|
-
targetI18nKey: model.targetI18nKey,
|
|
2802
|
-
writeToDisk: safeArgs.writeToDisk,
|
|
2803
|
-
sideEffects: {
|
|
2804
|
-
writeSupportFiles: safeArgs.writeSupportFiles,
|
|
2805
|
-
mergeI18nZh: safeArgs.mergeI18nZh,
|
|
2806
|
-
},
|
|
2807
|
-
files: files.map((file) => ({ type: file.type, path: file.path, bytes: Buffer.byteLength(file.content, 'utf8'), status: file.status || (safeArgs.writeToDisk ? 'success' : 'rendered') })),
|
|
2829
|
+
tableName: model.tableName,
|
|
2830
|
+
tableComment: model.tableComment,
|
|
2831
|
+
apiPath: buildApiRoutePath(model.moduleName, model.apiPath).replace(/^\/+/, ''),
|
|
2832
|
+
moduleName: model.moduleName,
|
|
2833
|
+
targetViewDir: model.targetViewDir,
|
|
2834
|
+
targetApiModule: model.targetApiModule,
|
|
2835
|
+
targetI18nKey: model.targetI18nKey,
|
|
2836
|
+
writeToDisk: safeArgs.writeToDisk,
|
|
2837
|
+
sideEffects: {
|
|
2838
|
+
writeSupportFiles: safeArgs.writeSupportFiles,
|
|
2839
|
+
mergeI18nZh: safeArgs.mergeI18nZh,
|
|
2840
|
+
},
|
|
2841
|
+
files: files.map((file) => ({ type: file.type, path: file.path, bytes: Buffer.byteLength(file.content, 'utf8'), status: file.status || (safeArgs.writeToDisk ? 'success' : 'rendered') })),
|
|
2808
2842
|
relation: relations.length === 1 ? relations[0] : null,
|
|
2809
2843
|
relations,
|
|
2810
2844
|
relationResolution: model.children.length
|
|
2811
2845
|
? {
|
|
2812
2846
|
status: 'structured_input',
|
|
2813
|
-
tableName: safeArgs.tableName,
|
|
2814
|
-
source: 'arguments',
|
|
2815
|
-
message:
|
|
2816
|
-
model.children.length > 1
|
|
2817
|
-
? 'Direct child relations were provided by the caller as structured metadata. MCP generated a single main form with multiple child tables.'
|
|
2818
|
-
: 'The relation was provided by the caller as structured metadata. MCP generated files directly.',
|
|
2847
|
+
tableName: safeArgs.tableName,
|
|
2848
|
+
source: 'arguments',
|
|
2849
|
+
message:
|
|
2850
|
+
model.children.length > 1
|
|
2851
|
+
? 'Direct child relations were provided by the caller as structured metadata. MCP generated a single main form with multiple child tables.'
|
|
2852
|
+
: 'The relation was provided by the caller as structured metadata. MCP generated files directly.',
|
|
2819
2853
|
selected: selectedList.length === 1 ? selectedList[0] : null,
|
|
2820
2854
|
selectedList,
|
|
2821
2855
|
correctionEntry: {
|
|
@@ -2851,24 +2885,24 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
2851
2885
|
};
|
|
2852
2886
|
}
|
|
2853
2887
|
|
|
2854
|
-
async function handleToolCall(argumentsObject) {
|
|
2855
|
-
const safeArgs = ensureArguments(argumentsObject);
|
|
2856
|
-
const stylePreset = getStylePreset(safeArgs.style);
|
|
2857
|
-
const model = buildModel(safeArgs);
|
|
2858
|
-
const sharedSupport = prepareSharedSupport(model.frontendPath, model.dictTypes, safeArgs.writeSupportFiles);
|
|
2859
|
-
const localeZhSupport = prepareZhCnLocaleFile(model, safeArgs.mergeI18nZh);
|
|
2860
|
-
|
|
2861
|
-
if (!hasRuntimeSupport(stylePreset)) {
|
|
2862
|
-
const manifest = buildManifest(model, safeArgs, stylePreset, [], 'Style mapping is declared, but runtime template rendering is not implemented yet for this style.');
|
|
2863
|
-
return toolTextResult(JSON.stringify(manifest, null, 2));
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
const files = renderFiles(model, stylePreset, sharedSupport, localeZhSupport);
|
|
2867
|
-
maybeWriteFiles(files, safeArgs.writeToDisk, safeArgs.overwrite);
|
|
2868
|
-
maybeWriteSharedSupport(sharedSupport, safeArgs.writeToDisk);
|
|
2869
|
-
const manifest = buildManifest(model, safeArgs, stylePreset, files, buildSupportNote(sharedSupport, localeZhSupport));
|
|
2870
|
-
return toolTextResult(JSON.stringify(manifest, null, 2));
|
|
2871
|
-
}
|
|
2888
|
+
async function handleToolCall(argumentsObject) {
|
|
2889
|
+
const safeArgs = ensureArguments(argumentsObject);
|
|
2890
|
+
const stylePreset = getStylePreset(safeArgs.style);
|
|
2891
|
+
const model = buildModel(safeArgs);
|
|
2892
|
+
const sharedSupport = prepareSharedSupport(model.frontendPath, model.dictTypes, safeArgs.writeSupportFiles);
|
|
2893
|
+
const localeZhSupport = prepareZhCnLocaleFile(model, safeArgs.mergeI18nZh);
|
|
2894
|
+
|
|
2895
|
+
if (!hasRuntimeSupport(stylePreset)) {
|
|
2896
|
+
const manifest = buildManifest(model, safeArgs, stylePreset, [], 'Style mapping is declared, but runtime template rendering is not implemented yet for this style.');
|
|
2897
|
+
return toolTextResult(JSON.stringify(manifest, null, 2));
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
const files = renderFiles(model, stylePreset, sharedSupport, localeZhSupport);
|
|
2901
|
+
maybeWriteFiles(files, safeArgs.writeToDisk, safeArgs.overwrite);
|
|
2902
|
+
maybeWriteSharedSupport(sharedSupport, safeArgs.writeToDisk);
|
|
2903
|
+
const manifest = buildManifest(model, safeArgs, stylePreset, files, buildSupportNote(sharedSupport, localeZhSupport));
|
|
2904
|
+
return toolTextResult(JSON.stringify(manifest, null, 2));
|
|
2905
|
+
}
|
|
2872
2906
|
|
|
2873
2907
|
async function onMessage(message) {
|
|
2874
2908
|
const { id, method, params } = message;
|
|
@@ -2894,11 +2928,11 @@ async function onMessage(message) {
|
|
|
2894
2928
|
successResponse(id, {
|
|
2895
2929
|
tools: [
|
|
2896
2930
|
{
|
|
2897
|
-
name: TOOL_NAME,
|
|
2898
|
-
description:
|
|
2899
|
-
'Generate Worsoft frontend files and menu SQL from structured feature metadata. In master_child_jump mode, the caller must provide children[] with explicit payloadField and child field metadata. In multi_level_dict mode, the caller must provide levels[] with explicit hierarchy metadata.',
|
|
2900
|
-
inputSchema: TOOL_SCHEMA,
|
|
2901
|
-
},
|
|
2931
|
+
name: TOOL_NAME,
|
|
2932
|
+
description:
|
|
2933
|
+
'Generate Worsoft frontend files and menu SQL from structured feature metadata. In master_child_jump mode, the caller must provide children[] with explicit payloadField and child field metadata. In multi_level_dict mode, the caller must provide levels[] with explicit hierarchy metadata.',
|
|
2934
|
+
inputSchema: TOOL_SCHEMA,
|
|
2935
|
+
},
|
|
2902
2936
|
],
|
|
2903
2937
|
})
|
|
2904
2938
|
);
|