Commit 3ed98e152a0c3efd22dcd26dfeecbbdb60bfb52f

Authored by YunaiV
1 parent c8a584c5

fix(infra): 修复前端代码生成模板的组件绑定与主子表行为

修复 Vben5 字典控件、日期及树选择属性、导入文件移除和 API 导入路径。
修复主子表数据获取、临时编号、必填校验和主表切换后的旧数据残留。
补齐工具方法与组件导入,修正 Ele 工具栏标签及 Vue3 单选多选绑定。
同步 Vue2 组件标签与 prop 声明、旧 Vben 日期范围格式。
更新生成快照,增加跨前端组件契约测试。

验证:85 条 CodegenEngine 测试、138 份 Vue 模板编译通过;
Codex、Qwen、DeepSeek 三路评审通过。
Showing 120 changed files with 3131 additions and 1753 deletions

Too many changes.

To preserve performance only 100 of 120 files are displayed.

yudao-module-infra/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm
... ... @@ -244,9 +244,13 @@
244 244 Editor,
245 245 #end
246 246 },
247   - props:[
248   - '${subJoinColumn.javaField}'
249   - ],// ${subJoinColumn.columnComment}(主表的关联字段)
  247 + props: {
  248 + // ${subJoinColumn.columnComment}(主表的关联字段)
  249 + ${subJoinColumn.javaField}: {
  250 + type: #if ($subJoinColumn.javaType == "String")String#else Number#end,
  251 + default: undefined
  252 + }
  253 + },
250 254 data() {
251 255 return {
252 256 // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
... ...
yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm
... ... @@ -78,7 +78,7 @@
78 78 </el-table>
79 79 #if ($table.templateType == 11)
80 80 <!-- 分页组件 -->
81   - <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  81 + <Pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
82 82 @pagination="getList"/>
83 83 <!-- 对话框(添加 / 修改) -->
84 84 <${subSimpleClassName}Form ref="formRef" @success="getList" />
... ... @@ -98,9 +98,13 @@
98 98 ${subSimpleClassName}Form
99 99 },
100 100 #end
101   - props:[
102   - '${subJoinColumn.javaField}'
103   - ],// ${subJoinColumn.columnComment}(主表的关联字段)
  101 + props: {
  102 + // ${subJoinColumn.columnComment}(主表的关联字段)
  103 + ${subJoinColumn.javaField}: {
  104 + type: #if ($subJoinColumn.javaType == "String")String#else Number#end,
  105 + default: undefined
  106 + }
  107 + },
104 108 data() {
105 109 return {
106 110 // 遮罩层
... ...
yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm
... ... @@ -86,7 +86,7 @@
86 86 </el-button>
87 87 </el-col>
88 88 #end
89   - <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  89 + <RightToolbar :showSearch.sync="showSearch" @queryTable="getList"></RightToolbar>
90 90 </el-row>
91 91 #if ($importEnable)
92 92 <input ref="importFileRef" type="file" style="display: none" accept=".xls,.xlsx" @change="handleImportFileChange" />
... ... @@ -186,7 +186,7 @@
186 186 ## 特殊:树表专属逻辑(树不需要分页)
187 187 #if ( $table.templateType != 2 )
188 188 <!-- 分页组件 -->
189   - <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  189 + <Pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
190 190 @pagination="getList"/>
191 191 #end
192 192 <!-- 对话框(添加 / 修改) -->
... ...
yudao-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm
... ... @@ -79,7 +79,7 @@
79 79 <el-radio
80 80 v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
81 81 :key="dict.value"
82   - :label="dict.value"
  82 + :value="dict.value"
83 83 >
84 84 {{ dict.label }}
85 85 </el-radio>
... ...
yudao-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm
... ... @@ -111,7 +111,7 @@
111 111 <el-radio
112 112 v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
113 113 :key="dict.value"
114   - :label="dict.value"
  114 + :value="dict.value"
115 115 >
116 116 {{ dict.label }}
117 117 </el-radio>
... ... @@ -233,7 +233,7 @@
233 233 <el-radio
234 234 v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
235 235 :key="dict.value"
236   - :label="dict.value"
  236 + :value="dict.value"
237 237 >
238 238 {{ dict.label }}
239 239 </el-radio>
... ...
yudao-module-infra/src/main/resources/codegen/vue3/views/form.vue.vm
... ... @@ -90,7 +90,7 @@
90 90 <el-radio
91 91 v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
92 92 :key="dict.value"
93   - :label="dict.value"
  93 + :value="dict.value"
94 94 >
95 95 {{ dict.label }}
96 96 </el-radio>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben/views/data.ts.vm
... ... @@ -78,6 +78,12 @@ export const searchFormSchema: FormSchema[] = [
78 78 },
79 79 #elseif($column.htmlType == "datetime")
80 80 component: 'RangePicker',
  81 + componentProps: {
  82 + showTime: true,
  83 + format: 'YYYY-MM-DD HH:mm:ss',
  84 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  85 + allowClear: true,
  86 + },
81 87 #end
82 88 colProps: { span: 8 },
83 89 },
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm
... ... @@ -112,7 +112,10 @@ const [Modal, modalApi] = useVbenModal({
112 112 #set ($index = $foreach.count - 1)
113 113 #set ($subClassNameVar = $subClassNameVars.get($index))
114 114 #if ($subTable.subJoinMany) ## 一对多
115   - ## TODO 列表值校验?
  115 + if (!${subClassNameVar}FormRef.value?.validate()) {
  116 + subTabsName.value = '${subClassNameVar}';
  117 + return;
  118 + }
116 119 #else
117 120 try {
118 121 await ${subClassNameVar}FormRef.value?.validate()
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/import.vue.vm
... ... @@ -48,6 +48,11 @@ function beforeUpload(file: FileType) {
48 48 return false;
49 49 }
50 50  
  51 +/** 移除文件 */
  52 +function handleRemove() {
  53 + fileRef.value = null;
  54 +}
  55 +
51 56 /** 下载导入模板 */
52 57 async function handleDownload() {
53 58 const data = await import${simpleClassName}Template();
... ... @@ -58,7 +63,12 @@ async function handleDownload() {
58 63 <template>
59 64 <Modal title="导入${table.classComment}" class="w-1/3">
60 65 <div class="mx-4">
61   - <Upload :max-count="1" accept=".xls,.xlsx" :before-upload="beforeUpload">
  66 + <Upload
  67 + :max-count="1"
  68 + accept=".xls,.xlsx"
  69 + :before-upload="beforeUpload"
  70 + :on-remove="handleRemove"
  71 + >
62 72 <Button type="primary"> 选择 Excel 文件 </Button>
63 73 </Upload>
64 74 </div>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm
... ... @@ -65,12 +65,7 @@ const queryParams = reactive({
65 65 #end
66 66 #foreach ($column in $columns)
67 67 #if ($column.listOperation)
68   - #if ($column.listOperationCondition != 'BETWEEN')
69   - $column.javaField: undefined,
70   - #end
71   - #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
72   - $column.javaField: undefined,
73   - #end
  68 + $column.javaField: undefined,
74 69 #end
75 70 #end
76 71 })
... ... @@ -197,7 +192,17 @@ function handleRowCheckboxChange({
197 192 async function handleExport() {
198 193 try {
199 194 exportLoading.value = true;
200   - const data = await export${simpleClassName}(queryParams);
  195 + const params = cloneDeep(queryParams) as any;
  196 + #foreach ($column in $columns)
  197 + #if ($column.listOperation)
  198 + #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
  199 + if (params.${column.javaField} && Array.isArray(params.${column.javaField})) {
  200 + params.${column.javaField} = (params.${column.javaField} as string[]).join(',');
  201 + }
  202 + #end
  203 + #end
  204 + #end
  205 + const data = await export${simpleClassName}(params);
201 206 downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
202 207 }finally {
203 208 exportLoading.value = false;
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm
... ... @@ -46,25 +46,54 @@ async function handleDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
46 46 await tableRef.value?.remove(row);
47 47 }
48 48  
49   -/** 提供获取表格数据的方法供父组件调用 */
50   -defineExpose({
51   - getData: (): ${simpleClassName}Api.${subSimpleClassName}[] => {
52   - const data = list.value as ${simpleClassName}Api.${subSimpleClassName}[];
53   - const removeRecords = tableRef.value?.getRemoveRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
54   - const insertRecords = tableRef.value?.getInsertRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
55   - return [
56   - ...data.filter(
57   - (row) => !removeRecords.some((removed) => removed.id === row.id),
58   - ),
59   - ...insertRecords.map((row: any) => ({ ...row, id: undefined })),
60   - ];
61   - },
62   -});
  49 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  50 +function getData(): ${simpleClassName}Api.${subSimpleClassName}[] {
  51 + const table = tableRef.value;
  52 + if (!table) {
  53 + return [];
  54 + }
  55 + const { fullData } = table.getTableData();
  56 + const inserted = new Set(table.getInsertRecords());
  57 + return fullData.map((row) =>
  58 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  59 + );
  60 +}
  61 +
  62 +#set ($hasRequiredColumn = false)
  63 +#foreach ($column in $subColumns)
  64 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  65 +#set ($hasRequiredColumn = true)
  66 +#end
  67 +#end
  68 +/** 校验子表必填字段 */
  69 +function validate(): boolean {
  70 +#if ($hasRequiredColumn)
  71 + for (const [index, row] of getData().entries()) {
  72 +#foreach ($column in $subColumns)
  73 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  74 + if (
  75 + row.${column.javaField} === null ||
  76 + row.${column.javaField} === undefined ||
  77 + (typeof row.${column.javaField} === 'string' && String(row.${column.javaField}).trim() === '') ||
  78 + (Array.isArray(row.${column.javaField}) && row.${column.javaField}.length === 0)
  79 + ) {
  80 + message.error(`第 #[[${index + 1}]]# 行:${column.columnComment}不能为空`);
  81 + return false;
  82 + }
  83 +#end
  84 +#end
  85 + }
  86 +#end
  87 + return true;
  88 +}
  89 +
  90 +defineExpose({ getData, validate });
63 91  
64 92 /** 监听主表的关联字段的变化,加载对应的子表数据 */
65 93 watch(
66 94 () => props.${subJoinColumn.javaField},
67 95 async (val) => {
  96 + list.value = [];
68 97 if (!val) {
69 98 return;
70 99 }
... ... @@ -103,6 +132,17 @@ defineExpose({
103 132 watch(
104 133 () => props.${subJoinColumn.javaField},
105 134 async (val) => {
  135 + formData.value = {
  136 +#foreach ($column in $subColumns)
  137 +#if ($column.createOperation || $column.updateOperation)
  138 +#if ($column.htmlType == "checkbox")
  139 + $column.javaField: [],
  140 +#else
  141 + $column.javaField: undefined,
  142 +#end
  143 +#end
  144 +#end
  145 + };
106 146 if (!val) {
107 147 return;
108 148 }
... ... @@ -120,6 +160,7 @@ watch(
120 160 #foreach($column in $subColumns)
121 161 #if ($column.createOperation || $column.updateOperation)
122 162 #set ($comment = $column.columnComment)
  163 + #set ($dictType = $column.dictType)
123 164 #set ($javaField = $column.javaField)
124 165 #set ($javaType = $column.javaType)
125 166 #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/data.ts.vm
... ... @@ -36,7 +36,7 @@ export function useFormSchema(): VbenFormSchema[] {
36 36 id: 0,
37 37 ${treeNameColumn.javaField}: '顶级${table.classComment}',
38 38 });
39   - return handleTree(data);
  39 + return handleTree(data, 'id', '${treeParentColumn.javaField}');
40 40 },
41 41 labelField: '${treeNameColumn.javaField}',
42 42 valueField: 'id',
... ... @@ -89,7 +89,7 @@ export function useFormSchema(): VbenFormSchema[] {
89 89 placeholder: '请选择${comment}',
90 90 },
91 91 #elseif($column.htmlType == "checkbox")## 多选框
92   - component: 'Checkbox',
  92 + component: 'CheckboxGroup',
93 93 componentProps: {
94 94 #if ("" != $dictType)## 有数据字典
95 95 options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
... ... @@ -306,7 +306,7 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
306 306 placeholder: '请选择${comment}',
307 307 },
308 308 #elseif($column.htmlType == "checkbox")## 多选框
309   - component: 'Checkbox',
  309 + component: 'CheckboxGroup',
310 310 componentProps: {
311 311 #if ("" != $dictType)## 有数据字典
312 312 options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
... ... @@ -446,6 +446,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
446 446 #if ($column.createOperation || $column.updateOperation)
447 447 #if (!$column.primaryKey && $column.listOperationResult && $column.id != $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
448 448 #set ($dictType = $column.dictType)
  449 + #set ($javaType = $column.javaType)
449 450 #set ($javaField = $column.javaField)
450 451 #set ($comment = $column.columnComment)
451 452 #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
... ... @@ -540,7 +541,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
540 541 placeholder: '请选择${comment}',
541 542 },
542 543 #elseif($column.htmlType == "checkbox")## 多选框
543   - component: 'Checkbox',
  544 + component: 'CheckboxGroup',
544 545 componentProps: {
545 546 #if ("" != $dictType)## 有数据字典
546 547 options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
... ... @@ -590,7 +591,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
590 591 #end
591 592 #if ($table.templateType == 12) ## 内嵌情况
592 593 /** 列表的字段 */
593   - export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
  594 + export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${apiName}.${subSimpleClassName}>['columns'] {
594 595 return [
595 596 #foreach($column in $subColumns)
596 597 #if ($column.listOperationResult)
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/form.vue.vm
... ... @@ -6,7 +6,7 @@ import { computed, ref } from &#39;vue&#39;;
6 6  
7 7 import { useVbenModal } from '@vben/common-ui';
8 8  
9   -import { message#if ($table.templateType == 11), Tabs#end } from 'ant-design-vue';
  9 +import { message#if ($table.templateType == 10 || $table.templateType == 12), Tabs#end } from 'ant-design-vue';
10 10  
11 11 import { useVbenForm } from '#/adapter/form';
12 12 import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
... ... @@ -69,15 +69,11 @@ const [Modal, modalApi] = useVbenModal({
69 69 #foreach ($subTable in $subTables)
70 70 #set ($index = $foreach.count - 1)
71 71 #set ($subClassNameVar = $subClassNameVars.get($index))
72   - #if ($subTable.subJoinMany) ## 一对多
73   - ## TODO 列表值校验?
74   - #else
75 72 const ${subClassNameVar}Valid = await ${subClassNameVar}FormRef.value?.validate();
76 73 if (!${subClassNameVar}Valid) {
77 74 subTabsName.value = '${subClassNameVar}';
78 75 return;
79 76 }
80   - #end
81 77 #end
82 78 #end
83 79 #end
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/import.vue.vm
... ... @@ -51,6 +51,11 @@ function beforeUpload(file: FileType) {
51 51 return false;
52 52 }
53 53  
  54 +/** 移除文件 */
  55 +function handleRemove() {
  56 + formApi.setFieldValue('file', undefined);
  57 +}
  58 +
54 59 /** 下载模板 */
55 60 async function handleDownload() {
56 61 const data = await import${simpleClassName}Template();
... ... @@ -67,6 +72,7 @@ async function handleDownload() {
67 72 :max-count="1"
68 73 accept=".xls,.xlsx"
69 74 :before-upload="beforeUpload"
  75 + :on-remove="handleRemove"
70 76 >
71 77 <Button type="primary"> 选择 Excel 文件 </Button>
72 78 </Upload>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm
... ... @@ -14,7 +14,7 @@
14 14  
15 15 #if ($subTable.subJoinMany) ## 一对多
16 16 import { Plus } from "@vben/icons";
17   -import { Button, Tabs, Checkbox, Input, Textarea, Select,RadioGroup,CheckboxGroup, DatePicker } from 'ant-design-vue';
  17 +import { Button, Checkbox, CheckboxGroup, DatePicker, Input, message, RadioGroup, Select, Tabs, Textarea } from 'ant-design-vue';
18 18 import { ImageUpload, FileUpload } from "#/components/upload";
19 19 import { useVbenVxeGrid } from '#/adapter/vxe-table';
20 20 import { use${subSimpleClassName}GridEditColumns } from '../data';
... ... @@ -59,26 +59,54 @@ const handleDelete = async (row: ${apiName}.${subSimpleClassName}) =&gt; {
59 59 await gridApi.grid.remove(row);
60 60 }
61 61  
62   -/** 提供获取表格数据的方法供父组件调用 */
63   -defineExpose({
64   - getData: (): ${apiName}.${subSimpleClassName}[] => {
65   - const data = gridApi.grid.getData() as ${apiName}.${subSimpleClassName}[];
66   - const removeRecords = gridApi.grid.getRemoveRecords() as ${apiName}.${subSimpleClassName}[];
67   - const insertRecords = gridApi.grid.getInsertRecords() as ${apiName}.${subSimpleClassName}[];
68   - return data
69   - .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
70   - .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
71   - },
72   -});
  62 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  63 +function getData(): ${apiName}.${subSimpleClassName}[] {
  64 + const { fullData } = gridApi.grid.getTableData();
  65 + const inserted = new Set(gridApi.grid.getInsertRecords());
  66 + return fullData.map((row) =>
  67 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  68 + );
  69 +}
  70 +
  71 +#set ($hasRequiredColumn = false)
  72 +#foreach ($column in $subColumns)
  73 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  74 +#set ($hasRequiredColumn = true)
  75 +#end
  76 +#end
  77 +/** 校验子表必填字段 */
  78 +function validate(): boolean {
  79 +#if ($hasRequiredColumn)
  80 + for (const [index, row] of getData().entries()) {
  81 +#foreach ($column in $subColumns)
  82 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  83 + if (
  84 + row.${column.javaField} === null ||
  85 + row.${column.javaField} === undefined ||
  86 + (typeof row.${column.javaField} === 'string' && String(row.${column.javaField}).trim() === '') ||
  87 + (Array.isArray(row.${column.javaField}) && row.${column.javaField}.length === 0)
  88 + ) {
  89 + message.error(`第 #[[${index + 1}]]# 行:${column.columnComment}不能为空`);
  90 + return false;
  91 + }
  92 +#end
  93 +#end
  94 + }
  95 +#end
  96 + return true;
  97 +}
  98 +
  99 +defineExpose({ getData, validate });
73 100  
74 101 /** 监听主表的关联字段的变化,加载对应的子表数据 */
75 102 watch(
76 103 () => props.${subJoinColumn.javaField},
77 104 async (val) => {
  105 + await nextTick();
78 106 if (!val) {
  107 + await gridApi.grid.loadData([]);
79 108 return;
80 109 }
81   - await nextTick();
82 110 await gridApi.grid.loadData(await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
83 111 },
84 112 { immediate: true },
... ... @@ -110,10 +138,11 @@ defineExpose({
110 138 watch(
111 139 () => props.${subJoinColumn.javaField},
112 140 async (val) => {
  141 + await nextTick();
  142 + await formApi.resetForm();
113 143 if (!val) {
114 144 return;
115 145 }
116   - await nextTick();
117 146 await formApi.setValues(await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
118 147 },
119 148 { immediate: true },
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/form.vue.vm
... ... @@ -118,7 +118,10 @@ const [Modal, modalApi] = useVbenModal({
118 118 #set ($index = $foreach.count - 1)
119 119 #set ($subClassNameVar = $subClassNameVars.get($index))
120 120 #if ($subTable.subJoinMany) ## 一对多
121   - ## TODO 列表值校验?
  121 + if (!${subClassNameVar}FormRef.value?.validate()) {
  122 + subTabsName.value = '${subClassNameVar}';
  123 + return;
  124 + }
122 125 #else
123 126 try {
124 127 await ${subClassNameVar}FormRef.value?.validate()
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/import.vue.vm
... ... @@ -46,6 +46,11 @@ function beforeUpload(file: File) {
46 46 return false;
47 47 }
48 48  
  49 +/** 移除文件 */
  50 +function handleRemove() {
  51 + fileRef.value = null;
  52 +}
  53 +
49 54 /** 下载导入模板 */
50 55 async function handleDownload() {
51 56 const data = await import${simpleClassName}Template();
... ... @@ -56,7 +61,8 @@ async function handleDownload() {
56 61 <template>
57 62 <Modal title="导入${table.classComment}" class="w-1/3">
58 63 <div class="mx-4">
59   - <Upload :max-count="1" accept=".xls,.xlsx" :before-upload="beforeUpload">
  64 + <Upload :max-count="1" accept=".xls,.xlsx" :before-upload="beforeUpload"
  65 + :on-remove="handleRemove">
60 66 <Button type="primary"> 选择 Excel 文件 </Button>
61 67 </Upload>
62 68 </div>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/index.vue.vm
... ... @@ -77,12 +77,7 @@ const queryParams = reactive({
77 77 #end
78 78 #foreach ($column in $columns)
79 79 #if ($column.listOperation)
80   - #if ($column.listOperationCondition != 'BETWEEN')
81   - $column.javaField: undefined,
82   - #end
83   - #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
84   - $column.javaField: undefined,
85   - #end
  80 + $column.javaField: undefined,
86 81 #end
87 82 #end
88 83 })
... ... @@ -209,7 +204,17 @@ function handleRowCheckboxChange({
209 204 async function handleExport() {
210 205 try {
211 206 exportLoading.value = true;
212   - const data = await export${simpleClassName}(queryParams);
  207 + const params = cloneDeep(queryParams) as any;
  208 + #foreach ($column in $columns)
  209 + #if ($column.listOperation)
  210 + #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
  211 + if (params.${column.javaField} && Array.isArray(params.${column.javaField})) {
  212 + params.${column.javaField} = (params.${column.javaField} as string[]).join(',');
  213 + }
  214 + #end
  215 + #end
  216 + #end
  217 + const data = await export${simpleClassName}(params);
213 218 downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
214 219 }finally {
215 220 exportLoading.value = false;
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_normal.vue.vm
... ... @@ -18,7 +18,7 @@ import { computed, ref, h, onMounted, watch, nextTick } from &#39;vue&#39;;
18 18 import { DICT_TYPE } from '@vben/constants';
19 19 import { getDictOptions } from '@vben/hooks';
20 20  
21   -import { Button, Checkbox, CheckboxGroup, DatePicker#if (!$subTable.subJoinMany), Form, FormItem#end, Input, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end, TextArea } from 'antdv-next';
  21 +import { Button, Checkbox, CheckboxGroup, DatePicker#if (!$subTable.subJoinMany), Form, FormItem#end, Input#if ($subTable.subJoinMany), message#end, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end, TextArea } from 'antdv-next';
22 22 import { $t } from '#/locales';
23 23  
24 24 #if ($subTable.subJoinMany) ## 一对多
... ... @@ -52,25 +52,54 @@ async function handleDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
52 52 await tableRef.value?.remove(row);
53 53 }
54 54  
55   -/** 提供获取表格数据的方法供父组件调用 */
56   -defineExpose({
57   - getData: (): ${simpleClassName}Api.${subSimpleClassName}[] => {
58   - const data = list.value as ${simpleClassName}Api.${subSimpleClassName}[];
59   - const removeRecords = tableRef.value?.getRemoveRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
60   - const insertRecords = tableRef.value?.getInsertRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
61   - return [
62   - ...data.filter(
63   - (row) => !removeRecords.some((removed) => removed.id === row.id),
64   - ),
65   - ...insertRecords.map((row: any) => ({ ...row, id: undefined })),
66   - ];
67   - },
68   -});
  55 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  56 +function getData(): ${simpleClassName}Api.${subSimpleClassName}[] {
  57 + const table = tableRef.value;
  58 + if (!table) {
  59 + return [];
  60 + }
  61 + const { fullData } = table.getTableData();
  62 + const inserted = new Set(table.getInsertRecords());
  63 + return fullData.map((row) =>
  64 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  65 + );
  66 +}
  67 +
  68 +#set ($hasRequiredColumn = false)
  69 +#foreach ($column in $subColumns)
  70 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  71 +#set ($hasRequiredColumn = true)
  72 +#end
  73 +#end
  74 +/** 校验子表必填字段 */
  75 +function validate(): boolean {
  76 +#if ($hasRequiredColumn)
  77 + for (const [index, row] of getData().entries()) {
  78 +#foreach ($column in $subColumns)
  79 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  80 + if (
  81 + row.${column.javaField} === null ||
  82 + row.${column.javaField} === undefined ||
  83 + (typeof row.${column.javaField} === 'string' && String(row.${column.javaField}).trim() === '') ||
  84 + (Array.isArray(row.${column.javaField}) && row.${column.javaField}.length === 0)
  85 + ) {
  86 + message.error(`第 #[[${index + 1}]]# 行:${column.columnComment}不能为空`);
  87 + return false;
  88 + }
  89 +#end
  90 +#end
  91 + }
  92 +#end
  93 + return true;
  94 +}
  95 +
  96 +defineExpose({ getData, validate });
69 97  
70 98 /** 监听主表的关联字段的变化,加载对应的子表数据 */
71 99 watch(
72 100 () => props.${subJoinColumn.javaField},
73 101 async (val) => {
  102 + list.value = [];
74 103 if (!val) {
75 104 return;
76 105 }
... ... @@ -109,6 +138,17 @@ defineExpose({
109 138 watch(
110 139 () => props.${subJoinColumn.javaField},
111 140 async (val) => {
  141 + formData.value = {
  142 +#foreach ($column in $subColumns)
  143 +#if ($column.createOperation || $column.updateOperation)
  144 +#if ($column.htmlType == "checkbox")
  145 + $column.javaField: [],
  146 +#else
  147 + $column.javaField: undefined,
  148 +#end
  149 +#end
  150 +#end
  151 + };
112 152 if (!val) {
113 153 return;
114 154 }
... ... @@ -126,6 +166,7 @@ watch(
126 166 #foreach($column in $subColumns)
127 167 #if ($column.createOperation || $column.updateOperation)
128 168 #set ($comment = $column.columnComment)
  169 + #set ($dictType = $column.dictType)
129 170 #set ($javaField = $column.javaField)
130 171 #set ($javaType = $column.javaType)
131 172 #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/data.ts.vm
... ... @@ -36,7 +36,7 @@ export function useFormSchema(): VbenFormSchema[] {
36 36 id: 0,
37 37 ${treeNameColumn.javaField}: '顶级${table.classComment}',
38 38 });
39   - return handleTree(data);
  39 + return handleTree(data, 'id', '${treeParentColumn.javaField}');
40 40 },
41 41 labelField: '${treeNameColumn.javaField}',
42 42 valueField: 'id',
... ... @@ -89,7 +89,7 @@ export function useFormSchema(): VbenFormSchema[] {
89 89 placeholder: '请选择${comment}',
90 90 },
91 91 #elseif($column.htmlType == "checkbox")## 多选框
92   - component: 'Checkbox',
  92 + component: 'CheckboxGroup',
93 93 componentProps: {
94 94 #if ("" != $dictType)## 有数据字典
95 95 options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
... ... @@ -305,7 +305,7 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
305 305 placeholder: '请选择${comment}',
306 306 },
307 307 #elseif($column.htmlType == "checkbox")## 多选框
308   - component: 'Checkbox',
  308 + component: 'CheckboxGroup',
309 309 componentProps: {
310 310 #if ("" != $dictType)## 有数据字典
311 311 options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
... ... @@ -444,6 +444,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
444 444 #if ($column.createOperation || $column.updateOperation)
445 445 #if (!$column.primaryKey && $column.listOperationResult && $column.id != $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
446 446 #set ($dictType = $column.dictType)
  447 + #set ($javaType = $column.javaType)
447 448 #set ($javaField = $column.javaField)
448 449 #set ($comment = $column.columnComment)
449 450 #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
... ... @@ -538,7 +539,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
538 539 placeholder: '请选择${comment}',
539 540 },
540 541 #elseif($column.htmlType == "checkbox")## 多选框
541   - component: 'Checkbox',
  542 + component: 'CheckboxGroup',
542 543 componentProps: {
543 544 #if ("" != $dictType)## 有数据字典
544 545 options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
... ... @@ -587,7 +588,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
587 588 #end
588 589 #if ($table.templateType == 12) ## 内嵌情况
589 590 /** 列表的字段 */
590   - export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
  591 + export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${apiName}.${subSimpleClassName}>['columns'] {
591 592 return [
592 593 #foreach($column in $subColumns)
593 594 #if ($column.listOperationResult)
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/form.vue.vm
... ... @@ -69,15 +69,11 @@ const [Modal, modalApi] = useVbenModal({
69 69 #foreach ($subTable in $subTables)
70 70 #set ($index = $foreach.count - 1)
71 71 #set ($subClassNameVar = $subClassNameVars.get($index))
72   - #if ($subTable.subJoinMany) ## 一对多
73   - ## TODO 列表值校验?
74   - #else
75 72 const ${subClassNameVar}Valid = await ${subClassNameVar}FormRef.value?.validate();
76 73 if (!${subClassNameVar}Valid) {
77 74 subTabsName.value = '${subClassNameVar}';
78 75 return;
79 76 }
80   - #end
81 77 #end
82 78 #end
83 79 #end
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/import.vue.vm
... ... @@ -49,6 +49,11 @@ function beforeUpload(file: File) {
49 49 return false;
50 50 }
51 51  
  52 +/** 移除文件 */
  53 +function handleRemove() {
  54 + formApi.setFieldValue('file', undefined);
  55 +}
  56 +
52 57 /** 下载模板 */
53 58 async function handleDownload() {
54 59 const data = await import${simpleClassName}Template();
... ... @@ -65,6 +70,7 @@ async function handleDownload() {
65 70 :max-count="1"
66 71 accept=".xls,.xlsx"
67 72 :before-upload="beforeUpload"
  73 + :on-remove="handleRemove"
68 74 >
69 75 <Button type="primary"> 选择 Excel 文件 </Button>
70 76 </Upload>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_normal.vue.vm
... ... @@ -26,6 +26,7 @@ import {
26 26 CheckboxGroup,
27 27 DatePicker,
28 28 Input,
  29 + message,
29 30 RadioGroup,
30 31 Select,
31 32 #if ($selectOptionEnable)
... ... @@ -77,26 +78,54 @@ const handleDelete = async (row: ${apiName}.${subSimpleClassName}) =&gt; {
77 78 await gridApi.grid.remove(row);
78 79 }
79 80  
80   -/** 提供获取表格数据的方法供父组件调用 */
81   -defineExpose({
82   - getData: (): ${apiName}.${subSimpleClassName}[] => {
83   - const data = gridApi.grid.getData() as ${apiName}.${subSimpleClassName}[];
84   - const removeRecords = gridApi.grid.getRemoveRecords() as ${apiName}.${subSimpleClassName}[];
85   - const insertRecords = gridApi.grid.getInsertRecords() as ${apiName}.${subSimpleClassName}[];
86   - return data
87   - .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
88   - .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
89   - },
90   -});
  81 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  82 +function getData(): ${apiName}.${subSimpleClassName}[] {
  83 + const { fullData } = gridApi.grid.getTableData();
  84 + const inserted = new Set(gridApi.grid.getInsertRecords());
  85 + return fullData.map((row) =>
  86 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  87 + );
  88 +}
  89 +
  90 +#set ($hasRequiredColumn = false)
  91 +#foreach ($column in $subColumns)
  92 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  93 +#set ($hasRequiredColumn = true)
  94 +#end
  95 +#end
  96 +/** 校验子表必填字段 */
  97 +function validate(): boolean {
  98 +#if ($hasRequiredColumn)
  99 + for (const [index, row] of getData().entries()) {
  100 +#foreach ($column in $subColumns)
  101 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  102 + if (
  103 + row.${column.javaField} === null ||
  104 + row.${column.javaField} === undefined ||
  105 + (typeof row.${column.javaField} === 'string' && String(row.${column.javaField}).trim() === '') ||
  106 + (Array.isArray(row.${column.javaField}) && row.${column.javaField}.length === 0)
  107 + ) {
  108 + message.error(`第 #[[${index + 1}]]# 行:${column.columnComment}不能为空`);
  109 + return false;
  110 + }
  111 +#end
  112 +#end
  113 + }
  114 +#end
  115 + return true;
  116 +}
  117 +
  118 +defineExpose({ getData, validate });
91 119  
92 120 /** 监听主表的关联字段的变化,加载对应的子表数据 */
93 121 watch(
94 122 () => props.${subJoinColumn.javaField},
95 123 async (val) => {
  124 + await nextTick();
96 125 if (!val) {
  126 + await gridApi.grid.loadData([]);
97 127 return;
98 128 }
99   - await nextTick();
100 129 await gridApi.grid.loadData(await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
101 130 },
102 131 { immediate: true },
... ... @@ -128,10 +157,11 @@ defineExpose({
128 157 watch(
129 158 () => props.${subJoinColumn.javaField},
130 159 async (val) => {
  160 + await nextTick();
  161 + await formApi.resetForm();
131 162 if (!val) {
132 163 return;
133 164 }
134   - await nextTick();
135 165 await formApi.setValues(await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
136 166 },
137 167 { immediate: true },
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/form.vue.vm
1 1 <script lang="ts" setup>
2   -import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  2 +import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
3 3 import type { FormRules } from 'element-plus';
4 4  
5 5 import { useVbenModal } from '@vben/common-ui';
... ... @@ -9,7 +9,7 @@ import { Tinymce as RichTextarea } from &#39;#/components/tinymce&#39;;
9 9 import { ImageUpload, FileUpload } from "#/components/upload";
10 10 import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker, ElTreeSelect } from 'element-plus';
11 11 #if($table.templateType == 2)## 树表需要导入这些
12   -import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  12 +import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}';
13 13 import { handleTree } from '@vben/utils'
14 14 #end
15 15 ## 特殊:主子表专属逻辑
... ... @@ -23,7 +23,7 @@ import { handleTree } from &#39;@vben/utils&#39;
23 23  
24 24 import { computed, ref, reactive } from 'vue';
25 25 import { $t } from '#/locales';
26   -import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  26 +import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
27 27  
28 28 const emit = defineEmits(['success']);
29 29  
... ... @@ -111,7 +111,10 @@ const [Modal, modalApi] = useVbenModal({
111 111 #set ($index = $foreach.count - 1)
112 112 #set ($subClassNameVar = $subClassNameVars.get($index))
113 113 #if ($subTable.subJoinMany) ## 一对多
114   - ## TODO 列表值校验?
  114 + if (!${subClassNameVar}FormRef.value?.validate()) {
  115 + subTabsName.value = '${subClassNameVar}';
  116 + return;
  117 + }
115 118 #else
116 119 try {
117 120 await ${subClassNameVar}FormRef.value?.validate()
... ... @@ -180,7 +183,7 @@ const [Modal, modalApi] = useVbenModal({
180 183  
181 184 <template>
182 185 <Modal :title="getTitle">
183   - <el-form
  186 + <ElForm
184 187 ref="formRef"
185 188 :model="formData"
186 189 :rules="rules"
... ... @@ -201,8 +204,8 @@ const [Modal, modalApi] = useVbenModal({
201 204 #set ($dictMethod = "boolean")
202 205 #end
203 206 #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
204   - <el-form-item label="${comment}" prop="${javaField}">
205   - <el-tree-select
  207 + <ElFormItem label="${comment}" prop="${javaField}">
  208 + <ElTreeSelect
206 209 v-model="formData.${javaField}"
207 210 :data="${classNameVar}Tree"
208 211 #if ($treeNameColumn.javaField == "name")
... ... @@ -222,100 +225,100 @@ const [Modal, modalApi] = useVbenModal({
222 225 default-expand-all
223 226 placeholder="请选择${comment}"
224 227 />
225   - </el-form-item>
  228 + </ElFormItem>
226 229 #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
227   - <el-form-item label="${comment}" prop="${javaField}">
228   - <el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
229   - </el-form-item>
  230 + <ElFormItem label="${comment}" prop="${javaField}">
  231 + <ElInput v-model="formData.${javaField}" placeholder="请输入${comment}" />
  232 + </ElFormItem>
230 233 #elseif($column.htmlType == "imageUpload")## 图片上传
231   - <el-form-item label="${comment}" prop="${javaField}">
  234 + <ElFormItem label="${comment}" prop="${javaField}">
232 235 <ImageUpload v-model="formData.${javaField}" />
233   - </el-form-item>
  236 + </ElFormItem>
234 237 #elseif($column.htmlType == "fileUpload")## 文件上传
235   - <el-form-item label="${comment}" prop="${javaField}">
  238 + <ElFormItem label="${comment}" prop="${javaField}">
236 239 <FileUpload v-model="formData.${javaField}" />
237   - </el-form-item>
  240 + </ElFormItem>
238 241 #elseif($column.htmlType == "editor")## 文本编辑器
239   - <el-form-item label="${comment}" prop="${javaField}">
  242 + <ElFormItem label="${comment}" prop="${javaField}">
240 243 <RichTextarea v-model="formData.${javaField}" height="500px" />
241   - </el-form-item>
  244 + </ElFormItem>
242 245 #elseif($column.htmlType == "select")## 下拉框
243   - <el-form-item label="${comment}" prop="${javaField}">
244   - <el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
  246 + <ElFormItem label="${comment}" prop="${javaField}">
  247 + <ElSelect v-model="formData.${javaField}" placeholder="请选择${comment}">
245 248 #if ("" != $dictType)## 有数据字典
246   - <el-option
  249 + <ElOption
247 250 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
248 251 :key="dict.value"
249 252 :value="dict.value"
250 253 :label="dict.label"
251 254 />
252 255 #else##没数据字典
253   - <el-option label="请选择字典生成" value="" />
  256 + <ElOption label="请选择字典生成" value="" />
254 257 #end
255   - </el-select>
256   - </el-form-item>
  258 + </ElSelect>
  259 + </ElFormItem>
257 260 #elseif($column.htmlType == "checkbox")## 多选框
258   - <el-form-item label="${comment}" prop="${javaField}">
259   - <el-checkbox-group v-model="formData.${javaField}">
  261 + <ElFormItem label="${comment}" prop="${javaField}">
  262 + <ElCheckboxGroup v-model="formData.${javaField}">
260 263 #if ("" != $dictType)## 有数据字典
261   - <el-checkbox
  264 + <ElCheckbox
262 265 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
263 266 :key="dict.value"
264   - :label="dict.value"
  267 + :value="dict.value"
265 268 >
266 269 {{ dict.label }}
267   - </el-checkbox>
  270 + </ElCheckbox>
268 271 #else##没数据字典
269   - <el-checkbox label="请选择字典生成" />
  272 + <ElCheckbox label="请选择字典生成" />
270 273 #end
271   - </el-checkbox-group>
272   - </el-form-item>
  274 + </ElCheckboxGroup>
  275 + </ElFormItem>
273 276 #elseif($column.htmlType == "radio")## 单选框
274   - <el-form-item label="${comment}" prop="${javaField}">
275   - <el-radio-group v-model="formData.${javaField}">
  277 + <ElFormItem label="${comment}" prop="${javaField}">
  278 + <ElRadioGroup v-model="formData.${javaField}">
276 279 #if ("" != $dictType)## 有数据字典
277   - <el-radio
  280 + <ElRadio
278 281 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
279 282 :key="dict.value"
280   - :label="dict.value"
  283 + :value="dict.value"
281 284 >
282 285 {{ dict.label }}
283   - </el-radio>
  286 + </ElRadio>
284 287 #else##没数据字典
285   - <el-radio :label="1">请选择字典生成</el-radio>
  288 + <ElRadio :label="1">请选择字典生成</ElRadio>
286 289 #end
287   - </el-radio-group>
288   - </el-form-item>
  290 + </ElRadioGroup>
  291 + </ElFormItem>
289 292 #elseif($column.htmlType == "datetime")## 时间框
290   - <el-form-item label="${comment}" prop="${javaField}">
291   - <el-date-picker
  293 + <ElFormItem label="${comment}" prop="${javaField}">
  294 + <ElDatePicker
292 295 v-model="formData.${javaField}"
293 296 value-format="x"
294 297 placeholder="选择${comment}"
295 298 />
296   - </el-form-item>
  299 + </ElFormItem>
297 300 #elseif($column.htmlType == "textarea")## 文本框
298   - <el-form-item label="${comment}" prop="${javaField}">
299   - <el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
300   - </el-form-item>
  301 + <ElFormItem label="${comment}" prop="${javaField}">
  302 + <ElInput v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
  303 + </ElFormItem>
301 304 #end
302 305 #end
303 306 #end
304   - </el-form>
  307 + </ElForm>
305 308 ## 特殊:主子表专属逻辑
306 309 #if ( $table.templateType == 10 || $table.templateType == 12 )
307 310 <!-- 子表的表单 -->
308   - <el-tabs v-model="subTabsName">
  311 + <ElTabs v-model="subTabsName">
309 312 #foreach ($subTable in $subTables)
310 313 #set ($index = $foreach.count - 1)
311 314 #set ($subClassNameVar = $subClassNameVars.get($index))
312 315 #set ($subSimpleClassName = $subSimpleClassNames.get($index))
313 316 #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
314   - <el-tab-pane name="$subClassNameVar" label="${subTable.classComment}">
  317 + <ElTabPane name="$subClassNameVar" label="${subTable.classComment}">
315 318 <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData?.id" />
316   - </el-tab-pane>
  319 + </ElTabPane>
317 320 #end
318   - </el-tabs>
  321 + </ElTabs>
319 322 #end
320 323 </Modal>
321 324 </template>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/import.vue.vm
... ... @@ -47,6 +47,16 @@ function handleChange(file: any) {
47 47 }
48 48 }
49 49  
  50 +/** 移除文件 */
  51 +function handleRemove() {
  52 + fileRef.value = null;
  53 +}
  54 +
  55 +/** 超出文件数量 */
  56 +function handleExceed() {
  57 + ElMessage.warning('只能选择一个文件,请先移除已选文件');
  58 +}
  59 +
50 60 /** 下载导入模板 */
51 61 async function handleDownload() {
52 62 const data = await import${simpleClassName}Template();
... ... @@ -61,6 +71,8 @@ async function handleDownload() {
61 71 :limit="1"
62 72 accept=".xls,.xlsx"
63 73 :on-change="handleChange"
  74 + :on-remove="handleRemove"
  75 + :on-exceed="handleExceed"
64 76 :auto-upload="false"
65 77 >
66 78 <ElButton type="primary"> 选择 Excel 文件 </ElButton>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm
1 1 <script lang="ts" setup>
2   -import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  2 +import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
3 3  
4 4 import { ref, h, reactive, onMounted, nextTick } from 'vue';
5 5  
... ... @@ -26,10 +26,10 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;;
26 26 import { $t } from '#/locales';
27 27 #if (${table.templateType} == 2)## 树表接口
28 28 import { handleTree,isEmpty } from '@vben/utils'
29   -import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  29 +import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
30 30 #else## 标准表接口
31 31 import { isEmpty } from '@vben/utils';
32   -import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  32 +import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
33 33 #end
34 34 #if ($importEnable)
35 35 import ${simpleClassName}Import from './modules/import-form.vue';
... ... @@ -65,12 +65,7 @@ const queryParams = reactive({
65 65 #end
66 66 #foreach ($column in $columns)
67 67 #if ($column.listOperation)
68   - #if ($column.listOperationCondition != 'BETWEEN')
69   - $column.javaField: undefined,
70   - #end
71   - #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
72   - $column.javaField: undefined,
73   - #end
  68 + $column.javaField: undefined,
74 69 #end
75 70 #end
76 71 })
... ... @@ -195,7 +190,17 @@ function handleRowCheckboxChange({
195 190 async function handleExport() {
196 191 try {
197 192 exportLoading.value = true;
198   - const data = await export${simpleClassName}(queryParams);
  193 + const params = cloneDeep(queryParams) as any;
  194 + #foreach ($column in $columns)
  195 + #if ($column.listOperation)
  196 + #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
  197 + if (params.${column.javaField} && Array.isArray(params.${column.javaField})) {
  198 + params.${column.javaField} = (params.${column.javaField} as string[]).join(',');
  199 + }
  200 + #end
  201 + #end
  202 + #end
  203 + const data = await export${simpleClassName}(params);
199 204 downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
200 205 }finally {
201 206 exportLoading.value = false;
... ... @@ -228,7 +233,7 @@ onMounted(() =&gt; {
228 233  
229 234 <ContentWrap v-if="!hiddenSearchBar">
230 235 <!-- 搜索工作栏 -->
231   - <el-form
  236 + <ElForm
232 237 :model="queryParams"
233 238 ref="queryFormRef"
234 239 inline
... ... @@ -247,49 +252,49 @@ onMounted(() =&gt; {
247 252 #set ($dictMethod = "boolean")
248 253 #end
249 254 #if ($column.htmlType == "input")
250   - <el-form-item label="${comment}">
251   - <el-input
  255 + <ElFormItem label="${comment}">
  256 + <ElInput
252 257 v-model="queryParams.${javaField}"
253 258 placeholder="请输入${comment}"
254 259 clearable
255 260 @keyup.enter="handleQuery"
256 261 class="!w-[240px]"
257 262 />
258   - </el-form-item>
  263 + </ElFormItem>
259 264 #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
260   - <el-form-item label="${comment}">
261   - <el-select
  265 + <ElFormItem label="${comment}">
  266 + <ElSelect
262 267 v-model="queryParams.${javaField}"
263 268 placeholder="请选择${comment}"
264 269 clearable
265 270 class="!w-[240px]"
266 271 >
267 272 #if ("" != $dictType)## 设置了 dictType 数据字典的情况
268   - <el-option
  273 + <ElOption
269 274 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
270 275 :key="dict.value"
271 276 :value="dict.value"
272 277 :label="dict.label"
273 278 />
274 279 #else## 未设置 dictType 数据字典的情况
275   - <el-option label="请选择字典生成" value="" />
  280 + <ElOption label="请选择字典生成" value="" />
276 281 #end
277   - </el-select>
278   - </el-form-item>
  282 + </ElSelect>
  283 + </ElFormItem>
279 284 #elseif($column.htmlType == "datetime")
280 285 #if ($column.listOperationCondition != "BETWEEN")## 非范围
281   - <el-form-item label="${comment}">
282   - <el-date-picker
  286 + <ElFormItem label="${comment}">
  287 + <ElDatePicker
283 288 v-model="queryParams.${javaField}"
284 289 value-format="YYYY-MM-DD"
285 290 placeholder="选择${comment}"
286 291 clearable
287 292 class="!w-[240px]"
288 293 />
289   - </el-form-item>
  294 + </ElFormItem>
290 295 #else## 范围
291   - <el-form-item label="${comment}">
292   - <el-date-picker
  296 + <ElFormItem label="${comment}">
  297 + <ElDatePicker
293 298 v-model="queryParams.${javaField}"
294 299 type="daterange"
295 300 value-format="YYYY-MM-DD"
... ... @@ -298,33 +303,33 @@ onMounted(() =&gt; {
298 303 end-placeholder="结束日期"
299 304 class="!w-[240px]"
300 305 />
301   - </el-form-item>
  306 + </ElFormItem>
302 307 #end
303 308 #end
304 309 #end
305 310 #end
306   - <el-form-item>
307   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
308   - <el-button class="ml-2" @click="handleQuery" type="primary">
  311 + <ElFormItem>
  312 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  313 + <ElButton class="ml-2" @click="handleQuery" type="primary">
309 314 搜索
310   - </el-button>
311   - </el-form-item>
312   - </el-form>
  315 + </ElButton>
  316 + </ElFormItem>
  317 + </ElForm>
313 318 </ContentWrap>
314 319  
315 320 <!-- 列表 -->
316 321 <ContentWrap title="${table.classComment}">
317 322 <template #extra>
318   - <TableToolbar
  323 + <VbenVxeTableToolbar
319 324 ref="tableToolbarRef"
320 325 v-model:hidden-search="hiddenSearchBar"
321 326 >
322 327 #if (${table.templateType} == 2)
323   - <el-button @click="handleExpand" class="mr-2">
  328 + <ElButton @click="handleExpand" class="mr-2">
324 329 {{ isExpanded ? '收缩' : '展开' }}
325   - </el-button>
  330 + </ElButton>
326 331 #end
327   - <el-button
  332 + <ElButton
328 333 class="ml-2"
329 334 :icon="h(Plus)"
330 335 type="primary"
... ... @@ -332,18 +337,18 @@ onMounted(() =&gt; {
332 337 v-access:code="['${permissionPrefix}:create']"
333 338 >
334 339 {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
335   - </el-button>
  340 + </ElButton>
336 341 #if ($importEnable)
337   - <el-button
  342 + <ElButton
338 343 class="ml-2"
339 344 type="primary"
340 345 @click="handleImport"
341 346 v-access:code="['${permissionPrefix}:import']"
342 347 >
343 348 导入
344   - </el-button>
  349 + </ElButton>
345 350 #end
346   - <el-button
  351 + <ElButton
347 352 :icon="h(Download)"
348 353 type="primary"
349 354 class="ml-2"
... ... @@ -352,9 +357,9 @@ onMounted(() =&gt; {
352 357 v-access:code="['${permissionPrefix}:export']"
353 358 >
354 359 {{ $t('ui.actionTitle.export') }}
355   - </el-button>
  360 + </ElButton>
356 361 #if ($table.templateType != 2 && $deleteBatchEnable)
357   - <el-button
  362 + <ElButton
358 363 :icon="h(Trash2)"
359 364 type="danger"
360 365 class="ml-2"
... ... @@ -363,11 +368,11 @@ onMounted(() =&gt; {
363 368 v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:delete']"
364 369 >
365 370 批量删除
366   - </el-button>
  371 + </ElButton>
367 372 #end
368   - </TableToolbar>
  373 + </VbenVxeTableToolbar>
369 374 </template>
370   - <vxe-table
  375 + <VxeTable
371 376 ref="tableRef"
372 377 :data="list"
373 378 #if ( $table.templateType == 2 )
... ... @@ -395,27 +400,27 @@ onMounted(() =&gt; {
395 400 #end
396 401 >
397 402 #if ($table.templateType != 2 && $deleteBatchEnable)
398   - <vxe-column type="checkbox" width="40"></vxe-column>
  403 + <VxeColumn type="checkbox" width="40"></VxeColumn>
399 404 #end
400 405 ## 特殊:主子表专属逻辑
401 406 #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
402 407 <!-- 子表的列表 -->
403   - <vxe-column type="expand" width="60">
  408 + <VxeColumn type="expand" width="60">
404 409 <template #content="{ row }">
405 410 <!-- 子表的表单 -->
406   - <el-tabs v-model="subTabsName" class="mx-8">
  411 + <ElTabs v-model="subTabsName" class="mx-8">
407 412 #foreach ($subTable in $subTables)
408 413 #set ($index = $foreach.count - 1)
409 414 #set ($subClassNameVar = $subClassNameVars.get($index))
410 415 #set ($subSimpleClassName = $subSimpleClassNames.get($index))
411 416 #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
412   - <el-tab-pane name="$subClassNameVar" label="${subTable.classComment}">
  417 + <ElTabPane name="$subClassNameVar" label="${subTable.classComment}">
413 418 <${subSimpleClassName}List :${subJoinColumn_strikeCase}="row?.id" />
414   - </el-tab-pane>
  419 + </ElTabPane>
415 420 #end
416   - </el-tabs>
  421 + </ElTabs>
417 422 </template>
418   - </vxe-column>
  423 + </VxeColumn>
419 424 #end
420 425 #foreach($column in $columns)
421 426 #if ($column.listOperationResult)
... ... @@ -424,28 +429,28 @@ onMounted(() =&gt; {
424 429 #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
425 430 #set ($comment=$column.columnComment)
426 431 #if ($column.javaType == "LocalDateTime")## 时间类型
427   - <vxe-column field="${javaField}" title="${comment}" align="center">
  432 + <VxeColumn field="${javaField}" title="${comment}" align="center">
428 433 <template #default="{row}">
429 434 {{formatDateTime(row.${javaField})}}
430 435 </template>
431   - </vxe-column>
  436 + </VxeColumn>
432 437 #elseif($column.dictType && "" != $column.dictType)## 数据字典
433   - <vxe-column field="${javaField}" title="${comment}" align="center">
  438 + <VxeColumn field="${javaField}" title="${comment}" align="center">
434 439 <template #default="{row}">
435 440 <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="row.${javaField}" />
436 441 </template>
437   - </vxe-column>
  442 + </VxeColumn>
438 443 #elseif ($table.templateType == 2 && $javaField == $treeNameColumn.javaField)
439   - <vxe-column field="${javaField}" title="${comment}" align="center" tree-node/>
  444 + <VxeColumn field="${javaField}" title="${comment}" align="center" tree-node/>
440 445 #else
441   - <vxe-column field="${javaField}" title="${comment}" align="center" />
  446 + <VxeColumn field="${javaField}" title="${comment}" align="center" />
442 447 #end
443 448 #end
444 449 #end
445   - <vxe-column field="operation" title="操作" align="center">
  450 + <VxeColumn field="operation" title="操作" align="center">
446 451 <template #default="{row}">
447 452 #if ( $table.templateType == 2 )
448   - <el-button
  453 + <ElButton
449 454 size="small"
450 455 type="primary"
451 456 link
... ... @@ -453,9 +458,9 @@ onMounted(() =&gt; {
453 458 v-access:code="['${permissionPrefix}:create']"
454 459 >
455 460 新增下级
456   - </el-button>
  461 + </ElButton>
457 462 #end
458   - <el-button
  463 + <ElButton
459 464 size="small"
460 465 type="primary"
461 466 link
... ... @@ -463,8 +468,8 @@ onMounted(() =&gt; {
463 468 v-access:code="['${permissionPrefix}:update']"
464 469 >
465 470 {{ $t('ui.actionTitle.edit') }}
466   - </el-button>
467   - <el-button
  471 + </ElButton>
  472 + <ElButton
468 473 size="small"
469 474 type="danger"
470 475 link
... ... @@ -476,14 +481,14 @@ onMounted(() =&gt; {
476 481 v-access:code="['${permissionPrefix}:delete']"
477 482 >
478 483 {{ $t('ui.actionTitle.delete') }}
479   - </el-button>
  484 + </ElButton>
480 485 </template>
481   - </vxe-column>
482   - </vxe-table>
  486 + </VxeColumn>
  487 + </VxeTable>
483 488 #if ( $table.templateType != 2 )
484 489 <!-- 分页 -->
485 490 <div class="mt-2 flex justify-end">
486   - <el-pagination
  491 + <ElPagination
487 492 :total="total"
488 493 v-model:current-page="queryParams.pageNo"
489 494 v-model:page-size="queryParams.pageSize"
... ... @@ -498,17 +503,17 @@ onMounted(() =&gt; {
498 503 #if ($table.templateType == 11) ## erp情况
499 504 <ContentWrap>
500 505 <!-- 子表的表单 -->
501   - <el-tabs v-model="subTabsName">
  506 + <ElTabs v-model="subTabsName">
502 507 #foreach ($subTable in $subTables)
503 508 #set ($index = $foreach.count - 1)
504 509 #set ($subClassNameVar = $subClassNameVars.get($index))
505 510 #set ($subSimpleClassName = $subSimpleClassNames.get($index))
506 511 #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
507   - <el-tab-pane name="$subClassNameVar" label="${subTable.classComment}">
  512 + <ElTabPane name="$subClassNameVar" label="${subTable.classComment}">
508 513 <${subSimpleClassName}List :${subJoinColumn_strikeCase}="select${simpleClassName}?.id" />
509   - </el-tab-pane>
  514 + </ElTabPane>
510 515 #end
511   - </el-tabs>
  516 + </ElTabs>
512 517 </ContentWrap>
513 518 #end
514 519 </Page>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/form_sub_erp.vue.vm
... ... @@ -3,7 +3,7 @@
3 3 #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
4 4 #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
5 5 <script lang="ts" setup>
6   - import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  6 + import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
7 7 import type { FormRules } from 'element-plus';
8 8  
9 9 import { computed, ref, reactive } from 'vue';
... ... @@ -17,7 +17,7 @@
17 17  
18 18 import { $t } from '#/locales';
19 19  
20   - import { get${subSimpleClassName}, create${subSimpleClassName}, update${subSimpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  20 + import { get${subSimpleClassName}, create${subSimpleClassName}, update${subSimpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
21 21  
22 22 const emit = defineEmits(['success']);
23 23 const getTitle = computed(() => {
... ... @@ -107,7 +107,7 @@
107 107  
108 108 <template>
109 109 <Modal :title="getTitle">
110   - <el-form
  110 + <ElForm
111 111 ref="formRef"
112 112 :model="formData"
113 113 :rules="rules"
... ... @@ -128,83 +128,83 @@
128 128 #set ($dictMethod = "boolean")
129 129 #end
130 130 #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
131   - <el-form-item label="${comment}" prop="${javaField}">
132   - <el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
133   - </el-form-item>
  131 + <ElFormItem label="${comment}" prop="${javaField}">
  132 + <ElInput v-model="formData.${javaField}" placeholder="请输入${comment}" />
  133 + </ElFormItem>
134 134 #elseif($column.htmlType == "imageUpload")## 图片上传
135   - <el-form-item label="${comment}" prop="${javaField}">
  135 + <ElFormItem label="${comment}" prop="${javaField}">
136 136 <ImageUpload v-model="formData.${javaField}" />
137   - </el-form-item>
  137 + </ElFormItem>
138 138 #elseif($column.htmlType == "fileUpload")## 文件上传
139   - <el-form-item label="${comment}" prop="${javaField}">
  139 + <ElFormItem label="${comment}" prop="${javaField}">
140 140 <FileUpload v-model="formData.${javaField}" />
141   - </el-form-item>
  141 + </ElFormItem>
142 142 #elseif($column.htmlType == "editor")## 文本编辑器
143   - <el-form-item label="${comment}" prop="${javaField}">
  143 + <ElFormItem label="${comment}" prop="${javaField}">
144 144 <RichTextarea v-model="formData.${javaField}" height="500px" />
145   - </el-form-item>
  145 + </ElFormItem>
146 146 #elseif($column.htmlType == "select")## 下拉框
147   - <el-form-item label="${comment}" prop="${javaField}">
148   - <el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
  147 + <ElFormItem label="${comment}" prop="${javaField}">
  148 + <ElSelect v-model="formData.${javaField}" placeholder="请选择${comment}">
149 149 #if ("" != $dictType)## 有数据字典
150   - <el-option
  150 + <ElOption
151 151 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
152 152 :key="dict.value"
153 153 :value="dict.value"
154 154 :label="dict.label"
155 155 />
156 156 #else##没数据字典
157   - <el-option label="请选择字典生成" value="" />
  157 + <ElOption label="请选择字典生成" value="" />
158 158 #end
159   - </el-select>
160   - </el-form-item>
  159 + </ElSelect>
  160 + </ElFormItem>
161 161 #elseif($column.htmlType == "checkbox")## 多选框
162   - <el-form-item label="${comment}" prop="${javaField}">
163   - <el-checkbox-group v-model="formData.${javaField}">
  162 + <ElFormItem label="${comment}" prop="${javaField}">
  163 + <ElCheckboxGroup v-model="formData.${javaField}">
164 164 #if ("" != $dictType)## 有数据字典
165   - <el-checkbox
  165 + <ElCheckbox
166 166 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
167 167 :key="dict.value"
168   - :label="dict.value"
  168 + :value="dict.value"
169 169 >
170 170 {{ dict.label }}
171   - </el-checkbox>
  171 + </ElCheckbox>
172 172 #else##没数据字典
173   - <el-checkbox label="请选择字典生成" />
  173 + <ElCheckbox label="请选择字典生成" />
174 174 #end
175   - </el-checkbox-group>
176   - </el-form-item>
  175 + </ElCheckboxGroup>
  176 + </ElFormItem>
177 177 #elseif($column.htmlType == "radio")## 单选框
178   - <el-form-item label="${comment}" prop="${javaField}">
179   - <el-radio-group v-model="formData.${javaField}">
  178 + <ElFormItem label="${comment}" prop="${javaField}">
  179 + <ElRadioGroup v-model="formData.${javaField}">
180 180 #if ("" != $dictType)## 有数据字典
181   - <el-radio
  181 + <ElRadio
182 182 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
183 183 :key="dict.value"
184   - :label="dict.value"
  184 + :value="dict.value"
185 185 >
186 186 {{ dict.label }}
187   - </el-radio>
  187 + </ElRadio>
188 188 #else##没数据字典
189   - <el-radio :label="1">请选择字典生成</el-radio>
  189 + <ElRadio :label="1">请选择字典生成</ElRadio>
190 190 #end
191   - </el-radio-group>
192   - </el-form-item>
  191 + </ElRadioGroup>
  192 + </ElFormItem>
193 193 #elseif($column.htmlType == "datetime")## 时间框
194   - <el-form-item label="${comment}" prop="${javaField}">
195   - <el-date-picker
  194 + <ElFormItem label="${comment}" prop="${javaField}">
  195 + <ElDatePicker
196 196 v-model="formData.${javaField}"
197 197 value-format="x"
198 198 placeholder="选择${comment}"
199 199 />
200   - </el-form-item>
  200 + </ElFormItem>
201 201 #elseif($column.htmlType == "textarea")## 文本框
202   - <el-form-item label="${comment}" prop="${javaField}">
203   - <el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
204   - </el-form-item>
  202 + <ElFormItem label="${comment}" prop="${javaField}">
  203 + <ElInput v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
  204 + </ElFormItem>
205 205 #end
206 206 #end
207 207 #end
208   - </el-form>
  208 + </ElForm>
209 209 </Modal>
210 210 </template>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/form_sub_normal.vue.vm
... ... @@ -5,26 +5,61 @@
5 5 #set ($subClassNameVar = $subClassNameVars.get($subIndex))
6 6 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
7 7 <script lang="ts" setup>
8   -import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  8 +#if ($subTable.subJoinMany)
  9 +import type { VxeTableInstance } from '#/adapter/vxe-table';
  10 +#else
  11 +import type { FormRules } from 'element-plus';
  12 +#end
  13 +import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
9 14  
10   -import { computed, ref, reactive, h, onMounted,watch,nextTick } from 'vue';
  15 +#if ($subTable.subJoinMany)
  16 +import { ref, watch } from 'vue';
  17 +#else
  18 +import { nextTick, reactive, ref, watch } from 'vue';
  19 +#end
11 20  
12 21 import { DICT_TYPE } from '@vben/constants';
13 22 import { getDictOptions } from '@vben/hooks';
  23 +#if ($subTable.subJoinMany)
  24 +import { Plus } from '@vben/icons';
  25 +#end
14 26  
15   -import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElButton, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker } from 'element-plus';
16   -import { $t } from '#/locales';
  27 +#set ($hasCheckbox = false)
  28 +#foreach ($column in $subColumns)
  29 +#if (($column.createOperation || $column.updateOperation) && $column.id != $subJoinColumn.id && $column.htmlType == "checkbox")
  30 +#set ($hasCheckbox = true)
  31 +#end
  32 +#end
  33 +import {
  34 +#if ($subTable.subJoinMany)
  35 + ElButton,
  36 +#else
  37 + ElForm,
  38 + ElFormItem,
  39 +#end
  40 +#if ($hasCheckbox)
  41 + ElCheckbox,
  42 + ElCheckboxGroup,
  43 +#end
  44 + ElDatePicker,
  45 + ElInput,
  46 +#if ($subTable.subJoinMany)
  47 + ElMessage,
  48 +#end
  49 + ElOption,
  50 + ElRadio,
  51 + ElRadioGroup,
  52 + ElSelect,
  53 +} from 'element-plus';
17 54  
18 55 #if ($subTable.subJoinMany) ## 一对多
19   -import type { VxeTableInstance } from '#/adapter/vxe-table';
20   -import { Plus } from "@vben/icons";
21 56 import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
22   -import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  57 +import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
23 58 #else
24   -import type { FormRules } from 'element-plus';
25 59 import { Tinymce as RichTextarea } from '#/components/tinymce';
26   -import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  60 +import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
27 61 #end
  62 +import { $t } from '#/locales';
28 63  
29 64 const props = defineProps<{
30 65 ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
... ... @@ -43,24 +78,54 @@ async function onDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
43 78 await tableRef.value?.remove(row);
44 79 }
45 80  
46   -/** 提供获取表格数据的方法供父组件调用 */
47   -defineExpose({
48   - getData: (): ${simpleClassName}Api.${subSimpleClassName}[] => {
49   - const data = list.value as ${simpleClassName}Api.${subSimpleClassName}[];
50   - const removeRecords = tableRef.value?.getRemoveRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
51   - const insertRecords = tableRef.value?.getInsertRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
52   - return [
53   - ...data.filter(
54   - (row) => !removeRecords.some((removed) => removed.id === row.id),
55   - ),
56   - ...insertRecords.map((row: any) => ({ ...row, id: undefined })),
57   - },
58   -});
  81 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  82 +function getData(): ${simpleClassName}Api.${subSimpleClassName}[] {
  83 + const table = tableRef.value;
  84 + if (!table) {
  85 + return [];
  86 + }
  87 + const { fullData } = table.getTableData();
  88 + const inserted = new Set(table.getInsertRecords());
  89 + return fullData.map((row) =>
  90 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  91 + );
  92 +}
  93 +
  94 +#set ($hasRequiredColumn = false)
  95 +#foreach ($column in $subColumns)
  96 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  97 +#set ($hasRequiredColumn = true)
  98 +#end
  99 +#end
  100 +/** 校验子表必填字段 */
  101 +function validate(): boolean {
  102 +#if ($hasRequiredColumn)
  103 + for (const [index, row] of getData().entries()) {
  104 +#foreach ($column in $subColumns)
  105 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  106 + if (
  107 + row.${column.javaField} === null ||
  108 + row.${column.javaField} === undefined ||
  109 + (typeof row.${column.javaField} === 'string' && String(row.${column.javaField}).trim() === '') ||
  110 + (Array.isArray(row.${column.javaField}) && row.${column.javaField}.length === 0)
  111 + ) {
  112 + ElMessage.error(`第 #[[${index + 1}]]# 行:${column.columnComment}不能为空`);
  113 + return false;
  114 + }
  115 +#end
  116 +#end
  117 + }
  118 +#end
  119 + return true;
  120 +}
  121 +
  122 +defineExpose({ getData, validate });
59 123  
60 124 /** 监听主表的关联字段的变化,加载对应的子表数据 */
61 125 watch(
62 126 () => props.${subJoinColumn.javaField},
63 127 async (val) => {
  128 + list.value = [];
64 129 if (!val) {
65 130 return;
66 131 }
... ... @@ -99,6 +164,17 @@ defineExpose({
99 164 watch(
100 165 () => props.${subJoinColumn.javaField},
101 166 async (val) => {
  167 + formData.value = {
  168 +#foreach ($column in $subColumns)
  169 +#if ($column.createOperation || $column.updateOperation)
  170 +#if ($column.htmlType == "checkbox")
  171 + $column.javaField: [],
  172 +#else
  173 + $column.javaField: undefined,
  174 +#end
  175 +#end
  176 +#end
  177 + };
102 178 if (!val) {
103 179 return;
104 180 }
... ... @@ -112,10 +188,11 @@ watch(
112 188  
113 189 <template>
114 190 #if ($subTable.subJoinMany) ## 一对多
115   - <vxe-table ref="tableRef" :data="list" show-overflow class="mx-4">
  191 + <VxeTable ref="tableRef" :data="list" show-overflow class="mx-4">
116 192 #foreach($column in $subColumns)
117 193 #if ($column.createOperation || $column.updateOperation)
118 194 #set ($comment = $column.columnComment)
  195 + #set ($dictType = $column.dictType)
119 196 #set ($javaField = $column.javaField)
120 197 #set ($javaType = $column.javaType)
121 198 #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
... ... @@ -127,99 +204,99 @@ watch(
127 204 #end
128 205 #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
129 206 #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
130   - <vxe-column field="${javaField}" title="${comment}" align="center">
  207 + <VxeColumn field="${javaField}" title="${comment}" align="center">
131 208 <template #default="{row}">
132   - <el-input v-model="row.${javaField}" />
  209 + <ElInput v-model="row.${javaField}" />
133 210 </template>
134   - </vxe-column>
  211 + </VxeColumn>
135 212 #elseif($column.htmlType == "imageUpload")## 图片上传
136   - <vxe-column field="${javaField}" title="${comment}" align="center">
  213 + <VxeColumn field="${javaField}" title="${comment}" align="center">
137 214 <template #default="{row}">
138 215 <ImageUpload v-model="row.${javaField}" />
139 216 </template>
140   - </vxe-column>
  217 + </VxeColumn>
141 218 #elseif($column.htmlType == "fileUpload")## 文件上传
142   - <vxe-column field="${javaField}" title="${comment}" align="center">
  219 + <VxeColumn field="${javaField}" title="${comment}" align="center">
143 220 <template #default="{row}">
144 221 <FileUpload v-model="row.${javaField}" />
145 222 </template>
146   - </vxe-column>
  223 + </VxeColumn>
147 224 #elseif($column.htmlType == "select")## 下拉框
148   - <vxe-column field="${javaField}" title="${comment}" align="center">
  225 + <VxeColumn field="${javaField}" title="${comment}" align="center">
149 226 <template #default="{row}">
150   - <el-select v-model="row.${javaField}" placeholder="请选择${comment}">
  227 + <ElSelect v-model="row.${javaField}" placeholder="请选择${comment}">
151 228 #if ("" != $dictType)## 有数据字典
152   - <el-option
  229 + <ElOption
153 230 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
154 231 :key="dict.value"
155 232 :value="dict.value"
156 233 :label="dict.label"
157 234 />
158 235 #else##没数据字典
159   - <el-option label="请选择字典生成" value="" />
  236 + <ElOption label="请选择字典生成" value="" />
160 237 #end
161   - </el-select>
  238 + </ElSelect>
162 239 </template>
163   - </vxe-column>
  240 + </VxeColumn>
164 241 #elseif($column.htmlType == "checkbox")## 多选框
165   - <vxe-column field="${javaField}" title="${comment}" align="center">
  242 + <VxeColumn field="${javaField}" title="${comment}" align="center">
166 243 <template #default="{row}">
167   - <el-checkbox-group v-model="row.${javaField}">
  244 + <ElCheckboxGroup v-model="row.${javaField}">
168 245 #if ("" != $dictType)## 有数据字典
169   - <el-checkbox
  246 + <ElCheckbox
170 247 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
171 248 :key="dict.value"
172   - :label="dict.value"
  249 + :value="dict.value"
173 250 >
174 251 {{ dict.label }}
175   - </el-checkbox>
  252 + </ElCheckbox>
176 253 #else##没数据字典
177   - <el-checkbox label="请选择字典生成" />
  254 + <ElCheckbox label="请选择字典生成" />
178 255 #end
179   - </el-checkbox-group>
  256 + </ElCheckboxGroup>
180 257 </template>
181   - </vxe-column>
  258 + </VxeColumn>
182 259 #elseif($column.htmlType == "radio")## 单选框
183   - <vxe-column field="${javaField}" title="${comment}" align="center">
  260 + <VxeColumn field="${javaField}" title="${comment}" align="center">
184 261 <template #default="{row}">
185   - <el-radio-group v-model="row.${javaField}">
  262 + <ElRadioGroup v-model="row.${javaField}">
186 263 #if ("" != $dictType)## 有数据字典
187   - <el-radio
  264 + <ElRadio
188 265 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
189 266 :key="dict.value"
190   - :label="dict.value"
  267 + :value="dict.value"
191 268 >
192 269 {{ dict.label }}
193   - </el-radio>
  270 + </ElRadio>
194 271 #else##没数据字典
195   - <el-radio :label="1">请选择字典生成</el-radio>
  272 + <ElRadio :label="1">请选择字典生成</ElRadio>
196 273 #end
197   - </el-radio-group>
  274 + </ElRadioGroup>
198 275 </template>
199   - </vxe-column>
  276 + </VxeColumn>
200 277 #elseif($column.htmlType == "datetime")## 时间框
201   - <vxe-column field="${javaField}" title="${comment}" align="center">
  278 + <VxeColumn field="${javaField}" title="${comment}" align="center">
202 279 <template #default="{row}">
203   - <el-date-picker
  280 + <ElDatePicker
204 281 v-model="row.${javaField}"
205 282 type="datetime"
206 283 value-format="x"
207 284 format="YYYY-MM-DD HH:mm:ss"
208 285 />
209 286 </template>
210   - </vxe-column>
  287 + </VxeColumn>
211 288 #elseif($column.htmlType == "textarea" || $column.htmlType == "editor")## 文本框
212   - <vxe-column field="${javaField}" title="${comment}" align="center">
  289 + <VxeColumn field="${javaField}" title="${comment}" align="center">
213 290 <template #default="{row}">
214   - <el-input v-model="row.${javaField}" type="textarea" />
  291 + <ElInput v-model="row.${javaField}" type="textarea" />
215 292 </template>
216   - </vxe-column>
  293 + </VxeColumn>
217 294 #end
218 295 #end
219 296 #end
220   - <vxe-column field="operation" title="操作" align="center">
  297 + <VxeColumn field="operation" title="操作" align="center">
221 298 <template #default="{row}">
222   - <el-button
  299 + <ElButton
223 300 size="small"
224 301 type="danger"
225 302 link
... ... @@ -227,17 +304,17 @@ watch(
227 304 v-access:code="['${permissionPrefix}:delete']"
228 305 >
229 306 {{ $t('ui.actionTitle.delete') }}
230   - </el-button>
  307 + </ElButton>
231 308 </template>
232   - </vxe-column>
233   - </vxe-table>
  309 + </VxeColumn>
  310 + </VxeTable>
234 311 <div class="flex justify-center mt-4">
235   - <el-button :icon="h(Plus)" type="primary" plain @click="handleAdd" v-access:code="['${permissionPrefix}:create']">
  312 + <ElButton :icon="Plus" type="primary" plain @click="handleAdd" v-access:code="['${permissionPrefix}:create']">
236 313 {{ $t('ui.actionTitle.create', ['${subTable.classComment}']) }}
237   - </el-button>
  314 + </ElButton>
238 315 </div>
239 316 #else
240   - <el-form
  317 + <ElForm
241 318 ref="formRef"
242 319 class="mx-4"
243 320 :model="formData"
... ... @@ -259,83 +336,83 @@ watch(
259 336 #set ($dictMethod = "boolean")
260 337 #end
261 338 #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
262   - <el-form-item label="${comment}" prop="${javaField}">
263   - <el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
264   - </el-form-item>
  339 + <ElFormItem label="${comment}" prop="${javaField}">
  340 + <ElInput v-model="formData.${javaField}" placeholder="请输入${comment}" />
  341 + </ElFormItem>
265 342 #elseif($column.htmlType == "imageUpload")## 图片上传
266   - <el-form-item label="${comment}" prop="${javaField}">
  343 + <ElFormItem label="${comment}" prop="${javaField}">
267 344 <ImageUpload v-model="formData.${javaField}" />
268   - </el-form-item>
  345 + </ElFormItem>
269 346 #elseif($column.htmlType == "fileUpload")## 文件上传
270   - <el-form-item label="${comment}" prop="${javaField}">
  347 + <ElFormItem label="${comment}" prop="${javaField}">
271 348 <FileUpload v-model="formData.${javaField}" />
272   - </el-form-item>
  349 + </ElFormItem>
273 350 #elseif($column.htmlType == "editor")## 文本编辑器
274   - <el-form-item label="${comment}" prop="${javaField}">
  351 + <ElFormItem label="${comment}" prop="${javaField}">
275 352 <RichTextarea v-model="formData.${javaField}" height="500px" />
276   - </el-form-item>
  353 + </ElFormItem>
277 354 #elseif($column.htmlType == "select")## 下拉框
278   - <el-form-item label="${comment}" prop="${javaField}">
279   - <el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
  355 + <ElFormItem label="${comment}" prop="${javaField}">
  356 + <ElSelect v-model="formData.${javaField}" placeholder="请选择${comment}">
280 357 #if ("" != $dictType)## 有数据字典
281   - <el-option
  358 + <ElOption
282 359 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
283 360 :key="dict.value"
284 361 :value="dict.value"
285 362 :label="dict.label"
286 363 />
287 364 #else##没数据字典
288   - <el-option label="请选择字典生成" value="" />
  365 + <ElOption label="请选择字典生成" value="" />
289 366 #end
290   - </el-select>
291   - </el-form-item>
  367 + </ElSelect>
  368 + </ElFormItem>
292 369 #elseif($column.htmlType == "checkbox")## 多选框
293   - <el-form-item label="${comment}" prop="${javaField}">
294   - <el-checkbox-group v-model="formData.${javaField}">
  370 + <ElFormItem label="${comment}" prop="${javaField}">
  371 + <ElCheckboxGroup v-model="formData.${javaField}">
295 372 #if ("" != $dictType)## 有数据字典
296   - <el-checkbox
  373 + <ElCheckbox
297 374 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
298 375 :key="dict.value"
299   - :label="dict.value"
  376 + :value="dict.value"
300 377 >
301 378 {{ dict.label }}
302   - </el-checkbox>
  379 + </ElCheckbox>
303 380 #else##没数据字典
304   - <el-checkbox label="请选择字典生成" />
  381 + <ElCheckbox label="请选择字典生成" />
305 382 #end
306   - </el-checkbox-group>
307   - </el-form-item>
  383 + </ElCheckboxGroup>
  384 + </ElFormItem>
308 385 #elseif($column.htmlType == "radio")## 单选框
309   - <el-form-item label="${comment}" prop="${javaField}">
310   - <el-radio-group v-model="formData.${javaField}">
  386 + <ElFormItem label="${comment}" prop="${javaField}">
  387 + <ElRadioGroup v-model="formData.${javaField}">
311 388 #if ("" != $dictType)## 有数据字典
312   - <el-radio
  389 + <ElRadio
313 390 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
314 391 :key="dict.value"
315   - :label="dict.value"
  392 + :value="dict.value"
316 393 >
317 394 {{ dict.label }}
318   - </el-radio>
  395 + </ElRadio>
319 396 #else##没数据字典
320   - <el-radio :label="1">请选择字典生成</el-radio>
  397 + <ElRadio :label="1">请选择字典生成</ElRadio>
321 398 #end
322   - </el-radio-group>
323   - </el-form-item>
  399 + </ElRadioGroup>
  400 + </ElFormItem>
324 401 #elseif($column.htmlType == "datetime")## 时间框
325   - <el-form-item label="${comment}" prop="${javaField}">
326   - <el-date-picker
  402 + <ElFormItem label="${comment}" prop="${javaField}">
  403 + <ElDatePicker
327 404 v-model="formData.${javaField}"
328 405 value-format="x"
329 406 placeholder="选择${comment}"
330 407 />
331   - </el-form-item>
  408 + </ElFormItem>
332 409 #elseif($column.htmlType == "textarea")## 文本框
333   - <el-form-item label="${comment}" prop="${javaField}">
334   - <el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
335   - </el-form-item>
  410 + <ElFormItem label="${comment}" prop="${javaField}">
  411 + <ElInput v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
  412 + </ElFormItem>
336 413 #end
337 414 #end
338 415 #end
339   - </el-form>
  416 + </ElForm>
340 417 #end
341 418 </template>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm
... ... @@ -6,13 +6,14 @@
6 6 #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex))
7 7 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
8 8 <script lang="ts" setup>
9   - import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  9 + import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
10 10 import type { VxeTableInstance } from '#/adapter/vxe-table';
11 11  
12 12 import { reactive,ref, h, nextTick,watch,onMounted } from 'vue';
13 13  
14 14 import { DICT_TYPE } from '@vben/constants';
15 15 import { getDictOptions } from '@vben/hooks';
  16 +import { formatDateTime } from '@vben/utils';
16 17  
17 18 import { DictTag } from '#/components/dict-tag';
18 19 import { getRangePickerDefaultProps } from '#/utils';
... ... @@ -31,13 +32,13 @@
31 32 #end
32 33  
33 34 #if ($table.templateType == 11) ## erp
34   - import { delete${subSimpleClassName},#if ($deleteBatchEnable) delete${subSimpleClassName}List,#end get${subSimpleClassName}Page } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
35   - import { isEmpty } from '@vben/utils';
  35 + import { delete${subSimpleClassName},#if ($deleteBatchEnable) delete${subSimpleClassName}List,#end get${subSimpleClassName}Page } from '#/api/${table.moduleName}/${table.businessName}';
  36 + import { cloneDeep, isEmpty } from '@vben/utils';
36 37 #else
37 38 #if ($subTable.subJoinMany) ## 一对多
38   - import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  39 + import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
39 40 #else
40   - import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  41 + import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
41 42 #end
42 43 #end
43 44  
... ... @@ -203,7 +204,7 @@ onMounted(() =&gt; {
203 204 <div class="h-[600px]">
204 205 <ContentWrap v-if="!hiddenSearchBar">
205 206 <!-- 搜索工作栏 -->
206   - <el-form
  207 + <ElForm
207 208 :model="queryParams"
208 209 ref="queryFormRef"
209 210 inline
... ... @@ -222,49 +223,49 @@ onMounted(() =&gt; {
222 223 #set ($dictMethod = "boolean")
223 224 #end
224 225 #if ($column.htmlType == "input")
225   - <el-form-item label="${comment}">
226   - <el-input
  226 + <ElFormItem label="${comment}">
  227 + <ElInput
227 228 v-model="queryParams.${javaField}"
228 229 placeholder="请输入${comment}"
229 230 clearable
230 231 @keyup.enter="handleQuery"
231 232 class="!w-[240px]"
232 233 />
233   - </el-form-item>
  234 + </ElFormItem>
234 235 #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
235   - <el-form-item label="${comment}">
236   - <el-select
  236 + <ElFormItem label="${comment}">
  237 + <ElSelect
237 238 v-model="queryParams.${javaField}"
238 239 placeholder="请选择${comment}"
239 240 clearable
240 241 class="!w-[240px]"
241 242 >
242 243 #if ("" != $dictType)## 设置了 dictType 数据字典的情况
243   - <el-option
  244 + <ElOption
244 245 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
245 246 :key="dict.value"
246 247 :value="dict.value"
247 248 :label="dict.label"
248 249 />
249 250 #else## 未设置 dictType 数据字典的情况
250   - <el-option label="请选择字典生成" value="" />
  251 + <ElOption label="请选择字典生成" value="" />
251 252 #end
252   - </el-select>
253   - </el-form-item>
  253 + </ElSelect>
  254 + </ElFormItem>
254 255 #elseif($column.htmlType == "datetime")
255 256 #if ($column.listOperationCondition != "BETWEEN")## 非范围
256   - <el-form-item label="${comment}">
257   - <el-date-picker
  257 + <ElFormItem label="${comment}">
  258 + <ElDatePicker
258 259 v-model="queryParams.${javaField}"
259 260 value-format="YYYY-MM-DD"
260 261 placeholder="选择${comment}"
261 262 clearable
262 263 class="!w-[240px]"
263 264 />
264   - </el-form-item>
  265 + </ElFormItem>
265 266 #else## 范围
266   - <el-form-item label="${comment}">
267   - <el-date-picker
  267 + <ElFormItem label="${comment}">
  268 + <ElDatePicker
268 269 v-model="queryParams.${javaField}"
269 270 type="daterange"
270 271 value-format="YYYY-MM-DD"
... ... @@ -273,18 +274,18 @@ onMounted(() =&gt; {
273 274 end-placeholder="结束日期"
274 275 class="!w-[240px]"
275 276 />
276   - </el-form-item>
  277 + </ElFormItem>
277 278 #end
278 279 #end
279 280 #end
280 281 #end
281   - <el-form-item>
282   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
283   - <el-button class="ml-2" @click="handleQuery" type="primary">
  282 + <ElFormItem>
  283 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  284 + <ElButton class="ml-2" @click="handleQuery" type="primary">
284 285 搜索
285   - </el-button>
286   - </el-form-item>
287   - </el-form>
  286 + </ElButton>
  287 + </ElFormItem>
  288 + </ElForm>
288 289 </ContentWrap>
289 290  
290 291 <!-- 列表 -->
... ... @@ -294,7 +295,7 @@ onMounted(() =&gt; {
294 295 ref="tableToolbarRef"
295 296 v-model:hidden-search="hiddenSearchBar"
296 297 >
297   - <el-button
  298 + <ElButton
298 299 class="ml-2"
299 300 :icon="h(Plus)"
300 301 type="primary"
... ... @@ -302,9 +303,9 @@ onMounted(() =&gt; {
302 303 v-access:code="['${permissionPrefix}:create']"
303 304 >
304 305 {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
305   - </el-button>
  306 + </ElButton>
306 307 #if ($deleteBatchEnable)
307   - <el-button
  308 + <ElButton
308 309 :icon="h(Trash2)"
309 310 type="danger"
310 311 class="ml-2"
... ... @@ -313,11 +314,11 @@ onMounted(() =&gt; {
313 314 v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:delete']"
314 315 >
315 316 批量删除
316   - </el-button>
  317 + </ElButton>
317 318 #end
318   - </TableToolbar>
  319 + </VbenVxeTableToolbar>
319 320 </template>
320   - <vxe-table
  321 + <VxeTable
321 322 ref="tableRef"
322 323 :data="list"
323 324 show-overflow
... ... @@ -328,7 +329,7 @@ onMounted(() =&gt; {
328 329 #end
329 330 >
330 331 #if ($deleteBatchEnable)
331   - <vxe-column type="checkbox" width="40"></vxe-column>
  332 + <VxeColumn type="checkbox" width="40"></VxeColumn>
332 333 #end
333 334 #foreach($column in $subColumns)
334 335 #if ($column.listOperationResult)
... ... @@ -336,27 +337,27 @@ onMounted(() =&gt; {
336 337 #set ($javaField = $column.javaField)
337 338 #set ($comment=$column.columnComment)
338 339 #if ($column.javaType == "LocalDateTime")## 时间类型
339   - <vxe-column field="${javaField}" title="${comment}" align="center">
  340 + <VxeColumn field="${javaField}" title="${comment}" align="center">
340 341 <template #default="{row}">
341 342 {{formatDateTime(row.${javaField})}}
342 343 </template>
343   - </vxe-column>
  344 + </VxeColumn>
344 345 #elseif($column.dictType && "" != $column.dictType)## 数据字典
345   - <vxe-column field="${javaField}" title="${comment}" align="center">
  346 + <VxeColumn field="${javaField}" title="${comment}" align="center">
346 347 <template #default="{row}">
347 348 <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="row.${javaField}" />
348 349 </template>
349   - </vxe-column>
  350 + </VxeColumn>
350 351 #elseif ($table.templateType == 2 && $javaField == $treeNameColumn.javaField)
351   - <vxe-column field="${javaField}" title="${comment}" align="center" tree-node/>
  352 + <VxeColumn field="${javaField}" title="${comment}" align="center" tree-node/>
352 353 #else
353   - <vxe-column field="${javaField}" title="${comment}" align="center" />
  354 + <VxeColumn field="${javaField}" title="${comment}" align="center" />
354 355 #end
355 356 #end
356 357 #end
357   - <vxe-column field="operation" title="操作" align="center">
  358 + <VxeColumn field="operation" title="操作" align="center">
358 359 <template #default="{row}">
359   - <el-button
  360 + <ElButton
360 361 size="small"
361 362 type="primary"
362 363 link
... ... @@ -364,8 +365,8 @@ onMounted(() =&gt; {
364 365 v-access:code="['${permissionPrefix}:update']"
365 366 >
366 367 {{ $t('ui.actionTitle.edit') }}
367   - </el-button>
368   - <el-button
  368 + </ElButton>
  369 + <ElButton
369 370 size="small"
370 371 type="danger"
371 372 link
... ... @@ -374,13 +375,13 @@ onMounted(() =&gt; {
374 375 v-access:code="['${permissionPrefix}:delete']"
375 376 >
376 377 {{ $t('ui.actionTitle.delete') }}
377   - </el-button>
  378 + </ElButton>
378 379 </template>
379   - </vxe-column>
380   - </vxe-table>
  380 + </VxeColumn>
  381 + </VxeTable>
381 382 <!-- 分页 -->
382 383 <div class="mt-2 flex justify-end">
383   - <el-pagination
  384 + <ElPagination
384 385 :total="total"
385 386 v-model:current-page="queryParams.pageNo"
386 387 v-model:page-size="queryParams.pageSize"
... ... @@ -394,7 +395,7 @@ onMounted(() =&gt; {
394 395 </div>
395 396 #else
396 397 <ContentWrap title="${subTable.classComment}列表">
397   - <vxe-table
  398 + <VxeTable
398 399 :data="list"
399 400 show-overflow
400 401 :loading="loading"
... ... @@ -405,23 +406,23 @@ onMounted(() =&gt; {
405 406 #set ($javaField = $column.javaField)
406 407 #set ($comment=$column.columnComment)
407 408 #if ($column.javaType == "LocalDateTime")## 时间类型
408   - <vxe-column field="${javaField}" title="${comment}" align="center">
  409 + <VxeColumn field="${javaField}" title="${comment}" align="center">
409 410 <template #default="{row}">
410 411 {{formatDateTime(row.${javaField})}}
411 412 </template>
412   - </vxe-column>
  413 + </VxeColumn>
413 414 #elseif($column.dictType && "" != $column.dictType)## 数据字典
414   - <vxe-column field="${javaField}" title="${comment}" align="center">
  415 + <VxeColumn field="${javaField}" title="${comment}" align="center">
415 416 <template #default="{row}">
416 417 <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="row.${javaField}" />
417 418 </template>
418   - </vxe-column>
  419 + </VxeColumn>
419 420 #else
420   - <vxe-column field="${javaField}" title="${comment}" align="center" />
  421 + <VxeColumn field="${javaField}" title="${comment}" align="center" />
421 422 #end
422 423 #end
423 424 #end
424   - </vxe-table>
  425 + </VxeTable>
425 426 </ContentWrap>
426 427 #end
427 428 </template>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/data.ts.vm
... ... @@ -36,13 +36,14 @@ export function useFormSchema(): VbenFormSchema[] {
36 36 id: 0,
37 37 ${treeNameColumn.javaField}: '顶级${table.classComment}',
38 38 });
39   - return handleTree(data);
  39 + return handleTree(data, 'id', '${treeParentColumn.javaField}');
40 40 },
41 41 labelField: '${treeNameColumn.javaField}',
42 42 valueField: 'id',
43 43 childrenField: 'children',
44 44 placeholder: '请选择上级${table.classComment}',
45   - treeDefaultExpandAll: true,
  45 + defaultExpandAll: true,
  46 + checkStrictly: true,
46 47 },
47 48 rules: 'selectRequired',
48 49 },
... ... @@ -89,7 +90,7 @@ export function useFormSchema(): VbenFormSchema[] {
89 90 placeholder: '请选择${comment}',
90 91 },
91 92 #elseif($column.htmlType == "checkbox")## 多选框
92   - component: 'Checkbox',
  93 + component: 'CheckboxGroup',
93 94 componentProps: {
94 95 #if ("" != $dictType)## 有数据字典
95 96 options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
... ... @@ -105,13 +106,12 @@ export function useFormSchema(): VbenFormSchema[] {
105 106 #else##没数据字典
106 107 options: [],
107 108 #end
108   - buttonStyle: 'solid',
109   - optionType: 'button',
  109 + isButton: true,
110 110 },
111 111 #elseif($column.htmlType == "datetime")## 时间框
112 112 component: 'DatePicker',
113 113 componentProps: {
114   - showTime: true,
  114 + type: 'datetime',
115 115 format: 'YYYY-MM-DD HH:mm:ss',
116 116 valueFormat: 'x',
117 117 class: '!w-full',
... ... @@ -308,7 +308,7 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
308 308 placeholder: '请选择${comment}',
309 309 },
310 310 #elseif($column.htmlType == "checkbox")## 多选框
311   - component: 'Checkbox',
  311 + component: 'CheckboxGroup',
312 312 componentProps: {
313 313 #if ("" != $dictType)## 有数据字典
314 314 options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
... ... @@ -324,13 +324,12 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
324 324 #else##没数据字典
325 325 options: [],
326 326 #end
327   - buttonStyle: 'solid',
328   - optionType: 'button',
  327 + isButton: true,
329 328 },
330 329 #elseif($column.htmlType == "datetime")## 时间框
331 330 component: 'DatePicker',
332 331 componentProps: {
333   - showTime: true,
  332 + type: 'datetime',
334 333 format: 'YYYY-MM-DD HH:mm:ss',
335 334 valueFormat: 'x',
336 335 class: '!w-full',
... ... @@ -450,6 +449,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
450 449 #if ($column.createOperation || $column.updateOperation)
451 450 #if (!$column.primaryKey && $column.listOperationResult && $column.id != $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
452 451 #set ($dictType = $column.dictType)
  452 + #set ($javaType = $column.javaType)
453 453 #set ($javaField = $column.javaField)
454 454 #set ($comment = $column.columnComment)
455 455 #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
... ... @@ -544,7 +544,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
544 544 placeholder: '请选择${comment}',
545 545 },
546 546 #elseif($column.htmlType == "checkbox")## 多选框
547   - component: 'Checkbox',
  547 + component: 'CheckboxGroup',
548 548 componentProps: {
549 549 #if ("" != $dictType)## 有数据字典
550 550 options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
... ... @@ -560,13 +560,12 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
560 560 #else##没数据字典
561 561 options: [],
562 562 #end
563   - buttonStyle: 'solid',
564   - optionType: 'button',
  563 + isButton: true,
565 564 },
566 565 #elseif($column.htmlType == "datetime")## 时间框
567 566 component: 'DatePicker',
568 567 componentProps: {
569   - showTime: true,
  568 + type: 'datetime',
570 569 format: 'YYYY-MM-DD HH:mm:ss',
571 570 valueFormat: 'x',
572 571 class: '!w-full',
... ... @@ -596,7 +595,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions&lt;${api
596 595 #end
597 596 #if ($table.templateType == 12) ## 内嵌情况
598 597 /** 列表的字段 */
599   - export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
  598 + export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${apiName}.${subSimpleClassName}>['columns'] {
600 599 return [
601 600 #foreach($column in $subColumns)
602 601 #if ($column.listOperationResult)
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/form.vue.vm
... ... @@ -6,7 +6,7 @@ import { computed, ref } from &#39;vue&#39;;
6 6  
7 7 import { useVbenModal } from '@vben/common-ui';
8 8  
9   -import { ElMessage#if ($table.templateType == 11), ElTabs, ElTabPane#end } from 'element-plus';
  9 +import { ElMessage#if ($table.templateType == 10 || $table.templateType == 12), ElTabPane, ElTabs#end } from 'element-plus';
10 10  
11 11 import { useVbenForm } from '#/adapter/form';
12 12 import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
... ... @@ -69,15 +69,11 @@ const [Modal, modalApi] = useVbenModal({
69 69 #foreach ($subTable in $subTables)
70 70 #set ($index = $foreach.count - 1)
71 71 #set ($subClassNameVar = $subClassNameVars.get($index))
72   - #if ($subTable.subJoinMany) ## 一对多
73   - ## TODO 列表值校验?
74   - #else
75 72 const ${subClassNameVar}Valid = await ${subClassNameVar}FormRef.value?.validate();
76 73 if (!${subClassNameVar}Valid) {
77 74 subTabsName.value = '${subClassNameVar}';
78 75 return;
79 76 }
80   - #end
81 77 #end
82 78 #end
83 79 #end
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/import.vue.vm
... ... @@ -50,6 +50,16 @@ function handleChange(file: any) {
50 50 }
51 51 }
52 52  
  53 +/** 移除文件 */
  54 +function handleRemove() {
  55 + formApi.setFieldValue('file', undefined);
  56 +}
  57 +
  58 +/** 超出文件数量 */
  59 +function handleExceed() {
  60 + ElMessage.warning('只能选择一个文件,请先移除已选文件');
  61 +}
  62 +
53 63 /** 下载模板 */
54 64 async function handleDownload() {
55 65 const data = await import${simpleClassName}Template();
... ... @@ -66,6 +76,8 @@ async function handleDownload() {
66 76 :limit="1"
67 77 accept=".xls,.xlsx"
68 78 :on-change="handleChange"
  79 + :on-remove="handleRemove"
  80 + :on-exceed="handleExceed"
69 81 :auto-upload="false"
70 82 >
71 83 <ElButton type="primary"> 选择 Excel 文件 </ElButton>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/form_sub_normal.vue.vm
... ... @@ -14,10 +14,9 @@
14 14  
15 15 #if ($subTable.subJoinMany) ## 一对多
16 16 import { Plus } from "@vben/icons";
17   -import { ElButton, ElTabs, ElTabPane, ElCheckbox, ElInput, ElSelect, ElOption, ElRadioGroup, ElCheckboxGroup, ElDatePicker } from 'element-plus';
  17 +import { ElButton, ElCheckbox, ElCheckboxGroup, ElDatePicker, ElInput, ElMessage, ElOption, ElRadio, ElRadioGroup, ElSelect, ElTabPane, ElTabs } from 'element-plus';
18 18 import { ImageUpload, FileUpload } from "#/components/upload";
19   -import type { OnActionClickParams } from '#/adapter/vxe-table';
20   -import { useVbenVxeGrid } from '#/adapter/vxe-table';
  19 +import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
21 20 import { use${subSimpleClassName}GridEditColumns } from '../data';
22 21 import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
23 22 #else
... ... @@ -31,22 +30,9 @@ const props = defineProps&lt;{
31 30 }>()
32 31  
33 32 #if ($subTable.subJoinMany) ## 一对多
34   -/** 表格操作按钮的回调函数 */
35   -function onActionClick({
36   - code,
37   - row,
38   -}: OnActionClickParams<${apiName}.${subSimpleClassName}>) {
39   - switch (code) {
40   - case 'delete': {
41   - onDelete(row);
42   - break;
43   - }
44   - }
45   -}
46   -
47 33 const [Grid, gridApi] = useVbenVxeGrid({
48 34 gridOptions: {
49   - columns: use${subSimpleClassName}GridEditColumns(onActionClick),
  35 + columns: use${subSimpleClassName}GridEditColumns(),
50 36 border: true,
51 37 showOverflow: true,
52 38 autoResize: true,
... ... @@ -65,7 +51,7 @@ gridOptions: {
65 51  
66 52 /** 添加${subTable.classComment} */
67 53 const onAdd = async () => {
68   - await gridApi.grid.insertAt({} as ${simpleClassName}Api.${subSimpleClassName}, -1);
  54 + await gridApi.grid.insertAt({} as ${apiName}.${subSimpleClassName}, -1);
69 55 }
70 56  
71 57 /** 删除${subTable.classComment} */
... ... @@ -73,26 +59,54 @@ const onDelete = async (row: ${apiName}.${subSimpleClassName}) =&gt; {
73 59 await gridApi.grid.remove(row);
74 60 }
75 61  
76   -/** 提供获取表格数据的方法供父组件调用 */
77   -defineExpose({
78   - getData: (): ${apiName}.${subSimpleClassName}[] => {
79   - const data = gridApi.grid.getData() as ${apiName}.${subSimpleClassName}[];
80   - const removeRecords = gridApi.grid.getRemoveRecords() as ${apiName}.${subSimpleClassName}[];
81   - const insertRecords = gridApi.grid.getInsertRecords() as ${apiName}.${subSimpleClassName}[];
82   - return data
83   - .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
84   - .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
85   - },
86   -});
  62 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  63 +function getData(): ${apiName}.${subSimpleClassName}[] {
  64 + const { fullData } = gridApi.grid.getTableData();
  65 + const inserted = new Set(gridApi.grid.getInsertRecords());
  66 + return fullData.map((row) =>
  67 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  68 + );
  69 +}
  70 +
  71 +#set ($hasRequiredColumn = false)
  72 +#foreach ($column in $subColumns)
  73 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  74 +#set ($hasRequiredColumn = true)
  75 +#end
  76 +#end
  77 +/** 校验子表必填字段 */
  78 +function validate(): boolean {
  79 +#if ($hasRequiredColumn)
  80 + for (const [index, row] of getData().entries()) {
  81 +#foreach ($column in $subColumns)
  82 +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey && $column.id != $subJoinColumn.id)
  83 + if (
  84 + row.${column.javaField} === null ||
  85 + row.${column.javaField} === undefined ||
  86 + (typeof row.${column.javaField} === 'string' && String(row.${column.javaField}).trim() === '') ||
  87 + (Array.isArray(row.${column.javaField}) && row.${column.javaField}.length === 0)
  88 + ) {
  89 + ElMessage.error(`第 #[[${index + 1}]]# 行:${column.columnComment}不能为空`);
  90 + return false;
  91 + }
  92 +#end
  93 +#end
  94 + }
  95 +#end
  96 + return true;
  97 +}
  98 +
  99 +defineExpose({ getData, validate });
87 100  
88 101 /** 监听主表的关联字段的变化,加载对应的子表数据 */
89 102 watch(
90 103 () => props.${subJoinColumn.javaField},
91 104 async (val) => {
  105 + await nextTick();
92 106 if (!val) {
  107 + await gridApi.grid.loadData([]);
93 108 return;
94 109 }
95   - await nextTick();
96 110 await gridApi.grid.loadData(await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
97 111 },
98 112 { immediate: true },
... ... @@ -124,10 +138,11 @@ defineExpose({
124 138 watch(
125 139 () => props.${subJoinColumn.javaField},
126 140 async (val) => {
  141 + await nextTick();
  142 + await formApi.resetForm();
127 143 if (!val) {
128 144 return;
129 145 }
130   - await nextTick();
131 146 await formApi.setValues(await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
132 147 },
133 148 { immediate: true },
... ... @@ -144,7 +159,7 @@ watch(
144 159 #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
145 160 #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
146 161 <template #${javaField}="{ row }">
147   - <el-input v-model="row.${javaField}" />
  162 + <ElInput v-model="row.${javaField}" />
148 163 </template>
149 164 #elseif($column.htmlType == "imageUpload")## 图片上传
150 165 <template #${javaField}="{ row }">
... ... @@ -156,29 +171,29 @@ watch(
156 171 </template>
157 172 #elseif($column.htmlType == "select")## 下拉框
158 173 <template #${javaField}="{ row, column }">
159   - <el-select v-model="row.${javaField}" class="w-full">
160   - <el-option v-for="option in column.params.options" :key="option.value" :value="option.value" :label="option.label" />
161   - </el-select>
  174 + <ElSelect v-model="row.${javaField}" class="w-full">
  175 + <ElOption v-for="option in column.params.options" :key="option.value" :value="option.value" :label="option.label" />
  176 + </ElSelect>
162 177 </template>
163 178 #elseif($column.htmlType == "checkbox")## 多选框
164 179 <template #${javaField}="{ row, column }">
165   - <el-checkbox-group v-model="row.${javaField}">
166   - <el-checkbox v-for="option in column.params.options" :key="option.value" :label="option.value">
  180 + <ElCheckboxGroup v-model="row.${javaField}">
  181 + <ElCheckbox v-for="option in column.params.options" :key="option.value" :value="option.value">
167 182 {{ option.label }}
168   - </el-checkbox>
169   - </el-checkbox-group>
  183 + </ElCheckbox>
  184 + </ElCheckboxGroup>
170 185 </template>
171 186 #elseif($column.htmlType == "radio")## 单选框
172 187 <template #${javaField}="{ row, column }">
173   - <el-radio-group v-model="row.${javaField}">
174   - <el-radio v-for="option in column.params.options" :key="option.value" :label="option.value">
  188 + <ElRadioGroup v-model="row.${javaField}">
  189 + <ElRadio v-for="option in column.params.options" :key="option.value" :value="option.value">
175 190 {{ option.label }}
176   - </el-radio>
177   - </el-radio-group>
  191 + </ElRadio>
  192 + </ElRadioGroup>
178 193 </template>
179 194 #elseif($column.htmlType == "datetime")## 时间框
180 195 <template #${javaField}="{ row }">
181   - <el-date-picker
  196 + <ElDatePicker
182 197 v-model="row.${javaField}"
183 198 type="datetime"
184 199 value-format="x"
... ... @@ -187,16 +202,29 @@ watch(
187 202 </template>
188 203 #elseif($column.htmlType == "textarea" || $column.htmlType == "editor")## 文本框
189 204 <template #${javaField}="{ row }">
190   - <el-input v-model="row.${javaField}" type="textarea" />
  205 + <ElInput v-model="row.${javaField}" type="textarea" />
191 206 </template>
192 207 #end
193 208 #end
194 209 #end
  210 + <template #actions="{ row }">
  211 + <TableAction
  212 + :actions="[
  213 + {
  214 + label: $t('common.delete'),
  215 + type: 'danger', link: true,
  216 + icon: ACTION_ICON.DELETE,
  217 + auth: ['${permissionPrefix}:delete'],
  218 + onClick: onDelete.bind(null, row),
  219 + },
  220 + ]"
  221 + />
  222 + </template>
195 223 </Grid>
196 224 <div class="flex justify-center -mt-4">
197   - <el-button :icon="Plus" type="primary" plain @click="onAdd" v-access:code="['${subTable.moduleName}:${simpleClassName_strikeCase}:create']">
  225 + <ElButton :icon="Plus" type="primary" plain @click="onAdd" v-access:code="['${subTable.moduleName}:${simpleClassName_strikeCase}:create']">
198 226 {{ $t('ui.actionTitle.create', ['${subTable.classComment}']) }}
199   - </el-button>
  227 + </ElButton>
200 228 </div>
201 229 #else
202 230 <Form class="mx-4" />
... ...
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineFrontendContractTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.service.codegen.inner;
  2 +
  3 +import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
  4 +import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
  5 +import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
  6 +import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
  7 +import com.baomidou.mybatisplus.annotation.DbType;
  8 +import org.junit.jupiter.api.Test;
  9 +import org.junit.jupiter.params.ParameterizedTest;
  10 +import org.junit.jupiter.params.provider.EnumSource;
  11 +
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static org.junit.jupiter.api.Assertions.*;
  16 +
  17 +/**
  18 + * {@link CodegenEngine} 的前端组件契约回归测试
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +public class CodegenEngineFrontendContractTest extends CodegenEngineAbstractTest {
  23 +
  24 + @ParameterizedTest
  25 + @EnumSource(value = CodegenFrontTypeEnum.class, names = {
  26 + "VUE3_VBEN5_ANTD_SCHEMA", "VUE3_VBEN5_ANTDV_NEXT_SCHEMA", "VUE3_VBEN5_EP_SCHEMA"})
  27 + public void testExecute_dictionaryControls(CodegenFrontTypeEnum frontType) {
  28 + // 准备参数:同一字典的多选、单选和数字必填字段
  29 + CodegenTableDO table = getTable("student").setFrontType(frontType.getType())
  30 + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
  31 + List<CodegenColumnDO> columns = getColumnList("student");
  32 + columns.add(new CodegenColumnDO().setJavaField("tags").setJavaType("String")
  33 + .setColumnComment("标签").setDictType("system_user_sex").setHtmlType("checkbox")
  34 + .setCreateOperation(true).setUpdateOperation(true).setNullable(false).setPrimaryKey(false));
  35 + columns.add(new CodegenColumnDO().setJavaField("status").setJavaType("Integer")
  36 + .setColumnComment("状态").setDictType("system_user_sex").setHtmlType("radio")
  37 + .setCreateOperation(true).setUpdateOperation(true).setNullable(false).setPrimaryKey(false));
  38 + columns.add(new CodegenColumnDO().setJavaField("amount").setJavaType("Integer")
  39 + .setColumnComment("金额").setHtmlType("inputNumber").setDictType("")
  40 + .setCreateOperation(true).setUpdateOperation(true).setNullable(false).setPrimaryKey(false));
  41 +
  42 + // 调用
  43 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
  44 + // 调试用:导出真实生成结果,供前端组件验证
  45 + String outputPath = System.getProperty("codegen.contractOutput");
  46 + if (outputPath != null) {
  47 + writeResult(result, outputPath + "/" + frontType.name());
  48 + }
  49 + // 断言:组选项不能变成 boolean Checkbox,必填数字不能被 Zod 推导为 null
  50 + String data = findFile(result, "/data.ts");
  51 + assertTrue(data.contains("component: 'CheckboxGroup'"));
  52 + assertFalse(data.contains("component: 'Checkbox',"));
  53 + assertTrue(data.contains("rules: 'required'"));
  54 + assertFalse(data.contains("z.number("));
  55 + if (frontType == CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA) {
  56 + assertTrue(data.contains("isButton: true"));
  57 + assertFalse(data.contains("buttonStyle:"));
  58 + assertFalse(data.contains("optionType:"));
  59 + assertFalse(data.contains("allowClear:"));
  60 + } else {
  61 + assertTrue(data.contains("optionType: 'button'"));
  62 + assertFalse(data.contains("clearable:"));
  63 + }
  64 + }
  65 +
  66 + @Test
  67 + public void testExecute_eleTreeParentSelectable() {
  68 + // 准备参数
  69 + CodegenTableDO table = getTable("category").setFrontType(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType())
  70 + .setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
  71 + // 调用
  72 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, getColumnList("category"), null, null);
  73 + // 断言:父节点可作为上级,使用 Element Plus 属性
  74 + String data = findFile(result, "/data.ts");
  75 + assertTrue(data.contains("defaultExpandAll: true"));
  76 + assertTrue(data.contains("checkStrictly: true"));
  77 + assertFalse(data.contains("treeDefaultExpandAll"));
  78 + }
  79 +
  80 + @ParameterizedTest
  81 + @EnumSource(value = CodegenFrontTypeEnum.class, names = {
  82 + "VUE3_VBEN5_ANTD_SCHEMA", "VUE3_VBEN5_ANTD_GENERAL",
  83 + "VUE3_VBEN5_ANTDV_NEXT_SCHEMA", "VUE3_VBEN5_ANTDV_NEXT_GENERAL",
  84 + "VUE3_VBEN5_EP_SCHEMA", "VUE3_VBEN5_EP_GENERAL"})
  85 + public void testExecute_importRemovalClearsPayload(CodegenFrontTypeEnum frontType) {
  86 + // 准备参数
  87 + codegenProperties.setImportEnable(true);
  88 + codegenEngine.initGlobalBindingMap();
  89 + CodegenTableDO table = getTable("student").setFrontType(frontType.getType())
  90 + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
  91 + // 调用
  92 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"), null, null);
  93 + // 断言:移除可视文件时也清空提交来源
  94 + String form = findFile(result, "/import-form.vue");
  95 + assertTrue(form.contains(":on-remove=\"handleRemove\""));
  96 + assertTrue(form.contains("formApi.setFieldValue('file', undefined)") || form.contains("fileRef.value = null"));
  97 + if (frontType == CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA
  98 + || frontType == CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL) {
  99 + assertTrue(form.contains(":on-exceed=\"handleExceed\""));
  100 + }
  101 + }
  102 +
  103 + @ParameterizedTest
  104 + @EnumSource(value = CodegenFrontTypeEnum.class, names = {
  105 + "VUE3_VBEN5_ANTD_SCHEMA", "VUE3_VBEN5_ANTD_GENERAL",
  106 + "VUE3_VBEN5_ANTDV_NEXT_SCHEMA", "VUE3_VBEN5_ANTDV_NEXT_GENERAL",
  107 + "VUE3_VBEN5_EP_SCHEMA", "VUE3_VBEN5_EP_GENERAL"})
  108 + public void testExecute_masterChildEditing(CodegenFrontTypeEnum frontType) {
  109 + // 准备参数:一对多联系人和一对一班主任
  110 + CodegenTableDO table = getTable("student").setFrontType(frontType.getType())
  111 + .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType());
  112 + CodegenTableDO contact = getTable("contact").setFrontType(frontType.getType())
  113 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
  114 + CodegenTableDO teacher = getTable("teacher").setFrontType(frontType.getType())
  115 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(200L).setSubJoinMany(false);
  116 + // 调用
  117 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"),
  118 + List.of(contact, teacher), List.of(getColumnList("contact"), getColumnList("teacher")));
  119 + // 断言:新增和删除以当前表格为准,不重复追加,新增临时编号不提交
  120 + String form = findFile(result, "/student-contact-form.vue");
  121 + assertTrue(form.contains("getTableData()"));
  122 + assertTrue(form.contains("inserted.has(row) ? { ...row, id: undefined } : { ...row }"));
  123 + assertFalse(form.contains("getRemoveRecords()"));
  124 + assertFalse(form.contains(".concat(insertRecords"));
  125 + assertFalse(form.contains("$dictType"));
  126 + assertTrue(form.contains("getStudentContactListByStudentId"));
  127 + if (frontType.name().endsWith("SCHEMA")) {
  128 + assertTrue(form.contains("<template #actions="));
  129 + }
  130 + result.forEach((path, content) -> {
  131 + if (path.endsWith(".vue") || path.endsWith("data.ts")) {
  132 + assertFalse(content.contains("#/api/infra/student"), path + ":API 导入必须匹配生成目录");
  133 + assertFalse(content.contains("VxeTableGridOptions<StudentApi.StudentContact>") && content.contains("import type { InfraStudentApi }"),
  134 + path + ":子表命名空间应与导入一致");
  135 + }
  136 + });
  137 + if (frontType == CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL) {
  138 + assertTrue(form.matches("(?s).*import\\s*\\{[^}]*\\bmessage\\b[^}]*}\\s*from 'antdv-next'.*"),
  139 + "子表校验提示必须导入实际的消息组件");
  140 + }
  141 + if (frontType == CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL) {
  142 + assertTrue(form.contains(":icon=\"Plus\""));
  143 + assertFalse(form.contains("h(Plus)"), "图标使用导入的组件,避免未绑定的 h");
  144 + }
  145 + assertTrue(form.contains("function validate(): boolean"));
  146 + assertTrue(form.contains("=== undefined"));
  147 + assertTrue(form.contains("loadData([])") || form.contains("list.value = []"));
  148 + String parentForm = findFile(result, "/modules/form.vue");
  149 + assertTrue(parentForm.contains("studentContactFormRef.value?.validate()"));
  150 + assertFalse(parentForm.contains("TODO 列表值校验"));
  151 + }
  152 +
  153 + @ParameterizedTest
  154 + @EnumSource(value = CodegenTemplateTypeEnum.class, names = { "MASTER_ERP", "MASTER_INNER" })
  155 + public void testExecute_eleChildListImports(CodegenTemplateTypeEnum templateType) {
  156 + // 准备参数
  157 + CodegenFrontTypeEnum frontType = CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL;
  158 + CodegenTableDO table = getTable("student").setFrontType(frontType.getType())
  159 + .setTemplateType(templateType.getType());
  160 + CodegenTableDO contact = getTable("contact").setFrontType(frontType.getType())
  161 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
  162 + // 调用
  163 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"),
  164 + List.of(contact), List.of(getColumnList("contact")));
  165 + // 断言:ERP 子表查询实际调用的工具必须导入
  166 + String index = findFile(result, "/index.vue");
  167 + assertTrue(index.contains("<VbenVxeTableToolbar"));
  168 + assertTrue(index.contains("</VbenVxeTableToolbar>"));
  169 + assertFalse(index.contains("<TableToolbar"));
  170 + String list = findFile(result, "/student-contact-list.vue");
  171 + if (templateType == CodegenTemplateTypeEnum.MASTER_ERP) {
  172 + assertTrue(list.contains("<VbenVxeTableToolbar"));
  173 + assertTrue(list.contains("</VbenVxeTableToolbar>"));
  174 + assertFalse(list.contains("</TableToolbar>"));
  175 + assertTrue(list.contains("cloneDeep(queryParams)"));
  176 + assertTrue(list.matches("(?s).*import\\s*\\{[^}]*\\bcloneDeep\\b[^}]*}\\s*from '@vben/utils'.*"));
  177 + }
  178 + assertTrue(list.contains("formatDateTime(row."));
  179 + assertTrue(list.matches("(?s).*import\\s*\\{[^}]*\\bformatDateTime\\b[^}]*}\\s*from '@vben/utils'.*"));
  180 + }
  181 +
  182 + @Test
  183 + public void testExecute_vue3ImportUploadResponse() {
  184 + // 准备参数
  185 + codegenProperties.setImportEnable(true);
  186 + codegenEngine.initGlobalBindingMap();
  187 + CodegenTableDO table = getTable("student").setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
  188 + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
  189 + // 调用
  190 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"), null, null);
  191 + // 断言
  192 + String form = findFile(result, "/StudentImportForm.vue");
  193 + assertTrue(form.contains("const res = await StudentApi.importStudent(formData)"));
  194 + assertTrue(form.contains("const data = res.data"));
  195 + }
  196 +
  197 +
  198 + @Test
  199 + public void testExecute_vben2DateRangeFormat() {
  200 + // 准备参数
  201 + CodegenTableDO table = getTable("student").setFrontType(CodegenFrontTypeEnum.VUE3_VBEN2_ANTD_SCHEMA.getType())
  202 + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
  203 + // 调用
  204 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"), null, null);
  205 + // 断言:查询区间提交后端需要的时间字符串
  206 + String data = findFile(result, ".data.ts");
  207 + assertTrue(data.contains("valueFormat: 'YYYY-MM-DD HH:mm:ss'"));
  208 + assertTrue(data.contains("allowClear: true"));
  209 + }
  210 +
  211 + @Test
  212 + public void testExecute_uniappPaginationContract() {
  213 + // 准备参数
  214 + CodegenTableDO table = getTable("student").setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
  215 + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
  216 + // 调用
  217 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"), null, null);
  218 + // 断言:移动端已有分页/失败恢复契约保持有效,避免机械套用 PC 组件写法
  219 + String api = findFile(result, "/index.ts");
  220 + assertTrue(api.contains("params: PageParam"));
  221 + assertFalse(api.contains("PageReqVO"));
  222 + String list = result.entrySet().stream()
  223 + .filter(entry -> entry.getKey().endsWith("/index.vue") && entry.getValue().contains("<z-paging"))
  224 + .findFirst().orElseThrow().getValue();
  225 + assertTrue(list.contains("completeByTotal(data.list, data.total)"));
  226 + assertTrue(list.contains("complete(false)"));
  227 + assertTrue(list.contains("uni.$off("));
  228 + }
  229 +
  230 + private static String findFile(Map<String, String> result, String suffix) {
  231 + return result.entrySet().stream().filter(entry -> entry.getKey().endsWith(suffix))
  232 + .findFirst().orElseThrow(() -> new AssertionError("缺少生成文件:" + suffix)).getValue();
  233 + }
  234 +}
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_erp/vue/index
... ... @@ -35,12 +35,11 @@ const total = ref(0) // 列表的总页数
35 35 const queryParams = reactive({
36 36 pageNo: 1,
37 37 pageSize: 10,
38   - name: undefined,
39   - birthday: undefined,
40   - birthday: undefined,
41   - sex: undefined,
42   - enabled: undefined,
43   - createTime: undefined,
  38 + name: undefined,
  39 + birthday: undefined,
  40 + sex: undefined,
  41 + enabled: undefined,
  42 + createTime: undefined,
44 43 })
45 44 const queryFormRef = ref() // 搜索的表单
46 45 const exportLoading = ref(false) // 导出的加载中
... ... @@ -136,7 +135,14 @@ function handleRowCheckboxChange({
136 135 async function handleExport() {
137 136 try {
138 137 exportLoading.value = true;
139   - const data = await exportStudent(queryParams);
  138 + const params = cloneDeep(queryParams) as any;
  139 + if (params.birthday && Array.isArray(params.birthday)) {
  140 + params.birthday = (params.birthday as string[]).join(',');
  141 + }
  142 + if (params.createTime && Array.isArray(params.createTime)) {
  143 + params.createTime = (params.createTime as string[]).join(',');
  144 + }
  145 + const data = await exportStudent(params);
140 146 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
141 147 }finally {
142 148 exportLoading.value = false;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_inner/vue/form
... ... @@ -72,7 +72,11 @@ const [Modal, modalApi] = useVbenModal({
72 72 async onConfirm() {
73 73 await formRef.value?.validate();
74 74 // 校验子表单
75   - try {
  75 + if (!studentContactFormRef.value?.validate()) {
  76 + subTabsName.value = 'studentContact';
  77 + return;
  78 + }
  79 + try {
76 80 await studentTeacherFormRef.value?.validate()
77 81 } catch (e) {
78 82 subTabsName.value = 'studentTeacher'
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_inner/vue/index
... ... @@ -31,12 +31,11 @@ const total = ref(0) // 列表的总页数
31 31 const queryParams = reactive({
32 32 pageNo: 1,
33 33 pageSize: 10,
34   - name: undefined,
35   - birthday: undefined,
36   - birthday: undefined,
37   - sex: undefined,
38   - enabled: undefined,
39   - createTime: undefined,
  34 + name: undefined,
  35 + birthday: undefined,
  36 + sex: undefined,
  37 + enabled: undefined,
  38 + createTime: undefined,
40 39 })
41 40 const queryFormRef = ref() // 搜索的表单
42 41 const exportLoading = ref(false) // 导出的加载中
... ... @@ -132,7 +131,14 @@ function handleRowCheckboxChange({
132 131 async function handleExport() {
133 132 try {
134 133 exportLoading.value = true;
135   - const data = await exportStudent(queryParams);
  134 + const params = cloneDeep(queryParams) as any;
  135 + if (params.birthday && Array.isArray(params.birthday)) {
  136 + params.birthday = (params.birthday as string[]).join(',');
  137 + }
  138 + if (params.createTime && Array.isArray(params.createTime)) {
  139 + params.createTime = (params.createTime as string[]).join(',');
  140 + }
  141 + const data = await exportStudent(params);
136 142 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
137 143 }finally {
138 144 exportLoading.value = false;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_inner/vue/student-contact-form
... ... @@ -32,25 +32,96 @@ async function handleDelete(row: StudentApi.StudentContact) {
32 32 await tableRef.value?.remove(row);
33 33 }
34 34  
35   -/** 提供获取表格数据的方法供父组件调用 */
36   -defineExpose({
37   - getData: (): StudentApi.StudentContact[] => {
38   - const data = list.value as StudentApi.StudentContact[];
39   - const removeRecords = tableRef.value?.getRemoveRecords() as StudentApi.StudentContact[];
40   - const insertRecords = tableRef.value?.getInsertRecords() as StudentApi.StudentContact[];
41   - return [
42   - ...data.filter(
43   - (row) => !removeRecords.some((removed) => removed.id === row.id),
44   - ),
45   - ...insertRecords.map((row: any) => ({ ...row, id: undefined })),
46   - ];
47   - },
48   -});
  35 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  36 +function getData(): StudentApi.StudentContact[] {
  37 + const table = tableRef.value;
  38 + if (!table) {
  39 + return [];
  40 + }
  41 + const { fullData } = table.getTableData();
  42 + const inserted = new Set(table.getInsertRecords());
  43 + return fullData.map((row) =>
  44 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  45 + );
  46 +}
  47 +
  48 +/** 校验子表必填字段 */
  49 +function validate(): boolean {
  50 + for (const [index, row] of getData().entries()) {
  51 + if (
  52 + row.name === null ||
  53 + row.name === undefined ||
  54 + (typeof row.name === 'string' && String(row.name).trim() === '') ||
  55 + (Array.isArray(row.name) && row.name.length === 0)
  56 + ) {
  57 + message.error(`第 ${index + 1} 行:名字不能为空`);
  58 + return false;
  59 + }
  60 + if (
  61 + row.description === null ||
  62 + row.description === undefined ||
  63 + (typeof row.description === 'string' && String(row.description).trim() === '') ||
  64 + (Array.isArray(row.description) && row.description.length === 0)
  65 + ) {
  66 + message.error(`第 ${index + 1} 行:简介不能为空`);
  67 + return false;
  68 + }
  69 + if (
  70 + row.birthday === null ||
  71 + row.birthday === undefined ||
  72 + (typeof row.birthday === 'string' && String(row.birthday).trim() === '') ||
  73 + (Array.isArray(row.birthday) && row.birthday.length === 0)
  74 + ) {
  75 + message.error(`第 ${index + 1} 行:出生日期不能为空`);
  76 + return false;
  77 + }
  78 + if (
  79 + row.sex === null ||
  80 + row.sex === undefined ||
  81 + (typeof row.sex === 'string' && String(row.sex).trim() === '') ||
  82 + (Array.isArray(row.sex) && row.sex.length === 0)
  83 + ) {
  84 + message.error(`第 ${index + 1} 行:性别不能为空`);
  85 + return false;
  86 + }
  87 + if (
  88 + row.enabled === null ||
  89 + row.enabled === undefined ||
  90 + (typeof row.enabled === 'string' && String(row.enabled).trim() === '') ||
  91 + (Array.isArray(row.enabled) && row.enabled.length === 0)
  92 + ) {
  93 + message.error(`第 ${index + 1} 行:是否有效不能为空`);
  94 + return false;
  95 + }
  96 + if (
  97 + row.avatar === null ||
  98 + row.avatar === undefined ||
  99 + (typeof row.avatar === 'string' && String(row.avatar).trim() === '') ||
  100 + (Array.isArray(row.avatar) && row.avatar.length === 0)
  101 + ) {
  102 + message.error(`第 ${index + 1} 行:头像不能为空`);
  103 + return false;
  104 + }
  105 + if (
  106 + row.memo === null ||
  107 + row.memo === undefined ||
  108 + (typeof row.memo === 'string' && String(row.memo).trim() === '') ||
  109 + (Array.isArray(row.memo) && row.memo.length === 0)
  110 + ) {
  111 + message.error(`第 ${index + 1} 行:备注不能为空`);
  112 + return false;
  113 + }
  114 + }
  115 + return true;
  116 +}
  117 +
  118 +defineExpose({ getData, validate });
49 119  
50 120 /** 监听主表的关联字段的变化,加载对应的子表数据 */
51 121 watch(
52 122 () => props.studentId,
53 123 async (val) => {
  124 + list.value = [];
54 125 if (!val) {
55 126 return;
56 127 }
... ... @@ -86,7 +157,7 @@ watch(
86 157 <template #default="{ row }">
87 158 <Select v-model:value="row.sex" placeholder="请选择性别">
88 159 <Select.Option
89   - v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number')"
  160 + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
90 161 :key="dict.value"
91 162 :value="dict.value"
92 163 >
... ... @@ -99,7 +170,7 @@ watch(
99 170 <template #default="{ row }">
100 171 <RadioGroup v-model:value="row.enabled">
101 172 <Radio
102   - v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'boolean')"
  173 + v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
103 174 :key="dict.value"
104 175 :value="dict.value"
105 176 >
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_inner/vue/student-teacher-form
... ... @@ -51,6 +51,18 @@ defineExpose({
51 51 watch(
52 52 () => props.studentId,
53 53 async (val) => {
  54 + formData.value = {
  55 + id: undefined,
  56 + studentId: undefined,
  57 + name: undefined,
  58 + description: undefined,
  59 + birthday: undefined,
  60 + sex: undefined,
  61 + enabled: undefined,
  62 + avatar: undefined,
  63 + video: undefined,
  64 + memo: undefined,
  65 + };
54 66 if (!val) {
55 67 return;
56 68 }
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_normal/vue/form
... ... @@ -72,7 +72,11 @@ const [Modal, modalApi] = useVbenModal({
72 72 async onConfirm() {
73 73 await formRef.value?.validate();
74 74 // 校验子表单
75   - try {
  75 + if (!studentContactFormRef.value?.validate()) {
  76 + subTabsName.value = 'studentContact';
  77 + return;
  78 + }
  79 + try {
76 80 await studentTeacherFormRef.value?.validate()
77 81 } catch (e) {
78 82 subTabsName.value = 'studentTeacher'
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_normal/vue/index
... ... @@ -27,12 +27,11 @@ const total = ref(0) // 列表的总页数
27 27 const queryParams = reactive({
28 28 pageNo: 1,
29 29 pageSize: 10,
30   - name: undefined,
31   - birthday: undefined,
32   - birthday: undefined,
33   - sex: undefined,
34   - enabled: undefined,
35   - createTime: undefined,
  30 + name: undefined,
  31 + birthday: undefined,
  32 + sex: undefined,
  33 + enabled: undefined,
  34 + createTime: undefined,
36 35 })
37 36 const queryFormRef = ref() // 搜索的表单
38 37 const exportLoading = ref(false) // 导出的加载中
... ... @@ -128,7 +127,14 @@ function handleRowCheckboxChange({
128 127 async function handleExport() {
129 128 try {
130 129 exportLoading.value = true;
131   - const data = await exportStudent(queryParams);
  130 + const params = cloneDeep(queryParams) as any;
  131 + if (params.birthday && Array.isArray(params.birthday)) {
  132 + params.birthday = (params.birthday as string[]).join(',');
  133 + }
  134 + if (params.createTime && Array.isArray(params.createTime)) {
  135 + params.createTime = (params.createTime as string[]).join(',');
  136 + }
  137 + const data = await exportStudent(params);
132 138 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
133 139 }finally {
134 140 exportLoading.value = false;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_normal/vue/student-contact-form
... ... @@ -32,25 +32,96 @@ async function handleDelete(row: StudentApi.StudentContact) {
32 32 await tableRef.value?.remove(row);
33 33 }
34 34  
35   -/** 提供获取表格数据的方法供父组件调用 */
36   -defineExpose({
37   - getData: (): StudentApi.StudentContact[] => {
38   - const data = list.value as StudentApi.StudentContact[];
39   - const removeRecords = tableRef.value?.getRemoveRecords() as StudentApi.StudentContact[];
40   - const insertRecords = tableRef.value?.getInsertRecords() as StudentApi.StudentContact[];
41   - return [
42   - ...data.filter(
43   - (row) => !removeRecords.some((removed) => removed.id === row.id),
44   - ),
45   - ...insertRecords.map((row: any) => ({ ...row, id: undefined })),
46   - ];
47   - },
48   -});
  35 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  36 +function getData(): StudentApi.StudentContact[] {
  37 + const table = tableRef.value;
  38 + if (!table) {
  39 + return [];
  40 + }
  41 + const { fullData } = table.getTableData();
  42 + const inserted = new Set(table.getInsertRecords());
  43 + return fullData.map((row) =>
  44 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  45 + );
  46 +}
  47 +
  48 +/** 校验子表必填字段 */
  49 +function validate(): boolean {
  50 + for (const [index, row] of getData().entries()) {
  51 + if (
  52 + row.name === null ||
  53 + row.name === undefined ||
  54 + (typeof row.name === 'string' && String(row.name).trim() === '') ||
  55 + (Array.isArray(row.name) && row.name.length === 0)
  56 + ) {
  57 + message.error(`第 ${index + 1} 行:名字不能为空`);
  58 + return false;
  59 + }
  60 + if (
  61 + row.description === null ||
  62 + row.description === undefined ||
  63 + (typeof row.description === 'string' && String(row.description).trim() === '') ||
  64 + (Array.isArray(row.description) && row.description.length === 0)
  65 + ) {
  66 + message.error(`第 ${index + 1} 行:简介不能为空`);
  67 + return false;
  68 + }
  69 + if (
  70 + row.birthday === null ||
  71 + row.birthday === undefined ||
  72 + (typeof row.birthday === 'string' && String(row.birthday).trim() === '') ||
  73 + (Array.isArray(row.birthday) && row.birthday.length === 0)
  74 + ) {
  75 + message.error(`第 ${index + 1} 行:出生日期不能为空`);
  76 + return false;
  77 + }
  78 + if (
  79 + row.sex === null ||
  80 + row.sex === undefined ||
  81 + (typeof row.sex === 'string' && String(row.sex).trim() === '') ||
  82 + (Array.isArray(row.sex) && row.sex.length === 0)
  83 + ) {
  84 + message.error(`第 ${index + 1} 行:性别不能为空`);
  85 + return false;
  86 + }
  87 + if (
  88 + row.enabled === null ||
  89 + row.enabled === undefined ||
  90 + (typeof row.enabled === 'string' && String(row.enabled).trim() === '') ||
  91 + (Array.isArray(row.enabled) && row.enabled.length === 0)
  92 + ) {
  93 + message.error(`第 ${index + 1} 行:是否有效不能为空`);
  94 + return false;
  95 + }
  96 + if (
  97 + row.avatar === null ||
  98 + row.avatar === undefined ||
  99 + (typeof row.avatar === 'string' && String(row.avatar).trim() === '') ||
  100 + (Array.isArray(row.avatar) && row.avatar.length === 0)
  101 + ) {
  102 + message.error(`第 ${index + 1} 行:头像不能为空`);
  103 + return false;
  104 + }
  105 + if (
  106 + row.memo === null ||
  107 + row.memo === undefined ||
  108 + (typeof row.memo === 'string' && String(row.memo).trim() === '') ||
  109 + (Array.isArray(row.memo) && row.memo.length === 0)
  110 + ) {
  111 + message.error(`第 ${index + 1} 行:备注不能为空`);
  112 + return false;
  113 + }
  114 + }
  115 + return true;
  116 +}
  117 +
  118 +defineExpose({ getData, validate });
49 119  
50 120 /** 监听主表的关联字段的变化,加载对应的子表数据 */
51 121 watch(
52 122 () => props.studentId,
53 123 async (val) => {
  124 + list.value = [];
54 125 if (!val) {
55 126 return;
56 127 }
... ... @@ -86,7 +157,7 @@ watch(
86 157 <template #default="{ row }">
87 158 <Select v-model:value="row.sex" placeholder="请选择性别">
88 159 <Select.Option
89   - v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number')"
  160 + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
90 161 :key="dict.value"
91 162 :value="dict.value"
92 163 >
... ... @@ -99,7 +170,7 @@ watch(
99 170 <template #default="{ row }">
100 171 <RadioGroup v-model:value="row.enabled">
101 172 <Radio
102   - v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'boolean')"
  173 + v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
103 174 :key="dict.value"
104 175 :value="dict.value"
105 176 >
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_master_normal/vue/student-teacher-form
... ... @@ -51,6 +51,18 @@ defineExpose({
51 51 watch(
52 52 () => props.studentId,
53 53 async (val) => {
  54 + formData.value = {
  55 + id: undefined,
  56 + studentId: undefined,
  57 + name: undefined,
  58 + description: undefined,
  59 + birthday: undefined,
  60 + sex: undefined,
  61 + enabled: undefined,
  62 + avatar: undefined,
  63 + video: undefined,
  64 + memo: undefined,
  65 + };
54 66 if (!val) {
55 67 return;
56 68 }
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_one/vue/index
... ... @@ -27,12 +27,11 @@ const total = ref(0) // 列表的总页数
27 27 const queryParams = reactive({
28 28 pageNo: 1,
29 29 pageSize: 10,
30   - name: undefined,
31   - birthday: undefined,
32   - birthday: undefined,
33   - sex: undefined,
34   - enabled: undefined,
35   - createTime: undefined,
  30 + name: undefined,
  31 + birthday: undefined,
  32 + sex: undefined,
  33 + enabled: undefined,
  34 + createTime: undefined,
36 35 })
37 36 const queryFormRef = ref() // 搜索的表单
38 37 const exportLoading = ref(false) // 导出的加载中
... ... @@ -128,7 +127,14 @@ function handleRowCheckboxChange({
128 127 async function handleExport() {
129 128 try {
130 129 exportLoading.value = true;
131   - const data = await exportStudent(queryParams);
  130 + const params = cloneDeep(queryParams) as any;
  131 + if (params.birthday && Array.isArray(params.birthday)) {
  132 + params.birthday = (params.birthday as string[]).join(',');
  133 + }
  134 + if (params.createTime && Array.isArray(params.createTime)) {
  135 + params.createTime = (params.createTime as string[]).join(',');
  136 + }
  137 + const data = await exportStudent(params);
132 138 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
133 139 }finally {
134 140 exportLoading.value = false;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_one_importEnable/vue/import-form
... ... @@ -48,6 +48,11 @@ function beforeUpload(file: FileType) {
48 48 return false;
49 49 }
50 50  
  51 +/** 移除文件 */
  52 +function handleRemove() {
  53 + fileRef.value = null;
  54 +}
  55 +
51 56 /** 下载导入模板 */
52 57 async function handleDownload() {
53 58 const data = await importStudentTemplate();
... ... @@ -58,7 +63,12 @@ async function handleDownload() {
58 63 <template>
59 64 <Modal title="导入学生" class="w-1/3">
60 65 <div class="mx-4">
61   - <Upload :max-count="1" accept=".xls,.xlsx" :before-upload="beforeUpload">
  66 + <Upload
  67 + :max-count="1"
  68 + accept=".xls,.xlsx"
  69 + :before-upload="beforeUpload"
  70 + :on-remove="handleRemove"
  71 + >
62 72 <Button type="primary"> 选择 Excel 文件 </Button>
63 73 </Upload>
64 74 </div>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_one_importEnable/vue/index
... ... @@ -28,12 +28,11 @@ const total = ref(0) // 列表的总页数
28 28 const queryParams = reactive({
29 29 pageNo: 1,
30 30 pageSize: 10,
31   - name: undefined,
32   - birthday: undefined,
33   - birthday: undefined,
34   - sex: undefined,
35   - enabled: undefined,
36   - createTime: undefined,
  31 + name: undefined,
  32 + birthday: undefined,
  33 + sex: undefined,
  34 + enabled: undefined,
  35 + createTime: undefined,
37 36 })
38 37 const queryFormRef = ref() // 搜索的表单
39 38 const exportLoading = ref(false) // 导出的加载中
... ... @@ -138,7 +137,14 @@ function handleRowCheckboxChange({
138 137 async function handleExport() {
139 138 try {
140 139 exportLoading.value = true;
141   - const data = await exportStudent(queryParams);
  140 + const params = cloneDeep(queryParams) as any;
  141 + if (params.birthday && Array.isArray(params.birthday)) {
  142 + params.birthday = (params.birthday as string[]).join(',');
  143 + }
  144 + if (params.createTime && Array.isArray(params.createTime)) {
  145 + params.createTime = (params.createTime as string[]).join(',');
  146 + }
  147 + const data = await exportStudent(params);
142 148 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
143 149 }finally {
144 150 exportLoading.value = false;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_general_tree/vue/index
... ... @@ -24,7 +24,7 @@ const loading = ref(true) // 列表的加载中
24 24 const list = ref<any[]>([]) // 树列表的数据
25 25  
26 26 const queryParams = reactive({
27   - name: undefined,
  27 + name: undefined,
28 28 })
29 29 const queryFormRef = ref() // 搜索的表单
30 30 const exportLoading = ref(false) // 导出的加载中
... ... @@ -91,7 +91,8 @@ async function handleDelete(row: CategoryApi.Category) {
91 91 async function handleExport() {
92 92 try {
93 93 exportLoading.value = true;
94   - const data = await exportCategory(queryParams);
  94 + const params = cloneDeep(queryParams) as any;
  95 + const data = await exportCategory(params);
95 96 downloadFileFromBlobPart({ fileName: '分类.xls', source: data });
96 97 }finally {
97 98 exportLoading.value = false;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_erp/vue/form
... ... @@ -5,7 +5,7 @@ import { computed, ref } from &#39;vue&#39;;
5 5  
6 6 import { useVbenModal } from '@vben/common-ui';
7 7  
8   -import { message, Tabs } from 'ant-design-vue';
  8 +import { message } from 'ant-design-vue';
9 9  
10 10 import { useVbenForm } from '#/adapter/form';
11 11 import { createStudent, getStudent, updateStudent } from '#/api/infra/demo';
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_inner/ts/data
... ... @@ -269,7 +269,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
269 269 minWidth: 120,
270 270 slots: { default: 'sex' },
271 271 params: {
272   - options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'boolean'),
  272 + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'),
273 273 },
274 274 },
275 275 {
... ... @@ -309,7 +309,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
309 309 }
310 310  
311 311 /** 列表的字段 */
312   - export function useStudentContactGridColumns(): VxeTableGridOptions<StudentApi.StudentContact>['columns'] {
  312 + export function useStudentContactGridColumns(): VxeTableGridOptions<InfraStudentApi.StudentContact>['columns'] {
313 313 return [
314 314 {
315 315 field: 'id',
... ... @@ -490,7 +490,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
490 490 }
491 491  
492 492 /** 列表的字段 */
493   - export function useStudentTeacherGridColumns(): VxeTableGridOptions<StudentApi.StudentTeacher>['columns'] {
  493 + export function useStudentTeacherGridColumns(): VxeTableGridOptions<InfraStudentApi.StudentTeacher>['columns'] {
494 494 return [
495 495 {
496 496 field: 'id',
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_inner/vue/form
... ... @@ -5,7 +5,7 @@ import { computed, ref } from &#39;vue&#39;;
5 5  
6 6 import { useVbenModal } from '@vben/common-ui';
7 7  
8   -import { message } from 'ant-design-vue';
  8 +import { message, Tabs } from 'ant-design-vue';
9 9  
10 10 import { useVbenForm } from '#/adapter/form';
11 11 import { createStudent, getStudent, updateStudent } from '#/api/infra/demo';
... ... @@ -48,7 +48,12 @@ const [Modal, modalApi] = useVbenModal({
48 48 return;
49 49 }
50 50 // 校验子表单
51   - const studentTeacherValid = await studentTeacherFormRef.value?.validate();
  51 + const studentContactValid = await studentContactFormRef.value?.validate();
  52 + if (!studentContactValid) {
  53 + subTabsName.value = 'studentContact';
  54 + return;
  55 + }
  56 + const studentTeacherValid = await studentTeacherFormRef.value?.validate();
52 57 if (!studentTeacherValid) {
53 58 subTabsName.value = 'studentTeacher';
54 59 return;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_inner/vue/student-contact-form
... ... @@ -6,7 +6,7 @@
6 6 import { $t } from '#/locales';
7 7  
8 8 import { Plus } from "@vben/icons";
9   -import { Button, Tabs, Checkbox, Input, Textarea, Select,RadioGroup,CheckboxGroup, DatePicker } from 'ant-design-vue';
  9 +import { Button, Checkbox, CheckboxGroup, DatePicker, Input, message, RadioGroup, Select, Tabs, Textarea } from 'ant-design-vue';
10 10 import { ImageUpload, FileUpload } from "#/components/upload";
11 11 import { useVbenVxeGrid } from '#/adapter/vxe-table';
12 12 import { useStudentContactGridEditColumns } from '../data';
... ... @@ -45,26 +45,96 @@ const handleDelete = async (row: InfraStudentApi.StudentContact) =&gt; {
45 45 await gridApi.grid.remove(row);
46 46 }
47 47  
48   -/** 提供获取表格数据的方法供父组件调用 */
49   -defineExpose({
50   - getData: (): InfraStudentApi.StudentContact[] => {
51   - const data = gridApi.grid.getData() as InfraStudentApi.StudentContact[];
52   - const removeRecords = gridApi.grid.getRemoveRecords() as InfraStudentApi.StudentContact[];
53   - const insertRecords = gridApi.grid.getInsertRecords() as InfraStudentApi.StudentContact[];
54   - return data
55   - .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
56   - .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
57   - },
58   -});
  48 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  49 +function getData(): InfraStudentApi.StudentContact[] {
  50 + const { fullData } = gridApi.grid.getTableData();
  51 + const inserted = new Set(gridApi.grid.getInsertRecords());
  52 + return fullData.map((row) =>
  53 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  54 + );
  55 +}
  56 +
  57 +/** 校验子表必填字段 */
  58 +function validate(): boolean {
  59 + for (const [index, row] of getData().entries()) {
  60 + if (
  61 + row.name === null ||
  62 + row.name === undefined ||
  63 + (typeof row.name === 'string' && String(row.name).trim() === '') ||
  64 + (Array.isArray(row.name) && row.name.length === 0)
  65 + ) {
  66 + message.error(`第 ${index + 1} 行:名字不能为空`);
  67 + return false;
  68 + }
  69 + if (
  70 + row.description === null ||
  71 + row.description === undefined ||
  72 + (typeof row.description === 'string' && String(row.description).trim() === '') ||
  73 + (Array.isArray(row.description) && row.description.length === 0)
  74 + ) {
  75 + message.error(`第 ${index + 1} 行:简介不能为空`);
  76 + return false;
  77 + }
  78 + if (
  79 + row.birthday === null ||
  80 + row.birthday === undefined ||
  81 + (typeof row.birthday === 'string' && String(row.birthday).trim() === '') ||
  82 + (Array.isArray(row.birthday) && row.birthday.length === 0)
  83 + ) {
  84 + message.error(`第 ${index + 1} 行:出生日期不能为空`);
  85 + return false;
  86 + }
  87 + if (
  88 + row.sex === null ||
  89 + row.sex === undefined ||
  90 + (typeof row.sex === 'string' && String(row.sex).trim() === '') ||
  91 + (Array.isArray(row.sex) && row.sex.length === 0)
  92 + ) {
  93 + message.error(`第 ${index + 1} 行:性别不能为空`);
  94 + return false;
  95 + }
  96 + if (
  97 + row.enabled === null ||
  98 + row.enabled === undefined ||
  99 + (typeof row.enabled === 'string' && String(row.enabled).trim() === '') ||
  100 + (Array.isArray(row.enabled) && row.enabled.length === 0)
  101 + ) {
  102 + message.error(`第 ${index + 1} 行:是否有效不能为空`);
  103 + return false;
  104 + }
  105 + if (
  106 + row.avatar === null ||
  107 + row.avatar === undefined ||
  108 + (typeof row.avatar === 'string' && String(row.avatar).trim() === '') ||
  109 + (Array.isArray(row.avatar) && row.avatar.length === 0)
  110 + ) {
  111 + message.error(`第 ${index + 1} 行:头像不能为空`);
  112 + return false;
  113 + }
  114 + if (
  115 + row.memo === null ||
  116 + row.memo === undefined ||
  117 + (typeof row.memo === 'string' && String(row.memo).trim() === '') ||
  118 + (Array.isArray(row.memo) && row.memo.length === 0)
  119 + ) {
  120 + message.error(`第 ${index + 1} 行:备注不能为空`);
  121 + return false;
  122 + }
  123 + }
  124 + return true;
  125 +}
  126 +
  127 +defineExpose({ getData, validate });
59 128  
60 129 /** 监听主表的关联字段的变化,加载对应的子表数据 */
61 130 watch(
62 131 () => props.studentId,
63 132 async (val) => {
  133 + await nextTick();
64 134 if (!val) {
  135 + await gridApi.grid.loadData([]);
65 136 return;
66 137 }
67   - await nextTick();
68 138 await gridApi.grid.loadData(await getStudentContactListByStudentId(props.studentId!));
69 139 },
70 140 { immediate: true },
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_inner/vue/student-teacher-form
... ... @@ -39,10 +39,11 @@ defineExpose({
39 39 watch(
40 40 () => props.studentId,
41 41 async (val) => {
  42 + await nextTick();
  43 + await formApi.resetForm();
42 44 if (!val) {
43 45 return;
44 46 }
45   - await nextTick();
46 47 await formApi.setValues(await getStudentTeacherByStudentId(props.studentId!));
47 48 },
48 49 { immediate: true },
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_normal/ts/data
... ... @@ -268,7 +268,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
268 268 minWidth: 120,
269 269 slots: { default: 'sex' },
270 270 params: {
271   - options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'boolean'),
  271 + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'),
272 272 },
273 273 },
274 274 {
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_normal/vue/form
... ... @@ -5,7 +5,7 @@ import { computed, ref } from &#39;vue&#39;;
5 5  
6 6 import { useVbenModal } from '@vben/common-ui';
7 7  
8   -import { message } from 'ant-design-vue';
  8 +import { message, Tabs } from 'ant-design-vue';
9 9  
10 10 import { useVbenForm } from '#/adapter/form';
11 11 import { createStudent, getStudent, updateStudent } from '#/api/infra/demo';
... ... @@ -48,7 +48,12 @@ const [Modal, modalApi] = useVbenModal({
48 48 return;
49 49 }
50 50 // 校验子表单
51   - const studentTeacherValid = await studentTeacherFormRef.value?.validate();
  51 + const studentContactValid = await studentContactFormRef.value?.validate();
  52 + if (!studentContactValid) {
  53 + subTabsName.value = 'studentContact';
  54 + return;
  55 + }
  56 + const studentTeacherValid = await studentTeacherFormRef.value?.validate();
52 57 if (!studentTeacherValid) {
53 58 subTabsName.value = 'studentTeacher';
54 59 return;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_normal/vue/student-contact-form
... ... @@ -6,7 +6,7 @@
6 6 import { $t } from '#/locales';
7 7  
8 8 import { Plus } from "@vben/icons";
9   -import { Button, Tabs, Checkbox, Input, Textarea, Select,RadioGroup,CheckboxGroup, DatePicker } from 'ant-design-vue';
  9 +import { Button, Checkbox, CheckboxGroup, DatePicker, Input, message, RadioGroup, Select, Tabs, Textarea } from 'ant-design-vue';
10 10 import { ImageUpload, FileUpload } from "#/components/upload";
11 11 import { useVbenVxeGrid } from '#/adapter/vxe-table';
12 12 import { useStudentContactGridEditColumns } from '../data';
... ... @@ -45,26 +45,96 @@ const handleDelete = async (row: InfraStudentApi.StudentContact) =&gt; {
45 45 await gridApi.grid.remove(row);
46 46 }
47 47  
48   -/** 提供获取表格数据的方法供父组件调用 */
49   -defineExpose({
50   - getData: (): InfraStudentApi.StudentContact[] => {
51   - const data = gridApi.grid.getData() as InfraStudentApi.StudentContact[];
52   - const removeRecords = gridApi.grid.getRemoveRecords() as InfraStudentApi.StudentContact[];
53   - const insertRecords = gridApi.grid.getInsertRecords() as InfraStudentApi.StudentContact[];
54   - return data
55   - .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
56   - .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
57   - },
58   -});
  48 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  49 +function getData(): InfraStudentApi.StudentContact[] {
  50 + const { fullData } = gridApi.grid.getTableData();
  51 + const inserted = new Set(gridApi.grid.getInsertRecords());
  52 + return fullData.map((row) =>
  53 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  54 + );
  55 +}
  56 +
  57 +/** 校验子表必填字段 */
  58 +function validate(): boolean {
  59 + for (const [index, row] of getData().entries()) {
  60 + if (
  61 + row.name === null ||
  62 + row.name === undefined ||
  63 + (typeof row.name === 'string' && String(row.name).trim() === '') ||
  64 + (Array.isArray(row.name) && row.name.length === 0)
  65 + ) {
  66 + message.error(`第 ${index + 1} 行:名字不能为空`);
  67 + return false;
  68 + }
  69 + if (
  70 + row.description === null ||
  71 + row.description === undefined ||
  72 + (typeof row.description === 'string' && String(row.description).trim() === '') ||
  73 + (Array.isArray(row.description) && row.description.length === 0)
  74 + ) {
  75 + message.error(`第 ${index + 1} 行:简介不能为空`);
  76 + return false;
  77 + }
  78 + if (
  79 + row.birthday === null ||
  80 + row.birthday === undefined ||
  81 + (typeof row.birthday === 'string' && String(row.birthday).trim() === '') ||
  82 + (Array.isArray(row.birthday) && row.birthday.length === 0)
  83 + ) {
  84 + message.error(`第 ${index + 1} 行:出生日期不能为空`);
  85 + return false;
  86 + }
  87 + if (
  88 + row.sex === null ||
  89 + row.sex === undefined ||
  90 + (typeof row.sex === 'string' && String(row.sex).trim() === '') ||
  91 + (Array.isArray(row.sex) && row.sex.length === 0)
  92 + ) {
  93 + message.error(`第 ${index + 1} 行:性别不能为空`);
  94 + return false;
  95 + }
  96 + if (
  97 + row.enabled === null ||
  98 + row.enabled === undefined ||
  99 + (typeof row.enabled === 'string' && String(row.enabled).trim() === '') ||
  100 + (Array.isArray(row.enabled) && row.enabled.length === 0)
  101 + ) {
  102 + message.error(`第 ${index + 1} 行:是否有效不能为空`);
  103 + return false;
  104 + }
  105 + if (
  106 + row.avatar === null ||
  107 + row.avatar === undefined ||
  108 + (typeof row.avatar === 'string' && String(row.avatar).trim() === '') ||
  109 + (Array.isArray(row.avatar) && row.avatar.length === 0)
  110 + ) {
  111 + message.error(`第 ${index + 1} 行:头像不能为空`);
  112 + return false;
  113 + }
  114 + if (
  115 + row.memo === null ||
  116 + row.memo === undefined ||
  117 + (typeof row.memo === 'string' && String(row.memo).trim() === '') ||
  118 + (Array.isArray(row.memo) && row.memo.length === 0)
  119 + ) {
  120 + message.error(`第 ${index + 1} 行:备注不能为空`);
  121 + return false;
  122 + }
  123 + }
  124 + return true;
  125 +}
  126 +
  127 +defineExpose({ getData, validate });
59 128  
60 129 /** 监听主表的关联字段的变化,加载对应的子表数据 */
61 130 watch(
62 131 () => props.studentId,
63 132 async (val) => {
  133 + await nextTick();
64 134 if (!val) {
  135 + await gridApi.grid.loadData([]);
65 136 return;
66 137 }
67   - await nextTick();
68 138 await gridApi.grid.loadData(await getStudentContactListByStudentId(props.studentId!));
69 139 },
70 140 { immediate: true },
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_master_normal/vue/student-teacher-form
... ... @@ -39,10 +39,11 @@ defineExpose({
39 39 watch(
40 40 () => props.studentId,
41 41 async (val) => {
  42 + await nextTick();
  43 + await formApi.resetForm();
42 44 if (!val) {
43 45 return;
44 46 }
45   - await nextTick();
46 47 await formApi.setValues(await getStudentTeacherByStudentId(props.studentId!));
47 48 },
48 49 { immediate: true },
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_one_importEnable/vue/import-form
... ... @@ -51,6 +51,11 @@ function beforeUpload(file: FileType) {
51 51 return false;
52 52 }
53 53  
  54 +/** 移除文件 */
  55 +function handleRemove() {
  56 + formApi.setFieldValue('file', undefined);
  57 +}
  58 +
54 59 /** 下载模板 */
55 60 async function handleDownload() {
56 61 const data = await importStudentTemplate();
... ... @@ -67,6 +72,7 @@ async function handleDownload() {
67 72 :max-count="1"
68 73 accept=".xls,.xlsx"
69 74 :before-upload="beforeUpload"
  75 + :on-remove="handleRemove"
70 76 >
71 77 <Button type="primary"> 选择 Excel 文件 </Button>
72 78 </Upload>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_antd_schema_tree/ts/data
... ... @@ -32,7 +32,7 @@ export function useFormSchema(): VbenFormSchema[] {
32 32 id: 0,
33 33 name: '顶级分类',
34 34 });
35   - return handleTree(data);
  35 + return handleTree(data, 'id', 'parentId');
36 36 },
37 37 labelField: 'name',
38 38 valueField: 'id',
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_erp/vue/form
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3 import type { FormRules } from 'element-plus';
4 4  
5 5 import { useVbenModal } from '@vben/common-ui';
... ... @@ -11,7 +11,7 @@ import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElSelect, El
11 11  
12 12 import { computed, ref, reactive } from 'vue';
13 13 import { $t } from '#/locales';
14   -import { getStudent, createStudent, updateStudent } from '#/api/infra/student';
  14 +import { getStudent, createStudent, updateStudent } from '#/api/infra/demo';
15 15  
16 16 const emit = defineEmits(['success']);
17 17  
... ... @@ -102,56 +102,56 @@ const [Modal, modalApi] = useVbenModal({
102 102  
103 103 <template>
104 104 <Modal :title="getTitle">
105   - <el-form
  105 + <ElForm
106 106 ref="formRef"
107 107 :model="formData"
108 108 :rules="rules"
109 109 label-width="120px"
110 110 label-position="right"
111 111 >
112   - <el-form-item label="名字" prop="name">
113   - <el-input v-model="formData.name" placeholder="请输入名字" />
114   - </el-form-item>
115   - <el-form-item label="简介" prop="description">
116   - <el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
117   - </el-form-item>
118   - <el-form-item label="出生日期" prop="birthday">
119   - <el-date-picker
  112 + <ElFormItem label="名字" prop="name">
  113 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  114 + </ElFormItem>
  115 + <ElFormItem label="简介" prop="description">
  116 + <ElInput v-model="formData.description" type="textarea" placeholder="请输入简介" />
  117 + </ElFormItem>
  118 + <ElFormItem label="出生日期" prop="birthday">
  119 + <ElDatePicker
120 120 v-model="formData.birthday"
121 121 value-format="x"
122 122 placeholder="选择出生日期"
123 123 />
124   - </el-form-item>
125   - <el-form-item label="性别" prop="sex">
126   - <el-select v-model="formData.sex" placeholder="请选择性别">
127   - <el-option
  124 + </ElFormItem>
  125 + <ElFormItem label="性别" prop="sex">
  126 + <ElSelect v-model="formData.sex" placeholder="请选择性别">
  127 + <ElOption
128 128 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
129 129 :key="dict.value"
130 130 :value="dict.value"
131 131 :label="dict.label"
132 132 />
133   - </el-select>
134   - </el-form-item>
135   - <el-form-item label="是否有效" prop="enabled">
136   - <el-radio-group v-model="formData.enabled">
137   - <el-radio
  133 + </ElSelect>
  134 + </ElFormItem>
  135 + <ElFormItem label="是否有效" prop="enabled">
  136 + <ElRadioGroup v-model="formData.enabled">
  137 + <ElRadio
138 138 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
139 139 :key="dict.value"
140   - :label="dict.value"
  140 + :value="dict.value"
141 141 >
142 142 {{ dict.label }}
143   - </el-radio>
144   - </el-radio-group>
145   - </el-form-item>
146   - <el-form-item label="头像" prop="avatar">
  143 + </ElRadio>
  144 + </ElRadioGroup>
  145 + </ElFormItem>
  146 + <ElFormItem label="头像" prop="avatar">
147 147 <ImageUpload v-model="formData.avatar" />
148   - </el-form-item>
149   - <el-form-item label="附件" prop="video">
  148 + </ElFormItem>
  149 + <ElFormItem label="附件" prop="video">
150 150 <FileUpload v-model="formData.video" />
151   - </el-form-item>
152   - <el-form-item label="备注" prop="memo">
  151 + </ElFormItem>
  152 + <ElFormItem label="备注" prop="memo">
153 153 <RichTextarea v-model="formData.memo" height="500px" />
154   - </el-form-item>
155   - </el-form>
  154 + </ElFormItem>
  155 + </ElForm>
156 156 </Modal>
157 157 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_erp/vue/index
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3  
4 4 import { ref, h, reactive, onMounted, nextTick } from 'vue';
5 5  
... ... @@ -19,7 +19,7 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;;
19 19  
20 20 import { $t } from '#/locales';
21 21 import { isEmpty } from '@vben/utils';
22   -import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/student';
  22 +import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/demo';
23 23  
24 24 /** 子表的列表 */
25 25 const subTabsName = ref('studentContact')
... ... @@ -35,12 +35,11 @@ const total = ref(0) // 列表的总页数
35 35 const queryParams = reactive({
36 36 pageNo: 1,
37 37 pageSize: 10,
38   - name: undefined,
39   - birthday: undefined,
40   - birthday: undefined,
41   - sex: undefined,
42   - enabled: undefined,
43   - createTime: undefined,
  38 + name: undefined,
  39 + birthday: undefined,
  40 + sex: undefined,
  41 + enabled: undefined,
  42 + createTime: undefined,
44 43 })
45 44 const queryFormRef = ref() // 搜索的表单
46 45 const exportLoading = ref(false) // 导出的加载中
... ... @@ -134,7 +133,14 @@ function handleRowCheckboxChange({
134 133 async function handleExport() {
135 134 try {
136 135 exportLoading.value = true;
137   - const data = await exportStudent(queryParams);
  136 + const params = cloneDeep(queryParams) as any;
  137 + if (params.birthday && Array.isArray(params.birthday)) {
  138 + params.birthday = (params.birthday as string[]).join(',');
  139 + }
  140 + if (params.createTime && Array.isArray(params.createTime)) {
  141 + params.createTime = (params.createTime as string[]).join(',');
  142 + }
  143 + const data = await exportStudent(params);
138 144 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
139 145 }finally {
140 146 exportLoading.value = false;
... ... @@ -156,61 +162,61 @@ onMounted(() =&gt; {
156 162  
157 163 <ContentWrap v-if="!hiddenSearchBar">
158 164 <!-- 搜索工作栏 -->
159   - <el-form
  165 + <ElForm
160 166 :model="queryParams"
161 167 ref="queryFormRef"
162 168 inline
163 169 >
164   - <el-form-item label="名字">
165   - <el-input
  170 + <ElFormItem label="名字">
  171 + <ElInput
166 172 v-model="queryParams.name"
167 173 placeholder="请输入名字"
168 174 clearable
169 175 @keyup.enter="handleQuery"
170 176 class="!w-[240px]"
171 177 />
172   - </el-form-item>
173   - <el-form-item label="出生日期">
174   - <el-date-picker
  178 + </ElFormItem>
  179 + <ElFormItem label="出生日期">
  180 + <ElDatePicker
175 181 v-model="queryParams.birthday"
176 182 value-format="YYYY-MM-DD"
177 183 placeholder="选择出生日期"
178 184 clearable
179 185 class="!w-[240px]"
180 186 />
181   - </el-form-item>
182   - <el-form-item label="性别">
183   - <el-select
  187 + </ElFormItem>
  188 + <ElFormItem label="性别">
  189 + <ElSelect
184 190 v-model="queryParams.sex"
185 191 placeholder="请选择性别"
186 192 clearable
187 193 class="!w-[240px]"
188 194 >
189   - <el-option
  195 + <ElOption
190 196 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
191 197 :key="dict.value"
192 198 :value="dict.value"
193 199 :label="dict.label"
194 200 />
195   - </el-select>
196   - </el-form-item>
197   - <el-form-item label="是否有效">
198   - <el-select
  201 + </ElSelect>
  202 + </ElFormItem>
  203 + <ElFormItem label="是否有效">
  204 + <ElSelect
199 205 v-model="queryParams.enabled"
200 206 placeholder="请选择是否有效"
201 207 clearable
202 208 class="!w-[240px]"
203 209 >
204   - <el-option
  210 + <ElOption
205 211 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
206 212 :key="dict.value"
207 213 :value="dict.value"
208 214 :label="dict.label"
209 215 />
210   - </el-select>
211   - </el-form-item>
212   - <el-form-item label="创建时间">
213   - <el-date-picker
  216 + </ElSelect>
  217 + </ElFormItem>
  218 + <ElFormItem label="创建时间">
  219 + <ElDatePicker
214 220 v-model="queryParams.createTime"
215 221 type="daterange"
216 222 value-format="YYYY-MM-DD"
... ... @@ -219,24 +225,24 @@ onMounted(() =&gt; {
219 225 end-placeholder="结束日期"
220 226 class="!w-[240px]"
221 227 />
222   - </el-form-item>
223   - <el-form-item>
224   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
225   - <el-button class="ml-2" @click="handleQuery" type="primary">
  228 + </ElFormItem>
  229 + <ElFormItem>
  230 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  231 + <ElButton class="ml-2" @click="handleQuery" type="primary">
226 232 搜索
227   - </el-button>
228   - </el-form-item>
229   - </el-form>
  233 + </ElButton>
  234 + </ElFormItem>
  235 + </ElForm>
230 236 </ContentWrap>
231 237  
232 238 <!-- 列表 -->
233 239 <ContentWrap title="学生">
234 240 <template #extra>
235   - <TableToolbar
  241 + <VbenVxeTableToolbar
236 242 ref="tableToolbarRef"
237 243 v-model:hidden-search="hiddenSearchBar"
238 244 >
239   - <el-button
  245 + <ElButton
240 246 class="ml-2"
241 247 :icon="h(Plus)"
242 248 type="primary"
... ... @@ -244,8 +250,8 @@ onMounted(() =&gt; {
244 250 v-access:code="['infra:student:create']"
245 251 >
246 252 {{ $t('ui.actionTitle.create', ['学生']) }}
247   - </el-button>
248   - <el-button
  253 + </ElButton>
  254 + <ElButton
249 255 :icon="h(Download)"
250 256 type="primary"
251 257 class="ml-2"
... ... @@ -254,8 +260,8 @@ onMounted(() =&gt; {
254 260 v-access:code="['infra:student:export']"
255 261 >
256 262 {{ $t('ui.actionTitle.export') }}
257   - </el-button>
258   - <el-button
  263 + </ElButton>
  264 + <ElButton
259 265 :icon="h(Trash2)"
260 266 type="danger"
261 267 class="ml-2"
... ... @@ -264,10 +270,10 @@ onMounted(() =&gt; {
264 270 v-access:code="['infra:student:delete']"
265 271 >
266 272 批量删除
267   - </el-button>
268   - </TableToolbar>
  273 + </ElButton>
  274 + </VbenVxeTableToolbar>
269 275 </template>
270   - <vxe-table
  276 + <VxeTable
271 277 ref="tableRef"
272 278 :data="list"
273 279 @cell-click="onCellClick"
... ... @@ -281,36 +287,36 @@ onMounted(() =&gt; {
281 287 @checkboxAll="handleRowCheckboxChange"
282 288 @checkboxChange="handleRowCheckboxChange"
283 289 >
284   - <vxe-column type="checkbox" width="40"></vxe-column>
285   - <vxe-column field="id" title="编号" align="center" />
286   - <vxe-column field="name" title="名字" align="center" />
287   - <vxe-column field="description" title="简介" align="center" />
288   - <vxe-column field="birthday" title="出生日期" align="center">
  290 + <VxeColumn type="checkbox" width="40"></VxeColumn>
  291 + <VxeColumn field="id" title="编号" align="center" />
  292 + <VxeColumn field="name" title="名字" align="center" />
  293 + <VxeColumn field="description" title="简介" align="center" />
  294 + <VxeColumn field="birthday" title="出生日期" align="center">
289 295 <template #default="{row}">
290 296 {{formatDateTime(row.birthday)}}
291 297 </template>
292   - </vxe-column>
293   - <vxe-column field="sex" title="性别" align="center">
  298 + </VxeColumn>
  299 + <VxeColumn field="sex" title="性别" align="center">
294 300 <template #default="{row}">
295 301 <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
296 302 </template>
297   - </vxe-column>
298   - <vxe-column field="enabled" title="是否有效" align="center">
  303 + </VxeColumn>
  304 + <VxeColumn field="enabled" title="是否有效" align="center">
299 305 <template #default="{row}">
300 306 <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.enabled" />
301 307 </template>
302   - </vxe-column>
303   - <vxe-column field="avatar" title="头像" align="center" />
304   - <vxe-column field="video" title="附件" align="center" />
305   - <vxe-column field="memo" title="备注" align="center" />
306   - <vxe-column field="createTime" title="创建时间" align="center">
  308 + </VxeColumn>
  309 + <VxeColumn field="avatar" title="头像" align="center" />
  310 + <VxeColumn field="video" title="附件" align="center" />
  311 + <VxeColumn field="memo" title="备注" align="center" />
  312 + <VxeColumn field="createTime" title="创建时间" align="center">
307 313 <template #default="{row}">
308 314 {{formatDateTime(row.createTime)}}
309 315 </template>
310   - </vxe-column>
311   - <vxe-column field="operation" title="操作" align="center">
  316 + </VxeColumn>
  317 + <VxeColumn field="operation" title="操作" align="center">
312 318 <template #default="{row}">
313   - <el-button
  319 + <ElButton
314 320 size="small"
315 321 type="primary"
316 322 link
... ... @@ -318,8 +324,8 @@ onMounted(() =&gt; {
318 324 v-access:code="['infra:student:update']"
319 325 >
320 326 {{ $t('ui.actionTitle.edit') }}
321   - </el-button>
322   - <el-button
  327 + </ElButton>
  328 + <ElButton
323 329 size="small"
324 330 type="danger"
325 331 link
... ... @@ -328,13 +334,13 @@ onMounted(() =&gt; {
328 334 v-access:code="['infra:student:delete']"
329 335 >
330 336 {{ $t('ui.actionTitle.delete') }}
331   - </el-button>
  337 + </ElButton>
332 338 </template>
333   - </vxe-column>
334   - </vxe-table>
  339 + </VxeColumn>
  340 + </VxeTable>
335 341 <!-- 分页 -->
336 342 <div class="mt-2 flex justify-end">
337   - <el-pagination
  343 + <ElPagination
338 344 :total="total"
339 345 v-model:current-page="queryParams.pageNo"
340 346 v-model:page-size="queryParams.pageSize"
... ... @@ -347,14 +353,14 @@ onMounted(() =&gt; {
347 353 </ContentWrap>
348 354 <ContentWrap>
349 355 <!-- 子表的表单 -->
350   - <el-tabs v-model="subTabsName">
351   - <el-tab-pane name="studentContact" label="学生联系人">
  356 + <ElTabs v-model="subTabsName">
  357 + <ElTabPane name="studentContact" label="学生联系人">
352 358 <StudentContactList :student-id="selectStudent?.id" />
353   - </el-tab-pane>
354   - <el-tab-pane name="studentTeacher" label="学生班主任">
  359 + </ElTabPane>
  360 + <ElTabPane name="studentTeacher" label="学生班主任">
355 361 <StudentTeacherList :student-id="selectStudent?.id" />
356   - </el-tab-pane>
357   - </el-tabs>
  362 + </ElTabPane>
  363 + </ElTabs>
358 364 </ContentWrap>
359 365 </Page>
360 366 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_erp/vue/student-contact-form
1 1 <script lang="ts" setup>
2   - import type { StudentApi } from '#/api/infra/student';
  2 + import type { StudentApi } from '#/api/infra/demo';
3 3 import type { FormRules } from 'element-plus';
4 4  
5 5 import { computed, ref, reactive } from 'vue';
... ... @@ -13,7 +13,7 @@
13 13  
14 14 import { $t } from '#/locales';
15 15  
16   - import { getStudentContact, createStudentContact, updateStudentContact } from '#/api/infra/student';
  16 + import { getStudentContact, createStudentContact, updateStudentContact } from '#/api/infra/demo';
17 17  
18 18 const emit = defineEmits(['success']);
19 19 const getTitle = computed(() => {
... ... @@ -107,56 +107,56 @@
107 107  
108 108 <template>
109 109 <Modal :title="getTitle">
110   - <el-form
  110 + <ElForm
111 111 ref="formRef"
112 112 :model="formData"
113 113 :rules="rules"
114 114 label-width="120px"
115 115 label-position="right"
116 116 >
117   - <el-form-item label="名字" prop="name">
118   - <el-input v-model="formData.name" placeholder="请输入名字" />
119   - </el-form-item>
120   - <el-form-item label="简介" prop="description">
121   - <el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
122   - </el-form-item>
123   - <el-form-item label="出生日期" prop="birthday">
124   - <el-date-picker
  117 + <ElFormItem label="名字" prop="name">
  118 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  119 + </ElFormItem>
  120 + <ElFormItem label="简介" prop="description">
  121 + <ElInput v-model="formData.description" type="textarea" placeholder="请输入简介" />
  122 + </ElFormItem>
  123 + <ElFormItem label="出生日期" prop="birthday">
  124 + <ElDatePicker
125 125 v-model="formData.birthday"
126 126 value-format="x"
127 127 placeholder="选择出生日期"
128 128 />
129   - </el-form-item>
130   - <el-form-item label="性别" prop="sex">
131   - <el-select v-model="formData.sex" placeholder="请选择性别">
132   - <el-option
  129 + </ElFormItem>
  130 + <ElFormItem label="性别" prop="sex">
  131 + <ElSelect v-model="formData.sex" placeholder="请选择性别">
  132 + <ElOption
133 133 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
134 134 :key="dict.value"
135 135 :value="dict.value"
136 136 :label="dict.label"
137 137 />
138   - </el-select>
139   - </el-form-item>
140   - <el-form-item label="是否有效" prop="enabled">
141   - <el-radio-group v-model="formData.enabled">
142   - <el-radio
  138 + </ElSelect>
  139 + </ElFormItem>
  140 + <ElFormItem label="是否有效" prop="enabled">
  141 + <ElRadioGroup v-model="formData.enabled">
  142 + <ElRadio
143 143 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
144 144 :key="dict.value"
145   - :label="dict.value"
  145 + :value="dict.value"
146 146 >
147 147 {{ dict.label }}
148   - </el-radio>
149   - </el-radio-group>
150   - </el-form-item>
151   - <el-form-item label="头像" prop="avatar">
  148 + </ElRadio>
  149 + </ElRadioGroup>
  150 + </ElFormItem>
  151 + <ElFormItem label="头像" prop="avatar">
152 152 <ImageUpload v-model="formData.avatar" />
153   - </el-form-item>
154   - <el-form-item label="附件" prop="video">
  153 + </ElFormItem>
  154 + <ElFormItem label="附件" prop="video">
155 155 <FileUpload v-model="formData.video" />
156   - </el-form-item>
157   - <el-form-item label="备注" prop="memo">
  156 + </ElFormItem>
  157 + <ElFormItem label="备注" prop="memo">
158 158 <RichTextarea v-model="formData.memo" height="500px" />
159   - </el-form-item>
160   - </el-form>
  159 + </ElFormItem>
  160 + </ElForm>
161 161 </Modal>
162 162 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_erp/vue/student-contact-list
1 1 <script lang="ts" setup>
2   - import type { StudentApi } from '#/api/infra/student';
  2 + import type { StudentApi } from '#/api/infra/demo';
3 3 import type { VxeTableInstance } from '#/adapter/vxe-table';
4 4  
5 5 import { reactive,ref, h, nextTick,watch,onMounted } from 'vue';
6 6  
7 7 import { DICT_TYPE } from '@vben/constants';
8 8 import { getDictOptions } from '@vben/hooks';
  9 +import { formatDateTime } from '@vben/utils';
9 10  
10 11 import { DictTag } from '#/components/dict-tag';
11 12 import { getRangePickerDefaultProps } from '#/utils';
... ... @@ -21,8 +22,8 @@
21 22 import { Plus, Trash2 } from '@vben/icons';
22 23 import { $t } from '#/locales';
23 24  
24   - import { deleteStudentContact, deleteStudentContactList, getStudentContactPage } from '#/api/infra/student';
25   - import { isEmpty } from '@vben/utils';
  25 + import { deleteStudentContact, deleteStudentContactList, getStudentContactPage } from '#/api/infra/demo';
  26 + import { cloneDeep, isEmpty } from '@vben/utils';
26 27  
27 28 const props = defineProps<{
28 29 studentId?: number // 学生编号(主表的关联字段)
... ... @@ -159,61 +160,61 @@ onMounted(() =&gt; {
159 160 <div class="h-[600px]">
160 161 <ContentWrap v-if="!hiddenSearchBar">
161 162 <!-- 搜索工作栏 -->
162   - <el-form
  163 + <ElForm
163 164 :model="queryParams"
164 165 ref="queryFormRef"
165 166 inline
166 167 >
167   - <el-form-item label="名字">
168   - <el-input
  168 + <ElFormItem label="名字">
  169 + <ElInput
169 170 v-model="queryParams.name"
170 171 placeholder="请输入名字"
171 172 clearable
172 173 @keyup.enter="handleQuery"
173 174 class="!w-[240px]"
174 175 />
175   - </el-form-item>
176   - <el-form-item label="出生日期">
177   - <el-date-picker
  176 + </ElFormItem>
  177 + <ElFormItem label="出生日期">
  178 + <ElDatePicker
178 179 v-model="queryParams.birthday"
179 180 value-format="YYYY-MM-DD"
180 181 placeholder="选择出生日期"
181 182 clearable
182 183 class="!w-[240px]"
183 184 />
184   - </el-form-item>
185   - <el-form-item label="性别">
186   - <el-select
  185 + </ElFormItem>
  186 + <ElFormItem label="性别">
  187 + <ElSelect
187 188 v-model="queryParams.sex"
188 189 placeholder="请选择性别"
189 190 clearable
190 191 class="!w-[240px]"
191 192 >
192   - <el-option
  193 + <ElOption
193 194 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
194 195 :key="dict.value"
195 196 :value="dict.value"
196 197 :label="dict.label"
197 198 />
198   - </el-select>
199   - </el-form-item>
200   - <el-form-item label="是否有效">
201   - <el-select
  199 + </ElSelect>
  200 + </ElFormItem>
  201 + <ElFormItem label="是否有效">
  202 + <ElSelect
202 203 v-model="queryParams.enabled"
203 204 placeholder="请选择是否有效"
204 205 clearable
205 206 class="!w-[240px]"
206 207 >
207   - <el-option
  208 + <ElOption
208 209 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
209 210 :key="dict.value"
210 211 :value="dict.value"
211 212 :label="dict.label"
212 213 />
213   - </el-select>
214   - </el-form-item>
215   - <el-form-item label="创建时间">
216   - <el-date-picker
  214 + </ElSelect>
  215 + </ElFormItem>
  216 + <ElFormItem label="创建时间">
  217 + <ElDatePicker
217 218 v-model="queryParams.createTime"
218 219 type="daterange"
219 220 value-format="YYYY-MM-DD"
... ... @@ -222,14 +223,14 @@ onMounted(() =&gt; {
222 223 end-placeholder="结束日期"
223 224 class="!w-[240px]"
224 225 />
225   - </el-form-item>
226   - <el-form-item>
227   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
228   - <el-button class="ml-2" @click="handleQuery" type="primary">
  226 + </ElFormItem>
  227 + <ElFormItem>
  228 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  229 + <ElButton class="ml-2" @click="handleQuery" type="primary">
229 230 搜索
230   - </el-button>
231   - </el-form-item>
232   - </el-form>
  231 + </ElButton>
  232 + </ElFormItem>
  233 + </ElForm>
233 234 </ContentWrap>
234 235  
235 236 <!-- 列表 -->
... ... @@ -239,7 +240,7 @@ onMounted(() =&gt; {
239 240 ref="tableToolbarRef"
240 241 v-model:hidden-search="hiddenSearchBar"
241 242 >
242   - <el-button
  243 + <ElButton
243 244 class="ml-2"
244 245 :icon="h(Plus)"
245 246 type="primary"
... ... @@ -247,8 +248,8 @@ onMounted(() =&gt; {
247 248 v-access:code="['infra:student:create']"
248 249 >
249 250 {{ $t('ui.actionTitle.create', ['学生']) }}
250   - </el-button>
251   - <el-button
  251 + </ElButton>
  252 + <ElButton
252 253 :icon="h(Trash2)"
253 254 type="danger"
254 255 class="ml-2"
... ... @@ -257,10 +258,10 @@ onMounted(() =&gt; {
257 258 v-access:code="['infra:student:delete']"
258 259 >
259 260 批量删除
260   - </el-button>
261   - </TableToolbar>
  261 + </ElButton>
  262 + </VbenVxeTableToolbar>
262 263 </template>
263   - <vxe-table
  264 + <VxeTable
264 265 ref="tableRef"
265 266 :data="list"
266 267 show-overflow
... ... @@ -268,37 +269,37 @@ onMounted(() =&gt; {
268 269 @checkboxAll="handleRowCheckboxChange"
269 270 @checkboxChange="handleRowCheckboxChange"
270 271 >
271   - <vxe-column type="checkbox" width="40"></vxe-column>
272   - <vxe-column field="id" title="编号" align="center" />
273   - <vxe-column field="studentId" title="学生编号" align="center" />
274   - <vxe-column field="name" title="名字" align="center" />
275   - <vxe-column field="description" title="简介" align="center" />
276   - <vxe-column field="birthday" title="出生日期" align="center">
  272 + <VxeColumn type="checkbox" width="40"></VxeColumn>
  273 + <VxeColumn field="id" title="编号" align="center" />
  274 + <VxeColumn field="studentId" title="学生编号" align="center" />
  275 + <VxeColumn field="name" title="名字" align="center" />
  276 + <VxeColumn field="description" title="简介" align="center" />
  277 + <VxeColumn field="birthday" title="出生日期" align="center">
277 278 <template #default="{row}">
278 279 {{formatDateTime(row.birthday)}}
279 280 </template>
280   - </vxe-column>
281   - <vxe-column field="sex" title="性别" align="center">
  281 + </VxeColumn>
  282 + <VxeColumn field="sex" title="性别" align="center">
282 283 <template #default="{row}">
283 284 <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
284 285 </template>
285   - </vxe-column>
286   - <vxe-column field="enabled" title="是否有效" align="center">
  286 + </VxeColumn>
  287 + <VxeColumn field="enabled" title="是否有效" align="center">
287 288 <template #default="{row}">
288 289 <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.enabled" />
289 290 </template>
290   - </vxe-column>
291   - <vxe-column field="avatar" title="头像" align="center" />
292   - <vxe-column field="video" title="附件" align="center" />
293   - <vxe-column field="memo" title="备注" align="center" />
294   - <vxe-column field="createTime" title="创建时间" align="center">
  291 + </VxeColumn>
  292 + <VxeColumn field="avatar" title="头像" align="center" />
  293 + <VxeColumn field="video" title="附件" align="center" />
  294 + <VxeColumn field="memo" title="备注" align="center" />
  295 + <VxeColumn field="createTime" title="创建时间" align="center">
295 296 <template #default="{row}">
296 297 {{formatDateTime(row.createTime)}}
297 298 </template>
298   - </vxe-column>
299   - <vxe-column field="operation" title="操作" align="center">
  299 + </VxeColumn>
  300 + <VxeColumn field="operation" title="操作" align="center">
300 301 <template #default="{row}">
301   - <el-button
  302 + <ElButton
302 303 size="small"
303 304 type="primary"
304 305 link
... ... @@ -306,8 +307,8 @@ onMounted(() =&gt; {
306 307 v-access:code="['infra:student:update']"
307 308 >
308 309 {{ $t('ui.actionTitle.edit') }}
309   - </el-button>
310   - <el-button
  310 + </ElButton>
  311 + <ElButton
311 312 size="small"
312 313 type="danger"
313 314 link
... ... @@ -316,13 +317,13 @@ onMounted(() =&gt; {
316 317 v-access:code="['infra:student:delete']"
317 318 >
318 319 {{ $t('ui.actionTitle.delete') }}
319   - </el-button>
  320 + </ElButton>
320 321 </template>
321   - </vxe-column>
322   - </vxe-table>
  322 + </VxeColumn>
  323 + </VxeTable>
323 324 <!-- 分页 -->
324 325 <div class="mt-2 flex justify-end">
325   - <el-pagination
  326 + <ElPagination
326 327 :total="total"
327 328 v-model:current-page="queryParams.pageNo"
328 329 v-model:page-size="queryParams.pageSize"
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_erp/vue/student-teacher-form
1 1 <script lang="ts" setup>
2   - import type { StudentApi } from '#/api/infra/student';
  2 + import type { StudentApi } from '#/api/infra/demo';
3 3 import type { FormRules } from 'element-plus';
4 4  
5 5 import { computed, ref, reactive } from 'vue';
... ... @@ -13,7 +13,7 @@
13 13  
14 14 import { $t } from '#/locales';
15 15  
16   - import { getStudentTeacher, createStudentTeacher, updateStudentTeacher } from '#/api/infra/student';
  16 + import { getStudentTeacher, createStudentTeacher, updateStudentTeacher } from '#/api/infra/demo';
17 17  
18 18 const emit = defineEmits(['success']);
19 19 const getTitle = computed(() => {
... ... @@ -107,56 +107,56 @@
107 107  
108 108 <template>
109 109 <Modal :title="getTitle">
110   - <el-form
  110 + <ElForm
111 111 ref="formRef"
112 112 :model="formData"
113 113 :rules="rules"
114 114 label-width="120px"
115 115 label-position="right"
116 116 >
117   - <el-form-item label="名字" prop="name">
118   - <el-input v-model="formData.name" placeholder="请输入名字" />
119   - </el-form-item>
120   - <el-form-item label="简介" prop="description">
121   - <el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
122   - </el-form-item>
123   - <el-form-item label="出生日期" prop="birthday">
124   - <el-date-picker
  117 + <ElFormItem label="名字" prop="name">
  118 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  119 + </ElFormItem>
  120 + <ElFormItem label="简介" prop="description">
  121 + <ElInput v-model="formData.description" type="textarea" placeholder="请输入简介" />
  122 + </ElFormItem>
  123 + <ElFormItem label="出生日期" prop="birthday">
  124 + <ElDatePicker
125 125 v-model="formData.birthday"
126 126 value-format="x"
127 127 placeholder="选择出生日期"
128 128 />
129   - </el-form-item>
130   - <el-form-item label="性别" prop="sex">
131   - <el-select v-model="formData.sex" placeholder="请选择性别">
132   - <el-option
  129 + </ElFormItem>
  130 + <ElFormItem label="性别" prop="sex">
  131 + <ElSelect v-model="formData.sex" placeholder="请选择性别">
  132 + <ElOption
133 133 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
134 134 :key="dict.value"
135 135 :value="dict.value"
136 136 :label="dict.label"
137 137 />
138   - </el-select>
139   - </el-form-item>
140   - <el-form-item label="是否有效" prop="enabled">
141   - <el-radio-group v-model="formData.enabled">
142   - <el-radio
  138 + </ElSelect>
  139 + </ElFormItem>
  140 + <ElFormItem label="是否有效" prop="enabled">
  141 + <ElRadioGroup v-model="formData.enabled">
  142 + <ElRadio
143 143 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
144 144 :key="dict.value"
145   - :label="dict.value"
  145 + :value="dict.value"
146 146 >
147 147 {{ dict.label }}
148   - </el-radio>
149   - </el-radio-group>
150   - </el-form-item>
151   - <el-form-item label="头像" prop="avatar">
  148 + </ElRadio>
  149 + </ElRadioGroup>
  150 + </ElFormItem>
  151 + <ElFormItem label="头像" prop="avatar">
152 152 <ImageUpload v-model="formData.avatar" />
153   - </el-form-item>
154   - <el-form-item label="附件" prop="video">
  153 + </ElFormItem>
  154 + <ElFormItem label="附件" prop="video">
155 155 <FileUpload v-model="formData.video" />
156   - </el-form-item>
157   - <el-form-item label="备注" prop="memo">
  156 + </ElFormItem>
  157 + <ElFormItem label="备注" prop="memo">
158 158 <RichTextarea v-model="formData.memo" height="500px" />
159   - </el-form-item>
160   - </el-form>
  159 + </ElFormItem>
  160 + </ElForm>
161 161 </Modal>
162 162 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_erp/vue/student-teacher-list
1 1 <script lang="ts" setup>
2   - import type { StudentApi } from '#/api/infra/student';
  2 + import type { StudentApi } from '#/api/infra/demo';
3 3 import type { VxeTableInstance } from '#/adapter/vxe-table';
4 4  
5 5 import { reactive,ref, h, nextTick,watch,onMounted } from 'vue';
6 6  
7 7 import { DICT_TYPE } from '@vben/constants';
8 8 import { getDictOptions } from '@vben/hooks';
  9 +import { formatDateTime } from '@vben/utils';
9 10  
10 11 import { DictTag } from '#/components/dict-tag';
11 12 import { getRangePickerDefaultProps } from '#/utils';
... ... @@ -21,8 +22,8 @@
21 22 import { Plus, Trash2 } from '@vben/icons';
22 23 import { $t } from '#/locales';
23 24  
24   - import { deleteStudentTeacher, deleteStudentTeacherList, getStudentTeacherPage } from '#/api/infra/student';
25   - import { isEmpty } from '@vben/utils';
  25 + import { deleteStudentTeacher, deleteStudentTeacherList, getStudentTeacherPage } from '#/api/infra/demo';
  26 + import { cloneDeep, isEmpty } from '@vben/utils';
26 27  
27 28 const props = defineProps<{
28 29 studentId?: number // 学生编号(主表的关联字段)
... ... @@ -159,61 +160,61 @@ onMounted(() =&gt; {
159 160 <div class="h-[600px]">
160 161 <ContentWrap v-if="!hiddenSearchBar">
161 162 <!-- 搜索工作栏 -->
162   - <el-form
  163 + <ElForm
163 164 :model="queryParams"
164 165 ref="queryFormRef"
165 166 inline
166 167 >
167   - <el-form-item label="名字">
168   - <el-input
  168 + <ElFormItem label="名字">
  169 + <ElInput
169 170 v-model="queryParams.name"
170 171 placeholder="请输入名字"
171 172 clearable
172 173 @keyup.enter="handleQuery"
173 174 class="!w-[240px]"
174 175 />
175   - </el-form-item>
176   - <el-form-item label="出生日期">
177   - <el-date-picker
  176 + </ElFormItem>
  177 + <ElFormItem label="出生日期">
  178 + <ElDatePicker
178 179 v-model="queryParams.birthday"
179 180 value-format="YYYY-MM-DD"
180 181 placeholder="选择出生日期"
181 182 clearable
182 183 class="!w-[240px]"
183 184 />
184   - </el-form-item>
185   - <el-form-item label="性别">
186   - <el-select
  185 + </ElFormItem>
  186 + <ElFormItem label="性别">
  187 + <ElSelect
187 188 v-model="queryParams.sex"
188 189 placeholder="请选择性别"
189 190 clearable
190 191 class="!w-[240px]"
191 192 >
192   - <el-option
  193 + <ElOption
193 194 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
194 195 :key="dict.value"
195 196 :value="dict.value"
196 197 :label="dict.label"
197 198 />
198   - </el-select>
199   - </el-form-item>
200   - <el-form-item label="是否有效">
201   - <el-select
  199 + </ElSelect>
  200 + </ElFormItem>
  201 + <ElFormItem label="是否有效">
  202 + <ElSelect
202 203 v-model="queryParams.enabled"
203 204 placeholder="请选择是否有效"
204 205 clearable
205 206 class="!w-[240px]"
206 207 >
207   - <el-option
  208 + <ElOption
208 209 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
209 210 :key="dict.value"
210 211 :value="dict.value"
211 212 :label="dict.label"
212 213 />
213   - </el-select>
214   - </el-form-item>
215   - <el-form-item label="创建时间">
216   - <el-date-picker
  214 + </ElSelect>
  215 + </ElFormItem>
  216 + <ElFormItem label="创建时间">
  217 + <ElDatePicker
217 218 v-model="queryParams.createTime"
218 219 type="daterange"
219 220 value-format="YYYY-MM-DD"
... ... @@ -222,14 +223,14 @@ onMounted(() =&gt; {
222 223 end-placeholder="结束日期"
223 224 class="!w-[240px]"
224 225 />
225   - </el-form-item>
226   - <el-form-item>
227   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
228   - <el-button class="ml-2" @click="handleQuery" type="primary">
  226 + </ElFormItem>
  227 + <ElFormItem>
  228 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  229 + <ElButton class="ml-2" @click="handleQuery" type="primary">
229 230 搜索
230   - </el-button>
231   - </el-form-item>
232   - </el-form>
  231 + </ElButton>
  232 + </ElFormItem>
  233 + </ElForm>
233 234 </ContentWrap>
234 235  
235 236 <!-- 列表 -->
... ... @@ -239,7 +240,7 @@ onMounted(() =&gt; {
239 240 ref="tableToolbarRef"
240 241 v-model:hidden-search="hiddenSearchBar"
241 242 >
242   - <el-button
  243 + <ElButton
243 244 class="ml-2"
244 245 :icon="h(Plus)"
245 246 type="primary"
... ... @@ -247,8 +248,8 @@ onMounted(() =&gt; {
247 248 v-access:code="['infra:student:create']"
248 249 >
249 250 {{ $t('ui.actionTitle.create', ['学生']) }}
250   - </el-button>
251   - <el-button
  251 + </ElButton>
  252 + <ElButton
252 253 :icon="h(Trash2)"
253 254 type="danger"
254 255 class="ml-2"
... ... @@ -257,10 +258,10 @@ onMounted(() =&gt; {
257 258 v-access:code="['infra:student:delete']"
258 259 >
259 260 批量删除
260   - </el-button>
261   - </TableToolbar>
  261 + </ElButton>
  262 + </VbenVxeTableToolbar>
262 263 </template>
263   - <vxe-table
  264 + <VxeTable
264 265 ref="tableRef"
265 266 :data="list"
266 267 show-overflow
... ... @@ -268,37 +269,37 @@ onMounted(() =&gt; {
268 269 @checkboxAll="handleRowCheckboxChange"
269 270 @checkboxChange="handleRowCheckboxChange"
270 271 >
271   - <vxe-column type="checkbox" width="40"></vxe-column>
272   - <vxe-column field="id" title="编号" align="center" />
273   - <vxe-column field="studentId" title="学生编号" align="center" />
274   - <vxe-column field="name" title="名字" align="center" />
275   - <vxe-column field="description" title="简介" align="center" />
276   - <vxe-column field="birthday" title="出生日期" align="center">
  272 + <VxeColumn type="checkbox" width="40"></VxeColumn>
  273 + <VxeColumn field="id" title="编号" align="center" />
  274 + <VxeColumn field="studentId" title="学生编号" align="center" />
  275 + <VxeColumn field="name" title="名字" align="center" />
  276 + <VxeColumn field="description" title="简介" align="center" />
  277 + <VxeColumn field="birthday" title="出生日期" align="center">
277 278 <template #default="{row}">
278 279 {{formatDateTime(row.birthday)}}
279 280 </template>
280   - </vxe-column>
281   - <vxe-column field="sex" title="性别" align="center">
  281 + </VxeColumn>
  282 + <VxeColumn field="sex" title="性别" align="center">
282 283 <template #default="{row}">
283 284 <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
284 285 </template>
285   - </vxe-column>
286   - <vxe-column field="enabled" title="是否有效" align="center">
  286 + </VxeColumn>
  287 + <VxeColumn field="enabled" title="是否有效" align="center">
287 288 <template #default="{row}">
288 289 <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.enabled" />
289 290 </template>
290   - </vxe-column>
291   - <vxe-column field="avatar" title="头像" align="center" />
292   - <vxe-column field="video" title="附件" align="center" />
293   - <vxe-column field="memo" title="备注" align="center" />
294   - <vxe-column field="createTime" title="创建时间" align="center">
  291 + </VxeColumn>
  292 + <VxeColumn field="avatar" title="头像" align="center" />
  293 + <VxeColumn field="video" title="附件" align="center" />
  294 + <VxeColumn field="memo" title="备注" align="center" />
  295 + <VxeColumn field="createTime" title="创建时间" align="center">
295 296 <template #default="{row}">
296 297 {{formatDateTime(row.createTime)}}
297 298 </template>
298   - </vxe-column>
299   - <vxe-column field="operation" title="操作" align="center">
  299 + </VxeColumn>
  300 + <VxeColumn field="operation" title="操作" align="center">
300 301 <template #default="{row}">
301   - <el-button
  302 + <ElButton
302 303 size="small"
303 304 type="primary"
304 305 link
... ... @@ -306,8 +307,8 @@ onMounted(() =&gt; {
306 307 v-access:code="['infra:student:update']"
307 308 >
308 309 {{ $t('ui.actionTitle.edit') }}
309   - </el-button>
310   - <el-button
  310 + </ElButton>
  311 + <ElButton
311 312 size="small"
312 313 type="danger"
313 314 link
... ... @@ -316,13 +317,13 @@ onMounted(() =&gt; {
316 317 v-access:code="['infra:student:delete']"
317 318 >
318 319 {{ $t('ui.actionTitle.delete') }}
319   - </el-button>
  320 + </ElButton>
320 321 </template>
321   - </vxe-column>
322   - </vxe-table>
  322 + </VxeColumn>
  323 + </VxeTable>
323 324 <!-- 分页 -->
324 325 <div class="mt-2 flex justify-end">
325   - <el-pagination
  326 + <ElPagination
326 327 :total="total"
327 328 v-model:current-page="queryParams.pageNo"
328 329 v-model:page-size="queryParams.pageSize"
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_inner/vue/form
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3 import type { FormRules } from 'element-plus';
4 4  
5 5 import { useVbenModal } from '@vben/common-ui';
... ... @@ -13,7 +13,7 @@ import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElSelect, El
13 13  
14 14 import { computed, ref, reactive } from 'vue';
15 15 import { $t } from '#/locales';
16   -import { getStudent, createStudent, updateStudent } from '#/api/infra/student';
  16 +import { getStudent, createStudent, updateStudent } from '#/api/infra/demo';
17 17  
18 18 const emit = defineEmits(['success']);
19 19  
... ... @@ -71,7 +71,11 @@ const [Modal, modalApi] = useVbenModal({
71 71 async onConfirm() {
72 72 await formRef.value?.validate();
73 73 // 校验子表单
74   - try {
  74 + if (!studentContactFormRef.value?.validate()) {
  75 + subTabsName.value = 'studentContact';
  76 + return;
  77 + }
  78 + try {
75 79 await studentTeacherFormRef.value?.validate()
76 80 } catch (e) {
77 81 subTabsName.value = 'studentTeacher'
... ... @@ -118,65 +122,65 @@ const [Modal, modalApi] = useVbenModal({
118 122  
119 123 <template>
120 124 <Modal :title="getTitle">
121   - <el-form
  125 + <ElForm
122 126 ref="formRef"
123 127 :model="formData"
124 128 :rules="rules"
125 129 label-width="120px"
126 130 label-position="right"
127 131 >
128   - <el-form-item label="名字" prop="name">
129   - <el-input v-model="formData.name" placeholder="请输入名字" />
130   - </el-form-item>
131   - <el-form-item label="简介" prop="description">
132   - <el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
133   - </el-form-item>
134   - <el-form-item label="出生日期" prop="birthday">
135   - <el-date-picker
  132 + <ElFormItem label="名字" prop="name">
  133 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  134 + </ElFormItem>
  135 + <ElFormItem label="简介" prop="description">
  136 + <ElInput v-model="formData.description" type="textarea" placeholder="请输入简介" />
  137 + </ElFormItem>
  138 + <ElFormItem label="出生日期" prop="birthday">
  139 + <ElDatePicker
136 140 v-model="formData.birthday"
137 141 value-format="x"
138 142 placeholder="选择出生日期"
139 143 />
140   - </el-form-item>
141   - <el-form-item label="性别" prop="sex">
142   - <el-select v-model="formData.sex" placeholder="请选择性别">
143   - <el-option
  144 + </ElFormItem>
  145 + <ElFormItem label="性别" prop="sex">
  146 + <ElSelect v-model="formData.sex" placeholder="请选择性别">
  147 + <ElOption
144 148 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
145 149 :key="dict.value"
146 150 :value="dict.value"
147 151 :label="dict.label"
148 152 />
149   - </el-select>
150   - </el-form-item>
151   - <el-form-item label="是否有效" prop="enabled">
152   - <el-radio-group v-model="formData.enabled">
153   - <el-radio
  153 + </ElSelect>
  154 + </ElFormItem>
  155 + <ElFormItem label="是否有效" prop="enabled">
  156 + <ElRadioGroup v-model="formData.enabled">
  157 + <ElRadio
154 158 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
155 159 :key="dict.value"
156   - :label="dict.value"
  160 + :value="dict.value"
157 161 >
158 162 {{ dict.label }}
159   - </el-radio>
160   - </el-radio-group>
161   - </el-form-item>
162   - <el-form-item label="头像" prop="avatar">
  163 + </ElRadio>
  164 + </ElRadioGroup>
  165 + </ElFormItem>
  166 + <ElFormItem label="头像" prop="avatar">
163 167 <ImageUpload v-model="formData.avatar" />
164   - </el-form-item>
165   - <el-form-item label="附件" prop="video">
  168 + </ElFormItem>
  169 + <ElFormItem label="附件" prop="video">
166 170 <FileUpload v-model="formData.video" />
167   - </el-form-item>
168   - <el-form-item label="备注" prop="memo">
  171 + </ElFormItem>
  172 + <ElFormItem label="备注" prop="memo">
169 173 <RichTextarea v-model="formData.memo" height="500px" />
170   - </el-form-item>
171   - </el-form>
  174 + </ElFormItem>
  175 + </ElForm>
172 176 <!-- 子表的表单 -->
173   - <el-tabs v-model="subTabsName">
174   - <el-tab-pane name="studentContact" label="学生联系人">
  177 + <ElTabs v-model="subTabsName">
  178 + <ElTabPane name="studentContact" label="学生联系人">
175 179 <StudentContactForm ref="studentContactFormRef" :student-id="formData?.id" />
176   - </el-tab-pane>
177   - <el-tab-pane name="studentTeacher" label="学生班主任">
  180 + </ElTabPane>
  181 + <ElTabPane name="studentTeacher" label="学生班主任">
178 182 <StudentTeacherForm ref="studentTeacherFormRef" :student-id="formData?.id" />
179   - </el-tab-pane>
180   - </el-tabs>
  183 + </ElTabPane>
  184 + </ElTabs>
181 185 </Modal>
182 186 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_inner/vue/index
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3  
4 4 import { ref, h, reactive, onMounted, nextTick } from 'vue';
5 5  
... ... @@ -19,7 +19,7 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;;
19 19  
20 20 import { $t } from '#/locales';
21 21 import { isEmpty } from '@vben/utils';
22   -import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/student';
  22 +import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/demo';
23 23  
24 24 /** 子表的列表 */
25 25 const subTabsName = ref('studentContact')
... ... @@ -31,12 +31,11 @@ const total = ref(0) // 列表的总页数
31 31 const queryParams = reactive({
32 32 pageNo: 1,
33 33 pageSize: 10,
34   - name: undefined,
35   - birthday: undefined,
36   - birthday: undefined,
37   - sex: undefined,
38   - enabled: undefined,
39   - createTime: undefined,
  34 + name: undefined,
  35 + birthday: undefined,
  36 + sex: undefined,
  37 + enabled: undefined,
  38 + createTime: undefined,
40 39 })
41 40 const queryFormRef = ref() // 搜索的表单
42 41 const exportLoading = ref(false) // 导出的加载中
... ... @@ -130,7 +129,14 @@ function handleRowCheckboxChange({
130 129 async function handleExport() {
131 130 try {
132 131 exportLoading.value = true;
133   - const data = await exportStudent(queryParams);
  132 + const params = cloneDeep(queryParams) as any;
  133 + if (params.birthday && Array.isArray(params.birthday)) {
  134 + params.birthday = (params.birthday as string[]).join(',');
  135 + }
  136 + if (params.createTime && Array.isArray(params.createTime)) {
  137 + params.createTime = (params.createTime as string[]).join(',');
  138 + }
  139 + const data = await exportStudent(params);
134 140 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
135 141 }finally {
136 142 exportLoading.value = false;
... ... @@ -152,61 +158,61 @@ onMounted(() =&gt; {
152 158  
153 159 <ContentWrap v-if="!hiddenSearchBar">
154 160 <!-- 搜索工作栏 -->
155   - <el-form
  161 + <ElForm
156 162 :model="queryParams"
157 163 ref="queryFormRef"
158 164 inline
159 165 >
160   - <el-form-item label="名字">
161   - <el-input
  166 + <ElFormItem label="名字">
  167 + <ElInput
162 168 v-model="queryParams.name"
163 169 placeholder="请输入名字"
164 170 clearable
165 171 @keyup.enter="handleQuery"
166 172 class="!w-[240px]"
167 173 />
168   - </el-form-item>
169   - <el-form-item label="出生日期">
170   - <el-date-picker
  174 + </ElFormItem>
  175 + <ElFormItem label="出生日期">
  176 + <ElDatePicker
171 177 v-model="queryParams.birthday"
172 178 value-format="YYYY-MM-DD"
173 179 placeholder="选择出生日期"
174 180 clearable
175 181 class="!w-[240px]"
176 182 />
177   - </el-form-item>
178   - <el-form-item label="性别">
179   - <el-select
  183 + </ElFormItem>
  184 + <ElFormItem label="性别">
  185 + <ElSelect
180 186 v-model="queryParams.sex"
181 187 placeholder="请选择性别"
182 188 clearable
183 189 class="!w-[240px]"
184 190 >
185   - <el-option
  191 + <ElOption
186 192 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
187 193 :key="dict.value"
188 194 :value="dict.value"
189 195 :label="dict.label"
190 196 />
191   - </el-select>
192   - </el-form-item>
193   - <el-form-item label="是否有效">
194   - <el-select
  197 + </ElSelect>
  198 + </ElFormItem>
  199 + <ElFormItem label="是否有效">
  200 + <ElSelect
195 201 v-model="queryParams.enabled"
196 202 placeholder="请选择是否有效"
197 203 clearable
198 204 class="!w-[240px]"
199 205 >
200   - <el-option
  206 + <ElOption
201 207 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
202 208 :key="dict.value"
203 209 :value="dict.value"
204 210 :label="dict.label"
205 211 />
206   - </el-select>
207   - </el-form-item>
208   - <el-form-item label="创建时间">
209   - <el-date-picker
  212 + </ElSelect>
  213 + </ElFormItem>
  214 + <ElFormItem label="创建时间">
  215 + <ElDatePicker
210 216 v-model="queryParams.createTime"
211 217 type="daterange"
212 218 value-format="YYYY-MM-DD"
... ... @@ -215,24 +221,24 @@ onMounted(() =&gt; {
215 221 end-placeholder="结束日期"
216 222 class="!w-[240px]"
217 223 />
218   - </el-form-item>
219   - <el-form-item>
220   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
221   - <el-button class="ml-2" @click="handleQuery" type="primary">
  224 + </ElFormItem>
  225 + <ElFormItem>
  226 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  227 + <ElButton class="ml-2" @click="handleQuery" type="primary">
222 228 搜索
223   - </el-button>
224   - </el-form-item>
225   - </el-form>
  229 + </ElButton>
  230 + </ElFormItem>
  231 + </ElForm>
226 232 </ContentWrap>
227 233  
228 234 <!-- 列表 -->
229 235 <ContentWrap title="学生">
230 236 <template #extra>
231   - <TableToolbar
  237 + <VbenVxeTableToolbar
232 238 ref="tableToolbarRef"
233 239 v-model:hidden-search="hiddenSearchBar"
234 240 >
235   - <el-button
  241 + <ElButton
236 242 class="ml-2"
237 243 :icon="h(Plus)"
238 244 type="primary"
... ... @@ -240,8 +246,8 @@ onMounted(() =&gt; {
240 246 v-access:code="['infra:student:create']"
241 247 >
242 248 {{ $t('ui.actionTitle.create', ['学生']) }}
243   - </el-button>
244   - <el-button
  249 + </ElButton>
  250 + <ElButton
245 251 :icon="h(Download)"
246 252 type="primary"
247 253 class="ml-2"
... ... @@ -250,8 +256,8 @@ onMounted(() =&gt; {
250 256 v-access:code="['infra:student:export']"
251 257 >
252 258 {{ $t('ui.actionTitle.export') }}
253   - </el-button>
254   - <el-button
  259 + </ElButton>
  260 + <ElButton
255 261 :icon="h(Trash2)"
256 262 type="danger"
257 263 class="ml-2"
... ... @@ -260,10 +266,10 @@ onMounted(() =&gt; {
260 266 v-access:code="['infra:student:delete']"
261 267 >
262 268 批量删除
263   - </el-button>
264   - </TableToolbar>
  269 + </ElButton>
  270 + </VbenVxeTableToolbar>
265 271 </template>
266   - <vxe-table
  272 + <VxeTable
267 273 ref="tableRef"
268 274 :data="list"
269 275 show-overflow
... ... @@ -271,50 +277,50 @@ onMounted(() =&gt; {
271 277 @checkboxAll="handleRowCheckboxChange"
272 278 @checkboxChange="handleRowCheckboxChange"
273 279 >
274   - <vxe-column type="checkbox" width="40"></vxe-column>
  280 + <VxeColumn type="checkbox" width="40"></VxeColumn>
275 281 <!-- 子表的列表 -->
276   - <vxe-column type="expand" width="60">
  282 + <VxeColumn type="expand" width="60">
277 283 <template #content="{ row }">
278 284 <!-- 子表的表单 -->
279   - <el-tabs v-model="subTabsName" class="mx-8">
280   - <el-tab-pane name="studentContact" label="学生联系人">
  285 + <ElTabs v-model="subTabsName" class="mx-8">
  286 + <ElTabPane name="studentContact" label="学生联系人">
281 287 <StudentContactList :student-id="row?.id" />
282   - </el-tab-pane>
283   - <el-tab-pane name="studentTeacher" label="学生班主任">
  288 + </ElTabPane>
  289 + <ElTabPane name="studentTeacher" label="学生班主任">
284 290 <StudentTeacherList :student-id="row?.id" />
285   - </el-tab-pane>
286   - </el-tabs>
  291 + </ElTabPane>
  292 + </ElTabs>
287 293 </template>
288   - </vxe-column>
289   - <vxe-column field="id" title="编号" align="center" />
290   - <vxe-column field="name" title="名字" align="center" />
291   - <vxe-column field="description" title="简介" align="center" />
292   - <vxe-column field="birthday" title="出生日期" align="center">
  294 + </VxeColumn>
  295 + <VxeColumn field="id" title="编号" align="center" />
  296 + <VxeColumn field="name" title="名字" align="center" />
  297 + <VxeColumn field="description" title="简介" align="center" />
  298 + <VxeColumn field="birthday" title="出生日期" align="center">
293 299 <template #default="{row}">
294 300 {{formatDateTime(row.birthday)}}
295 301 </template>
296   - </vxe-column>
297   - <vxe-column field="sex" title="性别" align="center">
  302 + </VxeColumn>
  303 + <VxeColumn field="sex" title="性别" align="center">
298 304 <template #default="{row}">
299 305 <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
300 306 </template>
301   - </vxe-column>
302   - <vxe-column field="enabled" title="是否有效" align="center">
  307 + </VxeColumn>
  308 + <VxeColumn field="enabled" title="是否有效" align="center">
303 309 <template #default="{row}">
304 310 <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.enabled" />
305 311 </template>
306   - </vxe-column>
307   - <vxe-column field="avatar" title="头像" align="center" />
308   - <vxe-column field="video" title="附件" align="center" />
309   - <vxe-column field="memo" title="备注" align="center" />
310   - <vxe-column field="createTime" title="创建时间" align="center">
  312 + </VxeColumn>
  313 + <VxeColumn field="avatar" title="头像" align="center" />
  314 + <VxeColumn field="video" title="附件" align="center" />
  315 + <VxeColumn field="memo" title="备注" align="center" />
  316 + <VxeColumn field="createTime" title="创建时间" align="center">
311 317 <template #default="{row}">
312 318 {{formatDateTime(row.createTime)}}
313 319 </template>
314   - </vxe-column>
315   - <vxe-column field="operation" title="操作" align="center">
  320 + </VxeColumn>
  321 + <VxeColumn field="operation" title="操作" align="center">
316 322 <template #default="{row}">
317   - <el-button
  323 + <ElButton
318 324 size="small"
319 325 type="primary"
320 326 link
... ... @@ -322,8 +328,8 @@ onMounted(() =&gt; {
322 328 v-access:code="['infra:student:update']"
323 329 >
324 330 {{ $t('ui.actionTitle.edit') }}
325   - </el-button>
326   - <el-button
  331 + </ElButton>
  332 + <ElButton
327 333 size="small"
328 334 type="danger"
329 335 link
... ... @@ -332,13 +338,13 @@ onMounted(() =&gt; {
332 338 v-access:code="['infra:student:delete']"
333 339 >
334 340 {{ $t('ui.actionTitle.delete') }}
335   - </el-button>
  341 + </ElButton>
336 342 </template>
337   - </vxe-column>
338   - </vxe-table>
  343 + </VxeColumn>
  344 + </VxeTable>
339 345 <!-- 分页 -->
340 346 <div class="mt-2 flex justify-end">
341   - <el-pagination
  347 + <ElPagination
342 348 :total="total"
343 349 v-model:current-page="queryParams.pageNo"
344 350 v-model:page-size="queryParams.pageSize"
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_inner/vue/student-contact-form
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { VxeTableInstance } from '#/adapter/vxe-table';
  3 +import type { StudentApi } from '#/api/infra/demo';
3 4  
4   -import { computed, ref, reactive, h, onMounted,watch,nextTick } from 'vue';
  5 +import { ref, watch } from 'vue';
5 6  
6 7 import { DICT_TYPE } from '@vben/constants';
7 8 import { getDictOptions } from '@vben/hooks';
  9 +import { Plus } from '@vben/icons';
8 10  
9   -import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElButton, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker } from 'element-plus';
10   -import { $t } from '#/locales';
  11 +import {
  12 + ElButton,
  13 + ElDatePicker,
  14 + ElInput,
  15 + ElMessage,
  16 + ElOption,
  17 + ElRadio,
  18 + ElRadioGroup,
  19 + ElSelect,
  20 +} from 'element-plus';
11 21  
12   - import type { VxeTableInstance } from '#/adapter/vxe-table';
13   -import { Plus } from "@vben/icons";
14   -import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
15   -import { getStudentContactListByStudentId } from '#/api/infra/student';
  22 + import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
  23 +import { getStudentContactListByStudentId } from '#/api/infra/demo';
  24 +import { $t } from '#/locales';
16 25  
17 26 const props = defineProps<{
18 27 studentId?: number // 学生编号(主表的关联字段)
... ... @@ -30,24 +39,96 @@ async function onDelete(row: StudentApi.StudentContact) {
30 39 await tableRef.value?.remove(row);
31 40 }
32 41  
33   -/** 提供获取表格数据的方法供父组件调用 */
34   -defineExpose({
35   - getData: (): StudentApi.StudentContact[] => {
36   - const data = list.value as StudentApi.StudentContact[];
37   - const removeRecords = tableRef.value?.getRemoveRecords() as StudentApi.StudentContact[];
38   - const insertRecords = tableRef.value?.getInsertRecords() as StudentApi.StudentContact[];
39   - return [
40   - ...data.filter(
41   - (row) => !removeRecords.some((removed) => removed.id === row.id),
42   - ),
43   - ...insertRecords.map((row: any) => ({ ...row, id: undefined })),
44   - },
45   -});
  42 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  43 +function getData(): StudentApi.StudentContact[] {
  44 + const table = tableRef.value;
  45 + if (!table) {
  46 + return [];
  47 + }
  48 + const { fullData } = table.getTableData();
  49 + const inserted = new Set(table.getInsertRecords());
  50 + return fullData.map((row) =>
  51 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  52 + );
  53 +}
  54 +
  55 +/** 校验子表必填字段 */
  56 +function validate(): boolean {
  57 + for (const [index, row] of getData().entries()) {
  58 + if (
  59 + row.name === null ||
  60 + row.name === undefined ||
  61 + (typeof row.name === 'string' && String(row.name).trim() === '') ||
  62 + (Array.isArray(row.name) && row.name.length === 0)
  63 + ) {
  64 + ElMessage.error(`第 ${index + 1} 行:名字不能为空`);
  65 + return false;
  66 + }
  67 + if (
  68 + row.description === null ||
  69 + row.description === undefined ||
  70 + (typeof row.description === 'string' && String(row.description).trim() === '') ||
  71 + (Array.isArray(row.description) && row.description.length === 0)
  72 + ) {
  73 + ElMessage.error(`第 ${index + 1} 行:简介不能为空`);
  74 + return false;
  75 + }
  76 + if (
  77 + row.birthday === null ||
  78 + row.birthday === undefined ||
  79 + (typeof row.birthday === 'string' && String(row.birthday).trim() === '') ||
  80 + (Array.isArray(row.birthday) && row.birthday.length === 0)
  81 + ) {
  82 + ElMessage.error(`第 ${index + 1} 行:出生日期不能为空`);
  83 + return false;
  84 + }
  85 + if (
  86 + row.sex === null ||
  87 + row.sex === undefined ||
  88 + (typeof row.sex === 'string' && String(row.sex).trim() === '') ||
  89 + (Array.isArray(row.sex) && row.sex.length === 0)
  90 + ) {
  91 + ElMessage.error(`第 ${index + 1} 行:性别不能为空`);
  92 + return false;
  93 + }
  94 + if (
  95 + row.enabled === null ||
  96 + row.enabled === undefined ||
  97 + (typeof row.enabled === 'string' && String(row.enabled).trim() === '') ||
  98 + (Array.isArray(row.enabled) && row.enabled.length === 0)
  99 + ) {
  100 + ElMessage.error(`第 ${index + 1} 行:是否有效不能为空`);
  101 + return false;
  102 + }
  103 + if (
  104 + row.avatar === null ||
  105 + row.avatar === undefined ||
  106 + (typeof row.avatar === 'string' && String(row.avatar).trim() === '') ||
  107 + (Array.isArray(row.avatar) && row.avatar.length === 0)
  108 + ) {
  109 + ElMessage.error(`第 ${index + 1} 行:头像不能为空`);
  110 + return false;
  111 + }
  112 + if (
  113 + row.memo === null ||
  114 + row.memo === undefined ||
  115 + (typeof row.memo === 'string' && String(row.memo).trim() === '') ||
  116 + (Array.isArray(row.memo) && row.memo.length === 0)
  117 + ) {
  118 + ElMessage.error(`第 ${index + 1} 行:备注不能为空`);
  119 + return false;
  120 + }
  121 + }
  122 + return true;
  123 +}
  124 +
  125 +defineExpose({ getData, validate });
46 126  
47 127 /** 监听主表的关联字段的变化,加载对应的子表数据 */
48 128 watch(
49 129 () => props.studentId,
50 130 async (val) => {
  131 + list.value = [];
51 132 if (!val) {
52 133 return;
53 134 }
... ... @@ -58,70 +139,70 @@ watch(
58 139 </script>
59 140  
60 141 <template>
61   - <vxe-table ref="tableRef" :data="list" show-overflow class="mx-4">
62   - <vxe-column field="name" title="名字" align="center">
  142 + <VxeTable ref="tableRef" :data="list" show-overflow class="mx-4">
  143 + <VxeColumn field="name" title="名字" align="center">
63 144 <template #default="{row}">
64   - <el-input v-model="row.name" />
  145 + <ElInput v-model="row.name" />
65 146 </template>
66   - </vxe-column>
67   - <vxe-column field="description" title="简介" align="center">
  147 + </VxeColumn>
  148 + <VxeColumn field="description" title="简介" align="center">
68 149 <template #default="{row}">
69   - <el-input v-model="row.description" type="textarea" />
  150 + <ElInput v-model="row.description" type="textarea" />
70 151 </template>
71   - </vxe-column>
72   - <vxe-column field="birthday" title="出生日期" align="center">
  152 + </VxeColumn>
  153 + <VxeColumn field="birthday" title="出生日期" align="center">
73 154 <template #default="{row}">
74   - <el-date-picker
  155 + <ElDatePicker
75 156 v-model="row.birthday"
76 157 type="datetime"
77 158 value-format="x"
78 159 format="YYYY-MM-DD HH:mm:ss"
79 160 />
80 161 </template>
81   - </vxe-column>
82   - <vxe-column field="sex" title="性别" align="center">
  162 + </VxeColumn>
  163 + <VxeColumn field="sex" title="性别" align="center">
83 164 <template #default="{row}">
84   - <el-select v-model="row.sex" placeholder="请选择性别">
85   - <el-option
86   - v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number')"
  165 + <ElSelect v-model="row.sex" placeholder="请选择性别">
  166 + <ElOption
  167 + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
87 168 :key="dict.value"
88 169 :value="dict.value"
89 170 :label="dict.label"
90 171 />
91   - </el-select>
  172 + </ElSelect>
92 173 </template>
93   - </vxe-column>
94   - <vxe-column field="enabled" title="是否有效" align="center">
  174 + </VxeColumn>
  175 + <VxeColumn field="enabled" title="是否有效" align="center">
95 176 <template #default="{row}">
96   - <el-radio-group v-model="row.enabled">
97   - <el-radio
98   - v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'boolean')"
  177 + <ElRadioGroup v-model="row.enabled">
  178 + <ElRadio
  179 + v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
99 180 :key="dict.value"
100   - :label="dict.value"
  181 + :value="dict.value"
101 182 >
102 183 {{ dict.label }}
103   - </el-radio>
104   - </el-radio-group>
  184 + </ElRadio>
  185 + </ElRadioGroup>
105 186 </template>
106   - </vxe-column>
107   - <vxe-column field="avatar" title="头像" align="center">
  187 + </VxeColumn>
  188 + <VxeColumn field="avatar" title="头像" align="center">
108 189 <template #default="{row}">
109 190 <ImageUpload v-model="row.avatar" />
110 191 </template>
111   - </vxe-column>
112   - <vxe-column field="video" title="附件" align="center">
  192 + </VxeColumn>
  193 + <VxeColumn field="video" title="附件" align="center">
113 194 <template #default="{row}">
114 195 <FileUpload v-model="row.video" />
115 196 </template>
116   - </vxe-column>
117   - <vxe-column field="memo" title="备注" align="center">
  197 + </VxeColumn>
  198 + <VxeColumn field="memo" title="备注" align="center">
118 199 <template #default="{row}">
119   - <el-input v-model="row.memo" type="textarea" />
  200 + <ElInput v-model="row.memo" type="textarea" />
120 201 </template>
121   - </vxe-column>
122   - <vxe-column field="operation" title="操作" align="center">
  202 + </VxeColumn>
  203 + <VxeColumn field="operation" title="操作" align="center">
123 204 <template #default="{row}">
124   - <el-button
  205 + <ElButton
125 206 size="small"
126 207 type="danger"
127 208 link
... ... @@ -129,13 +210,13 @@ watch(
129 210 v-access:code="['infra:student:delete']"
130 211 >
131 212 {{ $t('ui.actionTitle.delete') }}
132   - </el-button>
  213 + </ElButton>
133 214 </template>
134   - </vxe-column>
135   - </vxe-table>
  215 + </VxeColumn>
  216 + </VxeTable>
136 217 <div class="flex justify-center mt-4">
137   - <el-button :icon="h(Plus)" type="primary" plain @click="handleAdd" v-access:code="['infra:student:create']">
  218 + <ElButton :icon="Plus" type="primary" plain @click="handleAdd" v-access:code="['infra:student:create']">
138 219 {{ $t('ui.actionTitle.create', ['学生联系人']) }}
139   - </el-button>
  220 + </ElButton>
140 221 </div>
141 222 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_inner/vue/student-contact-list
1 1 <script lang="ts" setup>
2   - import type { StudentApi } from '#/api/infra/student';
  2 + import type { StudentApi } from '#/api/infra/demo';
3 3 import type { VxeTableInstance } from '#/adapter/vxe-table';
4 4  
5 5 import { reactive,ref, h, nextTick,watch,onMounted } from 'vue';
6 6  
7 7 import { DICT_TYPE } from '@vben/constants';
8 8 import { getDictOptions } from '@vben/hooks';
  9 +import { formatDateTime } from '@vben/utils';
9 10  
10 11 import { DictTag } from '#/components/dict-tag';
11 12 import { getRangePickerDefaultProps } from '#/utils';
... ... @@ -13,7 +14,7 @@
13 14 import { ContentWrap } from '#/components/content-wrap';
14 15  
15 16  
16   - import { getStudentContactListByStudentId } from '#/api/infra/student';
  17 + import { getStudentContactListByStudentId } from '#/api/infra/demo';
17 18  
18 19 const props = defineProps<{
19 20 studentId?: number // 学生编号(主表的关联字段)
... ... @@ -52,38 +53,38 @@ watch(
52 53  
53 54 <template>
54 55 <ContentWrap title="学生联系人列表">
55   - <vxe-table
  56 + <VxeTable
56 57 :data="list"
57 58 show-overflow
58 59 :loading="loading"
59 60 >
60   - <vxe-column field="id" title="编号" align="center" />
61   - <vxe-column field="studentId" title="学生编号" align="center" />
62   - <vxe-column field="name" title="名字" align="center" />
63   - <vxe-column field="description" title="简介" align="center" />
64   - <vxe-column field="birthday" title="出生日期" align="center">
  61 + <VxeColumn field="id" title="编号" align="center" />
  62 + <VxeColumn field="studentId" title="学生编号" align="center" />
  63 + <VxeColumn field="name" title="名字" align="center" />
  64 + <VxeColumn field="description" title="简介" align="center" />
  65 + <VxeColumn field="birthday" title="出生日期" align="center">
65 66 <template #default="{row}">
66 67 {{formatDateTime(row.birthday)}}
67 68 </template>
68   - </vxe-column>
69   - <vxe-column field="sex" title="性别" align="center">
  69 + </VxeColumn>
  70 + <VxeColumn field="sex" title="性别" align="center">
70 71 <template #default="{row}">
71 72 <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
72 73 </template>
73   - </vxe-column>
74   - <vxe-column field="enabled" title="是否有效" align="center">
  74 + </VxeColumn>
  75 + <VxeColumn field="enabled" title="是否有效" align="center">
75 76 <template #default="{row}">
76 77 <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.enabled" />
77 78 </template>
78   - </vxe-column>
79   - <vxe-column field="avatar" title="头像" align="center" />
80   - <vxe-column field="video" title="附件" align="center" />
81   - <vxe-column field="memo" title="备注" align="center" />
82   - <vxe-column field="createTime" title="创建时间" align="center">
  79 + </VxeColumn>
  80 + <VxeColumn field="avatar" title="头像" align="center" />
  81 + <VxeColumn field="video" title="附件" align="center" />
  82 + <VxeColumn field="memo" title="备注" align="center" />
  83 + <VxeColumn field="createTime" title="创建时间" align="center">
83 84 <template #default="{row}">
84 85 {{formatDateTime(row.createTime)}}
85 86 </template>
86   - </vxe-column>
87   - </vxe-table>
  87 + </VxeColumn>
  88 + </VxeTable>
88 89 </ContentWrap>
89 90 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_inner/vue/student-teacher-form
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { FormRules } from 'element-plus';
  3 +import type { StudentApi } from '#/api/infra/demo';
3 4  
4   -import { computed, ref, reactive, h, onMounted,watch,nextTick } from 'vue';
  5 +import { nextTick, reactive, ref, watch } from 'vue';
5 6  
6 7 import { DICT_TYPE } from '@vben/constants';
7 8 import { getDictOptions } from '@vben/hooks';
8 9  
9   -import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElButton, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker } from 'element-plus';
10   -import { $t } from '#/locales';
  10 +import {
  11 + ElForm,
  12 + ElFormItem,
  13 + ElDatePicker,
  14 + ElInput,
  15 + ElOption,
  16 + ElRadio,
  17 + ElRadioGroup,
  18 + ElSelect,
  19 +} from 'element-plus';
11 20  
12   -import type { FormRules } from 'element-plus';
13 21 import { Tinymce as RichTextarea } from '#/components/tinymce';
14   -import { getStudentTeacherByStudentId } from '#/api/infra/student';
  22 +import { getStudentTeacherByStudentId } from '#/api/infra/demo';
  23 +import { $t } from '#/locales';
15 24  
16 25 const props = defineProps<{
17 26 studentId?: number // 学生编号(主表的关联字段)
... ... @@ -50,6 +59,18 @@ defineExpose({
50 59 watch(
51 60 () => props.studentId,
52 61 async (val) => {
  62 + formData.value = {
  63 + id: undefined,
  64 + studentId: undefined,
  65 + name: undefined,
  66 + description: undefined,
  67 + birthday: undefined,
  68 + sex: undefined,
  69 + enabled: undefined,
  70 + avatar: undefined,
  71 + video: undefined,
  72 + memo: undefined,
  73 + };
53 74 if (!val) {
54 75 return;
55 76 }
... ... @@ -61,7 +82,7 @@ watch(
61 82 </script>
62 83  
63 84 <template>
64   - <el-form
  85 + <ElForm
65 86 ref="formRef"
66 87 class="mx-4"
67 88 :model="formData"
... ... @@ -69,48 +90,48 @@ watch(
69 90 label-width="120px"
70 91 label-position="right"
71 92 >
72   - <el-form-item label="名字" prop="name">
73   - <el-input v-model="formData.name" placeholder="请输入名字" />
74   - </el-form-item>
75   - <el-form-item label="简介" prop="description">
76   - <el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
77   - </el-form-item>
78   - <el-form-item label="出生日期" prop="birthday">
79   - <el-date-picker
  93 + <ElFormItem label="名字" prop="name">
  94 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  95 + </ElFormItem>
  96 + <ElFormItem label="简介" prop="description">
  97 + <ElInput v-model="formData.description" type="textarea" placeholder="请输入简介" />
  98 + </ElFormItem>
  99 + <ElFormItem label="出生日期" prop="birthday">
  100 + <ElDatePicker
80 101 v-model="formData.birthday"
81 102 value-format="x"
82 103 placeholder="选择出生日期"
83 104 />
84   - </el-form-item>
85   - <el-form-item label="性别" prop="sex">
86   - <el-select v-model="formData.sex" placeholder="请选择性别">
87   - <el-option
  105 + </ElFormItem>
  106 + <ElFormItem label="性别" prop="sex">
  107 + <ElSelect v-model="formData.sex" placeholder="请选择性别">
  108 + <ElOption
88 109 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
89 110 :key="dict.value"
90 111 :value="dict.value"
91 112 :label="dict.label"
92 113 />
93   - </el-select>
94   - </el-form-item>
95   - <el-form-item label="是否有效" prop="enabled">
96   - <el-radio-group v-model="formData.enabled">
97   - <el-radio
  114 + </ElSelect>
  115 + </ElFormItem>
  116 + <ElFormItem label="是否有效" prop="enabled">
  117 + <ElRadioGroup v-model="formData.enabled">
  118 + <ElRadio
98 119 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
99 120 :key="dict.value"
100   - :label="dict.value"
  121 + :value="dict.value"
101 122 >
102 123 {{ dict.label }}
103   - </el-radio>
104   - </el-radio-group>
105   - </el-form-item>
106   - <el-form-item label="头像" prop="avatar">
  124 + </ElRadio>
  125 + </ElRadioGroup>
  126 + </ElFormItem>
  127 + <ElFormItem label="头像" prop="avatar">
107 128 <ImageUpload v-model="formData.avatar" />
108   - </el-form-item>
109   - <el-form-item label="附件" prop="video">
  129 + </ElFormItem>
  130 + <ElFormItem label="附件" prop="video">
110 131 <FileUpload v-model="formData.video" />
111   - </el-form-item>
112   - <el-form-item label="备注" prop="memo">
  132 + </ElFormItem>
  133 + <ElFormItem label="备注" prop="memo">
113 134 <RichTextarea v-model="formData.memo" height="500px" />
114   - </el-form-item>
115   - </el-form>
  135 + </ElFormItem>
  136 + </ElForm>
116 137 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_inner/vue/student-teacher-list
1 1 <script lang="ts" setup>
2   - import type { StudentApi } from '#/api/infra/student';
  2 + import type { StudentApi } from '#/api/infra/demo';
3 3 import type { VxeTableInstance } from '#/adapter/vxe-table';
4 4  
5 5 import { reactive,ref, h, nextTick,watch,onMounted } from 'vue';
6 6  
7 7 import { DICT_TYPE } from '@vben/constants';
8 8 import { getDictOptions } from '@vben/hooks';
  9 +import { formatDateTime } from '@vben/utils';
9 10  
10 11 import { DictTag } from '#/components/dict-tag';
11 12 import { getRangePickerDefaultProps } from '#/utils';
... ... @@ -13,7 +14,7 @@
13 14 import { ContentWrap } from '#/components/content-wrap';
14 15  
15 16  
16   - import { getStudentTeacherByStudentId } from '#/api/infra/student';
  17 + import { getStudentTeacherByStudentId } from '#/api/infra/demo';
17 18  
18 19 const props = defineProps<{
19 20 studentId?: number // 学生编号(主表的关联字段)
... ... @@ -52,38 +53,38 @@ watch(
52 53  
53 54 <template>
54 55 <ContentWrap title="学生班主任列表">
55   - <vxe-table
  56 + <VxeTable
56 57 :data="list"
57 58 show-overflow
58 59 :loading="loading"
59 60 >
60   - <vxe-column field="id" title="编号" align="center" />
61   - <vxe-column field="studentId" title="学生编号" align="center" />
62   - <vxe-column field="name" title="名字" align="center" />
63   - <vxe-column field="description" title="简介" align="center" />
64   - <vxe-column field="birthday" title="出生日期" align="center">
  61 + <VxeColumn field="id" title="编号" align="center" />
  62 + <VxeColumn field="studentId" title="学生编号" align="center" />
  63 + <VxeColumn field="name" title="名字" align="center" />
  64 + <VxeColumn field="description" title="简介" align="center" />
  65 + <VxeColumn field="birthday" title="出生日期" align="center">
65 66 <template #default="{row}">
66 67 {{formatDateTime(row.birthday)}}
67 68 </template>
68   - </vxe-column>
69   - <vxe-column field="sex" title="性别" align="center">
  69 + </VxeColumn>
  70 + <VxeColumn field="sex" title="性别" align="center">
70 71 <template #default="{row}">
71 72 <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
72 73 </template>
73   - </vxe-column>
74   - <vxe-column field="enabled" title="是否有效" align="center">
  74 + </VxeColumn>
  75 + <VxeColumn field="enabled" title="是否有效" align="center">
75 76 <template #default="{row}">
76 77 <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.enabled" />
77 78 </template>
78   - </vxe-column>
79   - <vxe-column field="avatar" title="头像" align="center" />
80   - <vxe-column field="video" title="附件" align="center" />
81   - <vxe-column field="memo" title="备注" align="center" />
82   - <vxe-column field="createTime" title="创建时间" align="center">
  79 + </VxeColumn>
  80 + <VxeColumn field="avatar" title="头像" align="center" />
  81 + <VxeColumn field="video" title="附件" align="center" />
  82 + <VxeColumn field="memo" title="备注" align="center" />
  83 + <VxeColumn field="createTime" title="创建时间" align="center">
83 84 <template #default="{row}">
84 85 {{formatDateTime(row.createTime)}}
85 86 </template>
86   - </vxe-column>
87   - </vxe-table>
  87 + </VxeColumn>
  88 + </VxeTable>
88 89 </ContentWrap>
89 90 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_normal/vue/form
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3 import type { FormRules } from 'element-plus';
4 4  
5 5 import { useVbenModal } from '@vben/common-ui';
... ... @@ -13,7 +13,7 @@ import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElSelect, El
13 13  
14 14 import { computed, ref, reactive } from 'vue';
15 15 import { $t } from '#/locales';
16   -import { getStudent, createStudent, updateStudent } from '#/api/infra/student';
  16 +import { getStudent, createStudent, updateStudent } from '#/api/infra/demo';
17 17  
18 18 const emit = defineEmits(['success']);
19 19  
... ... @@ -71,7 +71,11 @@ const [Modal, modalApi] = useVbenModal({
71 71 async onConfirm() {
72 72 await formRef.value?.validate();
73 73 // 校验子表单
74   - try {
  74 + if (!studentContactFormRef.value?.validate()) {
  75 + subTabsName.value = 'studentContact';
  76 + return;
  77 + }
  78 + try {
75 79 await studentTeacherFormRef.value?.validate()
76 80 } catch (e) {
77 81 subTabsName.value = 'studentTeacher'
... ... @@ -118,65 +122,65 @@ const [Modal, modalApi] = useVbenModal({
118 122  
119 123 <template>
120 124 <Modal :title="getTitle">
121   - <el-form
  125 + <ElForm
122 126 ref="formRef"
123 127 :model="formData"
124 128 :rules="rules"
125 129 label-width="120px"
126 130 label-position="right"
127 131 >
128   - <el-form-item label="名字" prop="name">
129   - <el-input v-model="formData.name" placeholder="请输入名字" />
130   - </el-form-item>
131   - <el-form-item label="简介" prop="description">
132   - <el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
133   - </el-form-item>
134   - <el-form-item label="出生日期" prop="birthday">
135   - <el-date-picker
  132 + <ElFormItem label="名字" prop="name">
  133 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  134 + </ElFormItem>
  135 + <ElFormItem label="简介" prop="description">
  136 + <ElInput v-model="formData.description" type="textarea" placeholder="请输入简介" />
  137 + </ElFormItem>
  138 + <ElFormItem label="出生日期" prop="birthday">
  139 + <ElDatePicker
136 140 v-model="formData.birthday"
137 141 value-format="x"
138 142 placeholder="选择出生日期"
139 143 />
140   - </el-form-item>
141   - <el-form-item label="性别" prop="sex">
142   - <el-select v-model="formData.sex" placeholder="请选择性别">
143   - <el-option
  144 + </ElFormItem>
  145 + <ElFormItem label="性别" prop="sex">
  146 + <ElSelect v-model="formData.sex" placeholder="请选择性别">
  147 + <ElOption
144 148 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
145 149 :key="dict.value"
146 150 :value="dict.value"
147 151 :label="dict.label"
148 152 />
149   - </el-select>
150   - </el-form-item>
151   - <el-form-item label="是否有效" prop="enabled">
152   - <el-radio-group v-model="formData.enabled">
153   - <el-radio
  153 + </ElSelect>
  154 + </ElFormItem>
  155 + <ElFormItem label="是否有效" prop="enabled">
  156 + <ElRadioGroup v-model="formData.enabled">
  157 + <ElRadio
154 158 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
155 159 :key="dict.value"
156   - :label="dict.value"
  160 + :value="dict.value"
157 161 >
158 162 {{ dict.label }}
159   - </el-radio>
160   - </el-radio-group>
161   - </el-form-item>
162   - <el-form-item label="头像" prop="avatar">
  163 + </ElRadio>
  164 + </ElRadioGroup>
  165 + </ElFormItem>
  166 + <ElFormItem label="头像" prop="avatar">
163 167 <ImageUpload v-model="formData.avatar" />
164   - </el-form-item>
165   - <el-form-item label="附件" prop="video">
  168 + </ElFormItem>
  169 + <ElFormItem label="附件" prop="video">
166 170 <FileUpload v-model="formData.video" />
167   - </el-form-item>
168   - <el-form-item label="备注" prop="memo">
  171 + </ElFormItem>
  172 + <ElFormItem label="备注" prop="memo">
169 173 <RichTextarea v-model="formData.memo" height="500px" />
170   - </el-form-item>
171   - </el-form>
  174 + </ElFormItem>
  175 + </ElForm>
172 176 <!-- 子表的表单 -->
173   - <el-tabs v-model="subTabsName">
174   - <el-tab-pane name="studentContact" label="学生联系人">
  177 + <ElTabs v-model="subTabsName">
  178 + <ElTabPane name="studentContact" label="学生联系人">
175 179 <StudentContactForm ref="studentContactFormRef" :student-id="formData?.id" />
176   - </el-tab-pane>
177   - <el-tab-pane name="studentTeacher" label="学生班主任">
  180 + </ElTabPane>
  181 + <ElTabPane name="studentTeacher" label="学生班主任">
178 182 <StudentTeacherForm ref="studentTeacherFormRef" :student-id="formData?.id" />
179   - </el-tab-pane>
180   - </el-tabs>
  183 + </ElTabPane>
  184 + </ElTabs>
181 185 </Modal>
182 186 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_normal/vue/index
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3  
4 4 import { ref, h, reactive, onMounted, nextTick } from 'vue';
5 5  
... ... @@ -17,7 +17,7 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;;
17 17  
18 18 import { $t } from '#/locales';
19 19 import { isEmpty } from '@vben/utils';
20   -import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/student';
  20 +import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/demo';
21 21  
22 22  
23 23 const loading = ref(true) // 列表的加载中
... ... @@ -27,12 +27,11 @@ const total = ref(0) // 列表的总页数
27 27 const queryParams = reactive({
28 28 pageNo: 1,
29 29 pageSize: 10,
30   - name: undefined,
31   - birthday: undefined,
32   - birthday: undefined,
33   - sex: undefined,
34   - enabled: undefined,
35   - createTime: undefined,
  30 + name: undefined,
  31 + birthday: undefined,
  32 + sex: undefined,
  33 + enabled: undefined,
  34 + createTime: undefined,
36 35 })
37 36 const queryFormRef = ref() // 搜索的表单
38 37 const exportLoading = ref(false) // 导出的加载中
... ... @@ -126,7 +125,14 @@ function handleRowCheckboxChange({
126 125 async function handleExport() {
127 126 try {
128 127 exportLoading.value = true;
129   - const data = await exportStudent(queryParams);
  128 + const params = cloneDeep(queryParams) as any;
  129 + if (params.birthday && Array.isArray(params.birthday)) {
  130 + params.birthday = (params.birthday as string[]).join(',');
  131 + }
  132 + if (params.createTime && Array.isArray(params.createTime)) {
  133 + params.createTime = (params.createTime as string[]).join(',');
  134 + }
  135 + const data = await exportStudent(params);
130 136 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
131 137 }finally {
132 138 exportLoading.value = false;
... ... @@ -148,61 +154,61 @@ onMounted(() =&gt; {
148 154  
149 155 <ContentWrap v-if="!hiddenSearchBar">
150 156 <!-- 搜索工作栏 -->
151   - <el-form
  157 + <ElForm
152 158 :model="queryParams"
153 159 ref="queryFormRef"
154 160 inline
155 161 >
156   - <el-form-item label="名字">
157   - <el-input
  162 + <ElFormItem label="名字">
  163 + <ElInput
158 164 v-model="queryParams.name"
159 165 placeholder="请输入名字"
160 166 clearable
161 167 @keyup.enter="handleQuery"
162 168 class="!w-[240px]"
163 169 />
164   - </el-form-item>
165   - <el-form-item label="出生日期">
166   - <el-date-picker
  170 + </ElFormItem>
  171 + <ElFormItem label="出生日期">
  172 + <ElDatePicker
167 173 v-model="queryParams.birthday"
168 174 value-format="YYYY-MM-DD"
169 175 placeholder="选择出生日期"
170 176 clearable
171 177 class="!w-[240px]"
172 178 />
173   - </el-form-item>
174   - <el-form-item label="性别">
175   - <el-select
  179 + </ElFormItem>
  180 + <ElFormItem label="性别">
  181 + <ElSelect
176 182 v-model="queryParams.sex"
177 183 placeholder="请选择性别"
178 184 clearable
179 185 class="!w-[240px]"
180 186 >
181   - <el-option
  187 + <ElOption
182 188 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
183 189 :key="dict.value"
184 190 :value="dict.value"
185 191 :label="dict.label"
186 192 />
187   - </el-select>
188   - </el-form-item>
189   - <el-form-item label="是否有效">
190   - <el-select
  193 + </ElSelect>
  194 + </ElFormItem>
  195 + <ElFormItem label="是否有效">
  196 + <ElSelect
191 197 v-model="queryParams.enabled"
192 198 placeholder="请选择是否有效"
193 199 clearable
194 200 class="!w-[240px]"
195 201 >
196   - <el-option
  202 + <ElOption
197 203 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
198 204 :key="dict.value"
199 205 :value="dict.value"
200 206 :label="dict.label"
201 207 />
202   - </el-select>
203   - </el-form-item>
204   - <el-form-item label="创建时间">
205   - <el-date-picker
  208 + </ElSelect>
  209 + </ElFormItem>
  210 + <ElFormItem label="创建时间">
  211 + <ElDatePicker
206 212 v-model="queryParams.createTime"
207 213 type="daterange"
208 214 value-format="YYYY-MM-DD"
... ... @@ -211,24 +217,24 @@ onMounted(() =&gt; {
211 217 end-placeholder="结束日期"
212 218 class="!w-[240px]"
213 219 />
214   - </el-form-item>
215   - <el-form-item>
216   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
217   - <el-button class="ml-2" @click="handleQuery" type="primary">
  220 + </ElFormItem>
  221 + <ElFormItem>
  222 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  223 + <ElButton class="ml-2" @click="handleQuery" type="primary">
218 224 搜索
219   - </el-button>
220   - </el-form-item>
221   - </el-form>
  225 + </ElButton>
  226 + </ElFormItem>
  227 + </ElForm>
222 228 </ContentWrap>
223 229  
224 230 <!-- 列表 -->
225 231 <ContentWrap title="学生">
226 232 <template #extra>
227   - <TableToolbar
  233 + <VbenVxeTableToolbar
228 234 ref="tableToolbarRef"
229 235 v-model:hidden-search="hiddenSearchBar"
230 236 >
231   - <el-button
  237 + <ElButton
232 238 class="ml-2"
233 239 :icon="h(Plus)"
234 240 type="primary"
... ... @@ -236,8 +242,8 @@ onMounted(() =&gt; {
236 242 v-access:code="['infra:student:create']"
237 243 >
238 244 {{ $t('ui.actionTitle.create', ['学生']) }}
239   - </el-button>
240   - <el-button
  245 + </ElButton>
  246 + <ElButton
241 247 :icon="h(Download)"
242 248 type="primary"
243 249 class="ml-2"
... ... @@ -246,8 +252,8 @@ onMounted(() =&gt; {
246 252 v-access:code="['infra:student:export']"
247 253 >
248 254 {{ $t('ui.actionTitle.export') }}
249   - </el-button>
250   - <el-button
  255 + </ElButton>
  256 + <ElButton
251 257 :icon="h(Trash2)"
252 258 type="danger"
253 259 class="ml-2"
... ... @@ -256,10 +262,10 @@ onMounted(() =&gt; {
256 262 v-access:code="['infra:student:delete']"
257 263 >
258 264 批量删除
259   - </el-button>
260   - </TableToolbar>
  265 + </ElButton>
  266 + </VbenVxeTableToolbar>
261 267 </template>
262   - <vxe-table
  268 + <VxeTable
263 269 ref="tableRef"
264 270 :data="list"
265 271 show-overflow
... ... @@ -267,36 +273,36 @@ onMounted(() =&gt; {
267 273 @checkboxAll="handleRowCheckboxChange"
268 274 @checkboxChange="handleRowCheckboxChange"
269 275 >
270   - <vxe-column type="checkbox" width="40"></vxe-column>
271   - <vxe-column field="id" title="编号" align="center" />
272   - <vxe-column field="name" title="名字" align="center" />
273   - <vxe-column field="description" title="简介" align="center" />
274   - <vxe-column field="birthday" title="出生日期" align="center">
  276 + <VxeColumn type="checkbox" width="40"></VxeColumn>
  277 + <VxeColumn field="id" title="编号" align="center" />
  278 + <VxeColumn field="name" title="名字" align="center" />
  279 + <VxeColumn field="description" title="简介" align="center" />
  280 + <VxeColumn field="birthday" title="出生日期" align="center">
275 281 <template #default="{row}">
276 282 {{formatDateTime(row.birthday)}}
277 283 </template>
278   - </vxe-column>
279   - <vxe-column field="sex" title="性别" align="center">
  284 + </VxeColumn>
  285 + <VxeColumn field="sex" title="性别" align="center">
280 286 <template #default="{row}">
281 287 <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
282 288 </template>
283   - </vxe-column>
284   - <vxe-column field="enabled" title="是否有效" align="center">
  289 + </VxeColumn>
  290 + <VxeColumn field="enabled" title="是否有效" align="center">
285 291 <template #default="{row}">
286 292 <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.enabled" />
287 293 </template>
288   - </vxe-column>
289   - <vxe-column field="avatar" title="头像" align="center" />
290   - <vxe-column field="video" title="附件" align="center" />
291   - <vxe-column field="memo" title="备注" align="center" />
292   - <vxe-column field="createTime" title="创建时间" align="center">
  294 + </VxeColumn>
  295 + <VxeColumn field="avatar" title="头像" align="center" />
  296 + <VxeColumn field="video" title="附件" align="center" />
  297 + <VxeColumn field="memo" title="备注" align="center" />
  298 + <VxeColumn field="createTime" title="创建时间" align="center">
293 299 <template #default="{row}">
294 300 {{formatDateTime(row.createTime)}}
295 301 </template>
296   - </vxe-column>
297   - <vxe-column field="operation" title="操作" align="center">
  302 + </VxeColumn>
  303 + <VxeColumn field="operation" title="操作" align="center">
298 304 <template #default="{row}">
299   - <el-button
  305 + <ElButton
300 306 size="small"
301 307 type="primary"
302 308 link
... ... @@ -304,8 +310,8 @@ onMounted(() =&gt; {
304 310 v-access:code="['infra:student:update']"
305 311 >
306 312 {{ $t('ui.actionTitle.edit') }}
307   - </el-button>
308   - <el-button
  313 + </ElButton>
  314 + <ElButton
309 315 size="small"
310 316 type="danger"
311 317 link
... ... @@ -314,13 +320,13 @@ onMounted(() =&gt; {
314 320 v-access:code="['infra:student:delete']"
315 321 >
316 322 {{ $t('ui.actionTitle.delete') }}
317   - </el-button>
  323 + </ElButton>
318 324 </template>
319   - </vxe-column>
320   - </vxe-table>
  325 + </VxeColumn>
  326 + </VxeTable>
321 327 <!-- 分页 -->
322 328 <div class="mt-2 flex justify-end">
323   - <el-pagination
  329 + <ElPagination
324 330 :total="total"
325 331 v-model:current-page="queryParams.pageNo"
326 332 v-model:page-size="queryParams.pageSize"
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_normal/vue/student-contact-form
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { VxeTableInstance } from '#/adapter/vxe-table';
  3 +import type { StudentApi } from '#/api/infra/demo';
3 4  
4   -import { computed, ref, reactive, h, onMounted,watch,nextTick } from 'vue';
  5 +import { ref, watch } from 'vue';
5 6  
6 7 import { DICT_TYPE } from '@vben/constants';
7 8 import { getDictOptions } from '@vben/hooks';
  9 +import { Plus } from '@vben/icons';
8 10  
9   -import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElButton, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker } from 'element-plus';
10   -import { $t } from '#/locales';
  11 +import {
  12 + ElButton,
  13 + ElDatePicker,
  14 + ElInput,
  15 + ElMessage,
  16 + ElOption,
  17 + ElRadio,
  18 + ElRadioGroup,
  19 + ElSelect,
  20 +} from 'element-plus';
11 21  
12   - import type { VxeTableInstance } from '#/adapter/vxe-table';
13   -import { Plus } from "@vben/icons";
14   -import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
15   -import { getStudentContactListByStudentId } from '#/api/infra/student';
  22 + import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
  23 +import { getStudentContactListByStudentId } from '#/api/infra/demo';
  24 +import { $t } from '#/locales';
16 25  
17 26 const props = defineProps<{
18 27 studentId?: number // 学生编号(主表的关联字段)
... ... @@ -30,24 +39,96 @@ async function onDelete(row: StudentApi.StudentContact) {
30 39 await tableRef.value?.remove(row);
31 40 }
32 41  
33   -/** 提供获取表格数据的方法供父组件调用 */
34   -defineExpose({
35   - getData: (): StudentApi.StudentContact[] => {
36   - const data = list.value as StudentApi.StudentContact[];
37   - const removeRecords = tableRef.value?.getRemoveRecords() as StudentApi.StudentContact[];
38   - const insertRecords = tableRef.value?.getInsertRecords() as StudentApi.StudentContact[];
39   - return [
40   - ...data.filter(
41   - (row) => !removeRecords.some((removed) => removed.id === row.id),
42   - ),
43   - ...insertRecords.map((row: any) => ({ ...row, id: undefined })),
44   - },
45   -});
  42 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  43 +function getData(): StudentApi.StudentContact[] {
  44 + const table = tableRef.value;
  45 + if (!table) {
  46 + return [];
  47 + }
  48 + const { fullData } = table.getTableData();
  49 + const inserted = new Set(table.getInsertRecords());
  50 + return fullData.map((row) =>
  51 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  52 + );
  53 +}
  54 +
  55 +/** 校验子表必填字段 */
  56 +function validate(): boolean {
  57 + for (const [index, row] of getData().entries()) {
  58 + if (
  59 + row.name === null ||
  60 + row.name === undefined ||
  61 + (typeof row.name === 'string' && String(row.name).trim() === '') ||
  62 + (Array.isArray(row.name) && row.name.length === 0)
  63 + ) {
  64 + ElMessage.error(`第 ${index + 1} 行:名字不能为空`);
  65 + return false;
  66 + }
  67 + if (
  68 + row.description === null ||
  69 + row.description === undefined ||
  70 + (typeof row.description === 'string' && String(row.description).trim() === '') ||
  71 + (Array.isArray(row.description) && row.description.length === 0)
  72 + ) {
  73 + ElMessage.error(`第 ${index + 1} 行:简介不能为空`);
  74 + return false;
  75 + }
  76 + if (
  77 + row.birthday === null ||
  78 + row.birthday === undefined ||
  79 + (typeof row.birthday === 'string' && String(row.birthday).trim() === '') ||
  80 + (Array.isArray(row.birthday) && row.birthday.length === 0)
  81 + ) {
  82 + ElMessage.error(`第 ${index + 1} 行:出生日期不能为空`);
  83 + return false;
  84 + }
  85 + if (
  86 + row.sex === null ||
  87 + row.sex === undefined ||
  88 + (typeof row.sex === 'string' && String(row.sex).trim() === '') ||
  89 + (Array.isArray(row.sex) && row.sex.length === 0)
  90 + ) {
  91 + ElMessage.error(`第 ${index + 1} 行:性别不能为空`);
  92 + return false;
  93 + }
  94 + if (
  95 + row.enabled === null ||
  96 + row.enabled === undefined ||
  97 + (typeof row.enabled === 'string' && String(row.enabled).trim() === '') ||
  98 + (Array.isArray(row.enabled) && row.enabled.length === 0)
  99 + ) {
  100 + ElMessage.error(`第 ${index + 1} 行:是否有效不能为空`);
  101 + return false;
  102 + }
  103 + if (
  104 + row.avatar === null ||
  105 + row.avatar === undefined ||
  106 + (typeof row.avatar === 'string' && String(row.avatar).trim() === '') ||
  107 + (Array.isArray(row.avatar) && row.avatar.length === 0)
  108 + ) {
  109 + ElMessage.error(`第 ${index + 1} 行:头像不能为空`);
  110 + return false;
  111 + }
  112 + if (
  113 + row.memo === null ||
  114 + row.memo === undefined ||
  115 + (typeof row.memo === 'string' && String(row.memo).trim() === '') ||
  116 + (Array.isArray(row.memo) && row.memo.length === 0)
  117 + ) {
  118 + ElMessage.error(`第 ${index + 1} 行:备注不能为空`);
  119 + return false;
  120 + }
  121 + }
  122 + return true;
  123 +}
  124 +
  125 +defineExpose({ getData, validate });
46 126  
47 127 /** 监听主表的关联字段的变化,加载对应的子表数据 */
48 128 watch(
49 129 () => props.studentId,
50 130 async (val) => {
  131 + list.value = [];
51 132 if (!val) {
52 133 return;
53 134 }
... ... @@ -58,70 +139,70 @@ watch(
58 139 </script>
59 140  
60 141 <template>
61   - <vxe-table ref="tableRef" :data="list" show-overflow class="mx-4">
62   - <vxe-column field="name" title="名字" align="center">
  142 + <VxeTable ref="tableRef" :data="list" show-overflow class="mx-4">
  143 + <VxeColumn field="name" title="名字" align="center">
63 144 <template #default="{row}">
64   - <el-input v-model="row.name" />
  145 + <ElInput v-model="row.name" />
65 146 </template>
66   - </vxe-column>
67   - <vxe-column field="description" title="简介" align="center">
  147 + </VxeColumn>
  148 + <VxeColumn field="description" title="简介" align="center">
68 149 <template #default="{row}">
69   - <el-input v-model="row.description" type="textarea" />
  150 + <ElInput v-model="row.description" type="textarea" />
70 151 </template>
71   - </vxe-column>
72   - <vxe-column field="birthday" title="出生日期" align="center">
  152 + </VxeColumn>
  153 + <VxeColumn field="birthday" title="出生日期" align="center">
73 154 <template #default="{row}">
74   - <el-date-picker
  155 + <ElDatePicker
75 156 v-model="row.birthday"
76 157 type="datetime"
77 158 value-format="x"
78 159 format="YYYY-MM-DD HH:mm:ss"
79 160 />
80 161 </template>
81   - </vxe-column>
82   - <vxe-column field="sex" title="性别" align="center">
  162 + </VxeColumn>
  163 + <VxeColumn field="sex" title="性别" align="center">
83 164 <template #default="{row}">
84   - <el-select v-model="row.sex" placeholder="请选择性别">
85   - <el-option
86   - v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number')"
  165 + <ElSelect v-model="row.sex" placeholder="请选择性别">
  166 + <ElOption
  167 + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
87 168 :key="dict.value"
88 169 :value="dict.value"
89 170 :label="dict.label"
90 171 />
91   - </el-select>
  172 + </ElSelect>
92 173 </template>
93   - </vxe-column>
94   - <vxe-column field="enabled" title="是否有效" align="center">
  174 + </VxeColumn>
  175 + <VxeColumn field="enabled" title="是否有效" align="center">
95 176 <template #default="{row}">
96   - <el-radio-group v-model="row.enabled">
97   - <el-radio
98   - v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'boolean')"
  177 + <ElRadioGroup v-model="row.enabled">
  178 + <ElRadio
  179 + v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
99 180 :key="dict.value"
100   - :label="dict.value"
  181 + :value="dict.value"
101 182 >
102 183 {{ dict.label }}
103   - </el-radio>
104   - </el-radio-group>
  184 + </ElRadio>
  185 + </ElRadioGroup>
105 186 </template>
106   - </vxe-column>
107   - <vxe-column field="avatar" title="头像" align="center">
  187 + </VxeColumn>
  188 + <VxeColumn field="avatar" title="头像" align="center">
108 189 <template #default="{row}">
109 190 <ImageUpload v-model="row.avatar" />
110 191 </template>
111   - </vxe-column>
112   - <vxe-column field="video" title="附件" align="center">
  192 + </VxeColumn>
  193 + <VxeColumn field="video" title="附件" align="center">
113 194 <template #default="{row}">
114 195 <FileUpload v-model="row.video" />
115 196 </template>
116   - </vxe-column>
117   - <vxe-column field="memo" title="备注" align="center">
  197 + </VxeColumn>
  198 + <VxeColumn field="memo" title="备注" align="center">
118 199 <template #default="{row}">
119   - <el-input v-model="row.memo" type="textarea" />
  200 + <ElInput v-model="row.memo" type="textarea" />
120 201 </template>
121   - </vxe-column>
122   - <vxe-column field="operation" title="操作" align="center">
  202 + </VxeColumn>
  203 + <VxeColumn field="operation" title="操作" align="center">
123 204 <template #default="{row}">
124   - <el-button
  205 + <ElButton
125 206 size="small"
126 207 type="danger"
127 208 link
... ... @@ -129,13 +210,13 @@ watch(
129 210 v-access:code="['infra:student:delete']"
130 211 >
131 212 {{ $t('ui.actionTitle.delete') }}
132   - </el-button>
  213 + </ElButton>
133 214 </template>
134   - </vxe-column>
135   - </vxe-table>
  215 + </VxeColumn>
  216 + </VxeTable>
136 217 <div class="flex justify-center mt-4">
137   - <el-button :icon="h(Plus)" type="primary" plain @click="handleAdd" v-access:code="['infra:student:create']">
  218 + <ElButton :icon="Plus" type="primary" plain @click="handleAdd" v-access:code="['infra:student:create']">
138 219 {{ $t('ui.actionTitle.create', ['学生联系人']) }}
139   - </el-button>
  220 + </ElButton>
140 221 </div>
141 222 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_master_normal/vue/student-teacher-form
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { FormRules } from 'element-plus';
  3 +import type { StudentApi } from '#/api/infra/demo';
3 4  
4   -import { computed, ref, reactive, h, onMounted,watch,nextTick } from 'vue';
  5 +import { nextTick, reactive, ref, watch } from 'vue';
5 6  
6 7 import { DICT_TYPE } from '@vben/constants';
7 8 import { getDictOptions } from '@vben/hooks';
8 9  
9   -import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElButton, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker } from 'element-plus';
10   -import { $t } from '#/locales';
  10 +import {
  11 + ElForm,
  12 + ElFormItem,
  13 + ElDatePicker,
  14 + ElInput,
  15 + ElOption,
  16 + ElRadio,
  17 + ElRadioGroup,
  18 + ElSelect,
  19 +} from 'element-plus';
11 20  
12   -import type { FormRules } from 'element-plus';
13 21 import { Tinymce as RichTextarea } from '#/components/tinymce';
14   -import { getStudentTeacherByStudentId } from '#/api/infra/student';
  22 +import { getStudentTeacherByStudentId } from '#/api/infra/demo';
  23 +import { $t } from '#/locales';
15 24  
16 25 const props = defineProps<{
17 26 studentId?: number // 学生编号(主表的关联字段)
... ... @@ -50,6 +59,18 @@ defineExpose({
50 59 watch(
51 60 () => props.studentId,
52 61 async (val) => {
  62 + formData.value = {
  63 + id: undefined,
  64 + studentId: undefined,
  65 + name: undefined,
  66 + description: undefined,
  67 + birthday: undefined,
  68 + sex: undefined,
  69 + enabled: undefined,
  70 + avatar: undefined,
  71 + video: undefined,
  72 + memo: undefined,
  73 + };
53 74 if (!val) {
54 75 return;
55 76 }
... ... @@ -61,7 +82,7 @@ watch(
61 82 </script>
62 83  
63 84 <template>
64   - <el-form
  85 + <ElForm
65 86 ref="formRef"
66 87 class="mx-4"
67 88 :model="formData"
... ... @@ -69,48 +90,48 @@ watch(
69 90 label-width="120px"
70 91 label-position="right"
71 92 >
72   - <el-form-item label="名字" prop="name">
73   - <el-input v-model="formData.name" placeholder="请输入名字" />
74   - </el-form-item>
75   - <el-form-item label="简介" prop="description">
76   - <el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
77   - </el-form-item>
78   - <el-form-item label="出生日期" prop="birthday">
79   - <el-date-picker
  93 + <ElFormItem label="名字" prop="name">
  94 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  95 + </ElFormItem>
  96 + <ElFormItem label="简介" prop="description">
  97 + <ElInput v-model="formData.description" type="textarea" placeholder="请输入简介" />
  98 + </ElFormItem>
  99 + <ElFormItem label="出生日期" prop="birthday">
  100 + <ElDatePicker
80 101 v-model="formData.birthday"
81 102 value-format="x"
82 103 placeholder="选择出生日期"
83 104 />
84   - </el-form-item>
85   - <el-form-item label="性别" prop="sex">
86   - <el-select v-model="formData.sex" placeholder="请选择性别">
87   - <el-option
  105 + </ElFormItem>
  106 + <ElFormItem label="性别" prop="sex">
  107 + <ElSelect v-model="formData.sex" placeholder="请选择性别">
  108 + <ElOption
88 109 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
89 110 :key="dict.value"
90 111 :value="dict.value"
91 112 :label="dict.label"
92 113 />
93   - </el-select>
94   - </el-form-item>
95   - <el-form-item label="是否有效" prop="enabled">
96   - <el-radio-group v-model="formData.enabled">
97   - <el-radio
  114 + </ElSelect>
  115 + </ElFormItem>
  116 + <ElFormItem label="是否有效" prop="enabled">
  117 + <ElRadioGroup v-model="formData.enabled">
  118 + <ElRadio
98 119 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
99 120 :key="dict.value"
100   - :label="dict.value"
  121 + :value="dict.value"
101 122 >
102 123 {{ dict.label }}
103   - </el-radio>
104   - </el-radio-group>
105   - </el-form-item>
106   - <el-form-item label="头像" prop="avatar">
  124 + </ElRadio>
  125 + </ElRadioGroup>
  126 + </ElFormItem>
  127 + <ElFormItem label="头像" prop="avatar">
107 128 <ImageUpload v-model="formData.avatar" />
108   - </el-form-item>
109   - <el-form-item label="附件" prop="video">
  129 + </ElFormItem>
  130 + <ElFormItem label="附件" prop="video">
110 131 <FileUpload v-model="formData.video" />
111   - </el-form-item>
112   - <el-form-item label="备注" prop="memo">
  132 + </ElFormItem>
  133 + <ElFormItem label="备注" prop="memo">
113 134 <RichTextarea v-model="formData.memo" height="500px" />
114   - </el-form-item>
115   - </el-form>
  135 + </ElFormItem>
  136 + </ElForm>
116 137 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_one/vue/form
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3 import type { FormRules } from 'element-plus';
4 4  
5 5 import { useVbenModal } from '@vben/common-ui';
... ... @@ -11,7 +11,7 @@ import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElSelect, El
11 11  
12 12 import { computed, ref, reactive } from 'vue';
13 13 import { $t } from '#/locales';
14   -import { getStudent, createStudent, updateStudent } from '#/api/infra/student';
  14 +import { getStudent, createStudent, updateStudent } from '#/api/infra/demo';
15 15  
16 16 const emit = defineEmits(['success']);
17 17  
... ... @@ -102,56 +102,56 @@ const [Modal, modalApi] = useVbenModal({
102 102  
103 103 <template>
104 104 <Modal :title="getTitle">
105   - <el-form
  105 + <ElForm
106 106 ref="formRef"
107 107 :model="formData"
108 108 :rules="rules"
109 109 label-width="120px"
110 110 label-position="right"
111 111 >
112   - <el-form-item label="名字" prop="name">
113   - <el-input v-model="formData.name" placeholder="请输入名字" />
114   - </el-form-item>
115   - <el-form-item label="简介" prop="description">
116   - <el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
117   - </el-form-item>
118   - <el-form-item label="出生日期" prop="birthday">
119   - <el-date-picker
  112 + <ElFormItem label="名字" prop="name">
  113 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  114 + </ElFormItem>
  115 + <ElFormItem label="简介" prop="description">
  116 + <ElInput v-model="formData.description" type="textarea" placeholder="请输入简介" />
  117 + </ElFormItem>
  118 + <ElFormItem label="出生日期" prop="birthday">
  119 + <ElDatePicker
120 120 v-model="formData.birthday"
121 121 value-format="x"
122 122 placeholder="选择出生日期"
123 123 />
124   - </el-form-item>
125   - <el-form-item label="性别" prop="sex">
126   - <el-select v-model="formData.sex" placeholder="请选择性别">
127   - <el-option
  124 + </ElFormItem>
  125 + <ElFormItem label="性别" prop="sex">
  126 + <ElSelect v-model="formData.sex" placeholder="请选择性别">
  127 + <ElOption
128 128 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
129 129 :key="dict.value"
130 130 :value="dict.value"
131 131 :label="dict.label"
132 132 />
133   - </el-select>
134   - </el-form-item>
135   - <el-form-item label="是否有效" prop="enabled">
136   - <el-radio-group v-model="formData.enabled">
137   - <el-radio
  133 + </ElSelect>
  134 + </ElFormItem>
  135 + <ElFormItem label="是否有效" prop="enabled">
  136 + <ElRadioGroup v-model="formData.enabled">
  137 + <ElRadio
138 138 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
139 139 :key="dict.value"
140   - :label="dict.value"
  140 + :value="dict.value"
141 141 >
142 142 {{ dict.label }}
143   - </el-radio>
144   - </el-radio-group>
145   - </el-form-item>
146   - <el-form-item label="头像" prop="avatar">
  143 + </ElRadio>
  144 + </ElRadioGroup>
  145 + </ElFormItem>
  146 + <ElFormItem label="头像" prop="avatar">
147 147 <ImageUpload v-model="formData.avatar" />
148   - </el-form-item>
149   - <el-form-item label="附件" prop="video">
  148 + </ElFormItem>
  149 + <ElFormItem label="附件" prop="video">
150 150 <FileUpload v-model="formData.video" />
151   - </el-form-item>
152   - <el-form-item label="备注" prop="memo">
  151 + </ElFormItem>
  152 + <ElFormItem label="备注" prop="memo">
153 153 <RichTextarea v-model="formData.memo" height="500px" />
154   - </el-form-item>
155   - </el-form>
  154 + </ElFormItem>
  155 + </ElForm>
156 156 </Modal>
157 157 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_one/vue/index
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3  
4 4 import { ref, h, reactive, onMounted, nextTick } from 'vue';
5 5  
... ... @@ -17,7 +17,7 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;;
17 17  
18 18 import { $t } from '#/locales';
19 19 import { isEmpty } from '@vben/utils';
20   -import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/student';
  20 +import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/demo';
21 21  
22 22  
23 23 const loading = ref(true) // 列表的加载中
... ... @@ -27,12 +27,11 @@ const total = ref(0) // 列表的总页数
27 27 const queryParams = reactive({
28 28 pageNo: 1,
29 29 pageSize: 10,
30   - name: undefined,
31   - birthday: undefined,
32   - birthday: undefined,
33   - sex: undefined,
34   - enabled: undefined,
35   - createTime: undefined,
  30 + name: undefined,
  31 + birthday: undefined,
  32 + sex: undefined,
  33 + enabled: undefined,
  34 + createTime: undefined,
36 35 })
37 36 const queryFormRef = ref() // 搜索的表单
38 37 const exportLoading = ref(false) // 导出的加载中
... ... @@ -126,7 +125,14 @@ function handleRowCheckboxChange({
126 125 async function handleExport() {
127 126 try {
128 127 exportLoading.value = true;
129   - const data = await exportStudent(queryParams);
  128 + const params = cloneDeep(queryParams) as any;
  129 + if (params.birthday && Array.isArray(params.birthday)) {
  130 + params.birthday = (params.birthday as string[]).join(',');
  131 + }
  132 + if (params.createTime && Array.isArray(params.createTime)) {
  133 + params.createTime = (params.createTime as string[]).join(',');
  134 + }
  135 + const data = await exportStudent(params);
130 136 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
131 137 }finally {
132 138 exportLoading.value = false;
... ... @@ -148,61 +154,61 @@ onMounted(() =&gt; {
148 154  
149 155 <ContentWrap v-if="!hiddenSearchBar">
150 156 <!-- 搜索工作栏 -->
151   - <el-form
  157 + <ElForm
152 158 :model="queryParams"
153 159 ref="queryFormRef"
154 160 inline
155 161 >
156   - <el-form-item label="名字">
157   - <el-input
  162 + <ElFormItem label="名字">
  163 + <ElInput
158 164 v-model="queryParams.name"
159 165 placeholder="请输入名字"
160 166 clearable
161 167 @keyup.enter="handleQuery"
162 168 class="!w-[240px]"
163 169 />
164   - </el-form-item>
165   - <el-form-item label="出生日期">
166   - <el-date-picker
  170 + </ElFormItem>
  171 + <ElFormItem label="出生日期">
  172 + <ElDatePicker
167 173 v-model="queryParams.birthday"
168 174 value-format="YYYY-MM-DD"
169 175 placeholder="选择出生日期"
170 176 clearable
171 177 class="!w-[240px]"
172 178 />
173   - </el-form-item>
174   - <el-form-item label="性别">
175   - <el-select
  179 + </ElFormItem>
  180 + <ElFormItem label="性别">
  181 + <ElSelect
176 182 v-model="queryParams.sex"
177 183 placeholder="请选择性别"
178 184 clearable
179 185 class="!w-[240px]"
180 186 >
181   - <el-option
  187 + <ElOption
182 188 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
183 189 :key="dict.value"
184 190 :value="dict.value"
185 191 :label="dict.label"
186 192 />
187   - </el-select>
188   - </el-form-item>
189   - <el-form-item label="是否有效">
190   - <el-select
  193 + </ElSelect>
  194 + </ElFormItem>
  195 + <ElFormItem label="是否有效">
  196 + <ElSelect
191 197 v-model="queryParams.enabled"
192 198 placeholder="请选择是否有效"
193 199 clearable
194 200 class="!w-[240px]"
195 201 >
196   - <el-option
  202 + <ElOption
197 203 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
198 204 :key="dict.value"
199 205 :value="dict.value"
200 206 :label="dict.label"
201 207 />
202   - </el-select>
203   - </el-form-item>
204   - <el-form-item label="创建时间">
205   - <el-date-picker
  208 + </ElSelect>
  209 + </ElFormItem>
  210 + <ElFormItem label="创建时间">
  211 + <ElDatePicker
206 212 v-model="queryParams.createTime"
207 213 type="daterange"
208 214 value-format="YYYY-MM-DD"
... ... @@ -211,24 +217,24 @@ onMounted(() =&gt; {
211 217 end-placeholder="结束日期"
212 218 class="!w-[240px]"
213 219 />
214   - </el-form-item>
215   - <el-form-item>
216   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
217   - <el-button class="ml-2" @click="handleQuery" type="primary">
  220 + </ElFormItem>
  221 + <ElFormItem>
  222 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  223 + <ElButton class="ml-2" @click="handleQuery" type="primary">
218 224 搜索
219   - </el-button>
220   - </el-form-item>
221   - </el-form>
  225 + </ElButton>
  226 + </ElFormItem>
  227 + </ElForm>
222 228 </ContentWrap>
223 229  
224 230 <!-- 列表 -->
225 231 <ContentWrap title="学生">
226 232 <template #extra>
227   - <TableToolbar
  233 + <VbenVxeTableToolbar
228 234 ref="tableToolbarRef"
229 235 v-model:hidden-search="hiddenSearchBar"
230 236 >
231   - <el-button
  237 + <ElButton
232 238 class="ml-2"
233 239 :icon="h(Plus)"
234 240 type="primary"
... ... @@ -236,8 +242,8 @@ onMounted(() =&gt; {
236 242 v-access:code="['infra:student:create']"
237 243 >
238 244 {{ $t('ui.actionTitle.create', ['学生']) }}
239   - </el-button>
240   - <el-button
  245 + </ElButton>
  246 + <ElButton
241 247 :icon="h(Download)"
242 248 type="primary"
243 249 class="ml-2"
... ... @@ -246,8 +252,8 @@ onMounted(() =&gt; {
246 252 v-access:code="['infra:student:export']"
247 253 >
248 254 {{ $t('ui.actionTitle.export') }}
249   - </el-button>
250   - <el-button
  255 + </ElButton>
  256 + <ElButton
251 257 :icon="h(Trash2)"
252 258 type="danger"
253 259 class="ml-2"
... ... @@ -256,10 +262,10 @@ onMounted(() =&gt; {
256 262 v-access:code="['infra:student:delete']"
257 263 >
258 264 批量删除
259   - </el-button>
260   - </TableToolbar>
  265 + </ElButton>
  266 + </VbenVxeTableToolbar>
261 267 </template>
262   - <vxe-table
  268 + <VxeTable
263 269 ref="tableRef"
264 270 :data="list"
265 271 show-overflow
... ... @@ -267,36 +273,36 @@ onMounted(() =&gt; {
267 273 @checkboxAll="handleRowCheckboxChange"
268 274 @checkboxChange="handleRowCheckboxChange"
269 275 >
270   - <vxe-column type="checkbox" width="40"></vxe-column>
271   - <vxe-column field="id" title="编号" align="center" />
272   - <vxe-column field="name" title="名字" align="center" />
273   - <vxe-column field="description" title="简介" align="center" />
274   - <vxe-column field="birthday" title="出生日期" align="center">
  276 + <VxeColumn type="checkbox" width="40"></VxeColumn>
  277 + <VxeColumn field="id" title="编号" align="center" />
  278 + <VxeColumn field="name" title="名字" align="center" />
  279 + <VxeColumn field="description" title="简介" align="center" />
  280 + <VxeColumn field="birthday" title="出生日期" align="center">
275 281 <template #default="{row}">
276 282 {{formatDateTime(row.birthday)}}
277 283 </template>
278   - </vxe-column>
279   - <vxe-column field="sex" title="性别" align="center">
  284 + </VxeColumn>
  285 + <VxeColumn field="sex" title="性别" align="center">
280 286 <template #default="{row}">
281 287 <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
282 288 </template>
283   - </vxe-column>
284   - <vxe-column field="enabled" title="是否有效" align="center">
  289 + </VxeColumn>
  290 + <VxeColumn field="enabled" title="是否有效" align="center">
285 291 <template #default="{row}">
286 292 <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.enabled" />
287 293 </template>
288   - </vxe-column>
289   - <vxe-column field="avatar" title="头像" align="center" />
290   - <vxe-column field="video" title="附件" align="center" />
291   - <vxe-column field="memo" title="备注" align="center" />
292   - <vxe-column field="createTime" title="创建时间" align="center">
  294 + </VxeColumn>
  295 + <VxeColumn field="avatar" title="头像" align="center" />
  296 + <VxeColumn field="video" title="附件" align="center" />
  297 + <VxeColumn field="memo" title="备注" align="center" />
  298 + <VxeColumn field="createTime" title="创建时间" align="center">
293 299 <template #default="{row}">
294 300 {{formatDateTime(row.createTime)}}
295 301 </template>
296   - </vxe-column>
297   - <vxe-column field="operation" title="操作" align="center">
  302 + </VxeColumn>
  303 + <VxeColumn field="operation" title="操作" align="center">
298 304 <template #default="{row}">
299   - <el-button
  305 + <ElButton
300 306 size="small"
301 307 type="primary"
302 308 link
... ... @@ -304,8 +310,8 @@ onMounted(() =&gt; {
304 310 v-access:code="['infra:student:update']"
305 311 >
306 312 {{ $t('ui.actionTitle.edit') }}
307   - </el-button>
308   - <el-button
  313 + </ElButton>
  314 + <ElButton
309 315 size="small"
310 316 type="danger"
311 317 link
... ... @@ -314,13 +320,13 @@ onMounted(() =&gt; {
314 320 v-access:code="['infra:student:delete']"
315 321 >
316 322 {{ $t('ui.actionTitle.delete') }}
317   - </el-button>
  323 + </ElButton>
318 324 </template>
319   - </vxe-column>
320   - </vxe-table>
  325 + </VxeColumn>
  326 + </VxeTable>
321 327 <!-- 分页 -->
322 328 <div class="mt-2 flex justify-end">
323   - <el-pagination
  329 + <ElPagination
324 330 :total="total"
325 331 v-model:current-page="queryParams.pageNo"
326 332 v-model:page-size="queryParams.pageSize"
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_one_importEnable/vue/form
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3 import type { FormRules } from 'element-plus';
4 4  
5 5 import { useVbenModal } from '@vben/common-ui';
... ... @@ -11,7 +11,7 @@ import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElSelect, El
11 11  
12 12 import { computed, ref, reactive } from 'vue';
13 13 import { $t } from '#/locales';
14   -import { getStudent, createStudent, updateStudent } from '#/api/infra/student';
  14 +import { getStudent, createStudent, updateStudent } from '#/api/infra/demo';
15 15  
16 16 const emit = defineEmits(['success']);
17 17  
... ... @@ -102,56 +102,56 @@ const [Modal, modalApi] = useVbenModal({
102 102  
103 103 <template>
104 104 <Modal :title="getTitle">
105   - <el-form
  105 + <ElForm
106 106 ref="formRef"
107 107 :model="formData"
108 108 :rules="rules"
109 109 label-width="120px"
110 110 label-position="right"
111 111 >
112   - <el-form-item label="名字" prop="name">
113   - <el-input v-model="formData.name" placeholder="请输入名字" />
114   - </el-form-item>
115   - <el-form-item label="简介" prop="description">
116   - <el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
117   - </el-form-item>
118   - <el-form-item label="出生日期" prop="birthday">
119   - <el-date-picker
  112 + <ElFormItem label="名字" prop="name">
  113 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  114 + </ElFormItem>
  115 + <ElFormItem label="简介" prop="description">
  116 + <ElInput v-model="formData.description" type="textarea" placeholder="请输入简介" />
  117 + </ElFormItem>
  118 + <ElFormItem label="出生日期" prop="birthday">
  119 + <ElDatePicker
120 120 v-model="formData.birthday"
121 121 value-format="x"
122 122 placeholder="选择出生日期"
123 123 />
124   - </el-form-item>
125   - <el-form-item label="性别" prop="sex">
126   - <el-select v-model="formData.sex" placeholder="请选择性别">
127   - <el-option
  124 + </ElFormItem>
  125 + <ElFormItem label="性别" prop="sex">
  126 + <ElSelect v-model="formData.sex" placeholder="请选择性别">
  127 + <ElOption
128 128 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
129 129 :key="dict.value"
130 130 :value="dict.value"
131 131 :label="dict.label"
132 132 />
133   - </el-select>
134   - </el-form-item>
135   - <el-form-item label="是否有效" prop="enabled">
136   - <el-radio-group v-model="formData.enabled">
137   - <el-radio
  133 + </ElSelect>
  134 + </ElFormItem>
  135 + <ElFormItem label="是否有效" prop="enabled">
  136 + <ElRadioGroup v-model="formData.enabled">
  137 + <ElRadio
138 138 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
139 139 :key="dict.value"
140   - :label="dict.value"
  140 + :value="dict.value"
141 141 >
142 142 {{ dict.label }}
143   - </el-radio>
144   - </el-radio-group>
145   - </el-form-item>
146   - <el-form-item label="头像" prop="avatar">
  143 + </ElRadio>
  144 + </ElRadioGroup>
  145 + </ElFormItem>
  146 + <ElFormItem label="头像" prop="avatar">
147 147 <ImageUpload v-model="formData.avatar" />
148   - </el-form-item>
149   - <el-form-item label="附件" prop="video">
  148 + </ElFormItem>
  149 + <ElFormItem label="附件" prop="video">
150 150 <FileUpload v-model="formData.video" />
151   - </el-form-item>
152   - <el-form-item label="备注" prop="memo">
  151 + </ElFormItem>
  152 + <ElFormItem label="备注" prop="memo">
153 153 <RichTextarea v-model="formData.memo" height="500px" />
154   - </el-form-item>
155   - </el-form>
  154 + </ElFormItem>
  155 + </ElForm>
156 156 </Modal>
157 157 </template>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_one_importEnable/vue/import-form
... ... @@ -47,6 +47,16 @@ function handleChange(file: any) {
47 47 }
48 48 }
49 49  
  50 +/** 移除文件 */
  51 +function handleRemove() {
  52 + fileRef.value = null;
  53 +}
  54 +
  55 +/** 超出文件数量 */
  56 +function handleExceed() {
  57 + ElMessage.warning('只能选择一个文件,请先移除已选文件');
  58 +}
  59 +
50 60 /** 下载导入模板 */
51 61 async function handleDownload() {
52 62 const data = await importStudentTemplate();
... ... @@ -61,6 +71,8 @@ async function handleDownload() {
61 71 :limit="1"
62 72 accept=".xls,.xlsx"
63 73 :on-change="handleChange"
  74 + :on-remove="handleRemove"
  75 + :on-exceed="handleExceed"
64 76 :auto-upload="false"
65 77 >
66 78 <ElButton type="primary"> 选择 Excel 文件 </ElButton>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_one_importEnable/vue/index
1 1 <script lang="ts" setup>
2   -import type { StudentApi } from '#/api/infra/student';
  2 +import type { StudentApi } from '#/api/infra/demo';
3 3  
4 4 import { ref, h, reactive, onMounted, nextTick } from 'vue';
5 5  
... ... @@ -17,7 +17,7 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;;
17 17  
18 18 import { $t } from '#/locales';
19 19 import { isEmpty } from '@vben/utils';
20   -import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/student';
  20 +import { getStudentPage, deleteStudent, deleteStudentList, exportStudent } from '#/api/infra/demo';
21 21 import StudentImport from './modules/import-form.vue';
22 22  
23 23  
... ... @@ -28,12 +28,11 @@ const total = ref(0) // 列表的总页数
28 28 const queryParams = reactive({
29 29 pageNo: 1,
30 30 pageSize: 10,
31   - name: undefined,
32   - birthday: undefined,
33   - birthday: undefined,
34   - sex: undefined,
35   - enabled: undefined,
36   - createTime: undefined,
  31 + name: undefined,
  32 + birthday: undefined,
  33 + sex: undefined,
  34 + enabled: undefined,
  35 + createTime: undefined,
37 36 })
38 37 const queryFormRef = ref() // 搜索的表单
39 38 const exportLoading = ref(false) // 导出的加载中
... ... @@ -136,7 +135,14 @@ function handleRowCheckboxChange({
136 135 async function handleExport() {
137 136 try {
138 137 exportLoading.value = true;
139   - const data = await exportStudent(queryParams);
  138 + const params = cloneDeep(queryParams) as any;
  139 + if (params.birthday && Array.isArray(params.birthday)) {
  140 + params.birthday = (params.birthday as string[]).join(',');
  141 + }
  142 + if (params.createTime && Array.isArray(params.createTime)) {
  143 + params.createTime = (params.createTime as string[]).join(',');
  144 + }
  145 + const data = await exportStudent(params);
140 146 downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
141 147 }finally {
142 148 exportLoading.value = false;
... ... @@ -159,61 +165,61 @@ onMounted(() =&gt; {
159 165  
160 166 <ContentWrap v-if="!hiddenSearchBar">
161 167 <!-- 搜索工作栏 -->
162   - <el-form
  168 + <ElForm
163 169 :model="queryParams"
164 170 ref="queryFormRef"
165 171 inline
166 172 >
167   - <el-form-item label="名字">
168   - <el-input
  173 + <ElFormItem label="名字">
  174 + <ElInput
169 175 v-model="queryParams.name"
170 176 placeholder="请输入名字"
171 177 clearable
172 178 @keyup.enter="handleQuery"
173 179 class="!w-[240px]"
174 180 />
175   - </el-form-item>
176   - <el-form-item label="出生日期">
177   - <el-date-picker
  181 + </ElFormItem>
  182 + <ElFormItem label="出生日期">
  183 + <ElDatePicker
178 184 v-model="queryParams.birthday"
179 185 value-format="YYYY-MM-DD"
180 186 placeholder="选择出生日期"
181 187 clearable
182 188 class="!w-[240px]"
183 189 />
184   - </el-form-item>
185   - <el-form-item label="性别">
186   - <el-select
  190 + </ElFormItem>
  191 + <ElFormItem label="性别">
  192 + <ElSelect
187 193 v-model="queryParams.sex"
188 194 placeholder="请选择性别"
189 195 clearable
190 196 class="!w-[240px]"
191 197 >
192   - <el-option
  198 + <ElOption
193 199 v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
194 200 :key="dict.value"
195 201 :value="dict.value"
196 202 :label="dict.label"
197 203 />
198   - </el-select>
199   - </el-form-item>
200   - <el-form-item label="是否有效">
201   - <el-select
  204 + </ElSelect>
  205 + </ElFormItem>
  206 + <ElFormItem label="是否有效">
  207 + <ElSelect
202 208 v-model="queryParams.enabled"
203 209 placeholder="请选择是否有效"
204 210 clearable
205 211 class="!w-[240px]"
206 212 >
207   - <el-option
  213 + <ElOption
208 214 v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean')"
209 215 :key="dict.value"
210 216 :value="dict.value"
211 217 :label="dict.label"
212 218 />
213   - </el-select>
214   - </el-form-item>
215   - <el-form-item label="创建时间">
216   - <el-date-picker
  219 + </ElSelect>
  220 + </ElFormItem>
  221 + <ElFormItem label="创建时间">
  222 + <ElDatePicker
217 223 v-model="queryParams.createTime"
218 224 type="daterange"
219 225 value-format="YYYY-MM-DD"
... ... @@ -222,24 +228,24 @@ onMounted(() =&gt; {
222 228 end-placeholder="结束日期"
223 229 class="!w-[240px]"
224 230 />
225   - </el-form-item>
226   - <el-form-item>
227   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
228   - <el-button class="ml-2" @click="handleQuery" type="primary">
  231 + </ElFormItem>
  232 + <ElFormItem>
  233 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  234 + <ElButton class="ml-2" @click="handleQuery" type="primary">
229 235 搜索
230   - </el-button>
231   - </el-form-item>
232   - </el-form>
  236 + </ElButton>
  237 + </ElFormItem>
  238 + </ElForm>
233 239 </ContentWrap>
234 240  
235 241 <!-- 列表 -->
236 242 <ContentWrap title="学生">
237 243 <template #extra>
238   - <TableToolbar
  244 + <VbenVxeTableToolbar
239 245 ref="tableToolbarRef"
240 246 v-model:hidden-search="hiddenSearchBar"
241 247 >
242   - <el-button
  248 + <ElButton
243 249 class="ml-2"
244 250 :icon="h(Plus)"
245 251 type="primary"
... ... @@ -247,16 +253,16 @@ onMounted(() =&gt; {
247 253 v-access:code="['infra:student:create']"
248 254 >
249 255 {{ $t('ui.actionTitle.create', ['学生']) }}
250   - </el-button>
251   - <el-button
  256 + </ElButton>
  257 + <ElButton
252 258 class="ml-2"
253 259 type="primary"
254 260 @click="handleImport"
255 261 v-access:code="['infra:student:import']"
256 262 >
257 263 导入
258   - </el-button>
259   - <el-button
  264 + </ElButton>
  265 + <ElButton
260 266 :icon="h(Download)"
261 267 type="primary"
262 268 class="ml-2"
... ... @@ -265,8 +271,8 @@ onMounted(() =&gt; {
265 271 v-access:code="['infra:student:export']"
266 272 >
267 273 {{ $t('ui.actionTitle.export') }}
268   - </el-button>
269   - <el-button
  274 + </ElButton>
  275 + <ElButton
270 276 :icon="h(Trash2)"
271 277 type="danger"
272 278 class="ml-2"
... ... @@ -275,10 +281,10 @@ onMounted(() =&gt; {
275 281 v-access:code="['infra:student:delete']"
276 282 >
277 283 批量删除
278   - </el-button>
279   - </TableToolbar>
  284 + </ElButton>
  285 + </VbenVxeTableToolbar>
280 286 </template>
281   - <vxe-table
  287 + <VxeTable
282 288 ref="tableRef"
283 289 :data="list"
284 290 show-overflow
... ... @@ -286,36 +292,36 @@ onMounted(() =&gt; {
286 292 @checkboxAll="handleRowCheckboxChange"
287 293 @checkboxChange="handleRowCheckboxChange"
288 294 >
289   - <vxe-column type="checkbox" width="40"></vxe-column>
290   - <vxe-column field="id" title="编号" align="center" />
291   - <vxe-column field="name" title="名字" align="center" />
292   - <vxe-column field="description" title="简介" align="center" />
293   - <vxe-column field="birthday" title="出生日期" align="center">
  295 + <VxeColumn type="checkbox" width="40"></VxeColumn>
  296 + <VxeColumn field="id" title="编号" align="center" />
  297 + <VxeColumn field="name" title="名字" align="center" />
  298 + <VxeColumn field="description" title="简介" align="center" />
  299 + <VxeColumn field="birthday" title="出生日期" align="center">
294 300 <template #default="{row}">
295 301 {{formatDateTime(row.birthday)}}
296 302 </template>
297   - </vxe-column>
298   - <vxe-column field="sex" title="性别" align="center">
  303 + </VxeColumn>
  304 + <VxeColumn field="sex" title="性别" align="center">
299 305 <template #default="{row}">
300 306 <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
301 307 </template>
302   - </vxe-column>
303   - <vxe-column field="enabled" title="是否有效" align="center">
  308 + </VxeColumn>
  309 + <VxeColumn field="enabled" title="是否有效" align="center">
304 310 <template #default="{row}">
305 311 <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.enabled" />
306 312 </template>
307   - </vxe-column>
308   - <vxe-column field="avatar" title="头像" align="center" />
309   - <vxe-column field="video" title="附件" align="center" />
310   - <vxe-column field="memo" title="备注" align="center" />
311   - <vxe-column field="createTime" title="创建时间" align="center">
  313 + </VxeColumn>
  314 + <VxeColumn field="avatar" title="头像" align="center" />
  315 + <VxeColumn field="video" title="附件" align="center" />
  316 + <VxeColumn field="memo" title="备注" align="center" />
  317 + <VxeColumn field="createTime" title="创建时间" align="center">
312 318 <template #default="{row}">
313 319 {{formatDateTime(row.createTime)}}
314 320 </template>
315   - </vxe-column>
316   - <vxe-column field="operation" title="操作" align="center">
  321 + </VxeColumn>
  322 + <VxeColumn field="operation" title="操作" align="center">
317 323 <template #default="{row}">
318   - <el-button
  324 + <ElButton
319 325 size="small"
320 326 type="primary"
321 327 link
... ... @@ -323,8 +329,8 @@ onMounted(() =&gt; {
323 329 v-access:code="['infra:student:update']"
324 330 >
325 331 {{ $t('ui.actionTitle.edit') }}
326   - </el-button>
327   - <el-button
  332 + </ElButton>
  333 + <ElButton
328 334 size="small"
329 335 type="danger"
330 336 link
... ... @@ -333,13 +339,13 @@ onMounted(() =&gt; {
333 339 v-access:code="['infra:student:delete']"
334 340 >
335 341 {{ $t('ui.actionTitle.delete') }}
336   - </el-button>
  342 + </ElButton>
337 343 </template>
338   - </vxe-column>
339   - </vxe-table>
  344 + </VxeColumn>
  345 + </VxeTable>
340 346 <!-- 分页 -->
341 347 <div class="mt-2 flex justify-end">
342   - <el-pagination
  348 + <ElPagination
343 349 :total="total"
344 350 v-model:current-page="queryParams.pageNo"
345 351 v-model:page-size="queryParams.pageSize"
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_tree/vue/form
1 1 <script lang="ts" setup>
2   -import type { CategoryApi } from '#/api/infra/category';
  2 +import type { CategoryApi } from '#/api/infra/demo';
3 3 import type { FormRules } from 'element-plus';
4 4  
5 5 import { useVbenModal } from '@vben/common-ui';
... ... @@ -7,12 +7,12 @@ import { getDictOptions } from &#39;@vben/hooks&#39;;
7 7 import { Tinymce as RichTextarea } from '#/components/tinymce';
8 8 import { ImageUpload, FileUpload } from "#/components/upload";
9 9 import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker, ElTreeSelect } from 'element-plus';
10   -import { getCategoryList } from '#/api/infra/category';
  10 +import { getCategoryList } from '#/api/infra/demo';
11 11 import { handleTree } from '@vben/utils'
12 12  
13 13 import { computed, ref, reactive } from 'vue';
14 14 import { $t } from '#/locales';
15   -import { getCategory, createCategory, updateCategory } from '#/api/infra/category';
  15 +import { getCategory, createCategory, updateCategory } from '#/api/infra/demo';
16 16  
17 17 const emit = defineEmits(['success']);
18 18  
... ... @@ -98,18 +98,18 @@ const [Modal, modalApi] = useVbenModal({
98 98  
99 99 <template>
100 100 <Modal :title="getTitle">
101   - <el-form
  101 + <ElForm
102 102 ref="formRef"
103 103 :model="formData"
104 104 :rules="rules"
105 105 label-width="120px"
106 106 label-position="right"
107 107 >
108   - <el-form-item label="名字" prop="name">
109   - <el-input v-model="formData.name" placeholder="请输入名字" />
110   - </el-form-item>
111   - <el-form-item label="父编号" prop="parentId">
112   - <el-tree-select
  108 + <ElFormItem label="名字" prop="name">
  109 + <ElInput v-model="formData.name" placeholder="请输入名字" />
  110 + </ElFormItem>
  111 + <ElFormItem label="父编号" prop="parentId">
  112 + <ElTreeSelect
113 113 v-model="formData.parentId"
114 114 :data="categoryTree"
115 115 :props="{
... ... @@ -121,7 +121,7 @@ const [Modal, modalApi] = useVbenModal({
121 121 default-expand-all
122 122 placeholder="请选择父编号"
123 123 />
124   - </el-form-item>
125   - </el-form>
  124 + </ElFormItem>
  125 + </ElForm>
126 126 </Modal>
127 127 </template>
128 128 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_general_tree/vue/index
1 1 <script lang="ts" setup>
2   -import type { CategoryApi } from '#/api/infra/category';
  2 +import type { CategoryApi } from '#/api/infra/demo';
3 3  
4 4 import { ref, h, reactive, onMounted, nextTick } from 'vue';
5 5  
... ... @@ -16,14 +16,14 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;;
16 16  
17 17 import { $t } from '#/locales';
18 18 import { handleTree,isEmpty } from '@vben/utils'
19   -import { getCategoryList, deleteCategory, exportCategory } from '#/api/infra/category';
  19 +import { getCategoryList, deleteCategory, exportCategory } from '#/api/infra/demo';
20 20  
21 21  
22 22 const loading = ref(true) // 列表的加载中
23 23 const list = ref<any[]>([]) // 树列表的数据
24 24  
25 25 const queryParams = reactive({
26   - name: undefined,
  26 + name: undefined,
27 27 })
28 28 const queryFormRef = ref() // 搜索的表单
29 29 const exportLoading = ref(false) // 导出的加载中
... ... @@ -89,7 +89,8 @@ async function handleDelete(row: CategoryApi.Category) {
89 89 async function handleExport() {
90 90 try {
91 91 exportLoading.value = true;
92   - const data = await exportCategory(queryParams);
  92 + const params = cloneDeep(queryParams) as any;
  93 + const data = await exportCategory(params);
93 94 downloadFileFromBlobPart({ fileName: '分类.xls', source: data });
94 95 }finally {
95 96 exportLoading.value = false;
... ... @@ -117,40 +118,40 @@ onMounted(() =&gt; {
117 118  
118 119 <ContentWrap v-if="!hiddenSearchBar">
119 120 <!-- 搜索工作栏 -->
120   - <el-form
  121 + <ElForm
121 122 :model="queryParams"
122 123 ref="queryFormRef"
123 124 inline
124 125 >
125   - <el-form-item label="名字">
126   - <el-input
  126 + <ElFormItem label="名字">
  127 + <ElInput
127 128 v-model="queryParams.name"
128 129 placeholder="请输入名字"
129 130 clearable
130 131 @keyup.enter="handleQuery"
131 132 class="!w-[240px]"
132 133 />
133   - </el-form-item>
134   - <el-form-item>
135   - <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
136   - <el-button class="ml-2" @click="handleQuery" type="primary">
  134 + </ElFormItem>
  135 + <ElFormItem>
  136 + <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton>
  137 + <ElButton class="ml-2" @click="handleQuery" type="primary">
137 138 搜索
138   - </el-button>
139   - </el-form-item>
140   - </el-form>
  139 + </ElButton>
  140 + </ElFormItem>
  141 + </ElForm>
141 142 </ContentWrap>
142 143  
143 144 <!-- 列表 -->
144 145 <ContentWrap title="分类">
145 146 <template #extra>
146   - <TableToolbar
  147 + <VbenVxeTableToolbar
147 148 ref="tableToolbarRef"
148 149 v-model:hidden-search="hiddenSearchBar"
149 150 >
150   - <el-button @click="handleExpand" class="mr-2">
  151 + <ElButton @click="handleExpand" class="mr-2">
151 152 {{ isExpanded ? '收缩' : '展开' }}
152   - </el-button>
153   - <el-button
  153 + </ElButton>
  154 + <ElButton
154 155 class="ml-2"
155 156 :icon="h(Plus)"
156 157 type="primary"
... ... @@ -158,8 +159,8 @@ onMounted(() =&gt; {
158 159 v-access:code="['infra:category:create']"
159 160 >
160 161 {{ $t('ui.actionTitle.create', ['分类']) }}
161   - </el-button>
162   - <el-button
  162 + </ElButton>
  163 + <ElButton
163 164 :icon="h(Download)"
164 165 type="primary"
165 166 class="ml-2"
... ... @@ -168,10 +169,10 @@ onMounted(() =&gt; {
168 169 v-access:code="['infra:category:export']"
169 170 >
170 171 {{ $t('ui.actionTitle.export') }}
171   - </el-button>
172   - </TableToolbar>
  172 + </ElButton>
  173 + </VbenVxeTableToolbar>
173 174 </template>
174   - <vxe-table
  175 + <VxeTable
175 176 ref="tableRef"
176 177 :data="list"
177 178 :tree-config="{
... ... @@ -184,12 +185,12 @@ onMounted(() =&gt; {
184 185 show-overflow
185 186 :loading="loading"
186 187 >
187   - <vxe-column field="id" title="编号" align="center" />
188   - <vxe-column field="name" title="名字" align="center" tree-node/>
189   - <vxe-column field="parentId" title="父编号" align="center" />
190   - <vxe-column field="operation" title="操作" align="center">
  188 + <VxeColumn field="id" title="编号" align="center" />
  189 + <VxeColumn field="name" title="名字" align="center" tree-node/>
  190 + <VxeColumn field="parentId" title="父编号" align="center" />
  191 + <VxeColumn field="operation" title="操作" align="center">
191 192 <template #default="{row}">
192   - <el-button
  193 + <ElButton
193 194 size="small"
194 195 type="primary"
195 196 link
... ... @@ -197,8 +198,8 @@ onMounted(() =&gt; {
197 198 v-access:code="['infra:category:create']"
198 199 >
199 200 新增下级
200   - </el-button>
201   - <el-button
  201 + </ElButton>
  202 + <ElButton
202 203 size="small"
203 204 type="primary"
204 205 link
... ... @@ -206,8 +207,8 @@ onMounted(() =&gt; {
206 207 v-access:code="['infra:category:update']"
207 208 >
208 209 {{ $t('ui.actionTitle.edit') }}
209   - </el-button>
210   - <el-button
  210 + </ElButton>
  211 + <ElButton
211 212 size="small"
212 213 type="danger"
213 214 link
... ... @@ -217,10 +218,10 @@ onMounted(() =&gt; {
217 218 v-access:code="['infra:category:delete']"
218 219 >
219 220 {{ $t('ui.actionTitle.delete') }}
220   - </el-button>
  221 + </ElButton>
221 222 </template>
222   - </vxe-column>
223   - </vxe-table>
  223 + </VxeColumn>
  224 + </VxeTable>
224 225 </ContentWrap>
225 226 </Page>
226 227 </template>
227 228 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_erp/ts/data
... ... @@ -42,7 +42,7 @@ export function useFormSchema(): VbenFormSchema[] {
42 42 rules: 'required',
43 43 component: 'DatePicker',
44 44 componentProps: {
45   - showTime: true,
  45 + type: 'datetime',
46 46 format: 'YYYY-MM-DD HH:mm:ss',
47 47 valueFormat: 'x',
48 48 class: '!w-full',
... ... @@ -65,8 +65,7 @@ export function useFormSchema(): VbenFormSchema[] {
65 65 component: 'RadioGroup',
66 66 componentProps: {
67 67 options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
68   - buttonStyle: 'solid',
69   - optionType: 'button',
  68 + isButton: true,
70 69 },
71 70 },
72 71 {
... ... @@ -277,7 +276,7 @@ export function useStudentContactFormSchema(): VbenFormSchema[] {
277 276 rules: 'required',
278 277 component: 'DatePicker',
279 278 componentProps: {
280   - showTime: true,
  279 + type: 'datetime',
281 280 format: 'YYYY-MM-DD HH:mm:ss',
282 281 valueFormat: 'x',
283 282 class: '!w-full',
... ... @@ -300,8 +299,7 @@ export function useStudentContactFormSchema(): VbenFormSchema[] {
300 299 component: 'RadioGroup',
301 300 componentProps: {
302 301 options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
303   - buttonStyle: 'solid',
304   - optionType: 'button',
  302 + isButton: true,
305 303 },
306 304 },
307 305 {
... ... @@ -520,7 +518,7 @@ export function useStudentTeacherFormSchema(): VbenFormSchema[] {
520 518 rules: 'required',
521 519 component: 'DatePicker',
522 520 componentProps: {
523   - showTime: true,
  521 + type: 'datetime',
524 522 format: 'YYYY-MM-DD HH:mm:ss',
525 523 valueFormat: 'x',
526 524 class: '!w-full',
... ... @@ -543,8 +541,7 @@ export function useStudentTeacherFormSchema(): VbenFormSchema[] {
543 541 component: 'RadioGroup',
544 542 componentProps: {
545 543 options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
546   - buttonStyle: 'solid',
547   - optionType: 'button',
  544 + isButton: true,
548 545 },
549 546 },
550 547 {
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_erp/vue/form
... ... @@ -5,7 +5,7 @@ import { computed, ref } from &#39;vue&#39;;
5 5  
6 6 import { useVbenModal } from '@vben/common-ui';
7 7  
8   -import { ElMessage, ElTabs, ElTabPane } from 'element-plus';
  8 +import { ElMessage } from 'element-plus';
9 9  
10 10 import { useVbenForm } from '#/adapter/form';
11 11 import { createStudent, getStudent, updateStudent } from '#/api/infra/demo';
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_inner/ts/data
... ... @@ -42,7 +42,7 @@ export function useFormSchema(): VbenFormSchema[] {
42 42 rules: 'required',
43 43 component: 'DatePicker',
44 44 componentProps: {
45   - showTime: true,
  45 + type: 'datetime',
46 46 format: 'YYYY-MM-DD HH:mm:ss',
47 47 valueFormat: 'x',
48 48 class: '!w-full',
... ... @@ -65,8 +65,7 @@ export function useFormSchema(): VbenFormSchema[] {
65 65 component: 'RadioGroup',
66 66 componentProps: {
67 67 options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
68   - buttonStyle: 'solid',
69   - optionType: 'button',
  68 + isButton: true,
70 69 },
71 70 },
72 71 {
... ... @@ -270,7 +269,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
270 269 minWidth: 120,
271 270 slots: { default: 'sex' },
272 271 params: {
273   - options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'boolean'),
  272 + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'),
274 273 },
275 274 },
276 275 {
... ... @@ -310,7 +309,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
310 309 }
311 310  
312 311 /** 列表的字段 */
313   - export function useStudentContactGridColumns(): VxeTableGridOptions<StudentApi.StudentContact>['columns'] {
  312 + export function useStudentContactGridColumns(): VxeTableGridOptions<InfraStudentApi.StudentContact>['columns'] {
314 313 return [
315 314 {
316 315 field: 'id',
... ... @@ -444,7 +443,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
444 443 rules: 'required',
445 444 component: 'DatePicker',
446 445 componentProps: {
447   - showTime: true,
  446 + type: 'datetime',
448 447 format: 'YYYY-MM-DD HH:mm:ss',
449 448 valueFormat: 'x',
450 449 class: '!w-full',
... ... @@ -467,8 +466,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
467 466 component: 'RadioGroup',
468 467 componentProps: {
469 468 options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
470   - buttonStyle: 'solid',
471   - optionType: 'button',
  469 + isButton: true,
472 470 },
473 471 },
474 472 {
... ... @@ -492,7 +490,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
492 490 }
493 491  
494 492 /** 列表的字段 */
495   - export function useStudentTeacherGridColumns(): VxeTableGridOptions<StudentApi.StudentTeacher>['columns'] {
  493 + export function useStudentTeacherGridColumns(): VxeTableGridOptions<InfraStudentApi.StudentTeacher>['columns'] {
496 494 return [
497 495 {
498 496 field: 'id',
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_inner/vue/form
... ... @@ -5,7 +5,7 @@ import { computed, ref } from &#39;vue&#39;;
5 5  
6 6 import { useVbenModal } from '@vben/common-ui';
7 7  
8   -import { ElMessage } from 'element-plus';
  8 +import { ElMessage, ElTabPane, ElTabs } from 'element-plus';
9 9  
10 10 import { useVbenForm } from '#/adapter/form';
11 11 import { createStudent, getStudent, updateStudent } from '#/api/infra/demo';
... ... @@ -48,7 +48,12 @@ const [Modal, modalApi] = useVbenModal({
48 48 return;
49 49 }
50 50 // 校验子表单
51   - const studentTeacherValid = await studentTeacherFormRef.value?.validate();
  51 + const studentContactValid = await studentContactFormRef.value?.validate();
  52 + if (!studentContactValid) {
  53 + subTabsName.value = 'studentContact';
  54 + return;
  55 + }
  56 + const studentTeacherValid = await studentTeacherFormRef.value?.validate();
52 57 if (!studentTeacherValid) {
53 58 subTabsName.value = 'studentTeacher';
54 59 return;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_inner/vue/student-contact-form
... ... @@ -6,10 +6,9 @@
6 6 import { $t } from '#/locales';
7 7  
8 8 import { Plus } from "@vben/icons";
9   -import { ElButton, ElTabs, ElTabPane, ElCheckbox, ElInput, ElSelect, ElOption, ElRadioGroup, ElCheckboxGroup, ElDatePicker } from 'element-plus';
  9 +import { ElButton, ElCheckbox, ElCheckboxGroup, ElDatePicker, ElInput, ElMessage, ElOption, ElRadio, ElRadioGroup, ElSelect, ElTabPane, ElTabs } from 'element-plus';
10 10 import { ImageUpload, FileUpload } from "#/components/upload";
11   -import type { OnActionClickParams } from '#/adapter/vxe-table';
12   -import { useVbenVxeGrid } from '#/adapter/vxe-table';
  11 +import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
13 12 import { useStudentContactGridEditColumns } from '../data';
14 13 import { getStudentContactListByStudentId } from '#/api/infra/demo';
15 14  
... ... @@ -17,22 +16,9 @@ const props = defineProps&lt;{
17 16 studentId?: number // 学生编号(主表的关联字段)
18 17 }>()
19 18  
20   - /** 表格操作按钮的回调函数 */
21   -function onActionClick({
22   - code,
23   - row,
24   -}: OnActionClickParams<InfraStudentApi.StudentContact>) {
25   - switch (code) {
26   - case 'delete': {
27   - onDelete(row);
28   - break;
29   - }
30   - }
31   -}
32   -
33   -const [Grid, gridApi] = useVbenVxeGrid({
  19 + const [Grid, gridApi] = useVbenVxeGrid({
34 20 gridOptions: {
35   - columns: useStudentContactGridEditColumns(onActionClick),
  21 + columns: useStudentContactGridEditColumns(),
36 22 border: true,
37 23 showOverflow: true,
38 24 autoResize: true,
... ... @@ -51,7 +37,7 @@ gridOptions: {
51 37  
52 38 /** 添加学生联系人 */
53 39 const onAdd = async () => {
54   - await gridApi.grid.insertAt({} as StudentApi.StudentContact, -1);
  40 + await gridApi.grid.insertAt({} as InfraStudentApi.StudentContact, -1);
55 41 }
56 42  
57 43 /** 删除学生联系人 */
... ... @@ -59,26 +45,96 @@ const onDelete = async (row: InfraStudentApi.StudentContact) =&gt; {
59 45 await gridApi.grid.remove(row);
60 46 }
61 47  
62   -/** 提供获取表格数据的方法供父组件调用 */
63   -defineExpose({
64   - getData: (): InfraStudentApi.StudentContact[] => {
65   - const data = gridApi.grid.getData() as InfraStudentApi.StudentContact[];
66   - const removeRecords = gridApi.grid.getRemoveRecords() as InfraStudentApi.StudentContact[];
67   - const insertRecords = gridApi.grid.getInsertRecords() as InfraStudentApi.StudentContact[];
68   - return data
69   - .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
70   - .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
71   - },
72   -});
  48 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  49 +function getData(): InfraStudentApi.StudentContact[] {
  50 + const { fullData } = gridApi.grid.getTableData();
  51 + const inserted = new Set(gridApi.grid.getInsertRecords());
  52 + return fullData.map((row) =>
  53 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  54 + );
  55 +}
  56 +
  57 +/** 校验子表必填字段 */
  58 +function validate(): boolean {
  59 + for (const [index, row] of getData().entries()) {
  60 + if (
  61 + row.name === null ||
  62 + row.name === undefined ||
  63 + (typeof row.name === 'string' && String(row.name).trim() === '') ||
  64 + (Array.isArray(row.name) && row.name.length === 0)
  65 + ) {
  66 + ElMessage.error(`第 ${index + 1} 行:名字不能为空`);
  67 + return false;
  68 + }
  69 + if (
  70 + row.description === null ||
  71 + row.description === undefined ||
  72 + (typeof row.description === 'string' && String(row.description).trim() === '') ||
  73 + (Array.isArray(row.description) && row.description.length === 0)
  74 + ) {
  75 + ElMessage.error(`第 ${index + 1} 行:简介不能为空`);
  76 + return false;
  77 + }
  78 + if (
  79 + row.birthday === null ||
  80 + row.birthday === undefined ||
  81 + (typeof row.birthday === 'string' && String(row.birthday).trim() === '') ||
  82 + (Array.isArray(row.birthday) && row.birthday.length === 0)
  83 + ) {
  84 + ElMessage.error(`第 ${index + 1} 行:出生日期不能为空`);
  85 + return false;
  86 + }
  87 + if (
  88 + row.sex === null ||
  89 + row.sex === undefined ||
  90 + (typeof row.sex === 'string' && String(row.sex).trim() === '') ||
  91 + (Array.isArray(row.sex) && row.sex.length === 0)
  92 + ) {
  93 + ElMessage.error(`第 ${index + 1} 行:性别不能为空`);
  94 + return false;
  95 + }
  96 + if (
  97 + row.enabled === null ||
  98 + row.enabled === undefined ||
  99 + (typeof row.enabled === 'string' && String(row.enabled).trim() === '') ||
  100 + (Array.isArray(row.enabled) && row.enabled.length === 0)
  101 + ) {
  102 + ElMessage.error(`第 ${index + 1} 行:是否有效不能为空`);
  103 + return false;
  104 + }
  105 + if (
  106 + row.avatar === null ||
  107 + row.avatar === undefined ||
  108 + (typeof row.avatar === 'string' && String(row.avatar).trim() === '') ||
  109 + (Array.isArray(row.avatar) && row.avatar.length === 0)
  110 + ) {
  111 + ElMessage.error(`第 ${index + 1} 行:头像不能为空`);
  112 + return false;
  113 + }
  114 + if (
  115 + row.memo === null ||
  116 + row.memo === undefined ||
  117 + (typeof row.memo === 'string' && String(row.memo).trim() === '') ||
  118 + (Array.isArray(row.memo) && row.memo.length === 0)
  119 + ) {
  120 + ElMessage.error(`第 ${index + 1} 行:备注不能为空`);
  121 + return false;
  122 + }
  123 + }
  124 + return true;
  125 +}
  126 +
  127 +defineExpose({ getData, validate });
73 128  
74 129 /** 监听主表的关联字段的变化,加载对应的子表数据 */
75 130 watch(
76 131 () => props.studentId,
77 132 async (val) => {
  133 + await nextTick();
78 134 if (!val) {
  135 + await gridApi.grid.loadData([]);
79 136 return;
80 137 }
81   - await nextTick();
82 138 await gridApi.grid.loadData(await getStudentContactListByStudentId(props.studentId!));
83 139 },
84 140 { immediate: true },
... ... @@ -88,13 +144,13 @@ watch(
88 144 <template>
89 145 <Grid class="mx-4">
90 146 <template #name="{ row }">
91   - <el-input v-model="row.name" />
  147 + <ElInput v-model="row.name" />
92 148 </template>
93 149 <template #description="{ row }">
94   - <el-input v-model="row.description" type="textarea" />
  150 + <ElInput v-model="row.description" type="textarea" />
95 151 </template>
96 152 <template #birthday="{ row }">
97   - <el-date-picker
  153 + <ElDatePicker
98 154 v-model="row.birthday"
99 155 type="datetime"
100 156 value-format="x"
... ... @@ -102,16 +158,16 @@ watch(
102 158 />
103 159 </template>
104 160 <template #sex="{ row, column }">
105   - <el-select v-model="row.sex" class="w-full">
106   - <el-option v-for="option in column.params.options" :key="option.value" :value="option.value" :label="option.label" />
107   - </el-select>
  161 + <ElSelect v-model="row.sex" class="w-full">
  162 + <ElOption v-for="option in column.params.options" :key="option.value" :value="option.value" :label="option.label" />
  163 + </ElSelect>
108 164 </template>
109 165 <template #enabled="{ row, column }">
110   - <el-radio-group v-model="row.enabled">
111   - <el-radio v-for="option in column.params.options" :key="option.value" :label="option.value">
  166 + <ElRadioGroup v-model="row.enabled">
  167 + <ElRadio v-for="option in column.params.options" :key="option.value" :value="option.value">
112 168 {{ option.label }}
113   - </el-radio>
114   - </el-radio-group>
  169 + </ElRadio>
  170 + </ElRadioGroup>
115 171 </template>
116 172 <template #avatar="{ row }">
117 173 <ImageUpload v-model="row.avatar" />
... ... @@ -120,12 +176,25 @@ watch(
120 176 <FileUpload v-model="row.video" />
121 177 </template>
122 178 <template #memo="{ row }">
123   - <el-input v-model="row.memo" type="textarea" />
  179 + <ElInput v-model="row.memo" type="textarea" />
124 180 </template>
  181 + <template #actions="{ row }">
  182 + <TableAction
  183 + :actions="[
  184 + {
  185 + label: $t('common.delete'),
  186 + type: 'danger', link: true,
  187 + icon: ACTION_ICON.DELETE,
  188 + auth: ['infra:student:delete'],
  189 + onClick: onDelete.bind(null, row),
  190 + },
  191 + ]"
  192 + />
  193 + </template>
125 194 </Grid>
126 195 <div class="flex justify-center -mt-4">
127   - <el-button :icon="Plus" type="primary" plain @click="onAdd" v-access:code="['infra:student:create']">
  196 + <ElButton :icon="Plus" type="primary" plain @click="onAdd" v-access:code="['infra:student:create']">
128 197 {{ $t('ui.actionTitle.create', ['学生联系人']) }}
129   - </el-button>
  198 + </ElButton>
130 199 </div>
131 200 </template>
132 201 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_inner/vue/student-teacher-form
... ... @@ -39,10 +39,11 @@ defineExpose({
39 39 watch(
40 40 () => props.studentId,
41 41 async (val) => {
  42 + await nextTick();
  43 + await formApi.resetForm();
42 44 if (!val) {
43 45 return;
44 46 }
45   - await nextTick();
46 47 await formApi.setValues(await getStudentTeacherByStudentId(props.studentId!));
47 48 },
48 49 { immediate: true },
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_normal/ts/data
... ... @@ -42,7 +42,7 @@ export function useFormSchema(): VbenFormSchema[] {
42 42 rules: 'required',
43 43 component: 'DatePicker',
44 44 componentProps: {
45   - showTime: true,
  45 + type: 'datetime',
46 46 format: 'YYYY-MM-DD HH:mm:ss',
47 47 valueFormat: 'x',
48 48 class: '!w-full',
... ... @@ -65,8 +65,7 @@ export function useFormSchema(): VbenFormSchema[] {
65 65 component: 'RadioGroup',
66 66 componentProps: {
67 67 options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
68   - buttonStyle: 'solid',
69   - optionType: 'button',
  68 + isButton: true,
70 69 },
71 70 },
72 71 {
... ... @@ -269,7 +268,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
269 268 minWidth: 120,
270 269 slots: { default: 'sex' },
271 270 params: {
272   - options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'boolean'),
  271 + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'),
273 272 },
274 273 },
275 274 {
... ... @@ -345,7 +344,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
345 344 rules: 'required',
346 345 component: 'DatePicker',
347 346 componentProps: {
348   - showTime: true,
  347 + type: 'datetime',
349 348 format: 'YYYY-MM-DD HH:mm:ss',
350 349 valueFormat: 'x',
351 350 class: '!w-full',
... ... @@ -368,8 +367,7 @@ export function useGridColumns(): VxeTableGridOptions&lt;InfraStudentApi.Student&gt;[&#39;
368 367 component: 'RadioGroup',
369 368 componentProps: {
370 369 options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
371   - buttonStyle: 'solid',
372   - optionType: 'button',
  370 + isButton: true,
373 371 },
374 372 },
375 373 {
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_normal/vue/form
... ... @@ -5,7 +5,7 @@ import { computed, ref } from &#39;vue&#39;;
5 5  
6 6 import { useVbenModal } from '@vben/common-ui';
7 7  
8   -import { ElMessage } from 'element-plus';
  8 +import { ElMessage, ElTabPane, ElTabs } from 'element-plus';
9 9  
10 10 import { useVbenForm } from '#/adapter/form';
11 11 import { createStudent, getStudent, updateStudent } from '#/api/infra/demo';
... ... @@ -48,7 +48,12 @@ const [Modal, modalApi] = useVbenModal({
48 48 return;
49 49 }
50 50 // 校验子表单
51   - const studentTeacherValid = await studentTeacherFormRef.value?.validate();
  51 + const studentContactValid = await studentContactFormRef.value?.validate();
  52 + if (!studentContactValid) {
  53 + subTabsName.value = 'studentContact';
  54 + return;
  55 + }
  56 + const studentTeacherValid = await studentTeacherFormRef.value?.validate();
52 57 if (!studentTeacherValid) {
53 58 subTabsName.value = 'studentTeacher';
54 59 return;
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_normal/vue/student-contact-form
... ... @@ -6,10 +6,9 @@
6 6 import { $t } from '#/locales';
7 7  
8 8 import { Plus } from "@vben/icons";
9   -import { ElButton, ElTabs, ElTabPane, ElCheckbox, ElInput, ElSelect, ElOption, ElRadioGroup, ElCheckboxGroup, ElDatePicker } from 'element-plus';
  9 +import { ElButton, ElCheckbox, ElCheckboxGroup, ElDatePicker, ElInput, ElMessage, ElOption, ElRadio, ElRadioGroup, ElSelect, ElTabPane, ElTabs } from 'element-plus';
10 10 import { ImageUpload, FileUpload } from "#/components/upload";
11   -import type { OnActionClickParams } from '#/adapter/vxe-table';
12   -import { useVbenVxeGrid } from '#/adapter/vxe-table';
  11 +import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
13 12 import { useStudentContactGridEditColumns } from '../data';
14 13 import { getStudentContactListByStudentId } from '#/api/infra/demo';
15 14  
... ... @@ -17,22 +16,9 @@ const props = defineProps&lt;{
17 16 studentId?: number // 学生编号(主表的关联字段)
18 17 }>()
19 18  
20   - /** 表格操作按钮的回调函数 */
21   -function onActionClick({
22   - code,
23   - row,
24   -}: OnActionClickParams<InfraStudentApi.StudentContact>) {
25   - switch (code) {
26   - case 'delete': {
27   - onDelete(row);
28   - break;
29   - }
30   - }
31   -}
32   -
33   -const [Grid, gridApi] = useVbenVxeGrid({
  19 + const [Grid, gridApi] = useVbenVxeGrid({
34 20 gridOptions: {
35   - columns: useStudentContactGridEditColumns(onActionClick),
  21 + columns: useStudentContactGridEditColumns(),
36 22 border: true,
37 23 showOverflow: true,
38 24 autoResize: true,
... ... @@ -51,7 +37,7 @@ gridOptions: {
51 37  
52 38 /** 添加学生联系人 */
53 39 const onAdd = async () => {
54   - await gridApi.grid.insertAt({} as StudentApi.StudentContact, -1);
  40 + await gridApi.grid.insertAt({} as InfraStudentApi.StudentContact, -1);
55 41 }
56 42  
57 43 /** 删除学生联系人 */
... ... @@ -59,26 +45,96 @@ const onDelete = async (row: InfraStudentApi.StudentContact) =&gt; {
59 45 await gridApi.grid.remove(row);
60 46 }
61 47  
62   -/** 提供获取表格数据的方法供父组件调用 */
63   -defineExpose({
64   - getData: (): InfraStudentApi.StudentContact[] => {
65   - const data = gridApi.grid.getData() as InfraStudentApi.StudentContact[];
66   - const removeRecords = gridApi.grid.getRemoveRecords() as InfraStudentApi.StudentContact[];
67   - const insertRecords = gridApi.grid.getInsertRecords() as InfraStudentApi.StudentContact[];
68   - return data
69   - .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
70   - .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
71   - },
72   -});
  48 +/** 获取当前子表数据,新增行不提交表格生成的临时编号 */
  49 +function getData(): InfraStudentApi.StudentContact[] {
  50 + const { fullData } = gridApi.grid.getTableData();
  51 + const inserted = new Set(gridApi.grid.getInsertRecords());
  52 + return fullData.map((row) =>
  53 + inserted.has(row) ? { ...row, id: undefined } : { ...row },
  54 + );
  55 +}
  56 +
  57 +/** 校验子表必填字段 */
  58 +function validate(): boolean {
  59 + for (const [index, row] of getData().entries()) {
  60 + if (
  61 + row.name === null ||
  62 + row.name === undefined ||
  63 + (typeof row.name === 'string' && String(row.name).trim() === '') ||
  64 + (Array.isArray(row.name) && row.name.length === 0)
  65 + ) {
  66 + ElMessage.error(`第 ${index + 1} 行:名字不能为空`);
  67 + return false;
  68 + }
  69 + if (
  70 + row.description === null ||
  71 + row.description === undefined ||
  72 + (typeof row.description === 'string' && String(row.description).trim() === '') ||
  73 + (Array.isArray(row.description) && row.description.length === 0)
  74 + ) {
  75 + ElMessage.error(`第 ${index + 1} 行:简介不能为空`);
  76 + return false;
  77 + }
  78 + if (
  79 + row.birthday === null ||
  80 + row.birthday === undefined ||
  81 + (typeof row.birthday === 'string' && String(row.birthday).trim() === '') ||
  82 + (Array.isArray(row.birthday) && row.birthday.length === 0)
  83 + ) {
  84 + ElMessage.error(`第 ${index + 1} 行:出生日期不能为空`);
  85 + return false;
  86 + }
  87 + if (
  88 + row.sex === null ||
  89 + row.sex === undefined ||
  90 + (typeof row.sex === 'string' && String(row.sex).trim() === '') ||
  91 + (Array.isArray(row.sex) && row.sex.length === 0)
  92 + ) {
  93 + ElMessage.error(`第 ${index + 1} 行:性别不能为空`);
  94 + return false;
  95 + }
  96 + if (
  97 + row.enabled === null ||
  98 + row.enabled === undefined ||
  99 + (typeof row.enabled === 'string' && String(row.enabled).trim() === '') ||
  100 + (Array.isArray(row.enabled) && row.enabled.length === 0)
  101 + ) {
  102 + ElMessage.error(`第 ${index + 1} 行:是否有效不能为空`);
  103 + return false;
  104 + }
  105 + if (
  106 + row.avatar === null ||
  107 + row.avatar === undefined ||
  108 + (typeof row.avatar === 'string' && String(row.avatar).trim() === '') ||
  109 + (Array.isArray(row.avatar) && row.avatar.length === 0)
  110 + ) {
  111 + ElMessage.error(`第 ${index + 1} 行:头像不能为空`);
  112 + return false;
  113 + }
  114 + if (
  115 + row.memo === null ||
  116 + row.memo === undefined ||
  117 + (typeof row.memo === 'string' && String(row.memo).trim() === '') ||
  118 + (Array.isArray(row.memo) && row.memo.length === 0)
  119 + ) {
  120 + ElMessage.error(`第 ${index + 1} 行:备注不能为空`);
  121 + return false;
  122 + }
  123 + }
  124 + return true;
  125 +}
  126 +
  127 +defineExpose({ getData, validate });
73 128  
74 129 /** 监听主表的关联字段的变化,加载对应的子表数据 */
75 130 watch(
76 131 () => props.studentId,
77 132 async (val) => {
  133 + await nextTick();
78 134 if (!val) {
  135 + await gridApi.grid.loadData([]);
79 136 return;
80 137 }
81   - await nextTick();
82 138 await gridApi.grid.loadData(await getStudentContactListByStudentId(props.studentId!));
83 139 },
84 140 { immediate: true },
... ... @@ -88,13 +144,13 @@ watch(
88 144 <template>
89 145 <Grid class="mx-4">
90 146 <template #name="{ row }">
91   - <el-input v-model="row.name" />
  147 + <ElInput v-model="row.name" />
92 148 </template>
93 149 <template #description="{ row }">
94   - <el-input v-model="row.description" type="textarea" />
  150 + <ElInput v-model="row.description" type="textarea" />
95 151 </template>
96 152 <template #birthday="{ row }">
97   - <el-date-picker
  153 + <ElDatePicker
98 154 v-model="row.birthday"
99 155 type="datetime"
100 156 value-format="x"
... ... @@ -102,16 +158,16 @@ watch(
102 158 />
103 159 </template>
104 160 <template #sex="{ row, column }">
105   - <el-select v-model="row.sex" class="w-full">
106   - <el-option v-for="option in column.params.options" :key="option.value" :value="option.value" :label="option.label" />
107   - </el-select>
  161 + <ElSelect v-model="row.sex" class="w-full">
  162 + <ElOption v-for="option in column.params.options" :key="option.value" :value="option.value" :label="option.label" />
  163 + </ElSelect>
108 164 </template>
109 165 <template #enabled="{ row, column }">
110   - <el-radio-group v-model="row.enabled">
111   - <el-radio v-for="option in column.params.options" :key="option.value" :label="option.value">
  166 + <ElRadioGroup v-model="row.enabled">
  167 + <ElRadio v-for="option in column.params.options" :key="option.value" :value="option.value">
112 168 {{ option.label }}
113   - </el-radio>
114   - </el-radio-group>
  169 + </ElRadio>
  170 + </ElRadioGroup>
115 171 </template>
116 172 <template #avatar="{ row }">
117 173 <ImageUpload v-model="row.avatar" />
... ... @@ -120,12 +176,25 @@ watch(
120 176 <FileUpload v-model="row.video" />
121 177 </template>
122 178 <template #memo="{ row }">
123   - <el-input v-model="row.memo" type="textarea" />
  179 + <ElInput v-model="row.memo" type="textarea" />
124 180 </template>
  181 + <template #actions="{ row }">
  182 + <TableAction
  183 + :actions="[
  184 + {
  185 + label: $t('common.delete'),
  186 + type: 'danger', link: true,
  187 + icon: ACTION_ICON.DELETE,
  188 + auth: ['infra:student:delete'],
  189 + onClick: onDelete.bind(null, row),
  190 + },
  191 + ]"
  192 + />
  193 + </template>
125 194 </Grid>
126 195 <div class="flex justify-center -mt-4">
127   - <el-button :icon="Plus" type="primary" plain @click="onAdd" v-access:code="['infra:student:create']">
  196 + <ElButton :icon="Plus" type="primary" plain @click="onAdd" v-access:code="['infra:student:create']">
128 197 {{ $t('ui.actionTitle.create', ['学生联系人']) }}
129   - </el-button>
  198 + </ElButton>
130 199 </div>
131 200 </template>
132 201 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_master_normal/vue/student-teacher-form
... ... @@ -39,10 +39,11 @@ defineExpose({
39 39 watch(
40 40 () => props.studentId,
41 41 async (val) => {
  42 + await nextTick();
  43 + await formApi.resetForm();
42 44 if (!val) {
43 45 return;
44 46 }
45   - await nextTick();
46 47 await formApi.setValues(await getStudentTeacherByStudentId(props.studentId!));
47 48 },
48 49 { immediate: true },
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_one/ts/data
... ... @@ -42,7 +42,7 @@ export function useFormSchema(): VbenFormSchema[] {
42 42 rules: 'required',
43 43 component: 'DatePicker',
44 44 componentProps: {
45   - showTime: true,
  45 + type: 'datetime',
46 46 format: 'YYYY-MM-DD HH:mm:ss',
47 47 valueFormat: 'x',
48 48 class: '!w-full',
... ... @@ -65,8 +65,7 @@ export function useFormSchema(): VbenFormSchema[] {
65 65 component: 'RadioGroup',
66 66 componentProps: {
67 67 options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
68   - buttonStyle: 'solid',
69   - optionType: 'button',
  68 + isButton: true,
70 69 },
71 70 },
72 71 {
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_one_importEnable/ts/data
... ... @@ -42,7 +42,7 @@ export function useFormSchema(): VbenFormSchema[] {
42 42 rules: 'required',
43 43 component: 'DatePicker',
44 44 componentProps: {
45   - showTime: true,
  45 + type: 'datetime',
46 46 format: 'YYYY-MM-DD HH:mm:ss',
47 47 valueFormat: 'x',
48 48 class: '!w-full',
... ... @@ -65,8 +65,7 @@ export function useFormSchema(): VbenFormSchema[] {
65 65 component: 'RadioGroup',
66 66 componentProps: {
67 67 options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
68   - buttonStyle: 'solid',
69   - optionType: 'button',
  68 + isButton: true,
70 69 },
71 70 },
72 71 {
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_one_importEnable/vue/import-form
... ... @@ -50,6 +50,16 @@ function handleChange(file: any) {
50 50 }
51 51 }
52 52  
  53 +/** 移除文件 */
  54 +function handleRemove() {
  55 + formApi.setFieldValue('file', undefined);
  56 +}
  57 +
  58 +/** 超出文件数量 */
  59 +function handleExceed() {
  60 + ElMessage.warning('只能选择一个文件,请先移除已选文件');
  61 +}
  62 +
53 63 /** 下载模板 */
54 64 async function handleDownload() {
55 65 const data = await importStudentTemplate();
... ... @@ -66,6 +76,8 @@ async function handleDownload() {
66 76 :limit="1"
67 77 accept=".xls,.xlsx"
68 78 :on-change="handleChange"
  79 + :on-remove="handleRemove"
  80 + :on-exceed="handleExceed"
69 81 :auto-upload="false"
70 82 >
71 83 <ElButton type="primary"> 选择 Excel 文件 </ElButton>
... ...
yudao-module-infra/src/test/resources/codegen/vben5_ele_schema_tree/ts/data
... ... @@ -32,13 +32,14 @@ export function useFormSchema(): VbenFormSchema[] {
32 32 id: 0,
33 33 name: '顶级分类',
34 34 });
35   - return handleTree(data);
  35 + return handleTree(data, 'id', 'parentId');
36 36 },
37 37 labelField: 'name',
38 38 valueField: 'id',
39 39 childrenField: 'children',
40 40 placeholder: '请选择上级分类',
41   - treeDefaultExpandAll: true,
  41 + defaultExpandAll: true,
  42 + checkStrictly: true,
42 43 },
43 44 rules: 'selectRequired',
44 45 },
... ...
yudao-module-infra/src/test/resources/codegen/vue2_master_erp/vue/StudentContactList
... ... @@ -64,7 +64,7 @@
64 64 </el-table-column>
65 65 </el-table>
66 66 <!-- 分页组件 -->
67   - <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  67 + <Pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
68 68 @pagination="getList"/>
69 69 <!-- 对话框(添加 / 修改) -->
70 70 <StudentContactForm ref="formRef" @success="getList" />
... ... @@ -79,9 +79,13 @@
79 79 components: {
80 80 StudentContactForm
81 81 },
82   - props:[
83   - 'studentId'
84   - ],// 学生编号(主表的关联字段)
  82 + props: {
  83 + // 学生编号(主表的关联字段)
  84 + studentId: {
  85 + type: Number,
  86 + default: undefined
  87 + }
  88 + },
85 89 data() {
86 90 return {
87 91 // 遮罩层
... ...
yudao-module-infra/src/test/resources/codegen/vue2_master_erp/vue/StudentTeacherList
... ... @@ -64,7 +64,7 @@
64 64 </el-table-column>
65 65 </el-table>
66 66 <!-- 分页组件 -->
67   - <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  67 + <Pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
68 68 @pagination="getList"/>
69 69 <!-- 对话框(添加 / 修改) -->
70 70 <StudentTeacherForm ref="formRef" @success="getList" />
... ... @@ -79,9 +79,13 @@
79 79 components: {
80 80 StudentTeacherForm
81 81 },
82   - props:[
83   - 'studentId'
84   - ],// 学生编号(主表的关联字段)
  82 + props: {
  83 + // 学生编号(主表的关联字段)
  84 + studentId: {
  85 + type: Number,
  86 + default: undefined
  87 + }
  88 + },
85 89 data() {
86 90 return {
87 91 // 遮罩层
... ...
yudao-module-infra/src/test/resources/codegen/vue2_master_erp/vue/index
... ... @@ -53,7 +53,7 @@
53 53 批量删除
54 54 </el-button>
55 55 </el-col>
56   - <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  56 + <RightToolbar :showSearch.sync="showSearch" @queryTable="getList"></RightToolbar>
57 57 </el-row>
58 58  
59 59 <el-table
... ... @@ -127,7 +127,7 @@
127 127 </el-table-column>
128 128 </el-table>
129 129 <!-- 分页组件 -->
130   - <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  130 + <Pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
131 131 @pagination="getList"/>
132 132 <!-- 对话框(添加 / 修改) -->
133 133 <StudentForm ref="formRef" @success="getList" />
... ...
yudao-module-infra/src/test/resources/codegen/vue2_master_inner/vue/StudentContactForm
... ... @@ -97,9 +97,13 @@
97 97 FileUpload,
98 98 Editor,
99 99 },
100   - props:[
101   - 'studentId'
102   - ],// 学生编号(主表的关联字段)
  100 + props: {
  101 + // 学生编号(主表的关联字段)
  102 + studentId: {
  103 + type: Number,
  104 + default: undefined
  105 + }
  106 + },
103 107 data() {
104 108 return {
105 109 // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
... ...
yudao-module-infra/src/test/resources/codegen/vue2_master_inner/vue/StudentContactList
... ... @@ -48,9 +48,13 @@
48 48 import * as StudentApi from '@/api/infra/demo';
49 49 export default {
50 50 name: "StudentContactList",
51   - props:[
52   - 'studentId'
53   - ],// 学生编号(主表的关联字段)
  51 + props: {
  52 + // 学生编号(主表的关联字段)
  53 + studentId: {
  54 + type: Number,
  55 + default: undefined
  56 + }
  57 + },
54 58 data() {
55 59 return {
56 60 // 遮罩层
... ...