冷冷 1 year ago
parent
commit
cae59938a6
18 changed files with 1667 additions and 0 deletions
  1. 11 0
      Mapper
  2. 16 0
      Mapper.xml
  3. 24 0
      Service
  4. 74 0
      ServiceImpl
  5. 50 0
      api.ts
  6. 12 0
      i18n中文模板
  7. 12 0
      i18n英文模板
  8. 22 0
      menu.sql
  9. 108 0
      vform.json
  10. 218 0
      vform.vue
  11. 161 0
      主子Contoller
  12. 259 0
      主子表单
  13. 181 0
      主子表格
  14. 18 0
      子Mapper
  15. 50 0
      子实体
  16. 62 0
      实体
  17. 214 0
      表单
  18. 175 0
      表格

+ 11 - 0
Mapper

@@ -0,0 +1,11 @@
+package ${package}.${moduleName}.mapper;
+
+import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
+import ${package}.${moduleName}.entity.${ClassName}Entity;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ${ClassName}Mapper extends PigxBaseMapper<${ClassName}Entity> {
+
+
+}

+ 16 - 0
Mapper.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="${package}.${moduleName}.mapper.${ClassName}Mapper">
+
+  <resultMap id="${className}Map" type="${package}.${moduleName}.entity.${ClassName}Entity">
+#foreach ($field in $fieldList)
+	  #if($field.primaryPk)
+        <id property="$field.attrName" column="$field.fieldName"/>
+      #end
+      #if(!$field.primaryPk)
+        <result property="$field.attrName" column="$field.fieldName"/>
+      #end
+#end
+  </resultMap>
+</mapper>

+ 24 - 0
Service

@@ -0,0 +1,24 @@
+package ${package}.${moduleName}.service;
+
+#if($ChildClassName)
+import com.github.yulichang.extension.mapping.base.MPJDeepService;
+import ${package}.${moduleName}.entity.${ChildClassName}Entity;
+#else
+import com.baomidou.mybatisplus.extension.service.IService;
+#end
+import ${package}.${moduleName}.entity.${ClassName}Entity;
+
+#if($ChildClassName)
+public interface ${ClassName}Service extends MPJDeepService<${ClassName}Entity> {
+    Boolean saveDeep(${ClassName}Entity ${className});
+
+    Boolean updateDeep(${ClassName}Entity ${className});
+
+    Boolean removeDeep(Long[] ids);
+
+    Boolean removeChild(Long[] ids);
+#else
+public interface ${ClassName}Service extends IService<${ClassName}Entity> {
+#end
+
+}

+ 74 - 0
ServiceImpl

@@ -0,0 +1,74 @@
+package ${package}.${moduleName}.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import ${package}.${moduleName}.entity.${ClassName}Entity;
+import ${package}.${moduleName}.mapper.${ClassName}Mapper;
+import ${package}.${moduleName}.service.${ClassName}Service;
+import org.springframework.stereotype.Service;
+#if($ChildClassName)
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import ${package}.${moduleName}.entity.${ChildClassName}Entity;
+import ${package}.${moduleName}.mapper.${ChildClassName}Mapper;
+import org.springframework.transaction.annotation.Transactional;
+import lombok.RequiredArgsConstructor;
+import java.util.Objects;
+#end
+/**
+ * ${tableComment}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+@Service
+#if($ChildClassName)
+@RequiredArgsConstructor
+#end
+public class ${ClassName}ServiceImpl extends ServiceImpl<${ClassName}Mapper, ${ClassName}Entity> implements ${ClassName}Service {
+#if($ChildClassName)
+  private final ${ChildClassName}Mapper ${childClassName}Mapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean saveDeep(${ClassName}Entity ${className}) {
+        baseMapper.insert(${className});
+        for (${ChildClassName}Entity  ${childClassName} : ${className}.get${ChildClassName}List()) {
+            ${childClassName}.$str.setProperty($childField)(${className}.$str.getProperty($mainField)());
+            ${childClassName}Mapper.insert( ${childClassName});
+        }
+
+        return Boolean.TRUE;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateDeep(${ClassName}Entity ${className}) {
+        baseMapper.updateById(${className});
+        for (${ChildClassName}Entity  ${childClassName} : ${className}.get${ChildClassName}List()) {
+#set($getChildPkName=$str.getProperty(${pk.attrName}))
+            if (Objects.isNull(${childClassName}.$getChildPkName())) {
+                ${childClassName}.$str.setProperty($childField)(${className}.getId());
+                ${childClassName}Mapper.insert(${childClassName});
+            } else {
+                ${childClassName}Mapper.updateById(${childClassName});
+            }
+        }
+        return Boolean.TRUE;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean removeDeep(Long[] ids) {
+        baseMapper.deleteBatchIds(CollUtil.toList(ids));
+        ${childClassName}Mapper.delete(Wrappers.<${ChildClassName}Entity>lambdaQuery().in(${ChildClassName}Entity::$str.getProperty($childField), ids));
+        return Boolean.TRUE;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean removeChild(Long[] ids) {
+        ${childClassName}Mapper.deleteBatchIds(CollUtil.toList(ids));
+        return Boolean.TRUE;
+    }
+#end
+}

+ 50 - 0
api.ts

@@ -0,0 +1,50 @@
+import request from "/@/utils/request"
+
+export function fetchList(query?: Object) {
+  return request({
+    url: '/${moduleName}/${functionName}/page',
+    method: 'get',
+    params: query
+  })
+}
+
+export function addObj(obj?: Object) {
+  return request({
+    url: '/${moduleName}/${functionName}',
+    method: 'post',
+    data: obj
+  })
+}
+
+export function getObj(id?: string) {
+  return request({
+    url: '/${moduleName}/${functionName}/' + id,
+    method: 'get'
+  })
+}
+
+export function delObjs(ids?: Object) {
+  return request({
+    url: '/${moduleName}/${functionName}',
+    method: 'delete',
+    data: ids
+  })
+}
+
+export function putObj(obj?: Object) {
+  return request({
+    url: '/${moduleName}/${functionName}',
+    method: 'put',
+    data: obj
+  })
+}
+
+#if($ChildClassName)
+export function delChildObj(ids?: Object) {
+  return request({
+    url: '/${moduleName}/${functionName}/child',
+    method: 'delete',
+    data: ids
+  })
+}
+#end

+ 12 - 0
i18n中文模板

@@ -0,0 +1,12 @@
+export default {
+   ${functionName}: {
+        index: '#',
+        import${className}Tip: '导入${tableComment}',
+#foreach($field in $fieldList)
+        ${field.attrName}: '#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end',
+#end
+#foreach($field in $fieldList)
+        input$str.pascalCase(${field.attrName})Tip: '请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end',
+#end
+    }
+}

+ 12 - 0
i18n英文模板

@@ -0,0 +1,12 @@
+export default {
+   ${functionName}: {
+        index: '#',
+        import${className}Tip: 'import ${ClassName}',
+#foreach($field in $fieldList)
+        ${field.attrName}: '${field.attrName}',
+#end
+#foreach($field in $fieldList)
+        input$str.pascalCase(${field.attrName})Tip: 'input ${field.attrName}',
+#end
+    }
+}

+ 22 - 0
menu.sql

@@ -0,0 +1,22 @@
+-- 该脚本不要直接执行, 注意维护菜单的父节点ID 默认 父节点-1 , 默认租户 1
+#set($menuId=${dateTool.getSystemTime()})
+
+-- 菜单SQL
+insert into sys_menu ( menu_id,parent_id, path, permission, menu_type, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
+values (${menuId}, '-1', '/${moduleName}/${functionName}/index', '', '0', 'icon-bangzhushouji', '0', null , '8', null , '${tableComment}管理', 1);
+
+-- 菜单对应按钮SQL
+insert into sys_menu ( menu_id,parent_id, permission, menu_type, path, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
+values (${math.add($menuId,1)},${menuId}, '${moduleName}_${functionName}_view', '1', null, '1',  '0', null, '0', null, '${tableComment}查看', 1);
+
+insert into sys_menu ( menu_id,parent_id, permission, menu_type, path, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
+values (${math.add($menuId,2)},${menuId}, '${moduleName}_${functionName}_add', '1', null, '1',  '0', null, '1', null, '${tableComment}新增', 1);
+
+insert into sys_menu (menu_id, parent_id, permission, menu_type, path, icon,  del_flag, create_time, sort_order, update_time, name, tenant_id)
+values (${math.add($menuId,3)},${menuId}, '${moduleName}_${functionName}_edit', '1', null, '1',  '0', null, '2', null, '${tableComment}修改', 1);
+
+insert into sys_menu (menu_id, parent_id, permission, menu_type, path, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
+values (${math.add($menuId,4)},${menuId}, '${moduleName}_${functionName}_del', '1', null, '1',  '0', null, '3', null, '${tableComment}删除', 1);
+
+insert into sys_menu ( menu_id,parent_id, permission, menu_type, path, icon, del_flag, create_time, sort_order, update_time, name, tenant_id)
+values (${math.add($menuId,5)},${menuId}, '${moduleName}_${functionName}_export', '1', null, '1',  '0', null, '3', null, '导入导出', 1);

+ 108 - 0
vform.json

@@ -0,0 +1,108 @@
+#set($key=${dateTool.getSystemTime()})
+{
+  "widgetList": [
+    {
+      "key": $key,
+      "type": "grid",
+      "category": "container",
+      "icon": "grid",
+      "cols": [
+#foreach($field in $formList)
+#if($field.attrName != ${pk.attrName})
+        {
+          "type": "grid-col",
+          "category": "container",
+          "icon": "grid-col",
+          "internal": true,
+          "widgetList": [
+            {
+              "key": ${math.add($key,${foreach.index})},
+	#if($field.formType == 'text')
+              "type": "input",
+              "icon": "text-field",
+	#elseif($field.formType == 'number')
+              "type": "number",
+              "icon": "number-field",
+	#elseif($field.formType == 'textarea')
+              "type": "textarea",
+              "icon": "textarea-field",
+	#elseif($field.formType == 'select' && ${field.fieldDict})
+              "type": "select",
+              "icon": "select-field",
+	#elseif($field.formType == 'radio' && ${field.fieldDict})
+              "type": "radio",
+              "icon": "radio-field",
+	#elseif($field.formType == 'checkbox'  && ${field.fieldDict} )
+              "type": "checkbox",
+              "icon": "checkbox-field",
+	#elseif($field.formType == 'date')
+              "type": "date",
+              "icon": "date-field",
+	#elseif($field.formType == 'datetime')
+              "type": "time",
+              "icon": "time-field",
+	#elseif($field.formType == 'upload-file')
+              "type": "file-upload",
+              "icon": "file-upload-field",
+	#elseif($field.formType == 'upload-img')
+              "type": "picture-upload",
+              "icon": "picture-upload-field",
+	#elseif($field.formType == 'editor')
+              "type": "rich-editor",
+              "icon": "rich-editor-field",
+	#else
+              "type": "input",
+              "icon": "text-field",
+	#end
+              "formItemFlag": true,
+              "options": {
+	                "name": "${field.attrName}",
+	                "label": "#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end",
+	#if(($field.formType == 'select' || $field.formType == 'radio' || $field.formType == 'checkbox') && ${field.fieldDict})
+                    "optionItemsDictType": "${field.fieldDict}",
+	#end
+                    "placeholder": "请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"
+              },
+    #if($field.formRequired)
+             "required": true,
+    #end
+              "id": "input${math.add($key,${foreach.index})}"
+            }
+          ],
+          "options": {
+            "name": "gridCol${math.add($key,${foreach.index})}",
+            "hidden": false,
+            "offset": 0,
+            "push": 0,
+            "pull": 0,
+	#if($formLayout == 1)
+            "span": 24,
+	#elseif($formLayout == 2)
+            "span": 12,
+	#end
+            "responsive": false
+          },
+          "id": "grid-col-${math.add($key,${foreach.index})}"
+        }#if($foreach.hasNext),#end
+#end
+#end
+      ],
+      "options": {
+        "name": "grid${functionName}",
+        "hidden": false,
+        "gutter": 12
+      },
+      "id": "grid${functionName}"
+    }
+  ],
+  "formConfig": {
+    "modelName": "form",
+    "refName": "form",
+    "rulesName": "rules",
+    "labelWidth": 80,
+    "labelPosition": "left",
+    "labelAlign": "label-left-align",
+    "layoutType": "PC",
+    "jsonVersion": 3
+  }
+}

+ 218 - 0
vform.vue

@@ -0,0 +1,218 @@
+<template>
+    <el-dialog :title="form.${pk.attrName} ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
+      <el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px">
+#foreach($key in $resultMap.keySet())
+#set($itemList = $resultMap.get($key))
+<el-row :gutter="24">
+#foreach($field in $itemList)
+  <el-col :span="$field.span">
+#if($field.type == 'input')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+          <el-input v-model="form.${field.options.name}" placeholder="${field.options.placeholder}"/>
+        </el-form-item>
+#elseif($field.type == 'number')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+          <el-input-number :min="${field.options.min}" :max="${field.options.max}" v-model="form.${field.options.name}" placeholder="${field.options.placeholder}"></el-input-number>
+        </el-form-item>
+#elseif($field.type == 'textarea')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+          <el-input type="textarea" :rows="${field.options.rows}" v-model="form.${field.options.name}" placeholder="${field.options.placeholder}"/>
+        </el-form-item>
+#elseif($field.type == 'select')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+            <el-select v-model="form.${field.options.name}" placeholder="${field.options.placeholder}">
+       #if($field.options.optionItemsDictType)
+                <el-option :value="item.value" :label="item.label" v-for="(item, index) in ${field.options.optionItemsDictType}" :key="index"></el-option>
+       #else
+                <el-option label="请选择">0</el-option>
+       #end
+            </el-select>
+        </el-form-item>
+#elseif($field.type == 'radio')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+            <el-radio-group v-model="form.${field.options.name}">
+       #if($field.options.optionItemsDictType)
+             <el-radio :label="item.value" v-for="(item, index) in ${field.options.optionItemsDictType}" border :key="index">{{ item.label }}
+              </el-radio>
+       #end
+            </el-radio-group>
+        </el-form-item>
+#elseif($field.type == 'checkbox')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+            <el-checkbox-group v-model="form.${field.options.name}">
+       #if($field.options.optionItemsDictType)
+                <el-checkbox :label="item.value" :name="item.label" v-for="(item, index) in ${field.options.optionItemsDictType}" :key="index"></el-checkbox>
+       #else
+                <el-checkbox label="启用" name="type"></el-checkbox>
+                <el-checkbox label="禁用" name="type"></el-checkbox>
+       #end
+            </<el-checkbox-group>
+        </el-form-item>
+#elseif($field.type == 'date')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+            <el-date-picker type="date" placeholder="${field.options.placeholder}" v-model="form.${field.options.name}" :value-format="dateStr"></el-date-picker>
+        </el-form-item>
+#elseif($field.type == 'time')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+            <el-time-picker placeholder="${field.options.placeholder}" v-model="form.${field.options.name}" :value-format="dateTimeStr"></el-date-picker>
+        </el-form-item>
+#elseif($field.type == 'file-upload')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+            <upload-file  v-model="form.${field.attrName}" limit="${field.options.limit}" fileMaxSize="${field.options.fileMaxSize}"></upload-file>
+        </el-form-item>
+#elseif($field.type == 'picture-upload')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+            <upload-img v-model:imageUrl="form.${field.options.name}" limit="${field.options.limit}" fileMaxSize="${field.options.fileMaxSize}"></upload-img>
+        </el-form-item>
+#elseif($field.type == 'rich-editor')
+          <el-form-item label="${field.options.label}" prop="${field.options.name}">
+            <editor v-model:get-html="form.${field.options.name}" placeholder="${field.options.placeholder}"></editor>
+          </el-form-item>
+#elseif($field.type == 'switch')
+          <el-form-item label="${field.options.label}" prop="${field.options.name}">
+          <el-switch v-model="form.${field.options.name}" />
+          </el-form-item>
+#elseif($field.type == 'rate')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+          <el-rate v-model="form.${field.options.name}" />
+      </el-form-item>
+#elseif($field.type == 'slider')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+          <el-slider v-model="form.${field.options.name}" />
+      </el-form-item>
+#elseif($field.type == 'color')
+        <el-form-item label="${field.options.label}" prop="${field.options.name}">
+          <el-color-picker v-model="form.${field.options.name}" />
+      </el-form-item>
+#elseif($field.type == 'static-text' || $field.type == 'html-text')
+        <span>{{form.${field.options.name}}}</span>          
+#elseif($field.type == 'divider')
+      <el-divider />
+#else
+      <el-form-item label="${field.options.label}" prop="${field.options.name}">
+        <el-input v-model="form.${field.options.name}" placeholder="${field.options.placeholder}"/>
+      </el-form-item>
+#end
+  </el-col>
+#end
+</el-row>
+#end
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="visible = false" formDialogRef>取消</el-button>
+          <el-button type="primary" @click="onSubmit" formDialogRef>确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+</template>
+
+<script setup lang="ts" name="${ClassName}Dialog">
+import { useDict } from '/@/hooks/dict';
+import { useMessage } from "/@/hooks/message";
+import { getObj, addObj, putObj } from '/@/api/${moduleName}/${functionName}'
+import { rule } from '/@/utils/validate';
+const emit = defineEmits(['refresh']);
+
+// 定义变量内容
+const dataFormRef = ref();
+const visible = ref(false)
+// 定义字典
+#set($fieldDict=[])
+#foreach($key in $resultMap.keySet())
+#set($itemList = $resultMap.get($key))
+#foreach($field in $itemList)
+   #if($field.options.optionItemsDictType)
+        #set($void=$fieldDict.add($field.options.optionItemsDictType))
+    #end
+#end
+#end
+#if($fieldDict)
+const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict))
+#end
+
+// 提交表单数据
+const form = reactive({
+		${pk.attrName}:"",
+#foreach($key in $resultMap.keySet())
+#set($itemList = $resultMap.get($key))
+#foreach($field in $itemList)
+    ${field.options.name}: "",
+#end
+#end
+});
+
+// 定义校验规则
+const dataRules = ref({
+#foreach($key in $resultMap.keySet())
+#set($itemList = $resultMap.get($key))
+#foreach($field in $itemList)
+#if($field.options.required && $field.options.validation)
+    ${field.options.name}: [{required: true, message: '${field.options.label}不能为空', trigger: 'blur'}, {{ validator: rule.${field.options.validation}, trigger: 'blur' }],
+#elseif($field.options.required)
+    ${field.options.name}: [{required: true, message: '${field.options.label}不能为空', trigger: 'blur'}],
+#elseif($field.options.validation)
+   ${field.options.name}: [{ validator: rule.${field.options.validation}, trigger: 'blur' }],
+#end
+#end
+#end
+})
+
+// 打开弹窗
+const openDialog = (id: string) => {
+  visible.value = true
+  form.${pk.attrName} = ''
+
+  // 重置表单数据
+    nextTick(() => {
+        dataFormRef.value?.resetFields();
+    });
+  
+  // 获取${className}信息
+  if (id) {
+    form.${pk.attrName} = id
+    get${className}Data(id)
+  }
+};
+
+// 提交
+const onSubmit = () => {
+  dataFormRef.value.validate((valid: boolean) => {
+    if (!valid) {
+      return false
+    }
+
+    // 更新
+    if (form.${pk.attrName}) {
+      putObj(form).then(() => {
+        useMessage().success('修改成功')
+        visible.value = false // 关闭弹窗
+        emit('refresh')
+      }).catch((err: any) => {
+        useMessage().error(err.msg)
+      })
+    } else {
+      addObj(form).then(() => {
+        useMessage().success('添加成功')
+        visible.value = false // 关闭弹窗
+        emit('refresh')
+      }).catch((err: any) => {
+        useMessage().error(err.msg)
+      })
+    }
+  })
+}
+
+// 初始化表单数据
+const get${className}Data = (id: string) => {
+  // 获取数据
+  getObj(id).then((res: any) => {
+    Object.assign(form, res.data)
+  })
+};
+
+// 暴露变量
+defineExpose({
+  openDialog
+});
+</script>

+ 161 - 0
主子Contoller

@@ -0,0 +1,161 @@
+package ${package}.${moduleName}.controller;
+
+#if($queryList)
+import cn.hutool.core.util.StrUtil;
+#end
+import cn.hutool.core.util.ArrayUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.common.log.annotation.SysLog;
+import ${package}.${moduleName}.entity.${ClassName}Entity;
+import ${package}.${moduleName}.entity.${ChildClassName}Entity;
+import ${package}.${moduleName}.service.${ClassName}Service;
+import org.springframework.security.access.prepost.PreAuthorize;
+import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import org.springdoc.api.annotations.ParameterObject;
+import org.springframework.http.HttpHeaders;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * ${tableComment}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/${functionName}" )
+@Tag(description = "${functionName}" , name = "${tableComment}管理" )
+@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
+public class ${ClassName}Controller {
+
+    private final  ${ClassName}Service ${className}Service;
+
+    /**
+     * 分页查询
+     * @param page 分页对象
+     * @param ${className} ${tableComment}
+     * @return
+     */
+    @Operation(summary = "分页查询" , description = "分页查询" )
+    @GetMapping("/page" )
+    @PreAuthorize("@pms.hasPermission('${moduleName}_${functionName}_view')" )
+    public R get${ClassName}Page(@ParameterObject Page page, @ParameterObject ${ClassName}Entity ${className}) {
+        LambdaQueryWrapper<${ClassName}Entity> wrapper = Wrappers.lambdaQuery();
+#foreach ($field in $queryList)
+#set($getAttrName=$str.getProperty($field.attrName))
+#set($var="${className}.$getAttrName()")
+#if($field.attrType == 'String')
+#set($expression="StrUtil.isNotBlank")
+#else
+#set($expression="Objects.nonNull")
+#end
+#if($field.queryType == '=')
+		wrapper.eq($expression($var),${ClassName}Entity::$getAttrName,$var);
+#elseif( $field.queryType == 'like' )
+		wrapper.like($expression($var),${ClassName}Entity::$getAttrName,$var);
+#elseif( $field.queryType == '!-' )
+		wrapper.ne($expression($var),${ClassName}Entity::$getAttrName,$var);
+#elseif( $field.queryType == '>' )
+		wrapper.gt($expression($var),${ClassName}Entity::$getAttrName,$var);
+#elseif( $field.queryType == '<' )
+		wrapper.lt($expression($var),${ClassName}Entity::$getAttrName,$var);
+#elseif( $field.queryType == '>=' )
+		wrapper.ge($expression($var),${ClassName}Entity::$getAttrName,$var);
+#elseif( $field.queryType == '<=' )
+		wrapper.le($expression($var),${ClassName}Entity::$getAttrName,$var);
+#elseif( $field.queryType == 'left like' )
+		wrapper.likeLeft($expression($var),${ClassName}Entity::$getAttrName,$var);
+#elseif( $field.queryType == 'right like' )
+		wrapper.likeRight($expression($var),${ClassName}Entity::$getAttrName,$var);
+#end
+#end
+        return R.ok(${className}Service.page(page, wrapper));
+    }
+
+    /**
+     * 通过id查询${tableComment}
+     * @param ${pk.attrName} id
+     * @return R
+     */
+    @Operation(summary = "通过id查询" , description = "通过id查询" )
+    @GetMapping("/{${pk.attrName}}" )
+    @PreAuthorize("@pms.hasPermission('${moduleName}_${functionName}_view')" )
+    public R getById(@PathVariable("${pk.attrName}" ) ${pk.attrType} ${pk.attrName}) {
+        return R.ok(${className}Service.getByIdDeep(${pk.attrName}));
+    }
+
+    /**
+     * 新增${tableComment}
+     * @param ${className} ${tableComment}
+     * @return R
+     */
+    @Operation(summary = "新增${tableComment}" , description = "新增${tableComment}" )
+    @SysLog("新增${tableComment}" )
+    @PostMapping
+    @PreAuthorize("@pms.hasPermission('${moduleName}_${functionName}_add')" )
+    public R save(@RequestBody ${ClassName}Entity ${className}) {
+        return R.ok(${className}Service.saveDeep(${className}));
+    }
+
+    /**
+     * 修改${tableComment}
+     * @param ${className} ${tableComment}
+     * @return R
+     */
+    @Operation(summary = "修改${tableComment}" , description = "修改${tableComment}" )
+    @SysLog("修改${tableComment}" )
+    @PutMapping
+    @PreAuthorize("@pms.hasPermission('${moduleName}_${functionName}_edit')" )
+    public R updateById(@RequestBody ${ClassName}Entity ${className}) {
+        return R.ok(${className}Service.updateDeep(${className}));
+    }
+
+    /**
+     * 通过id删除${tableComment}
+     * @param ids ${pk.attrName}列表
+     * @return R
+     */
+    @Operation(summary = "通过id删除${tableComment}" , description = "通过id删除${tableComment}" )
+    @SysLog("通过id删除${tableComment}" )
+    @DeleteMapping
+    @PreAuthorize("@pms.hasPermission('${moduleName}_${functionName}_del')" )
+    public R removeById(@RequestBody ${pk.attrType}[] ids) {
+        return R.ok(${className}Service.removeDeep(ids));
+    }
+
+    /**
+     * 通过id删除${tableComment}子表数据
+     * @param ids ${pk.attrName}列表
+     * @return R
+     */
+    @Operation(summary = "通过id删除${tableComment}子表数据" , description = "通过id删除${tableComment}子表数据" )
+    @SysLog("通过id删除${tableComment}子表数据" )
+    @DeleteMapping("/child")
+    @PreAuthorize("@pms.hasPermission('${moduleName}_${functionName}_del')" )
+    public R removeChild(@RequestBody ${pk.attrType}[] ids) {
+        return R.ok(${className}Service.removeChild(ids));
+    }
+
+    /**
+     * 导出excel 表格
+     * @param ${className} 查询条件
+   	 * @param ids 导出指定ID
+     * @return excel 文件流
+     */
+    @ResponseExcel
+    @GetMapping("/export")
+    @PreAuthorize("@pms.hasPermission('${moduleName}_${functionName}_export')" )
+    public List<${ClassName}Entity> export(${ClassName}Entity ${className},${pk.attrType}[] ids) {
+        return ${className}Service.list(Wrappers.lambdaQuery(${className}).in(ArrayUtil.isNotEmpty(ids), ${ClassName}Entity::$str.getProperty($pk.attrName), ids));
+    }
+}

+ 259 - 0
主子表单

@@ -0,0 +1,259 @@
+<template>
+  <el-drawer :title="form.${pk.attrName} ? (detail ? '详情' : '编辑') : '添加'" v-model="visible" size="50%">
+      <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" :disabled="detail" v-loading="loading">
+        <el-row :gutter="24">
+#foreach($field in $formList)
+#if($field.attrName != ${pk.attrName})
+#if($formLayout == 1)
+    <el-col :span="24" class="mb20">
+#elseif($formLayout == 2)
+    <el-col :span="12" class="mb20">
+#end
+#if($field.formType == 'text')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+        <el-input v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"/>
+      </el-form-item>
+      </el-col>
+#elseif($field.formType == 'textarea')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+        <el-input type="textarea" v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"/>
+      </el-form-item>
+      </el-col>
+#elseif($field.formType == 'select')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+          <el-select v-model="form.${field.attrName}" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end">
+     #if($field.fieldDict)
+            <el-option :value="item.value" :label="item.label" v-for="(item, index) in ${field.fieldDict}" :key="index"></el-option>
+       #end
+     #if(!$field.fieldDict)
+            <el-option label="请选择">0</el-option>
+       #end
+          </el-select>
+        </el-form-item>
+      </el-col>
+#elseif($field.formType == 'radio')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+            <el-radio-group v-model="form.${field.attrName}">
+     #if($field.fieldDict)
+             <el-radio :label="item.value" v-for="(item, index) in ${field.fieldDict}" border :key="index">{{ item.label }}
+            </el-radio>
+       #else
+           <el-radio label="${field.fieldComment}" border>${field.fieldComment}</el-radio>
+       #end
+            </el-radio-group>
+        </el-form-item>
+      </el-col>
+#elseif($field.formType == 'checkbox')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+            <el-checkbox-group v-model="form.${field.attrName}">
+     #if($field.fieldDict)
+                <el-checkbox :label="item.value" v-for="(item, index) in ${field.fieldDict}" :key="index">{{ item.label }}</el-checkbox>
+       #end
+     #if(!$field.fieldDict)
+                <el-checkbox label="启用" name="type"></el-checkbox>
+                <el-checkbox label="禁用" name="type"></el-checkbox>
+       #end
+            </el-checkbox-group>
+        </el-form-item>
+      </el-col>
+#elseif($field.formType == 'date')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+      <el-date-picker type="date" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="form.${field.attrName}" :value-format="dateStr"></el-date-picker>
+      </el-form-item>
+      </el-col>
+#elseif($field.formType == 'datetime')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+            <el-date-picker type="datetime" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="form.${field.attrName}" :value-format="dateTimeStr"></el-date-picker>
+      </el-form-item>
+      </el-col>
+#elseif($field.formType == 'number')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+        <el-input-number :min="1" :max="1000" v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"></el-input-number>
+      </el-form-item>
+    </el-col>
+#elseif($field.formType == 'upload-file')
+  <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+    <upload-file  v-model="form.${field.attrName}"></upload-file>
+  </el-form-item>
+  </el-col>
+#elseif($field.formType == 'upload-img')
+  <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+    <upload-img v-model:imageUrl="form.${field.attrName}"></upload-img>
+  </el-form-item>
+  </el-col>
+#elseif($field.formType == 'editor')
+  <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+    <editor v-model:get-html="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"></editor>
+  </el-form-item>
+  </el-col>
+#end
+#if(!$field.formType)
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${column.attrName}">
+        <el-input v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"/>
+      </el-form-item>
+    </el-col>
+#end
+#end
+#end
+    </el-row>
+  <el-row :gutter="24">
+    <sc-form-table
+      v-model="form.${childClassName}List"
+      :addTemplate="childTemp"
+      @delete="deleteChild"
+      placeholder="暂无数据"
+    >
+#set($ignoreColumnList = ["create_by","create_time","update_by","update_time","del_flag","tenant_id"])
+#foreach($field in $childFieldList)
+#if($field.primaryPk == '1')
+#elseif($ignoreColumnList.contains(${field.fieldName}))
+#elseif($field.attrName == $childField)
+#else  
+      <el-table-column label="${field.fieldComment}" prop="${field.attrName}">
+        <template #default="{ row, $index }">
+          <el-form-item :prop="`${childClassName}List.${$index}.${field.attrName}`" :rules="[{ required: true, trigger: 'blur' }]">
+            <el-input v-model="row.${field.attrName}" style="width: 100%" />
+          </el-form-item>
+        </template>
+      </el-table-column>
+#end
+#end
+    </sc-form-table>
+  </el-row>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="visible = false">取消</el-button>
+          <el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
+        </span>
+      </template>
+    </el-drawer>
+</template>
+
+<script setup lang="ts" name="${ClassName}Dialog">
+import { useDict } from '/@/hooks/dict';
+import { rule } from '/@/utils/validate';
+import { useMessage } from "/@/hooks/message";
+import { getObj, addObj, putObj, delChildObj } from '/@/api/${moduleName}/${functionName}'
+const scFormTable = defineAsyncComponent(() => import('/@/components/FormTable/index.vue'));
+const emit = defineEmits(['refresh']);
+
+// 定义变量内容
+const dataFormRef = ref();
+const visible = ref(false);
+const loading = ref(false);
+const detail = ref(false);
+
+// 定义字典
+#set($fieldDict=[])
+#foreach($field in $gridList)
+	#if($field.fieldDict)
+		#set($void=$fieldDict.add($field.fieldDict))
+	#end
+#end
+#if($fieldDict)
+const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict))
+#end
+
+// 提交表单数据
+const form = reactive({
+#if(!$formList.contains(${pk.attrName}))
+		${pk.attrName}:'',
+#end
+#foreach($field in $formList)
+#if($field.formType == 'number')
+		${field.attrName}: 0,
+#elseif($field.formType == 'checkbox')
+    ${field.attrName}: [],
+#else
+	  ${field.attrName}: '',
+#end
+#end
+	  ${childClassName}List:[],
+});
+
+const childTemp = reactive({
+  #foreach($field in $childFieldList)
+    ${field.attrName}: '',
+  #end
+})
+
+// 定义校验规则
+const dataRules = ref({
+#foreach($field in $formList)
+#if($field.formRequired && $field.formValidator)
+    ${field.attrName}: [{required: true, message: '${field.fieldComment}不能为空', trigger: 'blur'}, { validator: rule.${field.formValidator}, trigger: 'blur' }],
+#elseif($field.formRequired)
+        ${field.attrName}: [{required: true, message: '${field.fieldComment}不能为空', trigger: 'blur'}],
+#elseif($field.formValidator)
+        ${field.attrName}: [{ validator: rule.${field.formValidator}, trigger: 'blur' }],
+#end
+#end
+})
+
+// 打开弹窗
+const openDialog = (id: string, isDetail: boolean) => {
+  visible.value = true
+  detail.value = isDetail
+  form.${pk.attrName} = ''
+
+  // 重置表单数据
+  nextTick(() => {
+    dataFormRef.value?.resetFields();
+    form.${childClassName}List = [];
+  });
+
+  // 获取${className}信息
+  if (id) {
+    form.${pk.attrName} = id
+    get${ClassName}Data(id)
+  }
+};
+
+// 提交
+const onSubmit = async () => {
+  const valid = await dataFormRef.value.validate().catch(() => {});
+  if (!valid) return false;
+
+  try {
+    loading.value = true;
+    form.${pk.attrName} ? await putObj(form) : await addObj(form);
+    useMessage().success(form.${pk.attrName} ? '修改成功' : '添加成功');
+    visible.value = false;
+    emit('refresh');
+  } catch (err: any) {
+    useMessage().error(err.msg);
+  } finally {
+    loading.value = false;
+  }
+};
+#foreach ($field in $childFieldList)
+#if($field.primaryPk == '1')
+#set($childPkName=$field.attrName)
+#end
+#end
+// 删除子表数据
+const deleteChild = async (obj: { $childPkName: string }) => {
+  if (obj.$childPkName) {
+    try {
+      await delChildObj([obj.$childPkName]);
+      useMessage().success('删除成功');
+    } catch (err: any) {
+      useMessage().error(err.msg);
+    }
+  }
+};
+
+// 初始化表单数据
+const get${ClassName}Data = (id: string) => {
+  // 获取数据
+  getObj(id).then((res: any) => {
+    Object.assign(form, res.data)
+  })
+};
+
+// 暴露变量
+defineExpose({
+  openDialog
+});
+</script>

+ 181 - 0
主子表格

@@ -0,0 +1,181 @@
+<template>
+  <div class="layout-padding">
+    <div class="layout-padding-auto layout-padding-view">
+#if($queryList)
+      <el-row v-show="showSearch">
+        <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
+#foreach($field in $queryList)
+#if($field.queryFormType == 'select')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+            <el-select v-model="state.queryForm.${field.attrName}" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end">
+       #if($field.fieldDict)
+              <el-option :label="item.label" :value="item.value" v-for="(item, index) in ${field.fieldDict}" :key="index"></el-option>
+         #else
+              <el-option label="请选择">0</el-option>
+         #end
+            </el-select>
+      </el-form-item>
+#elseif($field.queryFormType == 'date')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+      <el-date-picker type="date" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="state.queryForm.${field.attrName}"></el-date-picker>
+      </el-form-item>
+#elseif($field.queryFormType == 'datetime')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}" >
+            <el-date-picker type="datetime" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="state.queryForm.${field.attrName}"></el-date-picker>
+      </el-form-item>
+      </el-col>
+#else
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}" >
+        <el-input placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="state.queryForm.${field.attrName}"
+          style="max-width: 180px" />
+      </el-form-item>
+#end
+#end
+          <el-form-item>
+            <el-button icon="search" type="primary" @click="getDataList">
+              查询
+            </el-button>
+            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </el-row>
+#end
+      <el-row>
+        <div class="mb8" style="width: 100%">
+          <el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()"
+            v-auth="'${moduleName}_${functionName}_add'">
+            新 增
+          </el-button>
+          <el-button plain :disabled="multiple" icon="Delete" type="primary"
+            v-auth="'${moduleName}_${functionName}_del'" @click="handleDelete(selectObjs)">
+            删除
+          </el-button>
+          <right-toolbar v-model:showSearch="showSearch" :export="'${moduleName}_${functionName}_export'"
+                @exportExcel="exportExcel" class="ml10 mr20" style="float: right;"
+            @queryTable="getDataList"></right-toolbar>
+        </div>
+      </el-row>
+      <el-table :data="state.dataList" v-loading="state.loading" border 
+        :cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle"
+        @selection-change="selectionChangeHandle" @sort-change="sortChangeHandle">
+        <el-table-column type="selection" width="40" align="center" />
+        <el-table-column type="index" label="#" width="40" />
+      #foreach($field in $gridList)
+        #if($field.fieldDict)
+          <el-table-column prop="${field.attrName}" label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" show-overflow-tooltip>
+      <template #default="scope">
+                <dict-tag :options="$field.fieldDict" :value="scope.row.${field.attrName}"></dict-tag>
+            </template>
+          </el-table-column>
+        #else
+          <el-table-column prop="${field.attrName}" label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" #if(${field.gridSort} == '1')sortable="custom"#end show-overflow-tooltip/>
+        #end
+     #end
+        <el-table-column label="操作" width="200">
+          <template #default="scope">
+          #if($ChildClassName)
+            <el-button text type="primary" icon="view" v-auth="'sys_role_edit'" @click="formDialogRef.openDialog(scope.row.${pk.attrName}, true)">
+              详情
+            </el-button>
+          #end
+            <el-button icon="edit-pen" text type="primary" v-auth="'${moduleName}_${functionName}_edit'"
+              @click="formDialogRef.openDialog(scope.row.${pk.attrName})">编辑</el-button>
+            <el-button icon="delete" text type="primary" v-auth="'${moduleName}_${functionName}_del'" @click="handleDelete([scope.row.${pk.attrName}])">
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
+    </div>
+
+    <!-- 编辑、新增  -->
+    <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
+  </div>
+</template>
+
+<script setup lang="ts" name="system${ClassName}">
+import { BasicTableProps, useTable } from "/@/hooks/table";
+import { fetchList, delObjs } from "/@/api/${moduleName}/${functionName}";
+import { useMessage, useMessageBox } from "/@/hooks/message";
+import { useDict } from '/@/hooks/dict';
+// 引入组件
+const FormDialog = defineAsyncComponent(() => import('./form.vue'));
+
+// 定义查询字典
+#set($fieldDict=[])
+#foreach($field in $queryList)
+  #if($field.fieldDict)
+    #set($void=$fieldDict.add($field.fieldDict))
+  #end
+#end
+
+#foreach($field in $gridList)
+  #if($field.fieldDict)
+    #set($void=$fieldDict.add($field.fieldDict))
+  #end
+#end
+#if($fieldDict)
+const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict))
+#end
+// 定义变量内容
+const formDialogRef = ref()
+// 搜索变量
+const queryRef = ref()
+const showSearch = ref(true)
+// 多选变量
+const selectObjs = ref([]) as any
+const multiple = ref(true)
+
+const state: BasicTableProps = reactive<BasicTableProps>({
+  queryForm: {},
+  pageList: fetchList
+})
+
+//  table hook
+const {
+  getDataList,
+  currentChangeHandle,
+  sizeChangeHandle,
+  sortChangeHandle,
+  downBlobFile,
+  tableStyle
+} = useTable(state)
+
+// 清空搜索条件
+const resetQuery = () => {
+  // 清空搜索条件
+  queryRef.value?.resetFields()
+  // 清空多选
+  selectObjs.value = []
+  getDataList()
+}
+
+// 导出excel
+const exportExcel = () => {
+  downBlobFile('/${moduleName}/${functionName}/export', Object.assign(state.queryForm, { ids: selectObjs }), '${functionName}.xlsx')
+}
+
+// 多选事件
+const selectionChangeHandle = (objs: { $pk.attrName: string }[]) => {
+  selectObjs.value = objs.map(({ $pk.attrName }) => $pk.attrName);
+  multiple.value = !objs.length;
+};
+
+// 删除操作
+const handleDelete = async (ids: string[]) => {
+  try {
+    await useMessageBox().confirm('此操作将永久删除');
+  } catch {
+    return;
+  }
+
+  try {
+    await delObjs(ids);
+    getDataList();
+    useMessage().success('删除成功');
+  } catch (err: any) {
+    useMessage().error(err.msg);
+  }
+};
+</script>

+ 18 - 0
子Mapper

@@ -0,0 +1,18 @@
+package ${package}.${moduleName}.mapper;
+
+import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
+#if($ChildClassName)
+import ${package}.${moduleName}.entity.${ChildClassName}Entity;
+#else
+import ${package}.${moduleName}.entity.${ClassName}Entity;
+#end
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+#if($ChildClassName)
+public interface ${ChildClassName}Mapper extends PigxBaseMapper<${ChildClassName}Entity> {
+#else
+public interface ${ClassName}Mapper extends PigxBaseMapper<${ClassName}Entity> {
+#end
+
+}

+ 50 - 0
子实体

@@ -0,0 +1,50 @@
+package ${package}.${moduleName}.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+#foreach($import in $importList)
+import $import;
+#end
+
+/**
+ * ${tableComment}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+@Data
+@TableName("${childTableName}")
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "${childTableName}")
+public class ${ChildClassName}Entity extends Model<${ChildClassName}Entity> {
+
+#foreach ($field in $childFieldList)
+#if(${field.fieldComment})#set($comment=${field.fieldComment})#else #set($comment=${field.attrName})#end
+	/**
+	* $comment
+	*/
+#if($field.primaryPk == '1')
+	@TableId(type = IdType.ASSIGN_ID)
+#end
+#if($field.autoFill == 'INSERT')
+	@TableField(fill = FieldFill.INSERT)
+#elseif($field.autoFill == 'INSERT_UPDATE')
+	@TableField(fill = FieldFill.INSERT_UPDATE)
+#elseif($field.autoFill == 'UPDATE')
+	@TableField(fill = FieldFill.UPDATE)
+#end
+#if($field.fieldName == 'del_flag')
+  @TableLogic
+	@TableField(fill = FieldFill.INSERT)
+#end
+	@Schema(description="$comment"#if($field.hidden),hidden=$field.hidden#end)
+#if($field.formType == 'checkbox')
+   private ${field.attrType}[] $field.attrName;
+#else
+   private $field.attrType $field.attrName;
+#end 
+#end
+}

+ 62 - 0
实体

@@ -0,0 +1,62 @@
+package ${package}.${moduleName}.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+#foreach($import in $importList)
+import $import;
+#end
+#if($ChildClassName)
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.github.yulichang.annotation.EntityMapping;
+import java.util.List;
+#end
+
+/**
+ * ${tableComment}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+@Data
+@TableName("${tableName}")
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "${tableComment}")
+public class ${ClassName}Entity extends Model<${ClassName}Entity> {
+
+#foreach ($field in $fieldList)
+#if(${field.fieldComment})#set($comment=${field.fieldComment})#else #set($comment=${field.attrName})#end
+
+	/**
+	* $comment
+	*/
+#if($field.primaryPk == '1')
+    @TableId(type = IdType.ASSIGN_ID)
+#end
+#if($field.autoFill == 'INSERT')
+	@TableField(fill = FieldFill.INSERT)
+#elseif($field.autoFill == 'INSERT_UPDATE')
+	@TableField(fill = FieldFill.INSERT_UPDATE)
+#elseif($field.autoFill == 'UPDATE')
+	@TableField(fill = FieldFill.UPDATE)
+#end
+#if($field.fieldName == 'del_flag')
+    @TableLogic
+		@TableField(fill = FieldFill.INSERT)
+#end
+    @Schema(description="$comment"#if($field.hidden),hidden=$field.hidden#end)
+#if($field.formType == 'checkbox')
+    private ${field.attrType}[] $field.attrName;
+#else
+    private $field.attrType $field.attrName;
+#end    
+#end
+#if($ChildClassName)
+    @ExcelIgnore
+    @TableField(exist = false)
+    @EntityMapping(thisField = "$mainField", joinField = "$childField")
+    private List<${ChildClassName}Entity> ${childClassName}List;
+#end
+}

+ 214 - 0
表单

@@ -0,0 +1,214 @@
+<template>
+    <el-dialog :title="form.${pk.attrName} ? '编辑' : '新增'" v-model="visible"
+      :close-on-click-modal="false" draggable>
+      <el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
+       <el-row :gutter="24">
+#foreach($field in $formList)
+#if($field.attrName != ${pk.attrName})
+#if($formLayout == 1)
+    <el-col :span="24" class="mb20">
+#elseif($formLayout == 2)
+    <el-col :span="12" class="mb20">
+#end
+#if($field.formType == 'text')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+        <el-input v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"/>
+      </el-form-item>
+      </el-col>
+#elseif($field.formType == 'textarea')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+        <el-input type="textarea" v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"/>
+      </el-form-item>
+      </el-col>
+#elseif($field.formType == 'select')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+          <el-select v-model="form.${field.attrName}" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end">
+     #if($field.fieldDict)
+            <el-option :value="item.value" :label="item.label" v-for="(item, index) in ${field.fieldDict}" :key="index"></el-option>
+       #end
+     #if(!$field.fieldDict)
+            <el-option label="请选择">0</el-option>
+       #end
+          </el-select>
+        </el-form-item>
+      </el-col>
+#elseif($field.formType == 'radio')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+            <el-radio-group v-model="form.${field.attrName}">
+     #if($field.fieldDict)
+             <el-radio :label="item.value" v-for="(item, index) in ${field.fieldDict}" border :key="index">{{ item.label }}
+            </el-radio>
+       #else
+           <el-radio label="${field.fieldComment}" border>${field.fieldComment}</el-radio>
+       #end
+            </el-radio-group>
+        </el-form-item>
+      </el-col>
+#elseif($field.formType == 'checkbox')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+            <el-checkbox-group v-model="form.${field.attrName}">
+     #if($field.fieldDict)
+						<el-checkbox :label="item.value" v-for="(item, index) in ${field.fieldDict}" :key="index">{{ item.label }}</el-checkbox>
+       #end
+     #if(!$field.fieldDict)
+                <el-checkbox label="启用" name="type"></el-checkbox>
+                <el-checkbox label="禁用" name="type"></el-checkbox>
+       #end
+            </el-checkbox-group>
+        </el-form-item>
+      </el-col>
+#elseif($field.formType == 'date')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+      <el-date-picker type="date" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="form.${field.attrName}" :value-format="dateStr"></el-date-picker>
+      </el-form-item>
+      </el-col>
+#elseif($field.formType == 'datetime')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+            <el-date-picker type="datetime" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="form.${field.attrName}" :value-format="dateTimeStr"></el-date-picker>
+      </el-form-item>
+      </el-col>
+
+#elseif($field.formType == 'number')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+        <el-input-number :min="1" :max="1000" v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"></el-input-number>
+      </el-form-item>
+    </el-col>
+#elseif($field.formType == 'upload-file')
+  <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+    <upload-file v-model:imageUrl="form.${field.attrName}"></upload-file>
+  </el-form-item>
+  </el-col>
+#elseif($field.formType == 'upload-img')
+  <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+    <upload-img v-model="form.${field.attrName}"></upload-img>
+  </el-form-item>
+  </el-col>
+#elseif($field.formType == 'editor')
+  <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+    <editor v-if="visible" v-model:get-html="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"></editor>
+  </el-form-item>
+  </el-col>
+#end
+
+#if(!$field.formType)
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${column.attrName}">
+        <el-input v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end"/>
+      </el-form-item>
+    </el-col>
+#end
+#end
+#end
+			</el-row>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="visible = false">取消</el-button>
+          <el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+</template>
+
+<script setup lang="ts" name="${ClassName}Dialog">
+import { useDict } from '/@/hooks/dict';
+import { useMessage } from "/@/hooks/message";
+import { getObj, addObj, putObj } from '/@/api/${moduleName}/${functionName}'
+import { rule } from '/@/utils/validate';
+const emit = defineEmits(['refresh']);
+
+// 定义变量内容
+const dataFormRef = ref();
+const visible = ref(false)
+const loading = ref(false)
+// 定义字典
+#set($fieldDict=[])
+#foreach($field in $gridList)
+	#if($field.fieldDict)
+		#set($void=$fieldDict.add($field.fieldDict))
+	#end
+#end
+#if($fieldDict)
+const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict))
+#end
+
+// 提交表单数据
+const form = reactive({
+#if(!$formList.contains(${pk.attrName}))
+		${pk.attrName}:'',
+#end
+#foreach($field in $formList)
+#if($field.formType == 'number')
+		${field.attrName}: 0,
+#elseif($field.formType == 'checkbox')
+    ${field.attrName}: [],
+#else
+	  ${field.attrName}: '',
+#end
+#end
+});
+
+// 定义校验规则
+const dataRules = ref({
+#foreach($field in $formList)
+#if($field.formRequired && $field.formValidator)
+    ${field.attrName}: [{required: true, message: '${field.fieldComment}不能为空', trigger: 'blur'}, { validator: rule.${field.formValidator}, trigger: 'blur' }],
+#elseif($field.formRequired)
+        ${field.attrName}: [{required: true, message: '${field.fieldComment}不能为空', trigger: 'blur'}],
+#elseif($field.formValidator)
+        ${field.attrName}: [{ validator: rule.${field.formValidator}, trigger: 'blur' }],
+#end
+#end
+})
+
+// 打开弹窗
+const openDialog = (id: string) => {
+  visible.value = true
+  form.${pk.attrName} = ''
+
+  // 重置表单数据
+	nextTick(() => {
+		dataFormRef.value?.resetFields();
+	});
+
+  // 获取${className}信息
+  if (id) {
+    form.${pk.attrName} = id
+    get${className}Data(id)
+  }
+};
+
+// 提交
+const onSubmit = async () => {
+	const valid = await dataFormRef.value.validate().catch(() => {});
+	if (!valid) return false;
+
+	try {
+    loading.value = true;
+		form.${pk.attrName} ? await putObj(form) : await addObj(form);
+		useMessage().success(form.${pk.attrName} ? '修改成功' : '添加成功');
+		visible.value = false;
+		emit('refresh');
+	} catch (err: any) {
+		useMessage().error(err.msg);
+	} finally {
+    loading.value = false;
+  }
+};
+
+
+// 初始化表单数据
+const get${className}Data = (id: string) => {
+  // 获取数据
+  loading.value = true
+  getObj(id).then((res: any) => {
+    Object.assign(form, res.data)
+  }).finally(() => {
+    loading.value = false
+  })
+};
+
+// 暴露变量
+defineExpose({
+  openDialog
+});
+</script>

+ 175 - 0
表格

@@ -0,0 +1,175 @@
+<template>
+  <div class="layout-padding">
+    <div class="layout-padding-auto layout-padding-view">
+#if($queryList)
+      <el-row v-show="showSearch">
+        <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
+#foreach($field in $queryList)
+#if($field.queryFormType == 'select')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+            <el-select v-model="state.queryForm.${field.attrName}" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end">
+       #if($field.fieldDict)
+              <el-option :label="item.label" :value="item.value" v-for="(item, index) in ${field.fieldDict}" :key="index"></el-option>
+         #else
+              <el-option label="请选择">0</el-option>
+         #end
+            </el-select>
+      </el-form-item>
+#elseif($field.queryFormType == 'date')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}">
+      <el-date-picker type="date" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="state.queryForm.${field.attrName}"></el-date-picker>
+      </el-form-item>
+#elseif($field.queryFormType == 'datetime')
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}" >
+            <el-date-picker type="datetime" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="state.queryForm.${field.attrName}"></el-date-picker>
+      </el-form-item>
+      </el-col>
+#else
+      <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" prop="${field.attrName}" >
+        <el-input placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" v-model="state.queryForm.${field.attrName}" />
+      </el-form-item>
+#end
+#end
+          <el-form-item>
+            <el-button icon="search" type="primary" @click="getDataList">
+              查询
+            </el-button>
+            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </el-row>
+#end
+      <el-row>
+        <div class="mb8" style="width: 100%">
+          <el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()"
+            v-auth="'${moduleName}_${functionName}_add'">
+            新 增
+          </el-button>
+          <el-button plain :disabled="multiple" icon="Delete" type="primary"
+            v-auth="'${moduleName}_${functionName}_del'" @click="handleDelete(selectObjs)">
+            删除
+          </el-button>
+          <right-toolbar v-model:showSearch="showSearch" :export="'${moduleName}_${functionName}_export'"
+                @exportExcel="exportExcel" class="ml10 mr20" style="float: right;"
+            @queryTable="getDataList"></right-toolbar>
+        </div>
+      </el-row>
+      <el-table :data="state.dataList" v-loading="state.loading" border 
+        :cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle"
+				@selection-change="selectionChangHandle"
+        @sort-change="sortChangeHandle">
+        <el-table-column type="selection" width="40" align="center" />
+        <el-table-column type="index" label="#" width="40" />
+      #foreach($field in $gridList)
+        #if($field.fieldDict)
+          <el-table-column prop="${field.attrName}" label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" show-overflow-tooltip>
+      <template #default="scope">
+                <dict-tag :options="$field.fieldDict" :value="scope.row.${field.attrName}"></dict-tag>
+            </template>
+          </el-table-column>
+        #else
+          <el-table-column prop="${field.attrName}" label="#if(${field.fieldComment})${field.fieldComment}#else ${field.attrName}#end" #if(${field.gridSort} == '1')sortable="custom"#end show-overflow-tooltip/>
+        #end
+     #end
+        <el-table-column label="操作" width="150">
+          <template #default="scope">
+            <el-button icon="edit-pen" text type="primary" v-auth="'${moduleName}_${functionName}_edit'"
+              @click="formDialogRef.openDialog(scope.row.${pk.attrName})">编辑</el-button>
+            <el-button icon="delete" text type="primary" v-auth="'${moduleName}_${functionName}_del'" @click="handleDelete([scope.row.${pk.attrName}])">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
+    </div>
+
+    <!-- 编辑、新增  -->
+    <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
+
+  </div>
+</template>
+
+<script setup lang="ts" name="system${ClassName}">
+import { BasicTableProps, useTable } from "/@/hooks/table";
+import { fetchList, delObjs } from "/@/api/${moduleName}/${functionName}";
+import { useMessage, useMessageBox } from "/@/hooks/message";
+import { useDict } from '/@/hooks/dict';
+
+// 引入组件
+const FormDialog = defineAsyncComponent(() => import('./form.vue'));
+// 定义查询字典
+#set($fieldDict=[])
+#foreach($field in $queryList)
+  #if($field.fieldDict)
+    #set($void=$fieldDict.add($field.fieldDict))
+  #end
+#end
+
+#foreach($field in $gridList)
+  #if($field.fieldDict)
+    #set($void=$fieldDict.add($field.fieldDict))
+  #end
+#end
+#if($fieldDict)
+const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict))
+#end
+// 定义变量内容
+const formDialogRef = ref()
+// 搜索变量
+const queryRef = ref()
+const showSearch = ref(true)
+// 多选变量
+const selectObjs = ref([]) as any
+const multiple = ref(true)
+
+const state: BasicTableProps = reactive<BasicTableProps>({
+  queryForm: {},
+  pageList: fetchList
+})
+
+//  table hook
+const {
+  getDataList,
+  currentChangeHandle,
+  sizeChangeHandle,
+  sortChangeHandle,
+  downBlobFile,
+	tableStyle
+} = useTable(state)
+
+// 清空搜索条件
+const resetQuery = () => {
+  // 清空搜索条件
+  queryRef.value?.resetFields()
+  // 清空多选
+  selectObjs.value = []
+  getDataList()
+}
+
+// 导出excel
+const exportExcel = () => {
+  downBlobFile('/${moduleName}/${functionName}/export', Object.assign(state.queryForm, { ids: selectObjs }), '${functionName}.xlsx')
+}
+
+// 多选事件
+const selectionChangHandle = (objs: { $pk.attrName: string }[]) => {
+  selectObjs.value = objs.map(({ $pk.attrName }) => $pk.attrName);
+  multiple.value = !objs.length;
+};
+
+// 删除操作
+const handleDelete = async (ids: string[]) => {
+  try {
+    await useMessageBox().confirm('此操作将永久删除');
+  } catch {
+    return;
+  }
+
+  try {
+    await delObjs(ids);
+    getDataList();
+    useMessage().success('删除成功');
+  } catch (err: any) {
+    useMessage().error(err.msg);
+  }
+};
+</script>