worsoft-frontend-codegen-local-mcp 0.1.0
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 +78 -0
- package/README.md +121 -0
- package/assets/style-catalog.json +71 -0
- package/assets/templates/master_child_jump/api.tpl +49 -0
- package/assets/templates/master_child_jump/form.tpl +130 -0
- package/assets/templates/master_child_jump/index.tpl +99 -0
- package/assets/templates/master_child_jump/menu.sql.tpl +21 -0
- package/assets/templates/master_child_jump/options.tpl +10 -0
- package/assets/templates/single_table_dialog/api.tpl +41 -0
- package/assets/templates/single_table_dialog/form.tpl +91 -0
- package/assets/templates/single_table_dialog/index.tpl +86 -0
- package/assets/templates/single_table_dialog/menu.sql.tpl +21 -0
- package/assets/templates/single_table_dialog/options.tpl +10 -0
- package/assets/templates/single_table_jump/api.tpl +41 -0
- package/assets/templates/single_table_jump/form.tpl +105 -0
- package/assets/templates/single_table_jump/index.tpl +99 -0
- package/assets/templates/single_table_jump/menu.sql.tpl +21 -0
- package/assets/templates/single_table_jump/options.tpl +10 -0
- package/mcp_server.js +1013 -0
- package/package.json +23 -0
package/MVP_DESIGN.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# MVP Design
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Build a parallel MCP plugin that generates frontend files from:
|
|
6
|
+
- local Markdown design docs
|
|
7
|
+
- local template assets
|
|
8
|
+
- style-based template selection
|
|
9
|
+
|
|
10
|
+
without depending on the Worsoft backend generation API.
|
|
11
|
+
|
|
12
|
+
## First Supported Flow
|
|
13
|
+
|
|
14
|
+
1. Read `designFile` or fall back to `plugins/sql/SQL 设计说明.md`
|
|
15
|
+
2. Parse one table definition by `tableName`
|
|
16
|
+
3. Load style metadata from `assets/style-catalog.json`
|
|
17
|
+
4. Resolve the shared template selection for the requested `style`
|
|
18
|
+
5. Build a normalized metadata model
|
|
19
|
+
6. For `master_child_jump`, infer the child relation from the design doc when possible
|
|
20
|
+
7. Map parsed fields into simple frontend field types
|
|
21
|
+
8. Render plugin-local runtime templates when the style is implemented
|
|
22
|
+
9. Return a render manifest
|
|
23
|
+
10. Optionally write files
|
|
24
|
+
|
|
25
|
+
## Declared Styles
|
|
26
|
+
|
|
27
|
+
- `single_table_jump`
|
|
28
|
+
- `single_table_dialog`
|
|
29
|
+
- `master_child_jump`
|
|
30
|
+
|
|
31
|
+
## Runtime-Implemented Styles
|
|
32
|
+
|
|
33
|
+
- `single_table_jump`
|
|
34
|
+
- `single_table_dialog`
|
|
35
|
+
- `master_child_jump`
|
|
36
|
+
|
|
37
|
+
## Metadata Model
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"tableName": "iwm_sys_trade",
|
|
42
|
+
"className": "IwmSysTrade",
|
|
43
|
+
"functionName": "iwmSysTrade",
|
|
44
|
+
"moduleName": "admin/test",
|
|
45
|
+
"pk": { "fieldName": "id", "attrName": "id" },
|
|
46
|
+
"fields": [
|
|
47
|
+
{
|
|
48
|
+
"fieldName": "trade_name",
|
|
49
|
+
"attrName": "tradeName",
|
|
50
|
+
"sqlType": "VARCHAR",
|
|
51
|
+
"comment": "Trade Name",
|
|
52
|
+
"formType": "text",
|
|
53
|
+
"isAudit": false
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Master-Child Contract
|
|
60
|
+
|
|
61
|
+
When `style=master_child_jump`, local mode now prefers the Markdown design file contract:
|
|
62
|
+
- parse child relations from the "主从表关联说明" section
|
|
63
|
+
- auto-resolve `mainField` and `childField`
|
|
64
|
+
- require `childTableName` only when one main table has multiple child relations
|
|
65
|
+
|
|
66
|
+
## Risks
|
|
67
|
+
|
|
68
|
+
- Existing shared templates use Velocity syntax and need a larger rendering engine.
|
|
69
|
+
- Markdown design docs may drift from the real schema if they are not maintained.
|
|
70
|
+
- Query/list/form metadata inference is heuristic in local mode.
|
|
71
|
+
- Multi-child relations still need explicit disambiguation.
|
|
72
|
+
|
|
73
|
+
## Next Steps
|
|
74
|
+
|
|
75
|
+
1. Add query/grid heuristics
|
|
76
|
+
2. Add dict config sidecar file support
|
|
77
|
+
3. Decide whether to migrate shared templates into a neutral local template format
|
|
78
|
+
4. Consider cross-checking Markdown definitions against SQL or DB metadata
|
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Worsoft Frontend Codegen Local MCP
|
|
2
|
+
|
|
3
|
+
This plugin is a parallel local-template MVP for frontend code generation.
|
|
4
|
+
It does not call `/admin/generator/mcp/execute`.
|
|
5
|
+
|
|
6
|
+
## MVP Scope
|
|
7
|
+
|
|
8
|
+
- local Markdown design file input
|
|
9
|
+
- style-based template selection
|
|
10
|
+
- local template rendering
|
|
11
|
+
- single-table runtime generation
|
|
12
|
+
- master-child runtime generation with relation inference from the design file
|
|
13
|
+
- render plan preview
|
|
14
|
+
- optional write-to-disk
|
|
15
|
+
|
|
16
|
+
## Current Inputs
|
|
17
|
+
|
|
18
|
+
- `designFile` : optional, defaults to `../sql/SQL 设计说明.md`
|
|
19
|
+
- `tableName`
|
|
20
|
+
- `style`
|
|
21
|
+
- `frontendPath`
|
|
22
|
+
- `moduleName`
|
|
23
|
+
- `writeToDisk`
|
|
24
|
+
- `childTableName` when `style=master_child_jump` : optional, only needed when one main table has multiple child-table relations in the design file
|
|
25
|
+
|
|
26
|
+
## Recommended MCP Arguments
|
|
27
|
+
|
|
28
|
+
Use `designFile` as the source argument.
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"tableName": "iwm_sys_trade",
|
|
33
|
+
"style": "master_child_jump",
|
|
34
|
+
"designFile": "plugins/sql/SQL 设计说明.md",
|
|
35
|
+
"frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
|
|
36
|
+
"moduleName": "admin/test",
|
|
37
|
+
"writeToDisk": false
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Smoke Test Template
|
|
42
|
+
|
|
43
|
+
Use `smoke_test_mcp.js` for quick local MCP verification.
|
|
44
|
+
|
|
45
|
+
PowerShell is the recommended option on Windows because it does not depend on Node spawning a child Node process.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single
|
|
49
|
+
node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario master
|
|
50
|
+
node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario child
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```powershell
|
|
54
|
+
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario single
|
|
55
|
+
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario master
|
|
56
|
+
powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario child
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Optional overrides:
|
|
60
|
+
|
|
61
|
+
- `--design-file`
|
|
62
|
+
- `--table-name`
|
|
63
|
+
- `--style`
|
|
64
|
+
- `--child-table-name`
|
|
65
|
+
- `--frontend-path`
|
|
66
|
+
- `--write-to-disk`
|
|
67
|
+
|
|
68
|
+
For `master_child_jump`, the execution result will return the detected relation. If the relation is ambiguous or not the one you want, the MCP error/result includes a `correctionEntry` with `childTableName`, `example`, and `retryArguments`, so the caller can resubmit directly.
|
|
69
|
+
|
|
70
|
+
When one main table has multiple child relations in the design file, add `childTableName` to disambiguate:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"tableName": "iwm_sys_trade_level",
|
|
75
|
+
"style": "master_child_jump",
|
|
76
|
+
"designFile": "plugins/sql/SQL 设计说明.md",
|
|
77
|
+
"childTableName": "iwm_sys_trade_level_standard",
|
|
78
|
+
"frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
|
|
79
|
+
"writeToDisk": false
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Current Outputs
|
|
84
|
+
|
|
85
|
+
- `src/views/<module>/<function>/index.vue`
|
|
86
|
+
- `src/views/<module>/<function>/form.vue`
|
|
87
|
+
- `src/views/<module>/<function>/options.ts`
|
|
88
|
+
- `src/api/<module>/<function>.ts`
|
|
89
|
+
- `menu/<function>_menu.sql`
|
|
90
|
+
|
|
91
|
+
## Style Catalog
|
|
92
|
+
|
|
93
|
+
- Human guide: `STYLE_TEMPLATE_GUIDE.md`
|
|
94
|
+
- Machine source: `assets/style-catalog.json`
|
|
95
|
+
|
|
96
|
+
Declared styles:
|
|
97
|
+
- `single_table_jump`
|
|
98
|
+
- `single_table_dialog`
|
|
99
|
+
- `master_child_jump`
|
|
100
|
+
|
|
101
|
+
Runtime generation support:
|
|
102
|
+
- `single_table_jump`: supported
|
|
103
|
+
- `single_table_dialog`: supported
|
|
104
|
+
- `master_child_jump`: supported
|
|
105
|
+
|
|
106
|
+
## Non-goals for v0.1
|
|
107
|
+
|
|
108
|
+
- backend API forwarding
|
|
109
|
+
- generic Velocity rendering
|
|
110
|
+
- dict lookup from backend
|
|
111
|
+
- full compatibility with the old generator contract
|
|
112
|
+
|
|
113
|
+
## Notes
|
|
114
|
+
|
|
115
|
+
- Shared Worsoft template references live under `../template`.
|
|
116
|
+
- Plugin-local runtime assets live under `assets/templates`.
|
|
117
|
+
- The preferred source is `plugins/sql/SQL 设计说明.md`.
|
|
118
|
+
- Markdown mode parses both the field-definition tables and the "主从表关联说明" section.
|
|
119
|
+
- When a main table has multiple child relations, provide `childTableName` to disambiguate.
|
|
120
|
+
- Menu SQL is currently emitted to `frontendPath/menu/` because local mode does not accept `backendPath` yet.
|
|
121
|
+
- The renderer uses a simple placeholder engine instead of the old backend template engine.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"single_table_jump": {
|
|
3
|
+
"id": "single_table_jump",
|
|
4
|
+
"label": "单表跳转模板",
|
|
5
|
+
"description": "单表跳转页面,使用单表 form 和跳转列表模板。",
|
|
6
|
+
"templateFiles": {
|
|
7
|
+
"api": "api.md",
|
|
8
|
+
"options": "options.md",
|
|
9
|
+
"form": "单主表单跳转方式.md",
|
|
10
|
+
"list": "主子表单跳转列表.md",
|
|
11
|
+
"menuSql": "权限菜单V2025001.md"
|
|
12
|
+
},
|
|
13
|
+
"runtime": {
|
|
14
|
+
"supported": true,
|
|
15
|
+
"templateDir": "assets/templates/single_table_jump",
|
|
16
|
+
"files": {
|
|
17
|
+
"api": "api.tpl",
|
|
18
|
+
"options": "options.tpl",
|
|
19
|
+
"form": "form.tpl",
|
|
20
|
+
"list": "index.tpl",
|
|
21
|
+
"menuSql": "menu.sql.tpl"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"single_table_dialog": {
|
|
26
|
+
"id": "single_table_dialog",
|
|
27
|
+
"label": "单表弹窗模板",
|
|
28
|
+
"description": "单表弹窗页面,使用弹窗 form 和通用表格模板。",
|
|
29
|
+
"templateFiles": {
|
|
30
|
+
"api": "api.md",
|
|
31
|
+
"options": "options.md",
|
|
32
|
+
"form": "表单V2025001.md",
|
|
33
|
+
"list": "表格V2025001.md",
|
|
34
|
+
"menuSql": "权限菜单V2025001.md"
|
|
35
|
+
},
|
|
36
|
+
"runtime": {
|
|
37
|
+
"supported": true,
|
|
38
|
+
"templateDir": "assets/templates/single_table_dialog",
|
|
39
|
+
"files": {
|
|
40
|
+
"api": "api.tpl",
|
|
41
|
+
"options": "options.tpl",
|
|
42
|
+
"form": "form.tpl",
|
|
43
|
+
"list": "index.tpl",
|
|
44
|
+
"menuSql": "menu.sql.tpl"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"master_child_jump": {
|
|
49
|
+
"id": "master_child_jump",
|
|
50
|
+
"label": "主子表前端跳转类型",
|
|
51
|
+
"description": "主子表跳转页面,使用主子表 form 和主子表列表模板。",
|
|
52
|
+
"templateFiles": {
|
|
53
|
+
"api": "api.md",
|
|
54
|
+
"options": "options.md",
|
|
55
|
+
"form": "主子表单跳转方式.md",
|
|
56
|
+
"list": "主子表单跳转列表.md",
|
|
57
|
+
"menuSql": "权限菜单V2025001.md"
|
|
58
|
+
},
|
|
59
|
+
"runtime": {
|
|
60
|
+
"supported": true,
|
|
61
|
+
"templateDir": "assets/templates/master_child_jump",
|
|
62
|
+
"files": {
|
|
63
|
+
"api": "api.tpl",
|
|
64
|
+
"options": "options.tpl",
|
|
65
|
+
"form": "form.tpl",
|
|
66
|
+
"list": "index.tpl",
|
|
67
|
+
"menuSql": "menu.sql.tpl"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import request from '/@/utils/request';
|
|
2
|
+
|
|
3
|
+
export function fetchList(query?: object) {
|
|
4
|
+
return request({
|
|
5
|
+
url: '/{{API_PATH}}/page',
|
|
6
|
+
method: 'get',
|
|
7
|
+
params: query,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function addObj(obj?: object) {
|
|
12
|
+
return request({
|
|
13
|
+
url: '/{{API_PATH}}',
|
|
14
|
+
method: 'post',
|
|
15
|
+
data: obj,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getObj(obj?: object) {
|
|
20
|
+
return request({
|
|
21
|
+
url: '/{{API_PATH}}/details',
|
|
22
|
+
method: 'get',
|
|
23
|
+
params: obj,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function delObjs(ids?: object) {
|
|
28
|
+
return request({
|
|
29
|
+
url: '/{{API_PATH}}',
|
|
30
|
+
method: 'delete',
|
|
31
|
+
data: ids,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function putObj(obj?: object) {
|
|
36
|
+
return request({
|
|
37
|
+
url: '/{{API_PATH}}',
|
|
38
|
+
method: 'put',
|
|
39
|
+
data: obj,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function delChildObj(ids?: object) {
|
|
44
|
+
return request({
|
|
45
|
+
url: '/{{API_PATH}}/child',
|
|
46
|
+
method: 'delete',
|
|
47
|
+
data: ids,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="layout-padding-auto layout-padding-view">
|
|
3
|
+
<el-card shadow="never">
|
|
4
|
+
<template #header>
|
|
5
|
+
<span>{{ form.{{PK_ATTR}} ? (detail ? 'Detail' : 'Edit') : 'Add' }}</span>
|
|
6
|
+
</template>
|
|
7
|
+
<el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
|
|
8
|
+
<el-row :gutter="24">
|
|
9
|
+
{{FORM_FIELDS}}
|
|
10
|
+
</el-row>
|
|
11
|
+
<el-row :gutter="24">
|
|
12
|
+
<sc-form-table v-model="form.{{CHILD_LIST_NAME}}" :addTemplate="childTemp" @delete="deleteChild" placeholder="No data">
|
|
13
|
+
{{CHILD_TABLE_COLUMNS}}
|
|
14
|
+
</sc-form-table>
|
|
15
|
+
</el-row>
|
|
16
|
+
</el-form>
|
|
17
|
+
<div class="dialog-footer" style="text-align: right; margin-top: 18px;">
|
|
18
|
+
<el-button @click="handleBack">Cancel</el-button>
|
|
19
|
+
<el-button type="primary" @click="onSubmit" :disabled="loading">Confirm</el-button>
|
|
20
|
+
</div>
|
|
21
|
+
</el-card>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts" name="{{CLASS_NAME}}Form">
|
|
26
|
+
import mittBus from '/@/utils/mitt';
|
|
27
|
+
import { useMessage } from '/@/hooks/message';
|
|
28
|
+
import { getObj, addObj, putObj, delChildObj } from '/@/api/{{API_MODULE_PATH}}';
|
|
29
|
+
{{DICT_IMPORT_BLOCK}}
|
|
30
|
+
|
|
31
|
+
const scFormTable = defineAsyncComponent(() => import('/@/components/FormTable/index.vue'));
|
|
32
|
+
const route = useRoute();
|
|
33
|
+
const router = useRouter();
|
|
34
|
+
|
|
35
|
+
const dataFormRef = ref();
|
|
36
|
+
const loading = ref(false);
|
|
37
|
+
const detail = ref(false);
|
|
38
|
+
|
|
39
|
+
const form = reactive({
|
|
40
|
+
{{FORM_DEFAULTS}}
|
|
41
|
+
{{CHILD_LIST_NAME}}: [],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const childTemp = reactive({
|
|
45
|
+
{{CHILD_TEMP_DEFAULTS}}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const dataRules = ref({
|
|
49
|
+
{{FORM_RULES}}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const get{{CLASS_NAME}}Data = async (id: string) => {
|
|
53
|
+
try {
|
|
54
|
+
loading.value = true;
|
|
55
|
+
const { data } = await getObj({ {{PK_ATTR}}: id });
|
|
56
|
+
Object.assign(form, data[0] || {});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
useMessage().error('Failed to fetch data');
|
|
59
|
+
} finally {
|
|
60
|
+
loading.value = false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const resetFormState = () => {
|
|
65
|
+
Object.assign(form, {
|
|
66
|
+
{{FORM_DEFAULTS}}
|
|
67
|
+
{{CHILD_LIST_NAME}}: [],
|
|
68
|
+
});
|
|
69
|
+
nextTick(() => {
|
|
70
|
+
dataFormRef.value?.resetFields();
|
|
71
|
+
form.{{CHILD_LIST_NAME}} = [];
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const initPage = async () => {
|
|
76
|
+
const id = route.query.id as string;
|
|
77
|
+
const isDetail = route.query.detail === 'true' || route.query.detail === '1';
|
|
78
|
+
|
|
79
|
+
detail.value = isDetail;
|
|
80
|
+
resetFormState();
|
|
81
|
+
|
|
82
|
+
if (id) {
|
|
83
|
+
form.{{PK_ATTR}} = id;
|
|
84
|
+
await get{{CLASS_NAME}}Data(id);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const closeCurrentPage = () => {
|
|
89
|
+
mittBus.emit('onCurrentContextmenuClick', { contextMenuClickId: 1, ...route });
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleBack = () => {
|
|
93
|
+
router.back();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const onSubmit = async () => {
|
|
97
|
+
loading.value = true;
|
|
98
|
+
|
|
99
|
+
const valid = await dataFormRef.value.validate().catch(() => {});
|
|
100
|
+
if (!valid) {
|
|
101
|
+
loading.value = false;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
|
|
107
|
+
useMessage().success(form.{{PK_ATTR}} ? 'Updated successfully' : 'Created successfully');
|
|
108
|
+
closeCurrentPage();
|
|
109
|
+
} catch (err: any) {
|
|
110
|
+
useMessage().error(err.msg || 'Submit failed');
|
|
111
|
+
} finally {
|
|
112
|
+
loading.value = false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const deleteChild = async (obj: { {{CHILD_PK_ATTR}}: string }) => {
|
|
117
|
+
if (obj.{{CHILD_PK_ATTR}}) {
|
|
118
|
+
try {
|
|
119
|
+
await delChildObj([obj.{{CHILD_PK_ATTR}}]);
|
|
120
|
+
useMessage().success('Deleted successfully');
|
|
121
|
+
} catch (err: any) {
|
|
122
|
+
useMessage().error(err.msg || 'Delete failed');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
onMounted(() => {
|
|
128
|
+
initPage();
|
|
129
|
+
});
|
|
130
|
+
</script>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="layout-padding">
|
|
3
|
+
<div class="layout-padding-auto layout-padding-view">
|
|
4
|
+
<el-row>
|
|
5
|
+
<div class="mb8" style="width: 100%">
|
|
6
|
+
<el-button icon="folder-add" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="handleCreate">新增</el-button>
|
|
7
|
+
<el-button plain icon="upload-filled" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="excelUploadRef.show()">导入</el-button>
|
|
8
|
+
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete(selectObjs)">删除</el-button>
|
|
9
|
+
<right-toolbar v-model:showSearch="showSearch" :export="'{{PERMISSION_PREFIX}}_export'" @exportExcel="exportExcel" @queryTable="getDataList" class="ml10 mr20" style="float: right;" />
|
|
10
|
+
</div>
|
|
11
|
+
</el-row>
|
|
12
|
+
|
|
13
|
+
<el-table
|
|
14
|
+
:data="state.dataList"
|
|
15
|
+
v-loading="state.loading"
|
|
16
|
+
border
|
|
17
|
+
:cell-style="tableStyle.cellStyle"
|
|
18
|
+
:header-cell-style="tableStyle.headerCellStyle"
|
|
19
|
+
@selection-change="selectionChangeHandle"
|
|
20
|
+
@sort-change="sortChangeHandle"
|
|
21
|
+
>
|
|
22
|
+
<el-table-column type="selection" width="40" align="center" />
|
|
23
|
+
<el-table-column type="index" label="#" width="40" />
|
|
24
|
+
{{TABLE_COLUMNS}}
|
|
25
|
+
<el-table-column label="操作" width="220">
|
|
26
|
+
<template #default="scope">
|
|
27
|
+
<el-button text type="primary" icon="view" v-auth="'{{PERMISSION_PREFIX}}_view'" @click="handleDetail(scope.row.{{PK_ATTR}})">查看</el-button>
|
|
28
|
+
<el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="handleEdit(scope.row.{{PK_ATTR}})">编辑</el-button>
|
|
29
|
+
<el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([scope.row.{{PK_ATTR}}])">删除</el-button>
|
|
30
|
+
</template>
|
|
31
|
+
</el-table-column>
|
|
32
|
+
</el-table>
|
|
33
|
+
|
|
34
|
+
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<upload-excel ref="excelUploadRef" title="导入" url="/{{API_PATH}}/import" temp-url="/admin/sys-file/local/file/{{FUNCTION_NAME}}.xlsx" @refreshDataList="getDataList" />
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup lang="ts" name="system{{CLASS_NAME}}">
|
|
42
|
+
import { BasicTableProps, useTable } from '/@/hooks/table';
|
|
43
|
+
import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
|
|
44
|
+
import { useMessage, useMessageBox } from '/@/hooks/message';
|
|
45
|
+
{{DICT_IMPORT_BLOCK}}
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
|
|
48
|
+
const excelUploadRef = ref();
|
|
49
|
+
const queryRef = ref();
|
|
50
|
+
const showSearch = ref(true);
|
|
51
|
+
const selectObjs = ref([]) as any;
|
|
52
|
+
const multiple = ref(true);
|
|
53
|
+
|
|
54
|
+
const state: BasicTableProps = reactive<BasicTableProps>({
|
|
55
|
+
queryForm: {},
|
|
56
|
+
pageList: fetchList,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
|
60
|
+
|
|
61
|
+
const getFormPath = () => '/{{VIEW_MODULE_PATH}}/form';
|
|
62
|
+
|
|
63
|
+
const handleCreate = () => {
|
|
64
|
+
router.push({ path: getFormPath(), query: { tagsViewName: '新增' } });
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleDetail = (id: string) => {
|
|
68
|
+
router.push({ path: getFormPath(), query: { id, detail: '1', tagsViewName: '查看' } });
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const handleEdit = (id: string) => {
|
|
72
|
+
router.push({ path: getFormPath(), query: { id, tagsViewName: '编辑' } });
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const exportExcel = () => {
|
|
76
|
+
downBlobFile('/{{API_PATH}}/export', { ...state.queryForm, ids: selectObjs.value }, '{{FUNCTION_NAME}}.xlsx');
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const selectionChangeHandle = (objs: { {{PK_ATTR}}: string }[]) => {
|
|
80
|
+
selectObjs.value = objs.map((item) => item.{{PK_ATTR}});
|
|
81
|
+
multiple.value = !objs.length;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleDelete = async (ids: string[]) => {
|
|
85
|
+
try {
|
|
86
|
+
await useMessageBox().confirm('此操作将永久删除');
|
|
87
|
+
} catch {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await delObjs(ids);
|
|
93
|
+
getDataList();
|
|
94
|
+
useMessage().success('删除成功');
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
useMessage().error(err.msg || '删除失败');
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
</script>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- Do not execute directly. Review parent menu id and tenant settings before import.
|
|
2
|
+
-- Suggested default parent_id: -1
|
|
3
|
+
-- Generated by worsoft-codegen-local on {{GENERATED_AT}}
|
|
4
|
+
|
|
5
|
+
insert into sys_menu (menu_id, parent_id, path, permission, menu_type, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
|
|
6
|
+
values ({{MENU_BASE_ID}}, '-1', '/{{MENU_ROUTE_PATH}}/index', '', '0', 'icon-bangzhushouji', '0', null, '8', null, '{{TABLE_COMMENT}}管理', 1);
|
|
7
|
+
|
|
8
|
+
insert into sys_menu (menu_id, parent_id, permission, menu_type, path, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
|
|
9
|
+
values ({{MENU_BASE_ID_PLUS_1}}, {{MENU_BASE_ID}}, '{{PERMISSION_PREFIX}}_view', '1', null, '1', '0', null, '0', null, '{{TABLE_COMMENT}}查看', 1);
|
|
10
|
+
|
|
11
|
+
insert into sys_menu (menu_id, parent_id, permission, menu_type, path, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
|
|
12
|
+
values ({{MENU_BASE_ID_PLUS_2}}, {{MENU_BASE_ID}}, '{{PERMISSION_PREFIX}}_add', '1', null, '1', '0', null, '1', null, '{{TABLE_COMMENT}}新增', 1);
|
|
13
|
+
|
|
14
|
+
insert into sys_menu (menu_id, parent_id, permission, menu_type, path, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
|
|
15
|
+
values ({{MENU_BASE_ID_PLUS_3}}, {{MENU_BASE_ID}}, '{{PERMISSION_PREFIX}}_edit', '1', null, '1', '0', null, '2', null, '{{TABLE_COMMENT}}修改', 1);
|
|
16
|
+
|
|
17
|
+
insert into sys_menu (menu_id, parent_id, permission, menu_type, path, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
|
|
18
|
+
values ({{MENU_BASE_ID_PLUS_4}}, {{MENU_BASE_ID}}, '{{PERMISSION_PREFIX}}_del', '1', null, '1', '0', null, '3', null, '{{TABLE_COMMENT}}删除', 1);
|
|
19
|
+
|
|
20
|
+
insert into sys_menu (menu_id, parent_id, permission, menu_type, path, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
|
|
21
|
+
values ({{MENU_BASE_ID_PLUS_5}}, {{MENU_BASE_ID}}, '{{PERMISSION_PREFIX}}_export', '1', null, '1', '0', null, '4', null, '导入导出', 1);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import request from '/@/utils/request';
|
|
2
|
+
|
|
3
|
+
export function fetchList(query?: object) {
|
|
4
|
+
return request({
|
|
5
|
+
url: '/{{API_PATH}}/page',
|
|
6
|
+
method: 'get',
|
|
7
|
+
params: query,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function addObj(obj?: object) {
|
|
12
|
+
return request({
|
|
13
|
+
url: '/{{API_PATH}}',
|
|
14
|
+
method: 'post',
|
|
15
|
+
data: obj,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getObj(obj?: object) {
|
|
20
|
+
return request({
|
|
21
|
+
url: '/{{API_PATH}}/details',
|
|
22
|
+
method: 'get',
|
|
23
|
+
params: obj,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function delObjs(ids?: object) {
|
|
28
|
+
return request({
|
|
29
|
+
url: '/{{API_PATH}}',
|
|
30
|
+
method: 'delete',
|
|
31
|
+
data: ids,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function putObj(obj?: object) {
|
|
36
|
+
return request({
|
|
37
|
+
url: '/{{API_PATH}}',
|
|
38
|
+
method: 'put',
|
|
39
|
+
data: obj,
|
|
40
|
+
});
|
|
41
|
+
}
|