Commit 015565cc9a577f6e2174f08d8a683f2574d41d53

Authored by puhui999
1 parent 69486939

【功能新增】INFRA: vben next 树表代码生成

yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
... ... @@ -153,6 +153,15 @@ public class CodegenEngine {
153 153 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
154 154 .put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("api/api.ts"),
155 155 vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
  156 + // 主子表模板配置 - Vue3 vben5 schema 模版
  157 + //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/master_slave_data.ts"),
  158 + // vue3FilePath("views/${table.moduleName}/${table.businessName}/data.ts"))
  159 + //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/master_slave_index.vue"),
  160 + // vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
  161 + //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/modules/master_slave_form.vue"),
  162 + // vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
  163 + //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/modules/sub_table.vue"),
  164 + // vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/sub_table.vue"))
156 165 .build();
157 166  
158 167 @Resource
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/api/api.ts.vm
... ... @@ -17,6 +17,9 @@ export namespace ${simpleClassName}Api {
17 17 #end
18 18 #end
19 19 #end
  20 +#if ( $table.templateType == 2 )
  21 + children?: ${simpleClassName}[];
  22 +#end
20 23 }
21 24 }
22 25  
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/data.ts.vm
  1 +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
1 2 import type { VbenFormSchema } from '#/adapter/form';
2   -import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
  3 +import type { OnActionClickFn } from '#/adapter/vxe-table';
3 4 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
4 5  
5 6 import { z } from '#/adapter/form';
6   -import { CommonStatusEnum } from '#/utils/constants';
  7 +#if(${table.templateType} == 2)## 树表需要导入这些
  8 +import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  9 +import { handleTree } from '#/utils/tree';
  10 +#end
7 11 import { DICT_TYPE, getDictOptions } from '#/utils/dict';
8   -import { getRangePickerDefaultProps } from '#/utils/date';
  12 +import { CommonStatusEnum } from '#/utils/constants';
9 13 import { useAccess } from '@vben/access';
10 14  
11 15 const { hasAccessByCodes } = useAccess();
... ... @@ -21,9 +25,34 @@ export function useFormSchema(): VbenFormSchema[] {
21 25 show: () => false,
22 26 },
23 27 },
  28 +#if(${table.templateType} == 2)## 树表特有字段:上级
  29 + {
  30 + fieldName: '${treeParentColumn.javaField}',
  31 + label: '上级${table.classComment}',
  32 + component: 'ApiTreeSelect',
  33 + componentProps: {
  34 + allowClear: true,
  35 + api: async () => {
  36 + const data = await get${simpleClassName}List({});
  37 + data.unshift({
  38 + id: 0,
  39 + ${treeNameColumn.javaField}: '顶级${table.classComment}',
  40 + });
  41 + return handleTree(data);
  42 + },
  43 + class: 'w-full',
  44 + labelField: '${treeNameColumn.javaField}',
  45 + valueField: 'id',
  46 + childrenField: 'children',
  47 + placeholder: '请选择上级${table.classComment}',
  48 + treeDefaultExpandAll: true,
  49 + },
  50 + rules: 'selectRequired',
  51 + },
  52 +#end
24 53 #foreach($column in $columns)
25 54 #if ($column.createOperation || $column.updateOperation)
26   -#if (!$column.primaryKey)## 忽略主键,不用在表单里
  55 +#if (!$column.primaryKey && ($table.templateType != 2 || ($table.templateType == 2 && $column.id != $treeParentColumn.id)))## 树表中已经添加了父ID字段,这里排除
27 56 #set ($dictType = $column.dictType)
28 57 #set ($javaType = $column.javaType)
29 58 #set ($javaField = $column.javaField)
... ... @@ -69,6 +98,7 @@ export function useFormSchema(): VbenFormSchema[] {
69 98 options: [],
70 99 #end
71 100 placeholder: '请选择${comment}',
  101 + class: 'w-full',
72 102 },
73 103 #elseif($column.htmlType == "checkbox")## 多选框
74 104 component: 'Checkbox',
... ... @@ -102,6 +132,14 @@ export function useFormSchema(): VbenFormSchema[] {
102 132 componentProps: {
103 133 placeholder: '请输入${comment}',
104 134 },
  135 + #elseif($column.htmlType == "inputNumber")## 数字输入框
  136 + component: 'InputNumber',
  137 + componentProps: {
  138 + min: 0,
  139 + class: 'w-full',
  140 + controlsPosition: 'right',
  141 + placeholder: '请输入${comment}',
  142 + },
105 143 #end
106 144 },
107 145 #end
... ... @@ -159,7 +197,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
159 197 #elseif($column.htmlType == "datetime")
160 198 component: 'RangePicker',
161 199 componentProps: {
162   - ...getRangePickerDefaultProps(),
163 200 allowClear: true,
164 201 },
165 202 #end
... ... @@ -170,9 +207,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
170 207 }
171 208  
172 209 /** 列表的字段 */
173   -export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>(
174   - onActionClick: OnActionClickFn<T>,
175   -): VxeTableGridOptions['columns'] {
  210 +export function useGridColumns(
  211 + onActionClick?: OnActionClickFn<${simpleClassName}Api.${simpleClassName}>,
  212 +): VxeTableGridOptions<${simpleClassName}Api.${simpleClassName}>['columns'] {
176 213 return [
177 214 #foreach($column in $columns)
178 215 #if ($column.listOperationResult)
... ... @@ -191,15 +228,20 @@ export function useGridColumns&lt;T = ${simpleClassName}Api.${simpleClassName}&gt;(
191 228 props: { type: DICT_TYPE.$dictType.toUpperCase() },
192 229 },
193 230 #end
  231 + #if (${table.templateType} == 2 && $column.id == $treeNameColumn.id)## 树表特有:标记树节点列
  232 + treeNode: true,
  233 + #end
194 234 },
195 235 #end
196 236 #end
197 237 {
198 238 field: 'operation',
199 239 title: '操作',
200   - minWidth: 180,
201   - align: 'center',
  240 + minWidth: 200,
  241 + align: 'right',
202 242 fixed: 'right',
  243 + headerAlign: 'center',
  244 + showOverflow: false,
203 245 cellRender: {
204 246 attrs: {
205 247 nameField: '${columns[0].javaField}',
... ... @@ -208,6 +250,13 @@ export function useGridColumns&lt;T = ${simpleClassName}Api.${simpleClassName}&gt;(
208 250 },
209 251 name: 'CellOperation',
210 252 options: [
  253 +#if (${table.templateType} == 2)## 树表特有操作
  254 + {
  255 + code: 'add_child',
  256 + text: '新增下级',
  257 + show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:create']),
  258 + },
  259 +#end
211 260 {
212 261 code: 'edit',
213 262 show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:update']),
... ... @@ -215,7 +264,12 @@ export function useGridColumns&lt;T = ${simpleClassName}Api.${simpleClassName}&gt;(
215 264 {
216 265 code: 'delete',
217 266 show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:delete']),
218   - }
  267 +#if (${table.templateType} == 2)## 树表禁止删除带有子节点的数据
  268 + disabled: (row: ${simpleClassName}Api.${simpleClassName}) => {
  269 + return !!(row.children && row.children.length > 0);
  270 + },
  271 +#end
  272 + },
219 273 ],
220 274 },
221 275 },
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/form.vue.vm
... ... @@ -4,8 +4,8 @@ import type { ${simpleClassName}Api } from &#39;#/api/${table.moduleName}/${simpleCl
4 4 import { useVbenModal } from '@vben/common-ui';
5 5 import { message } from 'ant-design-vue';
6 6  
7   -import { $t } from '#/locales';
8 7 import { computed, ref } from 'vue';
  8 +import { $t } from '#/locales';
9 9 import { useVbenForm } from '#/adapter/form';
10 10 import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
11 11  
... ... @@ -13,11 +13,25 @@ import { useFormSchema } from &#39;../data&#39;;
13 13  
14 14 const emit = defineEmits(['success']);
15 15 const formData = ref<${simpleClassName}Api.${simpleClassName}>();
  16 +#if (${table.templateType} == 2)## 树表特有:父ID处理
  17 +// 新增下级时的父级ID
  18 +const parentId = ref<number>();
  19 +
  20 +const getTitle = computed(() => {
  21 + if (formData.value?.id) {
  22 + return $t('ui.actionTitle.edit', ['${table.classComment}']);
  23 + }
  24 + return parentId.value
  25 + ? $t('ui.actionTitle.create', ['下级${table.classComment}'])
  26 + : $t('ui.actionTitle.create', ['${table.classComment}']);
  27 +});
  28 +#else## 标准表标题
16 29 const getTitle = computed(() => {
17 30 return formData.value?.id
18 31 ? $t('ui.actionTitle.edit', ['${table.classComment}'])
19 32 : $t('ui.actionTitle.create', ['${table.classComment}']);
20 33 });
  34 +#end
21 35  
22 36 const [Form, formApi] = useVbenForm({
23 37 layout: 'horizontal',
... ... @@ -55,17 +69,47 @@ const [Modal, modalApi] = useVbenModal({
55 69 return;
56 70 }
57 71 // 加载数据
  72 +#if (${table.templateType} == 2)## 树表处理传入的父ID
  73 + let data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
  74 +#else## 标准表直接获取
58 75 const data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
59   - if (!data || !data.id) {
  76 +#end
  77 + if (!data) {
60 78 return;
61 79 }
62   - modalApi.lock();
63   - try {
64   - formData.value = await get${simpleClassName}(data.id as number);
65   - // 设置到 values
  80 +
  81 +#if (${table.templateType} == 2)## 树表特有:处理新增下级的情况
  82 + // 处理新增下级的情况
  83 + if (!data.id && data.${treeParentColumn.javaField}) {
  84 + parentId.value = data.${treeParentColumn.javaField};
  85 + formData.value = { ${treeParentColumn.javaField}: parentId.value } as ${simpleClassName}Api.${simpleClassName};
66 86 await formApi.setValues(formData.value);
67   - } finally {
68   - modalApi.lock(false);
  87 + return;
  88 + }
  89 +#end
  90 +
  91 + if (data.id) {
  92 + // 编辑
  93 + modalApi.lock();
  94 + try {
  95 +#if (${table.templateType} == 2)## 树表获取数据后重新赋值
  96 + data = await get${simpleClassName}(data.id);
  97 + formData.value = data;
  98 +#else## 标准表设置表单数据
  99 + formData.value = await get${simpleClassName}(data.id as number);
  100 +#end
  101 + await formApi.setValues(formData.value);
  102 + } finally {
  103 + modalApi.lock(false);
  104 + }
  105 + } else {
  106 + // 新增
  107 +#if (${table.templateType} == 2)## 树表特有:设置顶级ID
  108 + formData.value = { ${treeParentColumn.javaField}: 0 } as ${simpleClassName}Api.${simpleClassName};
  109 +#else## 标准表:设置空值
  110 + formData.value = data;
  111 +#end
  112 + await formApi.setValues(formData.value || {});
69 113 }
70 114 },
71 115 });
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/index.vue.vm
... ... @@ -8,9 +8,15 @@ import { Download, Plus } from &#39;@vben/icons&#39;;
8 8 import Form from './modules/form.vue';
9 9 import { DocAlert } from '#/components/doc-alert';
10 10  
  11 +import { ref } from 'vue';
11 12 import { $t } from '#/locales';
12 13 import { useVbenVxeGrid } from '#/adapter/vxe-table';
  14 +#if (${table.templateType} == 2)## 树表接口
  15 +import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  16 +import { handleTree } from '#/utils/tree';
  17 +#else## 标准表接口
13 18 import { get${simpleClassName}Page, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
  19 +#end
14 20 import { downloadByData } from '#/utils/download';
15 21  
16 22 import { useGridColumns, useGridFormSchema } from './data';
... ... @@ -20,6 +26,15 @@ const [FormModal, formModalApi] = useVbenModal({
20 26 destroyOnClose: true,
21 27 });
22 28  
  29 +#if (${table.templateType} == 2)## 树表特有:控制表格展开收缩
  30 +/** 切换树形展开/收缩状态 */
  31 +const isExpanded = ref(true);
  32 +function toggleExpand() {
  33 + isExpanded.value = !isExpanded.value;
  34 + gridApi.grid.setAllTreeExpand(isExpanded.value);
  35 +}
  36 +#end
  37 +
23 38 /** 刷新表格 */
24 39 function onRefresh() {
25 40 gridApi.query();
... ... @@ -41,17 +56,24 @@ function onEdit(row: ${simpleClassName}Api.${simpleClassName}) {
41 56 formModalApi.setData(row).open();
42 57 }
43 58  
  59 +#if (${table.templateType} == 2)## 树表特有:新增下级
  60 +/** 新增下级${table.classComment} */
  61 +function onAddChild(row: ${simpleClassName}Api.${simpleClassName}) {
  62 + formModalApi.setData({ ${treeParentColumn.javaField}: row.id }).open();
  63 +}
  64 +#end
  65 +
44 66 /** 删除${table.classComment} */
45 67 async function onDelete(row: ${simpleClassName}Api.${simpleClassName}) {
46 68 const hideLoading = message.loading({
47   - content: $t('ui.actionMessage.deleting', [row.${columns[0].javaField}]),
  69 + content: $t('ui.actionMessage.deleting', [row.id]),
48 70 duration: 0,
49 71 key: 'action_process_msg',
50 72 });
51 73 try {
52 74 await delete${simpleClassName}(row.id as number);
53 75 message.success({
54   - content: $t('ui.actionMessage.deleteSuccess', [row.${columns[0].javaField}]),
  76 + content: $t('ui.actionMessage.deleteSuccess', [row.id]),
55 77 key: 'action_process_msg',
56 78 });
57 79 onRefresh();
... ... @@ -74,6 +96,12 @@ function onActionClick({
74 96 onDelete(row);
75 97 break;
76 98 }
  99 +#if (${table.templateType} == 2)## 树表特有:新增下级
  100 + case 'add_child': {
  101 + onAddChild(row);
  102 + break;
  103 + }
  104 +#end
77 105 }
78 106 }
79 107  
... ... @@ -84,20 +112,40 @@ const [Grid, gridApi] = useVbenVxeGrid({
84 112 gridOptions: {
85 113 columns: useGridColumns(onActionClick),
86 114 height: 'auto',
87   - keepSource: true,
  115 +#if (${table.templateType} == 2)## 树表设置
  116 + treeConfig: {
  117 + parentField: '${treeParentColumn.javaField}',
  118 + rowField: 'id',
  119 + transform: true,
  120 + expandAll: true,
  121 + reserve: true,
  122 + },
  123 +#else## 标准表设置
  124 + pagerConfig: {
  125 + enabled: true,
  126 + },
  127 +#end
88 128 proxyConfig: {
89 129 ajax: {
  130 +#if (${table.templateType} == 2)## 树表数据加载
  131 + query: async (_, formValues) => {
  132 + return await get${simpleClassName}List(formValues);
  133 + },
  134 +#else## 标准表数据加载
90 135 query: async ({ page }, formValues) => {
91   - return await get${simpleClassName}Page({
  136 + const { items, total } = await get${simpleClassName}Page({
92 137 pageNo: page.currentPage,
93 138 pageSize: page.pageSize,
94 139 ...formValues,
95 140 });
  141 + return { items, total };
96 142 },
  143 +#end
97 144 },
98 145 },
99 146 rowConfig: {
100 147 keyField: 'id',
  148 + isHover: true,
101 149 },
102 150 toolbarConfig: {
103 151 refresh: { code: 'query' },
... ... @@ -112,8 +160,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
112 160 <DocAlert title="${table.classComment}" url="https://doc.iocoder.cn/${table.moduleName}/" />
113 161  
114 162 <FormModal @success="onRefresh" />
  163 +
115 164 <Grid table-title="${table.classComment}列表">
116 165 <template #toolbar-tools>
  166 +#if (${table.templateType} == 2)## 树表特有:展开/收缩按钮
  167 + <Button @click="toggleExpand" class="mr-2">
  168 + {{ isExpanded ? '收缩' : '展开' }}
  169 + </Button>
  170 +#end
117 171 <Button type="primary" @click="onCreate" v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:create']">
118 172 <Plus class="size-5" />
119 173 {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
... ...