Browse Source

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

puhui999 1 year ago
parent
commit
df69fecbb4

+ 5 - 0
sql/mysql/ruoyi-vue-pro.sql

@@ -450,6 +450,11 @@ CREATE TABLE `infra_codegen_table`  (
   `template_type` tinyint NOT NULL DEFAULT 1 COMMENT '模板类型',
   `front_type` tinyint NOT NULL COMMENT '前端类型',
   `parent_menu_id` bigint NULL DEFAULT NULL COMMENT '父菜单编号',
+  `master_table_id` bigint NULL DEFAULT NULL COMMENT '主表的编号',
+  `sub_join_column_id` bigint NULL DEFAULT NULL COMMENT '【自己】子表关联主表的字段编号',
+  `sub_join_many` bit(1) NULL DEFAULT NULL COMMENT '主表与子表是否一对多',
+  `tree_parent_column_id` bigint NULL DEFAULT NULL COMMENT '树表的父字段编号',
+  `tree_name_column_id` bigint NULL DEFAULT NULL COMMENT '树表的名字字段编号',
   `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',

+ 97 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm

@@ -35,21 +35,113 @@ export function get${simpleClassName}(id) {
   })
 }
 
+#if ( $table.templateType != 2 )
 // 获得${table.classComment}分页
-export function get${simpleClassName}Page(query) {
+export function get${simpleClassName}Page(params) {
   return request({
     url: '${baseURL}/page',
     method: 'get',
-    params: query
+    params
   })
 }
-
+#else
+// 获得${table.classComment}列表
+export function get${simpleClassName}List(params) {
+  return request({
+    url: '${baseURL}/list',
+    method: 'get',
+    params
+  })
+}
+#end
 // 导出${table.classComment} Excel
-export function export${simpleClassName}Excel(query) {
+export function export${simpleClassName}Excel(params) {
   return request({
     url: '${baseURL}/export-excel',
     method: 'get',
-    params: query,
+    params,
     responseType: 'blob'
   })
 }
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+  #set ($index = $foreach.count - 1)
+  #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+  #set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+  #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+  #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+  #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+  #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+  #set ($subClassNameVar = $subClassNameVars.get($index))
+
+// ==================== 子表($subTable.classComment) ====================
+  ## 情况一:MASTER_ERP 时,需要分查询页子表
+  #if ( $table.templateType == 11 )
+
+  // 获得${subTable.classComment}分页
+  export function get${simpleClassName}Page(params) {
+    return request({
+      url: '${baseURL}/${subSimpleClassName_strikeCase}/page',
+      method: 'get',
+      params
+    })
+  }
+    ## 情况二:非 MASTER_ERP 时,需要列表查询子表
+  #else
+    #if ( $subTable.subJoinMany )
+
+    // 获得${subTable.classComment}列表
+    export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) {
+      return request({
+        url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField},
+        method: 'get'
+      })
+    }
+    #else
+
+    // 获得${subTable.classComment}
+    export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}) {
+      return request({
+        url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField},
+        method: 'get'
+      })
+    }
+    #end
+  #end
+  ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+  #if ( $table.templateType == 11 )
+  // 新增${subTable.classComment}
+  export function create${subSimpleClassName}(data) {
+    return request({
+      url: `${baseURL}/${subSimpleClassName_strikeCase}/create`,
+      method: 'post',
+      data
+    })
+  }
+
+  // 修改${subTable.classComment}
+  export function update${subSimpleClassName}(data) {
+    return request({
+      url: `${baseURL}/${subSimpleClassName_strikeCase}/update`,
+      method: 'post',
+      data
+    })
+  }
+
+  // 删除${subTable.classComment}
+  export function delete${subSimpleClassName}(id) {
+    return request({
+      url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id,
+      method: 'delete'
+    })
+  }
+
+  // 获得${subTable.classComment}
+  export function get${subSimpleClassName}(id) {
+    return request({
+      url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id,
+      method: 'get'
+    })
+  }
+  #end
+#end

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

@@ -0,0 +1,204 @@
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+<template>
+  <Dialog :title="dialogTitle" :visible.sync="dialogVisible">
+    <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)
+        #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" && !$column.primaryKey)## 忽略主键,不用在表单里 TODO 芋艿:这里要忽略下 join 字段;
+      <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
+    #end
+#end
+    </el-form>
+    <template v-slot:footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </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}'
+
+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 $subColumns)
+    #if ($column.createOperation || $column.updateOperation)
+      #if ($column.htmlType == "checkbox")
+  $column.javaField: [],
+      #else
+  $column.javaField: undefined,
+      #end
+    #end
+#end
+})
+const formRules = reactive({
+#foreach ($column in $subColumns)
+    #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+        #set($comment=$column.columnComment)
+  $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
+    #end
+#end
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number, ${subJoinColumn.javaField}: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  formData.value.${subJoinColumn.javaField} = ${subJoinColumn.javaField}
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ${simpleClassName}Api.get${subSimpleClassName}(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value
+    if (formType.value === 'create') {
+      await ${simpleClassName}Api.create${subSimpleClassName}(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ${simpleClassName}Api.update${subSimpleClassName}(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+#foreach ($column in $subColumns)
+  #if ($column.createOperation || $column.updateOperation)
+      #if ($column.htmlType == "checkbox")
+    $column.javaField: [],
+      #else
+    $column.javaField: undefined,
+      #end
+  #end
+#end
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 2 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm

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

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

@@ -0,0 +1,362 @@
+#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>
+#if ( $subTable.subJoinMany )## 情况一:一对多,table + form
+  <el-form
+    ref="formRef"
+    :model="formData"
+    :rules="formRules"
+    v-loading="formLoading"
+    label-width="0px"
+    :inline-message="true"
+  >
+    <el-table :data="formData" class="-mt-10px">
+      <el-table-column label="序号" type="index" width="100" />
+#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)
+        #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.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+        #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+      <el-table-column label="${comment}" min-width="150">
+        <template #default="{ 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>
+        </template>
+      </el-table-column>
+        #elseif($column.htmlType == "imageUpload")## 图片上传
+      <el-table-column label="${comment}" min-width="200">
+        <template #default="{ row, $index }">
+          <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
+            <UploadImg v-model="row.${javaField}" />
+          </el-form-item>
+        </template>
+      </el-table-column>
+        #elseif($column.htmlType == "fileUpload")## 文件上传
+      <el-table-column label="${comment}" min-width="200">
+        <template #default="{ row, $index }">
+          <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
+            <UploadFile v-model="row.${javaField}" />
+          </el-form-item>
+        </template>
+      </el-table-column>
+        #elseif($column.htmlType == "editor")## 文本编辑器
+      <el-table-column label="${comment}" min-width="400">
+        <template #default="{ row, $index }">
+          <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
+            <Editor v-model="row.${javaField}" height="150px" />
+          </el-form-item>
+        </template>
+      </el-table-column>
+        #elseif($column.htmlType == "select")## 下拉框
+      <el-table-column label="${comment}" min-width="150">
+        <template #default="{ row, $index }">
+          <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
+            <el-select v-model="row.${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>
+        </template>
+      </el-table-column>
+        #elseif($column.htmlType == "checkbox")## 多选框
+      <el-table-column label="${comment}" min-width="150">
+        <template #default="{ row, $index }">
+          <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
+            <el-checkbox-group v-model="row.${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>
+        </template>
+      </el-table-column>
+        #elseif($column.htmlType == "radio")## 单选框
+      <el-table-column label="${comment}" min-width="150">
+        <template #default="{ row, $index }">
+          <el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
+            <el-radio-group v-model="row.${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>
+        </template>
+      </el-table-column>
+        #elseif($column.htmlType == "datetime")## 时间框
+      <el-table-column label="${comment}" min-width="150">
+        <template #default="{ 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-form-item>
+        </template>
+      </el-table-column>
+        #elseif($column.htmlType == "textarea")## 文本框
+      <el-table-column label="${comment}" min-width="200">
+        <template #default="{ 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>
+        </template>
+      </el-table-column>
+        #end
+    #end
+#end
+      <el-table-column align="center" fixed="right" label="操作" width="60">
+        <template #default="{ $index }">
+          <el-button @click="handleDelete($index)" link>—</el-button>
+        </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>
+#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)
+      #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.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")## 图片上传
+    <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
+  #end
+#end
+  </el-form>
+#end
+</template>
+<script setup lang="ts">
+import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
+import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
+
+const props = defineProps<{
+  ${subJoinColumn.javaField}: undefined // ${subJoinColumn.columnComment}(主表的关联字段)
+}>()
+const formLoading = ref(false) // 表单的加载中
+const formData = ref([])
+const formRules = reactive({
+#foreach ($column in $subColumns)
+    #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+        #set($comment=$column.columnComment)
+  $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
+    #end
+#end
+})
+const formRef = ref() // 表单 Ref
+
+/** 监听主表的关联字段的变化,加载对应的子表数据 */
+watch(
+  () => props.${subJoinColumn.javaField},
+  async (val) => {
+    // 1. 重置表单
+#if ( $subTable.subJoinMany )
+    formData.value = []
+#else
+    formData.value = {
+    #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 {
+      formLoading.value = true
+#if ( $subTable.subJoinMany )
+      formData.value = await ${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(val)
+#else
+      const data = await ${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(val)
+      if (!data) {
+        return
+      }
+      formData.value = data
+#end
+    } finally {
+      formLoading.value = false
+    }
+  },
+  { immediate: true }
+)
+#if ( $subTable.subJoinMany )
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  const row = {
+#foreach ($column in $subColumns)
+    #if ($column.createOperation || $column.updateOperation)
+      #if ($column.htmlType == "checkbox")
+    $column.javaField: [],
+      #else
+    $column.javaField: undefined,
+      #end
+  #end
+#end
+  }
+  row.${subJoinColumn.javaField} = props.${subJoinColumn.javaField}
+  formData.value.push(row)
+}
+
+/** 删除按钮操作 */
+const handleDelete = (index) => {
+  formData.value.splice(index, 1)
+}
+#end
+
+/** 表单校验 */
+const validate = () => {
+  return formRef.value.validate()
+}
+
+/** 表单值 */
+const getData = () => {
+  return formData.value
+}
+
+defineExpose({ validate, getData })
+</script>

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

@@ -0,0 +1,181 @@
+#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>
+#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>
+#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
+      #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>
+#if ($table.templateType == 11)
+    <!-- 表单弹窗:添加/修改 -->
+    <${subSimpleClassName}Form ref="formRef" @success="getList" />
+#end
+</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)
+  #end
+#end
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+#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 {}
+}
+#end
+#if ($table.templateType != 11)
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+#end
+</script>

+ 4 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm

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

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

@@ -0,0 +1,333 @@
+<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">
+          #foreach($column in $columns)
+              #if ($column.createOperation || $column.updateOperation)
+                  #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 ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
+                    <el-form-item label="${comment}" prop="${javaField}">
+                      <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 == "imageUpload")## 图片上传
+                      #set ($hasImageUploadColumn = true)
+                    <el-form-item label="${comment}">
+                      <imageUpload v-model="form.${javaField}"/>
+                    </el-form-item>
+                  #elseif($column.htmlType == "fileUpload")## 文件上传
+                      #set ($hasFileUploadColumn = true)
+                    <el-form-item label="${comment}">
+                      <fileUpload v-model="form.${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>
+                  #elseif($column.htmlType == "select")## 下拉框
+                    <el-form-item label="${comment}" prop="${javaField}">
+                      <el-select v-model="form.${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="form.${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="form.${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="form.${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-form-item>
+                  #end
+              #end
+          #end
+      </el-form>
+        ## 特殊:主子表专属逻辑
+        #if ( $subTables && $subTables.size() > 0 )
+          <!-- 子表的表单 -->
+          <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
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
+        <el-button @click="dialogVisible = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
+  #if ($hasImageUploadColumn)
+  import ImageUpload from '@/components/ImageUpload';
+  #end
+  #if ($hasFileUploadColumn)
+  import FileUpload from '@/components/FileUpload';
+  #end
+  #if ($hasEditorColumn)
+  import Editor from '@/components/Editor';
+  #end
+  ## 特殊:树表专属逻辑
+  #if ( $table.templateType == 2 )
+  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
+  #end
+  export default {
+    name: "${simpleClassName}",
+    components: {
+        #if ($hasImageUploadColumn)
+          ImageUpload,
+        #end
+        #if ($hasFileUploadColumn)
+          FileUpload,
+        #end
+        #if ($hasEditorColumn)
+          Editor,
+        #end
+        ## 特殊:树表专属逻辑
+        #if ( $table.templateType == 2 )
+          Treeselect,
+        #end
+        ## 特殊:主子表专属逻辑
+        #if ( $subTables && $subTables.size() > 0 )
+        #foreach ($subSimpleClassName in $subSimpleClassNames)
+          ${subSimpleClassName}Form,
+        #end
+        #end
+    },
+    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
+        },
+        // 表单校验
+        formRules: {
+            #foreach ($column in $columns)
+                #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+                    #set($comment=$column.columnComment)
+                        $column.javaField: [{ required: true, message: "${comment}不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }],
+                #end
+            #end
+        },
+          ## 特殊:树表专属逻辑
+          #if ( $table.templateType == 2 )
+             ${classNameVar}Tree: [], // 树形结构
+          #end
+          ## 特殊:主子表专属逻辑
+          #if ( $subTables && $subTables.size() > 0 )
+            /** 子表的表单 */
+             subTabsName: '$subClassNameVars.get(0)'
+          #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;
+        this.reset();
+        const that = this;
+        // 修改时,设置数据
+        if (id) {
+          this.formLoading = true;
+          try {
+            ${simpleClassName}Api.get${simpleClassName}(id).then(res=>{
+              that.formData = res.data;
+              that.title = "修改${table.classComment}";
+            })
+          } finally {
+            this.formLoading = false;
+          }
+        }
+        this.title = "新增${table.classComment}";
+        ## 特殊:树表专属逻辑
+        #if ( $table.templateType == 2 )
+            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(() => {
+            // 全部校验通过-拼接子表的数据
+            // 拼接子表的数据
+              #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()
+              #end
+          }).catch((err)=>{
+            that.subTabsName = err
+          })
+#end
+
+        this.getRef("formRef").validate(valid => {
+          if (!valid) {
+            return;
+          }
+          // 修改的提交
+          if (data.${primaryColumn.javaField}) {
+            ${simpleClassName}Api.update${simpleClassName}(data).then(response => {
+              this.#[[$modal]]#.msgSuccess("修改成功");
+              this.dialogVisible = false;
+              this.#[[$]]#emit('success');
+            });
+            return;
+          }
+          // 添加的提交
+          ${simpleClassName}Api.create${simpleClassName}(data).then(response => {
+            this.#[[$modal]]#.msgSuccess("新增成功");
+            this.dialogVisible = false;
+            this.#[[$]]#emit('success');
+          });
+        });
+        }finally {
+          this.formLoading = false
+        }
+      },
+      getRef(refName){
+        this.#[[$]]#refs[refName]
+      }
+    }
+  };
+</script>

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

@@ -47,18 +47,67 @@
     <!-- 操作工具栏 -->
     <el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
-        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
                    v-hasPermi="['${permissionPrefix}:create']">新增</el-button>
       </el-col>
       <el-col :span="1.5">
         <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
                    v-hasPermi="['${permissionPrefix}:export']">导出</el-button>
       </el-col>
+        #if ( $table.templateType == 2 )
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="el-icon-sort" size="mini" @click="toggleExpandAll">
+              展开/折叠
+            </el-button>
+          </el-col>
+        #end
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <!-- 列表 -->
-    <el-table v-loading="loading" :data="list">
+      ## 特殊:主子表专属逻辑 TODO puhui999: 普通模式
+      #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+      <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          :highlight-current-row="true"
+          :show-overflow-tooltip="true"
+          @current-change="handleCurrentChange"
+      >
+          ## 特殊:树表专属逻辑
+      #elseif ( $table.templateType == 2 )
+      <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          :show-overflow-tooltip="true"
+          v-if="refreshTable"
+          row-key="id"
+          :default-expand-all="isExpandAll"
+          :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+      >
+      #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 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)
@@ -84,7 +133,7 @@
 #end
       <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="handleUpdate(scope.row)"
+          <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>
@@ -96,90 +145,28 @@
                 @pagination="getList"/>
 
     <!-- 对话框(添加 / 修改) -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" v-dialogDrag append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-#foreach($column in $columns)
-#if ($column.createOperation || $column.updateOperation)
-    #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.htmlType == "input")
-  #if (!$column.primaryKey)## 忽略主键,不用在表单里
-        <el-form-item label="${comment}" prop="${javaField}">
-          <el-input v-model="form.${javaField}" placeholder="请输入${comment}" />
-        </el-form-item>
+    <${simpleClassName}Form ref="formRef" @success="getList" />
+  ## 特殊:主子表专属逻辑  TODO puhui999: ERP 模式
+  #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+    <!-- 子表的列表 -->
+      <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>
   #end
-#elseif($column.htmlType == "imageUpload")## 图片上传
-        #set ($hasImageUploadColumn = true)
-        <el-form-item label="${comment}">
-          <imageUpload v-model="form.${javaField}"/>
-        </el-form-item>
-#elseif($column.htmlType == "fileUpload")## 文件上传
-        #set ($hasFileUploadColumn = true)
-        <el-form-item label="${comment}">
-          <fileUpload v-model="form.${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>
-#elseif($column.htmlType == "select")## 下拉框
-        <el-form-item label="${comment}" prop="${javaField}">
-          <el-select v-model="form.${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="form.${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="form.${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="form.${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-form-item>
-#end
-#end
-#end
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
-      </div>
-    </el-dialog>
   </div>
 </template>
 
 <script>
-import { create${simpleClassName}, update${simpleClassName}, delete${simpleClassName}, get${simpleClassName}, get${simpleClassName}Page, export${simpleClassName}Excel } from "@/api/${table.moduleName}/${classNameVar}";
+import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}';
+import ${simpleClassName}Form from './${simpleClassName}Form.vue';
 #if ($hasImageUploadColumn)
 import ImageUpload from '@/components/ImageUpload';
 #end
@@ -189,10 +176,22 @@ import FileUpload from '@/components/FileUpload';
 #if ($hasEditorColumn)
 import Editor from '@/components/Editor';
 #end
-
+## 特殊:主子表专属逻辑
+#if ( $subTables && $subTables.size() > 0 )
+    #foreach ($subSimpleClassName in $subSimpleClassNames)
+    import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue'
+    #end
+#end
 export default {
   name: "${simpleClassName}",
   components: {
+          ${simpleClassName}Form,
+## 特殊:主子表专属逻辑
+#if ( $subTables && $subTables.size() > 0 )
+      #foreach ($subSimpleClassName in $subSimpleClassNames)
+          ${subSimpleClassName}List,
+      #end
+#end
 #if ($hasImageUploadColumn)
     ImageUpload,
 #end
@@ -212,17 +211,25 @@ export default {
       // 显示搜索条件
       showSearch: true,
       // 总条数
-      total: 0,
+      ## 特殊:树表专属逻辑(树不需要分页接口)
+      #if ( $table.templateType != 2 )
+        total: 0,
+      #end
       // ${table.classComment}列表
       list: [],
-      // 弹出层标题
-      title: "",
-      // 是否显示弹出层
-      open: false,
+      // 是否展开,默认全部展开
+      isExpandAll: true,
+      // 重新渲染表格状态
+      refreshTable: true,
+      // 选中行
+      currentRow: {},
       // 查询参数
       queryParams: {
-        pageNo: 1,
-        pageSize: 10,
+        ## 特殊:树表专属逻辑(树不需要分页接口)
+        #if ( $table.templateType != 2 )
+            pageNo: 1,
+            pageSize: 10,
+        #end
         #foreach ($column in $columns)
         #if ($column.listOperation)
         #if ($column.listOperationCondition != 'BETWEEN')
@@ -233,17 +240,6 @@ export default {
         #end
         #end
         #end
-      },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-      #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
       }
     };
   },
@@ -253,33 +249,22 @@ export default {
   methods: {
     /** 查询列表 */
     getList() {
+      try {
       this.loading = true;
-      // 执行查询
-      get${simpleClassName}Page(this.queryParams).then(response => {
+      ## 特殊:树表专属逻辑(树不需要分页接口)
+      #if ( $table.templateType == 2 )
+        ${simpleClassName}Api.get${simpleClassName}List(queryParams).then(response => {
+          this.list = this.handleTree(response.data, 'id', '${treeParentColumn.javaField}');
+        })
+      #else
+      ${simpleClassName}Api.get${simpleClassName}Page(this.queryParams).then(response => {
         this.list = response.data.list;
         this.total = response.data.total;
-        this.loading = false;
       });
-    },
-    /** 取消按钮 */
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    /** 表单重置 */
-    reset() {
-      this.form = {
-        #foreach ($column in $columns)
-        #if ($column.createOperation || $column.updateOperation)
-        #if ($column.htmlType == "checkbox")
-        $column.javaField: [],
-        #else
-        $column.javaField: undefined,
-        #end
-        #end
-        #end
-      };
-      this.resetForm("form");
+      #end
+      } finally {
+        this.loading = false;
+      }
     },
     /** 搜索按钮操作 */
     handleQuery() {
@@ -291,79 +276,56 @@ export default {
       this.resetForm("queryForm");
       this.handleQuery();
     },
-    /** 新增按钮操作 */
-    handleAdd() {
-      this.reset();
-      this.open = true;
-      this.title = "添加${table.classComment}";
-    },
-    /** 修改按钮操作 */
-    handleUpdate(row) {
-      this.reset();
-      const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
-      get${simpleClassName}(${primaryColumn.javaField}).then(response => {
-        this.form = response.data;
-        #foreach ($column in $columns)
-        #if($column.htmlType == "checkbox")## checkbox 特殊处理
-        this.form.$column.javaField = this.form.${column.javaField}.split(",");
-        #end
-        #end
-        this.open = true;
-        this.title = "修改${table.classComment}";
-      });
-    },
-    /** 提交按钮 */
-    submitForm() {
-      this.#[[$]]#refs["form"].validate(valid => {
-        if (!valid) {
-          return;
-        }
-        #foreach ($column in $columns)
-        #if($column.htmlType == "checkbox")
-        this.form.$column.javaField = this.form.${column.javaField}.join(",");
-        #end
-        #end
-        // 修改的提交
-        if (this.form.${primaryColumn.javaField} != null) {
-          update${simpleClassName}(this.form).then(response => {
-            this.#[[$modal]]#.msgSuccess("修改成功");
-            this.open = false;
-            this.getList();
-          });
-          return;
-        }
-        // 添加的提交
-        create${simpleClassName}(this.form).then(response => {
-          this.#[[$modal]]#.msgSuccess("新增成功");
-          this.open = false;
-          this.getList();
-        });
-      });
+    /** 添加/修改操作 */
+    openForm(id) {
+      this.#[[$]]#refs["formRef"].open(id)
     },
     /** 删除按钮操作 */
     handleDelete(row) {
+      const that = this;
+      try {
       const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
-      this.#[[$modal]]#.confirm('是否确认删除${table.classComment}编号为"' + ${primaryColumn.javaField} + '"的数据项?').then(function() {
-          return delete${simpleClassName}(${primaryColumn.javaField});
+      this.#[[$modal]]#.confirm('是否确认删除${table.classComment}编号为"' + ${primaryColumn.javaField} + '"的数据项?').then(()=>{
+          return ${simpleClassName}Api.delete${simpleClassName}(${primaryColumn.javaField});
         }).then(() => {
-          this.getList();
-          this.#[[$modal]]#.msgSuccess("删除成功");
+        that.getList();
+        that.#[[$modal]]#.msgSuccess("删除成功");
         }).catch(() => {});
+      } catch {}
     },
     /** 导出按钮操作 */
     handleExport() {
-      // 处理查询参数
-      let params = {...this.queryParams};
-      params.pageNo = undefined;
-      params.pageSize = undefined;
-      this.#[[$modal]]#.confirm('是否确认导出所有${table.classComment}数据项?').then(() => {
-          this.exportLoading = true;
-          return export${simpleClassName}Excel(params);
-        }).then(response => {
-          this.#[[$]]#download.excel(response, '${table.classComment}.xls');
-          this.exportLoading = false;
-        }).catch(() => {});
-    }
+      const that = this;
+      try {
+          this.#[[$modal]]#.confirm('是否确认导出所有${table.classComment}数据项?').then(() => {
+              that.exportLoading = true;
+              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 )
+        /** 选中行操作 */
+        handleCurrentChange(row) {
+         this.currentRow = row
+        },
+      #end
+      ## 特殊:树表专属逻辑
+      #if ( $table.templateType == 2 )
+        /** 展开/折叠操作 */
+        toggleExpandAll() {
+          this.refreshTable = false
+          this.isExpandAll = !this.isExpandAll
+          this.$nextTick(function () {
+            this.refreshTable = true
+          })
+        }
+      #end
   }
 };
 </script>

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

@@ -146,9 +146,11 @@ import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.busine
 import { defaultProps, handleTree } from '@/utils/tree'
 #end
 ## 特殊:主子表专属逻辑
+#if ( $subTables && $subTables.size() > 0 )
 #foreach ($subSimpleClassName in $subSimpleClassNames)
 import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'
 #end
+#end
 
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗

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

@@ -113,7 +113,7 @@
 
   <!-- 列表 -->
   <ContentWrap>
-## 特殊:主子表专属逻辑
+## 特殊:主子表专属逻辑  TODO puhui999: 普通模式
 #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
     <el-table
       v-loading="loading"
@@ -137,7 +137,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">
@@ -213,7 +213,7 @@
 
   <!-- 表单弹窗:添加/修改 -->
   <${simpleClassName}Form ref="formRef" @success="getList" />
-## 特殊:主子表专属逻辑
+## 特殊:主子表专属逻辑 TODO puhui999: ERP 模式
 #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
   <!-- 子表的列表 -->
   <ContentWrap>
@@ -243,7 +243,7 @@ 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 )
+#if ( $subTables && $subTables.size() > 0 )
 #foreach ($subSimpleClassName in $subSimpleClassNames)
 import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue'
 #end
@@ -343,9 +343,9 @@ const handleExport = async () => {
     exportLoading.value = false
   }
 }
-## 特殊:主子表专属逻辑
-#if ( $table.templateType == 11 )
 
+## 特殊:主子表专属逻辑
+#if ( $subTables && $subTables.size() > 0 )
 /** 选中行操作 */
 const currentRow = ref({}) // 选中行
 const handleCurrentChange = (row) => {

+ 4 - 4
yudao-server/src/main/resources/application-local.yaml

@@ -48,7 +48,7 @@ spring:
       primary: master
       datasource:
         master:
-          name: ruoyi-vue-pro
+          name: ruoyi-vue-pro4
           url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
           #          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
           #          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例
@@ -62,7 +62,7 @@ spring:
 #          username: SYSDBA # DM 连接的示例
 #          password: SYSDBA # DM 连接的示例
         slave: # 模拟从库,可根据自己需要修改
-          name: ruoyi-vue-pro
+          name: ruoyi-vue-pro4
           lazy: true # 开启懒加载,保证启动速度
           url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
           #          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
@@ -78,8 +78,8 @@ spring:
   redis:
     host: 127.0.0.1 # 地址
     port: 6379 # 端口
-    database: 0 # 数据库索引
-#    password: dev # 密码,建议生产环境开启
+    database: 4 # 数据库索引
+    password: 123456 # 密码,建议生产环境开启
 
 --- #################### 定时任务相关配置 ####################