浏览代码

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

puhui999 4 月之前
父节点
当前提交
015565cc9a

+ 9 - 0
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 {
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
             .put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("api/api.ts"),
                     vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
+            // 主子表模板配置 - Vue3 vben5 schema 模版
+            //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/master_slave_data.ts"),
+            //        vue3FilePath("views/${table.moduleName}/${table.businessName}/data.ts"))
+            //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/master_slave_index.vue"),
+            //        vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
+            //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/modules/master_slave_form.vue"),
+            //        vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
+            //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/modules/sub_table.vue"),
+            //        vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/sub_table.vue"))
             .build();
 
     @Resource

+ 3 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/api/api.ts.vm

@@ -16,6 +16,9 @@ export namespace ${simpleClassName}Api {
     ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
 #end
 #end
+#end
+#if ( $table.templateType == 2 )
+  children?: ${simpleClassName}[];
 #end
   }
 }

+ 65 - 11
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/data.ts.vm

@@ -1,11 +1,15 @@
+import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
 import type { VbenFormSchema } from '#/adapter/form';
-import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { OnActionClickFn } from '#/adapter/vxe-table';
 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 
 import { z } from '#/adapter/form';
-import { CommonStatusEnum } from '#/utils/constants';
+#if(${table.templateType} == 2)## 树表需要导入这些
+import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import { handleTree } from '#/utils/tree';
+#end
 import { DICT_TYPE, getDictOptions } from '#/utils/dict';
-import { getRangePickerDefaultProps } from '#/utils/date';
+import { CommonStatusEnum } from '#/utils/constants';
 import { useAccess } from '@vben/access';
 
 const { hasAccessByCodes } = useAccess();
@@ -21,9 +25,34 @@ export function useFormSchema(): VbenFormSchema[] {
         show: () => false,
       },
     },
+#if(${table.templateType} == 2)## 树表特有字段:上级
+    {
+      fieldName: '${treeParentColumn.javaField}',
+      label: '上级${table.classComment}',
+      component: 'ApiTreeSelect',
+      componentProps: {
+        allowClear: true,
+        api: async () => {
+          const data = await get${simpleClassName}List({});
+          data.unshift({
+            id: 0,
+            ${treeNameColumn.javaField}: '顶级${table.classComment}',
+          });
+          return handleTree(data);
+        },
+        class: 'w-full',
+        labelField: '${treeNameColumn.javaField}',
+        valueField: 'id',
+        childrenField: 'children',
+        placeholder: '请选择上级${table.classComment}',
+        treeDefaultExpandAll: true,
+      },
+      rules: 'selectRequired',
+    },
+#end
 #foreach($column in $columns)
 #if ($column.createOperation || $column.updateOperation)
-#if (!$column.primaryKey)## 忽略主键,不用在表单里
+#if (!$column.primaryKey && ($table.templateType != 2 || ($table.templateType == 2 && $column.id != $treeParentColumn.id)))## 树表中已经添加了父ID字段,这里排除
   #set ($dictType = $column.dictType)
   #set ($javaType = $column.javaType)
   #set ($javaField = $column.javaField)
@@ -69,6 +98,7 @@ export function useFormSchema(): VbenFormSchema[] {
         options: [],
         #end
         placeholder: '请选择${comment}',
+        class: 'w-full',
       },
   #elseif($column.htmlType == "checkbox")## 多选框
       component: 'Checkbox',
@@ -102,6 +132,14 @@ export function useFormSchema(): VbenFormSchema[] {
       componentProps: {
         placeholder: '请输入${comment}',
       },
+  #elseif($column.htmlType == "inputNumber")## 数字输入框
+      component: 'InputNumber',
+      componentProps: {
+        min: 0,
+        class: 'w-full',
+        controlsPosition: 'right',
+        placeholder: '请输入${comment}',
+      },
   #end
     },
 #end
@@ -159,7 +197,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
   #elseif($column.htmlType == "datetime")
       component: 'RangePicker',
       componentProps: {
-        ...getRangePickerDefaultProps(),
         allowClear: true,
       },
   #end
@@ -170,9 +207,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
 }
 
 /** 列表的字段 */
-export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>(
-  onActionClick: OnActionClickFn<T>,
-): VxeTableGridOptions['columns'] {
+export function useGridColumns(
+  onActionClick?: OnActionClickFn<${simpleClassName}Api.${simpleClassName}>,
+): VxeTableGridOptions<${simpleClassName}Api.${simpleClassName}>['columns'] {
   return [
 #foreach($column in $columns)
 #if ($column.listOperationResult)
@@ -190,6 +227,9 @@ export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>(
         name: 'CellDict',
         props: { type: DICT_TYPE.$dictType.toUpperCase() },
       },
+  #end
+  #if (${table.templateType} == 2 && $column.id == $treeNameColumn.id)## 树表特有:标记树节点列
+      treeNode: true,
   #end
     },
 #end
@@ -197,9 +237,11 @@ export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>(
     {
       field: 'operation',
       title: '操作',
-      minWidth: 180,
-      align: 'center',
+      minWidth: 200,
+      align: 'right',
       fixed: 'right',
+      headerAlign: 'center',
+      showOverflow: false,
       cellRender: {
         attrs: {
           nameField: '${columns[0].javaField}',
@@ -208,6 +250,13 @@ export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>(
         },
         name: 'CellOperation',
         options: [
+#if (${table.templateType} == 2)## 树表特有操作
+          {
+            code: 'add_child',
+            text: '新增下级',
+            show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:create']),
+          },
+#end
           {
             code: 'edit',
             show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:update']),
@@ -215,7 +264,12 @@ export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>(
           {
             code: 'delete',
             show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:delete']),
-          }
+#if (${table.templateType} == 2)## 树表禁止删除带有子节点的数据
+            disabled: (row: ${simpleClassName}Api.${simpleClassName}) => {
+                return !!(row.children && row.children.length > 0);
+            },
+#end
+          },
         ],
       },
     },

+ 52 - 8
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 '#/api/${table.moduleName}/${simpleCl
 import { useVbenModal } from '@vben/common-ui';
 import { message } from 'ant-design-vue';
 
-import { $t } from '#/locales';
 import { computed, ref } from 'vue';
+import { $t } from '#/locales';
 import { useVbenForm } from '#/adapter/form';
 import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 
@@ -13,11 +13,25 @@ import { useFormSchema } from '../data';
 
 const emit = defineEmits(['success']);
 const formData = ref<${simpleClassName}Api.${simpleClassName}>();
+#if (${table.templateType} == 2)## 树表特有:父ID处理
+// 新增下级时的父级ID
+const parentId = ref<number>();
+
+const getTitle = computed(() => {
+  if (formData.value?.id) {
+    return $t('ui.actionTitle.edit', ['${table.classComment}']);
+  }
+  return parentId.value 
+    ? $t('ui.actionTitle.create', ['下级${table.classComment}']) 
+    : $t('ui.actionTitle.create', ['${table.classComment}']);
+});
+#else## 标准表标题
 const getTitle = computed(() => {
   return formData.value?.id
     ? $t('ui.actionTitle.edit', ['${table.classComment}'])
     : $t('ui.actionTitle.create', ['${table.classComment}']);
 });
+#end
 
 const [Form, formApi] = useVbenForm({
   layout: 'horizontal',
@@ -55,17 +69,47 @@ const [Modal, modalApi] = useVbenModal({
       return;
     }
     // 加载数据
+#if (${table.templateType} == 2)## 树表处理传入的父ID
+    let data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
+#else## 标准表直接获取
     const data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
-    if (!data || !data.id) {
+#end
+    if (!data) {
       return;
     }
-    modalApi.lock();
-    try {
-      formData.value = await get${simpleClassName}(data.id as number);
-      // 设置到 values
+    
+#if (${table.templateType} == 2)## 树表特有:处理新增下级的情况
+    // 处理新增下级的情况
+    if (!data.id && data.${treeParentColumn.javaField}) {
+      parentId.value = data.${treeParentColumn.javaField};
+      formData.value = { ${treeParentColumn.javaField}: parentId.value } as ${simpleClassName}Api.${simpleClassName};
       await formApi.setValues(formData.value);
-    } finally {
-      modalApi.lock(false);
+      return;
+    }
+#end
+    
+    if (data.id) {
+      // 编辑
+      modalApi.lock();
+      try {
+#if (${table.templateType} == 2)## 树表获取数据后重新赋值
+        data = await get${simpleClassName}(data.id);
+        formData.value = data;
+#else## 标准表设置表单数据
+        formData.value = await get${simpleClassName}(data.id as number);
+#end
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.lock(false);
+      }
+    } else {
+      // 新增
+#if (${table.templateType} == 2)## 树表特有:设置顶级ID
+      formData.value = { ${treeParentColumn.javaField}: 0 } as ${simpleClassName}Api.${simpleClassName};
+#else## 标准表:设置空值
+      formData.value = data;
+#end
+      await formApi.setValues(formData.value || {});
     }
   },
 });

+ 58 - 4
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 '@vben/icons';
 import Form from './modules/form.vue';
 import { DocAlert } from '#/components/doc-alert';
 
+import { ref } from 'vue';
 import { $t } from '#/locales';
 import { useVbenVxeGrid } from '#/adapter/vxe-table';
+#if (${table.templateType} == 2)## 树表接口
+import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import { handleTree } from '#/utils/tree';
+#else## 标准表接口
 import { get${simpleClassName}Page, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+#end
 import { downloadByData } from '#/utils/download';
 
 import { useGridColumns, useGridFormSchema } from './data';
@@ -20,6 +26,15 @@ const [FormModal, formModalApi] = useVbenModal({
   destroyOnClose: true,
 });
 
+#if (${table.templateType} == 2)## 树表特有:控制表格展开收缩
+/** 切换树形展开/收缩状态 */
+const isExpanded = ref(true);
+function toggleExpand() {
+  isExpanded.value = !isExpanded.value;
+  gridApi.grid.setAllTreeExpand(isExpanded.value);
+}
+#end
+
 /** 刷新表格 */
 function onRefresh() {
   gridApi.query();
@@ -41,17 +56,24 @@ function onEdit(row: ${simpleClassName}Api.${simpleClassName}) {
   formModalApi.setData(row).open();
 }
 
+#if (${table.templateType} == 2)## 树表特有:新增下级
+/** 新增下级${table.classComment} */
+function onAddChild(row: ${simpleClassName}Api.${simpleClassName}) {
+  formModalApi.setData({ ${treeParentColumn.javaField}: row.id }).open();
+}
+#end
+
 /** 删除${table.classComment} */
 async function onDelete(row: ${simpleClassName}Api.${simpleClassName}) {
   const hideLoading = message.loading({
-    content: $t('ui.actionMessage.deleting', [row.${columns[0].javaField}]),
+    content: $t('ui.actionMessage.deleting', [row.id]),
     duration: 0,
     key: 'action_process_msg',
   });
   try {
     await delete${simpleClassName}(row.id as number);
     message.success({
-      content: $t('ui.actionMessage.deleteSuccess', [row.${columns[0].javaField}]),
+      content: $t('ui.actionMessage.deleteSuccess', [row.id]),
       key: 'action_process_msg',
     });
     onRefresh();
@@ -74,6 +96,12 @@ function onActionClick({
       onDelete(row);
       break;
     }
+#if (${table.templateType} == 2)## 树表特有:新增下级
+    case 'add_child': {
+      onAddChild(row);
+      break;
+    }
+#end
   }
 }
 
@@ -84,20 +112,40 @@ const [Grid, gridApi] = useVbenVxeGrid({
   gridOptions: {
     columns: useGridColumns(onActionClick),
     height: 'auto',
-    keepSource: true,
+#if (${table.templateType} == 2)## 树表设置
+  treeConfig: {
+    parentField: '${treeParentColumn.javaField}',
+    rowField: 'id',
+    transform: true,
+    expandAll: true,
+    reserve: true,
+  },
+#else## 标准表设置
+    pagerConfig: {
+      enabled: true,
+    },
+#end
     proxyConfig: {
       ajax: {
+#if (${table.templateType} == 2)## 树表数据加载
+        query: async (_, formValues) => {
+          return await get${simpleClassName}List(formValues);
+        },
+#else## 标准表数据加载
         query: async ({ page }, formValues) => {
-          return await get${simpleClassName}Page({
+          const { items, total } = await get${simpleClassName}Page({
             pageNo: page.currentPage,
             pageSize: page.pageSize,
             ...formValues,
           });
+          return { items, total };
         },
+#end
       },
     },
     rowConfig: {
       keyField: 'id',
+      isHover: true,
     },
     toolbarConfig: {
       refresh: { code: 'query' },
@@ -112,8 +160,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
     <DocAlert title="${table.classComment}" url="https://doc.iocoder.cn/${table.moduleName}/" />
 
     <FormModal @success="onRefresh" />
+
     <Grid table-title="${table.classComment}列表">
       <template #toolbar-tools>
+#if (${table.templateType} == 2)## 树表特有:展开/收缩按钮
+        <Button @click="toggleExpand" class="mr-2">
+          {{ isExpanded ? '收缩' : '展开' }}
+        </Button>
+#end
         <Button type="primary" @click="onCreate" v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:create']">
           <Plus class="size-5" />
           {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}