Parcourir la source

代码生成:重构 vue2 模版,适配树表和主子表

puhui999 il y a 1 an
Parent
commit
80db28ee5b

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

@@ -104,6 +104,18 @@ public class CodegenEngine {
                     vueFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("api/api.js"),
                     vueFilePath("api/${table.moduleName}/${classNameVar}.js"))
+            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/form.vue"),
+                    vueFilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
+                    vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
+            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
             // Vue3 标准模版
             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"),
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
@@ -452,7 +464,7 @@ public class CodegenEngine {
     }
 
     private static String vueFilePath(String path) {
-        return "yudao-ui-${sceneEnum.basePackage}/" + // 顶级目录
+        return "yudao-ui-${sceneEnum.basePackage}-vue2/" + // 顶级目录
                 "src/" + path;
     }
 

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

@@ -4,8 +4,8 @@
 <template>
   <div class="app-container">
     <!-- 对话框(添加 / 修改) -->
-    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" v-dialogDrag append-to-body>
-      <el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="80px">
+    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
+      <el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
           #foreach($column in $subColumns)
               #if ($column.createOperation || $column.updateOperation)
                   #set ($dictType = $column.dictType)
@@ -13,30 +13,29 @@
                   #set ($javaType = $column.javaType)
                   #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
                   #set ($comment = $column.columnComment)
-                  #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 TODO 芋艿:这里要忽略下 join 字段;
-                      #if (!$column.primaryKey)## 忽略主键,不用在表单里
-                        <el-form-item label="${comment}" prop="${javaField}">
-                          <el-input v-model="form.${javaField}" placeholder="请输入${comment}" />
-                        </el-form-item>
-                      #end
+                  #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+                  #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")## 图片上传
                       #set ($hasImageUploadColumn = true)
-                    <el-form-item label="${comment}">
-                      <ImageUpload v-model="form.${javaField}"/>
+                    <el-form-item label="${comment}" prop="${javaField}">
+                      <ImageUpload v-model="formData.${javaField}"/>
                     </el-form-item>
                   #elseif($column.htmlType == "fileUpload")## 文件上传
                       #set ($hasFileUploadColumn = true)
-                    <el-form-item label="${comment}">
-                      <FileUpload v-model="form.${javaField}"/>
+                    <el-form-item label="${comment}" prop="${javaField}">
+                      <FileUpload v-model="formData.${javaField}"/>
                     </el-form-item>
                   #elseif($column.htmlType == "editor")## 文本编辑器
                       #set ($hasEditorColumn = true)
-                    <el-form-item label="${comment}">
-                      <editor v-model="form.${javaField}" :min-height="192"/>
+                    <el-form-item label="${comment}" prop="${javaField}">
+                      <editor v-model="formData.${javaField}" :min-height="192"/>
                     </el-form-item>
                   #elseif($column.htmlType == "select")## 下拉框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-select v-model="form.${javaField}" placeholder="请选择${comment}">
+                      <el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
                           #if ("" != $dictType)## 有数据字典
                             <el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
                                        :key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
@@ -47,7 +46,7 @@
                     </el-form-item>
                   #elseif($column.htmlType == "checkbox")## 多选框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-checkbox-group v-model="form.${javaField}">
+                      <el-checkbox-group v-model="formData.${javaField}">
                           #if ("" != $dictType)## 有数据字典
                             <el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
                                          :key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
@@ -58,7 +57,7 @@
                     </el-form-item>
                   #elseif($column.htmlType == "radio")## 单选框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-radio-group v-model="form.${javaField}">
+                      <el-radio-group v-model="formData.${javaField}">
                           #if ("" != $dictType)## 有数据字典
                             <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
                                       :key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"
@@ -70,11 +69,11 @@
                     </el-form-item>
                   #elseif($column.htmlType == "datetime")## 时间框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-date-picker clearable v-model="form.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
+                      <el-date-picker clearable v-model="formData.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
                     </el-form-item>
                   #elseif($column.htmlType == "textarea")## 文本框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-input v-model="form.${javaField}" type="textarea" placeholder="请输入内容" />
+                      <el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入内容" />
                     </el-form-item>
                   #end
               #end
@@ -100,7 +99,7 @@
       import Editor from '@/components/Editor';
       #end
   export default {
-    name: "${simpleClassName}",
+    name: "${subSimpleClassName}Form",
     components: {
         #if ($hasImageUploadColumn)
           ImageUpload,
@@ -122,7 +121,7 @@
         formLoading: false,
         // 表单参数
         formData: {
-            #foreach ($column in $columns)
+            #foreach ($column in $subColumns)
                 #if ($column.createOperation || $column.updateOperation)
                     #if ($column.htmlType == "checkbox")
                             $column.javaField: [],
@@ -134,7 +133,7 @@
         },
         // 表单校验
         formRules: {
-            #foreach ($column in $columns)
+            #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 }],
@@ -144,39 +143,25 @@
       };
     },
     methods: {
-      /** 表单重置 */
-      reset() {
-        this.formData = {
-            #foreach ($column in $columns)
-                #if ($column.createOperation || $column.updateOperation)
-                    #if ($column.htmlType == "checkbox")
-                            $column.javaField: [],
-                    #else
-                            $column.javaField: undefined,
-                    #end
-                #end
-            #end
-        };
-        this.resetForm("formRef");
-      },
       /** 打开弹窗 */
-      open(id) {
+      open(id, ${subJoinColumn.javaField}) {
         this.dialogVisible = true;
         this.reset();
         const that = this;
+        this.formData.${subJoinColumn.javaField} = ${subJoinColumn.javaField};
         // 修改时,设置数据
         if (id) {
           this.formLoading = true;
           try {
               ${simpleClassName}Api.get${subSimpleClassName}(id).then(res=>{
               that.formData = res.data;
-              that.title = "修改${table.classComment}";
+              that.dialogTitle = "修改${table.classComment}";
             })
           } finally {
             this.formLoading = false;
           }
         }
-        this.title = "新增${table.classComment}";
+        this.dialogTitle = "新增${table.classComment}";
       },
       /** 提交按钮 */
       submitForm() {
@@ -189,7 +174,7 @@
             }
             // 修改的提交
             if (data.${primaryColumn.javaField}) {
-                    ${simpleClassName}Api.update${simpleClassName}(data).then(response => {
+              ${simpleClassName}Api.update${subSimpleClassName}(data).then(response => {
                 this.#[[$modal]]#.msgSuccess("修改成功");
                 this.dialogVisible = false;
                 this.#[[$]]#emit('success');
@@ -197,7 +182,7 @@
               return;
             }
             // 添加的提交
-            ${simpleClassName}Api.create${simpleClassName}(data).then(response => {
+            ${simpleClassName}Api.create${subSimpleClassName}(data).then(response => {
               this.#[[$modal]]#.msgSuccess("新增成功");
               this.dialogVisible = false;
               this.#[[$]]#emit('success');
@@ -206,7 +191,22 @@
         }finally {
           this.formLoading = false
         }
-      }
+      },
+      /** 表单重置 */
+      reset() {
+        this.formData = {
+            #foreach ($column in $subColumns)
+                #if ($column.createOperation || $column.updateOperation)
+                    #if ($column.htmlType == "checkbox")
+                            $column.javaField: [],
+                    #else
+                            $column.javaField: undefined,
+                    #end
+                #end
+            #end
+        };
+        this.resetForm("formRef");
+      },
     }
   };
 </script>

+ 157 - 55
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm

@@ -4,10 +4,8 @@
 #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
 #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
-
 <template>
-  <div class="app-container">
-#if ( $subTable.subJoinMany )## 情况一:一对多,table + form
+    #if ( $subTable.subJoinMany )## 情况一:一对多,table + form
       <el-form
           ref="formRef"
           :model="formData"
@@ -16,7 +14,6 @@
           label-width="0px"
           :inline-message="true"
       >
-##        // TODO puhui999: 看看样式是否需要调整
         <el-table :data="formData" class="-mt-10px">
           <el-table-column label="序号" type="index" width="100" />
             #foreach($column in $subColumns)
@@ -29,7 +26,7 @@
                     #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
                     #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
                       <el-table-column label="${comment}" min-width="150">
-                        <template slot-scope="{ row, $index }">
+                        <template v-slot="{ row, $index }">
                           <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
                             <el-input v-model="row.${javaField}" placeholder="请输入${comment}" />
                           </el-form-item>
@@ -38,7 +35,7 @@
                     #elseif($column.htmlType == "imageUpload")## 图片上传
                         #set ($hasImageUploadColumn = true)
                       <el-table-column label="${comment}" min-width="200">
-                        <template slot-scope="{ row, $index }">
+                        <template v-slot="{ row, $index }">
                           <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
                             <ImageUpload v-model="row.${javaField}"/>
                           </el-form-item>
@@ -47,7 +44,7 @@
                     #elseif($column.htmlType == "fileUpload")## 文件上传
                         #set ($hasFileUploadColumn = true)
                       <el-table-column label="${comment}" min-width="200">
-                        <template slot-scope="{ row, $index }">
+                        <template v-slot="{ row, $index }">
                           <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
                             <FileUpload v-model="row.${javaField}"/>
                           </el-form-item>
@@ -56,15 +53,15 @@
                     #elseif($column.htmlType == "editor")## 文本编辑器
                         #set ($hasEditorColumn = true)
                       <el-table-column label="${comment}" min-width="400">
-                        <template slot-scope="{ row, $index }">
+                        <template v-slot="{ row, $index }">
                           <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
-                            <editor v-model="row.${javaField}" :min-height="192"/>
+                            <Editor v-model="row.${javaField}" :min-height="192"/>
                           </el-form-item>
                         </template>
                       </el-table-column>
                     #elseif($column.htmlType == "select")## 下拉框
                       <el-table-column label="${comment}" min-width="150">
-                        <template slot-scope="{ row, $index }">
+                        <template v-slot="{ row, $index }">
                           <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
                             <el-select v-model="row.${javaField}" placeholder="请选择${comment}">
                                 #if ("" != $dictType)## 有数据字典
@@ -79,7 +76,7 @@
                       </el-table-column>
                     #elseif($column.htmlType == "checkbox")## 多选框
                       <el-table-column label="${comment}" min-width="150">
-                        <template slot-scope="{ row, $index }">
+                        <template v-slot="{ row, $index }">
                           <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
                             <el-checkbox-group v-model="row.${javaField}">
                                 #if ("" != $dictType)## 有数据字典
@@ -94,7 +91,7 @@
                       </el-table-column>
                     #elseif($column.htmlType == "radio")## 单选框
                       <el-table-column label="${comment}" min-width="150">
-                        <template slot-scope="{ row, $index }">
+                        <template v-slot="{ row, $index }">
                           <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
                             <el-radio-group v-model="row.${javaField}">
                                 #if ("" != $dictType)## 有数据字典
@@ -110,20 +107,15 @@
                       </el-table-column>
                     #elseif($column.htmlType == "datetime")## 时间框
                       <el-table-column label="${comment}" min-width="150">
-                        <template slot-scope="{ row, $index }">
+                        <template v-slot="{ row, $index }">
                           <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
-                            <el-date-picker
-                                v-model="row.${javaField}"
-                                type="date"
-                                value-format="x"
-                                placeholder="选择${comment}"
-                            />
+                            <el-date-picker clearable v-model="row.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
                           </el-form-item>
                         </template>
                       </el-table-column>
                     #elseif($column.htmlType == "textarea")## 文本框
                       <el-table-column label="${comment}" min-width="200">
-                        <template slot-scope="{ row, $index }">
+                        <template v-slot="{ row, $index }">
                           <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
                             <el-input v-model="row.${javaField}" type="textarea" placeholder="请输入${comment}" />
                           </el-form-item>
@@ -133,17 +125,97 @@
                 #end
             #end
           <el-table-column align="center" fixed="right" label="操作" width="60">
-            <template slot-scope="{ $index }">
-              <el-button @click="handleDelete($index)" link>—</el-button>
+            <template v-slot="{ $index }">
+              <el-link @click="handleDelete($index)">—</el-link>
             </template>
           </el-table-column>
         </el-table>
       </el-form>
-  <el-row justify="center" class="mt-3">
-    <el-button @click="handleAdd" round>+ 添加${subTable.classComment}</el-button>
-  </el-row>
-#end
-  </div>
+      <el-row justify="center" class="mt-3">
+        <el-button @click="handleAdd" round>+ 添加${subTable.classComment}</el-button>
+      </el-row>
+    #else## 情况二:一对一,form
+      <el-form
+          ref="formRef"
+          :model="formData"
+          :rules="formRules"
+          label-width="100px"
+          v-loading="formLoading"
+      >
+          #foreach($column in $subColumns)
+              #if ($column.createOperation || $column.updateOperation)
+                  #set ($dictType = $column.dictType)
+                  #set ($javaField = $column.javaField)
+                  #set ($javaType = $column.javaType)
+                  #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+                  #set ($comment = $column.columnComment)
+                  #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+                  #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")## 图片上传
+                      #set ($hasImageUploadColumn = true)
+                    <el-form-item label="${comment}">
+                      <ImageUpload v-model="formData.${javaField}"/>
+                    </el-form-item>
+                  #elseif($column.htmlType == "fileUpload")## 文件上传
+                      #set ($hasFileUploadColumn = true)
+                    <el-form-item label="${comment}">
+                      <FileUpload v-model="formData.${javaField}"/>
+                    </el-form-item>
+                  #elseif($column.htmlType == "editor")## 文本编辑器
+                      #set ($hasEditorColumn = true)
+                    <el-form-item label="${comment}">
+                      <Editor v-model="formData.${javaField}" :min-height="192"/>
+                    </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 this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
+                                       :key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
+                          #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 this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
+                                         :key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
+                          #else##没数据字典
+                            <el-checkbox>请选择字典生成</el-checkbox>
+                          #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 this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
+                                      :key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"
+                                      #else:label="dict.value"#end>{{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 clearable v-model="formData.${javaField}" type="date" value-format="timestamp" 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>
 
 <script>
@@ -158,7 +230,7 @@
       import Editor from '@/components/Editor';
       #end
   export default {
-    name: "${simpleClassName}",
+    name: "${subSimpleClassName}Form",
     components: {
         #if ($hasImageUploadColumn)
           ImageUpload,
@@ -170,29 +242,18 @@
           Editor,
         #end
     },
+    props:[
+      '${subJoinColumn.javaField}'
+    ],// ${subJoinColumn.columnComment}(主表的关联字段)
     data() {
       return {
-        // 弹出层标题
-        dialogTitle: "",
-        // 是否显示弹出层
-        dialogVisible: false,
         // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
         formLoading: false,
         // 表单参数
-        formData: {
-            #foreach ($column in $columns)
-                #if ($column.createOperation || $column.updateOperation)
-                    #if ($column.htmlType == "checkbox")
-                            $column.javaField: [],
-                    #else
-                            $column.javaField: undefined,
-                    #end
-                #end
-            #end
-        },
+        formData: [],
         // 表单校验
         formRules: {
-            #foreach ($column in $columns)
+            #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 }],
@@ -201,14 +262,56 @@
         },
       };
     },
-    props:{
-      ${subJoinColumn.javaField}: undefined // ${subJoinColumn.columnComment}(主表的关联字段)
+    watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
+      ${subJoinColumn.javaField}:{
+        handler(val) {
+          // 1. 重置表单
+            #if ( $subTable.subJoinMany )
+              this.formData = []
+            #else
+              this.formData = {
+                  #foreach ($column in $subColumns)
+                      #if ($column.createOperation || $column.updateOperation)
+                          #if ($column.htmlType == "checkbox")
+                                  $column.javaField: [],
+                          #else
+                                  $column.javaField: undefined,
+                          #end
+                      #end
+                  #end
+              }
+            #end
+          // 2. val 非空,则加载数据
+          if (!val) {
+            return;
+          }
+          try {
+            this.formLoading = true;
+            const that = this;
+            #if ( $subTable.subJoinMany )
+            ${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(val).then(res=>{
+              that.formData = res.data;
+            })
+            #else
+            ${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(val).then(res=>{
+              const data = res.data;
+              if (!data) {
+                return
+              }
+              that.formData = data;
+            })
+            #end
+          } finally {
+            this.formLoading = false;
+          }
+        },
+        immediate: true
+      }
     },
     methods: {
         #if ( $subTable.subJoinMany )
-
           /** 新增按钮操作 */
-        handleAdd(){
+          handleAdd() {
             const row = {
                 #foreach ($column in $subColumns)
                     #if ($column.createOperation || $column.updateOperation)
@@ -223,19 +326,18 @@
             row.${subJoinColumn.javaField} = this.${subJoinColumn.javaField}
             this.formData.push(row)
           },
-
           /** 删除按钮操作 */
-        handleDelete(index) {
+          handleDelete(index) {
             this.formData.splice(index, 1)
           },
         #end
+      /** 表单校验 */
+      validate(){
+        return this.#[[$]]#refs["formRef"].validate()
+      },
       /** 表单值 */
-      getData() {
+      getData(){
         return this.formData
-      },
-      /** 表单校验 */
-      validate() {
-
       }
     }
   };

+ 150 - 167
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm

@@ -1,181 +1,164 @@
-#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 ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
 <template>
-  <!-- 列表 -->
-  <ContentWrap>
+  <div class="app-container">
 #if ($table.templateType == 11)
-    <el-button
-      type="primary"
-      plain
-      @click="openForm('create')"
-      v-hasPermi="['${permissionPrefix}:create']"
-    >
-      <Icon icon="ep:plus" class="mr-5px" /> 新增
-    </el-button>
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
+                   v-hasPermi="['${permissionPrefix}:create']">新增</el-button>
+      </el-col>
+    </el-row>
 #end
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      #foreach($column in $subColumns)
-      #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.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
-        #elseif ($column.javaType == "LocalDateTime")## 时间类型
-      <el-table-column
-        label="${comment}"
-        align="center"
-        prop="${javaField}"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-        #elseif($column.dictType && "" != $column.dictType)## 数据字典
-      <el-table-column label="${comment}" align="center" prop="${javaField}">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.${column.javaField}" />
-        </template>
-      </el-table-column>
-        #else
-      <el-table-column label="${comment}" align="center" prop="${javaField}" />
-        #end
+      ## 列表
+      <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+          #foreach($column in $subColumns)
+              #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.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+                  #elseif ($column.javaType == "LocalDateTime")## 时间类型
+                <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+                  <template v-slot="scope">
+                    <span>{{ parseTime(scope.row.${javaField}) }}</span>
+                  </template>
+                </el-table-column>
+                  #elseif($column.dictType && "" != $column.dictType)## 数据字典
+                <el-table-column label="${comment}" align="center" prop="${javaField}">
+                  <template v-slot="scope">
+                    <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.${column.javaField}" />
+                  </template>
+                </el-table-column>
+              #else
+                <el-table-column label="${comment}" align="center" prop="${javaField}" />
+              #end
+          #end
       #end
-    #end
-    #if ($table.templateType == 11)
-      <el-table-column label="操作" align="center">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['${permissionPrefix}:update']"
-          >
-            编辑
-          </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['${permissionPrefix}:delete']"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    #end
-    </el-table>
-    #if ($table.templateType == 11)
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-    #end
-  </ContentWrap>
+    <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+      <template v-slot="scope">
+        <el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.${primaryColumn.javaField})"
+                   v-hasPermi="['${permissionPrefix}:update']">修改</el-button>
+        <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                   v-hasPermi="['${permissionPrefix}:delete']">删除</el-button>
+      </template>
+    </el-table-column>
+  </el-table>
 #if ($table.templateType == 11)
-    <!-- 表单弹窗:添加/修改 -->
-    <${subSimpleClassName}Form ref="formRef" @success="getList" />
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+  <!-- 对话框(添加 / 修改) -->
+  <${subSimpleClassName}Form ref="formRef" @success="getList" />
 #end
+  </div>
 </template>
-<script setup lang="ts">
-import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
-import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
-#if ($table.templateType == 11)
-import ${subSimpleClassName}Form from './${subSimpleClassName}Form.vue'
-#end
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-const props = defineProps<{
-  ${subJoinColumn.javaField}: undefined // ${subJoinColumn.columnComment}(主表的关联字段)
-}>()
-const loading = ref(false) // 列表的加载中
-const list = ref([]) // 列表的数据
-#if ($table.templateType == 11)
-const total = ref(0) // 列表的总页数
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  ${subJoinColumn.javaField}: undefined
-})
 
-/** 监听主表的关联字段的变化,加载对应的子表数据 */
-watch(
-  () => props.${subJoinColumn.javaField},
-  (val) => {
-    queryParams.${subJoinColumn.javaField} = val
-    handleQuery()
-  },
-  { immediate: false }
-)
-#end
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-#if ($table.templateType == 11)
-    const data = await ${simpleClassName}Api.get${subSimpleClassName}Page(queryParams)
-    list.value = data.list
-    total.value = data.total
-#else
-  #if ( $subTable.subJoinMany )
-    list.value = await ${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField})
-  #else
-    const data = await ${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField})
-    if (!data) {
-      return
-    }
-    list.value.push(data)
+<script>
+  import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
+  #if ($table.templateType == 11)
+  import ${subSimpleClassName}Form from './${subSimpleClassName}Form.vue'
   #end
+  export default {
+    name: "${subSimpleClassName}List",
+#if ($table.templateType == 11)
+    components: {
+       ${subSimpleClassName}Form
+    },
 #end
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
+    props:[
+      '${subJoinColumn.javaField}'
+    ],// ${subJoinColumn.columnComment}(主表的关联字段)
+    data() {
+      return {
+        // 遮罩层
+        loading: true,
+        // 列表的数据
+        list: [],
 #if ($table.templateType == 11)
-
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  if (!props.${subJoinColumn.javaField}) {
-    message.error('请选择一个${table.classComment}')
-    return
-  }
-  formRef.value.open(type, id, props.${subJoinColumn.javaField})
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await ${simpleClassName}Api.delete${subSimpleClassName}(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
+        // 列表的总页数
+        total: 0,
+        // 查询参数
+        queryParams: {
+          pageNo: 1,
+          pageSize: 10,
+          ${subJoinColumn.javaField}: undefined
+        }
 #end
+      };
+    },
 #if ($table.templateType != 11)
-
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
+    created() {
+      this.getList();
+    },
+#end
+    watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
+        ${subJoinColumn.javaField}:{
+            handler(val) {
+              this.queryParams.${subJoinColumn.javaField} = val
+              this.handleQuery()
+            },
+            immediate: true
+      }
+    },
+    methods: {
+      /** 查询列表 */
+      getList() {
+        try {
+          this.loading = true;
+          const that = this;
+          #if ($table.templateType == 11)
+          ${simpleClassName}Api.get${subSimpleClassName}Page(this.queryParams).then(response => {
+            that.list = response.data.list;
+            that.total = response.data.total;
+          });
+          #else
+              #if ( $subTable.subJoinMany )
+                ${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(this.${subJoinColumn.javaField}).then(response=>{
+                  that.list = response.data;
+              })
+              #else
+                ${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(this.${subJoinColumn.javaField}).then(response=>{
+                  const data = response.data;
+                  if (!data) {
+                    return
+                  }
+                  that.list.push(data)
+              })
+              #end
+          #end
+        } finally {
+          this.loading = false;
+        }
+      },
+      /** 搜索按钮操作 */
+      handleQuery() {
+        this.queryParams.pageNo = 1;
+        this.getList();
+      },
+#if ($table.templateType == 11)
+      /** 添加/修改操作 */
+      openForm(id) {
+        if (!this.${subJoinColumn.javaField}) {
+          that.#[[$modal]]#.msgError('请选择一个${table.classComment}');
+          return;
+        }
+        this.#[[$]]#refs["formRef"].open(id, this.${subJoinColumn.javaField});
+      },
+      /** 删除按钮操作 */
+      handleDelete(row) {
+        const that = this;
+        try {
+          const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
+          this.#[[$modal]]#.confirm('是否确认删除${table.classComment}编号为"' + ${primaryColumn.javaField} + '"的数据项?').then(()=>{
+            return ${simpleClassName}Api.delete${subSimpleClassName}(${primaryColumn.javaField});
+          }).then(() => {
+            that.getList();
+            that.#[[$modal]]#.msgSuccess("删除成功");
+          }).catch(() => {});
+        } catch {}
+      },
+    }
 #end
-</script>
+  };
+</script>

+ 153 - 119
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/form.vue.vm

@@ -1,8 +1,8 @@
 <template>
   <div class="app-container">
     <!-- 对话框(添加 / 修改) -->
-    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" v-dialogDrag append-to-body>
-      <el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="80px">
+    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
+      <el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
           #foreach($column in $columns)
               #if ($column.createOperation || $column.updateOperation)
                   #set ($dictType = $column.dictType)
@@ -11,37 +11,35 @@
                   #set ($comment = $column.columnComment)
                   #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <treeselect
+                      <TreeSelect
                           v-model="formData.${javaField}"
                           :options="${classNameVar}Tree"
                           :normalizer="normalizer"
                           placeholder="请选择${comment}"
                       />
                     </el-form-item>
-                  #elseif ($column.htmlType == "input" && !$column.primaryKey)
-                      #if (!$column.primaryKey)## 忽略主键,不用在表单里
-                        <el-form-item label="${comment}" prop="${javaField}">
-                          <el-input v-model="form.${javaField}" placeholder="请输入${comment}" />
-                        </el-form-item>
-                      #end
+                  #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")## 图片上传
                       #set ($hasImageUploadColumn = true)
                     <el-form-item label="${comment}">
-                      <imageUpload v-model="form.${javaField}"/>
+                      <ImageUpload v-model="formData.${javaField}"/>
                     </el-form-item>
                   #elseif($column.htmlType == "fileUpload")## 文件上传
                       #set ($hasFileUploadColumn = true)
                     <el-form-item label="${comment}">
-                      <fileUpload v-model="form.${javaField}"/>
+                      <FileUpload v-model="formData.${javaField}"/>
                     </el-form-item>
                   #elseif($column.htmlType == "editor")## 文本编辑器
                       #set ($hasEditorColumn = true)
                     <el-form-item label="${comment}">
-                      <editor v-model="form.${javaField}" :min-height="192"/>
+                      <Editor v-model="formData.${javaField}" :min-height="192"/>
                     </el-form-item>
                   #elseif($column.htmlType == "select")## 下拉框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-select v-model="form.${javaField}" placeholder="请选择${comment}">
+                      <el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
                           #if ("" != $dictType)## 有数据字典
                             <el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
                                        :key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
@@ -52,7 +50,7 @@
                     </el-form-item>
                   #elseif($column.htmlType == "checkbox")## 多选框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-checkbox-group v-model="form.${javaField}">
+                      <el-checkbox-group v-model="formData.${javaField}">
                           #if ("" != $dictType)## 有数据字典
                             <el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
                                          :key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
@@ -63,7 +61,7 @@
                     </el-form-item>
                   #elseif($column.htmlType == "radio")## 单选框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-radio-group v-model="form.${javaField}">
+                      <el-radio-group v-model="formData.${javaField}">
                           #if ("" != $dictType)## 有数据字典
                             <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
                                       :key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"
@@ -75,18 +73,18 @@
                     </el-form-item>
                   #elseif($column.htmlType == "datetime")## 时间框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-date-picker clearable v-model="form.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
+                      <el-date-picker clearable v-model="formData.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
                     </el-form-item>
                   #elseif($column.htmlType == "textarea")## 文本框
                     <el-form-item label="${comment}" prop="${javaField}">
-                      <el-input v-model="form.${javaField}" type="textarea" placeholder="请输入内容" />
+                      <el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入内容" />
                     </el-form-item>
                   #end
               #end
           #end
       </el-form>
         ## 特殊:主子表专属逻辑
-        #if ( $subTables && $subTables.size() > 0 )
+        #if ( $table.templateType == 10 || $table.templateType == 12 )
           <!-- 子表的表单 -->
           <el-tabs v-model="subTabsName">
               #foreach ($subTable in $subTables)
@@ -121,17 +119,17 @@
   #end
   ## 特殊:树表专属逻辑
   #if ( $table.templateType == 2 )
-  import Treeselect from "@riophae/vue-treeselect";
+  import TreeSelect from "@riophae/vue-treeselect";
   import "@riophae/vue-treeselect/dist/vue-treeselect.css";
   #end
   ## 特殊:主子表专属逻辑
-  #if ( $subTables && $subTables.size() > 0 )
-  #foreach ($subSimpleClassName in $subSimpleClassNames)
-  import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'
-  #end
+  #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #foreach ($subSimpleClassName in $subSimpleClassNames)
+      import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'
+      #end
   #end
   export default {
-    name: "${simpleClassName}",
+    name: "${simpleClassName}Form",
     components: {
         #if ($hasImageUploadColumn)
           ImageUpload,
@@ -144,13 +142,13 @@
         #end
         ## 特殊:树表专属逻辑
         #if ( $table.templateType == 2 )
-          Treeselect,
+          TreeSelect,
         #end
         ## 特殊:主子表专属逻辑
-        #if ( $subTables && $subTables.size() > 0 )
-        #foreach ($subSimpleClassName in $subSimpleClassNames)
-          ${subSimpleClassName}Form,
-        #end
+        #if ( $table.templateType == 10 || $table.templateType == 12 )
+            #foreach ($subSimpleClassName in $subSimpleClassNames)
+               ${subSimpleClassName}Form,
+            #end
         #end
     },
     data() {
@@ -178,7 +176,7 @@
             #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 }],
+                        $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
                 #end
             #end
         },
@@ -186,43 +184,16 @@
           #if ( $table.templateType == 2 )
              ${classNameVar}Tree: [], // 树形结构
           #end
-          ## 特殊:主子表专属逻辑
-          #if ( $subTables && $subTables.size() > 0 )
+        ## 特殊:主子表专属逻辑
+        #if ( $table.templateType == 10 || $table.templateType == 12 )
+        #if ( $subTables && $subTables.size() > 0 )
             /** 子表的表单 */
              subTabsName: '$subClassNameVars.get(0)'
-          #end
+        #end
+        #end
       };
     },
     methods: {
-        ## 特殊:树表专属逻辑
-        #if ( $table.templateType == 2 )
-          /** 转换${table.classComment}数据结构 */
-          normalizer(node) {
-            if (node.children && !node.children.length) {
-              delete node.children;
-            }
-            return {
-              id: node.id,
-              label: node.name,
-              children: node.children
-            };
-          },
-        #end
-      /** 表单重置 */
-      reset() {
-        this.formData = {
-            #foreach ($column in $columns)
-                #if ($column.createOperation || $column.updateOperation)
-                    #if ($column.htmlType == "checkbox")
-                            $column.javaField: [],
-                    #else
-                            $column.javaField: undefined,
-                    #end
-                #end
-            #end
-        };
-        this.resetForm("formRef");
-      },
       /** 打开弹窗 */
      open(id) {
         this.dialogVisible = true;
@@ -246,88 +217,151 @@
             this.get${simpleClassName}Tree()
         #end
       },
-        ## 特殊:树表专属逻辑
-        #if ( $table.templateType == 2 )
-          /** 获得${table.classComment}树 */
-          get${simpleClassName}Tree() {
-            const that = this;
-            that.${classNameVar}Tree = [];
-            ${simpleClassName}Api.get${simpleClassName}List().then(res=>{
-              const root = { id: 0, name: '顶级${table.classComment}', children: [] };
-              root.children = this.handleTree(res.data, 'id', '${treeParentColumn.javaField}')
-              that.${classNameVar}Tree.push(root)
-            });
-          },
-        #end
-
       /** 提交按钮 */
       submitForm() {
         this.formLoading = true;
         try {
-          let data = this.formData;
-## 特殊:主子表专属逻辑
-#if ( $subTables && $subTables.size() > 0 )
-          // 需要校验的表单 ref
-          const validFormRefArr = [
-              #foreach ($subTable in $subTables)
-                  #set ($index = $foreach.count - 1)
-                  #set ($subClassNameVar = $subClassNameVars.get($index))
-                "${subClassNameVar}FormRef",
-              #end
-          ];
-
-          const validArr = []; // 校验
           const that = this;
-          validFormRefArr.forEach((item, index) => {
-            validArr.push(new Promise((resolve, reject) => {
-              that.getRef(item).validate(valid => {
-                if (valid){ // 校验成功
-                  resolve()
-                }else {
-                  reject(item.replace("FormRef","")) // 校验失败返回对应的表单
-                }
-              })
-            }))})
-          Promise.all(validArr).then(() => {
+          let data = this.formData;
+          let validate = false;
+          // 校验主表
+          this.getRef("formRef").validate(valid => {
+            validate = valid;
+          });
+        ## 特殊:主子表专属逻辑
+        #if ( $table.templateType == 10 || $table.templateType == 12 )
+        #if ( $subTables && $subTables.size() > 0 )
+            // 校验子表
+            this.validateSubFrom01().then(() => {
             // 全部校验通过-拼接子表的数据
             // 拼接子表的数据
               #foreach ($subTable in $subTables)
                   #set ($index = $foreach.count - 1)
                   #set ($subClassNameVar = $subClassNameVars.get($index))
-                data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = that.getRef(${subClassNameVar}FormRef).getData()
+                data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = that.getRef('${subClassNameVar}FormRef').getData();
               #end
-          }).catch((err)=>{
-            that.subTabsName = err
-          })
-#end
-
-        this.getRef("formRef").validate(valid => {
-          if (!valid) {
+            }).catch((err) => {
+                validate = false
+                that.subTabsName = err.replace("FormRef", ""); // 定位到没有校验通过的子表单
+            })
+        #end
+        #end
+          // 所有表单校验通过后方可提交
+          if (!validate) {
             return;
           }
           // 修改的提交
           if (data.${primaryColumn.javaField}) {
-            ${simpleClassName}Api.update${simpleClassName}(data).then(response => {
-              this.#[[$modal]]#.msgSuccess("修改成功");
-              this.dialogVisible = false;
-              this.#[[$]]#emit('success');
+                  ${simpleClassName}Api.update${simpleClassName}(data).then(response => {
+              that.#[[$modal]]#.msgSuccess("修改成功");
+              that.dialogVisible = false;
+              that.#[[$]]#emit('success');
             });
             return;
           }
           // 添加的提交
           ${simpleClassName}Api.create${simpleClassName}(data).then(response => {
-            this.#[[$modal]]#.msgSuccess("新增成功");
-            this.dialogVisible = false;
-            this.#[[$]]#emit('success');
+            that.#[[$modal]]#.msgSuccess("新增成功");
+            that.dialogVisible = false;
+            that.#[[$]]#emit('success');
           });
-        });
         }finally {
           this.formLoading = false
         }
       },
-      getRef(refName){
-        this.#[[$]]#refs[refName]
-      }
+      getRef(refName){ // TODO puhui999: 获得表单 ref,提取出来的目的呢是解决 #[[$]]# 在 if 中 end闭合不了的问题,代码生成后可删除此方法
+        return this.#[[$]]#refs[refName]
+      },
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+#if ( $subTables && $subTables.size() > 0 )
+      /** 校验子表单 */
+      validateSubFrom(item) {
+        return new Promise((resolve, reject) => {
+          this.$refs[item].validate()
+          .then(() => {
+            resolve()
+          })
+          .catch(() => {
+            reject(item)
+          })
+        })
+      },
+      /** 校验所有子表单 */
+      validateSubFrom01() {
+        // 需要校验的表单 ref
+        const validFormRefArr = [
+            #foreach ($subTable in $subTables)
+                #set ($index = $foreach.count - 1)
+                #set ($subClassNameVar = $subClassNameVars.get($index))
+              "${subClassNameVar}FormRef",
+            #end
+        ];
+        const validArr = []; // 校验
+        for (const item of validFormRefArr) {
+          validArr.push(this.validateSubFrom(item))
+        }
+        return new Promise((resolve, reject) => {
+          // 校验所有
+          Promise.all(validArr).then(() => {
+            resolve()
+          }).catch((err) => {
+            reject(err)
+          })
+        })
+      },
+#end
+#end
+        ## 特殊:树表专属逻辑
+        #if ( $table.templateType == 2 )
+          /** 获得${table.classComment}树 */
+          get${simpleClassName}Tree() {
+            const that = this;
+            that.${classNameVar}Tree = [];
+                  ${simpleClassName}Api.get${simpleClassName}List().then(res=>{
+              const root = { id: 0, name: '顶级${table.classComment}', children: [] };
+              root.children = this.handleTree(res.data, 'id', '${treeParentColumn.javaField}')
+              that.${classNameVar}Tree.push(root)
+            });
+          },
+        #end
+        ## 特殊:树表专属逻辑
+        #if ( $table.templateType == 2 )
+          /** 转换${table.classComment}数据结构 */
+          normalizer(node) {
+            if (node.children && !node.children.length) {
+              delete node.children;
+            }
+              #if ($treeNameColumn.javaField == "name")
+                return {
+                  id: node.id,
+                  label: node.name,
+                  children: node.children
+                };
+              #else
+                return {
+                  id: node.id,
+                  label: node['$treeNameColumn.javaField'],
+                  children: node.children
+                };
+              #end
+          },
+        #end
+      /** 表单重置 */
+      reset() {
+        this.formData = {
+            #foreach ($column in $columns)
+                #if ($column.createOperation || $column.updateOperation)
+                    #if ($column.htmlType == "checkbox")
+                            $column.javaField: [],
+                    #else
+                            $column.javaField: undefined,
+                    #end
+                #end
+            #end
+        };
+        this.resetForm("formRef");
+      },
     }
   };
 </script>

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

@@ -54,6 +54,7 @@
         <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">
@@ -64,7 +65,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-      ## 特殊:主子表专属逻辑 TODO puhui999: 普通模式
+      ## 特殊:主子表专属逻辑
       #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
       <el-table
           v-loading="loading"
@@ -89,7 +90,7 @@
       #else
       <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
       #end
-      ## 特殊:主子表专属逻辑 TODO puhui999: 内嵌模式
+      ## 特殊:主子表专属逻辑
       #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
         <!-- 子表的列表 -->
         <el-table-column type="expand">
@@ -140,13 +141,15 @@
         </template>
       </el-table-column>
     </el-table>
+## 特殊:树表专属逻辑(树不需要分页)
+#if ( $table.templateType != 2 )
     <!-- 分页组件 -->
     <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
                 @pagination="getList"/>
-
+#end
     <!-- 对话框(添加 / 修改) -->
     <${simpleClassName}Form ref="formRef" @success="getList" />
-  ## 特殊:主子表专属逻辑  TODO puhui999: ERP 模式
+  ## 特殊:主子表专属逻辑
   #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
     <!-- 子表的列表 -->
       <el-tabs model-value="$subClassNameVars.get(0)">
@@ -177,21 +180,25 @@ import FileUpload from '@/components/FileUpload';
 import Editor from '@/components/Editor';
 #end
 ## 特殊:主子表专属逻辑
+#if ( $table.templateType != 10 )
 #if ( $subTables && $subTables.size() > 0 )
     #foreach ($subSimpleClassName in $subSimpleClassNames)
     import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue'
     #end
 #end
+#end
 export default {
   name: "${simpleClassName}",
   components: {
           ${simpleClassName}Form,
 ## 特殊:主子表专属逻辑
+#if ( $table.templateType != 10 )
 #if ( $subTables && $subTables.size() > 0 )
       #foreach ($subSimpleClassName in $subSimpleClassNames)
           ${subSimpleClassName}List,
       #end
 #end
+#end
 #if ($hasImageUploadColumn)
     ImageUpload,
 #end
@@ -210,9 +217,9 @@ export default {
       exportLoading: false,
       // 显示搜索条件
       showSearch: true,
-      // 总条数
       ## 特殊:树表专属逻辑(树不需要分页接口)
       #if ( $table.templateType != 2 )
+        // 总条数
         total: 0,
       #end
       // ${table.classComment}列表
@@ -235,7 +242,7 @@ export default {
         #if ($column.listOperationCondition != 'BETWEEN')
         $column.javaField: null,
         #end
-        #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
+        #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
         $column.javaField: [],
         #end
         #end
@@ -253,7 +260,7 @@ export default {
       this.loading = true;
       ## 特殊:树表专属逻辑(树不需要分页接口)
       #if ( $table.templateType == 2 )
-        ${simpleClassName}Api.get${simpleClassName}List(queryParams).then(response => {
+        ${simpleClassName}Api.get${simpleClassName}List(this.queryParams).then(response => {
           this.list = this.handleTree(response.data, 'id', '${treeParentColumn.javaField}');
         })
       #else
@@ -302,14 +309,14 @@ export default {
               return ${simpleClassName}Api.export${simpleClassName}Excel(params);
             }).then(response => {
               that.#[[$]]#download.excel(response, '${table.classComment}.xls');
-            }));
+            });
       } catch {
       } finally {
         that.exportLoading = false;
       }
     },
       ## 特殊:主子表专属逻辑
-      #if ( $subTables && $subTables.size() > 0 )
+      #if ( $table.templateType == 11 )
         /** 选中行操作 */
         handleCurrentChange(row) {
          this.currentRow = row

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

@@ -1,137 +1,137 @@
 <template>
   <Dialog :title="dialogTitle" v-model="dialogVisible">
     <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
+        ref="formRef"
+        :model="formData"
+        :rules="formRules"
+        label-width="100px"
+        v-loading="formLoading"
     >
-#foreach($column in $columns)
-    #if ($column.createOperation || $column.updateOperation)
-        #set ($dictType = $column.dictType)
-        #set ($javaField = $column.javaField)
-        #set ($javaType = $column.javaType)
-        #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
-        #set ($comment = $column.columnComment)
-        #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
-        #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
-            #set ($dictMethod = "getIntDictOptions")
-        #elseif ($javaType == "String")
-            #set ($dictMethod = "getStrDictOptions")
-        #elseif ($javaType == "Boolean")
-            #set ($dictMethod = "getBoolDictOptions")
-        #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="defaultProps"
-          #else
-          :props="{...defaultProps, label: '$treeNameColumn.javaField'}"
-          #end
-          check-strictly
-          default-expand-all
-          placeholder="请选择${comment}"
-        />
-      </el-form-item>
-        #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
-      <el-form-item label="${comment}" prop="${javaField}">
-        <el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
-      </el-form-item>
-        #elseif($column.htmlType == "imageUpload")## 图片上传
-      <el-form-item label="${comment}" prop="${javaField}">
-        <UploadImg v-model="formData.${javaField}" />
-      </el-form-item>
-        #elseif($column.htmlType == "fileUpload")## 文件上传
-      <el-form-item label="${comment}" prop="${javaField}">
-        <UploadFile v-model="formData.${javaField}" />
-      </el-form-item>
-        #elseif($column.htmlType == "editor")## 文本编辑器
-      <el-form-item label="${comment}" prop="${javaField}">
-        <Editor v-model="formData.${javaField}" height="150px" />
-      </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 $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-                #else##没数据字典
-          <el-option label="请选择字典生成" value="" />
+        #foreach($column in $columns)
+            #if ($column.createOperation || $column.updateOperation)
+                #set ($dictType = $column.dictType)
+                #set ($javaField = $column.javaField)
+                #set ($javaType = $column.javaType)
+                #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+                #set ($comment = $column.columnComment)
+                #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
+                #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                    #set ($dictMethod = "getIntDictOptions")
+                #elseif ($javaType == "String")
+                    #set ($dictMethod = "getStrDictOptions")
+                #elseif ($javaType == "Boolean")
+                    #set ($dictMethod = "getBoolDictOptions")
                 #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 $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
-            :key="dict.value"
-            :label="dict.value"
-          >
-            {{ dict.label }}
-          </el-checkbox>
-                #else##没数据字典
-          <el-checkbox>请选择字典生成</el-checkbox>
+                #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
+                  <el-form-item label="${comment}" prop="${javaField}">
+                    <el-tree-select
+                        v-model="formData.${javaField}"
+                        :data="${classNameVar}Tree"
+                        #if ($treeNameColumn.javaField == "name")
+                        :props="defaultProps"
+                        #else
+                        :props="{...defaultProps, label: '$treeNameColumn.javaField'}"
+                        #end
+                        check-strictly
+                        default-expand-all
+                        placeholder="请选择${comment}"
+                    />
+                  </el-form-item>
+                #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+                  <el-form-item label="${comment}" prop="${javaField}">
+                    <el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
+                  </el-form-item>
+                #elseif($column.htmlType == "imageUpload")## 图片上传
+                  <el-form-item label="${comment}" prop="${javaField}">
+                    <UploadImg v-model="formData.${javaField}" />
+                  </el-form-item>
+                #elseif($column.htmlType == "fileUpload")## 文件上传
+                  <el-form-item label="${comment}" prop="${javaField}">
+                    <UploadFile v-model="formData.${javaField}" />
+                  </el-form-item>
+                #elseif($column.htmlType == "editor")## 文本编辑器
+                  <el-form-item label="${comment}" prop="${javaField}">
+                    <Editor v-model="formData.${javaField}" height="150px" />
+                  </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 $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
+                              :key="dict.value"
+                              :label="dict.label"
+                              :value="dict.value"
+                          />
+                        #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 $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
+                              :key="dict.value"
+                              :label="dict.value"
+                          >
+                            {{ dict.label }}
+                          </el-checkbox>
+                        #else##没数据字典
+                          <el-checkbox>请选择字典生成</el-checkbox>
+                        #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 $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
+                              :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}"
+                        type="date"
+                        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
-        </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 $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
-            :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}"
-          type="date"
-          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
-#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 label="${subTable.classComment}" name="$subClassNameVar">
-        <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData.id" />
-      </el-tab-pane>
-    #end
-    </el-tabs>
-#end
+      ## 特殊:主子表专属逻辑
+      #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 label="${subTable.classComment}" name="$subClassNameVar">
+                <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData.id" />
+              </el-tab-pane>
+            #end
+        </el-tabs>
+      #end
     <template #footer>
       <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
       <el-button @click="dialogVisible = false">取 消</el-button>
@@ -139,160 +139,160 @@
   </Dialog>
 </template>
 <script setup lang="ts">
-import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
-import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
-## 特殊:树表专属逻辑
-#if ( $table.templateType == 2 )
-import { defaultProps, handleTree } from '@/utils/tree'
-#end
-## 特殊:主子表专属逻辑
-#if ( $table.templateType == 10 || $table.templateType == 12 )
-#foreach ($subSimpleClassName in $subSimpleClassNames)
-import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'
-#end
-#end
+  import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
+  import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
+      ## 特殊:树表专属逻辑
+      #if ( $table.templateType == 2 )
+      import { defaultProps, handleTree } from '@/utils/tree'
+      #end
+      ## 特殊:主子表专属逻辑
+      #if ( $table.templateType == 10 || $table.templateType == 12 )
+          #foreach ($subSimpleClassName in $subSimpleClassNames)
+          import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'
+          #end
+      #end
 
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
+  const { t } = useI18n() // 国际化
+  const message = useMessage() // 消息弹窗
+
+  const dialogVisible = ref(false) // 弹窗的是否展示
+  const dialogTitle = ref('') // 弹窗的标题
+  const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+  const formType = ref('') // 表单的类型:create - 新增;update - 修改
+  const formData = ref({
+      #foreach ($column in $columns)
+          #if ($column.createOperation || $column.updateOperation)
+              #if ($column.htmlType == "checkbox")
+                      $column.javaField: [],
+              #else
+                      $column.javaField: undefined,
+              #end
+          #end
+      #end
+  })
+  const formRules = reactive({
+      #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
+  })
+  const formRef = ref() // 表单 Ref
+      ## 特殊:树表专属逻辑
+      #if ( $table.templateType == 2 )
+      const ${classNameVar}Tree = ref() // 树形结构
+      #end
+      ## 特殊:主子表专属逻辑
+      #if ( $table.templateType == 10 || $table.templateType == 12 )
+          #if ( $subTables && $subTables.size() > 0 )
 
-const dialogVisible = ref(false) // 弹窗的是否展示
-const dialogTitle = ref('') // 弹窗的标题
-const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
-const formType = ref('') // 表单的类型:create - 新增;update - 修改
-const formData = ref({
-#foreach ($column in $columns)
-    #if ($column.createOperation || $column.updateOperation)
-      #if ($column.htmlType == "checkbox")
-  $column.javaField: [],
-      #else
-  $column.javaField: undefined,
+          /** 子表的表单 */
+          const subTabsName = ref('$subClassNameVars.get(0)')
+              #foreach ($subClassNameVar in $subClassNameVars)
+              const ${subClassNameVar}FormRef = ref()
+              #end
+          #end
       #end
-    #end
-#end
-})
-const formRules = reactive({
-#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
-})
-const formRef = ref() // 表单 Ref
-## 特殊:树表专属逻辑
-#if ( $table.templateType == 2 )
-const ${classNameVar}Tree = ref() // 树形结构
-#end
-## 特殊:主子表专属逻辑
-#if ( $table.templateType == 10 || $table.templateType == 12 )
-#if ( $subTables && $subTables.size() > 0 )
 
-/** 子表的表单 */
-const subTabsName = ref('$subClassNameVars.get(0)')
-#foreach ($subClassNameVar in $subClassNameVars)
-const ${subClassNameVar}FormRef = ref()
-#end
-#end
-#end
+  /** 打开弹窗 */
+  const open = async (type: string, id?: number) => {
+    dialogVisible.value = true
+    dialogTitle.value = t('action.' + type)
+    formType.value = type
+    resetForm()
+    // 修改时,设置数据
+    if (id) {
+      formLoading.value = true
+      try {
+        formData.value = await ${simpleClassName}Api.get${simpleClassName}(id)
+      } finally {
+        formLoading.value = false
+      }
+    }
+      ## 特殊:树表专属逻辑
+      #if ( $table.templateType == 2 )
+        await get${simpleClassName}Tree()
+      #end
+  }
+  defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
-/** 打开弹窗 */
-const open = async (type: string, id?: number) => {
-  dialogVisible.value = true
-  dialogTitle.value = t('action.' + type)
-  formType.value = type
-  resetForm()
-  // 修改时,设置数据
-  if (id) {
+  /** 提交表单 */
+  const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+  const submitForm = async () => {
+    // 校验表单
+    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))
+                try {
+                  await ${subClassNameVar}FormRef.value.validate()
+                } catch (e) {
+                  subTabsName.value = '${subClassNameVar}'
+                  return
+                }
+              #end
+          #end
+      #end
+    // 提交请求
     formLoading.value = true
     try {
-      formData.value = await ${simpleClassName}Api.get${simpleClassName}(id)
+      const data = formData.value as unknown as ${simpleClassName}Api.${simpleClassName}VO
+        ## 特殊:主子表专属逻辑
+        #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))
+                  data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = ${subClassNameVar}FormRef.value.getData()
+                #end
+            #end
+        #end
+      if (formType.value === 'create') {
+        await ${simpleClassName}Api.create${simpleClassName}(data)
+        message.success(t('common.createSuccess'))
+      } else {
+        await ${simpleClassName}Api.update${simpleClassName}(data)
+        message.success(t('common.updateSuccess'))
+      }
+      dialogVisible.value = false
+      // 发送操作成功的事件
+      emit('success')
     } finally {
       formLoading.value = false
     }
   }
-## 特殊:树表专属逻辑
-#if ( $table.templateType == 2 )
-  await get${simpleClassName}Tree()
-#end
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  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))
-  try {
-    await ${subClassNameVar}FormRef.value.validate()
-  } catch (e) {
-    subTabsName.value = '${subClassNameVar}'
-    return
-  }
-  #end
-#end
-#end
-  // 提交请求
-  formLoading.value = true
-  try {
-    const data = formData.value as unknown as ${simpleClassName}Api.${simpleClassName}VO
-## 特殊:主子表专属逻辑
-#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))
-    data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = ${subClassNameVar}FormRef.value.getData()
-  #end
-#end
-#end
-    if (formType.value === 'create') {
-      await ${simpleClassName}Api.create${simpleClassName}(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await ${simpleClassName}Api.update${simpleClassName}(data)
-      message.success(t('common.updateSuccess'))
+  /** 重置表单 */
+  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
     }
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
+    formRef.value?.resetFields()
   }
-}
+      ## 特殊:树表专属逻辑
+      #if ( $table.templateType == 2 )
 
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-#foreach ($column in $columns)
-  #if ($column.createOperation || $column.updateOperation)
-      #if ($column.htmlType == "checkbox")
-    $column.javaField: [],
-      #else
-    $column.javaField: undefined,
+      /** 获得${table.classComment}树 */
+      const get${simpleClassName}Tree = async () => {
+              ${classNameVar}Tree.value = []
+        const data = await ${simpleClassName}Api.get${simpleClassName}List()
+        const root: Tree = { id: 0, name: '顶级${table.classComment}', children: [] }
+        root.children = handleTree(data, 'id', '${treeParentColumn.javaField}')
+              ${classNameVar}Tree.value.push(root)
+      }
       #end
-  #end
-#end
-  }
-  formRef.value?.resetFields()
-}
-## 特殊:树表专属逻辑
-#if ( $table.templateType == 2 )
-
-/** 获得${table.classComment}树 */
-const get${simpleClassName}Tree = async () => {
-  ${classNameVar}Tree.value = []
-  const data = await ${simpleClassName}Api.get${simpleClassName}List()
-  const root: Tree = { id: 0, name: '顶级${table.classComment}', children: [] }
-  root.children = handleTree(data, 'id', '${treeParentColumn.javaField}')
-  ${classNameVar}Tree.value.push(root)
-}
-#end
 </script>

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

@@ -2,372 +2,375 @@
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
+        class="-mb-15px"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="68px"
     >
-    #foreach($column in $columns)
-        #if ($column.listOperation)
-            #set ($dictType = $column.dictType)
-            #set ($javaField = $column.javaField)
-            #set ($javaType = $column.javaType)
-            #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
-            #set ($comment = $column.columnComment)
-            #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
-            #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
-                #set ($dictMethod = "getIntDictOptions")
-            #elseif ($javaType == "String")
-                #set ($dictMethod = "getStrDictOptions")
-            #elseif ($javaType == "Boolean")
-                #set ($dictMethod = "getBoolDictOptions")
-            #end
-            #if ($column.htmlType == "input")
-      <el-form-item label="${comment}" prop="${javaField}">
-        <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")
-      <el-form-item label="${comment}" prop="${javaField}">
-        <el-select
-          v-model="queryParams.${javaField}"
-          placeholder="请选择${comment}"
-          clearable
-          class="!w-240px"
-        >
-                #if ("" != $dictType)## 设置了 dictType 数据字典的情况
-          <el-option
-            v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-                #else## 未设置 dictType 数据字典的情况
-          <el-option label="请选择字典生成" value="" />
+        #foreach($column in $columns)
+            #if ($column.listOperation)
+                #set ($dictType = $column.dictType)
+                #set ($javaField = $column.javaField)
+                #set ($javaType = $column.javaType)
+                #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+                #set ($comment = $column.columnComment)
+                #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
+                #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                    #set ($dictMethod = "getIntDictOptions")
+                #elseif ($javaType == "String")
+                    #set ($dictMethod = "getStrDictOptions")
+                #elseif ($javaType == "Boolean")
+                    #set ($dictMethod = "getBoolDictOptions")
                 #end
-        </el-select>
-      </el-form-item>
-    #elseif($column.htmlType == "datetime")
-      #if ($column.listOperationCondition != "BETWEEN")## 非范围
-      <el-form-item label="${comment}" prop="${javaField}">
-        <el-date-picker
-          v-model="queryParams.${javaField}"
-          value-format="YYYY-MM-DD"
-          type="date"
-          placeholder="选择${comment}"
-          clearable
-          class="!w-240px"
-        />
-      </el-form-item>
-      #else## 范围
-      <el-form-item label="${comment}" prop="${javaField}">
-        <el-date-picker
-          v-model="queryParams.${javaField}"
-          value-format="YYYY-MM-DD HH:mm:ss"
-          type="daterange"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-          class="!w-240px"
-        />
-      </el-form-item>
-      #end
-    #end
-    #end
-    #end
+                #if ($column.htmlType == "input")
+                  <el-form-item label="${comment}" prop="${javaField}">
+                    <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")
+                  <el-form-item label="${comment}" prop="${javaField}">
+                    <el-select
+                        v-model="queryParams.${javaField}"
+                        placeholder="请选择${comment}"
+                        clearable
+                        class="!w-240px"
+                    >
+                        #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+                          <el-option
+                              v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
+                              :key="dict.value"
+                              :label="dict.label"
+                              :value="dict.value"
+                          />
+                        #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}" prop="${javaField}">
+                        <el-date-picker
+                            v-model="queryParams.${javaField}"
+                            value-format="YYYY-MM-DD"
+                            type="date"
+                            placeholder="选择${comment}"
+                            clearable
+                            class="!w-240px"
+                        />
+                      </el-form-item>
+                    #else## 范围
+                      <el-form-item label="${comment}" prop="${javaField}">
+                        <el-date-picker
+                            v-model="queryParams.${javaField}"
+                            value-format="YYYY-MM-DD HH:mm:ss"
+                            type="daterange"
+                            start-placeholder="开始日期"
+                            end-placeholder="结束日期"
+                            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+                            class="!w-240px"
+                        />
+                      </el-form-item>
+                    #end
+                #end
+            #end
+        #end
       <el-form-item>
         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
         <el-button
-          type="primary"
-          plain
-          @click="openForm('create')"
-          v-hasPermi="['${permissionPrefix}:create']"
+            type="primary"
+            plain
+            @click="openForm('create')"
+            v-hasPermi="['${permissionPrefix}:create']"
         >
           <Icon icon="ep:plus" class="mr-5px" /> 新增
         </el-button>
         <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['${permissionPrefix}:export']"
+            type="success"
+            plain
+            @click="handleExport"
+            :loading="exportLoading"
+            v-hasPermi="['${permissionPrefix}:export']"
         >
           <Icon icon="ep:download" class="mr-5px" /> 导出
         </el-button>
-## 特殊:树表专属逻辑
-#if ( $table.templateType == 2 )
-        <el-button type="danger" plain @click="toggleExpandAll">
-          <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
-        </el-button>
-#end
+          ## 特殊:树表专属逻辑
+          #if ( $table.templateType == 2 )
+            <el-button type="danger" plain @click="toggleExpandAll">
+              <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
+            </el-button>
+          #end
       </el-form-item>
     </el-form>
   </ContentWrap>
 
   <!-- 列表 -->
   <ContentWrap>
-## 特殊:主子表专属逻辑  TODO puhui999: 普通模式
-#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
-    <el-table
-      v-loading="loading"
-      :data="list"
-      :stripe="true"
-      :show-overflow-tooltip="true"
-      highlight-current-row
-      @current-change="handleCurrentChange"
-    >
-## 特殊:树表专属逻辑
-#elseif ( $table.templateType == 2 )
-    <el-table
-      v-loading="loading"
-      :data="list"
-      :stripe="true"
-      :show-overflow-tooltip="true"
-      row-key="id"
-      :default-expand-all="isExpandAll"
-      v-if="refreshTable"
-    >
-#else
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-#end
-## 特殊:主子表专属逻辑  TODO puhui999: 内嵌模式
-#if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
-      <!-- 子表的列表 -->
-      <el-table-column type="expand">
-        <template #default="scope">
-          <el-tabs model-value="$subClassNameVars.get(0)">
-            #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 label="${subTable.classComment}" name="$subClassNameVar">
-              <${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
-            </el-tab-pane>
-            #end
-          </el-tabs>
-        </template>
-      </el-table-column>
-#end
+      ## 特殊:主子表专属逻辑
+      #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+      <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          :show-overflow-tooltip="true"
+          highlight-current-row
+          @current-change="handleCurrentChange"
+      >
+          ## 特殊:树表专属逻辑
+      #elseif ( $table.templateType == 2 )
+      <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          :show-overflow-tooltip="true"
+          row-key="id"
+          :default-expand-all="isExpandAll"
+          v-if="refreshTable"
+      >
+      #else
+      <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      #end
+      ## 特殊:主子表专属逻辑
+      #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
+        <!-- 子表的列表 -->
+        <el-table-column type="expand">
+          <template #default="scope">
+            <el-tabs model-value="$subClassNameVars.get(0)">
+                #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 label="${subTable.classComment}" name="$subClassNameVar">
+                    <${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
+                  </el-tab-pane>
+                #end
+            </el-tabs>
+          </template>
+        </el-table-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")## 时间类型
-      <el-table-column
-        label="${comment}"
-        align="center"
-        prop="${javaField}"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-        #elseif($column.dictType && "" != $column.dictType)## 数据字典
-      <el-table-column label="${comment}" align="center" prop="${javaField}">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.${column.javaField}" />
-        </template>
-      </el-table-column>
-        #else
-      <el-table-column label="${comment}" align="center" prop="${javaField}" />
-        #end
+          #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")## 时间类型
+                <el-table-column
+                    label="${comment}"
+                    align="center"
+                    prop="${javaField}"
+                    :formatter="dateFormatter"
+                    width="180px"
+                />
+              #elseif($column.dictType && "" != $column.dictType)## 数据字典
+                <el-table-column label="${comment}" align="center" prop="${javaField}">
+                  <template #default="scope">
+                    <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.${column.javaField}" />
+                  </template>
+                </el-table-column>
+              #else
+                <el-table-column label="${comment}" align="center" prop="${javaField}" />
+              #end
+          #end
       #end
-    #end
-      <el-table-column label="操作" align="center">
-        <template #default="scope">
-          <el-button
+    <el-table-column label="操作" align="center">
+      <template #default="scope">
+        <el-button
             link
             type="primary"
             @click="openForm('update', scope.row.id)"
             v-hasPermi="['${permissionPrefix}:update']"
-          >
-            编辑
-          </el-button>
-          <el-button
+        >
+          编辑
+        </el-button>
+        <el-button
             link
             type="danger"
             @click="handleDelete(scope.row.id)"
             v-hasPermi="['${permissionPrefix}:delete']"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
+        >
+          删除
+        </el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+## 特殊:树表专属逻辑(树不需要分页)
+#if ( $table.templateType != 2 )
     <!-- 分页 -->
     <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
     />
+#end
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
   <${simpleClassName}Form ref="formRef" @success="getList" />
-## 特殊:主子表专属逻辑 TODO puhui999: ERP 模式
-#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
-  <!-- 子表的列表 -->
-  <ContentWrap>
-    <el-tabs model-value="$subClassNameVars.get(0)">
-      #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 label="${subTable.classComment}" name="$subClassNameVar">
-        <${subSimpleClassName}List :${subJoinColumn_strikeCase}="currentRow.id" />
-      </el-tab-pane>
-      #end
-    </el-tabs>
-  </ContentWrap>
-#end
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+      <!-- 子表的列表 -->
+      <ContentWrap>
+        <el-tabs model-value="$subClassNameVars.get(0)">
+            #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 label="${subTable.classComment}" name="$subClassNameVar">
+                <${subSimpleClassName}List :${subJoinColumn_strikeCase}="currentRow.id" />
+              </el-tab-pane>
+            #end
+        </el-tabs>
+      </ContentWrap>
+    #end
 </template>
 
 <script setup lang="ts">
-import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
-## 特殊:树表专属逻辑
-#if ( $table.templateType == 2 )
-import { handleTree } from '@/utils/tree'
-#end
-import download from '@/utils/download'
-import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
-import ${simpleClassName}Form from './${simpleClassName}Form.vue'
-## 特殊:主子表专属逻辑
-#if ( $subTables && $subTables.size() > 0 )
-#foreach ($subSimpleClassName in $subSimpleClassNames)
-import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue'
-#end
-#end
+  import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
+  import { dateFormatter } from '@/utils/formatTime'
+      ## 特殊:树表专属逻辑
+      #if ( $table.templateType == 2 )
+      import { handleTree } from '@/utils/tree'
+      #end
+  import download from '@/utils/download'
+  import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
+  import ${simpleClassName}Form from './${simpleClassName}Form.vue'
+      ## 特殊:主子表专属逻辑
+      #if ( $table.templateType != 10 )
+          #foreach ($subSimpleClassName in $subSimpleClassNames)
+          import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue'
+          #end
+      #end
 
-defineOptions({ name: '${table.className}' })
+  defineOptions({ name: '${table.className}' })
 
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
+  const message = useMessage() // 消息弹窗
+  const { t } = useI18n() // 国际化
 
-const loading = ref(true) // 列表的加载中
-const list = ref([]) // 列表的数据
-## 特殊:树表专属逻辑(树不需要分页接口)
-#if ( $table.templateType != 2 )
-const total = ref(0) // 列表的总页数
-#end
-const queryParams = reactive({
-## 特殊:树表专属逻辑(树不需要分页接口)
-#if ( $table.templateType != 2 )
-  pageNo: 1,
-  pageSize: 10,
-#end
-  #foreach ($column in $columns)
-    #if ($column.listOperation)
-      #if ($column.listOperationCondition != 'BETWEEN')
-  $column.javaField: null,
-  #end
-      #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
-  $column.javaField: [],
+  const loading = ref(true) // 列表的加载中
+  const list = ref([]) // 列表的数据
+      ## 特殊:树表专属逻辑(树不需要分页接口)
+      #if ( $table.templateType != 2 )
+      const total = ref(0) // 列表的总页数
       #end
-    #end
-  #end
-})
-const queryFormRef = ref() // 搜索的表单
-const exportLoading = ref(false) // 导出的加载中
+  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: null,
+              #end
+              #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
+                      $column.javaField: [],
+              #end
+          #end
+      #end
+  })
+  const queryFormRef = ref() // 搜索的表单
+  const exportLoading = ref(false) // 导出的加载中
 
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-## 特殊:树表专属逻辑(树不需要分页接口)
-  #if ( $table.templateType == 2 )
-    const data = await ${simpleClassName}Api.get${simpleClassName}List(queryParams)
-    list.value = handleTree(data, 'id', '${treeParentColumn.javaField}')
-  #else
-    const data = await ${simpleClassName}Api.get${simpleClassName}Page(queryParams)
-    list.value = data.list
-    total.value = data.total
-  #end
-  } finally {
-    loading.value = false
+  /** 查询列表 */
+  const getList = async () => {
+    loading.value = true
+    try {
+        ## 特殊:树表专属逻辑(树不需要分页接口)
+        #if ( $table.templateType == 2 )
+          const data = await ${simpleClassName}Api.get${simpleClassName}List(queryParams)
+          list.value = handleTree(data, 'id', '${treeParentColumn.javaField}')
+        #else
+          const data = await ${simpleClassName}Api.get${simpleClassName}Page(queryParams)
+          list.value = data.list
+          total.value = data.total
+        #end
+    } finally {
+      loading.value = false
+    }
   }
-}
 
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
+  /** 搜索按钮操作 */
+  const handleQuery = () => {
+    queryParams.pageNo = 1
+    getList()
+  }
 
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  handleQuery()
-}
+  /** 重置按钮操作 */
+  const resetQuery = () => {
+    queryFormRef.value.resetFields()
+    handleQuery()
+  }
 
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
-}
+  /** 添加/修改操作 */
+  const formRef = ref()
+  const openForm = (type: string, id?: number) => {
+    formRef.value.open(type, id)
+  }
 
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await ${simpleClassName}Api.delete${simpleClassName}(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
+  /** 删除按钮操作 */
+  const handleDelete = async (id: number) => {
+    try {
+      // 删除的二次确认
+      await message.delConfirm()
+      // 发起删除
+      await ${simpleClassName}Api.delete${simpleClassName}(id)
+      message.success(t('common.delSuccess'))
+      // 刷新列表
+      await getList()
+    } catch {}
+  }
 
-/** 导出按钮操作 */
-const handleExport = async () => {
-  try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
-    exportLoading.value = true
-    const data = await ${simpleClassName}Api.export${simpleClassName}(queryParams)
-    download.excel(data, '${table.classComment}.xls')
-  } catch {
-  } finally {
-    exportLoading.value = false
+  /** 导出按钮操作 */
+  const handleExport = async () => {
+    try {
+      // 导出的二次确认
+      await message.exportConfirm()
+      // 发起导出
+      exportLoading.value = true
+      const data = await ${simpleClassName}Api.export${simpleClassName}(queryParams)
+      download.excel(data, '${table.classComment}.xls')
+    } catch {
+    } finally {
+      exportLoading.value = false
+    }
   }
-}
+      ## 特殊:主子表专属逻辑
+      #if ( $table.templateType == 11 )
 
-## 特殊:主子表专属逻辑
-#if ( $subTables && $subTables.size() > 0 )
-/** 选中行操作 */
-const currentRow = ref({}) // 选中行
-const handleCurrentChange = (row) => {
-  currentRow.value = row
-}
-#end
-## 特殊:树表专属逻辑
-#if ( $table.templateType == 2 )
+      /** 选中行操作 */
+      const currentRow = ref({}) // 选中行
+      const handleCurrentChange = (row) => {
+        currentRow.value = row
+      }
+      #end
+      ## 特殊:树表专属逻辑
+      #if ( $table.templateType == 2 )
 
-/** 展开/折叠操作 */
-const isExpandAll = ref(true) // 是否展开,默认全部展开
-const refreshTable = ref(true) // 重新渲染表格状态
-const toggleExpandAll = async () => {
-  refreshTable.value = false
-  isExpandAll.value = !isExpandAll.value
-  await nextTick()
-  refreshTable.value = true
-}
-#end
+      /** 展开/折叠操作 */
+      const isExpandAll = ref(true) // 是否展开,默认全部展开
+      const refreshTable = ref(true) // 重新渲染表格状态
+      const toggleExpandAll = async () => {
+        refreshTable.value = false
+        isExpandAll.value = !isExpandAll.value
+        await nextTick()
+        refreshTable.value = true
+      }
+      #end
 
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
+  /** 初始化 **/
+  onMounted(() => {
+    getList()
+  })
 </script>

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

@@ -0,0 +1,202 @@
+package cn.iocoder.yudao.module.infra.service.codegen.inner;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.ZipUtil;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
+import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link CodegenEngine} 的单元测试
+ *
+ * @author 芋道源码
+ */
+public class CodegenEngineVue2Test extends BaseMockitoUnitTest {
+
+    @InjectMocks
+    private CodegenEngine codegenEngine;
+
+    @Spy
+    private CodegenProperties codegenProperties = new CodegenProperties()
+            .setBasePackage("cn.iocoder.yudao");
+
+    @BeforeEach
+    public void setUp() {
+        codegenEngine.initGlobalBindingMap();
+    }
+
+    @Test
+    public void testExecute_vue2_one() {
+        // 准备参数
+        CodegenTableDO table = getTable("student")
+                .setFrontType(CodegenFrontTypeEnum.VUE2.getType())
+                .setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
+        List<CodegenColumnDO> columns = getColumnList("student");
+
+        // 调用
+        Map<String, String> result = codegenEngine.execute(table, columns, null, null);
+        // 断言
+        assertResult(result, "codegen/vue2_one");
+//        writeResult(result, "/root/ruoyi-vue-pro/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one");
+    }
+
+    @Test
+    public void testExecute_vue2_tree() {
+        // 准备参数
+        CodegenTableDO table = getTable("category")
+                .setFrontType(CodegenFrontTypeEnum.VUE2.getType())
+                .setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
+        List<CodegenColumnDO> columns = getColumnList("category");
+
+        // 调用
+        Map<String, String> result = codegenEngine.execute(table, columns, null, null);
+        // 断言
+        assertResult(result, "codegen/vue2_tree");
+//        writeResult(result, "/root/ruoyi-vue-pro/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree");
+//        writeFile(result, "/Users/yunai/test/demo66.zip");
+    }
+
+    @Test
+    public void testExecute_vue2_master_normal() {
+        testExecute_vue2_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "codegen/vue2_master_normal");
+    }
+
+    @Test
+    public void testExecute_vue2_master_erp() {
+        testExecute_vue2_master(CodegenTemplateTypeEnum.MASTER_ERP, "codegen/vue2_master_erp");
+    }
+
+    @Test
+    public void testExecute_vue2_master_inner() {
+        testExecute_vue2_master(CodegenTemplateTypeEnum.MASTER_INNER, "codegen/vue2_master_inner");
+    }
+
+    private void testExecute_vue2_master(CodegenTemplateTypeEnum templateType,
+                                         String path) {
+        // 准备参数
+        CodegenTableDO table = getTable("student")
+                .setFrontType(CodegenFrontTypeEnum.VUE2.getType())
+                .setTemplateType(templateType.getType());
+        List<CodegenColumnDO> columns = getColumnList("student");
+        // 准备参数(子表)
+        CodegenTableDO contactTable = getTable("contact")
+                .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                .setFrontType(CodegenFrontTypeEnum.VUE2.getType())
+                .setSubJoinColumnId(100L).setSubJoinMany(true);
+        List<CodegenColumnDO> contactColumns = getColumnList("contact");
+        // 准备参数(班主任)
+        CodegenTableDO teacherTable = getTable("teacher")
+                .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                .setFrontType(CodegenFrontTypeEnum.VUE2.getType())
+                .setSubJoinColumnId(200L).setSubJoinMany(false);
+        List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
+
+        // 调用
+        Map<String, String> result = codegenEngine.execute(table, columns,
+                Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
+        // 断言
+        assertResult(result, path);
+//        writeResult(result, "/root/ruoyi-vue-pro/yudao-module-infra/yudao-module-infra-biz/src/test/resources/" + path);
+//        writeFile(result, "/Users/yunai/test/demo11.zip");
+    }
+
+    private static CodegenTableDO getTable(String name) {
+        String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
+        return JsonUtils.parseObject(content, "table", CodegenTableDO.class);
+    }
+
+    private static List<CodegenColumnDO> getColumnList(String name) {
+        String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
+        List<CodegenColumnDO> list = JsonUtils.parseArray(content, "columns", CodegenColumnDO.class);
+        list.forEach(column -> {
+            if (column.getNullable() == null) {
+                column.setNullable(false);
+            }
+            if (column.getCreateOperation() == null) {
+                column.setCreateOperation(false);
+            }
+            if (column.getUpdateOperation() == null) {
+                column.setUpdateOperation(false);
+            }
+            if (column.getListOperation() == null) {
+                column.setListOperation(false);
+            }
+            if (column.getListOperationResult() == null) {
+                column.setListOperationResult(false);
+            }
+        });
+        return list;
+    }
+
+    @SuppressWarnings("rawtypes")
+    private static void assertResult(Map<String, String> result, String path) {
+        String assertContent = ResourceUtil.readUtf8Str(path + "/assert.json");
+        List<HashMap> asserts = JsonUtils.parseArray(assertContent, HashMap.class);
+        assertEquals(asserts.size(), result.size());
+        // 校验每个文件
+        asserts.forEach(assertMap -> {
+            String contentPath = (String) assertMap.get("contentPath");
+            String filePath = (String) assertMap.get("filePath");
+            String content = ResourceUtil.readUtf8Str(path + "/" + contentPath);
+            assertEquals(content, result.get(filePath), filePath + ":不匹配");
+        });
+    }
+
+    // ==================== 调试专用 ====================
+
+    /**
+     * 【调试使用】将生成的代码,写入到文件
+     *
+     * @param result 生成的代码
+     * @param path 写入文件的路径
+     */
+    private void writeFile(Map<String, String> result, String path) {
+        // 生成压缩包
+        String[] paths = result.keySet().toArray(new String[0]);
+        ByteArrayInputStream[] ins = result.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipUtil.zip(outputStream, paths, ins);
+        // 写入文件
+        FileUtil.writeBytes(outputStream.toByteArray(), path);
+    }
+
+    /**
+     * 【调试使用】将生成的结果,写入到文件
+     *
+     * @param result 生成的代码
+     * @param basePath 写入文件的路径(绝对路径)
+     */
+    private void writeResult(Map<String, String> result, String basePath) {
+        // 写入文件内容
+        List<Map<String, String>> asserts = new ArrayList<>();
+        result.forEach((filePath, fileContent) -> {
+            String lastFilePath = StrUtil.subAfter(filePath, '/', true);
+            String contentPath = StrUtil.subAfter(lastFilePath, '.', true)
+                    + '/' + StrUtil.subBefore(lastFilePath, '.', true);
+            asserts.add(MapUtil.<String, String>builder().put("filePath", filePath)
+                    .put("contentPath", contentPath).build());
+            FileUtil.writeUtf8String(fileContent, basePath + "/" + contentPath);
+        });
+        // 写入 assert.json 文件
+        FileUtil.writeUtf8String(JsonUtils.toJsonPrettyString(asserts), basePath +"/assert.json");
+    }
+
+}