Procházet zdrojové kódy

【功能新增】代码生成: vue3_vben5_antd schema 主子表

puhui999 před 6 měsíci
rodič
revize
3923189887

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

@@ -154,15 +154,16 @@ public class CodegenEngine {
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
             .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("api/api.ts"),
                     vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
-            // 主子表模板配置 - Vue3 vben5 schema 模版
-            //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/master_slave_data.ts"),
-            //        vue3FilePath("views/${table.moduleName}/${table.businessName}/data.ts"))
-            //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/master_slave_index.vue"),
-            //        vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
-            //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/modules/master_slave_form.vue"),
-            //        vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
-            //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/modules/sub_table.vue"),
-            //        vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/sub_table.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName}List.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName}List.vue"))
             .build();
 
     @Resource

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

@@ -19,8 +19,40 @@ export namespace ${simpleClassName}Api {
 #end
 #if ( $table.templateType == 2 )
   children?: ${simpleClassName}[];
+#end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #foreach ($subTable in $subTables)
+    #set ($index = $foreach.count - 1)
+    #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+    #if ( $subTable.subJoinMany )
+        ${subSimpleClassName.toLowerCase()}s?: ${subSimpleClassName}[]
+    #else
+        ${subSimpleClassName.toLowerCase()}?: ${subSimpleClassName}
+    #end
+  #end
 #end
   }
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+  #set ($index = $foreach.count - 1)
+  #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+  #set ($subColumns = $subColumnsList.get($index))##当前字段数组
+  /** ${subTable.classComment}信息 */
+  export interface ${subSimpleClassName} {
+    #foreach ($column in $subColumns)
+      #if ($column.createOperation || $column.updateOperation)
+        #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+        #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: Date; // ${column.columnComment}
+        #else
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
+        #end
+      #end
+    #end
+  }
+#end
 }
 
 #if ( $table.templateType != 2 )

+ 164 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/data.ts.vm

@@ -276,3 +276,167 @@ export function useGridColumns(
     },
   ];
 }
+
+// TODO @puhui999: 标准模式和内嵌模式时,主子关系一对一则生成表单schema,一对多则生成列表schema(内嵌模式时表单schema也要生成)。erp 模式时都生成
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+    #set ($index = $foreach.count - 1)
+    #set ($subColumns = $subColumnsList.get($index))##当前字段数组
+    #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+    #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+// ==================== 子表($subTable.classComment) ====================
+/** 新增/修改的表单 */
+export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
+    return [
+        {
+            fieldName: 'id',
+            component: 'Input',
+            dependencies: {
+                triggerFields: [''],
+                show: () => false,
+            },
+        },
+        #foreach($column in $subColumns)
+            #if ($column.createOperation || $column.updateOperation)
+                #if (!$column.primaryKey && ($table.templateType != 2 || ($table.templateType == 2 && $column.id != $treeParentColumn.id)))## 树表中已经添加了父ID字段,这里排除
+                    #set ($dictType = $column.dictType)
+                    #set ($javaType = $column.javaType)
+                    #set ($javaField = $column.javaField)
+                    #set ($comment = $column.columnComment)
+                    #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                        #set ($dictMethod = "number")
+                    #elseif ($javaType == "String")
+                        #set ($dictMethod = "string")
+                    #elseif ($javaType == "Boolean")
+                        #set ($dictMethod = "boolean")
+                    #end
+                    #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+                    #else
+                        {
+                            fieldName: '${javaField}',
+                            label: '${comment}',
+                            #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+                                rules: 'required',
+                            #end
+                            #if ($column.htmlType == "input")
+                                component: 'Input',
+                                componentProps: {
+                                    placeholder: '请输入${comment}',
+                                },
+                            #elseif($column.htmlType == "imageUpload")## 图片上传
+                                component: 'FileUpload',
+                                componentProps: {
+                                    fileType: 'image',
+                                    maxCount: 1,
+                                },
+                            #elseif($column.htmlType == "fileUpload")## 文件上传
+                                component: 'FileUpload',
+                                componentProps: {
+                                    fileType: 'file',
+                                    maxCount: 1,
+                                },
+                            #elseif($column.htmlType == "editor")## 文本编辑器
+                                component: 'Editor',
+                            #elseif($column.htmlType == "select")## 下拉框
+                                component: 'Select',
+                                componentProps: {
+                                    #if ("" != $dictType)## 有数据字典
+                                        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                    #else##没数据字典
+                                        options: [],
+                                    #end
+                                    placeholder: '请选择${comment}',
+                                    class: 'w-full',
+                                },
+                            #elseif($column.htmlType == "checkbox")## 多选框
+                                component: 'Checkbox',
+                                componentProps: {
+                                    #if ("" != $dictType)## 有数据字典
+                                        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                    #else##没数据字典
+                                        options: [],
+                                    #end
+                                },
+                            #elseif($column.htmlType == "radio")## 单选框
+                                component: 'RadioGroup',
+                                componentProps: {
+                                    #if ("" != $dictType)## 有数据字典
+                                        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                    #else##没数据字典
+                                        options: [],
+                                    #end
+                                    buttonStyle: 'solid',
+                                    optionType: 'button',
+                                },
+                            #elseif($column.htmlType == "datetime")## 时间框
+                                component: 'DatePicker',
+                                componentProps: {
+                                    showTime: true,
+                                    format: 'YYYY-MM-DD HH:mm:ss',
+                                    valueFormat: 'x',
+                                },
+                            #elseif($column.htmlType == "textarea")## 文本域
+                                component: 'Textarea',
+                                componentProps: {
+                                    placeholder: '请输入${comment}',
+                                },
+                            #elseif($column.htmlType == "inputNumber")## 数字输入框
+                                component: 'InputNumber',
+                                componentProps: {
+                                    min: 0,
+                                    class: 'w-full',
+                                    controlsPosition: 'right',
+                                    placeholder: '请输入${comment}',
+                                },
+                            #end
+                        },
+                    #end
+                #end
+            #end
+        #end
+    ];
+}
+/** 列表的字段 */
+export function use${subSimpleClassName}GridColumns(
+    onActionClick?: OnActionClickFn<${simpleClassName}Api.${subSimpleClassName}>,
+): VxeTableGridOptions<${subSimpleClassName}Api.${subSimpleClassName}>['columns'] {
+    return [
+        #foreach($column in $subColumns)
+            #if (!$column.primaryKey && $column.listOperationResult && $column.id != $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+                #set ($dictType = $column.dictType)
+                #set ($javaField = $column.javaField)
+                #set ($comment = $column.columnComment)
+                {
+                    field: '${javaField}',
+                    title: '${comment}',
+                    minWidth: 120,
+                    slots: { default: '${javaField}' },
+                },
+            #end
+        #end
+        {
+            field: 'operation',
+            title: '操作',
+            minWidth: 60,
+            align: 'center',
+            fixed: 'right',
+            headerAlign: 'center',
+            showOverflow: false,
+            cellRender: {
+                attrs: {
+                    nameField: '${columns[0].javaField}',
+                    nameTitle: '${table.classComment}',
+                    onClick: onActionClick,
+                },
+                name: 'CellOperation',
+                options: [
+                    {
+                        code: 'delete',
+                        show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:delete']),
+                    },
+                ],
+            },
+        },
+    ];
+}
+#end

+ 61 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/form.vue.vm

@@ -2,7 +2,13 @@
 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 
 import { useVbenModal } from '@vben/common-ui';
-import { message } from 'ant-design-vue';
+import { message, Tabs } from 'ant-design-vue';
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #foreach ($subSimpleClassName in $subSimpleClassNames)
+  import ${subSimpleClassName}Form from './${subSimpleClassName}Form.vue'
+  #end
+#end
 
 import { computed, ref } from 'vue';
 import { $t } from '#/locales';
@@ -32,6 +38,18 @@ const getTitle = computed(() => {
 });
 #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 [Form, formApi] = useVbenForm({
   layout: 'horizontal',
   schema: useFormSchema(),
@@ -44,9 +62,36 @@ const [Modal, modalApi] = useVbenModal({
     if (!valid) {
       return;
     }
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #if ( $subTables && $subTables.size() > 0 )
+        // 校验子表单
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          try {
+            await ${subClassNameVar}FormRef.value.validate()
+          } catch (e) {
+            subTabsName.value = '${subClassNameVar}'
+            return
+          }
+        #end
+      #end
+    #end
     modalApi.lock();
     // 提交表单
     const data = (await formApi.getValues()) as ${simpleClassName}Api.${simpleClassName};
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #if ( $subTables && $subTables.size() > 0 )
+        // 拼接子表的数据
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = ${subClassNameVar}FormRef.value.getData()
+        #end
+      #end
+    #end
     try {
       await (formData.value?.id ? update${simpleClassName}(data) : create${simpleClassName}(data));
       // 关闭并提示
@@ -90,5 +135,20 @@ const [Modal, modalApi] = useVbenModal({
 <template>
   <Modal :title="getTitle">
     <Form class="mx-4" />
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      <!-- 子表的表单 -->
+      <Tabs v-model:active-key="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))
+          <Tabs.TabPane key="$subClassNameVar" tab="${subTable.classComment}">
+            <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData.id" />
+          </Tabs.TabPane>
+        #end
+      </Tabs>
+    #end
   </Modal>
 </template>

+ 204 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/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" v-model="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.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.label"
+            :value="dict.value"
+          />
+                #else##没数据字典
+          <el-checkbox label="请选择字典生成" />
+                #end
+        </el-checkbox-group>
+      </el-form-item>
+        #elseif($column.htmlType == "radio")## 单选框
+      <el-form-item label="${comment}" prop="${javaField}">
+        <el-radio-group v-model="formData.${javaField}">
+                #if ("" != $dictType)## 有数据字典
+          <el-radio
+            v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
+            :key="dict.value"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio>
+                #else##没数据字典
+          <el-radio value="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 #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 { ${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/vue3_vben5_antd/schema/views/modules/form_sub_inner.vue.vm

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

+ 360 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm

@@ -0,0 +1,360 @@
+#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.label"
+                  :value="dict.value"
+                />
+              #else##没数据字典
+                <el-checkbox label="请选择字典生成" />
+              #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 value="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.label"
+          :value="dict.value"
+        />
+              #else##没数据字典
+        <el-checkbox label="请选择字典生成" />
+              #end
+      </el-checkbox-group>
+    </el-form-item>
+      #elseif($column.htmlType == "radio")## 单选框
+    <el-form-item label="${comment}" prop="${javaField}">
+      <el-radio-group v-model="formData.${javaField}">
+              #if ("" != $dictType)## 有数据字典
+        <el-radio
+          v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
+          :key="dict.value"
+          :label="dict.value"
+          >
+          {{ dict.label }}
+        </el-radio>
+              #else##没数据字典
+        <el-radio value="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 { ${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>

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

@@ -0,0 +1,184 @@
+#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 { ${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}?: number // ${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 as unknown
+})
+
+/** 监听主表的关联字段的变化,加载对应的子表数据 */
+watch(
+  () => props.${subJoinColumn.javaField},
+  (val: number) => {
+    if (!val) {
+      return
+    }
+    queryParams.${subJoinColumn.javaField} = val
+    handleQuery()
+  },
+    { immediate: true, deep: true }
+)
+#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/vue3_vben5_antd/schema/views/modules/list_sub_inner.vue.vm

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