Browse Source

代码生成器:增加树形结构的 vm 模版

zhijiantianya@gmail.com 1 year ago
parent
commit
b47c4fbd8d
15 changed files with 246 additions and 20 deletions
  1. 17 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenTableDO.java
  2. 12 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenTemplateTypeEnum.java
  3. 21 2
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
  4. 20 6
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/controller.vm
  5. 0 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/listReqVO.vm
  6. 3 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/convert/convert.vm
  7. 12 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper.vm
  8. 2 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/enums/errorcode.vm
  9. 6 3
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/service.vm
  10. 13 2
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm
  11. 4 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/test/serviceTest.vm
  12. 8 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm
  13. 40 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm
  14. 47 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
  15. 41 0
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineTest.java

+ 17 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/codegen/CodegenTableDO.java

@@ -138,4 +138,21 @@ public class CodegenTableDO extends BaseDO {
      */
     private Boolean subJoinMany;
 
+    // ========== 树表相关字段 ==========
+
+    /**
+     * 树表的父字段编号
+     *
+     * 关联 {@link CodegenColumnDO#getId()}
+     */
+    private Long treeParentColumnId;
+    /**
+     * 树表的名字字段编号
+     *
+     * 名字的用途:新增或修改时,select 框展示的字段
+     *
+     * 关联 {@link CodegenColumnDO#getId()}
+     */
+    private Long treeNameColumnId;
+
 }

+ 12 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenTemplateTypeEnum.java

@@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+import java.util.Objects;
+
 /**
  * 代码生成模板类型
  *
@@ -38,4 +40,14 @@ public enum CodegenTemplateTypeEnum {
                 MASTER_NORMAL.type, MASTER_ERP.type, MASTER_INNER.type);
     }
 
+    /**
+     * 是否为树表
+     *
+     * @param type 类型
+     * @return 是否树表
+     */
+    public static boolean isTree(Integer type) {
+        return Objects.equals(type, TREE.type);
+    }
+
 }

+ 21 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.infra.service.codegen.inner;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Filter;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
@@ -67,7 +68,7 @@ public class CodegenEngine {
             .put(javaTemplatePath("controller/vo/pageReqVO"), javaModuleImplVOFilePath("PageReqVO"))
             .put(javaTemplatePath("controller/vo/respVO"), javaModuleImplVOFilePath("RespVO"))
             .put(javaTemplatePath("controller/vo/updateReqVO"), javaModuleImplVOFilePath("UpdateReqVO"))
-            .put(javaTemplatePath("controller/vo/exportReqVO"), javaModuleImplVOFilePath("ExportReqVO"))
+            .put(javaTemplatePath("controller/vo/listReqVO"), javaModuleImplVOFilePath("ListReqVO"))
             .put(javaTemplatePath("controller/vo/excelVO"), javaModuleImplVOFilePath("ExcelVO"))
             .put(javaTemplatePath("controller/controller"), javaModuleImplControllerFilePath())
             .put(javaTemplatePath("convert/convert"),
@@ -216,8 +217,14 @@ public class CodegenEngine {
             if (isSubTemplate(vmPath)) {
                 generateSubCode(table, subTables, result, vmPath, filePath, bindingMap);
                 return;
+                // 2.2 特殊:树表专属逻辑
+            } else if (isPageTemplate(vmPath)) {
+                // 减少多余的类生成,例如说 PageVO.java 类
+                if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
+                    return;
+                }
             }
-            // 2.2 默认生成
+            // 2.3 默认生成
             generateCode(result, vmPath, filePath, bindingMap);
         });
         return result;
@@ -314,6 +321,14 @@ public class CodegenEngine {
         // permission 前缀
         bindingMap.put("permissionPrefix", table.getModuleName() + ":" + simpleClassNameStrikeCase);
 
+        // 特殊:树表专属逻辑
+        if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
+            bindingMap.put("treeParentColumn", CollUtil.findOne(columns,
+                    column -> Objects.equals(column.getId(), table.getTreeParentColumnId())));
+            bindingMap.put("treeNameColumn", CollUtil.findOne(columns,
+                    column -> Objects.equals(column.getId(), table.getTreeNameColumnId())));
+        }
+
         // 特殊:主子表专属逻辑
         if (CollUtil.isNotEmpty(subTables)) {
             // 创建 bindingMap
@@ -454,4 +469,8 @@ public class CodegenEngine {
         return path.contains("_sub");
     }
 
+    private static boolean isPageTemplate(String path) {
+        return path.contains("page");
+    }
+
 }

+ 20 - 6
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/controller.vm

@@ -85,28 +85,42 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
         return success(${table.className}Convert.INSTANCE.convert(${classNameVar}));
     }
 
+#if ( $table.templateType != 2 )
     @GetMapping("/page")
     @Operation(summary = "获得${table.classComment}分页")
 #if ($sceneEnum.scene == 1)
     @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
 #end
-    public CommonResult<PageResult<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}Page(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageVO) {
-        PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageVO);
+    public CommonResult<PageResult<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}Page(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) {
+        PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageReqVO);
         return success(${table.className}Convert.INSTANCE.convertPage(pageResult));
     }
 
+## 特殊:树表专属逻辑(树不需要分页接口)
+#else
+    @GetMapping("/list")
+    @Operation(summary = "获得${table.classComment}列表")
+#if ($sceneEnum.scene == 1)
+    @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+    public CommonResult<List<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}List(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO) {
+        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO);
+        return success(${table.className}Convert.INSTANCE.convertList(list));
+    }
+
+#end
     @GetMapping("/export-excel")
     @Operation(summary = "导出${table.classComment} Excel")
 #if ($sceneEnum.scene == 1)
     @PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')")
 #end
     @OperateLog(type = EXPORT)
-    public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO,
+    public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO,
               HttpServletResponse response) throws IOException {
-        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(exportReqVO);
+        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO);
         // 导出 Excel
-        List<${sceneEnum.prefixClass}${table.className}ExcelVO> datas = ${table.className}Convert.INSTANCE.convertList02(list);
-        ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${sceneEnum.prefixClass}${table.className}ExcelVO.class, datas);
+        List<${sceneEnum.prefixClass}${table.className}ExcelVO> dataList = ${table.className}Convert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${sceneEnum.prefixClass}${table.className}ExcelVO.class, dataList);
     }
 
 ## 特殊:主子表专属逻辑

+ 0 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/exportReqVO.vm → yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/listReqVO.vm


+ 3 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/convert/convert.vm

@@ -26,8 +26,10 @@ public interface ${table.className}Convert {
     ${sceneEnum.prefixClass}${table.className}RespVO convert(${table.className}DO bean);
 
     List<${sceneEnum.prefixClass}${table.className}RespVO> convertList(List<${table.className}DO> list);
-
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
     PageResult<${sceneEnum.prefixClass}${table.className}RespVO> convertPage(PageResult<${table.className}DO> page);
+#end
 
     List<${sceneEnum.prefixClass}${table.className}ExcelVO> convertList02(List<${table.className}DO> list);
 

+ 12 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/dal/mapper.vm

@@ -49,6 +49,8 @@ import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePack
 @Mapper
 public interface ${table.className}Mapper extends BaseMapperX<${table.className}DO> {
 
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
     default PageResult<${table.className}DO> selectPage(${sceneEnum.prefixClass}${table.className}PageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<${table.className}DO>()
 			#listCondition()
@@ -56,11 +58,20 @@ public interface ${table.className}Mapper extends BaseMapperX<${table.className}
 
     }
 
-    default List<${table.className}DO> selectList(${sceneEnum.prefixClass}${table.className}ExportReqVO reqVO) {
+#end
+    default List<${table.className}DO> selectList(${sceneEnum.prefixClass}${table.className}ListReqVO reqVO) {
         return selectList(new LambdaQueryWrapperX<${table.className}DO>()
 			#listCondition()
                 .orderByDesc(${table.className}DO::getId));## 大多数情况下,id 倒序
 
     }
 
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+#set ($ParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写
+    default Long selectCountBy${ParentJavaField}(${treeParentColumn.javaType} ${treeParentColumn.javaField}) {
+        return selectCount(${table.className}DO::get${ParentJavaField}, ${treeParentColumn.javaField});
+    }
+
+#end
 }

+ 2 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/enums/errorcode.vm

@@ -1,3 +1,4 @@
 // TODO 待办:请将下面的错误码复制到 yudao-module-${table.moduleName}-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
 // ========== ${table.classComment} TODO 补充编号 ==========
-ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${table.classComment}不存在");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${table.classComment}不存在");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_EXITS_CHILDREN = new ErrorCode(TODO 补充编号, "存在存在子${table.classComment},无法删除");

+ 6 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/service.vm

@@ -48,6 +48,8 @@ public interface ${table.className}Service {
      */
     ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id);
 
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
     /**
      * 获得${table.classComment}分页
      *
@@ -56,13 +58,14 @@ public interface ${table.className}Service {
      */
     PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO);
 
+#end
     /**
-     * 获得${table.classComment}列表, 用于 Excel 导出
+     * 获得${table.classComment}列表
      *
-     * @param exportReqVO 查询条件
+     * @param listReqVO 查询条件
      * @return ${table.classComment}列表
      */
-    List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO);
+    List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO);
 
 ## 特殊:主子表专属逻辑
 #foreach ($subTable in $subTables)

+ 13 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm

@@ -110,6 +110,14 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
     public void delete${simpleClassName}(${primaryColumn.javaType} id) {
         // 校验存在
         validate${simpleClassName}Exists(id);
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+#set ($ParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写
+        // 校验是否有子${table.classComment}
+        if (${classNameVar}Mapper.selectCountBy${ParentJavaField}(id) > 0) {
+            throw exception(${simpleClassName_underlineCase.toUpperCase()}_EXITS_CHILDREN);
+        }
+#end
         // 删除
         ${classNameVar}Mapper.deleteById(id);
 ## 特殊:主子表专属逻辑
@@ -137,14 +145,17 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
         return ${classNameVar}Mapper.selectById(id);
     }
 
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
     @Override
     public PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) {
         return ${classNameVar}Mapper.selectPage(pageReqVO);
     }
 
+#end
     @Override
-    public List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO) {
-        return ${classNameVar}Mapper.selectList(exportReqVO);
+    public List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO) {
+        return ${classNameVar}Mapper.selectList(listReqVO);
     }
 
 ## 特殊:主子表专属逻辑

+ 4 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/test/serviceTest.vm

@@ -137,6 +137,8 @@ public class ${table.className}ServiceImplTest extends BaseDbUnitTest {
         assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
     }
 
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
     @Test
     @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
     public void testGet${simpleClassName}Page() {
@@ -150,10 +152,11 @@ public class ${table.className}ServiceImplTest extends BaseDbUnitTest {
        assertPojoEquals(db${simpleClassName}, pageResult.getList().get(0));
     }
 
+#end
     @Test
     @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
     public void testGet${simpleClassName}List() {
-       #getPageCondition("ExportReqVO")
+       #getPageCondition("ListReqVO")
 
        // 调用
        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(reqVO);

+ 8 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm

@@ -15,10 +15,17 @@ export interface ${simpleClassName}VO {
 #end
 }
 
-// 查询${table.classComment}列表
+#if ( $table.templateType != 2 )
+// 查询${table.classComment}分页
 export const get${simpleClassName}Page = async (params) => {
   return await request.get({ url: `${baseURL}/page`, params })
 }
+#else
+// 查询${table.classComment}列表
+export const get${simpleClassName}List = async (params) => {
+  return await request.get({ url: `${baseURL}/list`, params })
+}
+#end
 
 // 查询${table.classComment}详情
 export const get${simpleClassName} = async (id: number) => {

+ 40 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm

@@ -22,7 +22,22 @@
         #elseif ($javaType == "Boolean")
             #set ($dictMethod = "getBoolDictOptions")
         #end
-        #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+        #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
+      <el-form-item label="${comment}" prop="${javaField}">
+        <el-tree-select
+          v-model="formData.${javaField}"
+          :data="${classNameVar}Tree"
+          #if ($treeNameColumn.javaField == "name")
+          :props="defaultProps"
+          #else
+          :props="{...defaultProps, label: '$treeNameColumn.javaField'}"
+          #end
+          check-strictly
+          default-expand-all
+          placeholder="请选择${comment}"
+        />
+      </el-form-item>
+        #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
       <el-form-item label="${comment}" prop="${javaField}">
         <el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
       </el-form-item>
@@ -126,6 +141,10 @@
 <script setup lang="ts">
 import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
 import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+import { defaultProps, handleTree } from '@/utils/tree'
+#end
 ## 特殊:主子表专属逻辑
 #foreach ($subSimpleClassName in $subSimpleClassNames)
 import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'
@@ -158,6 +177,10 @@ const formRules = reactive({
 #end
 })
 const formRef = ref() // 表单 Ref
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+const ${classNameVar}Tree = ref() // 树形结构
+#end
 ## 特殊:主子表专属逻辑
 #if ( $subTables && $subTables.size() > 0 )
 
@@ -183,6 +206,10 @@ const open = async (type: string, id?: number) => {
       formLoading.value = false
     }
   }
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+  await get${simpleClassName}Tree()
+#end
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
@@ -248,4 +275,16 @@ const resetForm = () => {
   }
   formRef.value?.resetFields()
 }
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+
+/** 获得${table.classComment}树 */
+const get${simpleClassName}Tree = async () => {
+  ${classNameVar}Tree.value = []
+  const data = await ${simpleClassName}Api.get${simpleClassName}List()
+  const root: Tree = { id: 0, name: '顶级${table.classComment}', children: [] }
+  root.children = handleTree(data, 'id', '${treeParentColumn.javaField}')
+  ${classNameVar}Tree.value.push(root)
+}
+#end
 </script>

+ 47 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm

@@ -101,6 +101,12 @@
         >
           <Icon icon="ep:download" class="mr-5px" /> 导出
         </el-button>
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+        <el-button type="danger" plain @click="toggleExpandAll">
+          <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
+        </el-button>
+#end
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -117,6 +123,17 @@
       highlight-current-row
       @current-change="handleCurrentChange"
     >
+## 特殊:树表专属逻辑
+#elseif ( $table.templateType == 2 )
+    <el-table
+      v-loading="loading"
+      :data="list"
+      :stripe="true"
+      :show-overflow-tooltip="true"
+      row-key="id"
+      :default-expand-all="isExpandAll"
+      v-if="refreshTable"
+    >
 #else
     <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
 #end
@@ -218,6 +235,10 @@
 <script setup lang="ts">
 import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+import { handleTree } from '@/utils/tree'
+#end
 import download from '@/utils/download'
 import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
 import ${simpleClassName}Form from './${simpleClassName}Form.vue'
@@ -232,11 +253,17 @@ const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
 const loading = ref(true) // 列表的加载中
-const total = ref(0) // 列表的总页数
 const list = ref([]) // 列表的数据
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
+const total = ref(0) // 列表的总页数
+#end
 const queryParams = reactive({
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
   pageNo: 1,
   pageSize: 10,
+#end
   #foreach ($column in $columns)
     #if ($column.listOperation)
       #if ($column.listOperationCondition != 'BETWEEN')
@@ -255,9 +282,15 @@ const exportLoading = ref(false) // 导出的加载中
 const getList = async () => {
   loading.value = true
   try {
+## 特殊:树表专属逻辑(树不需要分页接口)
+  #if ( $table.templateType == 2 )
+    const data = await ${simpleClassName}Api.get${simpleClassName}List(queryParams)
+    list.value = handleTree(data, 'id', '${treeParentColumn.javaField}')
+  #else
     const data = await ${simpleClassName}Api.get${simpleClassName}Page(queryParams)
     list.value = data.list
     total.value = data.total
+  #end
   } finally {
     loading.value = false
   }
@@ -317,6 +350,19 @@ const handleCurrentChange = (row) => {
   currentRow.value = row
 }
 #end
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+
+/** 展开/折叠操作 */
+const isExpandAll = ref(true) // 是否展开,默认全部展开
+const refreshTable = ref(true) // 重新渲染表格状态
+const toggleExpandAll = async () => {
+  refreshTable.value = false
+  isExpandAll.value = !isExpandAll.value
+  await nextTick()
+  refreshTable.value = true
+}
+#end
 
 /** 初始化 **/
 onMounted(() => {

+ 41 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineTest.java

@@ -42,6 +42,47 @@ public class CodegenEngineTest extends BaseMockitoUnitTest {
         codegenEngine.initGlobalBindingMap();
     }
 
+    @Test
+    public void testExecute_vue3_tree() {
+        // 准备请求参数
+        CodegenTableDO table = new CodegenTableDO().setScene(CodegenSceneEnum.ADMIN.getScene()).setParentMenuId(888L)
+                .setTableName("infra_demo66_student").setTableComment("学生表")
+                .setModuleName("infra").setBusinessName("demo66").setClassName("InfraDemo66Student")
+                .setClassComment("学生").setAuthor("芋道源码")
+                .setTemplateType(CodegenTemplateTypeEnum.TREE.getType())
+                .setFrontType(CodegenFrontTypeEnum.VUE3.getType())
+                .setTreeParentColumnId(22L).setTreeNameColumnId(11L)
+                ;
+        CodegenColumnDO idColumn = new CodegenColumnDO().setColumnName("id").setDataType(JdbcType.BIGINT.name())
+                .setColumnComment("编号").setNullable(false).setPrimaryKey(true).setAutoIncrement(true)
+                .setJavaType("Long").setJavaField("id").setExample("1024")
+                .setCreateOperation(false).setUpdateOperation(true)
+                .setListOperation(false)
+                .setListOperationResult(true);
+        CodegenColumnDO nameColumn = new CodegenColumnDO().setColumnName("name").setDataType(JdbcType.VARCHAR.name())
+                .setId(11L)
+                .setColumnComment("名字").setNullable(false)
+                .setJavaType("String").setJavaField("name").setExample("芋头")
+                .setCreateOperation(true).setUpdateOperation(true)
+                .setListOperation(true).setListOperationCondition(CodegenColumnListConditionEnum.LIKE.getCondition())
+                .setListOperationResult(true)
+                .setHtmlType(CodegenColumnHtmlTypeEnum.INPUT.getType());
+        CodegenColumnDO parentIdColumn = new CodegenColumnDO().setColumnName("description").setDataType(JdbcType.VARCHAR.name())
+                .setId(22L)
+                .setColumnComment("父编号").setNullable(false)
+                .setJavaType("Long").setJavaField("parentId").setExample("2048")
+                .setCreateOperation(true).setUpdateOperation(true)
+                .setListOperation(false)
+                .setListOperationResult(true);
+        List<CodegenColumnDO> columns = Arrays.asList(idColumn, nameColumn, parentIdColumn);
+
+        // 调用
+        Map<String, String> result = codegenEngine.execute(table, columns, null, null);
+
+        // 构建 zip 包
+        writeFile(result, "/Users/yunai/test/demo66.zip");
+    }
+
     @Test
     public void testExecute_vue3_crud() {
         // 准备请求参数