worsoft-frontend-codegen-local-mcp 0.1.5 → 0.1.6
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/assets/templates/master_child_jump/form.tpl +46 -14
- package/assets/templates/master_child_jump/index.tpl +69 -38
- package/assets/templates/master_child_jump/options.tpl +1 -9
- package/assets/templates/single_table_dialog/form.tpl +21 -8
- package/assets/templates/single_table_dialog/index.tpl +23 -14
- package/assets/templates/single_table_dialog/options.tpl +1 -9
- package/assets/templates/single_table_jump/form.tpl +21 -8
- package/assets/templates/single_table_jump/index.tpl +27 -18
- package/assets/templates/single_table_jump/options.tpl +1 -9
- package/mcp_server.js +236 -32
- package/package.json +1 -1
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="layout-padding-auto layout-padding-view">
|
|
3
|
-
<el-card shadow="never">
|
|
3
|
+
<el-card shadow="never" class="w100">
|
|
4
4
|
<template #header>
|
|
5
|
-
<
|
|
5
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
6
|
+
<span style="font-size: 16px; font-weight: bold;">{{ form.{{PK_ATTR}} ? (detail ? t('common.viewBtn') : t('common.editBtn')) : t('common.addBtn') }}</span>
|
|
7
|
+
<div v-if="!detail">
|
|
8
|
+
<el-button @click="onSubmit('save')" :disabled="loading" icon="document">{{ actionLabel('save') }}</el-button>
|
|
9
|
+
<el-button @click="onSubmit('flow')" :disabled="loading" plain type="success" icon="position">{{ actionLabel('flow') }}</el-button>
|
|
10
|
+
<el-button @click="onSubmit('submit')" :disabled="loading" type="primary" icon="check">{{ actionLabel('submit') }}</el-button>
|
|
11
|
+
<el-divider direction="vertical" />
|
|
12
|
+
<el-button @click="handleBack" icon="close">{{ t('common.cancelButtonText') }}</el-button>
|
|
13
|
+
</div>
|
|
14
|
+
<div v-else>
|
|
15
|
+
<el-button @click="handleBack" icon="back">{{ actionLabel('back') }}</el-button>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
6
18
|
</template>
|
|
7
19
|
<el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
|
|
8
20
|
<el-row :gutter="24">
|
|
@@ -12,10 +24,6 @@
|
|
|
12
24
|
{{CHILD_SECTIONS}}
|
|
13
25
|
</el-row>
|
|
14
26
|
</el-form>
|
|
15
|
-
<div class="dialog-footer" style="text-align: right; margin-top: 18px;">
|
|
16
|
-
<el-button @click="handleBack">取消</el-button>
|
|
17
|
-
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
|
|
18
|
-
</div>
|
|
19
27
|
</el-card>
|
|
20
28
|
</div>
|
|
21
29
|
</template>
|
|
@@ -25,13 +33,16 @@ import mittBus from '/@/utils/mitt';
|
|
|
25
33
|
import { useMessage } from '/@/hooks/message';
|
|
26
34
|
import { getObj, addObj, putObj, delChildObj } from '/@/api/{{API_MODULE_PATH}}';
|
|
27
35
|
import { useDict } from '/@/hooks/dict';
|
|
36
|
+
import { useI18n } from 'vue-i18n';
|
|
28
37
|
import { allDictTypes, childFieldGroups, dataMasterEntity } from './options';
|
|
29
38
|
|
|
30
39
|
const dictRefs = useDict(...allDictTypes);
|
|
40
|
+
const { t } = useI18n();
|
|
31
41
|
|
|
32
42
|
const scFormTable = defineAsyncComponent(() => import('/@/components/FormTable/index.vue'));
|
|
33
43
|
const route = useRoute();
|
|
34
44
|
const router = useRouter();
|
|
45
|
+
const pageI18nKey = '{{I18N_NAMESPACE}}';
|
|
35
46
|
|
|
36
47
|
const dataFormRef = ref();
|
|
37
48
|
const loading = ref(false);
|
|
@@ -45,9 +56,25 @@ const isMasterFieldVisible = (prop: string) => {
|
|
|
45
56
|
return Boolean(config) && !config.alwaysHide && config.show !== false;
|
|
46
57
|
};
|
|
47
58
|
const getChildFieldMeta = (groupName: string, prop: string) => childFieldGroups[groupName]?.[prop];
|
|
59
|
+
const resolveLabel = (labelKey?: string, fallback = '') => {
|
|
60
|
+
if (!labelKey) return fallback;
|
|
61
|
+
const translated = t(labelKey);
|
|
62
|
+
return translated === labelKey ? fallback : translated;
|
|
63
|
+
};
|
|
64
|
+
const getMasterFieldLabel = (prop: string) => {
|
|
65
|
+
const config = getMasterFieldMeta(prop);
|
|
66
|
+
return resolveLabel(config?.labelKey, config?.label || prop);
|
|
67
|
+
};
|
|
68
|
+
const getChildFieldLabel = (groupName: string, prop: string) => {
|
|
69
|
+
const config = getChildFieldMeta(groupName, prop);
|
|
70
|
+
return resolveLabel(config?.labelKey, config?.label || prop);
|
|
71
|
+
};
|
|
72
|
+
const childSectionTitle = (groupName: string) => t(`${pageI18nKey}.children.${groupName}.title`);
|
|
48
73
|
const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
|
|
49
|
-
const inputPlaceholder = (label: string) =>
|
|
50
|
-
const selectPlaceholder = (label: string) =>
|
|
74
|
+
const inputPlaceholder = (label: string) => t(`${pageI18nKey}.placeholders.input`, { label });
|
|
75
|
+
const selectPlaceholder = (label: string) => t(`${pageI18nKey}.placeholders.select`, { label });
|
|
76
|
+
const fieldRequiredMessage = (prop: string) => t(`${pageI18nKey}.messages.required`, { label: getMasterFieldLabel(prop) });
|
|
77
|
+
const actionLabel = (action: string) => t(`${pageI18nKey}.actions.${action}`);
|
|
51
78
|
|
|
52
79
|
const form = reactive({
|
|
53
80
|
{{FORM_DEFAULTS}}
|
|
@@ -66,7 +93,7 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
|
|
|
66
93
|
const { data } = await getObj({ {{PK_ATTR}}: id });
|
|
67
94
|
Object.assign(form, data[0] || {});
|
|
68
95
|
} catch (error) {
|
|
69
|
-
useMessage().error(
|
|
96
|
+
useMessage().error(t(`${pageI18nKey}.messages.fetchError`));
|
|
70
97
|
} finally {
|
|
71
98
|
loading.value = false;
|
|
72
99
|
}
|
|
@@ -104,7 +131,7 @@ const handleBack = () => {
|
|
|
104
131
|
router.back();
|
|
105
132
|
};
|
|
106
133
|
|
|
107
|
-
const onSubmit = async () => {
|
|
134
|
+
const onSubmit = async (actionType?: string) => {
|
|
108
135
|
loading.value = true;
|
|
109
136
|
|
|
110
137
|
const valid = await dataFormRef.value.validate().catch(() => {});
|
|
@@ -115,10 +142,15 @@ const onSubmit = async () => {
|
|
|
115
142
|
|
|
116
143
|
try {
|
|
117
144
|
form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
|
|
118
|
-
|
|
145
|
+
|
|
146
|
+
let msg = form.{{PK_ATTR}} ? t(`${pageI18nKey}.messages.updateSuccess`) : t(`${pageI18nKey}.messages.createSuccess`);
|
|
147
|
+
if (actionType === 'submit') msg = t(`${pageI18nKey}.messages.quickSubmitSuccess`);
|
|
148
|
+
if (actionType === 'flow') msg = t(`${pageI18nKey}.messages.quickFlowSuccess`);
|
|
149
|
+
|
|
150
|
+
useMessage().success(msg);
|
|
119
151
|
closeCurrentPage();
|
|
120
152
|
} catch (err: any) {
|
|
121
|
-
useMessage().error(err.msg ||
|
|
153
|
+
useMessage().error(err.msg || t(`${pageI18nKey}.messages.submitError`));
|
|
122
154
|
} finally {
|
|
123
155
|
loading.value = false;
|
|
124
156
|
}
|
|
@@ -128,9 +160,9 @@ const deleteChild = async (obj: Record<string, any>, childPkAttr: string, childT
|
|
|
128
160
|
if (obj[childPkAttr]) {
|
|
129
161
|
try {
|
|
130
162
|
await delChildObj([obj[childPkAttr]], childTableName);
|
|
131
|
-
useMessage().success('
|
|
163
|
+
useMessage().success(t('common.delSuccessText'));
|
|
132
164
|
} catch (err: any) {
|
|
133
|
-
useMessage().error(err.msg || '
|
|
165
|
+
useMessage().error(err.msg || t('common.delBtn'));
|
|
134
166
|
}
|
|
135
167
|
}
|
|
136
168
|
};
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="layout-padding">
|
|
3
|
-
<div class="layout-padding-auto layout-padding-view">
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
3
|
+
<div class="layout-padding-auto layout-padding-view flex-column" style="display: flex; flex-direction: column; height: 100%;">
|
|
4
|
+
<div class="mb8" style="width: 100%; flex-shrink: 0;">
|
|
5
|
+
<el-button icon="folder-add" type="primary" class="ml10" @click="handleCreate">{{ t('common.addBtn') }}</el-button>
|
|
6
|
+
<el-button plain icon="upload-filled" type="primary" class="ml10" @click="excelUploadRef.show()">{{ t('common.importBtn') }}</el-button>
|
|
7
|
+
<el-button plain :disabled="multiple" icon="Delete" type="primary" @click="handleDelete(selectObjs)">{{ t('common.delBtn') }}</el-button>
|
|
8
|
+
<right-toolbar v-model:showSearch="showSearch" :export="'{{PERMISSION_PREFIX}}_export'" @exportExcel="exportExcel" @queryTable="getDataList" class="ml10 mr20" style="float: right;" />
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div style="flex: 1; overflow: hidden; display: flex; flex-direction: column;">
|
|
12
|
+
<el-table
|
|
13
|
+
:data="state.dataList"
|
|
14
|
+
v-loading="state.loading"
|
|
15
|
+
border
|
|
16
|
+
height="100%"
|
|
17
|
+
style="width: 100%;"
|
|
18
|
+
:cell-style="tableStyle.cellStyle"
|
|
19
|
+
:header-cell-style="tableStyle.headerCellStyle"
|
|
20
|
+
@selection-change="selectionChangeHandle"
|
|
21
|
+
@sort-change="sortChangeHandle"
|
|
22
|
+
>
|
|
22
23
|
<el-table-column type="selection" width="40" align="center" />
|
|
23
|
-
<el-table-column type="index" label="
|
|
24
|
+
<el-table-column type="index" :label="t('common.serial')" width="60" />
|
|
24
25
|
<el-table-column
|
|
25
26
|
v-for="column in visibleTableColumns"
|
|
26
27
|
:key="column.prop"
|
|
27
28
|
:prop="column.prop"
|
|
28
|
-
:label="column.
|
|
29
|
-
:width="column.width"
|
|
29
|
+
:label="resolveLabel(column.labelKey, column.fallbackLabel)"
|
|
30
|
+
:min-width="column.width"
|
|
30
31
|
show-overflow-tooltip
|
|
31
32
|
>
|
|
32
33
|
<template #default="scope">
|
|
@@ -34,19 +35,24 @@
|
|
|
34
35
|
<span v-else>{{ scope.row[column.prop] }}</span>
|
|
35
36
|
</template>
|
|
36
37
|
</el-table-column>
|
|
37
|
-
<el-table-column label="
|
|
38
|
+
<el-table-column :label="t('common.action')" width="380" fixed="right">
|
|
38
39
|
<template #default="scope">
|
|
39
|
-
<el-button text type="primary" icon="view" v-auth="'{{PERMISSION_PREFIX}}_view'" @click="handleDetail(scope.row.{{
|
|
40
|
-
<el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="handleEdit(scope.row.{{
|
|
41
|
-
<el-button
|
|
40
|
+
<el-button text type="primary" icon="view" v-auth="'{{PERMISSION_PREFIX}}_view'" @click="handleDetail(scope.row.id)">{{ t('common.viewBtn') }}</el-button>
|
|
41
|
+
<el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="handleEdit(scope.row.id)">{{ t('common.editBtn') }}</el-button>
|
|
42
|
+
<el-button text type="primary" icon="check" @click="handleQuickAction(scope.row, 'submit')">{{ actionLabel('submit') }}</el-button>
|
|
43
|
+
<el-button text type="success" icon="position" @click="handleQuickAction(scope.row, 'flow')">{{ actionLabel('flow') }}</el-button>
|
|
44
|
+
<el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([scope.row.id])">{{ t('common.delBtn') }}</el-button>
|
|
42
45
|
</template>
|
|
43
46
|
</el-table-column>
|
|
44
|
-
|
|
47
|
+
</el-table>
|
|
48
|
+
</div>
|
|
45
49
|
|
|
46
|
-
<
|
|
50
|
+
<div style="flex-shrink: 0; margin-top: 10px; display: flex; justify-content: flex-end;">
|
|
51
|
+
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
|
52
|
+
</div>
|
|
47
53
|
</div>
|
|
48
54
|
|
|
49
|
-
<upload-excel ref="excelUploadRef" title="
|
|
55
|
+
<upload-excel ref="excelUploadRef" :title="t('common.importBtn')" url="/{{API_PATH}}/import" temp-url="/admin/sys-file/local/file/{{FUNCTION_NAME}}.xlsx" @refreshDataList="getDataList" />
|
|
50
56
|
</div>
|
|
51
57
|
</template>
|
|
52
58
|
|
|
@@ -55,10 +61,13 @@ import { BasicTableProps, useTable } from '/@/hooks/table';
|
|
|
55
61
|
import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
|
|
56
62
|
import { useMessage, useMessageBox } from '/@/hooks/message';
|
|
57
63
|
import { useDict } from '/@/hooks/dict';
|
|
64
|
+
import { useI18n } from 'vue-i18n';
|
|
58
65
|
import { allDictTypes, dataMasterEntity } from './options';
|
|
59
66
|
|
|
60
67
|
const dictRefs = useDict(...allDictTypes);
|
|
68
|
+
const { t } = useI18n();
|
|
61
69
|
const router = useRouter();
|
|
70
|
+
const pageI18nKey = '{{I18N_NAMESPACE}}';
|
|
62
71
|
|
|
63
72
|
const excelUploadRef = ref();
|
|
64
73
|
const showSearch = ref(true);
|
|
@@ -70,12 +79,21 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
|
|
70
79
|
pageList: fetchList,
|
|
71
80
|
});
|
|
72
81
|
|
|
82
|
+
const resolveLabel = (labelKey?: string, fallback = '') => {
|
|
83
|
+
if (!labelKey) return fallback;
|
|
84
|
+
const translated = t(labelKey);
|
|
85
|
+
return translated === labelKey ? fallback : translated;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const actionLabel = (action: string) => t(`${pageI18nKey}.actions.${action}`);
|
|
89
|
+
|
|
73
90
|
const visibleTableColumns = computed(() =>
|
|
74
91
|
Object.entries(dataMasterEntity)
|
|
75
92
|
.filter(([, config]) => !config.alwaysHide && config.show !== false)
|
|
76
93
|
.map(([prop, config]) => ({
|
|
77
94
|
prop,
|
|
78
|
-
|
|
95
|
+
labelKey: config.labelKey,
|
|
96
|
+
fallbackLabel: config.label || prop,
|
|
79
97
|
width: config.width,
|
|
80
98
|
dictType: config.dictType || '',
|
|
81
99
|
}))
|
|
@@ -88,29 +106,42 @@ const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, do
|
|
|
88
106
|
const getFormPath = () => '/{{VIEW_MODULE_PATH}}/form';
|
|
89
107
|
|
|
90
108
|
const handleCreate = () => {
|
|
91
|
-
router.push({ path: getFormPath(), query: { tagsViewName: '
|
|
109
|
+
router.push({ path: getFormPath(), query: { tagsViewName: t('common.addBtn') } });
|
|
92
110
|
};
|
|
93
111
|
|
|
94
112
|
const handleDetail = (id: string) => {
|
|
95
|
-
router.push({ path: getFormPath(), query: { id, detail: '1', tagsViewName: '
|
|
113
|
+
router.push({ path: getFormPath(), query: { id, detail: '1', tagsViewName: t('common.viewBtn') } });
|
|
96
114
|
};
|
|
97
115
|
|
|
98
116
|
const handleEdit = (id: string) => {
|
|
99
|
-
router.push({ path: getFormPath(), query: { id, tagsViewName: '
|
|
117
|
+
router.push({ path: getFormPath(), query: { id, tagsViewName: t('common.editBtn') } });
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const handleQuickAction = async (row: any, actionType: string) => {
|
|
121
|
+
const actionName = actionLabel(actionType);
|
|
122
|
+
try {
|
|
123
|
+
await useMessageBox().confirm(t(`${pageI18nKey}.messages.quickActionConfirm`, { action: actionName }));
|
|
124
|
+
useMessage().success(
|
|
125
|
+
actionType === 'submit'
|
|
126
|
+
? t(`${pageI18nKey}.messages.quickSubmitSuccess`)
|
|
127
|
+
: t(`${pageI18nKey}.messages.quickFlowSuccess`)
|
|
128
|
+
);
|
|
129
|
+
getDataList();
|
|
130
|
+
} catch {}
|
|
100
131
|
};
|
|
101
132
|
|
|
102
133
|
const exportExcel = () => {
|
|
103
134
|
downBlobFile('/{{API_PATH}}/export', { ...state.queryForm, ids: selectObjs.value }, '{{FUNCTION_NAME}}.xlsx');
|
|
104
135
|
};
|
|
105
136
|
|
|
106
|
-
const selectionChangeHandle = (objs: {
|
|
107
|
-
selectObjs.value = objs.map((item) => item.
|
|
137
|
+
const selectionChangeHandle = (objs: { id: string }[]) => {
|
|
138
|
+
selectObjs.value = objs.map((item) => item.id);
|
|
108
139
|
multiple.value = !objs.length;
|
|
109
140
|
};
|
|
110
141
|
|
|
111
142
|
const handleDelete = async (ids: string[]) => {
|
|
112
143
|
try {
|
|
113
|
-
await useMessageBox().confirm('
|
|
144
|
+
await useMessageBox().confirm(t('common.delConfirmText'));
|
|
114
145
|
} catch {
|
|
115
146
|
return;
|
|
116
147
|
}
|
|
@@ -118,9 +149,9 @@ const handleDelete = async (ids: string[]) => {
|
|
|
118
149
|
try {
|
|
119
150
|
await delObjs(ids);
|
|
120
151
|
getDataList();
|
|
121
|
-
useMessage().success('
|
|
152
|
+
useMessage().success(t('common.delSuccessText'));
|
|
122
153
|
} catch (err: any) {
|
|
123
|
-
useMessage().error(err.msg || '
|
|
154
|
+
useMessage().error(err.msg || t('common.delBtn'));
|
|
124
155
|
}
|
|
125
156
|
};
|
|
126
157
|
</script>
|
|
@@ -4,29 +4,21 @@ import type { CrudSchemaDefinition } from '/@/utils/crudSchema';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* {{TABLE_NAME}} 页面字段声明。
|
|
7
|
-
*
|
|
7
|
+
* 这里只维护字段 key、双语 key、字典类型和显示元数据。
|
|
8
8
|
*/
|
|
9
9
|
const definition: CrudSchemaDefinition = {
|
|
10
|
-
// 主表字段
|
|
11
10
|
master: [
|
|
12
11
|
{{MASTER_OPTION_FIELDS}}
|
|
13
12
|
],
|
|
14
|
-
// 子表字段,key 对应 form.vue 中的列表字段名
|
|
15
13
|
children: {
|
|
16
14
|
{{CHILD_OPTION_GROUPS}}
|
|
17
15
|
},
|
|
18
16
|
};
|
|
19
17
|
|
|
20
|
-
// 统一派生页面所需配置,避免在 options 文件里重复手写
|
|
21
18
|
const schema = createCrudSchema(definition);
|
|
22
19
|
|
|
23
|
-
// 主表字段配置:列表页、主表表单共用
|
|
24
20
|
export const dataMasterEntity = schema.master;
|
|
25
|
-
// 子表字段配置:主子表 form 页使用
|
|
26
21
|
export const childFieldGroups = schema.children;
|
|
27
|
-
// 主表字典筛选配置
|
|
28
22
|
export const filterTypes = schema.filterTypes;
|
|
29
|
-
// 子表字典筛选配置
|
|
30
23
|
export const childFilterTypes = schema.childFilterTypes;
|
|
31
|
-
// 当前功能涉及的全部字典编码
|
|
32
24
|
export const allDictTypes = schema.allDictTypes;
|
|
@@ -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}} ? t('common.editBtn') : t('common.addBtn')" :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">{{ t('common.cancelButtonText') }}</el-button>
|
|
11
|
+
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ t('common.confirmButtonText') }}</el-button>
|
|
12
12
|
</span>
|
|
13
13
|
</template>
|
|
14
14
|
</el-dialog>
|
|
@@ -18,9 +18,12 @@
|
|
|
18
18
|
import { useMessage } from '/@/hooks/message';
|
|
19
19
|
import { getObj, addObj, putObj } from '/@/api/{{API_MODULE_PATH}}';
|
|
20
20
|
import { useDict } from '/@/hooks/dict';
|
|
21
|
+
import { useI18n } from 'vue-i18n';
|
|
21
22
|
import { allDictTypes, dataMasterEntity } from './options';
|
|
22
23
|
|
|
23
24
|
const dictRefs = useDict(...allDictTypes);
|
|
25
|
+
const { t } = useI18n();
|
|
26
|
+
const pageI18nKey = '{{I18N_NAMESPACE}}';
|
|
24
27
|
const emit = defineEmits(['refresh']);
|
|
25
28
|
|
|
26
29
|
const dataFormRef = ref();
|
|
@@ -34,9 +37,19 @@ const isMasterFieldVisible = (prop: string) => {
|
|
|
34
37
|
const config = getMasterFieldMeta(prop);
|
|
35
38
|
return Boolean(config) && !config.alwaysHide && config.show !== false;
|
|
36
39
|
};
|
|
40
|
+
const resolveLabel = (labelKey?: string, fallback = '') => {
|
|
41
|
+
if (!labelKey) return fallback;
|
|
42
|
+
const translated = t(labelKey);
|
|
43
|
+
return translated === labelKey ? fallback : translated;
|
|
44
|
+
};
|
|
45
|
+
const getMasterFieldLabel = (prop: string) => {
|
|
46
|
+
const config = getMasterFieldMeta(prop);
|
|
47
|
+
return resolveLabel(config?.labelKey, config?.label || prop);
|
|
48
|
+
};
|
|
37
49
|
const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
|
|
38
|
-
const inputPlaceholder = (label: string) =>
|
|
39
|
-
const selectPlaceholder = (label: string) =>
|
|
50
|
+
const inputPlaceholder = (label: string) => t(`${pageI18nKey}.placeholders.input`, { label });
|
|
51
|
+
const selectPlaceholder = (label: string) => t(`${pageI18nKey}.placeholders.select`, { label });
|
|
52
|
+
const fieldRequiredMessage = (prop: string) => t(`${pageI18nKey}.messages.required`, { label: getMasterFieldLabel(prop) });
|
|
40
53
|
|
|
41
54
|
const form = reactive({
|
|
42
55
|
{{FORM_DEFAULTS}}
|
|
@@ -52,7 +65,7 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
|
|
|
52
65
|
const { data } = await getObj({ {{PK_ATTR}}: id });
|
|
53
66
|
Object.assign(form, data[0] || {});
|
|
54
67
|
} catch (error) {
|
|
55
|
-
useMessage().error(
|
|
68
|
+
useMessage().error(t(`${pageI18nKey}.messages.fetchError`));
|
|
56
69
|
} finally {
|
|
57
70
|
loading.value = false;
|
|
58
71
|
}
|
|
@@ -88,11 +101,11 @@ const onSubmit = async () => {
|
|
|
88
101
|
|
|
89
102
|
try {
|
|
90
103
|
form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
|
|
91
|
-
useMessage().success(form.{{PK_ATTR}} ?
|
|
104
|
+
useMessage().success(form.{{PK_ATTR}} ? t(`${pageI18nKey}.messages.updateSuccess`) : t(`${pageI18nKey}.messages.createSuccess`));
|
|
92
105
|
visible.value = false;
|
|
93
106
|
emit('refresh');
|
|
94
107
|
} catch (err: any) {
|
|
95
|
-
useMessage().error(err.msg ||
|
|
108
|
+
useMessage().error(err.msg || t(`${pageI18nKey}.messages.submitError`));
|
|
96
109
|
} finally {
|
|
97
110
|
loading.value = false;
|
|
98
111
|
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
<div class="layout-padding-auto layout-padding-view">
|
|
4
4
|
<el-row>
|
|
5
5
|
<div class="mb8" style="width: 100%">
|
|
6
|
-
<el-button icon="folder-add" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="formDialogRef.openDialog()"
|
|
7
|
-
<el-button plain icon="upload-filled" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="excelUploadRef.show()"
|
|
8
|
-
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete(selectObjs)"
|
|
6
|
+
<el-button icon="folder-add" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="formDialogRef.openDialog()">{{ t('common.addBtn') }}</el-button>
|
|
7
|
+
<el-button plain icon="upload-filled" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="excelUploadRef.show()">{{ t('common.importBtn') }}</el-button>
|
|
8
|
+
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete(selectObjs)">{{ t('common.delBtn') }}</el-button>
|
|
9
9
|
<right-toolbar v-model:showSearch="showSearch" :export="'{{PERMISSION_PREFIX}}_export'" @exportExcel="exportExcel" @queryTable="getDataList" class="ml10 mr20" style="float: right;" />
|
|
10
10
|
</div>
|
|
11
11
|
</el-row>
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
@sort-change="sortChangeHandle"
|
|
21
21
|
>
|
|
22
22
|
<el-table-column type="selection" width="40" align="center" />
|
|
23
|
-
<el-table-column type="index" label="
|
|
23
|
+
<el-table-column type="index" :label="t('common.serial')" width="60" />
|
|
24
24
|
<el-table-column
|
|
25
25
|
v-for="column in visibleTableColumns"
|
|
26
26
|
:key="column.prop"
|
|
27
27
|
:prop="column.prop"
|
|
28
|
-
:label="column.
|
|
29
|
-
:width="column.width"
|
|
28
|
+
:label="resolveLabel(column.labelKey, column.fallbackLabel)"
|
|
29
|
+
:min-width="column.width"
|
|
30
30
|
show-overflow-tooltip
|
|
31
31
|
>
|
|
32
32
|
<template #default="scope">
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
<span v-else>{{ scope.row[column.prop] }}</span>
|
|
35
35
|
</template>
|
|
36
36
|
</el-table-column>
|
|
37
|
-
<el-table-column label="
|
|
37
|
+
<el-table-column :label="t('common.action')" width="180">
|
|
38
38
|
<template #default="scope">
|
|
39
|
-
<el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="formDialogRef.openDialog(scope.row.{{PK_ATTR}})"
|
|
40
|
-
<el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([scope.row.{{PK_ATTR}}])"
|
|
39
|
+
<el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="formDialogRef.openDialog(scope.row.{{PK_ATTR}})">{{ t('common.editBtn') }}</el-button>
|
|
40
|
+
<el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([scope.row.{{PK_ATTR}}])">{{ t('common.delBtn') }}</el-button>
|
|
41
41
|
</template>
|
|
42
42
|
</el-table-column>
|
|
43
43
|
</el-table>
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
48
|
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
|
|
49
|
-
<upload-excel ref="excelUploadRef" title="
|
|
49
|
+
<upload-excel ref="excelUploadRef" :title="t('common.importBtn')" url="/{{API_PATH}}/import" temp-url="/admin/sys-file/local/file/{{FUNCTION_NAME}}.xlsx" @refreshDataList="getDataList" />
|
|
50
50
|
</div>
|
|
51
51
|
</template>
|
|
52
52
|
|
|
@@ -55,9 +55,11 @@ import { BasicTableProps, useTable } from '/@/hooks/table';
|
|
|
55
55
|
import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
|
|
56
56
|
import { useMessage, useMessageBox } from '/@/hooks/message';
|
|
57
57
|
import { useDict } from '/@/hooks/dict';
|
|
58
|
+
import { useI18n } from 'vue-i18n';
|
|
58
59
|
import { allDictTypes, dataMasterEntity } from './options';
|
|
59
60
|
|
|
60
61
|
const dictRefs = useDict(...allDictTypes);
|
|
62
|
+
const { t } = useI18n();
|
|
61
63
|
|
|
62
64
|
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
|
63
65
|
|
|
@@ -72,12 +74,19 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
|
|
72
74
|
pageList: fetchList,
|
|
73
75
|
});
|
|
74
76
|
|
|
77
|
+
const resolveLabel = (labelKey?: string, fallback = '') => {
|
|
78
|
+
if (!labelKey) return fallback;
|
|
79
|
+
const translated = t(labelKey);
|
|
80
|
+
return translated === labelKey ? fallback : translated;
|
|
81
|
+
};
|
|
82
|
+
|
|
75
83
|
const visibleTableColumns = computed(() =>
|
|
76
84
|
Object.entries(dataMasterEntity)
|
|
77
85
|
.filter(([, config]) => !config.alwaysHide && config.show !== false)
|
|
78
86
|
.map(([prop, config]) => ({
|
|
79
87
|
prop,
|
|
80
|
-
|
|
88
|
+
labelKey: config.labelKey,
|
|
89
|
+
fallbackLabel: config.label || prop,
|
|
81
90
|
width: config.width,
|
|
82
91
|
dictType: config.dictType || '',
|
|
83
92
|
}))
|
|
@@ -98,7 +107,7 @@ const selectionChangeHandle = (objs: { {{PK_ATTR}}: string }[]) => {
|
|
|
98
107
|
|
|
99
108
|
const handleDelete = async (ids: string[]) => {
|
|
100
109
|
try {
|
|
101
|
-
await useMessageBox().confirm('
|
|
110
|
+
await useMessageBox().confirm(t('common.delConfirmText'));
|
|
102
111
|
} catch {
|
|
103
112
|
return;
|
|
104
113
|
}
|
|
@@ -106,9 +115,9 @@ const handleDelete = async (ids: string[]) => {
|
|
|
106
115
|
try {
|
|
107
116
|
await delObjs(ids);
|
|
108
117
|
getDataList();
|
|
109
|
-
useMessage().success('
|
|
118
|
+
useMessage().success(t('common.delSuccessText'));
|
|
110
119
|
} catch (err: any) {
|
|
111
|
-
useMessage().error(err.msg || '
|
|
120
|
+
useMessage().error(err.msg || t('common.delBtn'));
|
|
112
121
|
}
|
|
113
122
|
};
|
|
114
123
|
</script>
|
|
@@ -4,29 +4,21 @@ import type { CrudSchemaDefinition } from '/@/utils/crudSchema';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* {{TABLE_NAME}} 页面字段声明。
|
|
7
|
-
*
|
|
7
|
+
* 这里只维护字段 key、双语 key、字典类型和显示元数据。
|
|
8
8
|
*/
|
|
9
9
|
const definition: CrudSchemaDefinition = {
|
|
10
|
-
// 主表字段
|
|
11
10
|
master: [
|
|
12
11
|
{{MASTER_OPTION_FIELDS}}
|
|
13
12
|
],
|
|
14
|
-
// 子表字段,key 对应 form.vue 中的列表字段名
|
|
15
13
|
children: {
|
|
16
14
|
{{CHILD_OPTION_GROUPS}}
|
|
17
15
|
},
|
|
18
16
|
};
|
|
19
17
|
|
|
20
|
-
// 统一派生页面所需配置,避免在 options 文件里重复手写
|
|
21
18
|
const schema = createCrudSchema(definition);
|
|
22
19
|
|
|
23
|
-
// 主表字段配置:列表页、主表表单共用
|
|
24
20
|
export const dataMasterEntity = schema.master;
|
|
25
|
-
// 子表字段配置:主子表 form 页使用
|
|
26
21
|
export const childFieldGroups = schema.children;
|
|
27
|
-
// 主表字典筛选配置
|
|
28
22
|
export const filterTypes = schema.filterTypes;
|
|
29
|
-
// 子表字典筛选配置
|
|
30
23
|
export const childFilterTypes = schema.childFilterTypes;
|
|
31
|
-
// 当前功能涉及的全部字典编码
|
|
32
24
|
export const allDictTypes = schema.allDictTypes;
|
|
@@ -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 ? t('common.viewBtn') : t('common.editBtn')) : t('common.addBtn') }}</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">{{ t('common.cancelButtonText') }}</el-button>
|
|
14
|
+
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ t('common.confirmButtonText') }}</el-button>
|
|
15
15
|
</div>
|
|
16
16
|
</el-card>
|
|
17
17
|
</div>
|
|
@@ -22,11 +22,14 @@ import mittBus from '/@/utils/mitt';
|
|
|
22
22
|
import { useMessage } from '/@/hooks/message';
|
|
23
23
|
import { getObj, addObj, putObj } from '/@/api/{{API_MODULE_PATH}}';
|
|
24
24
|
import { useDict } from '/@/hooks/dict';
|
|
25
|
+
import { useI18n } from 'vue-i18n';
|
|
25
26
|
import { allDictTypes, dataMasterEntity } from './options';
|
|
26
27
|
|
|
27
28
|
const dictRefs = useDict(...allDictTypes);
|
|
29
|
+
const { t } = useI18n();
|
|
28
30
|
const route = useRoute();
|
|
29
31
|
const router = useRouter();
|
|
32
|
+
const pageI18nKey = '{{I18N_NAMESPACE}}';
|
|
30
33
|
|
|
31
34
|
const dataFormRef = ref();
|
|
32
35
|
const loading = ref(false);
|
|
@@ -39,9 +42,19 @@ const isMasterFieldVisible = (prop: string) => {
|
|
|
39
42
|
const config = getMasterFieldMeta(prop);
|
|
40
43
|
return Boolean(config) && !config.alwaysHide && config.show !== false;
|
|
41
44
|
};
|
|
45
|
+
const resolveLabel = (labelKey?: string, fallback = '') => {
|
|
46
|
+
if (!labelKey) return fallback;
|
|
47
|
+
const translated = t(labelKey);
|
|
48
|
+
return translated === labelKey ? fallback : translated;
|
|
49
|
+
};
|
|
50
|
+
const getMasterFieldLabel = (prop: string) => {
|
|
51
|
+
const config = getMasterFieldMeta(prop);
|
|
52
|
+
return resolveLabel(config?.labelKey, config?.label || prop);
|
|
53
|
+
};
|
|
42
54
|
const getDictOptions = (dictType?: string) => (dictType ? dictRefs[dictType]?.value || [] : []);
|
|
43
|
-
const inputPlaceholder = (label: string) =>
|
|
44
|
-
const selectPlaceholder = (label: string) =>
|
|
55
|
+
const inputPlaceholder = (label: string) => t(`${pageI18nKey}.placeholders.input`, { label });
|
|
56
|
+
const selectPlaceholder = (label: string) => t(`${pageI18nKey}.placeholders.select`, { label });
|
|
57
|
+
const fieldRequiredMessage = (prop: string) => t(`${pageI18nKey}.messages.required`, { label: getMasterFieldLabel(prop) });
|
|
45
58
|
|
|
46
59
|
const form = reactive({
|
|
47
60
|
{{FORM_DEFAULTS}}
|
|
@@ -57,7 +70,7 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
|
|
|
57
70
|
const { data } = await getObj({ {{PK_ATTR}}: id });
|
|
58
71
|
Object.assign(form, data[0] || {});
|
|
59
72
|
} catch (error) {
|
|
60
|
-
useMessage().error(
|
|
73
|
+
useMessage().error(t(`${pageI18nKey}.messages.fetchError`));
|
|
61
74
|
} finally {
|
|
62
75
|
loading.value = false;
|
|
63
76
|
}
|
|
@@ -104,10 +117,10 @@ const onSubmit = async () => {
|
|
|
104
117
|
|
|
105
118
|
try {
|
|
106
119
|
form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
|
|
107
|
-
useMessage().success(form.{{PK_ATTR}} ?
|
|
120
|
+
useMessage().success(form.{{PK_ATTR}} ? t(`${pageI18nKey}.messages.updateSuccess`) : t(`${pageI18nKey}.messages.createSuccess`));
|
|
108
121
|
closeCurrentPage();
|
|
109
122
|
} catch (err: any) {
|
|
110
|
-
useMessage().error(err.msg ||
|
|
123
|
+
useMessage().error(err.msg || t(`${pageI18nKey}.messages.submitError`));
|
|
111
124
|
} finally {
|
|
112
125
|
loading.value = false;
|
|
113
126
|
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
<div class="layout-padding-auto layout-padding-view">
|
|
4
4
|
<el-row>
|
|
5
5
|
<div class="mb8" style="width: 100%">
|
|
6
|
-
<el-button icon="folder-add" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="handleCreate"
|
|
7
|
-
<el-button plain icon="upload-filled" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="excelUploadRef.show()"
|
|
8
|
-
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete(selectObjs)"
|
|
6
|
+
<el-button icon="folder-add" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="handleCreate">{{ t('common.addBtn') }}</el-button>
|
|
7
|
+
<el-button plain icon="upload-filled" type="primary" class="ml10" v-auth="'{{PERMISSION_PREFIX}}_add'" @click="excelUploadRef.show()">{{ t('common.importBtn') }}</el-button>
|
|
8
|
+
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete(selectObjs)">{{ t('common.delBtn') }}</el-button>
|
|
9
9
|
<right-toolbar v-model:showSearch="showSearch" :export="'{{PERMISSION_PREFIX}}_export'" @exportExcel="exportExcel" @queryTable="getDataList" class="ml10 mr20" style="float: right;" />
|
|
10
10
|
</div>
|
|
11
11
|
</el-row>
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
@sort-change="sortChangeHandle"
|
|
21
21
|
>
|
|
22
22
|
<el-table-column type="selection" width="40" align="center" />
|
|
23
|
-
<el-table-column type="index" label="
|
|
23
|
+
<el-table-column type="index" :label="t('common.serial')" width="60" />
|
|
24
24
|
<el-table-column
|
|
25
25
|
v-for="column in visibleTableColumns"
|
|
26
26
|
:key="column.prop"
|
|
27
27
|
:prop="column.prop"
|
|
28
|
-
:label="column.
|
|
29
|
-
:width="column.width"
|
|
28
|
+
:label="resolveLabel(column.labelKey, column.fallbackLabel)"
|
|
29
|
+
:min-width="column.width"
|
|
30
30
|
show-overflow-tooltip
|
|
31
31
|
>
|
|
32
32
|
<template #default="scope">
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
<span v-else>{{ scope.row[column.prop] }}</span>
|
|
35
35
|
</template>
|
|
36
36
|
</el-table-column>
|
|
37
|
-
<el-table-column label="
|
|
37
|
+
<el-table-column :label="t('common.action')" width="220">
|
|
38
38
|
<template #default="scope">
|
|
39
|
-
<el-button text type="primary" icon="view" v-auth="'{{PERMISSION_PREFIX}}_view'" @click="handleDetail(scope.row.{{PK_ATTR}})"
|
|
40
|
-
<el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="handleEdit(scope.row.{{PK_ATTR}})"
|
|
41
|
-
<el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([scope.row.{{PK_ATTR}}])"
|
|
39
|
+
<el-button text type="primary" icon="view" v-auth="'{{PERMISSION_PREFIX}}_view'" @click="handleDetail(scope.row.{{PK_ATTR}})">{{ t('common.viewBtn') }}</el-button>
|
|
40
|
+
<el-button icon="edit-pen" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_edit'" @click="handleEdit(scope.row.{{PK_ATTR}})">{{ t('common.editBtn') }}</el-button>
|
|
41
|
+
<el-button icon="delete" text type="primary" v-auth="'{{PERMISSION_PREFIX}}_del'" @click="handleDelete([scope.row.{{PK_ATTR}}])">{{ t('common.delBtn') }}</el-button>
|
|
42
42
|
</template>
|
|
43
43
|
</el-table-column>
|
|
44
44
|
</el-table>
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
|
-
<upload-excel ref="excelUploadRef" title="
|
|
49
|
+
<upload-excel ref="excelUploadRef" :title="t('common.importBtn')" url="/{{API_PATH}}/import" temp-url="/admin/sys-file/local/file/{{FUNCTION_NAME}}.xlsx" @refreshDataList="getDataList" />
|
|
50
50
|
</div>
|
|
51
51
|
</template>
|
|
52
52
|
|
|
@@ -55,9 +55,11 @@ import { BasicTableProps, useTable } from '/@/hooks/table';
|
|
|
55
55
|
import { fetchList, delObjs } from '/@/api/{{API_MODULE_PATH}}';
|
|
56
56
|
import { useMessage, useMessageBox } from '/@/hooks/message';
|
|
57
57
|
import { useDict } from '/@/hooks/dict';
|
|
58
|
+
import { useI18n } from 'vue-i18n';
|
|
58
59
|
import { allDictTypes, dataMasterEntity } from './options';
|
|
59
60
|
|
|
60
61
|
const dictRefs = useDict(...allDictTypes);
|
|
62
|
+
const { t } = useI18n();
|
|
61
63
|
const router = useRouter();
|
|
62
64
|
|
|
63
65
|
const excelUploadRef = ref();
|
|
@@ -70,12 +72,19 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
|
|
70
72
|
pageList: fetchList,
|
|
71
73
|
});
|
|
72
74
|
|
|
75
|
+
const resolveLabel = (labelKey?: string, fallback = '') => {
|
|
76
|
+
if (!labelKey) return fallback;
|
|
77
|
+
const translated = t(labelKey);
|
|
78
|
+
return translated === labelKey ? fallback : translated;
|
|
79
|
+
};
|
|
80
|
+
|
|
73
81
|
const visibleTableColumns = computed(() =>
|
|
74
82
|
Object.entries(dataMasterEntity)
|
|
75
83
|
.filter(([, config]) => !config.alwaysHide && config.show !== false)
|
|
76
84
|
.map(([prop, config]) => ({
|
|
77
85
|
prop,
|
|
78
|
-
|
|
86
|
+
labelKey: config.labelKey,
|
|
87
|
+
fallbackLabel: config.label || prop,
|
|
79
88
|
width: config.width,
|
|
80
89
|
dictType: config.dictType || '',
|
|
81
90
|
}))
|
|
@@ -88,15 +97,15 @@ const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, do
|
|
|
88
97
|
const getFormPath = () => '/{{VIEW_MODULE_PATH}}/form';
|
|
89
98
|
|
|
90
99
|
const handleCreate = () => {
|
|
91
|
-
router.push({ path: getFormPath(), query: { tagsViewName: '
|
|
100
|
+
router.push({ path: getFormPath(), query: { tagsViewName: t('common.addBtn') } });
|
|
92
101
|
};
|
|
93
102
|
|
|
94
103
|
const handleDetail = (id: string) => {
|
|
95
|
-
router.push({ path: getFormPath(), query: { id, detail: '1', tagsViewName: '
|
|
104
|
+
router.push({ path: getFormPath(), query: { id, detail: '1', tagsViewName: t('common.viewBtn') } });
|
|
96
105
|
};
|
|
97
106
|
|
|
98
107
|
const handleEdit = (id: string) => {
|
|
99
|
-
router.push({ path: getFormPath(), query: { id, tagsViewName: '
|
|
108
|
+
router.push({ path: getFormPath(), query: { id, tagsViewName: t('common.editBtn') } });
|
|
100
109
|
};
|
|
101
110
|
|
|
102
111
|
const exportExcel = () => {
|
|
@@ -110,7 +119,7 @@ const selectionChangeHandle = (objs: { {{PK_ATTR}}: string }[]) => {
|
|
|
110
119
|
|
|
111
120
|
const handleDelete = async (ids: string[]) => {
|
|
112
121
|
try {
|
|
113
|
-
await useMessageBox().confirm('
|
|
122
|
+
await useMessageBox().confirm(t('common.delConfirmText'));
|
|
114
123
|
} catch {
|
|
115
124
|
return;
|
|
116
125
|
}
|
|
@@ -118,9 +127,9 @@ const handleDelete = async (ids: string[]) => {
|
|
|
118
127
|
try {
|
|
119
128
|
await delObjs(ids);
|
|
120
129
|
getDataList();
|
|
121
|
-
useMessage().success('
|
|
130
|
+
useMessage().success(t('common.delSuccessText'));
|
|
122
131
|
} catch (err: any) {
|
|
123
|
-
useMessage().error(err.msg || '
|
|
132
|
+
useMessage().error(err.msg || t('common.delBtn'));
|
|
124
133
|
}
|
|
125
134
|
};
|
|
126
135
|
</script>
|
|
@@ -4,29 +4,21 @@ import type { CrudSchemaDefinition } from '/@/utils/crudSchema';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* {{TABLE_NAME}} 页面字段声明。
|
|
7
|
-
*
|
|
7
|
+
* 这里只维护字段 key、双语 key、字典类型和显示元数据。
|
|
8
8
|
*/
|
|
9
9
|
const definition: CrudSchemaDefinition = {
|
|
10
|
-
// 主表字段
|
|
11
10
|
master: [
|
|
12
11
|
{{MASTER_OPTION_FIELDS}}
|
|
13
12
|
],
|
|
14
|
-
// 子表字段,key 对应 form.vue 中的列表字段名
|
|
15
13
|
children: {
|
|
16
14
|
{{CHILD_OPTION_GROUPS}}
|
|
17
15
|
},
|
|
18
16
|
};
|
|
19
17
|
|
|
20
|
-
// 统一派生页面所需配置,避免在 options 文件里重复手写
|
|
21
18
|
const schema = createCrudSchema(definition);
|
|
22
19
|
|
|
23
|
-
// 主表字段配置:列表页、主表表单共用
|
|
24
20
|
export const dataMasterEntity = schema.master;
|
|
25
|
-
// 子表字段配置:主子表 form 页使用
|
|
26
21
|
export const childFieldGroups = schema.children;
|
|
27
|
-
// 主表字典筛选配置
|
|
28
22
|
export const filterTypes = schema.filterTypes;
|
|
29
|
-
// 子表字典筛选配置
|
|
30
23
|
export const childFilterTypes = schema.childFilterTypes;
|
|
31
|
-
// 当前功能涉及的全部字典编码
|
|
32
24
|
export const allDictTypes = schema.allDictTypes;
|
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.6';
|
|
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');
|
|
@@ -21,14 +21,16 @@ const DEFAULT_CRUD_SCHEMA_TEMPLATE = `export interface FieldMeta {
|
|
|
21
21
|
show: boolean;
|
|
22
22
|
alwaysHide: boolean;
|
|
23
23
|
smart: boolean;
|
|
24
|
-
|
|
24
|
+
labelKey: string;
|
|
25
|
+
label?: string;
|
|
25
26
|
width: string;
|
|
26
27
|
dictType?: string;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export interface FieldConfig {
|
|
30
31
|
key: string;
|
|
31
|
-
|
|
32
|
+
labelKey?: string;
|
|
33
|
+
label?: string;
|
|
32
34
|
width?: string;
|
|
33
35
|
show?: boolean;
|
|
34
36
|
alwaysHide?: boolean;
|
|
@@ -51,16 +53,16 @@ export interface CrudSchema {
|
|
|
51
53
|
|
|
52
54
|
const DEFAULT_WIDTH = '120';
|
|
53
55
|
|
|
54
|
-
export const field = (
|
|
56
|
+
export const field = (labelKey: string, width = DEFAULT_WIDTH): FieldMeta => ({
|
|
55
57
|
show: true,
|
|
56
58
|
alwaysHide: false,
|
|
57
59
|
smart: false,
|
|
58
|
-
|
|
60
|
+
labelKey,
|
|
59
61
|
width,
|
|
60
62
|
});
|
|
61
63
|
|
|
62
|
-
export const dictField = (
|
|
63
|
-
...field(
|
|
64
|
+
export const dictField = (labelKey: string, dictType: string, width = DEFAULT_WIDTH): FieldMeta => ({
|
|
65
|
+
...field(labelKey, width),
|
|
64
66
|
dictType,
|
|
65
67
|
});
|
|
66
68
|
|
|
@@ -82,7 +84,8 @@ const normalizeField = (item: FieldConfig): FieldMeta => ({
|
|
|
82
84
|
show: item.show ?? true,
|
|
83
85
|
alwaysHide: item.alwaysHide ?? false,
|
|
84
86
|
smart: item.smart ?? false,
|
|
85
|
-
|
|
87
|
+
labelKey: item.labelKey ?? item.label ?? item.key,
|
|
88
|
+
...(item.label ? { label: item.label } : {}),
|
|
86
89
|
width: item.width ?? DEFAULT_WIDTH,
|
|
87
90
|
...(item.dictType ? { dictType: item.dictType } : {}),
|
|
88
91
|
});
|
|
@@ -226,6 +229,76 @@ function renderDictRegistryContent(entries) {
|
|
|
226
229
|
].join('\n');
|
|
227
230
|
}
|
|
228
231
|
|
|
232
|
+
function isPlainObject(value) {
|
|
233
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function parseExportDefaultObject(fileContent) {
|
|
237
|
+
const source = String(fileContent || '').replace(/^\uFEFF/, '').trim();
|
|
238
|
+
if (!source) return null;
|
|
239
|
+
try {
|
|
240
|
+
const executable = source.replace(/^export\s+default/, 'return');
|
|
241
|
+
return new Function(executable)();
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function deepMergeMissing(target, source) {
|
|
248
|
+
if (!isPlainObject(source)) {
|
|
249
|
+
return target === undefined ? source : target;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const result = isPlainObject(target) ? { ...target } : {};
|
|
253
|
+
for (const [key, value] of Object.entries(source)) {
|
|
254
|
+
if (result[key] === undefined) {
|
|
255
|
+
result[key] = value;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (isPlainObject(result[key]) && isPlainObject(value)) {
|
|
259
|
+
result[key] = deepMergeMissing(result[key], value);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function escapeTsString(value) {
|
|
266
|
+
return String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function renderTsLiteral(value, indentLevel = 0) {
|
|
270
|
+
const indent = ' '.repeat(indentLevel);
|
|
271
|
+
const childIndent = ' '.repeat(indentLevel + 1);
|
|
272
|
+
|
|
273
|
+
if (typeof value === 'string') {
|
|
274
|
+
return `'${escapeTsString(value)}'`;
|
|
275
|
+
}
|
|
276
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
277
|
+
return String(value);
|
|
278
|
+
}
|
|
279
|
+
if (Array.isArray(value)) {
|
|
280
|
+
if (!value.length) return '[]';
|
|
281
|
+
return ['[', ...value.map((item) => `${childIndent}${renderTsLiteral(item, indentLevel + 1)},`), `${indent}]`].join('\n');
|
|
282
|
+
}
|
|
283
|
+
if (isPlainObject(value)) {
|
|
284
|
+
const entries = Object.entries(value);
|
|
285
|
+
if (!entries.length) return '{}';
|
|
286
|
+
return [
|
|
287
|
+
'{',
|
|
288
|
+
...entries.map(([key, item]) => {
|
|
289
|
+
const renderedKey = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : `'${escapeTsString(key)}'`;
|
|
290
|
+
return `${childIndent}${renderedKey}: ${renderTsLiteral(item, indentLevel + 1)},`;
|
|
291
|
+
}),
|
|
292
|
+
`${indent}}`,
|
|
293
|
+
].join('\n');
|
|
294
|
+
}
|
|
295
|
+
return 'null';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function renderExportDefaultContent(objectValue) {
|
|
299
|
+
return `export default ${renderTsLiteral(objectValue, 0)};\n`;
|
|
300
|
+
}
|
|
301
|
+
|
|
229
302
|
function getPreferredDictRegistryKey(dictType) {
|
|
230
303
|
return DEFAULT_DICT_REGISTRY_KEYS[dictType] || toConstantCase(dictType) || 'DICT_TYPE';
|
|
231
304
|
}
|
|
@@ -243,6 +316,102 @@ function buildUniqueDictRegistryKey(dictType, usedKeys, existingByKey) {
|
|
|
243
316
|
return candidate;
|
|
244
317
|
}
|
|
245
318
|
|
|
319
|
+
function buildI18nNamespaceSegments(model) {
|
|
320
|
+
return [...model.moduleName.split('/').filter(Boolean), model.functionName];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function buildI18nNamespace(model) {
|
|
324
|
+
return buildI18nNamespaceSegments(model).join('.');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function buildFieldLabelKey(model, field) {
|
|
328
|
+
return `${buildI18nNamespace(model)}.fields.${field.attrName}`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function buildChildFieldLabelKey(model, childModel, field) {
|
|
332
|
+
return `${buildI18nNamespace(model)}.children.${childModel.listName}.fields.${field.attrName}`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function buildChildSectionTitleKey(model, childModel) {
|
|
336
|
+
return `${buildI18nNamespace(model)}.children.${childModel.listName}.title`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function buildLocaleLeaf(model) {
|
|
340
|
+
const leaf = {
|
|
341
|
+
title: model.tableComment,
|
|
342
|
+
fields: Object.fromEntries(model.visibleFields.map((field) => [field.attrName, field.comment])),
|
|
343
|
+
placeholders: {
|
|
344
|
+
input: '请输入{label}',
|
|
345
|
+
select: '请选择{label}',
|
|
346
|
+
},
|
|
347
|
+
actions: {
|
|
348
|
+
save: '保存',
|
|
349
|
+
submit: '提交',
|
|
350
|
+
flow: '流转',
|
|
351
|
+
back: '返回',
|
|
352
|
+
},
|
|
353
|
+
messages: {
|
|
354
|
+
required: '{label}不能为空',
|
|
355
|
+
fetchError: '获取数据失败',
|
|
356
|
+
submitError: '提交失败',
|
|
357
|
+
createSuccess: '添加成功',
|
|
358
|
+
updateSuccess: '修改成功',
|
|
359
|
+
quickSubmitSuccess: '提交操作成功',
|
|
360
|
+
quickFlowSuccess: '流转操作成功',
|
|
361
|
+
quickActionConfirm: '确定要{action}该记录吗?',
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
if (model.children.length) {
|
|
366
|
+
leaf.children = Object.fromEntries(
|
|
367
|
+
model.children.map((childModel) => [
|
|
368
|
+
childModel.listName,
|
|
369
|
+
{
|
|
370
|
+
title: childModel.tableComment,
|
|
371
|
+
fields: Object.fromEntries(childModel.visibleFields.map((field) => [field.attrName, field.comment])),
|
|
372
|
+
},
|
|
373
|
+
])
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return leaf;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function buildZhCnLocaleObject(model) {
|
|
381
|
+
const root = {};
|
|
382
|
+
const segments = buildI18nNamespaceSegments(model);
|
|
383
|
+
let cursor = root;
|
|
384
|
+
|
|
385
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
386
|
+
const segment = segments[index];
|
|
387
|
+
cursor[segment] = cursor[segment] || {};
|
|
388
|
+
cursor = cursor[segment];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
cursor[segments[segments.length - 1]] = buildLocaleLeaf(model);
|
|
392
|
+
return root;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function prepareZhCnLocaleFile(model) {
|
|
396
|
+
const localePath = path.join(model.frontendPath, 'src', 'i18n', 'biz', ...model.moduleName.split('/'), `${model.functionName}.zh-cn.ts`);
|
|
397
|
+
const exists = fs.existsSync(localePath);
|
|
398
|
+
const currentContent = exists ? readUtf8File(localePath) : '';
|
|
399
|
+
const currentObject = exists ? parseExportDefaultObject(currentContent) : null;
|
|
400
|
+
const generatedObject = buildZhCnLocaleObject(model);
|
|
401
|
+
const isCompatible = !exists || isPlainObject(currentObject);
|
|
402
|
+
const mergedObject = isCompatible ? deepMergeMissing(currentObject || {}, generatedObject) : null;
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
path: localePath,
|
|
406
|
+
frontendPath: model.frontendPath,
|
|
407
|
+
exists,
|
|
408
|
+
isCompatible,
|
|
409
|
+
namespace: buildI18nNamespace(model),
|
|
410
|
+
content: mergedObject ? renderExportDefaultContent(mergedObject) : '',
|
|
411
|
+
needsWrite: !exists || (isCompatible && renderExportDefaultContent(currentObject || {}) !== renderExportDefaultContent(mergedObject)),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
246
415
|
function prepareDictRegistry(frontendPath, dictTypes) {
|
|
247
416
|
const registryPath = path.join(frontendPath, 'src', 'enums', 'dict-registry.ts');
|
|
248
417
|
const exists = fs.existsSync(registryPath);
|
|
@@ -275,17 +444,19 @@ function ensureCrudSchemaSupportFile(frontendPath) {
|
|
|
275
444
|
const schemaPath = path.join(frontendPath, 'src', 'utils', 'crudSchema.ts');
|
|
276
445
|
const exists = fs.existsSync(schemaPath);
|
|
277
446
|
const currentContent = exists ? readUtf8File(schemaPath) : '';
|
|
278
|
-
const
|
|
447
|
+
const hasCoreShape =
|
|
279
448
|
currentContent.includes('export interface CrudSchemaDefinition') &&
|
|
280
449
|
currentContent.includes('export function createCrudSchema') &&
|
|
281
450
|
currentContent.includes('export interface FieldConfig');
|
|
451
|
+
const supportsLabelKey = currentContent.includes('labelKey');
|
|
452
|
+
const shouldUpgradeLegacy = exists && hasCoreShape && !supportsLabelKey;
|
|
282
453
|
|
|
283
454
|
return {
|
|
284
455
|
path: schemaPath,
|
|
285
456
|
content: DEFAULT_CRUD_SCHEMA_TEMPLATE,
|
|
286
457
|
exists,
|
|
287
|
-
isCompatible,
|
|
288
|
-
needsWrite: !exists,
|
|
458
|
+
isCompatible: hasCoreShape && supportsLabelKey,
|
|
459
|
+
needsWrite: !exists || shouldUpgradeLegacy,
|
|
289
460
|
};
|
|
290
461
|
}
|
|
291
462
|
|
|
@@ -320,14 +491,24 @@ function maybeWriteSharedSupport(sharedSupport, writeToDisk) {
|
|
|
320
491
|
}
|
|
321
492
|
}
|
|
322
493
|
|
|
323
|
-
function
|
|
494
|
+
function buildSupportNote(sharedSupport, localeZhSupport) {
|
|
495
|
+
const notes = [];
|
|
496
|
+
|
|
324
497
|
if (sharedSupport.crudSchema.exists && !sharedSupport.crudSchema.isCompatible) {
|
|
325
|
-
|
|
498
|
+
notes.push(
|
|
326
499
|
'Detected an existing src/utils/crudSchema.ts that does not match the expected helper signature. ' +
|
|
327
|
-
|
|
500
|
+
'MCP preserved the existing file and did not overwrite it. Generated pages now depend on that file being manually aligned.'
|
|
328
501
|
);
|
|
329
502
|
}
|
|
330
|
-
|
|
503
|
+
|
|
504
|
+
if (localeZhSupport.exists && !localeZhSupport.isCompatible) {
|
|
505
|
+
notes.push(
|
|
506
|
+
`Detected an existing ${path.relative(localeZhSupport.frontendPath, localeZhSupport.path).replace(/\\/g, '/')} that MCP could not parse. ` +
|
|
507
|
+
'The file was preserved and not updated, so new Chinese i18n keys may need to be merged manually.'
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return notes.length ? notes.join(' ') : 'Runtime template rendering completed.';
|
|
331
512
|
}
|
|
332
513
|
|
|
333
514
|
function getDictRegistryReference(dictType, keyByValue) {
|
|
@@ -1168,9 +1349,8 @@ function getDefaultOptionFieldWidthV2(field) {
|
|
|
1168
1349
|
return '120';
|
|
1169
1350
|
}
|
|
1170
1351
|
|
|
1171
|
-
function renderOptionFieldV2(field, dictRegistryRefs, indent = ' ') {
|
|
1172
|
-
const
|
|
1173
|
-
const parts = [`key: '${field.attrName}'`, `label: '${label}'`];
|
|
1352
|
+
function renderOptionFieldV2(field, labelKey, dictRegistryRefs, indent = ' ') {
|
|
1353
|
+
const parts = [`key: '${field.attrName}'`, `labelKey: '${labelKey}'`];
|
|
1174
1354
|
const width = getDefaultOptionFieldWidthV2(field);
|
|
1175
1355
|
|
|
1176
1356
|
if (width !== '120') {
|
|
@@ -1184,10 +1364,12 @@ function renderOptionFieldV2(field, dictRegistryRefs, indent = ' ') {
|
|
|
1184
1364
|
return `${indent}{ ${parts.join(', ')} },`;
|
|
1185
1365
|
}
|
|
1186
1366
|
|
|
1187
|
-
function renderChildOptionGroupV2(childModel, dictRegistryRefs) {
|
|
1367
|
+
function renderChildOptionGroupV2(model, childModel, dictRegistryRefs) {
|
|
1188
1368
|
return [
|
|
1189
1369
|
` ${childModel.listName}: [`,
|
|
1190
|
-
childModel.visibleFields
|
|
1370
|
+
childModel.visibleFields
|
|
1371
|
+
.map((field) => renderOptionFieldV2(field, buildChildFieldLabelKey(model, childModel, field), dictRegistryRefs, ' '))
|
|
1372
|
+
.join('\n'),
|
|
1191
1373
|
' ],',
|
|
1192
1374
|
].join('\n');
|
|
1193
1375
|
}
|
|
@@ -1203,9 +1385,8 @@ function renderTextareaMaxlengthAttrsV2(field) {
|
|
|
1203
1385
|
}
|
|
1204
1386
|
|
|
1205
1387
|
function renderFormFieldV2(field) {
|
|
1206
|
-
const label = field.comment.replace(/'/g, "\\'");
|
|
1207
1388
|
const prop = field.attrName;
|
|
1208
|
-
const labelExpr = `
|
|
1389
|
+
const labelExpr = `getMasterFieldLabel('${prop}')`;
|
|
1209
1390
|
const dictExpr = `getMasterFieldMeta('${prop}')?.dictType`;
|
|
1210
1391
|
const visibilityExpr = `isMasterFieldVisible('${prop}')`;
|
|
1211
1392
|
|
|
@@ -1281,9 +1462,8 @@ function renderTableColumnV2(field, dictRegistryRefs) {
|
|
|
1281
1462
|
}
|
|
1282
1463
|
|
|
1283
1464
|
function renderChildTableColumnV2(field, childListName) {
|
|
1284
|
-
const label = field.comment.replace(/'/g, "\\'");
|
|
1285
1465
|
const rules = field.notNull ? ` :rules="[{ required: true, trigger: 'blur' }]"` : '';
|
|
1286
|
-
const labelExpr = `
|
|
1466
|
+
const labelExpr = `getChildFieldLabel('${childListName}', '${field.attrName}')`;
|
|
1287
1467
|
const dictExpr = `getChildFieldMeta('${childListName}', '${field.attrName}')?.dictType`;
|
|
1288
1468
|
|
|
1289
1469
|
let control = ` <el-input v-model="row.${field.attrName}"${renderTextMaxlengthAttrV2(field)} />`;
|
|
@@ -1317,7 +1497,6 @@ function renderChildTableColumnV2(field, childListName) {
|
|
|
1317
1497
|
}
|
|
1318
1498
|
|
|
1319
1499
|
function renderChildSectionV2(childModel, childCount) {
|
|
1320
|
-
const title = childModel.tableComment.replace(/'/g, "\\'");
|
|
1321
1500
|
const deleteExpression =
|
|
1322
1501
|
childCount > 1
|
|
1323
1502
|
? `deleteChild(obj, '${childModel.pk.attrName}', '${childModel.tableName}')`
|
|
@@ -1325,7 +1504,7 @@ function renderChildSectionV2(childModel, childCount) {
|
|
|
1325
1504
|
|
|
1326
1505
|
return [
|
|
1327
1506
|
' <el-col :span="24" class="mb20">',
|
|
1328
|
-
` <div class="mb10" style="font-weight: 600;"
|
|
1507
|
+
` <div class="mb10" style="font-weight: 600;">{{ childSectionTitle('${childModel.listName}') }}</div>`,
|
|
1329
1508
|
` <sc-form-table v-model="form.${childModel.listName}" :addTemplate="childTemp${childModel.className}" @delete="(obj) => ${deleteExpression}" placeholder="鏆傛棤鏁版嵁">`,
|
|
1330
1509
|
childModel.visibleFields.map((field) => renderChildTableColumnV2(field, childModel.listName)).join('\n'),
|
|
1331
1510
|
' </sc-form-table>',
|
|
@@ -1344,12 +1523,20 @@ function renderFormRules(visibleFields) {
|
|
|
1344
1523
|
return lines.join('\n');
|
|
1345
1524
|
}
|
|
1346
1525
|
|
|
1526
|
+
function renderFormRulesV2(visibleFields) {
|
|
1527
|
+
const lines = visibleFields
|
|
1528
|
+
.filter((field) => field.notNull)
|
|
1529
|
+
.map((field) => ` ${field.attrName}: [{ required: true, message: fieldRequiredMessage('${field.attrName}'), trigger: 'blur' }],`);
|
|
1530
|
+
return lines.join('\n');
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1347
1533
|
function buildReplacements(model, sharedSupport) {
|
|
1348
1534
|
const menuBaseId = Date.now();
|
|
1349
1535
|
const apiModulePath = `${model.moduleName}/${model.functionName}`;
|
|
1350
1536
|
const routePath = `${model.moduleName}/${model.functionName}`;
|
|
1351
1537
|
const permissionPrefix = `${model.moduleName}/${model.functionName}`.replace(/\//g, '_');
|
|
1352
1538
|
const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
|
|
1539
|
+
const i18nNamespace = buildI18nNamespace(model);
|
|
1353
1540
|
|
|
1354
1541
|
return {
|
|
1355
1542
|
TABLE_NAME: model.tableName,
|
|
@@ -1361,6 +1548,7 @@ function buildReplacements(model, sharedSupport) {
|
|
|
1361
1548
|
API_PATH: apiModulePath,
|
|
1362
1549
|
VIEW_MODULE_PATH: routePath,
|
|
1363
1550
|
MENU_ROUTE_PATH: routePath,
|
|
1551
|
+
I18N_NAMESPACE: i18nNamespace,
|
|
1364
1552
|
PERMISSION_PREFIX: permissionPrefix,
|
|
1365
1553
|
MENU_BASE_ID: menuBaseId,
|
|
1366
1554
|
MENU_BASE_ID_PLUS_1: menuBaseId + 1,
|
|
@@ -1373,9 +1561,9 @@ function buildReplacements(model, sharedSupport) {
|
|
|
1373
1561
|
TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumnV2(field, dictRegistryRefs)).join('\n'),
|
|
1374
1562
|
FORM_DEFAULTS: renderFormDefaults(model),
|
|
1375
1563
|
DICT_REGISTRY_IMPORT_BLOCK: model.dictTypes.length ? "import { DictRegistry } from '/@/enums/dict-registry';" : '',
|
|
1376
|
-
MASTER_OPTION_FIELDS: model.visibleFields.map((field) => renderOptionFieldV2(field, dictRegistryRefs)).join('\n'),
|
|
1377
|
-
CHILD_OPTION_GROUPS: model.children.map((childModel) => renderChildOptionGroupV2(childModel, dictRegistryRefs)).join('\n'),
|
|
1378
|
-
FORM_RULES:
|
|
1564
|
+
MASTER_OPTION_FIELDS: model.visibleFields.map((field) => renderOptionFieldV2(field, buildFieldLabelKey(model, field), dictRegistryRefs)).join('\n'),
|
|
1565
|
+
CHILD_OPTION_GROUPS: model.children.map((childModel) => renderChildOptionGroupV2(model, childModel, dictRegistryRefs)).join('\n'),
|
|
1566
|
+
FORM_RULES: renderFormRulesV2(model.visibleFields),
|
|
1379
1567
|
CHILD_FORM_LIST_DEFAULTS: renderChildFormListDefaults(model.children),
|
|
1380
1568
|
CHILD_TEMP_DECLARATIONS: renderChildTempDeclarations(model.children),
|
|
1381
1569
|
CHILD_RESET_LISTS: renderChildResetListLines(model.children),
|
|
@@ -1383,7 +1571,7 @@ function buildReplacements(model, sharedSupport) {
|
|
|
1383
1571
|
};
|
|
1384
1572
|
}
|
|
1385
1573
|
|
|
1386
|
-
|
|
1574
|
+
function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
|
|
1387
1575
|
if (!hasRuntimeSupport(stylePreset)) throw new Error('Runtime templates are not implemented for style: ' + model.style);
|
|
1388
1576
|
|
|
1389
1577
|
const runtime = stylePreset.runtime;
|
|
@@ -1404,6 +1592,13 @@ function buildReplacements(model, sharedSupport) {
|
|
|
1404
1592
|
{ type: 'list', path: path.join(viewRoot, 'index.vue'), content: renderTemplate(listTemplate, replacements) },
|
|
1405
1593
|
{ type: 'options', path: path.join(viewRoot, 'options.ts'), content: renderTemplate(optionsTemplate, replacements) },
|
|
1406
1594
|
{ type: 'api', path: path.join(apiRoot, `${model.functionName}.ts`), content: renderTemplate(apiTemplate, replacements) },
|
|
1595
|
+
{
|
|
1596
|
+
type: 'i18nZh',
|
|
1597
|
+
path: localeZhSupport.path,
|
|
1598
|
+
content: localeZhSupport.content,
|
|
1599
|
+
canWrite: localeZhSupport.isCompatible,
|
|
1600
|
+
needsWrite: localeZhSupport.needsWrite,
|
|
1601
|
+
},
|
|
1407
1602
|
];
|
|
1408
1603
|
|
|
1409
1604
|
if (menuSqlTemplate) {
|
|
@@ -1440,6 +1635,14 @@ function ensureArguments(input) {
|
|
|
1440
1635
|
function maybeWriteFiles(files, writeToDisk, overwrite) {
|
|
1441
1636
|
if (!writeToDisk) return;
|
|
1442
1637
|
for (const file of files) {
|
|
1638
|
+
if (file.canWrite === false) {
|
|
1639
|
+
file.status = 'skipped';
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
if (file.needsWrite === false) {
|
|
1643
|
+
file.status = 'unchanged';
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1443
1646
|
if (!overwrite && fs.existsSync(file.path)) {
|
|
1444
1647
|
file.status = 'skipped';
|
|
1445
1648
|
continue;
|
|
@@ -1513,16 +1716,17 @@ async function handleToolCall(argumentsObject) {
|
|
|
1513
1716
|
const model = buildModel(safeArgs);
|
|
1514
1717
|
const sharedTemplates = resolveSharedTemplates(stylePreset);
|
|
1515
1718
|
const sharedSupport = prepareSharedSupport(model.frontendPath, model.dictTypes);
|
|
1719
|
+
const localeZhSupport = prepareZhCnLocaleFile(model);
|
|
1516
1720
|
|
|
1517
1721
|
if (!hasRuntimeSupport(stylePreset)) {
|
|
1518
1722
|
const manifest = buildManifest(model, safeArgs, stylePreset, sharedTemplates, [], 'Style mapping is declared, but runtime template rendering is not implemented yet for this style.');
|
|
1519
1723
|
return toolTextResult(JSON.stringify(manifest, null, 2));
|
|
1520
1724
|
}
|
|
1521
1725
|
|
|
1522
|
-
const files = renderFiles(model, stylePreset, sharedSupport);
|
|
1726
|
+
const files = renderFiles(model, stylePreset, sharedSupport, localeZhSupport);
|
|
1523
1727
|
maybeWriteFiles(files, safeArgs.writeToDisk, safeArgs.overwrite);
|
|
1524
1728
|
maybeWriteSharedSupport(sharedSupport, safeArgs.writeToDisk);
|
|
1525
|
-
const manifest = buildManifest(model, safeArgs, stylePreset, sharedTemplates, files,
|
|
1729
|
+
const manifest = buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, buildSupportNote(sharedSupport, localeZhSupport));
|
|
1526
1730
|
return toolTextResult(JSON.stringify(manifest, null, 2));
|
|
1527
1731
|
}
|
|
1528
1732
|
|
package/package.json
CHANGED