worsoft-frontend-codegen-local-mcp 0.1.3 → 0.1.5

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
@@ -24,6 +24,7 @@ This MCP generates Worsoft frontend files from a local Markdown design document.
24
24
  - `mainField` when `style=master_child_jump`: required
25
25
  - `childField` when `style=master_child_jump`: required
26
26
  - `relationType` when `style=master_child_jump`: optional
27
+ - `children` when `style=master_child_jump`: optional array of direct child relations; when provided it overrides the legacy single-child fields
27
28
 
28
29
  ## Recommended MCP Arguments
29
30
 
@@ -57,6 +58,33 @@ Master-child:
57
58
  }
58
59
  ```
59
60
 
61
+ Multi direct children on one main form:
62
+
63
+ ```json
64
+ {
65
+ "tableName": "iwm_sys_trade_level",
66
+ "style": "master_child_jump",
67
+ "children": [
68
+ {
69
+ "childTableName": "iwm_sys_trade_level_standard",
70
+ "mainField": "id",
71
+ "childField": "trade_level_id",
72
+ "relationType": "1:N"
73
+ },
74
+ {
75
+ "childTableName": "iwm_sys_trade_level_competency",
76
+ "mainField": "id",
77
+ "childField": "trade_level_id",
78
+ "relationType": "1:N"
79
+ }
80
+ ],
81
+ "designFile": "plugins/sql/SQL 璁捐璇存槑.md",
82
+ "frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
83
+ "moduleName": "admin/test",
84
+ "writeToDisk": false
85
+ }
86
+ ```
87
+
60
88
  ## Smoke Test
61
89
 
62
90
  PowerShell:
@@ -65,6 +93,7 @@ PowerShell:
65
93
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario single
66
94
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario master
67
95
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario child
96
+ powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario multi
68
97
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario missing_relation
69
98
  ```
70
99
 
@@ -74,6 +103,7 @@ Node:
74
103
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single
75
104
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario master
76
105
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario child
106
+ node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario multi
77
107
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario missing_relation
78
108
  ```
79
109
 
@@ -95,9 +125,8 @@ For `master_child_jump`, MCP does not infer relations from the design file anymo
95
125
 
96
126
  The caller must provide:
97
127
 
98
- - `childTableName`
99
- - `mainField`
100
- - `childField`
128
+ - either `children[]`
129
+ - or the legacy single-child fields: `childTableName`, `mainField`, `childField`
101
130
 
102
131
  If these fields are missing, MCP returns `relation_input_required`.
103
132
 
@@ -40,10 +40,10 @@ export function putObj(obj?: object) {
40
40
  });
41
41
  }
42
42
 
43
- export function delChildObj(ids?: object) {
43
+ export function delChildObj(ids?: object, childTableName?: string) {
44
44
  return request({
45
45
  url: '/{{API_PATH}}/child',
46
46
  method: 'delete',
47
- data: ids,
47
+ data: childTableName ? { ids, childTableName } : ids,
48
48
  });
49
49
  }
@@ -9,9 +9,7 @@
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="暂无数据">
13
- {{CHILD_TABLE_COLUMNS}}
14
- </sc-form-table>
12
+ {{CHILD_SECTIONS}}
15
13
  </el-row>
16
14
  </el-form>
17
15
  <div class="dialog-footer" style="text-align: right; margin-top: 18px;">
@@ -26,7 +24,10 @@
26
24
  import mittBus from '/@/utils/mitt';
27
25
  import { useMessage } from '/@/hooks/message';
28
26
  import { getObj, addObj, putObj, delChildObj } from '/@/api/{{API_MODULE_PATH}}';
29
- {{DICT_IMPORT_BLOCK}}
27
+ import { useDict } from '/@/hooks/dict';
28
+ import { allDictTypes, childFieldGroups, dataMasterEntity } from './options';
29
+
30
+ const dictRefs = useDict(...allDictTypes);
30
31
 
31
32
  const scFormTable = defineAsyncComponent(() => import('/@/components/FormTable/index.vue'));
32
33
  const route = useRoute();
@@ -36,14 +37,24 @@ const dataFormRef = ref();
36
37
  const loading = ref(false);
37
38
  const detail = ref(false);
38
39
 
40
+ const masterFields = dataMasterEntity;
41
+
42
+ const getMasterFieldMeta = (prop: string) => masterFields[prop];
43
+ const isMasterFieldVisible = (prop: string) => {
44
+ const config = getMasterFieldMeta(prop);
45
+ return Boolean(config) && !config.alwaysHide && config.show !== false;
46
+ };
47
+ const getChildFieldMeta = (groupName: string, prop: string) => childFieldGroups[groupName]?.[prop];
48
+ const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
49
+ const inputPlaceholder = (label: string) => `请输入${label}`;
50
+ const selectPlaceholder = (label: string) => `请选择${label}`;
51
+
39
52
  const form = reactive({
40
53
  {{FORM_DEFAULTS}}
41
- {{CHILD_LIST_NAME}}: [],
54
+ {{CHILD_FORM_LIST_DEFAULTS}}
42
55
  });
43
56
 
44
- const childTemp = reactive({
45
- {{CHILD_TEMP_DEFAULTS}}
46
- });
57
+ {{CHILD_TEMP_DECLARATIONS}}
47
58
 
48
59
  const dataRules = ref({
49
60
  {{FORM_RULES}}
@@ -64,11 +75,11 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
64
75
  const resetFormState = () => {
65
76
  Object.assign(form, {
66
77
  {{FORM_DEFAULTS}}
67
- {{CHILD_LIST_NAME}}: [],
78
+ {{CHILD_FORM_LIST_DEFAULTS}}
68
79
  });
69
80
  nextTick(() => {
70
81
  dataFormRef.value?.resetFields();
71
- form.{{CHILD_LIST_NAME}} = [];
82
+ {{CHILD_RESET_LISTS}}
72
83
  });
73
84
  };
74
85
 
@@ -113,10 +124,10 @@ const onSubmit = async () => {
113
124
  }
114
125
  };
115
126
 
116
- const deleteChild = async (obj: { {{CHILD_PK_ATTR}}: string }) => {
117
- if (obj.{{CHILD_PK_ATTR}}) {
127
+ const deleteChild = async (obj: Record<string, any>, childPkAttr: string, childTableName?: string) => {
128
+ if (obj[childPkAttr]) {
118
129
  try {
119
- await delChildObj([obj.{{CHILD_PK_ATTR}}]);
130
+ await delChildObj([obj[childPkAttr]], childTableName);
120
131
  useMessage().success('删除成功');
121
132
  } catch (err: any) {
122
133
  useMessage().error(err.msg || '删除失败');
@@ -21,7 +21,19 @@
21
21
  >
22
22
  <el-table-column type="selection" width="40" align="center" />
23
23
  <el-table-column type="index" label="#" width="40" />
24
- {{TABLE_COLUMNS}}
24
+ <el-table-column
25
+ v-for="column in visibleTableColumns"
26
+ :key="column.prop"
27
+ :prop="column.prop"
28
+ :label="column.label"
29
+ :width="column.width"
30
+ show-overflow-tooltip
31
+ >
32
+ <template #default="scope">
33
+ <dict-tag v-if="column.dictType" :options="getDictOptions(column.dictType)" :value="scope.row[column.prop]" />
34
+ <span v-else>{{ scope.row[column.prop] }}</span>
35
+ </template>
36
+ </el-table-column>
25
37
  <el-table-column label="操作" width="220">
26
38
  <template #default="scope">
27
39
  <el-button text type="primary" icon="view" v-auth="'{{PERMISSION_PREFIX}}_view'" @click="handleDetail(scope.row.{{PK_ATTR}})">查看</el-button>
@@ -42,11 +54,13 @@
42
54
  import { BasicTableProps, useTable } from '/@/hooks/table';
43
55
  import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
44
56
  import { useMessage, useMessageBox } from '/@/hooks/message';
45
- {{DICT_IMPORT_BLOCK}}
57
+ import { useDict } from '/@/hooks/dict';
58
+ import { allDictTypes, dataMasterEntity } from './options';
59
+
60
+ const dictRefs = useDict(...allDictTypes);
46
61
  const router = useRouter();
47
62
 
48
63
  const excelUploadRef = ref();
49
- const queryRef = ref();
50
64
  const showSearch = ref(true);
51
65
  const selectObjs = ref([]) as any;
52
66
  const multiple = ref(true);
@@ -56,6 +70,19 @@ const state: BasicTableProps = reactive<BasicTableProps>({
56
70
  pageList: fetchList,
57
71
  });
58
72
 
73
+ const visibleTableColumns = computed(() =>
74
+ Object.entries(dataMasterEntity)
75
+ .filter(([, config]) => !config.alwaysHide && config.show !== false)
76
+ .map(([prop, config]) => ({
77
+ prop,
78
+ label: config.label,
79
+ width: config.width,
80
+ dictType: config.dictType || '',
81
+ }))
82
+ );
83
+
84
+ const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
85
+
59
86
  const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
60
87
 
61
88
  const getFormPath = () => '/{{VIEW_MODULE_PATH}}/form';
@@ -1,10 +1,32 @@
1
+ {{DICT_REGISTRY_IMPORT_BLOCK}}
2
+ import { createCrudSchema } from '/@/utils/crudSchema';
3
+ import type { CrudSchemaDefinition } from '/@/utils/crudSchema';
4
+
1
5
  /**
2
- * Local codegen metadata for {{TABLE_NAME}}
6
+ * {{TABLE_NAME}} 页面字段声明。
7
+ * 这里只维护字段清单,不手写派生配置。
3
8
  */
4
- export const dataMasterEntity = {
5
- {{OPTIONS_FIELDS}}
9
+ const definition: CrudSchemaDefinition = {
10
+ // 主表字段
11
+ master: [
12
+ {{MASTER_OPTION_FIELDS}}
13
+ ],
14
+ // 子表字段,key 对应 form.vue 中的列表字段名
15
+ children: {
16
+ {{CHILD_OPTION_GROUPS}}
17
+ },
6
18
  };
7
19
 
8
- export const filterTypes = {
9
- {{FILTER_TYPES}}
10
- };
20
+ // 统一派生页面所需配置,避免在 options 文件里重复手写
21
+ const schema = createCrudSchema(definition);
22
+
23
+ // 主表字段配置:列表页、主表表单共用
24
+ export const dataMasterEntity = schema.master;
25
+ // 子表字段配置:主子表 form 页使用
26
+ export const childFieldGroups = schema.children;
27
+ // 主表字典筛选配置
28
+ export const filterTypes = schema.filterTypes;
29
+ // 子表字典筛选配置
30
+ export const childFilterTypes = schema.childFilterTypes;
31
+ // 当前功能涉及的全部字典编码
32
+ export const allDictTypes = schema.allDictTypes;
@@ -17,14 +17,27 @@
17
17
  <script setup lang="ts" name="{{CLASS_NAME}}Dialog">
18
18
  import { useMessage } from '/@/hooks/message';
19
19
  import { getObj, addObj, putObj } from '/@/api/{{API_MODULE_PATH}}';
20
- {{FORM_DICT_IMPORT_BLOCK}}
20
+ import { useDict } from '/@/hooks/dict';
21
+ import { allDictTypes, dataMasterEntity } from './options';
21
22
 
23
+ const dictRefs = useDict(...allDictTypes);
22
24
  const emit = defineEmits(['refresh']);
23
25
 
24
26
  const dataFormRef = ref();
25
27
  const visible = ref(false);
26
28
  const loading = ref(false);
27
29
 
30
+ const masterFields = dataMasterEntity;
31
+
32
+ const getMasterFieldMeta = (prop: string) => masterFields[prop];
33
+ const isMasterFieldVisible = (prop: string) => {
34
+ const config = getMasterFieldMeta(prop);
35
+ return Boolean(config) && !config.alwaysHide && config.show !== false;
36
+ };
37
+ const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
38
+ const inputPlaceholder = (label: string) => `请输入${label}`;
39
+ const selectPlaceholder = (label: string) => `请选择${label}`;
40
+
28
41
  const form = reactive({
29
42
  {{FORM_DEFAULTS}}
30
43
  });
@@ -88,4 +101,4 @@ const onSubmit = async () => {
88
101
  defineExpose({
89
102
  openDialog,
90
103
  });
91
- </script>
104
+ </script>
@@ -21,7 +21,19 @@
21
21
  >
22
22
  <el-table-column type="selection" width="40" align="center" />
23
23
  <el-table-column type="index" label="#" width="40" />
24
- {{TABLE_COLUMNS}}
24
+ <el-table-column
25
+ v-for="column in visibleTableColumns"
26
+ :key="column.prop"
27
+ :prop="column.prop"
28
+ :label="column.label"
29
+ :width="column.width"
30
+ show-overflow-tooltip
31
+ >
32
+ <template #default="scope">
33
+ <dict-tag v-if="column.dictType" :options="getDictOptions(column.dictType)" :value="scope.row[column.prop]" />
34
+ <span v-else>{{ scope.row[column.prop] }}</span>
35
+ </template>
36
+ </el-table-column>
25
37
  <el-table-column label="操作" width="180">
26
38
  <template #default="scope">
27
39
  <el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="formDialogRef.openDialog(scope.row.{{PK_ATTR}})">编辑</el-button>
@@ -42,7 +54,10 @@
42
54
  import { BasicTableProps, useTable } from '/@/hooks/table';
43
55
  import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
44
56
  import { useMessage, useMessageBox } from '/@/hooks/message';
45
- {{TABLE_DICT_IMPORT_BLOCK}}
57
+ import { useDict } from '/@/hooks/dict';
58
+ import { allDictTypes, dataMasterEntity } from './options';
59
+
60
+ const dictRefs = useDict(...allDictTypes);
46
61
 
47
62
  const FormDialog = defineAsyncComponent(() => import('./form.vue'));
48
63
 
@@ -57,6 +72,19 @@ const state: BasicTableProps = reactive<BasicTableProps>({
57
72
  pageList: fetchList,
58
73
  });
59
74
 
75
+ const visibleTableColumns = computed(() =>
76
+ Object.entries(dataMasterEntity)
77
+ .filter(([, config]) => !config.alwaysHide && config.show !== false)
78
+ .map(([prop, config]) => ({
79
+ prop,
80
+ label: config.label,
81
+ width: config.width,
82
+ dictType: config.dictType || '',
83
+ }))
84
+ );
85
+
86
+ const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
87
+
60
88
  const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
61
89
 
62
90
  const exportExcel = () => {
@@ -83,4 +111,4 @@ const handleDelete = async (ids: string[]) => {
83
111
  useMessage().error(err.msg || '删除失败');
84
112
  }
85
113
  };
86
- </script>
114
+ </script>
@@ -1,10 +1,32 @@
1
+ {{DICT_REGISTRY_IMPORT_BLOCK}}
2
+ import { createCrudSchema } from '/@/utils/crudSchema';
3
+ import type { CrudSchemaDefinition } from '/@/utils/crudSchema';
4
+
1
5
  /**
2
- * Local codegen metadata for {{TABLE_NAME}}
6
+ * {{TABLE_NAME}} 页面字段声明。
7
+ * 这里只维护字段清单,不手写派生配置。
3
8
  */
4
- export const dataMasterEntity = {
5
- {{OPTIONS_FIELDS}}
9
+ const definition: CrudSchemaDefinition = {
10
+ // 主表字段
11
+ master: [
12
+ {{MASTER_OPTION_FIELDS}}
13
+ ],
14
+ // 子表字段,key 对应 form.vue 中的列表字段名
15
+ children: {
16
+ {{CHILD_OPTION_GROUPS}}
17
+ },
6
18
  };
7
19
 
8
- export const filterTypes = {
9
- {{FILTER_TYPES}}
10
- };
20
+ // 统一派生页面所需配置,避免在 options 文件里重复手写
21
+ const schema = createCrudSchema(definition);
22
+
23
+ // 主表字段配置:列表页、主表表单共用
24
+ export const dataMasterEntity = schema.master;
25
+ // 子表字段配置:主子表 form 页使用
26
+ export const childFieldGroups = schema.children;
27
+ // 主表字典筛选配置
28
+ export const filterTypes = schema.filterTypes;
29
+ // 子表字典筛选配置
30
+ export const childFilterTypes = schema.childFilterTypes;
31
+ // 当前功能涉及的全部字典编码
32
+ export const allDictTypes = schema.allDictTypes;
@@ -21,7 +21,10 @@
21
21
  import mittBus from '/@/utils/mitt';
22
22
  import { useMessage } from '/@/hooks/message';
23
23
  import { getObj, addObj, putObj } from '/@/api/{{API_MODULE_PATH}}';
24
- {{DICT_IMPORT_BLOCK}}
24
+ import { useDict } from '/@/hooks/dict';
25
+ import { allDictTypes, dataMasterEntity } from './options';
26
+
27
+ const dictRefs = useDict(...allDictTypes);
25
28
  const route = useRoute();
26
29
  const router = useRouter();
27
30
 
@@ -29,6 +32,17 @@ const dataFormRef = ref();
29
32
  const loading = ref(false);
30
33
  const detail = ref(false);
31
34
 
35
+ const masterFields = dataMasterEntity;
36
+
37
+ const getMasterFieldMeta = (prop: string) => masterFields[prop];
38
+ const isMasterFieldVisible = (prop: string) => {
39
+ const config = getMasterFieldMeta(prop);
40
+ return Boolean(config) && !config.alwaysHide && config.show !== false;
41
+ };
42
+ const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
43
+ const inputPlaceholder = (label: string) => `请输入${label}`;
44
+ const selectPlaceholder = (label: string) => `请选择${label}`;
45
+
32
46
  const form = reactive({
33
47
  {{FORM_DEFAULTS}}
34
48
  });
@@ -21,7 +21,19 @@
21
21
  >
22
22
  <el-table-column type="selection" width="40" align="center" />
23
23
  <el-table-column type="index" label="#" width="40" />
24
- {{TABLE_COLUMNS}}
24
+ <el-table-column
25
+ v-for="column in visibleTableColumns"
26
+ :key="column.prop"
27
+ :prop="column.prop"
28
+ :label="column.label"
29
+ :width="column.width"
30
+ show-overflow-tooltip
31
+ >
32
+ <template #default="scope">
33
+ <dict-tag v-if="column.dictType" :options="getDictOptions(column.dictType)" :value="scope.row[column.prop]" />
34
+ <span v-else>{{ scope.row[column.prop] }}</span>
35
+ </template>
36
+ </el-table-column>
25
37
  <el-table-column label="操作" width="220">
26
38
  <template #default="scope">
27
39
  <el-button text type="primary" icon="view" v-auth="'{{PERMISSION_PREFIX}}_view'" @click="handleDetail(scope.row.{{PK_ATTR}})">查看</el-button>
@@ -42,11 +54,13 @@
42
54
  import { BasicTableProps, useTable } from '/@/hooks/table';
43
55
  import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
44
56
  import { useMessage, useMessageBox } from '/@/hooks/message';
45
- {{DICT_IMPORT_BLOCK}}
57
+ import { useDict } from '/@/hooks/dict';
58
+ import { allDictTypes, dataMasterEntity } from './options';
59
+
60
+ const dictRefs = useDict(...allDictTypes);
46
61
  const router = useRouter();
47
62
 
48
63
  const excelUploadRef = ref();
49
- const queryRef = ref();
50
64
  const showSearch = ref(true);
51
65
  const selectObjs = ref([]) as any;
52
66
  const multiple = ref(true);
@@ -56,6 +70,19 @@ const state: BasicTableProps = reactive<BasicTableProps>({
56
70
  pageList: fetchList,
57
71
  });
58
72
 
73
+ const visibleTableColumns = computed(() =>
74
+ Object.entries(dataMasterEntity)
75
+ .filter(([, config]) => !config.alwaysHide && config.show !== false)
76
+ .map(([prop, config]) => ({
77
+ prop,
78
+ label: config.label,
79
+ width: config.width,
80
+ dictType: config.dictType || '',
81
+ }))
82
+ );
83
+
84
+ const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
85
+
59
86
  const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
60
87
 
61
88
  const getFormPath = () => '/{{VIEW_MODULE_PATH}}/form';
@@ -1,10 +1,32 @@
1
+ {{DICT_REGISTRY_IMPORT_BLOCK}}
2
+ import { createCrudSchema } from '/@/utils/crudSchema';
3
+ import type { CrudSchemaDefinition } from '/@/utils/crudSchema';
4
+
1
5
  /**
2
- * Local codegen metadata for {{TABLE_NAME}}
6
+ * {{TABLE_NAME}} 页面字段声明。
7
+ * 这里只维护字段清单,不手写派生配置。
3
8
  */
4
- export const dataMasterEntity = {
5
- {{OPTIONS_FIELDS}}
9
+ const definition: CrudSchemaDefinition = {
10
+ // 主表字段
11
+ master: [
12
+ {{MASTER_OPTION_FIELDS}}
13
+ ],
14
+ // 子表字段,key 对应 form.vue 中的列表字段名
15
+ children: {
16
+ {{CHILD_OPTION_GROUPS}}
17
+ },
6
18
  };
7
19
 
8
- export const filterTypes = {
9
- {{FILTER_TYPES}}
10
- };
20
+ // 统一派生页面所需配置,避免在 options 文件里重复手写
21
+ const schema = createCrudSchema(definition);
22
+
23
+ // 主表字段配置:列表页、主表表单共用
24
+ export const dataMasterEntity = schema.master;
25
+ // 子表字段配置:主子表 form 页使用
26
+ export const childFieldGroups = schema.children;
27
+ // 主表字典筛选配置
28
+ export const filterTypes = schema.filterTypes;
29
+ // 子表字典筛选配置
30
+ export const childFilterTypes = schema.childFilterTypes;
31
+ // 当前功能涉及的全部字典编码
32
+ export const allDictTypes = schema.allDictTypes;