worsoft-frontend-codegen-local-mcp 0.1.1 → 0.1.3
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
CHANGED
|
@@ -1,36 +1,38 @@
|
|
|
1
1
|
# Worsoft Frontend Codegen Local MCP
|
|
2
2
|
|
|
3
|
-
This
|
|
4
|
-
It does not call `/admin/generator/mcp/execute`.
|
|
3
|
+
This MCP generates Worsoft frontend files from a local Markdown design document.
|
|
5
4
|
|
|
6
|
-
##
|
|
5
|
+
## Scope
|
|
7
6
|
|
|
8
7
|
- local Markdown design file input
|
|
9
8
|
- style-based template selection
|
|
10
9
|
- local template rendering
|
|
11
10
|
- single-table runtime generation
|
|
12
|
-
- master-child runtime generation with relation
|
|
13
|
-
- render plan preview
|
|
11
|
+
- master-child runtime generation with caller-provided relation input
|
|
14
12
|
- optional write-to-disk
|
|
15
13
|
|
|
16
14
|
## Current Inputs
|
|
17
15
|
|
|
18
|
-
- `designFile
|
|
16
|
+
- `designFile`: optional, defaults to `../sql/SQL 设计说明.md`
|
|
19
17
|
- `tableName`
|
|
20
18
|
- `style`
|
|
21
19
|
- `frontendPath`
|
|
22
20
|
- `moduleName`
|
|
23
21
|
- `writeToDisk`
|
|
24
|
-
- `
|
|
22
|
+
- `overwrite`
|
|
23
|
+
- `childTableName` when `style=master_child_jump`: required
|
|
24
|
+
- `mainField` when `style=master_child_jump`: required
|
|
25
|
+
- `childField` when `style=master_child_jump`: required
|
|
26
|
+
- `relationType` when `style=master_child_jump`: optional
|
|
25
27
|
|
|
26
28
|
## Recommended MCP Arguments
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
Single table:
|
|
29
31
|
|
|
30
32
|
```json
|
|
31
33
|
{
|
|
32
34
|
"tableName": "iwm_sys_trade",
|
|
33
|
-
"style": "
|
|
35
|
+
"style": "single_table_dialog",
|
|
34
36
|
"designFile": "plugins/sql/SQL 设计说明.md",
|
|
35
37
|
"frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
|
|
36
38
|
"moduleName": "admin/test",
|
|
@@ -38,26 +40,41 @@ Use `designFile` as the source argument.
|
|
|
38
40
|
}
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
Master-child:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"tableName": "iwm_sys_trade",
|
|
48
|
+
"style": "master_child_jump",
|
|
49
|
+
"childTableName": "iwm_sys_trade_level",
|
|
50
|
+
"mainField": "id",
|
|
51
|
+
"childField": "trade_id",
|
|
52
|
+
"relationType": "1:N",
|
|
53
|
+
"designFile": "plugins/sql/SQL 设计说明.md",
|
|
54
|
+
"frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
|
|
55
|
+
"moduleName": "admin/test",
|
|
56
|
+
"writeToDisk": false
|
|
57
|
+
}
|
|
58
|
+
```
|
|
42
59
|
|
|
43
|
-
|
|
60
|
+
## Smoke Test
|
|
44
61
|
|
|
45
|
-
|
|
62
|
+
PowerShell:
|
|
46
63
|
|
|
47
|
-
|
|
64
|
+
```powershell
|
|
65
|
+
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario single
|
|
66
|
+
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario master
|
|
67
|
+
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario child
|
|
68
|
+
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario missing_relation
|
|
69
|
+
```
|
|
48
70
|
|
|
49
|
-
|
|
71
|
+
Node:
|
|
50
72
|
|
|
51
73
|
```bash
|
|
52
74
|
node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single
|
|
53
75
|
node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario master
|
|
54
76
|
node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario child
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```powershell
|
|
58
|
-
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario single
|
|
59
|
-
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario master
|
|
60
|
-
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario child
|
|
77
|
+
node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario missing_relation
|
|
61
78
|
```
|
|
62
79
|
|
|
63
80
|
Optional overrides:
|
|
@@ -66,21 +83,30 @@ Optional overrides:
|
|
|
66
83
|
- `--table-name`
|
|
67
84
|
- `--style`
|
|
68
85
|
- `--child-table-name`
|
|
86
|
+
- `--main-field`
|
|
87
|
+
- `--child-field`
|
|
88
|
+
- `--relation-type`
|
|
69
89
|
- `--frontend-path`
|
|
70
90
|
- `--write-to-disk`
|
|
71
91
|
|
|
72
|
-
|
|
92
|
+
## Master-Child Contract
|
|
93
|
+
|
|
94
|
+
For `master_child_jump`, MCP does not infer relations from the design file anymore.
|
|
73
95
|
|
|
74
|
-
|
|
96
|
+
The caller must provide:
|
|
97
|
+
|
|
98
|
+
- `childTableName`
|
|
99
|
+
- `mainField`
|
|
100
|
+
- `childField`
|
|
101
|
+
|
|
102
|
+
If these fields are missing, MCP returns `relation_input_required`.
|
|
103
|
+
|
|
104
|
+
Example:
|
|
75
105
|
|
|
76
106
|
```json
|
|
77
107
|
{
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"designFile": "plugins/sql/SQL 设计说明.md",
|
|
81
|
-
"childTableName": "iwm_sys_trade_level_standard",
|
|
82
|
-
"frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
|
|
83
|
-
"writeToDisk": false
|
|
108
|
+
"type": "relation_input_required",
|
|
109
|
+
"requiredFields": ["childTableName", "mainField", "childField"]
|
|
84
110
|
}
|
|
85
111
|
```
|
|
86
112
|
|
|
@@ -98,28 +124,20 @@ When one main table has multiple child relations in the design file, add `childT
|
|
|
98
124
|
- Machine source: `assets/style-catalog.json`
|
|
99
125
|
|
|
100
126
|
Declared styles:
|
|
127
|
+
|
|
101
128
|
- `single_table_jump`
|
|
102
129
|
- `single_table_dialog`
|
|
103
130
|
- `master_child_jump`
|
|
104
131
|
|
|
105
|
-
Runtime generation support:
|
|
106
|
-
- `single_table_jump`: supported
|
|
107
|
-
- `single_table_dialog`: supported
|
|
108
|
-
- `master_child_jump`: supported
|
|
109
|
-
|
|
110
|
-
## Non-goals for v0.1
|
|
111
|
-
|
|
112
|
-
- backend API forwarding
|
|
113
|
-
- generic Velocity rendering
|
|
114
|
-
- dict lookup from backend
|
|
115
|
-
- full compatibility with the old generator contract
|
|
116
|
-
|
|
117
132
|
## Notes
|
|
118
133
|
|
|
119
134
|
- Shared Worsoft template references live under `../template`.
|
|
120
135
|
- Plugin-local runtime assets live under `assets/templates`.
|
|
121
136
|
- The preferred source is `plugins/sql/SQL 设计说明.md`.
|
|
122
|
-
- Markdown
|
|
123
|
-
-
|
|
124
|
-
- Menu SQL is
|
|
125
|
-
|
|
137
|
+
- Markdown parsing is used for field-definition tables.
|
|
138
|
+
- Master-child relation parsing is handled outside MCP and passed in through tool arguments.
|
|
139
|
+
- Menu SQL is emitted to `frontendPath/menu/`.
|
|
140
|
+
|
|
141
|
+
## IDE Guide
|
|
142
|
+
|
|
143
|
+
- `IDE_MCP_SETUP.md`
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
<div class="layout-padding-auto layout-padding-view">
|
|
3
3
|
<el-card shadow="never">
|
|
4
4
|
<template #header>
|
|
5
|
-
<span>{{ form.{{PK_ATTR}} ? (detail ? '
|
|
5
|
+
<span>{{ form.{{PK_ATTR}} ? (detail ? '查看' : '编辑') : '新增' }}</span>
|
|
6
6
|
</template>
|
|
7
7
|
<el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
|
|
8
8
|
<el-row :gutter="24">
|
|
9
9
|
{{FORM_FIELDS}}
|
|
10
10
|
</el-row>
|
|
11
11
|
<el-row :gutter="24">
|
|
12
|
-
<sc-form-table v-model="form.{{CHILD_LIST_NAME}}" :addTemplate="childTemp" @delete="deleteChild" placeholder="
|
|
12
|
+
<sc-form-table v-model="form.{{CHILD_LIST_NAME}}" :addTemplate="childTemp" @delete="deleteChild" placeholder="暂无数据">
|
|
13
13
|
{{CHILD_TABLE_COLUMNS}}
|
|
14
14
|
</sc-form-table>
|
|
15
15
|
</el-row>
|
|
16
16
|
</el-form>
|
|
17
17
|
<div class="dialog-footer" style="text-align: right; margin-top: 18px;">
|
|
18
|
-
<el-button @click="handleBack"
|
|
19
|
-
<el-button type="primary" @click="onSubmit" :disabled="loading"
|
|
18
|
+
<el-button @click="handleBack">取消</el-button>
|
|
19
|
+
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
|
|
20
20
|
</div>
|
|
21
21
|
</el-card>
|
|
22
22
|
</div>
|
|
@@ -55,7 +55,7 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
|
|
|
55
55
|
const { data } = await getObj({ {{PK_ATTR}}: id });
|
|
56
56
|
Object.assign(form, data[0] || {});
|
|
57
57
|
} catch (error) {
|
|
58
|
-
useMessage().error('
|
|
58
|
+
useMessage().error('获取数据失败');
|
|
59
59
|
} finally {
|
|
60
60
|
loading.value = false;
|
|
61
61
|
}
|
|
@@ -104,10 +104,10 @@ const onSubmit = async () => {
|
|
|
104
104
|
|
|
105
105
|
try {
|
|
106
106
|
form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
|
|
107
|
-
useMessage().success(form.{{PK_ATTR}} ? '
|
|
107
|
+
useMessage().success(form.{{PK_ATTR}} ? '修改成功' : '添加成功');
|
|
108
108
|
closeCurrentPage();
|
|
109
109
|
} catch (err: any) {
|
|
110
|
-
useMessage().error(err.msg || '
|
|
110
|
+
useMessage().error(err.msg || '提交失败');
|
|
111
111
|
} finally {
|
|
112
112
|
loading.value = false;
|
|
113
113
|
}
|
|
@@ -117,9 +117,9 @@ const deleteChild = async (obj: { {{CHILD_PK_ATTR}}: string }) => {
|
|
|
117
117
|
if (obj.{{CHILD_PK_ATTR}}) {
|
|
118
118
|
try {
|
|
119
119
|
await delChildObj([obj.{{CHILD_PK_ATTR}}]);
|
|
120
|
-
useMessage().success('
|
|
120
|
+
useMessage().success('删除成功');
|
|
121
121
|
} catch (err: any) {
|
|
122
|
-
useMessage().error(err.msg || '
|
|
122
|
+
useMessage().error(err.msg || '删除失败');
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-dialog v-model="visible" :title="form.{{PK_ATTR}} ? '
|
|
2
|
+
<el-dialog v-model="visible" :title="form.{{PK_ATTR}} ? '编辑' : '新增'" :close-on-click-modal="false" draggable>
|
|
3
3
|
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading">
|
|
4
4
|
<el-row :gutter="24">
|
|
5
5
|
{{FORM_FIELDS}}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
</el-form>
|
|
8
8
|
<template #footer>
|
|
9
9
|
<span class="dialog-footer">
|
|
10
|
-
<el-button @click="visible = false"
|
|
11
|
-
<el-button type="primary" @click="onSubmit" :disabled="loading"
|
|
10
|
+
<el-button @click="visible = false">取消</el-button>
|
|
11
|
+
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
|
|
12
12
|
</span>
|
|
13
13
|
</template>
|
|
14
14
|
</el-dialog>
|
|
@@ -39,7 +39,7 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
|
|
|
39
39
|
const { data } = await getObj({ {{PK_ATTR}}: id });
|
|
40
40
|
Object.assign(form, data[0] || {});
|
|
41
41
|
} catch (error) {
|
|
42
|
-
useMessage().error('
|
|
42
|
+
useMessage().error('获取数据失败');
|
|
43
43
|
} finally {
|
|
44
44
|
loading.value = false;
|
|
45
45
|
}
|
|
@@ -75,11 +75,11 @@ const onSubmit = async () => {
|
|
|
75
75
|
|
|
76
76
|
try {
|
|
77
77
|
form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
|
|
78
|
-
useMessage().success(form.{{PK_ATTR}} ? '
|
|
78
|
+
useMessage().success(form.{{PK_ATTR}} ? '修改成功' : '添加成功');
|
|
79
79
|
visible.value = false;
|
|
80
80
|
emit('refresh');
|
|
81
81
|
} catch (err: any) {
|
|
82
|
-
useMessage().error(err.msg || '
|
|
82
|
+
useMessage().error(err.msg || '提交失败');
|
|
83
83
|
} finally {
|
|
84
84
|
loading.value = false;
|
|
85
85
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="layout-padding-auto layout-padding-view">
|
|
3
3
|
<el-card shadow="never">
|
|
4
4
|
<template #header>
|
|
5
|
-
<span>{{ form.{{PK_ATTR}} ? (detail ? '
|
|
5
|
+
<span>{{ form.{{PK_ATTR}} ? (detail ? '查看' : '编辑') : '新增' }}</span>
|
|
6
6
|
</template>
|
|
7
7
|
<el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
|
|
8
8
|
<el-row :gutter="24">
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
</el-row>
|
|
11
11
|
</el-form>
|
|
12
12
|
<div class="dialog-footer" style="text-align: right; margin-top: 18px;">
|
|
13
|
-
<el-button @click="handleBack"
|
|
14
|
-
<el-button type="primary" @click="onSubmit" :disabled="loading"
|
|
13
|
+
<el-button @click="handleBack">取消</el-button>
|
|
14
|
+
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
|
|
15
15
|
</div>
|
|
16
16
|
</el-card>
|
|
17
17
|
</div>
|
|
@@ -43,7 +43,7 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
|
|
|
43
43
|
const { data } = await getObj({ {{PK_ATTR}}: id });
|
|
44
44
|
Object.assign(form, data[0] || {});
|
|
45
45
|
} catch (error) {
|
|
46
|
-
useMessage().error('
|
|
46
|
+
useMessage().error('获取数据失败');
|
|
47
47
|
} finally {
|
|
48
48
|
loading.value = false;
|
|
49
49
|
}
|
|
@@ -90,10 +90,10 @@ const onSubmit = async () => {
|
|
|
90
90
|
|
|
91
91
|
try {
|
|
92
92
|
form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
|
|
93
|
-
useMessage().success(form.{{PK_ATTR}} ? '
|
|
93
|
+
useMessage().success(form.{{PK_ATTR}} ? '修改成功' : '添加成功');
|
|
94
94
|
closeCurrentPage();
|
|
95
95
|
} catch (err: any) {
|
|
96
|
-
useMessage().error(err.msg || '
|
|
96
|
+
useMessage().error(err.msg || '提交失败');
|
|
97
97
|
} finally {
|
|
98
98
|
loading.value = false;
|
|
99
99
|
}
|
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.3';
|
|
9
9
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
10
10
|
const TOOL_NAME = 'worsoft_codegen_local_generate_frontend';
|
|
11
11
|
const TEMPLATE_LIBRARY_ROOT = path.resolve(__dirname, '..', 'template');
|
|
@@ -19,7 +19,10 @@ const TOOL_SCHEMA = {
|
|
|
19
19
|
designFile: { type: 'string', description: 'Absolute or relative Markdown design file path. Defaults to ../sql/SQL 设计说明.md when omitted.' },
|
|
20
20
|
tableName: { type: 'string', description: 'Target main table name from the design file.' },
|
|
21
21
|
style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Style id from assets/style-catalog.json.' },
|
|
22
|
-
childTableName: { type: 'string', description: '
|
|
22
|
+
childTableName: { type: 'string', description: 'Child table name. Required when style=master_child_jump.' },
|
|
23
|
+
mainField: { type: 'string', description: 'Main table relation field. Required when style=master_child_jump.' },
|
|
24
|
+
childField: { type: 'string', description: 'Child table relation field. Required when style=master_child_jump.' },
|
|
25
|
+
relationType: { type: 'string', description: 'Optional relation type label, for example 1:N.' },
|
|
23
26
|
frontendPath: { type: 'string', description: 'Absolute frontend output root path.' },
|
|
24
27
|
moduleName: { type: 'string', description: 'Relative frontend module path, for example admin/test.' },
|
|
25
28
|
writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
|
|
@@ -375,7 +378,7 @@ function parseMarkdownRelations(markdownText) {
|
|
|
375
378
|
function parseMarkdownDesignFile(markdownText) {
|
|
376
379
|
return {
|
|
377
380
|
tables: parseMarkdownDesignTables(markdownText),
|
|
378
|
-
relations:
|
|
381
|
+
relations: [],
|
|
379
382
|
};
|
|
380
383
|
}
|
|
381
384
|
|
|
@@ -475,6 +478,18 @@ function buildRetryArguments(safeArgs, sourceFile, childTableName) {
|
|
|
475
478
|
retryArguments.childTableName = childTableName;
|
|
476
479
|
}
|
|
477
480
|
|
|
481
|
+
if (safeArgs.mainField) {
|
|
482
|
+
retryArguments.mainField = safeArgs.mainField;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (safeArgs.childField) {
|
|
486
|
+
retryArguments.childField = safeArgs.childField;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (safeArgs.relationType) {
|
|
490
|
+
retryArguments.relationType = safeArgs.relationType;
|
|
491
|
+
}
|
|
492
|
+
|
|
478
493
|
return retryArguments;
|
|
479
494
|
}
|
|
480
495
|
|
|
@@ -522,26 +537,39 @@ function loadSourceDocument(safeArgs) {
|
|
|
522
537
|
}
|
|
523
538
|
|
|
524
539
|
function resolveMarkdownChildRelation(sourceDocument, safeArgs) {
|
|
525
|
-
|
|
526
|
-
if (
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
: '
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
540
|
+
const missingFields = ['childTableName', 'mainField', 'childField'].filter((field) => !safeArgs[field]);
|
|
541
|
+
if (missingFields.length) {
|
|
542
|
+
const message =
|
|
543
|
+
'master_child_jump requires externally provided relation fields. Missing: ' +
|
|
544
|
+
missingFields.join(', ') +
|
|
545
|
+
'.';
|
|
546
|
+
const error = new Error(message);
|
|
547
|
+
error.details = {
|
|
548
|
+
type: 'relation_input_required',
|
|
549
|
+
status: 'missing_required_relation_input',
|
|
550
|
+
tableName: safeArgs.tableName,
|
|
551
|
+
designFile: sourceDocument.path,
|
|
552
|
+
message,
|
|
553
|
+
requiredFields: ['childTableName', 'mainField', 'childField'],
|
|
554
|
+
correctionEntry: {
|
|
555
|
+
fields: ['childTableName', 'mainField', 'childField'],
|
|
556
|
+
example: {
|
|
557
|
+
...buildRetryArguments(safeArgs, sourceDocument.path, safeArgs.childTableName || '<child_table_name>'),
|
|
558
|
+
mainField: safeArgs.mainField || '<main_field>',
|
|
559
|
+
childField: safeArgs.childField || '<child_field>',
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
throw error;
|
|
542
564
|
}
|
|
543
565
|
|
|
544
|
-
return
|
|
566
|
+
return {
|
|
567
|
+
mainTableName: safeArgs.tableName,
|
|
568
|
+
childTableName: safeArgs.childTableName,
|
|
569
|
+
mainField: safeArgs.mainField,
|
|
570
|
+
childField: safeArgs.childField,
|
|
571
|
+
relationType: safeArgs.relationType || '',
|
|
572
|
+
};
|
|
545
573
|
}
|
|
546
574
|
|
|
547
575
|
function getStylePreset(styleId) {
|
|
@@ -606,8 +634,9 @@ function buildModel(safeArgs) {
|
|
|
606
634
|
const fields = normalizeFields(mainParsed);
|
|
607
635
|
const visibleFields = fields.filter((field) => field.fieldName !== mainParsed.pkField.fieldName && !field.isAudit);
|
|
608
636
|
const gridFields = visibleFields.slice(0, 8);
|
|
609
|
-
const dictTypes = [...new Set(visibleFields.map((field) => field.dictType).filter(Boolean))];
|
|
610
637
|
const child = buildChildModel(sourceDocument, safeArgs, mainParsed);
|
|
638
|
+
const childDictTypes = child ? child.visibleFields.map((field) => field.dictType).filter(Boolean) : [];
|
|
639
|
+
const dictTypes = [...new Set([...visibleFields.map((field) => field.dictType).filter(Boolean), ...childDictTypes])];
|
|
611
640
|
|
|
612
641
|
return {
|
|
613
642
|
sourceFile: sourceDocument.path,
|
|
@@ -644,7 +673,7 @@ function renderFormField(field) {
|
|
|
644
673
|
return [
|
|
645
674
|
' <el-col :span="12" class="mb20">',
|
|
646
675
|
` <el-form-item label="${label}" prop="${prop}">`,
|
|
647
|
-
` <el-select v-model="form.${prop}" placeholder="
|
|
676
|
+
` <el-select v-model="form.${prop}" placeholder="请选择${label}" style="width: 100%">`,
|
|
648
677
|
` <el-option v-for="item in ${field.dictType}" :key="item.value" :label="item.label" :value="Number(item.value)" />`,
|
|
649
678
|
' </el-select>',
|
|
650
679
|
' </el-form-item>',
|
|
@@ -658,7 +687,7 @@ function renderFormField(field) {
|
|
|
658
687
|
return [
|
|
659
688
|
' <el-col :span="12" class="mb20">',
|
|
660
689
|
` <el-form-item label="${label}" prop="${prop}">`,
|
|
661
|
-
` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} placeholder="
|
|
690
|
+
` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} placeholder="请输入${label}" style="width: 100%" />`,
|
|
662
691
|
' </el-form-item>',
|
|
663
692
|
' </el-col>',
|
|
664
693
|
].join('\n');
|
|
@@ -670,7 +699,7 @@ function renderFormField(field) {
|
|
|
670
699
|
return [
|
|
671
700
|
' <el-col :span="12" class="mb20">',
|
|
672
701
|
` <el-form-item label="${label}" prop="${prop}">`,
|
|
673
|
-
` <el-date-picker type="${pickerType}" placeholder="
|
|
702
|
+
` <el-date-picker type="${pickerType}" placeholder="请选择${label}" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"></el-date-picker>`,
|
|
674
703
|
' </el-form-item>',
|
|
675
704
|
' </el-col>',
|
|
676
705
|
].join('\n');
|
|
@@ -680,7 +709,7 @@ function renderFormField(field) {
|
|
|
680
709
|
return [
|
|
681
710
|
' <el-col :span="24" class="mb20">',
|
|
682
711
|
` <el-form-item label="${label}" prop="${prop}">`,
|
|
683
|
-
` <el-input type="textarea" v-model="form.${prop}" placeholder="
|
|
712
|
+
` <el-input type="textarea" v-model="form.${prop}" placeholder="请输入${label}" />`,
|
|
684
713
|
' </el-form-item>',
|
|
685
714
|
' </el-col>',
|
|
686
715
|
].join('\n');
|
|
@@ -689,7 +718,7 @@ function renderFormField(field) {
|
|
|
689
718
|
return [
|
|
690
719
|
' <el-col :span="12" class="mb20">',
|
|
691
720
|
` <el-form-item label="${label}" prop="${prop}">`,
|
|
692
|
-
` <el-input v-model="form.${prop}" placeholder="
|
|
721
|
+
` <el-input v-model="form.${prop}" placeholder="请输入${label}" />`,
|
|
693
722
|
' </el-form-item>',
|
|
694
723
|
' </el-col>',
|
|
695
724
|
].join('\n');
|
|
@@ -711,11 +740,26 @@ function renderTableColumn(field) {
|
|
|
711
740
|
|
|
712
741
|
function renderChildTableColumn(field, childListName) {
|
|
713
742
|
const label = field.comment.replace(/'/g, "\\'");
|
|
743
|
+
const rules = field.notNull ? ` :rules="[{ required: true, trigger: 'blur' }]"` : '';
|
|
744
|
+
|
|
745
|
+
let control = ` <el-input v-model="row.${field.attrName}" />`;
|
|
746
|
+
if (field.formType === 'select' && field.dictType) {
|
|
747
|
+
control = [
|
|
748
|
+
` <el-select v-model="row.${field.attrName}" placeholder="请选择${label}" style="width: 100%">`,
|
|
749
|
+
` <el-option v-for="item in ${field.dictType}" :key="item.value" :label="item.label" :value="Number(item.value)" />`,
|
|
750
|
+
' </el-select>',
|
|
751
|
+
].join('\n');
|
|
752
|
+
} else if (field.formType === 'number') {
|
|
753
|
+
const max = field.comment.includes('%') || field.comment.includes('比例') ? ' :max="100"' : '';
|
|
754
|
+
const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
|
|
755
|
+
control = ` <el-input-number v-model="row.${field.attrName}" :min="0"${max}${precision} style="width: 100%" />`;
|
|
756
|
+
}
|
|
757
|
+
|
|
714
758
|
return [
|
|
715
759
|
` <el-table-column label="${label}" prop="${field.attrName}">`,
|
|
716
760
|
' <template #default="{ row, $index }">',
|
|
717
|
-
` <el-form-item :prop="\`${childListName}.\${$index}.${field.attrName}\`"
|
|
718
|
-
|
|
761
|
+
` <el-form-item :prop="\`${childListName}.\${$index}.${field.attrName}\`"${rules}>`,
|
|
762
|
+
control,
|
|
719
763
|
' </el-form-item>',
|
|
720
764
|
' </template>',
|
|
721
765
|
' </el-table-column>',
|
|
@@ -854,6 +898,9 @@ function ensureArguments(input) {
|
|
|
854
898
|
tableName: String(input.tableName),
|
|
855
899
|
style,
|
|
856
900
|
childTableName: input.childTableName ? String(input.childTableName) : null,
|
|
901
|
+
mainField: input.mainField ? String(input.mainField) : null,
|
|
902
|
+
childField: input.childField ? String(input.childField) : null,
|
|
903
|
+
relationType: input.relationType ? String(input.relationType) : '',
|
|
857
904
|
frontendPath: String(input.frontendPath),
|
|
858
905
|
moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
|
|
859
906
|
writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
|
|
@@ -875,7 +922,6 @@ function maybeWriteFiles(files, writeToDisk, overwrite) {
|
|
|
875
922
|
}
|
|
876
923
|
|
|
877
924
|
function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, note) {
|
|
878
|
-
const relationCandidates = getRelationCandidates(loadSourceDocument(safeArgs).designDoc, safeArgs.tableName);
|
|
879
925
|
return {
|
|
880
926
|
mode: 'local-template',
|
|
881
927
|
style: safeArgs.style,
|
|
@@ -901,17 +947,21 @@ function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, not
|
|
|
901
947
|
: null,
|
|
902
948
|
relationResolution: model.child
|
|
903
949
|
? {
|
|
904
|
-
status: '
|
|
950
|
+
status: 'provided',
|
|
905
951
|
tableName: safeArgs.tableName,
|
|
906
952
|
designFile: model.designFile,
|
|
907
|
-
|
|
953
|
+
source: 'arguments',
|
|
954
|
+
message: 'The relation was provided by the caller. MCP skipped design-doc relation inference and generated files directly.',
|
|
908
955
|
selected: formatRelationCandidate({
|
|
909
956
|
childTableName: model.child.tableName,
|
|
910
957
|
mainField: model.child.mainField.fieldName,
|
|
911
958
|
childField: model.child.childField.fieldName,
|
|
912
959
|
relationType: model.child.relationType || '',
|
|
913
960
|
}),
|
|
914
|
-
correctionEntry:
|
|
961
|
+
correctionEntry: {
|
|
962
|
+
fields: ['childTableName', 'mainField', 'childField'],
|
|
963
|
+
example: buildRetryArguments(safeArgs, model.designFile, model.child.tableName),
|
|
964
|
+
},
|
|
915
965
|
}
|
|
916
966
|
: null,
|
|
917
967
|
summary: {
|
|
@@ -962,7 +1012,7 @@ async function onMessage(message) {
|
|
|
962
1012
|
}
|
|
963
1013
|
|
|
964
1014
|
if (method === 'tools/list') {
|
|
965
|
-
writeMessage(successResponse(id, { tools: [{ name: TOOL_NAME, description: 'Generate Worsoft frontend files and menu SQL from a local Markdown design file with style-based local templates. In master_child_jump mode,
|
|
1015
|
+
writeMessage(successResponse(id, { tools: [{ name: TOOL_NAME, description: 'Generate Worsoft frontend files and menu SQL from a local Markdown design file with style-based local templates. In master_child_jump mode, the caller must provide childTableName, mainField, and childField.', inputSchema: TOOL_SCHEMA }] }));
|
|
966
1016
|
return;
|
|
967
1017
|
}
|
|
968
1018
|
|
package/package.json
CHANGED