worsoft-frontend-codegen-local-mcp 0.1.29 → 0.1.33

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/MVP_DESIGN.md CHANGED
@@ -4,40 +4,43 @@
4
4
 
5
5
  Build a local MCP plugin that generates frontend files from:
6
6
 
7
- - API-first structured metadata
8
- - local template assets
9
- - style-based template selection
7
+ - structured feature metadata
8
+ - local runtime templates
9
+ - style-based rendering rules
10
10
 
11
- without depending on the Worsoft backend generation API
11
+ without depending on the Worsoft backend generation API.
12
12
 
13
13
  ## Current Supported Flow
14
14
 
15
- 1. Caller-side skill reads API doc and requirement doc
16
- 2. Caller-side skill builds structured metadata
15
+ 1. Upstream caller parses PRD, API docs, or any other source
16
+ 2. Upstream caller builds structured JSON metadata
17
17
  3. MCP receives:
18
18
  - main table metadata
19
19
  - child table metadata
20
20
  - explicit `payloadField`
21
+ - explicit `levels[]`
21
22
  - field arrays
22
23
  4. MCP loads style metadata from `assets/style-catalog.json`
23
- 5. MCP resolves shared template selection for the requested `style`
24
- 6. MCP builds a normalized runtime model
25
- 7. MCP maps structured fields into frontend field types
26
- 8. MCP renders runtime templates
27
- 9. MCP returns a render manifest
28
- 10. MCP optionally writes files
24
+ 5. MCP builds a normalized runtime model
25
+ 6. MCP maps structured fields into frontend field types
26
+ 7. MCP renders runtime templates
27
+ 8. MCP returns a render manifest
28
+ 9. MCP optionally writes files
29
+ 10. MCP optionally writes support files and optionally merges `zh-cn.ts`
29
30
 
30
31
  ## Declared Styles
31
32
 
32
33
  - `single_table_jump`
33
34
  - `single_table_dialog`
34
35
  - `master_child_jump`
36
+ - `multi_level_dict`
35
37
 
36
38
  ## Runtime-Implemented Styles
37
39
 
38
40
  - `single_table_jump`
39
41
  - `single_table_dialog`
40
42
  - `master_child_jump`
43
+ - `multi_level_dict`
41
44
 
42
45
  ## Metadata Model
43
46
 
@@ -76,22 +79,36 @@ without depending on the Worsoft backend generation API
76
79
  }
77
80
  ```
78
81
 
79
- ## Master-Child Contract
82
+ ## Contracts
80
83
 
81
- When `style=master_child_jump`:
84
+ ### master_child_jump
82
85
 
83
86
  - MCP never infers child relations
84
87
  - caller must always pass explicit `children[]`
85
88
  - `payloadField` is the only child list truth
86
89
 
90
+ ### multi_level_dict
91
+
92
+ - MCP never infers level hierarchy
93
+ - caller must always pass explicit `levels[]`
94
+ - non-root modules must provide `queryParentField`
95
+
96
+ ### side effects
97
+
98
+ - `writeSupportFiles=true` keeps project helper writes enabled
99
+ - `writeSupportFiles=false` turns MCP into a render-only generator for support files
100
+ - `mergeI18nZh=true` keeps existing Chinese locale merge behavior
101
+ - `mergeI18nZh=false` makes zh-cn output source-of-truth from current metadata only
102
+
87
103
  ## Risks
88
104
 
89
- - Quality now depends on caller-side extraction from API doc and requirement doc
90
- - If API doc omits structure or dict codes, caller-side skill must stop and report the gap
91
- - Query/list/form metadata inference still contains heuristics for widths and control defaults
105
+ - Quality depends on upstream structured metadata quality
106
+ - If upstream metadata omits structure or dict codes, caller must stop and report the gap
107
+ - Query/list/form metadata normalization still contains heuristics for widths and default controls
92
108
 
93
109
  ## Next Steps
94
110
 
95
- 1. Continue hardening API-first extraction rules on caller-side skills
111
+ 1. Keep the MCP contract source-agnostic and JSON-only
96
112
  2. Expand structured validation for field completeness
97
- 3. Keep template behavior aligned with API-first contract
113
+ 3. Keep render behavior aligned with the structured metadata contract
114
+ 4. Continue reducing project-specific side effects so MCP focuses on validate, render, and optional write
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Worsoft Frontend Codegen Local MCP
2
2
 
3
- This MCP generates Worsoft frontend files from API-first structured metadata.
3
+ This MCP generates Worsoft frontend files from structured JSON metadata.
4
4
 
5
5
  ## Scope
6
6
 
7
- - API-first structured input
7
+ - structured JSON input
8
8
  - style-based template selection
9
9
  - local template rendering
10
10
  - single-table runtime generation
@@ -20,10 +20,13 @@ This MCP generates Worsoft frontend files from API-first structured metadata.
20
20
  - `style`
21
21
  - `fields`
22
22
  - `children`
23
+ - `levels`
23
24
  - `frontendPath`
24
25
  - `moduleName`
25
26
  - `writeToDisk`
26
27
  - `overwrite`
28
+ - `writeSupportFiles`
29
+ - `mergeI18nZh`
27
30
 
28
31
  ## Recommended MCP Arguments
29
32
 
@@ -88,6 +91,7 @@ PowerShell:
88
91
 
89
92
  ```powershell
90
93
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario single
94
+ powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario single_dialog
91
95
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario master
92
96
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario multi
93
97
  ```
@@ -96,8 +100,9 @@ Node:
96
100
 
97
101
  ```bash
98
102
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single
103
+ node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single_dialog
99
104
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario master
100
- node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario multi
105
+ node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario dict_multi
101
106
  ```
102
107
 
103
108
  Optional overrides:
@@ -110,6 +115,17 @@ Optional overrides:
110
115
  - `--frontend-path`
111
116
  - `--write-to-disk`
112
117
 
118
+ ## Side-Effect Controls
119
+
120
+ - `writeSupportFiles=true`:
121
+ MCP may write `src/enums/dict-registry.ts` and `src/utils/crudSchema.ts`
122
+ - `writeSupportFiles=false`:
123
+ MCP does not write support files. Generated code still expects those helpers to already exist in the target project
124
+ - `mergeI18nZh=true`:
125
+ MCP reads an existing `zh-cn.ts` and merges missing keys
126
+ - `mergeI18nZh=false`:
127
+ MCP renders `zh-cn.ts` from current metadata only and does not parse the existing file
128
+
113
129
  ## Master-Child Contract
114
130
 
115
131
  For `master_child_jump`, MCP does not infer relations.
@@ -125,6 +141,21 @@ The caller must provide explicit `children[]`, and each child must include:
125
141
 
126
142
  If these fields are missing, MCP returns a structured validation error.
127
143
 
144
+ ## Multi-Level Dictionary Contract
145
+
146
+ For `multi_level_dict`, MCP does not infer hierarchy.
147
+
148
+ The caller must provide explicit `levels[]`. Each level must include:
149
+
150
+ - `levelIndex`
151
+ - `modules`
152
+
153
+ Each module must include:
154
+
155
+ - `tableName`
156
+ - `apiPath`
157
+ - `fields`
158
+
128
159
  ## Current Outputs
129
160
 
130
161
  - `src/views/<module>/<function>/index.vue`
@@ -144,13 +175,14 @@ Declared styles:
144
175
  - `single_table_jump`
145
176
  - `single_table_dialog`
146
177
  - `master_child_jump`
178
+ - `multi_level_dict`
147
179
 
148
180
  ## Notes
149
181
 
150
- - Shared Worsoft template references live under `../template`
151
182
  - Plugin-local runtime assets live under `assets/templates`
152
- - API-first means MCP no longer reads Markdown design docs or SQL design docs
153
- - Caller-side skills are responsible for turning API doc + requirement doc into structured arguments
183
+ - MCP does not parse PRD, API, Markdown, or SQL design documents
184
+ - Upstream caller-side skills are responsible for turning documents into structured JSON metadata
185
+ - Support-file writes and zh-cn merge behavior can be controlled through `writeSupportFiles` and `mergeI18nZh`
154
186
  - Menu SQL is emitted to `frontendPath/menu/`
155
187
 
156
188
  ## IDE Guide
@@ -1,56 +1,35 @@
1
1
  <template>
2
2
  <div class="layout-padding">
3
- <!-- 功能名称:{{FEATURE_TITLE}} -->
4
- <div class="layout-padding-auto layout-padding-view flex-column" style="display: flex; flex-direction: column; height: 100%;">
5
- <div class="mb8" style="width: 100%; flex-shrink: 0;">
6
- <el-button icon="folder-add" type="primary" class="ml10" @click="handleCreate">{{ t('common.addBtn') }}</el-button>
7
- <el-button plain icon="upload-filled" type="primary" class="ml10" @click="excelUploadRef.show()">{{ t('common.importBtn') }}</el-button>
8
- <el-button plain :disabled="multiple" icon="Delete" type="primary" @click="handleDelete(selectObjs)">{{ t('common.delBtn') }}</el-button>
9
- <right-toolbar :export="'{{PERMISSION_PREFIX}}_export'" @exportExcel="exportExcel" class="ml10 mr20" style="float: right;" />
10
- </div>
11
-
12
- <div style="flex: 1; overflow: hidden; display: flex; flex-direction: column;">
13
- <el-table
14
- :data="state.dataList"
15
- v-loading="state.loading"
16
- border
17
- height="100%"
18
- style="width: 100%;"
19
- :cell-style="tableStyle.cellStyle"
20
- :header-cell-style="tableStyle.headerCellStyle"
21
- @selection-change="selectionChangeHandle"
22
- @sort-change="sortChangeHandle"
23
- >
24
- <el-table-column type="selection" width="40" align="center" />
25
- <el-table-column type="index" :label="t('common.serial')" width="60" />
26
- <el-table-column
27
- v-for="column in visibleTableColumns"
28
- :key="column.prop"
29
- :prop="column.prop"
30
- :label="resolveLabel(column.labelKey, column.fallbackLabel)"
31
- :min-width="column.width"
32
- show-overflow-tooltip
33
- >
34
- <template #default="scope">
35
- <dict-tag v-if="column.dictType" :options="getDictOptions(column.dictType)" :value="scope.row[column.prop]" />
36
- <span v-else>{{ scope.row[column.prop] }}</span>
37
- </template>
38
- </el-table-column>
39
- <el-table-column :label="t('common.action')" width="380" fixed="right">
40
- <template #default="scope">
41
- <el-button text type="primary" icon="view" v-auth="'{{PERMISSION_PREFIX}}_view'" @click="handleDetail(scope.row.id)">{{ t('common.viewBtn') }}</el-button>
42
- <el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="handleEdit(scope.row.id)">{{ t('common.editBtn') }}</el-button>
43
- <el-button text type="primary" icon="check" @click="handleQuickAction(scope.row, 'submit')">{{ commonActionLabel('submit') }}</el-button>
44
- <el-button text type="success" icon="position" @click="handleQuickAction(scope.row, 'flow')">{{ commonActionLabel('flow') }}</el-button>
45
- <el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([scope.row.id])">{{ t('common.delBtn') }}</el-button>
46
- </template>
47
- </el-table-column>
48
- </el-table>
49
- </div>
50
-
51
- <div style="flex-shrink: 0; margin-top: 10px; display: flex; justify-content: flex-end;">
52
- <pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
53
- </div>
3
+ <div class="layout-padding-auto layout-padding-view flex h-full flex-col">
4
+ <SchemaListToolbar
5
+ v-bind="toolbarProps"
6
+ @update:keyword="state.queryForm.smartVal = $event"
7
+ @add="handleToolbarAdd"
8
+ @import="handleToolbarImport"
9
+ @delete="handleToolbarDelete"
10
+ @query="handleToolbarQuery"
11
+ @reset="handleToolbarReset"
12
+ @custom-query-confirm="handleToolbarCustomQueryConfirm"
13
+ @export="handleToolbarExport"
14
+ @refresh="handleToolbarRefresh"
15
+ />
16
+
17
+ <SchemaListTable
18
+ v-bind="tableProps"
19
+ row-id-key="{{PK_ATTR}}"
20
+ @selection-change="handleTableSelectionChange"
21
+ @sort-change="handleTableSortChange"
22
+ @size-change="handleTableSizeChange"
23
+ @current-change="handleTableCurrentChange"
24
+ >
25
+ <template #actions="{ row }">
26
+ <el-button text type="primary" icon="view" v-auth="'{{PERMISSION_PREFIX}}_view'" @click="handleDetail(row.{{PK_ATTR}})">{{ t('common.viewBtn') }}</el-button>
27
+ <el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="handleEdit(row.{{PK_ATTR}})">{{ t('common.editBtn') }}</el-button>
28
+ <el-button text type="primary" icon="check" @click="handleQuickAction(row, 'submit')">{{ commonActionLabel('submit') }}</el-button>
29
+ <el-button text type="success" icon="position" @click="handleQuickAction(row, 'flow')">{{ commonActionLabel('flow') }}</el-button>
30
+ <el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([row.{{PK_ATTR}}])">{{ t('common.delBtn') }}</el-button>
31
+ </template>
32
+ </SchemaListTable>
54
33
  </div>
55
34
 
56
35
  <upload-excel ref="excelUploadRef" :title="t('common.importBtn')" url="/{{API_PATH}}/import" temp-url="/admin/sys-file/local/file/{{FUNCTION_NAME}}.xlsx" @refreshDataList="getDataList" />
@@ -58,52 +37,83 @@
58
37
  </template>
59
38
 
60
39
  <script setup lang="ts" name="system{{CLASS_NAME}}">
61
- import { BasicTableProps, useTable } from '/@/hooks/table';
62
40
  import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
63
41
  import { useMessage, useMessageBox } from '/@/hooks/message';
64
42
  import { useDict } from '/@/hooks/dict';
65
43
  import { useCrudPageMeta } from '/@/hooks/useCrudPageMeta';
44
+ import { useSchemaListQuery } from '/@/hooks/useSchemaListQuery';
45
+ import SchemaListToolbar from '/@/components/schema-list/SchemaListToolbar.vue';
46
+ import SchemaListTable from '/@/components/schema-list/SchemaListTable.vue';
66
47
  import { useI18n } from 'vue-i18n';
67
- import { allDictTypes, dataMasterEntity } from './options';
48
+ import { allDictTypes, crudSchema, dataMasterEntity } from './options';
68
49
 
69
50
  const dictRefs = useDict(...allDictTypes);
70
51
  const { t } = useI18n();
71
52
  const router = useRouter();
72
53
 
73
54
  const excelUploadRef = ref();
74
- const selectObjs = ref([]) as any;
55
+ const selectObjs = ref<string[]>([]);
75
56
  const multiple = ref(true);
76
57
 
77
- // 列表页基础状态,由 useTable 统一接管分页、排序和数据加载
78
- const state: BasicTableProps = reactive<BasicTableProps>({
79
- queryForm: {},
58
+ const { state, getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle, exportExcel: runExportExcel, resetQueryForm } = useSchemaListQuery({
59
+ schema: crudSchema,
80
60
  pageList: fetchList,
61
+ exportUrl: '/{{API_PATH}}/export',
62
+ exportFileName: '{{FUNCTION_NAME}}.xlsx',
81
63
  });
82
64
 
83
- // 页面字段、双语和字典相关的公共能力统一从 hook 中获取
84
- const { resolveLabel, getDictOptions, commonActionLabel, visibleTableColumns } = useCrudPageMeta(dataMasterEntity, dictRefs);
85
-
86
- const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
65
+ const { resolveLabel, getDictOptions, commonActionLabel, visibleTableColumns, searchKeywordTooltip, queryableDictFields } = useCrudPageMeta(dataMasterEntity, dictRefs);
66
+
67
+ const queryableDictOptions = computed(() =>
68
+ queryableDictFields.value.map((field) => ({
69
+ ...field,
70
+ options: getDictOptions(field.dictType),
71
+ }))
72
+ );
73
+
74
+ const tableColumns = computed(() =>
75
+ visibleTableColumns.value.map((column) => ({
76
+ prop: column.prop,
77
+ label: resolveLabel(column.labelKey, column.fallbackLabel),
78
+ width: column.width,
79
+ dictType: column.dictType,
80
+ options: column.dictType ? getDictOptions(column.dictType) : [],
81
+ }))
82
+ );
83
+
84
+ const toolbarProps = computed(() => ({
85
+ keyword: state.queryForm.smartVal,
86
+ searchPlaceholder: searchKeywordTooltip.value,
87
+ queryModel: state.queryForm,
88
+ customQueryFields: queryableDictOptions.value,
89
+ selectedIds: selectObjs.value,
90
+ deleteDisabled: multiple.value,
91
+ exportPermission: '{{PERMISSION_PREFIX}}_export',
92
+ }));
93
+
94
+ const tableProps = computed(() => ({
95
+ data: state.dataList,
96
+ loading: !!state.loading,
97
+ columns: tableColumns.value,
98
+ pagination: state.pagination,
99
+ tableStyle,
100
+ }));
87
101
 
88
102
  const getFormPath = () => '/{{VIEW_MODULE_PATH}}/form';
89
103
 
90
- // 进入新增页
91
104
  const handleCreate = () => {
92
105
  router.push({ path: getFormPath(), query: { tagsViewName: t('common.addBtn') } });
93
106
  };
94
107
 
95
- // 进入详情页
96
108
  const handleDetail = (id: string) => {
97
109
  router.push({ path: getFormPath(), query: { id, detail: '1', tagsViewName: t('common.viewBtn') } });
98
110
  };
99
111
 
100
- // 进入编辑页
101
112
  const handleEdit = (id: string) => {
102
113
  router.push({ path: getFormPath(), query: { id, tagsViewName: t('common.editBtn') } });
103
114
  };
104
115
 
105
- // 快捷提交和流转共用一套确认和成功提示
106
- const handleQuickAction = async (row: any, actionType: string) => {
116
+ const handleQuickAction = async (_row: any, actionType: string) => {
107
117
  const actionName = commonActionLabel(actionType);
108
118
  try {
109
119
  await useMessageBox().confirm(t('common.messages.quickActionConfirm', { action: actionName }));
@@ -113,21 +123,32 @@ const handleQuickAction = async (row: any, actionType: string) => {
113
123
  : t('common.messages.quickFlowSuccess')
114
124
  );
115
125
  getDataList();
116
- } catch {}
126
+ } catch {
127
+ return;
128
+ }
117
129
  };
118
130
 
119
- // 导出当前查询结果
120
131
  const exportExcel = () => {
121
- downBlobFile('/{{API_PATH}}/export', { ...state.queryForm, ids: selectObjs.value }, '{{FUNCTION_NAME}}.xlsx');
132
+ runExportExcel(selectObjs.value);
122
133
  };
123
134
 
124
- // 维护当前表格勾选项
125
- const selectionChangeHandle = (objs: { id: string }[]) => {
126
- selectObjs.value = objs.map((item) => item.id);
127
- multiple.value = !objs.length;
135
+ const handleQueryFilterConfirm = (values: Record<string, any>) => {
136
+ queryableDictFields.value.forEach((field) => {
137
+ const nextValue = values[field.prop];
138
+ if (Array.isArray(nextValue) && nextValue.length) {
139
+ state.queryForm[field.prop] = nextValue;
140
+ return;
141
+ }
142
+ delete state.queryForm[field.prop];
143
+ });
144
+ getDataList();
145
+ };
146
+
147
+ const resetQuery = () => {
148
+ resetQueryForm();
149
+ getDataList();
128
150
  };
129
151
 
130
- // 删除前先进行二次确认
131
152
  const handleDelete = async (ids: string[]) => {
132
153
  try {
133
154
  await useMessageBox().confirm(t('common.delConfirmText'));
@@ -143,4 +164,53 @@ const handleDelete = async (ids: string[]) => {
143
164
  useMessage().error(err.msg || t('common.delBtn'));
144
165
  }
145
166
  };
167
+
168
+ const handleToolbarAdd = () => {
169
+ handleCreate();
170
+ };
171
+
172
+ const handleToolbarImport = () => {
173
+ excelUploadRef.value?.show();
174
+ };
175
+
176
+ const handleToolbarDelete = () => {
177
+ handleDelete(selectObjs.value);
178
+ };
179
+
180
+ const handleToolbarQuery = () => {
181
+ getDataList();
182
+ };
183
+
184
+ const handleToolbarReset = () => {
185
+ resetQuery();
186
+ };
187
+
188
+ const handleToolbarCustomQueryConfirm = (payload: { values: Record<string, any> }) => {
189
+ handleQueryFilterConfirm(payload.values);
190
+ };
191
+
192
+ const handleToolbarExport = () => {
193
+ exportExcel();
194
+ };
195
+
196
+ const handleToolbarRefresh = () => {
197
+ getDataList();
198
+ };
199
+
200
+ const handleTableSelectionChange = (payload: { ids: Array<string | number> }) => {
201
+ selectObjs.value = payload.ids.map((id) => String(id));
202
+ multiple.value = !payload.ids.length;
203
+ };
204
+
205
+ const handleTableSortChange = (payload: { raw: any }) => {
206
+ sortChangeHandle(payload.raw);
207
+ };
208
+
209
+ const handleTableSizeChange = (payload: { size: number }) => {
210
+ sizeChangeHandle(payload.size);
211
+ };
212
+
213
+ const handleTableCurrentChange = (payload: { current: number }) => {
214
+ currentChangeHandle(payload.current);
215
+ };
146
216
  </script>
@@ -18,6 +18,7 @@ const definition: CrudSchemaDefinition = {
18
18
  // 将页面声明转换成 index/form 统一消费的 schema 结果
19
19
  const schema = createCrudSchema(definition);
20
20
 
21
+ export const crudSchema = schema;
21
22
  // 主表字段配置,供列表页和表单页使用
22
23
  export const dataMasterEntity = schema.master;
23
24
  // 子表字段配置,供主子表区块读取