Sfoglia il codice sorgente

!1361 优化 vue2、vue3、vben5代码生成模板。新增 vben5-ele 代码生成模板
Merge pull request !1361 from puhui999/master-jdk17

芋道源码 5 mesi fa
parent
commit
d5ea976770
49 ha cambiato i file con 4194 aggiunte e 173 eliminazioni
  1. 1 1
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/Demo01ContactController.java
  2. 3 4
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/erp/Demo03StudentErpController.java
  3. 1 1
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/inner/Demo03StudentInnerController.java
  4. 1 1
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/normal/Demo03StudentNormalController.java
  5. 4 0
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java
  6. 44 0
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
  7. 1 1
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo01/Demo01ContactService.java
  8. 1 1
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo01/Demo01ContactServiceImpl.java
  9. 3 3
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/erp/Demo03StudentErpService.java
  10. 3 3
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/erp/Demo03StudentErpServiceImpl.java
  11. 1 1
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/inner/Demo03StudentInnerService.java
  12. 1 1
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/inner/Demo03StudentInnerServiceImpl.java
  13. 1 1
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/normal/Demo03StudentNormalService.java
  14. 1 1
      yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/normal/Demo03StudentNormalServiceImpl.java
  15. 7 2
      yudao-module-infra/src/main/resources/codegen/java/service/serviceImpl.vm
  16. 19 0
      yudao-module-infra/src/main/resources/codegen/vue/api/api.js.vm
  17. 50 6
      yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm
  18. 54 9
      yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm
  19. 67 16
      yudao-module-infra/src/main/resources/codegen/vue3/api/api.ts.vm
  20. 4 4
      yudao-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm
  21. 3 3
      yudao-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm
  22. 49 4
      yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm
  23. 2 2
      yudao-module-infra/src/main/resources/codegen/vue3/views/form.vue.vm
  24. 49 3
      yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm
  25. 46 47
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm
  26. 2 2
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm
  27. 18 18
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm
  28. 16 16
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm
  29. 2 2
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/api/api.ts.vm
  30. 1 3
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/data.ts.vm
  31. 9 9
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm
  32. 8 8
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm
  33. 167 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/api/api.ts.vm
  34. 320 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/form.vue.vm
  35. 490 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm
  36. 208 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/form_sub_erp.vue.vm
  37. 2 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/form_sub_inner.vue.vm
  38. 336 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/form_sub_normal.vue.vm
  39. 426 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm
  40. 4 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_inner.vue.vm
  41. 167 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/api/api.ts.vm
  42. 605 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/data.ts.vm
  43. 154 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/form.vue.vm
  44. 309 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm
  45. 90 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/form_sub_erp.vue.vm
  46. 2 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/form_sub_inner.vue.vm
  47. 202 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/form_sub_normal.vue.vm
  48. 236 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm
  49. 4 0
      yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_inner.vue.vm

+ 1 - 1
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo01/Demo01ContactController.java

@@ -65,7 +65,7 @@ public class Demo01ContactController {
     @Operation(summary = "批量删除示例联系人")
     @PreAuthorize("@ss.hasPermission('infra:demo01-contact:delete')")
     public CommonResult<Boolean> deleteDemo0iContactList(@RequestParam("ids") List<Long> ids) {
-        demo01ContactService.deleteDemo0iContactListByIds(ids);
+        demo01ContactService.deleteDemo0iContactList(ids);
         return success(true);
     }
 

+ 3 - 4
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/erp/Demo03StudentErpController.java

@@ -67,8 +67,7 @@ public class Demo03StudentErpController {
     @Operation(summary = "批量删除学生")
     @PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
     public CommonResult<Boolean> deleteDemo03StudentList(@RequestParam("ids") List<Long> ids) {
-        // TODO @puhui999:deleteDemo03StudentList
-        demo03StudentErpService.deleteDemo03StudentListByIds(ids);
+        demo03StudentErpService.deleteDemo03StudentList(ids);
         return success(true);
     }
 
@@ -142,7 +141,7 @@ public class Demo03StudentErpController {
     @Operation(summary = "批量删除学生课程")
     @PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
     public CommonResult<Boolean> deleteDemo03CourseList(@RequestParam("ids") List<Long> ids) {
-        demo03StudentErpService.deleteDemo03CourseListByIds(ids);
+        demo03StudentErpService.deleteDemo03CourseList(ids);
         return success(true);
     }
 
@@ -194,7 +193,7 @@ public class Demo03StudentErpController {
     @Operation(summary = "批量删除学生班级")
     @PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
     public CommonResult<Boolean> deleteDemo03GradeList(@RequestParam("ids") List<Long> ids) {
-        demo03StudentErpService.deleteDemo03GradeListByIds(ids);
+        demo03StudentErpService.deleteDemo03GradeList(ids);
         return success(true);
     }
 

+ 1 - 1
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/inner/Demo03StudentInnerController.java

@@ -67,7 +67,7 @@ public class Demo03StudentInnerController {
     @Operation(summary = "批量删除学生")
     @PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
     public CommonResult<Boolean> deleteDemo03StudentList(@RequestParam("ids") List<Long> ids) {
-        demo03StudentInnerService.deleteDemo03StudentListByIds(ids);
+        demo03StudentInnerService.deleteDemo03StudentList(ids);
         return success(true);
     }
 

+ 1 - 1
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/demo03/normal/Demo03StudentNormalController.java

@@ -67,7 +67,7 @@ public class Demo03StudentNormalController {
     @Operation(summary = "批量删除学生")
     @PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
     public CommonResult<Boolean> deleteDemo03StudentList(@RequestParam("ids") List<Long> ids) {
-        demo03StudentNormalService.deleteDemo03StudentListByIds(ids);
+        demo03StudentNormalService.deleteDemo03StudentList(ids);
         return success(true);
     }
 

+ 4 - 0
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java

@@ -21,6 +21,10 @@ public enum CodegenFrontTypeEnum {
     VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版
 
     VUE3_VBEN5_ANTD_GENERAL(41), // Vue3 VBEN5 + ANTD 标准模版
+
+    VUE3_VBEN5_EP_SCHEMA(42), // Vue3 VBEN5 + EP + schema 模版
+
+    VUE3_VBEN5_EP_GENERAL(43), // Vue3 VBEN5 + EP 标准模版
     ;
 
     /**

+ 44 - 0
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java

@@ -182,6 +182,42 @@ public class CodegenEngine {
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
             .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
+            // VUE3_VBEN5_EP_SCHEMA
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/data.ts"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/data.ts"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/index.vue"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/form.vue"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("api/api.ts"),
+                    vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/modules/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/modules/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/modules/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/modules/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/modules/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
+            // VUE3_VBEN5_EP
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/index.vue"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/form.vue"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("api/api.ts"),
+                    vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/modules/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/modules/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/modules/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/modules/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/modules/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
             .build();
 
     @Resource
@@ -590,6 +626,14 @@ public class CodegenEngine {
         return "codegen/vue3_vben5_antd/general/" + path + ".vm";
     }
 
+    private static String vue3Vben5EpSchemaTemplatePath(String path) {
+        return "codegen/vue3_vben5_ele/schema/" + path + ".vm";
+    }
+
+    private static String vue3Vben5EpGeneralTemplatePath(String path) {
+        return "codegen/vue3_vben5_ele/general/" + path + ".vm";
+    }
+
     private static boolean isSubTemplate(String path) {
         return path.contains("_sub");
     }

+ 1 - 1
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo01/Demo01ContactService.java

@@ -42,7 +42,7 @@ public interface Demo01ContactService {
      *
      * @param ids 编号
      */
-    void deleteDemo0iContactListByIds(List<Long> ids);
+    void deleteDemo0iContactList(List<Long> ids);
 
     /**
      * 获得示例联系人

+ 1 - 1
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo01/Demo01ContactServiceImpl.java

@@ -55,7 +55,7 @@ public class Demo01ContactServiceImpl implements Demo01ContactService {
     }
 
     @Override
-    public void deleteDemo0iContactListByIds(List<Long> ids) {
+    public void deleteDemo0iContactList(List<Long> ids) {
         // 校验存在
         validateDemo01ContactExists(ids);
         // 删除

+ 3 - 3
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/erp/Demo03StudentErpService.java

@@ -45,7 +45,7 @@ public interface Demo03StudentErpService {
      *
      * @param ids 编号
      */
-    void deleteDemo03StudentListByIds(List<Long> ids);
+    void deleteDemo03StudentList(List<Long> ids);
 
     /**
      * 获得学生
@@ -101,7 +101,7 @@ public interface Demo03StudentErpService {
      *
      * @param ids 编号
      */
-    void deleteDemo03CourseListByIds(List<Long> ids);
+    void deleteDemo03CourseList(List<Long> ids);
 
     /**
      * 获得学生课程
@@ -149,7 +149,7 @@ public interface Demo03StudentErpService {
      *
      * @param ids 编号
      */
-    void deleteDemo03GradeListByIds(List<Long> ids);
+    void deleteDemo03GradeList(List<Long> ids);
 
     /**
      * 获得学生班级

+ 3 - 3
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/erp/Demo03StudentErpServiceImpl.java

@@ -71,7 +71,7 @@ public class Demo03StudentErpServiceImpl implements Demo03StudentErpService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void deleteDemo03StudentListByIds(List<Long> ids) {
+    public void deleteDemo03StudentList(List<Long> ids) {
         // 校验存在
         validateDemo03StudentExists(ids);
         // 删除
@@ -134,7 +134,7 @@ public class Demo03StudentErpServiceImpl implements Demo03StudentErpService {
     }
 
     @Override
-    public void deleteDemo03CourseListByIds(List<Long> ids) {
+    public void deleteDemo03CourseList(List<Long> ids) {
         // 删除
         demo03CourseErpMapper.deleteByIds(ids);
     }
@@ -192,7 +192,7 @@ public class Demo03StudentErpServiceImpl implements Demo03StudentErpService {
     }
 
     @Override
-    public void deleteDemo03GradeListByIds(List<Long> ids) {
+    public void deleteDemo03GradeList(List<Long> ids) {
         // 删除
         demo03GradeErpMapper.deleteByIds(ids);
     }

+ 1 - 1
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/inner/Demo03StudentInnerService.java

@@ -44,7 +44,7 @@ public interface Demo03StudentInnerService {
      *
      * @param ids 编号
      */
-    void deleteDemo03StudentListByIds(List<Long> ids);
+    void deleteDemo03StudentList(List<Long> ids);
 
     /**
      * 获得学生

+ 1 - 1
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/inner/Demo03StudentInnerServiceImpl.java

@@ -83,7 +83,7 @@ public class Demo03StudentInnerServiceImpl implements Demo03StudentInnerService
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void deleteDemo03StudentListByIds(List<Long> ids) {
+    public void deleteDemo03StudentList(List<Long> ids) {
         // 校验存在
         validateDemo03StudentExists(ids);
         // 删除

+ 1 - 1
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/normal/Demo03StudentNormalService.java

@@ -44,7 +44,7 @@ public interface Demo03StudentNormalService {
      *
      * @param ids 编号
      */
-    void deleteDemo03StudentListByIds(List<Long> ids);
+    void deleteDemo03StudentList(List<Long> ids);
 
     /**
      * 获得学生

+ 1 - 1
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/demo03/normal/Demo03StudentNormalServiceImpl.java

@@ -83,7 +83,7 @@ public class Demo03StudentNormalServiceImpl implements Demo03StudentNormalServic
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void deleteDemo03StudentListByIds(List<Long> ids) {
+    public void deleteDemo03StudentList(List<Long> ids) {
         // 校验存在
         validateDemo03StudentExists(ids);
         // 删除

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

@@ -65,10 +65,13 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
 #end
         // 插入
 #if ($voType == 10)
-## TODO @puhui999:insert 也要加下 clean。万一前端乱传递,哈哈哈。这个就是 do 模式的缺点;(只在 do 模式下);看看主子表,是不是也可能存在,insert 的时候;
         ${table.className}DO ${classNameVar} = BeanUtils.toBean(createReqVO, ${table.className}DO.class);
-#end
         ${classNameVar}Mapper.insert(${classNameVar});
+#else
+        ${saveReqVOVar}.clean() // 清理掉创建、更新时间等相关属性值
+        ${classNameVar}Mapper.insert(${saveReqVOVar});
+#end
+
 ## 特殊:主子表专属逻辑(非 ERP 模式)
 #if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )
 
@@ -112,6 +115,7 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
         ${table.className}DO updateObj = BeanUtils.toBean(updateReqVO, ${table.className}DO.class);
         ${classNameVar}Mapper.updateById(updateObj);
 #else
+        ${updateReqVOVar}.clean() // 清理掉创建、更新时间等相关属性值
         ${classNameVar}Mapper.updateById(${updateReqVOVar});
 #end
 ## 特殊:主子表专属逻辑(非 ERP 模式)
@@ -320,6 +324,7 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
         }
         // 插入
 #end
+        ${subClassNameVar}.clean() // 清理掉创建、更新时间等相关属性值
         ${subClassNameVars.get($index)}Mapper.insert(${subClassNameVar});
         return ${subClassNameVar}.getId();
     }

+ 19 - 0
yudao-module-infra/src/main/resources/codegen/vue/api/api.js.vm

@@ -27,6 +27,16 @@ export function delete${simpleClassName}(id) {
   })
 }
 
+#if ( $table.templateType != 2 && $deleteBatchEnable)
+/** 批量删除${table.classComment} */
+export function delete${simpleClassName}List(ids) {
+  return request({
+    url: `${baseURL}/delete-list?ids=${ids.join(',')}`,
+    method: 'delete'
+  })
+}
+#end
+
 // 获得${table.classComment}
 export function get${simpleClassName}(id) {
   return request({
@@ -130,6 +140,15 @@ export function export${simpleClassName}Excel(params) {
       method: 'delete'
     })
   }
+  #if ($deleteBatchEnable)
+  /** 批量删除${subTable.classComment} */
+  export function delete${subSimpleClassName}List(ids) {
+    return request({
+      url: `${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}`,
+      method: 'delete'
+    })
+  }
+  #end
   // 获得${subTable.classComment}
   export function get${subSimpleClassName}(id) {
     return request({

+ 50 - 6
yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm

@@ -13,10 +13,36 @@
         <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
                    v-hasPermi="['${permissionPrefix}:create']">新增</el-button>
       </el-col>
+    #if ($deleteBatchEnable)
+      <el-col :span="1.5">
+        <el-button
+            type="danger"
+            plain
+            icon="el-icon-delete"
+            size="mini"
+            :disabled="isEmpty(checkedIds)"
+            @click="handleDeleteBatch"
+            v-hasPermi="['${permissionPrefix}:delete']"
+        >
+          批量删除
+        </el-button>
+      </el-col>
+    #end
     </el-row>
 #end
       ## 列表
-      <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          :show-overflow-tooltip="true"
+          #if ($table.templateType == 11 && $deleteBatchEnable)
+          @selection-change="handleRowCheckboxChange"
+          #end
+      >
+          #if ($table.templateType == 11 && $deleteBatchEnable)
+            <el-table-column type="selection" width="55" />
+          #end
           #foreach($column in $subColumns)
               #if ($column.listOperationResult)
                   #set ($dictType=$column.dictType)
@@ -82,6 +108,9 @@
         // 列表的数据
         list: [],
 #if ($table.templateType == 11)
+        #if ($deleteBatchEnable)
+        checkedIds: [],
+        #end
         // 列表的总页数
         total: 0,
         // 查询参数
@@ -135,12 +164,27 @@
           this.loading = false;
         }
       },
-      /** 搜索按钮操作 */
-      handleQuery() {
-        this.queryParams.pageNo = 1;
-        this.getList();
-      },
+      #if ($table.templateType == 11 && $deleteBatchEnable)
+        /** 批量删除${table.classComment} */
+        async handleDeleteBatch() {
+          await this.#[[$modal]]#.confirm('是否确认删除?')
+          try {
+            await ${simpleClassName}Api.delete${subSimpleClassName}List(this.checkedIds);
+            await this.getList();
+            this.#[[$modal]]#.msgSuccess("删除成功");
+          } catch {}
+        },
+        handleRowCheckboxChange(records) {
+          this.checkedIds = records.map((item) => item.id);
+        },
+        #end
+
 #if ($table.templateType == 11)
+        /** 搜索按钮操作 */
+        handleQuery() {
+          this.queryParams.pageNo = 1;
+          this.getList();
+        },
       /** 添加/修改操作 */
       openForm(id) {
         if (!this.${subJoinColumn.javaField}) {

+ 54 - 9
yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm

@@ -53,26 +53,45 @@
         <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
                    v-hasPermi="['${permissionPrefix}:export']">导出</el-button>
       </el-col>
-        ## 特殊:树表专属逻辑
-        #if ( $table.templateType == 2 )
-          <el-col :span="1.5">
-            <el-button type="danger" plain icon="el-icon-sort" size="mini" @click="toggleExpandAll">
-              展开/折叠
-            </el-button>
-          </el-col>
-        #end
+    ## 特殊:树表专属逻辑
+    #if ( $table.templateType == 2 )
+      <el-col :span="1.5">
+        <el-button type="danger" plain icon="el-icon-sort" size="mini" @click="toggleExpandAll">
+          展开/折叠
+        </el-button>
+      </el-col>
+    #end
+    #if ($table.templateType != 2 && $deleteBatchEnable)
+      <el-col :span="1.5">
+        <el-button
+            type="danger"
+            plain
+            icon="el-icon-delete"
+            size="mini"
+            :disabled="isEmpty(checkedIds)"
+            @click="handleDeleteBatch"
+            v-hasPermi="['${permissionPrefix}:delete']"
+        >
+          批量删除
+        </el-button>
+      </el-col>
+    #end
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
       ## 特殊:主子表专属逻辑
       #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
       <el-table
+        row-key="id"
         v-loading="loading"
         :data="list"
         :stripe="true"
         :highlight-current-row="true"
         :show-overflow-tooltip="true"
         @current-change="handleCurrentChange"
+        #if ($deleteBatchEnable)
+        @selection-change="handleRowCheckboxChange"
+        #end
       >
           ## 特殊:树表专属逻辑
       #elseif ( $table.templateType == 2 )
@@ -87,7 +106,18 @@
         :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
       >
       #else
-      <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          :show-overflow-tooltip="true"
+          #if ($deleteBatchEnable)
+          @selection-change="handleRowCheckboxChange"
+          #end
+      >
+      #end
+      #if ($table.templateType != 2 && $deleteBatchEnable)
+        <el-table-column type="selection" width="55" />
       #end
       ## 特殊:主子表专属逻辑
       #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
@@ -229,6 +259,7 @@ export default {
       refreshTable: true,
       // 选中行
       currentRow: {},
+      checkedIds: [],
       // 查询参数
       queryParams: {
         ## 特殊:树表专属逻辑(树不需要分页接口)
@@ -301,6 +332,20 @@ export default {
        this.#[[$modal]]#.msgSuccess("删除成功");
       } catch {}
     },
+    #if ($table.templateType != 2 && $deleteBatchEnable)
+    /** 批量删除${table.classComment} */
+    async handleDeleteBatch() {
+      await this.#[[$modal]]#.confirm('是否确认删除?')
+      try {
+        await ${simpleClassName}Api.delete${simpleClassName}List(this.checkedIds);
+        await this.getList();
+        this.#[[$modal]]#.msgSuccess("删除成功");
+      } catch {}
+    },
+    handleRowCheckboxChange(records) {
+      this.checkedIds = records.map((item) => item.id);
+    },
+    #end
     /** 导出按钮操作 */
     async handleExport() {
       await this.#[[$modal]]#.confirm('是否确认导出所有${table.classComment}数据项?');

+ 67 - 16
yudao-module-infra/src/main/resources/codegen/vue3/api/api.ts.vm

@@ -1,19 +1,56 @@
 import request from '@/config/axios'
+import type { Dayjs } from 'dayjs';
 #set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
 
-// ${table.classComment} VO
-export interface ${simpleClassName}VO {
-#foreach ($column in $columns)
-#if ($column.createOperation || $column.updateOperation)
-#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
-  ${column.javaField}: number // ${column.columnComment}
-#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
-  ${column.javaField}: Date // ${column.columnComment}
-#else
-  ${column.javaField}: ${column.javaType.toLowerCase()} // ${column.columnComment}
-#end
-#end
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+  #set ($index = $foreach.count - 1)
+  #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+  #set ($subColumns = $subColumnsList.get($index))##当前字段数组
+/** ${subTable.classComment}信息 */
+export interface ${subSimpleClassName} {
+  #foreach ($column in $subColumns)
+    #if ($column.createOperation || $column.updateOperation)
+      #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+          ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+      #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+          ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
+      #else
+          ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
+      #end
+    #end
+  #end
+}
+
 #end
+/** ${table.classComment}信息 */
+export interface ${simpleClassName} {
+  #foreach ($column in $columns)
+    #if ($column.createOperation || $column.updateOperation)
+      #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+          ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+      #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+          ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
+      #else
+          ${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
+  ## 特殊:主子表专属逻辑
+  #if ( $table.templateType == 10 || $table.templateType == 12 )
+    #foreach ($subTable in $subTables)
+      #set ($index = $foreach.count - 1)
+      #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+      #if ( $subTable.subJoinMany )
+          ${subSimpleClassName.toLowerCase()}s?: ${subSimpleClassName}[]
+      #else
+          ${subSimpleClassName.toLowerCase()}?: ${subSimpleClassName}
+      #end
+    #end
+  #end
 }
 
 // ${table.classComment} API
@@ -36,12 +73,12 @@ export const ${simpleClassName}Api = {
   },
 
   // 新增${table.classComment}
-  create${simpleClassName}: async (data: ${simpleClassName}VO) => {
+  create${simpleClassName}: async (data: ${simpleClassName}) => {
     return await request.post({ url: `${baseURL}/create`, data })
   },
 
   // 修改${table.classComment}
-  update${simpleClassName}: async (data: ${simpleClassName}VO) => {
+  update${simpleClassName}: async (data: ${simpleClassName}) => {
     return await request.put({ url: `${baseURL}/update`, data })
   },
 
@@ -50,6 +87,13 @@ export const ${simpleClassName}Api = {
     return await request.delete({ url: `${baseURL}/delete?id=` + id })
   },
 
+#if ( $table.templateType != 2 && $deleteBatchEnable)
+  /** 批量删除${table.classComment} */
+  delete${simpleClassName}List: async (ids: number[]) => {
+    return await request.delete({ url: `${baseURL}/delete-list?ids=${ids.join(',')}` })
+  },
+#end
+
   // 导出${table.classComment} Excel
   export${simpleClassName}: async (params) => {
     return await request.download({ url: `${baseURL}/export-excel`, params })
@@ -92,12 +136,12 @@ export const ${simpleClassName}Api = {
 ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
 #if ( $table.templateType == 11 )
   // 新增${subTable.classComment}
-  create${subSimpleClassName}: async (data) => {
+  create${subSimpleClassName}: async (data: ${subSimpleClassName}) => {
     return await request.post({ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, data })
   },
 
   // 修改${subTable.classComment}
-  update${subSimpleClassName}: async (data) => {
+  update${subSimpleClassName}: async (data: ${subSimpleClassName}) => {
     return await request.put({ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, data })
   },
 
@@ -106,6 +150,13 @@ export const ${simpleClassName}Api = {
     return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id })
   },
 
+  #if ($deleteBatchEnable)
+  /** 批量删除${subTable.classComment} */
+  delete${subSimpleClassName}List: async (ids: number[]) => {
+    return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}` })
+  },
+  #end
+
   // 获得${subTable.classComment}
   get${subSimpleClassName}: async (id: number) => {
     return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id })

+ 4 - 4
yudao-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm

@@ -113,7 +113,7 @@
 </template>
 <script setup lang="ts">
 import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
-import { ${simpleClassName}Api } from '@/api/${table.moduleName}/${table.businessName}'
+import { ${simpleClassName}Api, ${subSimpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
 
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
@@ -144,12 +144,12 @@ const formRules = reactive({
 const formRef = ref() // 表单 Ref
 
 /** 打开弹窗 */
-const open = async (type: string, id?: number, ${subJoinColumn.javaField}: number) => {
+const open = async (type: string, id?: number, ${subJoinColumn.javaField}?: number) => {
   dialogVisible.value = true
   dialogTitle.value = t('action.' + type)
   formType.value = type
   resetForm()
-  formData.value.${subJoinColumn.javaField} = ${subJoinColumn.javaField}
+  formData.value.${subJoinColumn.javaField} = ${subJoinColumn.javaField}  as any
   // 修改时,设置数据
   if (id) {
     formLoading.value = true
@@ -170,7 +170,7 @@ const submitForm = async () => {
   // 提交请求
   formLoading.value = true
   try {
-    const data = formData.value
+    const data = formData.value as unknown as  ${subSimpleClassName}
     if (formType.value === 'create') {
       await ${simpleClassName}Api.create${subSimpleClassName}(data)
       message.success(t('common.createSuccess'))

+ 3 - 3
yudao-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm

@@ -266,10 +266,10 @@ import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } f
 import { ${simpleClassName}Api } from '@/api/${table.moduleName}/${table.businessName}'
 
 const props = defineProps<{
-  ${subJoinColumn.javaField}: undefined // ${subJoinColumn.columnComment}(主表的关联字段)
+  ${subJoinColumn.javaField}: number // ${subJoinColumn.columnComment}(主表的关联字段)
 }>()
 const formLoading = ref(false) // 表单的加载中
-const formData = ref([])
+const formData = ref<any#if ( $subTable.subJoinMany )[]#end>(#if ( $subTable.subJoinMany )[]#else{}#end)
 const formRules = reactive({
 #foreach ($column in $subColumns)
     #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
@@ -336,7 +336,7 @@ const handleAdd = () => {
   #end
 #end
   }
-  row.${subJoinColumn.javaField} = props.${subJoinColumn.javaField}
+  row.${subJoinColumn.javaField} = props.${subJoinColumn.javaField} as any
   formData.value.push(row)
 }
 

+ 49 - 4
yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm

@@ -16,8 +16,31 @@
     >
       <Icon icon="ep:plus" class="mr-5px" /> 新增
     </el-button>
+    #if ($deleteBatchEnable)
+      <el-button
+          type="danger"
+          plain
+          :disabled="isEmpty(checkedIds)"
+          @click="handleDeleteBatch"
+          v-hasPermi="['${permissionPrefix}:delete']"
+      >
+        <Icon icon="ep:delete" class="mr-5px" /> 批量删除
+      </el-button>
+    #end
 #end
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+    <el-table
+        row-key="id"
+        v-loading="loading"
+        :data="list"
+        :stripe="true"
+        :show-overflow-tooltip="true"
+        #if ($table.templateType == 11 && $deleteBatchEnable)
+        @selection-change="handleRowCheckboxChange"
+        #end
+    >
+        #if ($table.templateType == 11 && $deleteBatchEnable)
+          <el-table-column type="selection" width="55" />
+        #end
       #foreach($column in $subColumns)
       #if ($column.listOperationResult)
         #set ($dictType=$column.dictType)
@@ -85,14 +108,18 @@
 <script setup lang="ts">
 import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
-import { ${simpleClassName}Api } from '@/api/${table.moduleName}/${table.businessName}'
+#if ($deleteBatchEnable)
+import { isEmpty } from '@/utils/is'
+#end
+import { ${simpleClassName}Api, ${subSimpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
 #if ($table.templateType == 11)
 import ${subSimpleClassName}Form from './${subSimpleClassName}Form.vue'
 #end
 
+#if ($table.templateType == 11)
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
-
+#end
 const props = defineProps<{
   ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
 }>()
@@ -144,12 +171,12 @@ const getList = async () => {
   }
 }
 
+#if ($table.templateType == 11)
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
   getList()
 }
-#if ($table.templateType == 11)
 
 /** 添加/修改操作 */
 const formRef = ref()
@@ -173,6 +200,24 @@ const handleDelete = async (id: number) => {
     await getList()
   } catch {}
 }
+
+#if ($table.templateType == 11 && $deleteBatchEnable)
+/** 批量删除${subTable.classComment} */
+const handleDeleteBatch = async () => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    await ${simpleClassName}Api.delete${subSimpleClassName}List(checkedIds.value);
+    message.success(t('common.delSuccess'))
+    await getList();
+  } catch {}
+}
+
+const checkedIds = ref<number[]>([])
+const handleRowCheckboxChange = (records: ${subSimpleClassName}[]) => {
+  checkedIds.value = records.map((item) => item.id);
+}
+#end
 #end
 #if ($table.templateType != 11)
 

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

@@ -139,7 +139,7 @@
 </template>
 <script setup lang="ts">
 import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
-import { ${simpleClassName}Api, ${simpleClassName}VO } from '@/api/${table.moduleName}/${table.businessName}'
+import { ${simpleClassName}Api, ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
 ## 特殊:树表专属逻辑
 #if ( $table.templateType == 2 )
 import { defaultProps, handleTree } from '@/utils/tree'
@@ -243,7 +243,7 @@ const submitForm = async () => {
   // 提交请求
   formLoading.value = true
   try {
-    const data = formData.value as unknown as ${simpleClassName}VO
+    const data = formData.value as unknown as ${simpleClassName}
 ## 特殊:主子表专属逻辑
 #if ( $table.templateType == 10 || $table.templateType == 12 )
 #if ( $subTables && $subTables.size() > 0 )

+ 49 - 3
yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm

@@ -107,6 +107,17 @@
           <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
         </el-button>
 #end
+      #if ($table.templateType != 2 && $deleteBatchEnable)
+        <el-button
+            type="danger"
+            plain
+            :disabled="isEmpty(checkedIds)"
+            @click="handleDeleteBatch"
+            v-hasPermi="['${table.moduleName}:${simpleClassName_strikeCase}:delete']"
+        >
+          <Icon icon="ep:delete" class="mr-5px" /> 批量删除
+        </el-button>
+      #end
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -116,12 +127,16 @@
 ## 特殊:主子表专属逻辑
 #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
     <el-table
+      row-key="id"
       v-loading="loading"
       :data="list"
       :stripe="true"
       :show-overflow-tooltip="true"
       highlight-current-row
       @current-change="handleCurrentChange"
+      #if ($deleteBatchEnable)
+      @selection-change="handleRowCheckboxChange"
+      #end
     >
 ## 特殊:树表专属逻辑
 #elseif ( $table.templateType == 2 )
@@ -135,7 +150,19 @@
       v-if="refreshTable"
     >
 #else
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+    <el-table
+        row-key="id"
+        v-loading="loading"
+        :data="list"
+        :stripe="true"
+        :show-overflow-tooltip="true"
+        #if ($deleteBatchEnable)
+        @selection-change="handleRowCheckboxChange"
+        #end
+    >
+#end
+#if ($table.templateType != 2 && $deleteBatchEnable)
+    <el-table-column type="selection" width="55" />
 #end
 ## 特殊:主子表专属逻辑
 #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
@@ -234,13 +261,14 @@
 
 <script setup lang="ts">
 import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
+import { isEmpty } from '@/utils/is'
 import { dateFormatter } from '@/utils/formatTime'
 ## 特殊:树表专属逻辑
 #if ( $table.templateType == 2 )
 import { handleTree } from '@/utils/tree'
 #end
 import download from '@/utils/download'
-import { ${simpleClassName}Api, ${simpleClassName}VO } from '@/api/${table.moduleName}/${table.businessName}'
+import { ${simpleClassName}Api, ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
 import ${simpleClassName}Form from './${simpleClassName}Form.vue'
 ## 特殊:主子表专属逻辑
 #if ( $table.templateType != 10 )
@@ -256,7 +284,7 @@ const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
 const loading = ref(true) // 列表的加载中
-const list = ref<${simpleClassName}VO[]>([]) // 列表的数据
+const list = ref<${simpleClassName}[]>([]) // 列表的数据
 ## 特殊:树表专属逻辑(树不需要分页接口)
 #if ( $table.templateType != 2 )
 const total = ref(0) // 列表的总页数
@@ -330,6 +358,24 @@ const handleDelete = async (id: number) => {
   } catch {}
 }
 
+#if ($table.templateType != 2 && $deleteBatchEnable)
+/** 批量删除${table.classComment} */
+const handleDeleteBatch = async () => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    await ${simpleClassName}Api.delete${simpleClassName}List(checkedIds.value);
+    message.success(t('common.delSuccess'))
+    await getList();
+  } catch {}
+}
+
+const checkedIds = ref<number[]>([])
+const handleRowCheckboxChange = (records: ${simpleClassName}[]) => {
+  checkedIds.value = records.map((item) => item.id);
+}
+#end
+
 /** 导出按钮操作 */
 const handleExport = async () => {
   try {

+ 46 - 47
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm

@@ -91,7 +91,7 @@ export function delete${simpleClassName}(id: number) {
 
 #if ( $table.templateType != 2 && $deleteBatchEnable)
 /** 批量删除${table.classComment} */
-export function delete${simpleClassName}ListByIds(ids: number[]) {
+export function delete${simpleClassName}List(ids: number[]) {
   return requestClient.delete(`${baseURL}/delete-list?ids=${ids.join(',')}`)
 }
 #end
@@ -102,7 +102,6 @@ export function export${simpleClassName}(params: any) {
 }
 
 ## 特殊:主子表专属逻辑
-## TODO @puhui999:下面这块缩进调整了,会乱掉么?
 #foreach ($subTable in $subTables)
   #set ($index = $foreach.count - 1)
   #set ($subSimpleClassName = $subSimpleClassNames.get($index))
@@ -115,54 +114,54 @@ export function export${simpleClassName}(params: any) {
 
 // ==================== 子表($subTable.classComment) ====================
 
-  ## 情况一:MASTER_ERP 时,需要分查询页子表
-  #if ( $table.templateType == 11 )
-  /** 获得${subTable.classComment}分页 */
-  export function get${subSimpleClassName}Page(params: PageParam) {
-    return requestClient.get<PageResult<${simpleClassName}Api.${subSimpleClassName}>>(`${baseURL}/${subSimpleClassName_strikeCase}/page`, { params });
-  }
-    ## 情况二:非 MASTER_ERP 时,需要列表查询子表
-  #else
-    #if ( $subTable.subJoinMany )
-    /** 获得${subTable.classComment}列表 */
-    export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
-      return requestClient.get<${simpleClassName}Api.${subSimpleClassName}[]>(`${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
-    }
-    #else
-    /** 获得${subTable.classComment} */
-    export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
-      return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
-    }
-    #end
-  #end
-  ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
-  #if ( $table.templateType == 11 )
-  /** 新增${subTable.classComment} */
-  export function create${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
-    return requestClient.post(`${baseURL}/${subSimpleClassName_strikeCase}/create`, data);
-  }
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+/** 获得${subTable.classComment}分页 */
+export function get${subSimpleClassName}Page(params: PageParam) {
+  return requestClient.get<PageResult<${simpleClassName}Api.${subSimpleClassName}>>(`${baseURL}/${subSimpleClassName_strikeCase}/page`, { params });
+}
+  ## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+#if ( $subTable.subJoinMany )
+/** 获得${subTable.classComment}列表 */
+export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}[]>(`${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+#else
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+#end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+/** 新增${subTable.classComment} */
+export function create${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
+  return requestClient.post(`${baseURL}/${subSimpleClassName_strikeCase}/create`, data);
+}
 
-  /** 修改${subTable.classComment} */
-  export function update${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
-    return requestClient.put(`${baseURL}/${subSimpleClassName_strikeCase}/update`, data);
-  }
+/** 修改${subTable.classComment} */
+export function update${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
+  return requestClient.put(`${baseURL}/${subSimpleClassName_strikeCase}/update`, data);
+}
 
-  /** 删除${subTable.classComment} */
-  export function delete${subSimpleClassName}(id: number) {
-    return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete?id=${id}`);
-  }
+/** 删除${subTable.classComment} */
+export function delete${subSimpleClassName}(id: number) {
+  return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete?id=${id}`);
+}
 
-    #if ($deleteBatchEnable)
-    /** 批量删除${subTable.classComment} */
-    export function delete${subSimpleClassName}ListByIds(ids: number[]) {
-      return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}`)
-    }
-    #end
+#if ($deleteBatchEnable)
+/** 批量删除${subTable.classComment} */
+export function delete${subSimpleClassName}List(ids: number[]) {
+  return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}`)
+}
+#end
 
-  /** 获得${subTable.classComment} */
-  export function get${subSimpleClassName}(id: number) {
-    return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get?id=${id}`);
-  }
-  #end
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}(id: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get?id=${id}`);
+}
+#end
 #end
 

+ 2 - 2
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm

@@ -8,7 +8,7 @@ import { ImageUpload, FileUpload } from "#/components/upload";
 import { message, Tabs, Form, Input, Textarea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } from 'ant-design-vue';
 import { DICT_TYPE, getDictOptions } from '#/utils';
 #if($table.templateType == 2)## 树表需要导入这些
-import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}';
+import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 import { handleTree } from '@vben/utils'
 #end
 ## 特殊:主子表专属逻辑
@@ -22,7 +22,7 @@ import { handleTree } from '@vben/utils'
 
 import { computed, ref } from 'vue';
 import { $t } from '#/locales';
-import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
+import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 
 const emit = defineEmits(['success']);
 

+ 18 - 18
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm

@@ -30,7 +30,7 @@ import { handleTree,isEmpty } from '@vben/utils'
 import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 #else## 标准表接口
 import { isEmpty } from '@vben/utils';
-import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}ListByIds,#end export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
+import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 #end
 import { downloadFileFromBlobPart } from '@vben/utils';
 
@@ -123,24 +123,24 @@ const [FormModal, formModalApi] = useVbenModal({
 });
 
 /** 创建${table.classComment} */
-function onCreate() {
+function handleCreate() {
   formModalApi.setData({}).open();
 }
 
 /** 编辑${table.classComment} */
-function onEdit(row: ${simpleClassName}Api.${simpleClassName}) {
+function handleEdit(row: ${simpleClassName}Api.${simpleClassName}) {
   formModalApi.setData(row).open();
 }
 
 #if (${table.templateType} == 2)## 树表特有:新增下级
 /** 新增下级${table.classComment} */
-function onAppend(row: ${simpleClassName}Api.${simpleClassName}) {
+function handleAppend(row: ${simpleClassName}Api.${simpleClassName}) {
   formModalApi.setData({ ${treeParentColumn.javaField}: row.id }).open();
 }
 #end
 
 /** 删除${table.classComment} */
-async function onDelete(row: ${simpleClassName}Api.${simpleClassName}) {
+async function handleDelete(row: ${simpleClassName}Api.${simpleClassName}) {
   const hideLoading = message.loading({
     content: $t('ui.actionMessage.deleting', [row.id]),
     duration: 0,
@@ -160,14 +160,14 @@ async function onDelete(row: ${simpleClassName}Api.${simpleClassName}) {
 
 #if ($table.templateType != 2 && $deleteBatchEnable)
 /** 批量删除${table.classComment} */
-async function onDeleteBatch() {
+async function handleDeleteBatch() {
   const hideLoading = message.loading({
     content: $t('ui.actionMessage.deleting'),
     duration: 0,
     key: 'action_process_msg',
   });
   try {
-    await delete${simpleClassName}ListByIds(deleteIds.value);
+    await delete${simpleClassName}List(checkedIds.value);
     message.success( $t('ui.actionMessage.deleteSuccess') );
     await getList();
   } finally {
@@ -175,13 +175,13 @@ async function onDeleteBatch() {
   }
 }
 
-const deleteIds = ref<number[]>([]) // 待删除${table.classComment} ID
-function setDeleteIds({
+const checkedIds = ref<number[]>([])
+function handleRowCheckboxChange({
   records,
 }: {
   records: ${simpleClassName}Api.${simpleClassName}[];
 }) {
-  deleteIds.value = records.map((item) => item.id);
+  checkedIds.value = records.map((item) => item.id);
 }
 #end
 
@@ -315,7 +315,7 @@ onMounted(() => {
               class="ml-2"
               :icon="h(Plus)"
               type="primary"
-              @click="onCreate"
+              @click="handleCreate"
               v-access:code="['${permissionPrefix}:create']"
           >
             {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
@@ -336,8 +336,8 @@ onMounted(() => {
               type="primary"
               danger
               class="ml-2"
-              :disabled="isEmpty(deleteIds)"
-              @click="onDeleteBatch"
+              :disabled="isEmpty(checkedIds)"
+              @click="handleDeleteBatch"
               v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:delete']"
           >
             批量删除
@@ -368,8 +368,8 @@ onMounted(() => {
           show-overflow
           :loading="loading"
 #if ($table.templateType != 2 && $deleteBatchEnable)
-          @checkboxAll="setDeleteIds"
-          @checkboxChange="setDeleteIds"
+          @checkboxAll="handleRowCheckboxChange"
+          @checkboxChange="handleRowCheckboxChange"
 #end
       >
 #if ($table.templateType != 2 && $deleteBatchEnable)
@@ -426,7 +426,7 @@ onMounted(() => {
   <Button
       size="small"
       type="link"
-      @click="onAppend(row as any)"
+      @click="handleAppend(row as any)"
       v-access:code="['${permissionPrefix}:create']"
   >
     新增下级
@@ -435,7 +435,7 @@ onMounted(() => {
             <Button
                 size="small"
                 type="link"
-                @click="onEdit(row as any)"
+                @click="handleEdit(row as any)"
                 v-access:code="['${permissionPrefix}:update']"
             >
               {{ $t('ui.actionTitle.edit') }}
@@ -448,7 +448,7 @@ onMounted(() => {
                 #if ( $table.templateType == 2 )
                 :disabled="!isEmpty(row?.children)"
                 #end
-                @click="onDelete(row as any)"
+                @click="handleDelete(row as any)"
                 v-access:code="['${permissionPrefix}:delete']"
             >
               {{ $t('ui.actionTitle.delete') }}

+ 16 - 16
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm

@@ -29,7 +29,7 @@
 #end
 
 #if ($table.templateType == 11) ## erp
-    import { delete${subSimpleClassName},#if ($deleteBatchEnable) delete${subSimpleClassName}ListByIds,#end get${subSimpleClassName}Page } from '#/api/${table.moduleName}/${table.businessName}';
+    import { delete${subSimpleClassName},#if ($deleteBatchEnable) delete${subSimpleClassName}List,#end get${subSimpleClassName}Page } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
     import { isEmpty } from '@vben/utils';
   #else
   #if ($subTable.subJoinMany) ## 一对多
@@ -50,7 +50,7 @@ const props = defineProps<{
   });
 
 /** 创建${subTable.classComment} */
-function onCreate() {
+function handleCreate() {
   if (!props.${subJoinColumn.javaField}){
     message.warning("请先选择一个${table.classComment}!")
     return
@@ -59,12 +59,12 @@ function onCreate() {
 }
 
 /** 编辑${subTable.classComment} */
-function onEdit(row: ${simpleClassName}Api.${subSimpleClassName}) {
+function handleEdit(row: ${simpleClassName}Api.${subSimpleClassName}) {
   formModalApi.setData(row).open();
 }
 
 /** 删除${subTable.classComment} */
-async function onDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
+async function handleDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
   const hideLoading = message.loading({
     content: $t('ui.actionMessage.deleting', [row.id]),
     duration: 0,
@@ -84,14 +84,14 @@ async function onDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
 
 #if ($deleteBatchEnable)
 /** 批量删除${subTable.classComment} */
-async function onDeleteBatch() {
+async function handleDeleteBatch() {
   const hideLoading = message.loading({
     content: $t('ui.actionMessage.deleting'),
     duration: 0,
     key: 'action_process_msg',
   });
   try {
-    await delete${subSimpleClassName}ListByIds(deleteIds.value);
+    await delete${subSimpleClassName}List(checkedIds.value);
     message.success( $t('ui.actionMessage.deleteSuccess') );
     await getList();
   } finally {
@@ -99,13 +99,13 @@ async function onDeleteBatch() {
   }
 }
 
-const deleteIds = ref<number[]>([]) // 待删除${subTable.classComment} ID
-function setDeleteIds({
+const checkedIds = ref<number[]>([])
+function handleRowCheckboxChange({
   records,
 }: {
   records: ${simpleClassName}Api.${subSimpleClassName}[];
 }) {
-  deleteIds.value = records.map((item) => item.id);
+  checkedIds.value = records.map((item) => item.id);
 }
 #end
 #end
@@ -299,7 +299,7 @@ onMounted(() => {
                   class="ml-2"
                   :icon="h(Plus)"
                   type="primary"
-                  @click="onCreate"
+                  @click="handleCreate"
                   v-access:code="['${permissionPrefix}:create']"
               >
                 {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
@@ -310,8 +310,8 @@ onMounted(() => {
                       type="primary"
                       danger
                       class="ml-2"
-                      :disabled="isEmpty(deleteIds)"
-                      @click="onDeleteBatch"
+                      :disabled="isEmpty(checkedIds)"
+                      @click="handleDeleteBatch"
                       v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:delete']"
                   >
                     批量删除
@@ -325,8 +325,8 @@ onMounted(() => {
               show-overflow
               :loading="loading"
               #if ($deleteBatchEnable)
-              @checkboxAll="setDeleteIds"
-              @checkboxChange="setDeleteIds"
+              @checkboxAll="handleRowCheckboxChange"
+              @checkboxChange="handleRowCheckboxChange"
               #end
           >
               #if ($deleteBatchEnable)
@@ -361,7 +361,7 @@ onMounted(() => {
                 <Button
                     size="small"
                     type="link"
-                    @click="onEdit(row as any)"
+                    @click="handleEdit(row as any)"
                     v-access:code="['${permissionPrefix}:update']"
                 >
                   {{ $t('ui.actionTitle.edit') }}
@@ -371,7 +371,7 @@ onMounted(() => {
                     type="link"
                     danger
                     class="ml-2"
-                    @click="onDelete(row as any)"
+                    @click="handleDelete(row as any)"
                     v-access:code="['${permissionPrefix}:delete']"
                 >
                   {{ $t('ui.actionTitle.delete') }}

+ 2 - 2
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/api/api.ts.vm

@@ -91,7 +91,7 @@ export function delete${simpleClassName}(id: number) {
 
 #if ( $table.templateType != 2 && $deleteBatchEnable)
 /** 批量删除${table.classComment} */
-export function delete${simpleClassName}ListByIds(ids: number[]) {
+export function delete${simpleClassName}List(ids: number[]) {
   return requestClient.delete(`${baseURL}/delete-list?ids=${ids.join(',')}`)
 }
 #end
@@ -153,7 +153,7 @@ export function delete${subSimpleClassName}(id: number) {
 
 #if ($deleteBatchEnable)
 /** 批量删除${subTable.classComment} */
-export function delete${subSimpleClassName}ListByIds(ids: number[]) {
+export function delete${subSimpleClassName}List(ids: number[]) {
   return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}`)
 }
 #end

+ 1 - 3
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/data.ts.vm

@@ -426,9 +426,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${sim
 #else
     #if ($subTable.subJoinMany) ## 一对多
     /** 新增/修改列表的字段 */
-    export function use${subSimpleClassName}GridEditColumns(
-        onActionClick?: OnActionClickFn<${simpleClassName}Api.${subSimpleClassName}>,
-    ): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
+    export function use${subSimpleClassName}GridEditColumns(): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
         return [
             #foreach($column in $subColumns)
                 #if ($column.createOperation || $column.updateOperation)

+ 9 - 9
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm

@@ -21,7 +21,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 #if (${table.templateType} == 2)## 树表接口
 import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
 #else## 标准表接口
-import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}ListByIds,#end export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
+import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
 #end
 import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
 
@@ -101,7 +101,7 @@ async function handleDeleteBatch() {
     key: 'action_key_msg',
   });
   try {
-    await delete${simpleClassName}ListByIds(deleteIds.value);
+    await delete${simpleClassName}List(checkedIds.value);
     message.success({
       content: $t('ui.actionMessage.deleteSuccess'),
       key: 'action_key_msg',
@@ -112,13 +112,13 @@ async function handleDeleteBatch() {
   }
 }
 
-const deleteIds = ref<number[]>([]) // 待删除${table.classComment} ID
-function setDeleteIds({
+const checkedIds = ref<number[]>([])
+function handleRowCheckboxChange({
   records,
 }: {
   records: ${simpleClassName}Api.${simpleClassName}[];
 }) {
-  deleteIds.value = records.map((item) => item.id);
+  checkedIds.value = records.map((item) => item.id);
 }
 #end
 
@@ -191,9 +191,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
       select${simpleClassName}.value = row;
     },
     #end
-    #if($deleteBatchEnable)
-      checkboxAll: setDeleteIds,
-      checkboxChange: setDeleteIds,
+    #if(${table.templateType} != 2 && $deleteBatchEnable)
+      checkboxAll: handleRowCheckboxChange,
+      checkboxChange: handleRowCheckboxChange,
     #end
   }
 #end
@@ -254,7 +254,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
               type: 'primary',
               danger: true,
               icon: ACTION_ICON.DELETE,
-              disabled: isEmpty(deleteIds),
+              disabled: isEmpty(checkedIds),
               auth: ['${table.moduleName}:${simpleClassName_strikeCase}:delete'],
               onClick: handleDeleteBatch,
             },

+ 8 - 8
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm

@@ -19,7 +19,7 @@ import { $t } from '#/locales';
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 
 #if ($table.templateType == 11) ## erp
-import { delete${subSimpleClassName},#if ($deleteBatchEnable) delete${subSimpleClassName}ListByIds,#end get${subSimpleClassName}Page } from '#/api/${table.moduleName}/${table.businessName}';
+import { delete${subSimpleClassName},#if ($deleteBatchEnable) delete${subSimpleClassName}List,#end get${subSimpleClassName}Page } from '#/api/${table.moduleName}/${table.businessName}';
 import { use${subSimpleClassName}GridFormSchema, use${subSimpleClassName}GridColumns } from '../data';
 import { isEmpty } from '@vben/utils';
 #else
@@ -81,7 +81,7 @@ async function handleDeleteBatch() {
     key: 'action_key_msg',
   });
   try {
-    await delete${subSimpleClassName}ListByIds(deleteIds.value);
+    await delete${subSimpleClassName}List(checkedIds.value);
     message.success({
       content: $t('ui.actionMessage.deleteSuccess', [row.id]),
       key: 'action_key_msg',
@@ -92,13 +92,13 @@ async function handleDeleteBatch() {
   }
 }
 
-const deleteIds = ref<number[]>([]) // 待删除${subTable.classComment} ID
-function setDeleteIds({
+const checkedIds = ref<number[]>([])
+function handleRowCheckboxChange({
   records,
 }: {
   records: ${simpleClassName}Api.${subSimpleClassName}[];
 }) {
-  deleteIds.value = records.map((item) => item.id);
+  checkedIds.value = records.map((item) => item.id);
 }
 #end
 
@@ -151,8 +151,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
   } as VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>,
   #if (${table.templateType} == 11 && $deleteBatchEnable)
   gridEvents:{
-    checkboxAll: setDeleteIds,
-    checkboxChange: setDeleteIds,
+    checkboxAll: handleRowCheckboxChange,
+    checkboxChange: handleRowCheckboxChange,
   }
   #end
 });
@@ -204,7 +204,7 @@ watch(
                 type: 'primary',
                 danger: true,
                 icon: ACTION_ICON.DELETE,
-                disabled: isEmpty(deleteIds),
+                disabled: isEmpty(checkedIds),
                 auth: ['${table.moduleName}:${simpleClassName_strikeCase}:delete'],
                 onClick: handleDeleteBatch,
               },

+ 167 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/api/api.ts.vm

@@ -0,0 +1,167 @@
+import type { PageParam, PageResult } from '@vben/request';
+import type { Dayjs } from 'dayjs';
+
+import { requestClient } from '#/api/request';
+#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
+
+export namespace ${simpleClassName}Api {
+  ## 特殊:主子表专属逻辑
+  #foreach ($subTable in $subTables)
+    #set ($index = $foreach.count - 1)
+    #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+    #set ($subColumns = $subColumnsList.get($index))##当前字段数组
+    /** ${subTable.classComment}信息 */
+    export interface ${subSimpleClassName} {
+      #foreach ($column in $subColumns)
+        #if ($column.createOperation || $column.updateOperation)
+          #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+              ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+          #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+              ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
+          #else
+              ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
+          #end
+        #end
+      #end
+    }
+
+  #end
+  /** ${table.classComment}信息 */
+  export interface ${simpleClassName} {
+    #foreach ($column in $columns)
+      #if ($column.createOperation || $column.updateOperation)
+        #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+        #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
+        #else
+            ${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
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #foreach ($subTable in $subTables)
+        #set ($index = $foreach.count - 1)
+        #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+        #if ( $subTable.subJoinMany )
+            ${subSimpleClassName.toLowerCase()}s?: ${subSimpleClassName}[]
+        #else
+            ${subSimpleClassName.toLowerCase()}?: ${subSimpleClassName}
+        #end
+      #end
+    #end
+  }
+}
+
+#if ( $table.templateType != 2 )
+/** 查询${table.classComment}分页 */
+export function get${simpleClassName}Page(params: PageParam) {
+  return requestClient.get<PageResult<${simpleClassName}Api.${simpleClassName}>>('${baseURL}/page', { params });
+}
+#else
+/** 查询${table.classComment}列表 */
+export function get${simpleClassName}List(params: any) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}[]>('${baseURL}/list', { params });
+}
+#end
+
+/** 查询${table.classComment}详情 */
+export function get${simpleClassName}(id: number) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}>(`${baseURL}/get?id=${id}`);
+}
+
+/** 新增${table.classComment} */
+export function create${simpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.post('${baseURL}/create', data);
+}
+
+/** 修改${table.classComment} */
+export function update${simpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.put('${baseURL}/update', data);
+}
+
+/** 删除${table.classComment} */
+export function delete${simpleClassName}(id: number) {
+  return requestClient.delete(`${baseURL}/delete?id=${id}`);
+}
+
+#if ( $table.templateType != 2 && $deleteBatchEnable)
+/** 批量删除${table.classComment} */
+export function delete${simpleClassName}List(ids: number[]) {
+  return requestClient.delete(`${baseURL}/delete-list?ids=${ids.join(',')}`)
+}
+#end
+
+/** 导出${table.classComment} */
+export function export${simpleClassName}(params: any) {
+  return requestClient.download('${baseURL}/export-excel', params);
+}
+
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+  #set ($index = $foreach.count - 1)
+  #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+  #set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+  #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+  #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+  #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+  #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+  #set ($subClassNameVar = $subClassNameVars.get($index))
+
+// ==================== 子表($subTable.classComment) ====================
+
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+/** 获得${subTable.classComment}分页 */
+export function get${subSimpleClassName}Page(params: PageParam) {
+  return requestClient.get<PageResult<${simpleClassName}Api.${subSimpleClassName}>>(`${baseURL}/${subSimpleClassName_strikeCase}/page`, { params });
+}
+  ## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+#if ( $subTable.subJoinMany )
+/** 获得${subTable.classComment}列表 */
+export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}[]>(`${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+#else
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+#end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+/** 新增${subTable.classComment} */
+export function create${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
+  return requestClient.post(`${baseURL}/${subSimpleClassName_strikeCase}/create`, data);
+}
+
+/** 修改${subTable.classComment} */
+export function update${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
+  return requestClient.put(`${baseURL}/${subSimpleClassName_strikeCase}/update`, data);
+}
+
+/** 删除${subTable.classComment} */
+export function delete${subSimpleClassName}(id: number) {
+  return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete?id=${id}`);
+}
+
+#if ($deleteBatchEnable)
+/** 批量删除${subTable.classComment} */
+export function delete${subSimpleClassName}List(ids: number[]) {
+  return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}`)
+}
+#end
+
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}(id: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get?id=${id}`);
+}
+#end
+#end
+

+ 320 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/form.vue.vm

@@ -0,0 +1,320 @@
+<script lang="ts" setup>
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import type { FormRules } from 'element-plus';
+
+import { useVbenModal } from '@vben/common-ui';
+import { Tinymce as RichTextarea } from '#/components/tinymce';
+import { ImageUpload, FileUpload } from "#/components/upload";
+import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker, ElTreeSelect } from 'element-plus';
+import { DICT_TYPE, getDictOptions } from '#/utils';
+#if($table.templateType == 2)## 树表需要导入这些
+import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import { handleTree } from '@vben/utils'
+#end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #foreach ($subSimpleClassName in $subSimpleClassNames)
+  #set ($index = $foreach.count - 1)
+  #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+  import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
+  #end
+#end
+
+import { computed, ref, reactive } from 'vue';
+import { $t } from '#/locales';
+import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+const emit = defineEmits(['success']);
+
+const formRef = ref();
+const formData = ref<Partial<${simpleClassName}Api.${simpleClassName}>>({
+#foreach ($column in $columns)
+  #if ($column.createOperation || $column.updateOperation)
+    #if ($column.htmlType == "checkbox")
+        $column.javaField: [],
+    #else
+        $column.javaField: undefined,
+    #end
+  #end
+#end
+});
+const rules = reactive<FormRules>({
+  #foreach ($column in $columns)
+    #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+      #set($comment=$column.columnComment)
+        $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
+    #end
+  #end
+});
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+const ${classNameVar}Tree = ref<any[]>([]) // 树形结构
+#end
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['${table.classComment}'])
+    : $t('ui.actionTitle.create', ['${table.classComment}']);
+});
+
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #if ( $subTables && $subTables.size() > 0 )
+  /** 子表的表单 */
+  const subTabsName = ref('$subClassNameVars.get(0)')
+    #foreach ($subClassNameVar in $subClassNameVars)
+      #set ($index = $foreach.count - 1)
+      #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+      const ${subClassNameVar}FormRef = ref<InstanceType<typeof ${subSimpleClassName}Form>>()
+    #end
+  #end
+#end
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    #foreach ($column in $columns)
+      #if ($column.createOperation || $column.updateOperation)
+        #if ($column.htmlType == "checkbox")
+            $column.javaField: [],
+        #else
+            $column.javaField: undefined,
+        #end
+      #end
+    #end
+  };
+  formRef.value?.resetFields();
+}
+
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+/** 获得${table.classComment}树 */
+const get${simpleClassName}Tree = async () => {
+  ${classNameVar}Tree.value = []
+  const data = await get${simpleClassName}List({});
+  data.unshift({
+    id: 0,
+    name: '顶级${table.classComment}',
+  });
+    ${classNameVar}Tree.value = handleTree(data);
+}
+#end
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    await formRef.value?.validate();
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #if ( $subTables && $subTables.size() > 0 )
+        // 校验子表单
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          #if ($subTable.subJoinMany) ## 一对多
+            ## TODO 列表值校验?
+          #else
+            try {
+              await ${subClassNameVar}FormRef.value?.validate()
+            } catch (e) {
+              subTabsName.value = '${subClassNameVar}'
+              return
+            }
+          #end
+        #end
+      #end
+    #end
+    modalApi.lock();
+    // 提交表单
+    const data = formData.value as ${simpleClassName}Api.${simpleClassName};
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #if ( $subTables && $subTables.size() > 0 )
+        // 拼接子表的数据
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          #if ($subTable.subJoinMany)
+            data.${subClassNameVar}s = ${subClassNameVar}FormRef.value?.getData();
+          #else
+            data.${subClassNameVar} = ${subClassNameVar}FormRef.value?.getValues();
+          #end
+        #end
+      #end
+    #end
+    try {
+      await (formData.value?.id ? update${simpleClassName}(data) : create${simpleClassName}(data));
+      // 关闭并提示
+      await modalApi.close();
+      emit('success');
+      ElMessage.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      resetForm()
+      return;
+    }
+    // 加载数据
+    let data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
+    if (!data) {
+      return;
+    }
+    if (data.id) {
+      modalApi.lock();
+      try {
+        data = await get${simpleClassName}(data.id);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    formData.value = data;
+#if ( $table.templateType == 2 )
+    // 加载树数据
+    await get${simpleClassName}Tree()
+#end
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="rules"
+      label-width="120px"
+      label-position="right"
+    >
+      #foreach($column in $columns)
+        #if ($column.createOperation || $column.updateOperation)
+          #set ($dictType = $column.dictType)
+          #set ($javaField = $column.javaField)
+          #set ($javaType = $column.javaType)
+          #set ($comment = $column.columnComment)
+          #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+            #set ($dictMethod = "number")
+          #elseif ($javaType == "String")
+            #set ($dictMethod = "string")
+          #elseif ($javaType == "Boolean")
+            #set ($dictMethod = "boolean")
+          #end
+          #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="{
+            label: 'name',
+            value: 'id',
+            children: 'children',
+          }"
+                #else
+                      :props="{
+                        label: '$treeNameColumn.javaField',
+                        value: 'id',
+                        children: 'children',
+                        }"
+                #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>
+          #elseif($column.htmlType == "imageUpload")## 图片上传
+            <el-form-item label="${comment}" prop="${javaField}">
+              <ImageUpload v-model="formData.${javaField}" />
+            </el-form-item>
+          #elseif($column.htmlType == "fileUpload")## 文件上传
+            <el-form-item label="${comment}" prop="${javaField}">
+              <FileUpload v-model="formData.${javaField}" />
+            </el-form-item>
+          #elseif($column.htmlType == "editor")## 文本编辑器
+            <el-form-item label="${comment}" prop="${javaField}">
+              <RichTextarea v-model="formData.${javaField}" height="500px" />
+            </el-form-item>
+          #elseif($column.htmlType == "select")## 下拉框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
+                #if ("" != $dictType)## 有数据字典
+                  <el-option
+                          v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                          :key="dict.value"
+                          :value="dict.value"
+                          :label="dict.label"
+                  />
+                #else##没数据字典
+                  <el-option label="请选择字典生成" value="" />
+                #end
+              </el-select>
+            </el-form-item>
+          #elseif($column.htmlType == "checkbox")## 多选框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-checkbox-group v-model="formData.${javaField}">
+                #if ("" != $dictType)## 有数据字典
+                  <el-checkbox
+                          v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                          :key="dict.value"
+                          :label="dict.value"
+                 >
+                    {{ dict.label }}
+                  </el-checkbox>
+                #else##没数据字典
+                  <el-checkbox label="请选择字典生成" />
+                #end
+              </el-checkbox-group>
+            </el-form-item>
+          #elseif($column.htmlType == "radio")## 单选框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-radio-group v-model="formData.${javaField}">
+                #if ("" != $dictType)## 有数据字典
+                  <el-radio
+                          v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                          :key="dict.value"
+                          :label="dict.value"
+                  >
+                    {{ dict.label }}
+                  </el-radio>
+                #else##没数据字典
+                  <el-radio :label="1">请选择字典生成</el-radio>
+                #end
+              </el-radio-group>
+            </el-form-item>
+          #elseif($column.htmlType == "datetime")## 时间框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-date-picker
+                      v-model="formData.${javaField}"
+                      value-format="x"
+                      placeholder="选择${comment}"
+              />
+            </el-form-item>
+          #elseif($column.htmlType == "textarea")## 文本框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
+            </el-form-item>
+          #end
+        #end
+      #end
+    </el-form>
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      <!-- 子表的表单 -->
+      <el-tabs v-model="subTabsName">
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+          #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+          <el-tab-pane name="$subClassNameVar" label="${subTable.classComment}">
+            <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData?.id" />
+          </el-tab-pane>
+        #end
+      </el-tabs>
+    #end
+  </Modal>
+</template>

+ 490 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm

@@ -0,0 +1,490 @@
+<script lang="ts" setup>
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import type { VxeTableInstance } from '#/adapter/vxe-table';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { cloneDeep, formatDateTime } from '@vben/utils';
+import { ElButton, ElMessage, ElLoading, ElTabs, ElTabPane, ElPagination, ElForm, ElFormItem, ElDatePicker, ElSelect, ElOption, ElInput } from 'element-plus';
+import { DictTag } from '#/components/dict-tag';
+import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
+import ${simpleClassName}Form from './modules/form.vue';
+import { Download, Plus, RefreshCw, Search, Trash2 } from '@vben/icons';
+import { ContentWrap } from '#/components/content-wrap';
+import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
+import { TableToolbar } from '#/components/table-toolbar';
+import { useTableToolbar } from '#/hooks';
+
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 11 || $table.templateType == 12 )
+    #foreach ($subSimpleClassName in $subSimpleClassNames)
+    #set ($index = $foreach.count - 1)
+    #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+    import ${subSimpleClassName}List from './modules/${subSimpleClassName_strikeCase}-list.vue'
+    #end
+#end
+
+import { ref, h, reactive,onMounted,nextTick } from 'vue';
+import { $t } from '#/locales';
+#if (${table.templateType} == 2)## 树表接口
+import { handleTree,isEmpty } from '@vben/utils'
+import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+#else## 标准表接口
+import { isEmpty } from '@vben/utils';
+import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+#end
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+#if ($table.templateType == 12 || $table.templateType == 11) ## 内嵌和erp情况
+/** 子表的列表 */
+const subTabsName = ref('$subClassNameVars.get(0)')
+#if ($table.templateType == 11)
+const select${simpleClassName} = ref<${simpleClassName}Api.${simpleClassName}>();
+async function onCellClick({ row }: { row: ${simpleClassName}Api.${simpleClassName} }) {
+  select${simpleClassName}.value = row
+}
+#end
+#end
+
+const loading = ref(true) // 列表的加载中
+#if ( $table.templateType == 2 )
+const list = ref<any[]>([]) // 树列表的数据
+#else
+const list = ref<${simpleClassName}Api.${simpleClassName}[]>([]) // 列表的数据
+#end
+
+## 特殊:树表专属逻辑(树不需要分页接口)
+#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')
+                $column.javaField: undefined,
+        #end
+        #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
+                $column.javaField: undefined,
+        #end
+    #end
+#end
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const params = cloneDeep(queryParams) as any;
+      #foreach ($column in $columns)
+          #if ($column.listOperation)
+              #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
+                if (params.${column.javaField} && Array.isArray(params.${column.javaField})) {
+                  params.${column.javaField} = (params.${column.javaField} as string[]).join(',');
+                }
+              #end
+          #end
+      #end
+      ## 特殊:树表专属逻辑(树不需要分页接口)
+      #if ( $table.templateType == 2 )
+        list.value = await get${simpleClassName}List(params);
+      #else
+        const data = await get${simpleClassName}Page(params)
+        list.value = data.list
+        total.value = data.total
+      #end
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+#if ( $table.templateType != 2 )
+  queryParams.pageNo = 1
+#end
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: ${simpleClassName}Form,
+  destroyOnClose: true,
+});
+
+/** 创建${table.classComment} */
+function handleCreate() {
+  formModalApi.setData({}).open();
+}
+
+/** 编辑${table.classComment} */
+function handleEdit(row: ${simpleClassName}Api.${simpleClassName}) {
+  formModalApi.setData(row).open();
+}
+
+#if (${table.templateType} == 2)## 树表特有:新增下级
+/** 新增下级${table.classComment} */
+function handleAppend(row: ${simpleClassName}Api.${simpleClassName}) {
+  formModalApi.setData({ ${treeParentColumn.javaField}: row.id }).open();
+}
+#end
+
+/** 删除${table.classComment} */
+async function handleDelete(row: ${simpleClassName}Api.${simpleClassName}) {
+  const loadingInstance = ElLoading.service({
+    text: $t('ui.actionMessage.deleting', [row.id]),
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+  try {
+    await delete${simpleClassName}(row.id as number);
+    ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.id]));
+    await getList();
+  } finally {
+    loadingInstance.close();
+  }
+}
+
+#if ($table.templateType != 2 && $deleteBatchEnable)
+/** 批量删除${table.classComment} */
+async function handleDeleteBatch() {
+  const loadingInstance = ElLoading.service({
+    text: $t('ui.actionMessage.deleting'),
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+  try {
+    await delete${simpleClassName}List(checkedIds.value);
+    ElMessage.success($t('ui.actionMessage.deleteSuccess'));
+    await getList();
+  } finally {
+    loadingInstance.close();
+  }
+}
+
+const checkedIds = ref<number[]>([])
+function handleRowCheckboxChange({
+  records,
+}: {
+  records: ${simpleClassName}Api.${simpleClassName}[];
+}) {
+  checkedIds.value = records.map((item) => item.id);
+}
+#end
+
+/** 导出表格 */
+async function onExport() {
+try {
+  exportLoading.value = true;
+  const data = await export${simpleClassName}(queryParams);
+  downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
+}finally {
+  exportLoading.value = false;
+}
+}
+
+#if (${table.templateType} == 2)
+/** 切换树形展开/收缩状态 */
+const isExpanded = ref(true);
+function toggleExpand() {
+  isExpanded.value = !isExpanded.value;
+  tableRef.value?.setAllTreeExpand(isExpanded.value);
+}
+#end
+
+/** 初始化 */
+const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar();
+onMounted(() => {
+  getList();
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="getList" />
+
+    <ContentWrap v-if="!hiddenSearchBar">
+      <!-- 搜索工作栏 -->
+      <el-form
+          :model="queryParams"
+          ref="queryFormRef"
+          inline
+      >
+          #foreach($column in $columns)
+              #if ($column.listOperation)
+                  #set ($dictType = $column.dictType)
+                  #set ($javaField = $column.javaField)
+                  #set ($javaType = $column.javaType)
+                  #set ($comment = $column.columnComment)
+                  #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                      #set ($dictMethod = "number")
+                  #elseif ($javaType == "String")
+                      #set ($dictMethod = "string")
+                  #elseif ($javaType == "Boolean")
+                      #set ($dictMethod = "boolean")
+                  #end
+                  #if ($column.htmlType == "input")
+                    <el-form-item label="${comment}">
+                      <el-input
+                          v-model="queryParams.${javaField}"
+                          placeholder="请输入${comment}"
+                          clearable
+                          @keyup.enter="handleQuery"
+                           class="!w-[240px]"
+                      />
+                    </el-form-item>
+                  #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
+                    <el-form-item label="${comment}">
+                      <el-select
+                          v-model="queryParams.${javaField}"
+                          placeholder="请选择${comment}"
+                          clearable
+                           class="!w-[240px]"
+                      >
+                          #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+                            <el-option
+                                v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                                :key="dict.value"
+                                :value="dict.value"
+                                :label="dict.label"
+                            />
+                          #else## 未设置 dictType 数据字典的情况
+                            <el-option label="请选择字典生成" value="" />
+                          #end
+                      </el-select>
+                    </el-form-item>
+                  #elseif($column.htmlType == "datetime")
+                      #if ($column.listOperationCondition != "BETWEEN")## 非范围
+                        <el-form-item label="${comment}">
+                          <el-date-picker
+                              v-model="queryParams.${javaField}"
+                              value-format="YYYY-MM-DD"
+                              placeholder="选择${comment}"
+                              clearable
+                               class="!w-[240px]"
+                          />
+                        </el-form-item>
+                      #else## 范围
+                        <el-form-item label="${comment}">
+                          <el-date-picker
+                              v-model="queryParams.${javaField}"
+                              type="daterange"
+                              value-format="YYYY-MM-DD"
+                              range-separator="至"
+                              start-placeholder="开始日期"
+                              end-placeholder="结束日期"
+                              class="!w-[240px]"
+                          />
+                        </el-form-item>
+                      #end
+                  #end
+              #end
+          #end
+        <el-form-item>
+          <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
+          <el-button class="ml-2" @click="handleQuery" type="primary">
+            搜索
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 列表 -->
+    <ContentWrap title="${table.classComment}">
+      <template #extra>
+        <TableToolbar
+            ref="tableToolbarRef"
+            v-model:hidden-search="hiddenSearchBar"
+        >
+        #if (${table.templateType} == 2)
+          <el-button @click="toggleExpand" class="mr-2">
+            {{ isExpanded ? '收缩' : '展开' }}
+          </el-button>
+        #end
+          <el-button
+              class="ml-2"
+              :icon="h(Plus)"
+              type="primary"
+              @click="handleCreate"
+              v-access:code="['${permissionPrefix}:create']"
+          >
+            {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
+          </el-button>
+          <el-button
+              :icon="h(Download)"
+              type="primary"
+              class="ml-2"
+              :loading="exportLoading"
+              @click="onExport"
+              v-access:code="['${permissionPrefix}:export']"
+          >
+            {{ $t('ui.actionTitle.export') }}
+          </el-button>
+        #if ($table.templateType != 2 && $deleteBatchEnable)
+          <el-button
+              :icon="h(Trash2)"
+              type="danger"
+              class="ml-2"
+              :disabled="isEmpty(checkedIds)"
+              @click="handleDeleteBatch"
+              v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:delete']"
+          >
+            批量删除
+          </el-button>
+        #end
+        </TableToolbar>
+      </template>
+      <vxe-table
+          ref="tableRef"
+          :data="list"
+        #if ( $table.templateType == 2 )
+          :tree-config="{
+          parentField: '${treeParentColumn.javaField}',
+          rowField: 'id',
+          transform: true,
+          expandAll: true,
+          reserve: true,
+        }"
+        #end
+#if ($table.templateType == 11) ## erp情况
+          @cell-click="onCellClick"
+          :row-config="{
+            keyField: 'id',
+            isHover: true,
+            isCurrent: true,
+          }"
+#end
+          show-overflow
+          :loading="loading"
+#if ($table.templateType != 2 && $deleteBatchEnable)
+          @checkboxAll="handleRowCheckboxChange"
+          @checkboxChange="handleRowCheckboxChange"
+#end
+      >
+#if ($table.templateType != 2 && $deleteBatchEnable)
+        <vxe-column type="checkbox" width="40"></vxe-column>
+#end
+          ## 特殊:主子表专属逻辑
+          #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
+            <!-- 子表的列表 -->
+            <vxe-column type="expand" width="60">
+              <template #content="{ row }">
+                <!-- 子表的表单 -->
+                <el-tabs v-model="subTabsName" class="mx-8">
+                    #foreach ($subTable in $subTables)
+                        #set ($index = $foreach.count - 1)
+                        #set ($subClassNameVar = $subClassNameVars.get($index))
+                        #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+                        #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+                      <el-tab-pane name="$subClassNameVar" label="${subTable.classComment}">
+                        <${subSimpleClassName}List :${subJoinColumn_strikeCase}="row?.id" />
+                      </el-tab-pane>
+                    #end
+                </el-tabs>
+              </template>
+            </vxe-column>
+          #end
+          #foreach($column in $columns)
+              #if ($column.listOperationResult)
+                  #set ($dictType=$column.dictType)
+                  #set ($javaField = $column.javaField)
+                  #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+                  #set ($comment=$column.columnComment)
+                  #if ($column.javaType == "LocalDateTime")## 时间类型
+                    <vxe-column field="${javaField}" title="${comment}" align="center">
+                      <template #default="{row}">
+                        {{formatDateTime(row.${javaField})}}
+                      </template>
+                    </vxe-column>
+                  #elseif($column.dictType && "" != $column.dictType)## 数据字典
+                    <vxe-column field="${javaField}" title="${comment}" align="center">
+                      <template #default="{row}">
+                        <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="row.${javaField}" />
+                      </template>
+                    </vxe-column>
+                  #elseif ($table.templateType == 2 && $javaField == $treeNameColumn.javaField)
+                    <vxe-column field="${javaField}" title="${comment}" align="center"  tree-node/>
+                  #else
+                    <vxe-column field="${javaField}" title="${comment}" align="center" />
+                  #end
+              #end
+          #end
+        <vxe-column field="operation" title="操作" align="center">
+          <template #default="{row}">
+#if ( $table.templateType == 2 )
+  <el-button
+      size="small"
+      type="primary"
+      link
+      @click="handleAppend(row as any)"
+      v-access:code="['${permissionPrefix}:create']"
+  >
+    新增下级
+  </el-button>
+#end
+            <el-button
+                size="small"
+                type="primary"
+                link
+                @click="handleEdit(row as any)"
+                v-access:code="['${permissionPrefix}:update']"
+            >
+              {{ $t('ui.actionTitle.edit') }}
+            </el-button>
+            <el-button
+                size="small"
+                type="danger"
+                link
+                class="ml-2"
+                #if ( $table.templateType == 2 )
+                :disabled="!isEmpty(row?.children)"
+                #end
+                @click="handleDelete(row as any)"
+                v-access:code="['${permissionPrefix}:delete']"
+            >
+              {{ $t('ui.actionTitle.delete') }}
+            </el-button>
+          </template>
+        </vxe-column>
+      </vxe-table>
+#if ( $table.templateType != 2 )
+      <!-- 分页 -->
+      <div class="mt-2 flex justify-end">
+        <el-pagination
+            :total="total"
+            v-model:current-page="queryParams.pageNo"
+            v-model:page-size="queryParams.pageSize"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            @size-change="getList"
+            @current-change="getList"
+        />
+      </div>
+#end
+    </ContentWrap>
+#if ($table.templateType == 11) ## erp情况
+  <ContentWrap>
+    <!-- 子表的表单 -->
+    <el-tabs v-model="subTabsName">
+        #foreach ($subTable in $subTables)
+            #set ($index = $foreach.count - 1)
+            #set ($subClassNameVar = $subClassNameVars.get($index))
+            #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+            #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+          <el-tab-pane name="$subClassNameVar" label="${subTable.classComment}">
+            <${subSimpleClassName}List :${subJoinColumn_strikeCase}="select${simpleClassName}?.id" />
+          </el-tab-pane>
+        #end
+    </el-tabs>
+  </ContentWrap>
+#end
+  </Page>
+</template>

+ 208 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/form_sub_erp.vue.vm

@@ -0,0 +1,208 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+<script lang="ts" setup>
+  import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+  import type { FormRules } from 'element-plus';
+
+  import { useVbenModal } from '@vben/common-ui';
+  import { Tinymce as RichTextarea } from '#/components/tinymce';
+  import { ImageUpload, FileUpload } from "#/components/upload";
+  import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker, ElTreeSelect } from 'element-plus';
+  import { DICT_TYPE, getDictOptions } from '#/utils';
+
+  import { computed, ref, reactive } from 'vue';
+  import { $t } from '#/locales';
+
+  import { get${subSimpleClassName}, create${subSimpleClassName}, update${subSimpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+  const emit = defineEmits(['success']);
+  const getTitle = computed(() => {
+    return formData.value?.id
+        ? $t('ui.actionTitle.edit', ['${subTable.classComment}'])
+        : $t('ui.actionTitle.create', ['${subTable.classComment}']);
+  });
+
+  const formRef = ref();
+  const formData = ref<Partial<${simpleClassName}Api.${subSimpleClassName}>>({
+    #foreach ($column in $subColumns)
+      #if ($column.createOperation || $column.updateOperation)
+        #if ($column.htmlType == "checkbox")
+            $column.javaField: [],
+        #else
+            $column.javaField: undefined,
+        #end
+      #end
+    #end
+  });
+  const rules = reactive<FormRules>({
+    #foreach ($column in $subColumns)
+      #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+        #set($comment=$column.columnComment)
+          $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
+      #end
+    #end
+  });
+
+  const [Modal, modalApi] = useVbenModal({
+    async onConfirm() {
+      await formRef.value?.validate();
+
+      modalApi.lock();
+      // 提交表单
+      const data = formData.value as ${simpleClassName}Api.${subSimpleClassName};
+      try {
+        await (formData.value?.id ? update${subSimpleClassName}(data) : create${subSimpleClassName}(data));
+        // 关闭并提示
+        await modalApi.close();
+        emit('success');
+        ElMessage.success($t('ui.actionMessage.operationSuccess'));
+      } finally {
+        modalApi.unlock();
+      }
+    },
+    async onOpenChange(isOpen: boolean) {
+      if (!isOpen) {
+        resetForm()
+        return;
+      }
+
+      // 加载数据
+      let data = modalApi.getData<${simpleClassName}Api.${subSimpleClassName}>();
+      if (!data) {
+        return;
+      }
+      if (data.id) {
+        modalApi.lock();
+        try {
+          data = await get${subSimpleClassName}(data.id);
+        } finally {
+          modalApi.unlock();
+        }
+      }
+      // 设置到 values
+      formData.value = data;
+    },
+  });
+
+  /** 重置表单 */
+  const resetForm = () => {
+    formData.value = {
+      #foreach ($column in $subColumns)
+        #if ($column.createOperation || $column.updateOperation)
+          #if ($column.htmlType == "checkbox")
+              $column.javaField: [],
+          #else
+              $column.javaField: undefined,
+          #end
+        #end
+      #end
+    };
+    formRef.value?.resetFields();
+  }
+</script>
+
+<template>
+  <Modal :title="getTitle">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="rules"
+      label-width="120px"
+      label-position="right"
+    >
+      #foreach($column in $subColumns)
+        #if ($column.createOperation || $column.updateOperation)
+          #set ($dictType = $column.dictType)
+          #set ($javaField = $column.javaField)
+          #set ($javaType = $column.javaType)
+          #set ($comment = $column.columnComment)
+          #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+            #set ($dictMethod = "number")
+          #elseif ($javaType == "String")
+            #set ($dictMethod = "string")
+          #elseif ($javaType == "Boolean")
+            #set ($dictMethod = "boolean")
+          #end
+          #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
+            </el-form-item>
+          #elseif($column.htmlType == "imageUpload")## 图片上传
+            <el-form-item label="${comment}" prop="${javaField}">
+              <ImageUpload v-model="formData.${javaField}" />
+            </el-form-item>
+          #elseif($column.htmlType == "fileUpload")## 文件上传
+            <el-form-item label="${comment}" prop="${javaField}">
+              <FileUpload v-model="formData.${javaField}" />
+            </el-form-item>
+          #elseif($column.htmlType == "editor")## 文本编辑器
+            <el-form-item label="${comment}" prop="${javaField}">
+              <RichTextarea v-model="formData.${javaField}" height="500px" />
+            </el-form-item>
+          #elseif($column.htmlType == "select")## 下拉框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
+                #if ("" != $dictType)## 有数据字典
+                  <el-option
+                          v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                          :key="dict.value"
+                          :value="dict.value"
+                          :label="dict.label"
+                  />
+                #else##没数据字典
+                  <el-option label="请选择字典生成" value="" />
+                #end
+              </el-select>
+            </el-form-item>
+          #elseif($column.htmlType == "checkbox")## 多选框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-checkbox-group v-model="formData.${javaField}">
+                #if ("" != $dictType)## 有数据字典
+                  <el-checkbox
+                          v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                          :key="dict.value"
+                          :label="dict.value"
+                  >
+                    {{ dict.label }}
+                  </el-checkbox>
+                #else##没数据字典
+                  <el-checkbox label="请选择字典生成" />
+                #end
+              </el-checkbox-group>
+            </el-form-item>
+          #elseif($column.htmlType == "radio")## 单选框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-radio-group v-model="formData.${javaField}">
+                #if ("" != $dictType)## 有数据字典
+                  <el-radio
+                          v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                          :key="dict.value"
+                          :label="dict.value"
+                  >
+                    {{ dict.label }}
+                  </el-radio>
+                #else##没数据字典
+                  <el-radio :label="1">请选择字典生成</el-radio>
+                #end
+              </el-radio-group>
+            </el-form-item>
+          #elseif($column.htmlType == "datetime")## 时间框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-date-picker
+                      v-model="formData.${javaField}"
+                      value-format="x"
+                      placeholder="选择${comment}"
+              />
+            </el-form-item>
+          #elseif($column.htmlType == "textarea")## 文本框
+            <el-form-item label="${comment}" prop="${javaField}">
+              <el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
+            </el-form-item>
+          #end
+        #end
+      #end
+    </el-form>
+  </Modal>
+</template>

+ 2 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/form_sub_inner.vue.vm

@@ -0,0 +1,2 @@
+## 主表的 normal 和 inner 使用相同的 form 表单
+#parse("codegen/vue3_vben5_ele/general/views/modules/form_sub_normal.vue.vm")

+ 336 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/form_sub_normal.vue.vm

@@ -0,0 +1,336 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subClassNameVar = $subClassNameVars.get($subIndex))
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+<script lang="ts" setup>
+  import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+  import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElInput, ElButton, ElSelect, ElOption, ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElDatePicker } from 'element-plus';
+  import { computed, ref, reactive, h, onMounted,watch,nextTick } from 'vue';
+  import { $t } from '#/locales';
+  import { DICT_TYPE, getDictOptions } from '#/utils';
+
+#if ($subTable.subJoinMany) ## 一对多
+import type { VxeTableInstance } from '#/adapter/vxe-table';
+import { Plus } from "@vben/icons";
+import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
+import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+#else
+import type { FormRules } from 'element-plus';
+import { Tinymce as RichTextarea } from '#/components/tinymce';
+import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+#end
+
+const props = defineProps<{
+   ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
+}>()
+
+#if ($subTable.subJoinMany) ## 一对多
+const list = ref<${simpleClassName}Api.${subSimpleClassName}[]>([]) // 列表的数据
+const tableRef = ref<VxeTableInstance>();
+/** 添加${subTable.classComment} */
+const onAdd = async () => {
+  await tableRef.value?.insertAt({} as ${simpleClassName}Api.${subSimpleClassName}, -1);
+}
+
+/** 删除${subTable.classComment} */
+const onDelete =  async (row: ${simpleClassName}Api.${subSimpleClassName}) => {
+  await tableRef.value?.remove(row);
+}
+
+/** 提供获取表格数据的方法供父组件调用 */
+defineExpose({
+  getData: (): ${simpleClassName}Api.${subSimpleClassName}[] => {
+    const data = list.value as ${simpleClassName}Api.${subSimpleClassName}[];
+    const removeRecords = tableRef.value?.getRemoveRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
+    const insertRecords = tableRef.value?.getInsertRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
+    return data
+        .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
+        ?.concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
+  },
+});
+
+/** 监听主表的关联字段的变化,加载对应的子表数据 */
+watch(
+    () => props.${subJoinColumn.javaField},
+    async (val) => {
+      if (!val) {
+        return;
+      }
+      list.value = await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!);
+    },
+    { immediate: true },
+);
+#else
+const formRef = ref();
+const formData = ref<Partial<${simpleClassName}Api.${subSimpleClassName}>>({
+    #foreach ($column in $subColumns)
+        #if ($column.createOperation || $column.updateOperation)
+            #if ($column.htmlType == "checkbox")
+                    $column.javaField: [],
+            #else
+                    $column.javaField: undefined,
+            #end
+        #end
+    #end
+});
+const rules = reactive<FormRules>({
+    #foreach ($column in $subColumns)
+        #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+            #set($comment=$column.columnComment)
+                $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
+        #end
+    #end
+});
+/** 暴露出表单校验方法和表单值获取方法 */
+defineExpose({
+  validate: async () => await formRef.value?.validate(),
+  getValues: ()=> formData.value,
+});
+
+/** 监听主表的关联字段的变化,加载对应的子表数据 */
+watch(
+    () => props.${subJoinColumn.javaField},
+    async (val) => {
+      if (!val) {
+        return;
+      }
+      await nextTick();
+      formData.value = await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!);
+    },
+    { immediate: true },
+);
+#end
+</script>
+
+<template>
+#if ($subTable.subJoinMany) ## 一对多
+  <vxe-table ref="tableRef" :data="list" show-overflow class="mx-4">
+      #foreach($column in $subColumns)
+          #if ($column.createOperation || $column.updateOperation)
+              #set ($comment = $column.columnComment)
+              #set ($javaField = $column.javaField)
+              #set ($javaType = $column.javaType)
+              #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                  #set ($dictMethod = "number")
+              #elseif ($javaType == "String")
+                  #set ($dictMethod = "string")
+              #elseif ($javaType == "Boolean")
+                  #set ($dictMethod = "boolean")
+              #end
+              #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+              #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <el-input v-model="row.${javaField}" />
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "imageUpload")## 图片上传
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <ImageUpload v-model="row.${javaField}" />
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "fileUpload")## 文件上传
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <FileUpload v-model="row.${javaField}" />
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "select")## 下拉框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <el-select v-model="row.${javaField}" placeholder="请选择${comment}">
+                        #if ("" != $dictType)## 有数据字典
+                          <el-option
+                              v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                              :key="dict.value"
+                              :value="dict.value"
+                              :label="dict.label"
+                          />
+                        #else##没数据字典
+                          <el-option label="请选择字典生成" value="" />
+                        #end
+                    </el-select>
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "checkbox")## 多选框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <el-checkbox-group v-model="row.${javaField}">
+                        #if ("" != $dictType)## 有数据字典
+                          <el-checkbox
+                              v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                              :key="dict.value"
+                              :label="dict.value"
+                          >
+                            {{ dict.label }}
+                          </el-checkbox>
+                        #else##没数据字典
+                          <el-checkbox label="请选择字典生成" />
+                        #end
+                    </el-checkbox-group>
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "radio")## 单选框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <el-radio-group v-model="row.${javaField}">
+                        #if ("" != $dictType)## 有数据字典
+                          <el-radio
+                              v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                              :key="dict.value"
+                              :label="dict.value"
+                          >
+                            {{ dict.label }}
+                          </el-radio>
+                        #else##没数据字典
+                          <el-radio :label="1">请选择字典生成</el-radio>
+                        #end
+                    </el-radio-group>
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "datetime")## 时间框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <el-date-picker
+                        v-model="row.${javaField}"
+                        type="datetime"
+                        value-format="x"
+                        format="YYYY-MM-DD HH:mm:ss"
+                    />
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "textarea" || $column.htmlType == "editor")## 文本框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <el-input v-model="row.${javaField}" type="textarea" />
+                  </template>
+                </vxe-column>
+              #end
+          #end
+      #end
+    <vxe-column field="operation" title="操作" align="center">
+      <template #default="{row}">
+        <el-button
+            size="small"
+            type="danger"
+            link
+            @click="onDelete(row as any)"
+            v-access:code="['${permissionPrefix}:delete']"
+        >
+          {{ $t('ui.actionTitle.delete') }}
+        </el-button>
+      </template>
+    </vxe-column>
+  </vxe-table>
+  <div class="flex justify-center mt-4">
+    <el-button :icon="h(Plus)" type="primary" plain @click="onAdd" v-access:code="['${permissionPrefix}:create']">
+      {{ $t('ui.actionTitle.create', ['${subTable.classComment}']) }}
+    </el-button>
+  </div>
+#else
+  <el-form
+      ref="formRef"
+      class="mx-4"
+      :model="formData"
+      :rules="rules"
+      label-width="120px"
+      label-position="right"
+  >
+      #foreach($column in $subColumns)
+          #if ($column.createOperation || $column.updateOperation)
+              #set ($dictType = $column.dictType)
+              #set ($javaField = $column.javaField)
+              #set ($javaType = $column.javaType)
+              #set ($comment = $column.columnComment)
+              #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                  #set ($dictMethod = "number")
+              #elseif ($javaType == "String")
+                  #set ($dictMethod = "string")
+              #elseif ($javaType == "Boolean")
+                  #set ($dictMethod = "boolean")
+              #end
+              #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+                <el-form-item label="${comment}" prop="${javaField}">
+                  <el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
+                </el-form-item>
+              #elseif($column.htmlType == "imageUpload")## 图片上传
+                <el-form-item label="${comment}" prop="${javaField}">
+                  <ImageUpload v-model="formData.${javaField}" />
+                </el-form-item>
+              #elseif($column.htmlType == "fileUpload")## 文件上传
+                <el-form-item label="${comment}" prop="${javaField}">
+                  <FileUpload v-model="formData.${javaField}" />
+                </el-form-item>
+              #elseif($column.htmlType == "editor")## 文本编辑器
+                <el-form-item label="${comment}" prop="${javaField}">
+                  <RichTextarea v-model="formData.${javaField}" height="500px" />
+                </el-form-item>
+              #elseif($column.htmlType == "select")## 下拉框
+                <el-form-item label="${comment}" prop="${javaField}">
+                  <el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
+                      #if ("" != $dictType)## 有数据字典
+                        <el-option
+                            v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                            :key="dict.value"
+                            :value="dict.value"
+                            :label="dict.label"
+                        />
+                      #else##没数据字典
+                        <el-option label="请选择字典生成" value="" />
+                      #end
+                  </el-select>
+                </el-form-item>
+              #elseif($column.htmlType == "checkbox")## 多选框
+                <el-form-item label="${comment}" prop="${javaField}">
+                  <el-checkbox-group v-model="formData.${javaField}">
+                      #if ("" != $dictType)## 有数据字典
+                        <el-checkbox
+                            v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                            :key="dict.value"
+                            :label="dict.value"
+                        >
+                          {{ dict.label }}
+                        </el-checkbox>
+                      #else##没数据字典
+                        <el-checkbox label="请选择字典生成" />
+                      #end
+                  </el-checkbox-group>
+                </el-form-item>
+              #elseif($column.htmlType == "radio")## 单选框
+                <el-form-item label="${comment}" prop="${javaField}">
+                  <el-radio-group v-model="formData.${javaField}">
+                      #if ("" != $dictType)## 有数据字典
+                        <el-radio
+                            v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                            :key="dict.value"
+                            :label="dict.value"
+                        >
+                          {{ dict.label }}
+                        </el-radio>
+                      #else##没数据字典
+                        <el-radio :label="1">请选择字典生成</el-radio>
+                      #end
+                  </el-radio-group>
+                </el-form-item>
+              #elseif($column.htmlType == "datetime")## 时间框
+                <el-form-item label="${comment}" prop="${javaField}">
+                  <el-date-picker
+                      v-model="formData.${javaField}"
+                      value-format="x"
+                      placeholder="选择${comment}"
+                  />
+                </el-form-item>
+              #elseif($column.htmlType == "textarea")## 文本框
+                <el-form-item label="${comment}" prop="${javaField}">
+                  <el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
+                </el-form-item>
+              #end
+          #end
+      #end
+  </el-form>
+#end
+</template>

+ 426 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm

@@ -0,0 +1,426 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex))
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+<script lang="ts" setup>
+  import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+  import type { VxeTableInstance } from '#/adapter/vxe-table';
+
+  import { DictTag } from '#/components/dict-tag';
+  import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
+  import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
+  import { reactive,ref, h, nextTick,watch,onMounted } from 'vue';
+  import { cloneDeep, formatDateTime } from '@vben/utils';
+  import { ContentWrap } from '#/components/content-wrap';
+
+#if ($table.templateType == 11) ## erp
+    import { useVbenModal } from '@vben/common-ui';
+    import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
+    import { Tinymce as RichTextarea } from '#/components/tinymce';
+    import { ImageUpload, FileUpload } from "#/components/upload";
+    import { ElMessage, ElLoading, ElButton, ElTabs, ElTabPane, ElPagination, ElForm, ElFormItem, ElDatePicker, ElSelect, ElOption, ElInput } from 'element-plus';
+    import { Plus, Trash2 } from '@vben/icons';
+    import { $t } from '#/locales';
+    import { TableToolbar } from '#/components/table-toolbar';
+    import { useTableToolbar } from '#/hooks';
+#end
+
+#if ($table.templateType == 11) ## erp
+    import { delete${subSimpleClassName},#if ($deleteBatchEnable) delete${subSimpleClassName}List,#end get${subSimpleClassName}Page } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+    import { isEmpty } from '@vben/utils';
+  #else
+  #if ($subTable.subJoinMany) ## 一对多
+  import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+  #else
+  import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+  #end
+#end
+
+const props = defineProps<{
+      ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
+}>()
+
+#if ($table.templateType == 11) ## erp
+  const [FormModal, formModalApi] = useVbenModal({
+    connectedComponent: ${subSimpleClassName}Form,
+    destroyOnClose: true,
+  });
+
+/** 创建${subTable.classComment} */
+function handleCreate() {
+  if (!props.${subJoinColumn.javaField}){
+    ElMessage.warning("请先选择一个${table.classComment}!")
+    return
+  }
+  formModalApi.setData({${subJoinColumn.javaField}: props.${subJoinColumn.javaField}}).open();
+}
+
+/** 编辑${subTable.classComment} */
+function handleEdit(row: ${simpleClassName}Api.${subSimpleClassName}) {
+  formModalApi.setData(row).open();
+}
+
+/** 删除${subTable.classComment} */
+async function handleDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
+  const loadingInstance = ElLoading.service({
+    text: $t('ui.actionMessage.deleting', [row.id]),
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+  try {
+    await delete${subSimpleClassName}(row.id as number);
+    ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.id]));
+    await getList();
+  } finally {
+    loadingInstance.close();
+  }
+}
+
+#if ($deleteBatchEnable)
+/** 批量删除${subTable.classComment} */
+async function handleDeleteBatch() {
+  const loadingInstance = ElLoading.service({
+    text: $t('ui.actionMessage.deleting'),
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+  try {
+    await delete${subSimpleClassName}List(checkedIds.value);
+    ElMessage.success($t('ui.actionMessage.deleteSuccess'));
+    await getList();
+  } finally {
+    loadingInstance.close();
+  }
+}
+
+const checkedIds = ref<number[]>([])
+function handleRowCheckboxChange({
+  records,
+}: {
+  records: ${simpleClassName}Api.${subSimpleClassName}[];
+}) {
+  checkedIds.value = records.map((item) => item.id);
+}
+#end
+#end
+
+  const loading = ref(true) // 列表的加载中
+  const list = ref<${simpleClassName}Api.${subSimpleClassName}[]>([]) // 列表的数据
+#if ($table.templateType == 11) ## erp
+  const total = ref(0) // 列表的总页数
+#end
+#if ($table.templateType == 11) ## erp
+  const queryFormRef = ref() // 搜索的表单
+  const queryParams = reactive({
+      pageNo: 1,
+      pageSize: 10,
+      #foreach ($column in $subColumns)
+          #if ($column.listOperation)
+              #if ($column.listOperationCondition != 'BETWEEN')
+                      $column.javaField: undefined,
+              #end
+              #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
+                      $column.javaField: undefined,
+              #end
+          #end
+      #end
+  })
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+#end
+  /** 查询列表 */
+  const getList = async () => {
+    loading.value = true
+    try {
+      if (!props.${subJoinColumn.javaField}){
+        return []
+      }
+        ## 特殊:树表专属逻辑(树不需要分页接口)
+        #if ($table.templateType == 11) ## erp
+          const params = cloneDeep(queryParams) as any;
+            #foreach ($column in $columns)
+                #if ($column.listOperation)
+                    #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
+                      if (params.${column.javaField} && Array.isArray(params.${column.javaField})) {
+                        params.${column.javaField} = (params.${column.javaField} as string[]).join(',');
+                      }
+                    #end
+                #end
+            #end
+          params.${subJoinColumn.javaField} = props.${subJoinColumn.javaField};
+          const data = await get${subSimpleClassName}Page(params)
+          list.value = data.list
+          total.value = data.total
+        #else
+            #if ($subTable.subJoinMany) ## 一对多
+             list.value = await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!);
+            #else
+             list.value = [await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!)];
+            #end
+        #end
+    } finally {
+      loading.value = false
+    }
+  }
+
+  /** 监听主表的关联字段的变化,加载对应的子表数据 */
+  watch(
+      () => props.${subJoinColumn.javaField},
+      async (val) => {
+        if (!val) {
+          return;
+        }
+        await nextTick();
+        await getList()
+      },
+      { immediate: true },
+  );
+
+#if ($table.templateType == 11) ## erp
+/** 初始化 */
+const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar();
+onMounted(() => {
+  getList();
+});
+#end
+</script>
+
+<template>
+    #if ($table.templateType == 11) ## erp
+      <FormModal @success="getList" />
+      <div class="h-[600px]">
+        <ContentWrap v-if="!hiddenSearchBar">
+          <!-- 搜索工作栏 -->
+          <el-form
+              :model="queryParams"
+              ref="queryFormRef"
+              inline
+          >
+              #foreach($column in $subColumns)
+                  #if ($column.listOperation)
+                      #set ($dictType = $column.dictType)
+                      #set ($javaField = $column.javaField)
+                      #set ($javaType = $column.javaType)
+                      #set ($comment = $column.columnComment)
+                      #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                          #set ($dictMethod = "number")
+                      #elseif ($javaType == "String")
+                          #set ($dictMethod = "string")
+                      #elseif ($javaType == "Boolean")
+                          #set ($dictMethod = "boolean")
+                      #end
+                      #if ($column.htmlType == "input")
+                        <el-form-item label="${comment}">
+                          <el-input
+                              v-model="queryParams.${javaField}"
+                              placeholder="请输入${comment}"
+                              clearable
+                              @keyup.enter="handleQuery"
+                              class="!w-[240px]"
+                          />
+                        </el-form-item>
+                      #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
+                        <el-form-item label="${comment}">
+                          <el-select
+                              v-model="queryParams.${javaField}"
+                              placeholder="请选择${comment}"
+                              clearable
+                              class="!w-[240px]"
+                          >
+                              #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+                                <el-option
+                                    v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                                    :key="dict.value"
+                                    :value="dict.value"
+                                    :label="dict.label"
+                                />
+                              #else## 未设置 dictType 数据字典的情况
+                                <el-option label="请选择字典生成" value="" />
+                              #end
+                          </el-select>
+                        </el-form-item>
+                      #elseif($column.htmlType == "datetime")
+                          #if ($column.listOperationCondition != "BETWEEN")## 非范围
+                            <el-form-item label="${comment}">
+                              <el-date-picker
+                                  v-model="queryParams.${javaField}"
+                                  value-format="YYYY-MM-DD"
+                                  placeholder="选择${comment}"
+                                  clearable
+                                  class="!w-[240px]"
+                              />
+                            </el-form-item>
+                          #else## 范围
+                            <el-form-item label="${comment}">
+                              <el-date-picker
+                                  v-model="queryParams.${javaField}"
+                                  type="daterange"
+                                  value-format="YYYY-MM-DD"
+                                  range-separator="至"
+                                  start-placeholder="开始日期"
+                                  end-placeholder="结束日期"
+                                  class="!w-[240px]"
+                              />
+                            </el-form-item>
+                          #end
+                      #end
+                  #end
+              #end
+            <el-form-item>
+              <el-button class="ml-2" @click="resetQuery"> 重置 </el-button>
+              <el-button class="ml-2" @click="handleQuery" type="primary">
+                搜索
+              </el-button>
+            </el-form-item>
+          </el-form>
+        </ContentWrap>
+
+        <!-- 列表 -->
+        <ContentWrap title="${table.classComment}">
+          <template #extra>
+            <TableToolbar
+                ref="tableToolbarRef"
+                v-model:hidden-search="hiddenSearchBar"
+            >
+              <el-button
+                  class="ml-2"
+                  :icon="h(Plus)"
+                  type="primary"
+                  @click="handleCreate"
+                  v-access:code="['${permissionPrefix}:create']"
+              >
+                {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
+              </el-button>
+                #if ($deleteBatchEnable)
+                  <el-button
+                      :icon="h(Trash2)"
+                      type="danger"
+                      class="ml-2"
+                      :disabled="isEmpty(checkedIds)"
+                      @click="handleDeleteBatch"
+                      v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:delete']"
+                  >
+                    批量删除
+                  </el-button>
+                #end
+            </TableToolbar>
+          </template>
+          <vxe-table
+              ref="tableRef"
+              :data="list"
+              show-overflow
+              :loading="loading"
+              #if ($deleteBatchEnable)
+              @checkboxAll="handleRowCheckboxChange"
+              @checkboxChange="handleRowCheckboxChange"
+              #end
+          >
+              #if ($deleteBatchEnable)
+                <vxe-column type="checkbox" width="40"></vxe-column>
+              #end
+              #foreach($column in $subColumns)
+                  #if ($column.listOperationResult)
+                      #set ($dictType=$column.dictType)
+                      #set ($javaField = $column.javaField)
+                      #set ($comment=$column.columnComment)
+                      #if ($column.javaType == "LocalDateTime")## 时间类型
+                        <vxe-column field="${javaField}" title="${comment}" align="center">
+                          <template #default="{row}">
+                            {{formatDateTime(row.${javaField})}}
+                          </template>
+                        </vxe-column>
+                      #elseif($column.dictType && "" != $column.dictType)## 数据字典
+                        <vxe-column field="${javaField}" title="${comment}" align="center">
+                          <template #default="{row}">
+                            <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="row.${javaField}" />
+                          </template>
+                        </vxe-column>
+                      #elseif ($table.templateType == 2 && $javaField == $treeNameColumn.javaField)
+                        <vxe-column field="${javaField}" title="${comment}" align="center"  tree-node/>
+                      #else
+                        <vxe-column field="${javaField}" title="${comment}" align="center" />
+                      #end
+                  #end
+              #end
+            <vxe-column field="operation" title="操作" align="center">
+              <template #default="{row}">
+                <el-button
+                    size="small"
+                    type="primary"
+                    link
+                    @click="handleEdit(row as any)"
+                    v-access:code="['${permissionPrefix}:update']"
+                >
+                  {{ $t('ui.actionTitle.edit') }}
+                </el-button>
+                <el-button
+                    size="small"
+                    type="danger"
+                    link
+                    class="ml-2"
+                    @click="handleDelete(row as any)"
+                    v-access:code="['${permissionPrefix}:delete']"
+                >
+                  {{ $t('ui.actionTitle.delete') }}
+                </el-button>
+              </template>
+            </vxe-column>
+          </vxe-table>
+          <!-- 分页 -->
+          <div class="mt-2 flex justify-end">
+            <el-pagination
+                :total="total"
+                v-model:current-page="queryParams.pageNo"
+                v-model:page-size="queryParams.pageSize"
+                :page-sizes="[10, 20, 50, 100]"
+                layout="total, sizes, prev, pager, next, jumper"
+                @size-change="getList"
+                @current-change="getList"
+            />
+          </div>
+        </ContentWrap>
+      </div>
+    #else
+    <ContentWrap title="${subTable.classComment}列表">
+      <vxe-table
+          :data="list"
+          show-overflow
+          :loading="loading"
+      >
+          #foreach($column in $subColumns)
+              #if ($column.listOperationResult)
+                  #set ($dictType=$column.dictType)
+                  #set ($javaField = $column.javaField)
+                  #set ($comment=$column.columnComment)
+                  #if ($column.javaType == "LocalDateTime")## 时间类型
+                    <vxe-column field="${javaField}" title="${comment}" align="center">
+                      <template #default="{row}">
+                        {{formatDateTime(row.${javaField})}}
+                      </template>
+                    </vxe-column>
+                  #elseif($column.dictType && "" != $column.dictType)## 数据字典
+                    <vxe-column field="${javaField}" title="${comment}" align="center">
+                      <template #default="{row}">
+                        <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="row.${javaField}" />
+                      </template>
+                    </vxe-column>
+                  #else
+                    <vxe-column field="${javaField}" title="${comment}" align="center" />
+                  #end
+              #end
+          #end
+      </vxe-table>
+    </ContentWrap>
+    #end
+</template>

+ 4 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_inner.vue.vm

@@ -0,0 +1,4 @@
+## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
+## 1)inner 使用 list 不分页,erp 使用 page 分页
+## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
+#parse("codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm")

+ 167 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/api/api.ts.vm

@@ -0,0 +1,167 @@
+import type { PageParam, PageResult } from '@vben/request';
+import type { Dayjs } from 'dayjs';
+
+import { requestClient } from '#/api/request';
+#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
+
+export namespace ${simpleClassName}Api {
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+  #set ($index = $foreach.count - 1)
+  #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+  #set ($subColumns = $subColumnsList.get($index))##当前字段数组
+  /** ${subTable.classComment}信息 */
+  export interface ${subSimpleClassName} {
+    #foreach ($column in $subColumns)
+      #if ($column.createOperation || $column.updateOperation)
+        #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+        #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
+        #else
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
+        #end
+      #end
+    #end
+  }
+
+#end
+  /** ${table.classComment}信息 */
+  export interface ${simpleClassName} {
+#foreach ($column in $columns)
+#if ($column.createOperation || $column.updateOperation)
+#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
+#else
+    ${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
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #foreach ($subTable in $subTables)
+    #set ($index = $foreach.count - 1)
+    #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+    #if ( $subTable.subJoinMany )
+        ${subSimpleClassName.toLowerCase()}s?: ${subSimpleClassName}[]
+    #else
+        ${subSimpleClassName.toLowerCase()}?: ${subSimpleClassName}
+    #end
+  #end
+#end
+  }
+}
+
+#if ( $table.templateType != 2 )
+/** 查询${table.classComment}分页 */
+export function get${simpleClassName}Page(params: PageParam) {
+  return requestClient.get<PageResult<${simpleClassName}Api.${simpleClassName}>>('${baseURL}/page', { params });
+}
+#else
+/** 查询${table.classComment}列表 */
+export function get${simpleClassName}List(params: any) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}[]>('${baseURL}/list', { params });
+}
+#end
+
+/** 查询${table.classComment}详情 */
+export function get${simpleClassName}(id: number) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}>(`${baseURL}/get?id=${id}`);
+}
+
+/** 新增${table.classComment} */
+export function create${simpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.post('${baseURL}/create', data);
+}
+
+/** 修改${table.classComment} */
+export function update${simpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.put('${baseURL}/update', data);
+}
+
+/** 删除${table.classComment} */
+export function delete${simpleClassName}(id: number) {
+  return requestClient.delete(`${baseURL}/delete?id=${id}`);
+}
+
+#if ( $table.templateType != 2 && $deleteBatchEnable)
+/** 批量删除${table.classComment} */
+export function delete${simpleClassName}List(ids: number[]) {
+  return requestClient.delete(`${baseURL}/delete-list?ids=${ids.join(',')}`)
+}
+#end
+
+/** 导出${table.classComment} */
+export function export${simpleClassName}(params: any) {
+  return requestClient.download('${baseURL}/export-excel', params);
+}
+
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+#set ($subClassNameVar = $subClassNameVars.get($index))
+
+// ==================== 子表($subTable.classComment) ====================
+
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+/** 获得${subTable.classComment}分页 */
+export function get${subSimpleClassName}Page(params: PageParam) {
+  return requestClient.get<PageResult<${simpleClassName}Api.${subSimpleClassName}>>(`${baseURL}/${subSimpleClassName_strikeCase}/page`, { params });
+}
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+  #if ( $subTable.subJoinMany )
+/** 获得${subTable.classComment}列表 */
+export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}[]>(`${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+  #else
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+  #end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+/** 新增${subTable.classComment} */
+export function create${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
+  return requestClient.post(`${baseURL}/${subSimpleClassName_strikeCase}/create`, data);
+}
+
+/** 修改${subTable.classComment} */
+export function update${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
+  return requestClient.put(`${baseURL}/${subSimpleClassName_strikeCase}/update`, data);
+}
+
+/** 删除${subTable.classComment} */
+export function delete${subSimpleClassName}(id: number) {
+  return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete?id=${id}`);
+}
+
+#if ($deleteBatchEnable)
+/** 批量删除${subTable.classComment} */
+export function delete${subSimpleClassName}List(ids: number[]) {
+  return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete-list?ids=${ids.join(',')}`)
+}
+#end
+
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}(id: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get?id=${id}`);
+}
+#end
+#end
+

+ 605 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/data.ts.vm

@@ -0,0 +1,605 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
+
+import { z } from '#/adapter/form';
+import {
+    DICT_TYPE,
+    getDictOptions,
+    getRangePickerDefaultProps,
+} from '#/utils';
+#if(${table.templateType} == 2)## 树表需要导入这些
+import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}';
+import { handleTree } from '@vben/utils';
+#end
+
+/** 新增/修改的表单 */
+export function useFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        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);
+        },
+        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 && ($table.templateType != 2 || ($table.templateType == 2 && $column.id != $treeParentColumn.id)))## 树表中已经添加了父ID字段,这里排除
+  #set ($dictType = $column.dictType)
+  #set ($javaType = $column.javaType)
+  #set ($javaField = $column.javaField)
+  #set ($comment = $column.columnComment)
+  #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+    #set ($dictMethod = "number")
+  #elseif ($javaType == "String")
+    #set ($dictMethod = "string")
+  #elseif ($javaType == "Boolean")
+    #set ($dictMethod = "boolean")
+  #end
+    {
+      fieldName: '${javaField}',
+      label: '${comment}',
+  #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+      rules: 'required',
+  #end
+  #if ($column.htmlType == "input")
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入${comment}',
+      },
+  #elseif($column.htmlType == "imageUpload")## 图片上传
+      component: 'ImageUpload',
+  #elseif($column.htmlType == "fileUpload")## 文件上传
+      component: 'FileUpload',
+  #elseif($column.htmlType == "editor")## 文本编辑器
+      component: 'RichTextarea',
+  #elseif($column.htmlType == "select")## 下拉框
+      component: 'Select',
+      componentProps: {
+        #if ("" != $dictType)## 有数据字典
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else##没数据字典
+        options: [],
+        #end
+        placeholder: '请选择${comment}',
+      },
+  #elseif($column.htmlType == "checkbox")## 多选框
+      component: 'Checkbox',
+      componentProps: {
+        #if ("" != $dictType)## 有数据字典
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else##没数据字典
+        options: [],
+        #end
+      },
+  #elseif($column.htmlType == "radio")## 单选框
+      component: 'RadioGroup',
+      componentProps: {
+        #if ("" != $dictType)## 有数据字典
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else##没数据字典
+        options: [],
+        #end
+        buttonStyle: 'solid',
+        optionType: 'button',
+      },
+  #elseif($column.htmlType == "datetime")## 时间框
+      component: 'DatePicker',
+      componentProps: {
+        showTime: true,
+        format: 'YYYY-MM-DD HH:mm:ss',
+        valueFormat: 'x',
+      },
+  #elseif($column.htmlType == "textarea")## 文本域
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '请输入${comment}',
+      },
+  #elseif($column.htmlType == "inputNumber")## 数字输入框
+      component: 'InputNumber',
+      componentProps: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '请输入${comment}',
+      },
+  #end
+    },
+#end
+#end
+#end
+  ];
+}
+
+/** 列表的搜索表单 */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+#foreach($column in $columns)
+#if ($column.listOperation)
+  #set ($dictType = $column.dictType)
+  #set ($javaType = $column.javaType)
+  #set ($javaField = $column.javaField)
+  #set ($comment = $column.columnComment)
+  #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+    #set ($dictMethod = "number")
+  #elseif ($javaType == "String")
+    #set ($dictMethod = "string")
+  #elseif ($javaType == "Boolean")
+    #set ($dictMethod = "boolean")
+  #end
+    {
+      fieldName: '${javaField}',
+      label: '${comment}',
+  #if ($column.htmlType == "input" || $column.htmlType == "textarea" || $column.htmlType == "editor")
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '请输入${comment}',
+      },
+  #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else## 未设置 dictType 数据字典的情况
+        options: [],
+        #end
+        placeholder: '请选择${comment}',
+      },
+  #elseif($column.htmlType == "datetime")
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+  #end
+    },
+#end
+#end
+  ];
+}
+
+/** 列表的字段 */
+export function useGridColumns(): VxeTableGridOptions<${simpleClassName}Api.${simpleClassName}>['columns'] {
+  return [
+#if ($table.templateType != 2 && $deleteBatchEnable)
+  { type: 'checkbox', width: 40 },
+#end
+#if ($table.templateType == 12) ## 内嵌情况
+      { type: 'expand', width: 80, slots: { content: 'expand_content' } },
+#end
+#foreach($column in $columns)
+#if ($column.listOperationResult)
+  #set ($dictType = $column.dictType)
+  #set ($javaField = $column.javaField)
+  #set ($comment = $column.columnComment)
+    {
+      field: '${javaField}',
+      title: '${comment}',
+      minWidth: 120,
+  #if ($column.javaType == "LocalDateTime")## 时间类型
+      formatter: 'formatDateTime',
+  #elseif("" != $dictType)## 数据字典
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.$dictType.toUpperCase() },
+      },
+  #end
+  #if (${table.templateType} == 2 && $column.id == $treeNameColumn.id)## 树表特有:标记树节点列
+      treeNode: true,
+  #end
+    },
+#end
+#end
+    {
+      title: '操作',
+      width: 200,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+## 标准模式和内嵌模式时,主子关系一对一则生成表单schema,一对多则生成列表schema(内嵌模式时表单schema也要生成)。erp 模式时都生成
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+    #set ($index = $foreach.count - 1)
+    #set ($subColumns = $subColumnsList.get($index))##当前字段数组
+    #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+    #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+    #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+// ==================== 子表($subTable.classComment) ====================
+
+#if ($table.templateType == 11) ## erp 情况
+/** 新增/修改的表单 */
+export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
+    return [
+        {
+            fieldName: 'id',
+            component: 'Input',
+            dependencies: {
+                triggerFields: [''],
+                show: () => false,
+            },
+        },
+        #foreach($column in $subColumns)
+            #if ($column.createOperation || $column.updateOperation)
+                #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)
+                    #set ($comment = $column.columnComment)
+                    #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                        #set ($dictMethod = "number")
+                    #elseif ($javaType == "String")
+                        #set ($dictMethod = "string")
+                    #elseif ($javaType == "Boolean")
+                        #set ($dictMethod = "boolean")
+                    #end
+                    #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+                    #else
+                        {
+                            fieldName: '${javaField}',
+                            label: '${comment}',
+                            #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+                                rules: 'required',
+                            #end
+                            #if ($column.htmlType == "input")
+                                component: 'Input',
+                                componentProps: {
+                                    placeholder: '请输入${comment}',
+                                },
+                            #elseif($column.htmlType == "imageUpload")## 图片上传
+                                component: 'ImageUpload',
+                            #elseif($column.htmlType == "fileUpload")## 文件上传
+                                component: 'FileUpload',
+                            #elseif($column.htmlType == "editor")## 文本编辑器
+                                component: 'RichTextarea',
+                            #elseif($column.htmlType == "select")## 下拉框
+                                component: 'Select',
+                                componentProps: {
+                                    #if ("" != $dictType)## 有数据字典
+                                        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                    #else##没数据字典
+                                        options: [],
+                                    #end
+                                    placeholder: '请选择${comment}',
+                                },
+                            #elseif($column.htmlType == "checkbox")## 多选框
+                                component: 'Checkbox',
+                                componentProps: {
+                                    #if ("" != $dictType)## 有数据字典
+                                        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                    #else##没数据字典
+                                        options: [],
+                                    #end
+                                },
+                            #elseif($column.htmlType == "radio")## 单选框
+                                component: 'RadioGroup',
+                                componentProps: {
+                                    #if ("" != $dictType)## 有数据字典
+                                        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                    #else##没数据字典
+                                        options: [],
+                                    #end
+                                    buttonStyle: 'solid',
+                                    optionType: 'button',
+                                },
+                            #elseif($column.htmlType == "datetime")## 时间框
+                                component: 'DatePicker',
+                                componentProps: {
+                                    showTime: true,
+                                    format: 'YYYY-MM-DD HH:mm:ss',
+                                    valueFormat: 'x',
+                                },
+                            #elseif($column.htmlType == "textarea")## 文本域
+                                component: 'Textarea',
+                                componentProps: {
+                                    placeholder: '请输入${comment}',
+                                },
+                            #elseif($column.htmlType == "inputNumber")## 数字输入框
+                                component: 'InputNumber',
+                                componentProps: {
+                                    min: 0,
+                                    controlsPosition: 'right',
+                                    placeholder: '请输入${comment}',
+                                },
+                            #end
+                        },
+                    #end
+                #end
+            #end
+        #end
+    ];
+}
+
+/** 列表的搜索表单 */
+export function use${subSimpleClassName}GridFormSchema(): VbenFormSchema[] {
+    return [
+        #foreach($column in $subColumns)
+            #if ($column.listOperation)
+                #set ($dictType = $column.dictType)
+                #set ($javaType = $column.javaType)
+                #set ($javaField = $column.javaField)
+                #set ($comment = $column.columnComment)
+                #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                    #set ($dictMethod = "number")
+                #elseif ($javaType == "String")
+                    #set ($dictMethod = "string")
+                #elseif ($javaType == "Boolean")
+                    #set ($dictMethod = "boolean")
+                #end
+                {
+                    fieldName: '${javaField}',
+                    label: '${comment}',
+                    #if ($column.htmlType == "input" || $column.htmlType == "textarea" || $column.htmlType == "editor")
+                        component: 'Input',
+                        componentProps: {
+                            allowClear: true,
+                            placeholder: '请输入${comment}',
+                        },
+                    #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
+                        component: 'Select',
+                        componentProps: {
+                            allowClear: true,
+                            #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+                                options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                            #else## 未设置 dictType 数据字典的情况
+                                options: [],
+                            #end
+                            placeholder: '请选择${comment}',
+                        },
+                    #elseif($column.htmlType == "datetime")
+                        component: 'RangePicker',
+                        componentProps: {
+                            ...getRangePickerDefaultProps(),
+                            allowClear: true,
+                        },
+                    #end
+                },
+            #end
+        #end
+    ];
+}
+
+/** 列表的字段 */
+export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
+    return [
+        #if ($table.templateType != 2 && $deleteBatchEnable)
+            { type: 'checkbox', width: 40 },
+        #end
+        #foreach($column in $subColumns)
+            #if ($column.listOperationResult)
+                #set ($dictType = $column.dictType)
+                #set ($javaField = $column.javaField)
+                #set ($comment = $column.columnComment)
+                {
+                    field: '${javaField}',
+                    title: '${comment}',
+                    minWidth: 120,
+                    #if ($column.javaType == "LocalDateTime")## 时间类型
+                        formatter: 'formatDateTime',
+                    #elseif("" != $dictType)## 数据字典
+                        cellRender: {
+                            name: 'CellDict',
+                            props: { type: DICT_TYPE.$dictType.toUpperCase() },
+                        },
+                    #end
+                },
+            #end
+        #end
+        {
+            title: '操作',
+            width: 200,
+            fixed: 'right',
+            slots: { default: 'actions' },
+        },
+    ];
+}
+
+#else
+    #if ($subTable.subJoinMany) ## 一对多
+    /** 新增/修改列表的字段 */
+    export function use${subSimpleClassName}GridEditColumns(): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
+        return [
+            #foreach($column in $subColumns)
+                #if ($column.createOperation || $column.updateOperation)
+                    #if (!$column.primaryKey && $column.listOperationResult && $column.id != $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+                        #set ($dictType = $column.dictType)
+                        #set ($javaField = $column.javaField)
+                        #set ($comment = $column.columnComment)
+                        #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                            #set ($dictMethod = "number")
+                        #elseif ($javaType == "String")
+                            #set ($dictMethod = "string")
+                        #elseif ($javaType == "Boolean")
+                            #set ($dictMethod = "boolean")
+                        #end
+                        {
+                            field: '${javaField}',
+                            title: '${comment}',
+                            minWidth: 120,
+                            slots: { default: '${javaField}' },
+                            #if ($column.htmlType == "select" || $column.htmlType == "checkbox" || $column.htmlType == "radio")
+                                #if ("" != $dictType)## 有数据字典
+                                    params: {
+                                        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                    },
+                                #else
+                                    params: {
+                                        options: [],
+                                    },
+                                #end
+                            #end
+                        },
+                    #end
+                #end
+            #end
+            {
+                title: '操作',
+                width: 200,
+                fixed: 'right',
+                slots: { default: 'actions' },
+            },
+        ];
+    }
+
+    #else
+    /** 新增/修改的表单 */
+    export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
+        return [
+            {
+                fieldName: 'id',
+                component: 'Input',
+                dependencies: {
+                    triggerFields: [''],
+                    show: () => false,
+                },
+            },
+            #foreach($column in $subColumns)
+                #if ($column.createOperation || $column.updateOperation)
+                    #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)
+                        #set ($comment = $column.columnComment)
+                        #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                            #set ($dictMethod = "number")
+                        #elseif ($javaType == "String")
+                            #set ($dictMethod = "string")
+                        #elseif ($javaType == "Boolean")
+                            #set ($dictMethod = "boolean")
+                        #end
+                        #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+                        #else
+                            {
+                                fieldName: '${javaField}',
+                                label: '${comment}',
+                                #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+                                    rules: 'required',
+                                #end
+                                #if ($column.htmlType == "input")
+                                    component: 'Input',
+                                    componentProps: {
+                                        placeholder: '请输入${comment}',
+                                    },
+                                #elseif($column.htmlType == "imageUpload")## 图片上传
+                                    component: 'ImageUpload',
+                                #elseif($column.htmlType == "fileUpload")## 文件上传
+                                    component: 'FileUpload',
+                                #elseif($column.htmlType == "editor")## 文本编辑器
+                                    component: 'RichTextarea',
+                                #elseif($column.htmlType == "select")## 下拉框
+                                    component: 'Select',
+                                    componentProps: {
+                                        #if ("" != $dictType)## 有数据字典
+                                            options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                        #else##没数据字典
+                                            options: [],
+                                        #end
+                                        placeholder: '请选择${comment}',
+                                    },
+                                #elseif($column.htmlType == "checkbox")## 多选框
+                                    component: 'Checkbox',
+                                    componentProps: {
+                                        #if ("" != $dictType)## 有数据字典
+                                            options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                        #else##没数据字典
+                                            options: [],
+                                        #end
+                                    },
+                                #elseif($column.htmlType == "radio")## 单选框
+                                    component: 'RadioGroup',
+                                    componentProps: {
+                                        #if ("" != $dictType)## 有数据字典
+                                            options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                        #else##没数据字典
+                                            options: [],
+                                        #end
+                                        buttonStyle: 'solid',
+                                        optionType: 'button',
+                                    },
+                                #elseif($column.htmlType == "datetime")## 时间框
+                                    component: 'DatePicker',
+                                    componentProps: {
+                                        showTime: true,
+                                        format: 'YYYY-MM-DD HH:mm:ss',
+                                        valueFormat: 'x',
+                                    },
+                                #elseif($column.htmlType == "textarea")## 文本域
+                                    component: 'Textarea',
+                                    componentProps: {
+                                        placeholder: '请输入${comment}',
+                                    },
+                                #elseif($column.htmlType == "inputNumber")## 数字输入框
+                                    component: 'InputNumber',
+                                    componentProps: {
+                                        min: 0,
+                                        controlsPosition: 'right',
+                                        placeholder: '请输入${comment}',
+                                    },
+                                #end
+                            },
+                        #end
+                    #end
+                #end
+            #end
+        ];
+    }
+
+    #end
+    #if ($table.templateType == 12) ## 内嵌情况
+    /** 列表的字段 */
+    export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
+        return [
+            #foreach($column in $subColumns)
+                #if ($column.listOperationResult)
+                    #set ($dictType = $column.dictType)
+                    #set ($javaField = $column.javaField)
+                    #set ($comment = $column.columnComment)
+                    {
+                        field: '${javaField}',
+                        title: '${comment}',
+                        minWidth: 120,
+                        #if ($column.javaType == "LocalDateTime")## 时间类型
+                            formatter: 'formatDateTime',
+                        #elseif("" != $dictType)## 数据字典
+                            cellRender: {
+                                name: 'CellDict',
+                                props: { type: DICT_TYPE.$dictType.toUpperCase() },
+                            },
+                        #end
+                    },
+                #end
+            #end
+        ];
+    }
+    #end
+#end
+#end

+ 154 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/form.vue.vm

@@ -0,0 +1,154 @@
+<script lang="ts" setup>
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
+
+import { useVbenModal } from '@vben/common-ui';
+import { ElMessage, ElTabs, ElTabPane, ElCheckbox, ElInput, ElSelect, ElRadioGroup, ElCheckboxGroup, ElDatePicker } from 'element-plus';
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #foreach ($subSimpleClassName in $subSimpleClassNames)
+  #set ($index = $foreach.count - 1)
+  #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+  import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
+  #end
+#end
+
+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}/${table.businessName}';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<${simpleClassName}Api.${simpleClassName}>();
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['${table.classComment}'])
+    : $t('ui.actionTitle.create', ['${table.classComment}']);
+});
+
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #if ( $subTables && $subTables.size() > 0 )
+
+  /** 子表的表单 */
+  const subTabsName = ref('$subClassNameVars.get(0)')
+    #foreach ($subClassNameVar in $subClassNameVars)
+      #set ($index = $foreach.count - 1)
+      #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+      const ${subClassNameVar}FormRef = ref<InstanceType<typeof ${subSimpleClassName}Form>>()
+    #end
+  #end
+#end
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 80,
+  },
+  layout: 'horizontal',
+  schema: useFormSchema(),
+  showDefaultActions: false
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #if ( $subTables && $subTables.size() > 0 )
+        // 校验子表单
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          #if ($subTable.subJoinMany) ## 一对多
+            ## TODO 列表值校验?
+          #else
+            const ${subClassNameVar}Valid = await ${subClassNameVar}FormRef.value?.validate();
+            if (!${subClassNameVar}Valid) {
+              subTabsName.value = '${subClassNameVar}';
+              return;
+            }
+          #end
+        #end
+      #end
+    #end
+    modalApi.lock();
+    // 提交表单
+    const data = (await formApi.getValues()) as ${simpleClassName}Api.${simpleClassName};
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #if ( $subTables && $subTables.size() > 0 )
+        // 拼接子表的数据
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          #if ($subTable.subJoinMany)
+            data.${subClassNameVar}s = ${subClassNameVar}FormRef.value?.getData();
+          #else
+            data.${subClassNameVar} = await ${subClassNameVar}FormRef.value?.getValues();
+          #end
+        #end
+      #end
+    #end
+    try {
+      await (formData.value?.id ? update${simpleClassName}(data) : create${simpleClassName}(data));
+      // 关闭并提示
+      await modalApi.close();
+      emit('success');
+      ElMessage.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 加载数据
+    let data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
+    if (!data) {
+      return;
+    }
+    if (data.id) {
+      modalApi.lock();
+      try {
+        data = await get${simpleClassName}(data.id);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    // 设置到 values
+    formData.value = data;
+    await formApi.setValues(formData.value);
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle">
+    <Form class="mx-4" />
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      <!-- 子表的表单 -->
+      <el-tabs v-model="subTabsName">
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+          #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+          <el-tab-pane name="$subClassNameVar" label="${subTable.classComment}">
+            <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData?.id" />
+          </el-tab-pane>
+        #end
+      </el-tabs>
+    #end
+  </Modal>
+</template>

+ 309 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm

@@ -0,0 +1,309 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { ElMessage, ElTabs, ElTabPane, ElLoading } from 'element-plus';
+import Form from './modules/form.vue';
+
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 11 || $table.templateType == 12 )
+    #foreach ($subSimpleClassName in $subSimpleClassNames)
+    #set ($index = $foreach.count - 1)
+    #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+import ${subSimpleClassName}List from './modules/${subSimpleClassName_strikeCase}-list.vue'
+    #end
+#end
+
+import { ref, computed } from 'vue';
+import { $t } from '#/locales';
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+#if (${table.templateType} == 2)## 树表接口
+import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
+#else## 标准表接口
+import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
+#end
+import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
+
+import { useGridColumns, useGridFormSchema } from './data';
+
+#if ($table.templateType == 12 || $table.templateType == 11) ## 内嵌和erp情况
+/** 子表的列表 */
+const subTabsName = ref('$subClassNameVars.get(0)')
+#if ($table.templateType == 11)
+const select${simpleClassName} = ref<${simpleClassName}Api.${simpleClassName}>();
+#end
+#end
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+#if (${table.templateType} == 2)## 树表特有:控制表格展开收缩
+/** 切换树形展开/收缩状态 */
+const isExpanded = ref(true);
+function toggleExpand() {
+  isExpanded.value = !isExpanded.value;
+  gridApi.grid.setAllTreeExpand(isExpanded.value);
+}
+#end
+
+/** 刷新表格 */
+function onRefresh() {
+#if ($table.templateType == 12) ## 内嵌情况
+  gridApi.reload();
+#else
+  gridApi.query();
+#end
+}
+
+/** 创建${table.classComment} */
+function handleCreate() {
+  formModalApi.setData({}).open();
+}
+
+/** 编辑${table.classComment} */
+function handleEdit(row: ${simpleClassName}Api.${simpleClassName}) {
+  formModalApi.setData(row).open();
+}
+
+#if (${table.templateType} == 2)## 树表特有:新增下级
+/** 新增下级${table.classComment} */
+function handleAppend(row: ${simpleClassName}Api.${simpleClassName}) {
+  formModalApi.setData({ ${treeParentColumn.javaField}: row.id }).open();
+}
+#end
+
+/** 删除${table.classComment} */
+async function handleDelete(row: ${simpleClassName}Api.${simpleClassName}) {
+  const loadingInstance = ElLoading.service({
+    text: $t('ui.actionMessage.deleting', [row.id]),
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+  try {
+    await delete${simpleClassName}(row.id as number);
+    ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.id]));
+    onRefresh();
+  } finally {
+    loadingInstance.close();
+  }
+}
+
+#if ($table.templateType != 2 && $deleteBatchEnable)
+/** 批量删除${table.classComment} */
+async function handleDeleteBatch() {
+  const loadingInstance = ElLoading.service({
+    text: $t('ui.actionMessage.deleting'),
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+  try {
+    await delete${simpleClassName}List(checkedIds.value);
+    ElMessage.success($t('ui.actionMessage.deleteSuccess'));
+    onRefresh();
+  } finally {
+    loadingInstance.close();
+  }
+}
+
+const checkedIds = ref<number[]>([])
+function handleRowCheckboxChange({
+  records,
+}: {
+  records: ${simpleClassName}Api.${simpleClassName}[];
+}) {
+  checkedIds.value = records.map((item) => item.id);
+}
+#end
+
+/** 导出表格 */
+async function handleExport() {
+  const data = await export${simpleClassName}(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+#if (${table.templateType} == 11)
+    height: '600px',
+#else
+    height: 'auto',
+#end
+#if (${table.templateType} == 2)## 树表设置
+  treeConfig: {
+    parentField: '${treeParentColumn.javaField}',
+    rowField: 'id',
+    transform: true,
+    expandAll: true,
+    reserve: true,
+  },
+  pagerConfig: {
+    enabled: false,
+  },
+#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({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+#end
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+#if (${table.templateType} == 11)
+      isCurrent: true,
+#end
+    },
+    toolbarConfig: {
+      refresh: { code: 'query' },
+      search: true,
+    },
+  } as VxeTableGridOptions<${simpleClassName}Api.${simpleClassName}>,
+#if (${table.templateType} == 11 || $deleteBatchEnable)
+  gridEvents:{
+    #if(${table.templateType} == 11)
+    cellClick: ({ row }: { row: ${simpleClassName}Api.${simpleClassName}}) => {
+      select${simpleClassName}.value = row;
+    },
+    #end
+    #if(${table.templateType} != 2 && $deleteBatchEnable)
+      checkboxAll: handleRowCheckboxChange,
+      checkboxChange: handleRowCheckboxChange,
+    #end
+  }
+#end
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="onRefresh" />
+
+#if ($table.templateType == 11) ## erp情况
+  <div>
+#end
+    <Grid table-title="${table.classComment}列表">
+        #if ($table.templateType == 12) ## 内嵌情况
+          <template #expand_content="{ row }">
+            <!-- 子表的表单 -->
+            <el-tabs v-model="subTabsName" class="mx-8">
+                #foreach ($subTable in $subTables)
+                    #set ($index = $foreach.count - 1)
+                    #set ($subClassNameVar = $subClassNameVars.get($index))
+                    #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+                    #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+                  <el-tab-pane name="$subClassNameVar" label="${subTable.classComment}">
+                    <${subSimpleClassName}List :${subJoinColumn_strikeCase}="row?.id" />
+                  </el-tab-pane>
+                #end
+            </el-tabs>
+          </template>
+        #end
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            #if (${table.templateType} == 2)## 树表特有:展开/收缩按钮
+            {
+              label: isExpanded ? '收缩' : '展开',
+              type: 'primary',
+              onClick: toggleExpand,
+            },
+            #end
+            {
+              label: $t('ui.actionTitle.create', ['${table.classComment}']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['${table.moduleName}:${simpleClassName_strikeCase}:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['${table.moduleName}:${simpleClassName_strikeCase}:export'],
+              onClick: handleExport,
+            },
+            #if ($table.templateType != 2 && $deleteBatchEnable)
+            {
+              label: $t('ui.actionTitle.deleteBatch'),
+              type: 'danger',
+              icon: ACTION_ICON.DELETE,
+              disabled: isEmpty(checkedIds),
+              auth: ['${table.moduleName}:${simpleClassName_strikeCase}:delete'],
+              onClick: handleDeleteBatch,
+            },
+            #end
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            #if (${table.templateType} == 2)## 树表特有:新增下级
+            {
+              label: '新增下级',
+              type: 'text',
+              icon: ACTION_ICON.ADD,
+              auth: ['${table.moduleName}:${simpleClassName_strikeCase}:create'],
+              onClick: handleAppend.bind(null, row),
+            },
+            #end
+            {
+              label: $t('common.edit'),
+              type: 'text',
+              icon: ACTION_ICON.EDIT,
+              auth: ['${table.moduleName}:${simpleClassName_strikeCase}:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'danger',
+              text: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['${table.moduleName}:${simpleClassName_strikeCase}:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.id]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+
+#if ($table.templateType == 11) ## erp情况
+    <!-- 子表的表单 -->
+    <el-tabs v-model="subTabsName" class="mt-2">
+        #foreach ($subTable in $subTables)
+            #set ($index = $foreach.count - 1)
+            #set ($subClassNameVar = $subClassNameVars.get($index))
+            #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+            #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+          <el-tab-pane name="$subClassNameVar" label="${subTable.classComment}">
+            <${subSimpleClassName}List :${subJoinColumn_strikeCase}="select${simpleClassName}?.id" />
+          </el-tab-pane>
+        #end
+    </el-tabs>
+    </div>
+#end
+  </Page>
+</template>

+ 90 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/form_sub_erp.vue.vm

@@ -0,0 +1,90 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+<script lang="ts" setup>
+  import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
+
+  import { useVbenModal } from '@vben/common-ui';
+  import { ElMessage } from 'element-plus';
+
+  import { computed, ref } from 'vue';
+  import { $t } from '#/locales';
+  import { useVbenForm } from '#/adapter/form';
+  import { get${subSimpleClassName}, create${subSimpleClassName}, update${subSimpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
+
+  import { use${subSimpleClassName}FormSchema } from '../data';
+
+  const emit = defineEmits(['success']);
+  const formData = ref<${simpleClassName}Api.${subSimpleClassName}>();
+  const getTitle = computed(() => {
+    return formData.value?.id
+        ? $t('ui.actionTitle.edit', ['${subTable.classComment}'])
+        : $t('ui.actionTitle.create', ['${subTable.classComment}']);
+  });
+
+  const [Form, formApi] = useVbenForm({
+    commonConfig: {
+      componentProps: {
+        class: 'w-full',
+      },
+      formItemClass: 'col-span-2',
+      labelWidth: 80,
+    },
+    layout: 'horizontal',
+    schema: use${subSimpleClassName}FormSchema(),
+    showDefaultActions: false
+  });
+
+  const [Modal, modalApi] = useVbenModal({
+    async onConfirm() {
+      const { valid } = await formApi.validate();
+      if (!valid) {
+        return;
+      }
+
+      modalApi.lock();
+      // 提交表单
+      const data = (await formApi.getValues()) as ${simpleClassName}Api.${subSimpleClassName};
+      data.${subJoinColumn.javaField} = formData.value?.${subJoinColumn.javaField};
+      try {
+        await (formData.value?.id ? update${subSimpleClassName}(data) : create${subSimpleClassName}(data));
+        // 关闭并提示
+        await modalApi.close();
+        emit('success');
+        ElMessage.success($t('ui.actionMessage.operationSuccess'));
+      } finally {
+        modalApi.unlock();
+      }
+    },
+    async onOpenChange(isOpen: boolean) {
+      if (!isOpen) {
+        formData.value = undefined;
+        return;
+      }
+
+      // 加载数据
+      let data = modalApi.getData<${simpleClassName}Api.${subSimpleClassName}>();
+      if (!data) {
+        return;
+      }
+      if (data.id) {
+        modalApi.lock();
+        try {
+          data = await get${subSimpleClassName}(data.id);
+        } finally {
+          modalApi.unlock();
+        }
+      }
+      // 设置到 values
+      formData.value = data;
+      await formApi.setValues(formData.value);
+    },
+  });
+</script>
+
+<template>
+  <Modal :title="getTitle">
+    <Form class="mx-4" />
+  </Modal>
+</template>

+ 2 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/form_sub_inner.vue.vm

@@ -0,0 +1,2 @@
+## 主表的 normal 和 inner 使用相同的 form 表单
+#parse("codegen/vue3_vben5_ele/schema/views/modules/form_sub_normal.vue.vm")

+ 202 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/form_sub_normal.vue.vm

@@ -0,0 +1,202 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subClassNameVar = $subClassNameVars.get($subIndex))
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+<script lang="ts" setup>
+  import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
+
+  import { computed, ref, h, onMounted,watch,nextTick } from 'vue';
+  import { $t } from '#/locales';
+
+#if ($subTable.subJoinMany) ## 一对多
+import { Plus } from "@vben/icons";
+import { ElButton, ElTabs, ElTabPane, ElCheckbox, ElInput, ElSelect, ElOption, ElRadioGroup, ElCheckboxGroup, ElDatePicker } from 'element-plus';
+import { ImageUpload, FileUpload } from "#/components/upload";
+import type { OnActionClickParams } from '#/adapter/vxe-table';
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { use${subSimpleClassName}GridEditColumns } from '../data';
+import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
+#else
+import { useVbenForm } from '#/adapter/form';
+import { use${subSimpleClassName}FormSchema } from '../data';
+import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
+#end
+
+const props = defineProps<{
+   ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
+}>()
+
+#if ($subTable.subJoinMany) ## 一对多
+/** 表格操作按钮的回调函数 */
+function onActionClick({
+ code,
+ row,
+}: OnActionClickParams<${simpleClassName}Api.${subSimpleClassName}>) {
+  switch (code) {
+    case 'delete': {
+      onDelete(row);
+      break;
+    }
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+gridOptions: {
+  columns: use${subSimpleClassName}GridEditColumns(onActionClick),
+  border: true,
+  showOverflow: true,
+  autoResize: true,
+  keepSource: true,
+  rowConfig: {
+    keyField: 'id',
+  },
+  pagerConfig: {
+    enabled: false,
+  },
+  toolbarConfig: {
+    enabled: false,
+  },
+},
+});
+
+/** 添加${subTable.classComment} */
+const onAdd = async () => {
+  await gridApi.grid.insertAt({} as ${simpleClassName}Api.${subSimpleClassName}, -1);
+}
+
+/** 删除${subTable.classComment} */
+const onDelete =  async (row: ${simpleClassName}Api.${subSimpleClassName}) => {
+  await gridApi.grid.remove(row);
+}
+
+/** 提供获取表格数据的方法供父组件调用 */
+defineExpose({
+  getData: (): ${simpleClassName}Api.${subSimpleClassName}[] => {
+    const data = gridApi.grid.getData() as ${simpleClassName}Api.${subSimpleClassName}[];
+    const removeRecords = gridApi.grid.getRemoveRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
+    const insertRecords = gridApi.grid.getInsertRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
+    return data
+        .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
+        .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
+  },
+});
+
+/** 监听主表的关联字段的变化,加载对应的子表数据 */
+watch(
+    () => props.${subJoinColumn.javaField},
+    async (val) => {
+      if (!val) {
+        return;
+      }
+      await nextTick();
+      await gridApi.grid.loadData(await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
+    },
+    { immediate: true },
+);
+#else
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 80,
+  },
+  layout: 'horizontal',
+  schema: use${subSimpleClassName}FormSchema(),
+  showDefaultActions: false
+});
+
+/** 暴露出表单校验方法和表单值获取方法 */
+defineExpose({
+  validate: async () => {
+    const { valid } = await formApi.validate();
+    return valid;
+  },
+  getValues: formApi.getValues,
+});
+
+/** 监听主表的关联字段的变化,加载对应的子表数据 */
+watch(
+    () => props.${subJoinColumn.javaField},
+    async (val) => {
+      if (!val) {
+        return;
+      }
+      await nextTick();
+      await formApi.setValues(await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
+    },
+    { immediate: true },
+);
+#end
+</script>
+
+<template>
+#if ($subTable.subJoinMany) ## 一对多
+  <Grid class="mx-4">
+      #foreach($column in $subColumns)
+          #if ($column.createOperation || $column.updateOperation)
+              #set ($javaField = $column.javaField)
+              #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+              #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+                <template #${javaField}="{ row }">
+                  <el-input v-model="row.${javaField}" />
+                </template>
+              #elseif($column.htmlType == "imageUpload")## 图片上传
+                <template #${javaField}="{ row }">
+                  <ImageUpload v-model="row.${javaField}" />
+                </template>
+              #elseif($column.htmlType == "fileUpload")## 文件上传
+                <template #${javaField}="{ row }">
+                  <FileUpload v-model="row.${javaField}" />
+                </template>
+              #elseif($column.htmlType == "select")## 下拉框
+                <template #${javaField}="{ row, column }">
+                  <el-select v-model="row.${javaField}" class="w-full">
+                    <el-option v-for="option in column.params.options" :key="option.value" :value="option.value" :label="option.label" />
+                  </el-select>
+                </template>
+              #elseif($column.htmlType == "checkbox")## 多选框
+                <template #${javaField}="{ row, column }">
+                  <el-checkbox-group v-model="row.${javaField}">
+                    <el-checkbox v-for="option in column.params.options" :key="option.value" :label="option.value">
+                      {{ option.label }}
+                    </el-checkbox>
+                  </el-checkbox-group>
+                </template>
+              #elseif($column.htmlType == "radio")## 单选框
+                <template #${javaField}="{ row, column }">
+                  <el-radio-group v-model="row.${javaField}">
+                    <el-radio v-for="option in column.params.options" :key="option.value" :label="option.value">
+                      {{ option.label }}
+                    </el-radio>
+                  </el-radio-group>
+                </template>
+              #elseif($column.htmlType == "datetime")## 时间框
+                <template #${javaField}="{ row }">
+                  <el-date-picker
+                      v-model="row.${javaField}"
+                      type="datetime"
+                      value-format="x"
+                      format="YYYY-MM-DD HH:mm:ss"
+                  />
+                </template>
+              #elseif($column.htmlType == "textarea" || $column.htmlType == "editor")## 文本框
+                <template #${javaField}="{ row }">
+                  <el-input v-model="row.${javaField}" type="textarea" />
+                </template>
+              #end
+          #end
+      #end
+  </Grid>
+  <div class="flex justify-center -mt-4">
+    <el-button :icon="Plus" type="primary" plain @click="onAdd" v-access:code="['${subTable.moduleName}:${simpleClassName_strikeCase}:create']">
+      {{ $t('ui.actionTitle.create', ['${subTable.classComment}']) }}
+    </el-button>
+  </div>
+#else
+  <Form class="mx-4" />
+#end
+</template>

+ 236 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm

@@ -0,0 +1,236 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex))
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
+
+#if ($table.templateType == 11) ## erp
+import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
+#end
+import { useVbenModal } from '@vben/common-ui';
+import { ElMessage, ElLoading } from 'element-plus';
+import { ref, computed, nextTick,watch } from 'vue';
+import { $t } from '#/locales';
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+
+#if ($table.templateType == 11) ## erp
+import { delete${subSimpleClassName},#if ($deleteBatchEnable) delete${subSimpleClassName}List,#end get${subSimpleClassName}Page } from '#/api/${table.moduleName}/${table.businessName}';
+import { use${subSimpleClassName}GridFormSchema, use${subSimpleClassName}GridColumns } from '../data';
+import { isEmpty } from '@vben/utils';
+#else
+#if ($subTable.subJoinMany) ## 一对多
+import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
+#else
+import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
+#end
+import { use${subSimpleClassName}GridColumns } from '../data';
+#end
+
+const props = defineProps<{
+  ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
+}>()
+
+#if ($table.templateType == 11) ## erp
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: ${subSimpleClassName}Form,
+  destroyOnClose: true,
+});
+
+/** 创建${subTable.classComment} */
+function handleCreate() {
+  if (!props.${subJoinColumn.javaField}){
+    ElMessage.warning("请先选择一个${table.classComment}!")
+    return
+  }
+  formModalApi.setData({${subJoinColumn.javaField}: props.${subJoinColumn.javaField}}).open();
+}
+
+/** 编辑${subTable.classComment} */
+function handleEdit(row: ${simpleClassName}Api.${subSimpleClassName}) {
+  formModalApi.setData(row).open();
+}
+
+/** 删除${subTable.classComment} */
+async function handleDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
+  const loadingInstance = ElLoading.service({
+    text: $t('ui.actionMessage.deleting', [row.id]),
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+  try {
+    await delete${subSimpleClassName}(row.id as number);
+    ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.id]));
+    onRefresh();
+  } finally {
+    loadingInstance.close();
+  }
+}
+
+#if ($deleteBatchEnable)
+/** 批量删除${subTable.classComment} */
+async function handleDeleteBatch() {
+  const loadingInstance = ElLoading.service({
+    text: $t('ui.actionMessage.deleting'),
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+  try {
+    await delete${subSimpleClassName}List(checkedIds.value);
+    ElMessage.success($t('ui.actionMessage.deleteSuccess'));
+    onRefresh();
+  } finally {
+    loadingInstance.close();
+  }
+}
+
+const checkedIds = ref<number[]>([])
+function handleRowCheckboxChange({
+  records,
+}: {
+  records: ${simpleClassName}Api.${subSimpleClassName}[];
+}) {
+  checkedIds.value = records.map((item) => item.id);
+}
+#end
+
+#end
+const [Grid, gridApi] = useVbenVxeGrid({
+#if ($table.templateType == 11)
+  formOptions: {
+    schema: use${subSimpleClassName}GridFormSchema(),
+  },
+#end
+  gridOptions: {
+#if ($table.templateType == 11)
+  columns: use${subSimpleClassName}GridColumns(),
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          if (!props.${subJoinColumn.javaField}){
+              return []
+          }
+          return await get${subSimpleClassName}Page({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ${subJoinColumn.javaField}: props.${subJoinColumn.javaField},
+            ...formValues,
+          });
+        },
+      },
+    },
+  pagerConfig: {
+    enabled: true,
+  },
+  toolbarConfig: {
+    refresh: { code: 'query' },
+    search: true,
+  },
+#else
+  columns: use${subSimpleClassName}GridColumns(),
+  pagerConfig: {
+    nabled: false,
+  },
+  toolbarConfig: {
+    enabled: false,
+  },
+#end
+  height: '600px',
+  rowConfig: {
+    keyField: 'id',
+    isHover: true,
+  },
+  } as VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>,
+  #if (${table.templateType} == 11 && $deleteBatchEnable)
+  gridEvents:{
+    checkboxAll: handleRowCheckboxChange,
+    checkboxChange: handleRowCheckboxChange,
+  }
+  #end
+});
+
+/** 刷新表格 */
+async function onRefresh() {
+#if ($table.templateType == 11) ## erp
+  await gridApi.query();
+#else
+  #if ($subTable.subJoinMany) ## 一对多
+  await gridApi.grid.loadData(await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
+  #else
+  await gridApi.grid.loadData([await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!)]);
+  #end
+#end
+}
+
+/** 监听主表的关联字段的变化,加载对应的子表数据 */
+watch(
+  () => props.${subJoinColumn.javaField},
+  async (val) => {
+    if (!val) {
+      return;
+    }
+    await nextTick();
+    await onRefresh()
+  },
+  { immediate: true },
+);
+</script>
+
+<template>
+    #if ($table.templateType == 11) ## erp
+      <FormModal @success="onRefresh" />
+      <Grid table-title="${subTable.classComment}列表">
+        <template #toolbar-tools>
+          <TableAction
+            :actions="[
+              {
+                label: $t('ui.actionTitle.create', ['${table.classComment}']),
+                type: 'primary',
+                icon: ACTION_ICON.ADD,
+                auth: ['${table.moduleName}:${simpleClassName_strikeCase}:create'],
+                onClick: handleCreate,
+              },
+              #if ($table.templateType == 11 && $deleteBatchEnable)
+              {
+                label: $t('ui.actionTitle.deleteBatch'),
+                type: 'danger',
+                icon: ACTION_ICON.DELETE,
+                disabled: isEmpty(checkedIds),
+                auth: ['${table.moduleName}:${simpleClassName_strikeCase}:delete'],
+                onClick: handleDeleteBatch,
+              },
+              #end
+            ]"
+          />
+        </template>
+        <template #actions="{ row }">
+          <TableAction
+            :actions="[
+              {
+                label: $t('common.edit'),
+                type: 'text',
+                icon: ACTION_ICON.EDIT,
+                auth: ['${table.moduleName}:${simpleClassName_strikeCase}:update'],
+                onClick: handleEdit.bind(null, row),
+              },
+              {
+                label: $t('common.delete'),
+                type: 'danger',
+                text: true,
+                icon: ACTION_ICON.DELETE,
+                auth: ['${table.moduleName}:${simpleClassName_strikeCase}:delete'],
+                popConfirm: {
+                  title: $t('ui.actionMessage.deleteConfirm', [row.id]),
+                  confirm: handleDelete.bind(null, row),
+                },
+              },
+            ]"
+          />
+        </template>
+      </Grid>
+    #else
+      <Grid table-title="${subTable.classComment}列表" />
+    #end
+</template>

+ 4 - 0
yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_inner.vue.vm

@@ -0,0 +1,4 @@
+## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
+## 1)inner 使用 list 不分页,erp 使用 page 分页
+## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
+#parse("codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm")