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