Files
qhmes/.trae/skills/jeecg-codegen/codegen-reference.md

2210 lines
74 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JeecgBoot 代码生成参考模板
本文档包含 JeecgBoot CRUD 代码的完整模板骨架,用 `{{变量}}` 标注替换位置。
## 变量说明
| 变量 | 说明 | 示例 |
|------|------|------|
| `{{tableName}}` | 数据库表名 | `biz_goods` |
| `{{entityName}}` | 实体类名(PascalCase) | `BizGoods` |
| `{{entityName_uncap}}` | 实体变量名(camelCase) | `bizGoods` |
| `{{entityPackage}}` | 模块包名 | `biz` |
| `{{entityPackagePath}}` | URL路径(同entityPackage或含/) | `biz` |
| `{{description}}` | 功能描述 | `商品管理` |
| `{{today}}` | 生成日期 | `2026-03-11` |
| `{{timestamp}}` | 13位毫秒级真实时间戳(用于菜单ID通过`date +%s%3N`获取) | `1741704000123` |
| `{{moduleRoot}}` | 后端模块根路径 | `jeecg-module-system/jeecg-system-biz` |
| `{{viewDir}}` | 前端视图目录 | `biz/goods` |
---
## A. 单表模式
### 生成文件清单
**后端 6 个文件:**
1. `{{moduleRoot}}/src/main/java/org/jeecg/modules/{{entityPackage}}/entity/{{entityName}}.java`
2. `{{moduleRoot}}/src/main/java/org/jeecg/modules/{{entityPackage}}/controller/{{entityName}}Controller.java`
3. `{{moduleRoot}}/src/main/java/org/jeecg/modules/{{entityPackage}}/service/I{{entityName}}Service.java`
4. `{{moduleRoot}}/src/main/java/org/jeecg/modules/{{entityPackage}}/service/impl/{{entityName}}ServiceImpl.java`
5. `{{moduleRoot}}/src/main/java/org/jeecg/modules/{{entityPackage}}/mapper/{{entityName}}Mapper.java`
6. `{{moduleRoot}}/src/main/java/org/jeecg/modules/{{entityPackage}}/mapper/xml/{{entityName}}Mapper.xml`
**前端 - vue3 封装风格4个文件**
7. `src/views/{{viewDir}}/{{entityName}}.api.ts`
8. `src/views/{{viewDir}}/{{entityName}}.data.ts`
9. `src/views/{{viewDir}}/{{entityName}}List.vue`
10. `src/views/{{viewDir}}/components/{{entityName}}Modal.vue`
**前端 - vue3Native 原生风格5个文件**
7. `src/views/{{viewDir}}/{{entityName}}.api.ts`
8. `src/views/{{viewDir}}/{{entityName}}.data.ts`
9. `src/views/{{viewDir}}/{{entityName}}List.vue`
10. `src/views/{{viewDir}}/components/{{entityName}}Modal.vue`
11. `src/views/{{viewDir}}/components/{{entityName}}Form.vue`
**SQL1个文件**
12. Flyway SQL: `jeecg-module-system/jeecg-system-start/src/main/resources/db/flyway/V{{version}}__{{description}}.sql`
---
### A1. Entity.java
```java
package org.jeecg.modules.{{entityPackage}}.entity;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.jeecg.common.aspect.annotation.Dict;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Description: {{description}}
* @Author: jeecg-boot
* @Date: {{today}}
* @Version: V1.0
*/
@Data
@TableName("{{tableName}}")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "{{description}}")
public class {{entityName}} implements Serializable {
private static final long serialVersionUID = 1L;
// === 主键字段根据表DDL自适应 ===
// --- 方式1: JeecgBoot 标准字符串主键 (varchar(36)/varchar(32), 无AUTO_INCREMENT) ---
// @TableId(type = IdType.ASSIGN_ID)
// @Schema(description = "主键")
// private String id;
// --- 方式2: int 自增主键 (int AUTO_INCREMENT) ---
// @TableId(type = IdType.AUTO)
// @Schema(description = "主键")
// private Integer id;
// --- 方式3: bigint 自增主键 (bigint AUTO_INCREMENT) ---
// @TableId(type = IdType.AUTO)
// @Schema(description = "主键")
// private Long id;
// --- 方式4: bigint 雪花ID (bigint, 无AUTO_INCREMENT) ---
// @TableId(type = IdType.ASSIGN_ID)
// @Schema(description = "主键")
// private Long id;
// === 业务字段(根据需求生成) ===
// 每个业务字段按以下规则生成注解:
// --- String 字段 ---
// @Excel(name = "字段注释", width = 15)
// private String fieldName;
// --- 带字典的 String 字段 ---
// @Excel(name = "字段注释", width = 15, dicCode = "dict_code")
// @Dict(dicCode = "dict_code")
// private String fieldName;
// --- 关联表字典的 String 字段 ---
// @Excel(name = "字段注释", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username")
// @Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
// private String fieldName;
// --- Integer 字段 ---
// @Excel(name = "字段注释", width = 15)
// private Integer fieldName;
// --- BigDecimal 字段 ---
// @Excel(name = "字段注释", width = 15)
// private BigDecimal fieldName;
// --- Date 字段 ---
// @Excel(name = "字段注释", width = 15, format = "yyyy-MM-dd")
// @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
// @DateTimeFormat(pattern = "yyyy-MM-dd")
// private Date fieldName;
// --- DateTime 字段 ---
// @Excel(name = "字段注释", width = 20, format = "yyyy-MM-dd HH:mm:ss")
// @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
// private Date fieldName;
// === 系统字段(仅在表中实际存在时才生成,不要盲目添加!) ===
// --- 以下每个字段都需要检查表DDL中是否存在对应列不存在则不生成 ---
// 如果表有 create_by 列:
// /**创建人*/
// @Schema(description = "创建人")
// private String createBy;
// 如果表有 create_time 列:
// /**创建日期*/
// @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
// @Schema(description = "创建日期")
// private Date createTime;
// 如果表有 update_by 列:
// /**更新人*/
// @Schema(description = "更新人")
// private String updateBy;
// 如果表有 update_time 列:
// /**更新日期*/
// @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
// @Schema(description = "更新日期")
// private Date updateTime;
// 如果表有 sys_org_code 列:
// /**所属部门*/
// @Schema(description = "所属部门")
// private String sysOrgCode;
// 新建表时默认添加全部系统字段已有表按实际DDL决定。
}
```
**字典注解规则:**
- 下拉/单选/多选/搜索框 + 字典编码: `@Dict(dicCode = "xxx")`
- 下拉/单选/多选/搜索框 + 字典表: `@Dict(dictTable = "tableName", dicText = "textField", dicCode = "codeField")`
- 用户选择: `@Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")`
- 部门选择: `@Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")`
---
### A2. Controller.java
```java
package org.jeecg.modules.{{entityPackage}}.controller;
import java.util.Arrays;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.{{entityPackage}}.entity.{{entityName}};
import org.jeecg.modules.{{entityPackage}}.service.I{{entityName}}Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.system.base.controller.JeecgController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.apache.shiro.authz.annotation.RequiresPermissions;
/**
* @Description: {{description}}
* @Author: jeecg-boot
* @Date: {{today}}
* @Version: V1.0
*/
@Tag(name = "{{description}}")
@RestController
@RequestMapping("/{{entityPackagePath}}/{{entityName_uncap}}")
@Slf4j
public class {{entityName}}Controller extends JeecgController<{{entityName}}, I{{entityName}}Service> {
@Autowired
private I{{entityName}}Service {{entityName_uncap}}Service;
/**
* 分页列表查询
*/
@Operation(summary = "{{description}}-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<{{entityName}}>> queryPageList({{entityName}} {{entityName_uncap}},
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<{{entityName}}> queryWrapper = QueryGenerator.initQueryWrapper({{entityName_uncap}}, req.getParameterMap());
Page<{{entityName}}> page = new Page<>(pageNo, pageSize);
IPage<{{entityName}}> pageList = {{entityName_uncap}}Service.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 添加
*/
@AutoLog(value = "{{description}}-添加")
@Operation(summary = "{{description}}-添加")
@RequiresPermissions("{{entityPackage}}:{{tableName}}:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody {{entityName}} {{entityName_uncap}}) {
{{entityName_uncap}}Service.save({{entityName_uncap}});
return Result.OK("添加成功!");
}
/**
* 编辑
*/
@AutoLog(value = "{{description}}-编辑")
@Operation(summary = "{{description}}-编辑")
@RequiresPermissions("{{entityPackage}}:{{tableName}}:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody {{entityName}} {{entityName_uncap}}) {
{{entityName_uncap}}Service.updateById({{entityName_uncap}});
return Result.OK("编辑成功!");
}
/**
* 通过id删除
* 注意参数类型需与Entity主键类型一致
* - String主键: @RequestParam(name = "id", required = true) String id
* - Integer主键: @RequestParam(name = "id", required = true) Integer id
* - Long主键: @RequestParam(name = "id", required = true) Long id
*/
@AutoLog(value = "{{description}}-通过id删除")
@Operation(summary = "{{description}}-通过id删除")
@RequiresPermissions("{{entityPackage}}:{{tableName}}:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
{{entityName_uncap}}Service.removeById(id);
return Result.OK("删除成功!");
}
/**
* 批量删除
* 注意:当主键为 Integer/Long 时,需将 ids 转为对应类型的 List
* - Integer: Arrays.stream(ids.split(",")).map(Integer::parseInt).collect(Collectors.toList())
* - Long: Arrays.stream(ids.split(",")).map(Long::parseLong).collect(Collectors.toList())
* - String: Arrays.asList(ids.split(","))
*/
@AutoLog(value = "{{description}}-批量删除")
@Operation(summary = "{{description}}-批量删除")
@RequiresPermissions("{{entityPackage}}:{{tableName}}:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
this.{{entityName_uncap}}Service.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
/**
* 通过id查询
* 注意参数类型需与Entity主键类型一致同 delete 方法)
*/
@Operation(summary = "{{description}}-通过id查询")
@GetMapping(value = "/queryById")
public Result<{{entityName}}> queryById(@RequestParam(name = "id", required = true) String id) {
{{entityName}} {{entityName_uncap}} = {{entityName_uncap}}Service.getById(id);
if ({{entityName_uncap}} == null) {
return Result.error("未找到对应数据");
}
return Result.OK({{entityName_uncap}});
}
/**
* 导出excel
*/
@RequiresPermissions("{{entityPackage}}:{{tableName}}:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, {{entityName}} {{entityName_uncap}}) {
return super.exportXls(request, {{entityName_uncap}}, {{entityName}}.class, "{{description}}");
}
/**
* 通过excel导入数据
*/
@RequiresPermissions("{{entityPackage}}:{{tableName}}:importExcel")
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, {{entityName}}.class);
}
}
```
**注意:** 如果查询字段中有下拉/单选/多选/复选框类型的,需要添加自定义查询规则:
```java
// 在 queryPageList 方法中:
Map<String, QueryRuleEnum> customeRuleMap = new HashMap<>();
customeRuleMap.put("status", QueryRuleEnum.LIKE_WITH_OR);
QueryWrapper<{{entityName}}> queryWrapper = QueryGenerator.initQueryWrapper({{entityName_uncap}}, req.getParameterMap(), customeRuleMap);
```
---
### A3. IService.java
```java
package org.jeecg.modules.{{entityPackage}}.service;
import org.jeecg.modules.{{entityPackage}}.entity.{{entityName}};
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @Description: {{description}}
* @Author: jeecg-boot
* @Date: {{today}}
* @Version: V1.0
*/
public interface I{{entityName}}Service extends IService<{{entityName}}> {
}
```
---
### A4. ServiceImpl.java
```java
package org.jeecg.modules.{{entityPackage}}.service.impl;
import org.jeecg.modules.{{entityPackage}}.entity.{{entityName}};
import org.jeecg.modules.{{entityPackage}}.mapper.{{entityName}}Mapper;
import org.jeecg.modules.{{entityPackage}}.service.I{{entityName}}Service;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
/**
* @Description: {{description}}
* @Author: jeecg-boot
* @Date: {{today}}
* @Version: V1.0
*/
@Service
public class {{entityName}}ServiceImpl extends ServiceImpl<{{entityName}}Mapper, {{entityName}}> implements I{{entityName}}Service {
}
```
---
### A5. Mapper.java
```java
package org.jeecg.modules.{{entityPackage}}.mapper;
import org.jeecg.modules.{{entityPackage}}.entity.{{entityName}};
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @Description: {{description}}
* @Author: jeecg-boot
* @Date: {{today}}
* @Version: V1.0
*/
public interface {{entityName}}Mapper extends BaseMapper<{{entityName}}> {
}
```
---
### A6. Mapper.xml
```xml
<?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="org.jeecg.modules.{{entityPackage}}.mapper.{{entityName}}Mapper">
</mapper>
```
---
### A7. API文件 (vue3 和 vue3Native 通用)
```typescript
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
list = '/{{entityPackagePath}}/{{entityName_uncap}}/list',
save = '/{{entityPackagePath}}/{{entityName_uncap}}/add',
edit = '/{{entityPackagePath}}/{{entityName_uncap}}/edit',
deleteOne = '/{{entityPackagePath}}/{{entityName_uncap}}/delete',
deleteBatch = '/{{entityPackagePath}}/{{entityName_uncap}}/deleteBatch',
importExcel = '/{{entityPackagePath}}/{{entityName_uncap}}/importExcel',
exportXls = '/{{entityPackagePath}}/{{entityName_uncap}}/exportXls',
}
/**
* 导出api
*/
export const getExportUrl = Api.exportXls;
/**
* 导入api
*/
export const getImportUrl = Api.importExcel;
/**
* 列表接口
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 删除单个
*/
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
/**
* 批量删除
* @param params
*/
export const batchDelete = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
/**
* 保存或者更新
* @param params
* @param isUpdate
*/
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params });
};
```
**vue3Native 风格差异:** `saveOrUpdate` 使用 `{ isTransformResponse: false }` 选项:
```typescript
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params }, { isTransformResponse: false });
};
```
---
### A8. Data文件 - vue3 封装风格
```typescript
import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table';
import { rules } from '/@/utils/helper/validator';
import { render } from '/@/utils/common/renderUtils';
// 列表列定义
export const columns: BasicColumn[] = [
// --- 普通字符串列 ---
// {
// title: '字段名称',
// align: 'center',
// dataIndex: 'fieldName',
// },
// --- 日期列截取前10位 ---
// {
// title: '日期',
// align: 'center',
// dataIndex: 'dateField',
// customRender: ({ text }) => {
// text = !text ? '' : (text.length > 10 ? text.substr(0, 10) : text);
// return text;
// },
// },
// --- 字典翻译列自动渲染_dictText后缀 ---
// {
// title: '状态',
// align: 'center',
// dataIndex: 'status_dictText',
// },
// --- Switch 列 ---
// {
// title: '是否启用',
// align: 'center',
// dataIndex: 'enabled',
// customRender: ({ text }) => {
// return render.renderSwitch(text, [{ text: '是', value: 'Y' }, { text: '否', value: 'N' }]);
// },
// },
// --- 图片列 ---
// {
// title: '图片',
// align: 'center',
// dataIndex: 'imageField',
// customRender: render.renderImage,
// },
// --- 分类树列 ---
// {
// title: '分类',
// align: 'center',
// dataIndex: 'categoryField',
// customRender: ({ text }) => {
// return render.renderCategoryTree(text, 'categoryDictCode');
// },
// },
];
// 查询表单 Schema
export const searchFormSchema: FormSchema[] = [
// --- 文本查询 ---
// {
// label: '名称',
// field: 'name',
// component: 'JInput',
// colProps: { span: 6 },
// },
// --- 字典下拉查询 ---
// {
// label: '状态',
// field: 'status',
// component: 'JDictSelectTag',
// componentProps: { dictCode: 'dict_code' },
// colProps: { span: 6 },
// },
// --- 日期范围查询 ---
// {
// label: '创建日期',
// field: 'createTime',
// component: 'RangePicker',
// componentProps: { showTime: true },
// colProps: { span: 6 },
// },
];
// 编辑表单 Schema
export const formSchema: FormSchema[] = [
// 隐藏ID
{
label: '',
field: 'id',
component: 'Input',
show: false,
},
// --- 文本输入 ---
// {
// label: '名称',
// field: 'name',
// required: true,
// component: 'Input',
// componentProps: { placeholder: '请输入名称' },
// },
// --- 数字输入 ---
// {
// label: '数量',
// field: 'quantity',
// component: 'InputNumber',
// componentProps: { placeholder: '请输入数量' },
// },
// --- 字典下拉 ---
// {
// label: '状态',
// field: 'status',
// component: 'JDictSelectTag',
// componentProps: { dictCode: 'dict_code', placeholder: '请选择状态' },
// },
// --- 关联表字典下拉 ---
// {
// label: '类型',
// field: 'type',
// component: 'JDictSelectTag',
// componentProps: { dictCode: 'tableName,textField,codeField', placeholder: '请选择类型' },
// },
// --- Switch ---
// {
// label: '是否启用',
// field: 'enabled',
// component: 'JSwitch',
// componentProps: { options: ['Y', 'N'] },
// },
// --- 日期选择 ---
// {
// label: '日期',
// field: 'dateField',
// component: 'DatePicker',
// componentProps: { showTime: false, valueFormat: 'YYYY-MM-DD', placeholder: '请选择日期' },
// },
// --- 日期时间选择 ---
// {
// label: '日期时间',
// field: 'datetimeField',
// component: 'DatePicker',
// componentProps: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss', placeholder: '请选择日期时间' },
// },
// --- 文本域 ---
// {
// label: '备注',
// field: 'remark',
// component: 'InputTextArea',
// componentProps: { placeholder: '请输入备注' },
// },
// --- 富文本编辑器 ---
// {
// label: '内容',
// field: 'content',
// component: 'JEditor',
// },
// --- 图片上传 ---
// {
// label: '图片',
// field: 'imageField',
// component: 'JImageUpload',
// },
// --- 文件上传 ---
// {
// label: '附件',
// field: 'fileField',
// component: 'JUpload',
// },
// --- 用户选择 ---
// {
// label: '负责人',
// field: 'userId',
// component: 'JSelectUserByDept',
// componentProps: { labelKey: 'realname' },
// },
// --- 部门选择 ---
// {
// label: '部门',
// field: 'deptId',
// component: 'JSelectDept',
// },
// --- 分类树选择 ---
// {
// label: '分类',
// field: 'categoryField',
// component: 'JCategorySelect',
// componentProps: { pcode: 'categoryDictCode' },
// },
// --- 搜索选择 ---
// {
// label: '搜索',
// field: 'searchField',
// component: 'JSearchSelect',
// componentProps: { dict: 'tableName,textField,codeField', placeholder: '请选择' },
// },
];
// 高级查询配置
export const superQuerySchema = {
// fieldName: { title: '字段名', order: 0, view: 'text' },
// status: { title: '状态', order: 1, view: 'list', dictCode: 'dict_code' },
// dateField: { title: '日期', order: 2, view: 'date' },
// datetimeField: { title: '日期时间', order: 3, view: 'datetime' },
// quantity: { title: '数量', order: 4, view: 'number' },
};
```
**高级查询 view 类型映射:**
- string → `text`
- int/double/BigDecimal → `number`
- date → `date`
- datetime → `datetime`
- 字典字段(list/radio/checkbox) → `list`, 带 `dictCode`
- 关联表字典 → `list_multi``sel_search`, 带 `dictTable/dictCode/dictText`
- switch → `radio`
- user_select → `sel_user`
- dept_select → `sel_depart`
---
### A9. List页面 - vue3 封装风格
```vue
<template>
<div>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" v-auth="'{{entityPackage}}:{{tableName}}:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
<a-button type="primary" v-auth="'{{entityPackage}}:{{tableName}}:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
<j-upload-button type="primary" v-auth="'{{entityPackage}}:{{tableName}}:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined" />
删除
</a-menu-item>
</a-menu>
</template>
<a-button v-auth="'{{entityPackage}}:{{tableName}}:deleteBatch'">批量操作
<Icon icon="mdi:chevron-down" />
</a-button>
</a-dropdown>
<!-- 高级查询 -->
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
<!--字段回显插槽-->
<template v-slot:bodyCell="{ column, record, index, text }">
<!-- 富文本回显 -->
<!-- <template v-if="column.dataIndex==='content'">
<div v-html="text"></div>
</template> -->
<!-- 文件下载 -->
<!-- <template v-if="column.dataIndex==='fileField'">
<span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download-outlined" size="small" @click="downloadFile(text)">下载</a-button>
</template> -->
</template>
</BasicTable>
<!-- 表单区域 -->
<{{entityName}}Modal @register="registerModal" @success="handleSuccess" />
</div>
</template>
<script lang="ts" name="{{entityPackage}}-{{entityName_uncap}}" setup>
import { ref, reactive } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import {{entityName}}Modal from './components/{{entityName}}Modal.vue';
import { columns, searchFormSchema, superQuerySchema } from './{{entityName}}.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './{{entityName}}.api';
import { downloadFile } from '/@/utils/common/renderUtils';
const queryParam = reactive<any>({});
const checkedKeys = ref<Array<string | number>>([]);
// 注册 modal
const [registerModal, { openModal }] = useModal();
// 注册 table
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '{{description}}',
api: list,
columns,
canResize: true,
formConfig: {
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: true,
fieldMapToNumber: [
// 数字/时间范围查询映射
// ['fieldName', ['fieldName_begin', 'fieldName_end']],
],
fieldMapToTime: [
// 日期范围查询映射
// ['dateField', ['dateField_begin', 'dateField_end'], 'YYYY-MM-DD'],
// ['datetimeField', ['datetimeField_begin', 'datetimeField_end'], 'YYYY-MM-DD HH:mm:ss'],
],
},
actionColumn: {
width: 120,
fixed: 'right',
},
beforeFetch: (params) => {
return Object.assign(params, queryParam);
},
},
exportConfig: {
name: '{{description}}',
url: getExportUrl,
params: queryParam,
},
importConfig: {
url: getImportUrl,
success: handleSuccess,
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
// 高级查询配置
const superQueryConfig = reactive(superQuerySchema);
/**
* 高级查询事件
*/
function handleSuperQuery(params) {
Object.keys(params).map((k) => {
queryParam[k] = params[k];
});
reload();
}
/**
* 新增事件
*/
function handleAdd() {
openModal(true, {
isUpdate: false,
showFooter: true,
});
}
/**
* 编辑事件
*/
function handleEdit(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
showFooter: true,
});
}
/**
* 详情
*/
function handleDetail(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
showFooter: false,
});
}
/**
* 删除事件
*/
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
/**
* 批量删除事件
*/
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
/**
* 成功回调
*/
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: '{{entityPackage}}:{{tableName}}:edit',
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
placement: 'topLeft',
},
auth: '{{entityPackage}}:{{tableName}}:delete',
},
];
}
</script>
<style lang="less" scoped>
:deep(.ant-picker-range) {
width: 100%;
}
</style>
```
---
### A10. Modal组件 - vue3 封装风格
```vue
<template>
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit">
<BasicForm @register="registerForm" name="{{entityName}}Form" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../{{entityName}}.data';
import { saveOrUpdate } from '../{{entityName}}.api';
// Emits声明
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const isDetail = ref(false);
// 表单配置
const [registerForm, { setProps, resetFields, setFieldsValue, validate, scrollToField }] = useForm({
// 单列布局用 labelWidth: 150
labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
// 多列布局用 baseColProps: { span: 12 } (双列) 或 { span: 8 } (三列)
baseColProps: { span: 24 },
});
// 表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
// 重置表单
await resetFields();
setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
isUpdate.value = !!data?.isUpdate;
isDetail.value = !!data?.showFooter;
if (unref(isUpdate)) {
// 表单赋值
await setFieldsValue({
...data.record,
});
}
// 隐藏底部时禁用整个表单
setProps({ disabled: !data?.showFooter });
});
// 设置标题
const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(isDetail) ? '详情' : '编辑'));
// 表单提交事件
async function handleSubmit(v) {
try {
let values = await validate();
setModalProps({ confirmLoading: true });
// 提交表单
await saveOrUpdate(values, isUpdate.value);
// 关闭弹窗
closeModal();
// 刷新列表
emit('success');
} catch ({ errorFields }) {
if (errorFields) {
const firstField = errorFields[0];
if (firstField) {
scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
}
}
return Promise.reject(errorFields);
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
<style lang="less" scoped>
:deep(.ant-input-number) {
width: 100%;
}
:deep(.ant-calendar-picker) {
width: 100%;
}
</style>
```
**宽度规则:**
- 单列表单(fieldRowNum=1): width=800, baseColProps={span:24}
- 双列表单(fieldRowNum=2): width=1000, baseColProps={span:12}
- 三列表单(fieldRowNum=3): width=1200, baseColProps={span:8}
- 四列表单(fieldRowNum=4): width=1280, baseColProps={span:6}
---
### A8N. Data文件 - vue3Native 原生风格
vue3Native 的 data.ts 只包含 columns 和 superQuerySchema不包含 formSchema表单在模板中直接写控件
```typescript
import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table';
import { rules } from '/@/utils/helper/validator';
import { render } from '/@/utils/common/renderUtils';
// 列表列定义(与 vue3 封装风格完全相同)
export const columns: BasicColumn[] = [
// ... 同 A8 的 columns
];
// 高级查询配置(与 vue3 封装风格完全相同)
export const superQuerySchema = {
// ... 同 A8 的 superQuerySchema
};
```
---
### A9N. List页面 - vue3Native 原生风格
```vue
<template>
<div>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" v-auth="'{{entityPackage}}:{{tableName}}:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
<a-button type="primary" v-auth="'{{entityPackage}}:{{tableName}}:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
<j-upload-button type="primary" v-auth="'{{entityPackage}}:{{tableName}}:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined" />
删除
</a-menu-item>
</a-menu>
</template>
<a-button v-auth="'{{entityPackage}}:{{tableName}}:deleteBatch'">批量操作
<Icon icon="mdi:chevron-down" />
</a-button>
</a-dropdown>
<!-- 高级查询 -->
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
<!--字段回显插槽 vue3 封装风格-->
<template v-slot:bodyCell="{ column, record, index, text }">
</template>
</BasicTable>
<!-- 表单区域 -->
<{{entityName}}Modal ref="registerModal" @success="handleSuccess" />
</div>
</template>
<script lang="ts" name="{{entityPackage}}-{{entityName_uncap}}" setup>
import { ref, reactive } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import {{entityName}}Modal from './components/{{entityName}}Modal.vue';
import { columns, superQuerySchema } from './{{entityName}}.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './{{entityName}}.api';
import { downloadFile } from '/@/utils/common/renderUtils';
const queryParam = reactive<any>({});
const registerModal = ref();
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '{{description}}',
api: list,
columns,
canResize: true,
useSearchForm: false,
actionColumn: {
width: 120,
fixed: 'right',
},
beforeFetch: (params) => {
return Object.assign(params, queryParam);
},
},
exportConfig: {
name: '{{description}}',
url: getExportUrl,
params: queryParam,
},
importConfig: {
url: getImportUrl,
success: handleSuccess,
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
// 高级查询配置
const superQueryConfig = reactive(superQuerySchema);
function handleSuperQuery(params) {
Object.keys(params).map((k) => {
queryParam[k] = params[k];
});
reload();
}
function handleAdd() {
registerModal.value.disableSubmit = false;
registerModal.value.add();
}
function handleEdit(record: Recordable) {
registerModal.value.disableSubmit = false;
registerModal.value.edit(record);
}
function handleDetail(record: Recordable) {
registerModal.value.disableSubmit = true;
registerModal.value.edit(record);
}
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: '{{entityPackage}}:{{tableName}}:edit',
},
];
}
function getDropDownAction(record) {
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
placement: 'topLeft',
},
auth: '{{entityPackage}}:{{tableName}}:delete',
},
];
}
</script>
<style lang="less" scoped>
:deep(.ant-picker-range) {
width: 100%;
}
</style>
```
---
### A10N. Modal组件 - vue3Native 原生风格
```vue
<template>
<j-modal :title="title" :width="800" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
<{{entityName}}Form ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false" />
<template #footer>
<a-button @click="handleCancel">取消</a-button>
<a-button :class="{ 'jee-hidden': disableSubmit }" type="primary" @click="handleOk">确认</a-button>
</template>
</j-modal>
</template>
<script lang="ts" setup>
import { ref, nextTick, defineExpose } from 'vue';
import {{entityName}}Form from './{{entityName}}Form.vue';
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
const title = ref<string>('');
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();
const emit = defineEmits(['register', 'success']);
/**
* 新增
*/
function add() {
title.value = '新增';
visible.value = true;
nextTick(() => {
registerForm.value.add();
});
}
/**
* 编辑
*/
function edit(record) {
title.value = disableSubmit.value ? '详情' : '编辑';
visible.value = true;
nextTick(() => {
registerForm.value.edit(record);
});
}
/**
* 确定按钮点击事件
*/
function handleOk() {
registerForm.value.submitForm();
}
/**
* form保存回调事件
*/
function submitCallback() {
handleCancel();
emit('success');
}
/**
* 取消按钮回调事件
*/
function handleCancel() {
visible.value = false;
}
defineExpose({
add,
edit,
disableSubmit,
});
</script>
<style lang="less">
.jee-hidden {
display: none !important;
}
</style>
```
---
### A11N. Form组件 - vue3Native 原生风格
```vue
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer :disabled="disabled">
<template #detail>
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol" name="{{entityName}}Form">
<a-row>
<!-- 每个字段生成一个 a-col + a-form-item -->
<!-- === 文本输入 === -->
<!-- <a-col :span="24">
<a-form-item label="名称" v-bind="validateInfos.name" id="{{entityName}}Form-name" name="name">
<a-input v-model:value="formData.name" placeholder="请输入名称" allow-clear />
</a-form-item>
</a-col> -->
<!-- === 数字输入 === -->
<!-- <a-col :span="24">
<a-form-item label="数量" v-bind="validateInfos.quantity" id="{{entityName}}Form-quantity" name="quantity">
<a-input-number v-model:value="formData.quantity" placeholder="请输入数量" style="width: 100%" />
</a-form-item>
</a-col> -->
<!-- === 字典下拉 === -->
<!-- <a-col :span="24">
<a-form-item label="状态" v-bind="validateInfos.status" id="{{entityName}}Form-status" name="status">
<JDictSelectTag v-model:value="formData.status" dictCode="dict_code" placeholder="请选择状态" />
</a-form-item>
</a-col> -->
<!-- === Switch === -->
<!-- <a-col :span="24">
<a-form-item label="是否启用" v-bind="validateInfos.enabled" id="{{entityName}}Form-enabled" name="enabled">
<a-switch v-model:checked="formData.enabled" checkedValue="Y" unCheckedValue="N" />
</a-form-item>
</a-col> -->
<!-- === 日期选择 === -->
<!-- <a-col :span="24">
<a-form-item label="日期" v-bind="validateInfos.dateField" id="{{entityName}}Form-dateField" name="dateField">
<a-date-picker v-model:value="formData.dateField" placeholder="请选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
</a-form-item>
</a-col> -->
<!-- === 日期时间选择 === -->
<!-- <a-col :span="24">
<a-form-item label="日期时间" v-bind="validateInfos.datetimeField" id="{{entityName}}Form-datetimeField" name="datetimeField">
<a-date-picker v-model:value="formData.datetimeField" placeholder="请选择日期时间" :showTime="true" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
</a-form-item>
</a-col> -->
<!-- === 文本域 === -->
<!-- <a-col :span="24">
<a-form-item label="备注" v-bind="validateInfos.remark" id="{{entityName}}Form-remark" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入备注" :rows="4" />
</a-form-item>
</a-col> -->
<!-- === 图片上传 === -->
<!-- <a-col :span="24">
<a-form-item label="图片" v-bind="validateInfos.imageField" id="{{entityName}}Form-imageField" name="imageField">
<JImageUpload v-model:value="formData.imageField" />
</a-form-item>
</a-col> -->
<!-- === 文件上传 === -->
<!-- <a-col :span="24">
<a-form-item label="附件" v-bind="validateInfos.fileField" id="{{entityName}}Form-fileField" name="fileField">
<JUpload v-model:value="formData.fileField" />
</a-form-item>
</a-col> -->
<!-- === 富文本 === -->
<!-- <a-col :span="24">
<a-form-item label="内容" v-bind="validateInfos.content" id="{{entityName}}Form-content" name="content">
<JEditor v-model:value="formData.content" />
</a-form-item>
</a-col> -->
<!-- === 用户选择 === -->
<!-- <a-col :span="24">
<a-form-item label="负责人" v-bind="validateInfos.userId" id="{{entityName}}Form-userId" name="userId">
<JSelectUserByDept v-model:value="formData.userId" />
</a-form-item>
</a-col> -->
<!-- === 部门选择 === -->
<!-- <a-col :span="24">
<a-form-item label="部门" v-bind="validateInfos.deptId" id="{{entityName}}Form-deptId" name="deptId">
<JSelectDept v-model:value="formData.deptId" />
</a-form-item>
</a-col> -->
<!-- === 搜索选择 === -->
<!-- <a-col :span="24">
<a-form-item label="搜索" v-bind="validateInfos.searchField" id="{{entityName}}Form-searchField" name="searchField">
<JSearchSelect v-model:value="formData.searchField" dict="tableName,textField,codeField" placeholder="请选择" />
</a-form-item>
</a-col> -->
</a-row>
</a-form>
</template>
</JFormContainer>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick, defineProps, computed } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { getValueType } from '/@/utils';
import { saveOrUpdate } from '../{{entityName}}.api';
import { Form } from 'ant-design-vue';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
// 按需导入组件(根据实际用到的字段类型)
// import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
// import JSearchSelect from '/@/components/Form/src/jeecg/components/JSearchSelect.vue';
// import JImageUpload from '/@/components/Form/src/jeecg/components/JImageUpload.vue';
// import JUpload from '/@/components/Form/src/jeecg/components/JUpload.vue';
// import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
// import JSelectUserByDept from '/@/components/Form/src/jeecg/components/JSelectUserByDept.vue';
// import JSelectDept from '/@/components/Form/src/jeecg/components/JSelectDept.vue';
const props = defineProps({
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: () => ({}) },
formBpm: { type: Boolean, default: true },
});
const formRef = ref();
const useForm = Form.useForm;
const emit = defineEmits(['register', 'ok']);
const formData = reactive<Record<string, any>>({
id: '',
// 业务字段初始值
// name: '',
// status: '',
// ... 所有字段默认值
});
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
// 表单验证规则
const validatorRules = reactive({
// name: [{ required: true, message: '请输入名称!' }],
// ... 必填字段的验证规则
});
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: false });
// 表单禁用
const disabled = computed(() => {
if (props.formBpm === true) {
if (props.formData.disabled === false) {
return false;
} else {
return true;
}
}
return props.formDisabled;
});
/**
* 新增
*/
function add() {
edit({});
}
/**
* 编辑
*/
function edit(record) {
nextTick(() => {
resetFields();
const tmpData = {};
Object.keys(formData).forEach((key) => {
if (record.hasOwnProperty(key)) {
tmpData[key] = record[key];
}
});
// 赋值
Object.assign(formData, tmpData);
});
}
/**
* 提交数据
*/
async function submitForm() {
try {
await validate();
} catch ({ errorFields }) {
if (errorFields) {
const firstField = errorFields[0];
if (firstField) {
formRef.value.scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
}
}
return Promise.reject(errorFields);
}
confirmLoading.value = true;
const isUpdate = ref<boolean>(false);
let model = formData;
if (model.id) {
isUpdate.value = true;
}
// 处理数组类型字段(多选等)
for (let data in model) {
if (model[data] instanceof Array) {
let valueType = getValueType(formRef.value.getProps, data);
if (valueType === 'string') {
model[data] = model[data].join(',');
}
}
}
await saveOrUpdate(model, isUpdate.value)
.then((res) => {
if (res.success) {
createMessage.success(res.message);
emit('ok');
} else {
createMessage.warning(res.message);
}
})
.finally(() => {
confirmLoading.value = false;
});
}
defineExpose({
add,
edit,
submitForm,
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
</style>
```
**vue3Native 多列布局:**
- 单列: `<a-col :span="24">`
- 双列: `<a-col :span="12">`
- 三列: `<a-col :span="8">`
---
### A12. 菜单权限 SQL
```sql
-- 注意:该页面对应的前台目录为 views/{{viewDir}} 文件夹下
-- 如果你想更改到其他目录请修改sql中component字段对应的值
-- 主菜单
INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external)
VALUES ('{{timestamp}}01', NULL, '{{description}}', '/{{entityPackagePath}}/{{entityName_uncap}}List', '{{viewDir}}/{{entityName}}List', NULL, NULL, 0, NULL, '1', 0.00, 0, NULL, 1, 0, 0, 0, 0, NULL, '1', 0, 0, 'admin', '{{today}} 00:00:00', NULL, NULL, 0);
-- 新增
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('{{timestamp}}02', '{{timestamp}}01', '添加{{description}}', NULL, NULL, 0, NULL, NULL, 2, '{{entityPackage}}:{{tableName}}:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '{{today}} 00:00:00', NULL, NULL, 0, 0, '1', 0);
-- 编辑
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('{{timestamp}}03', '{{timestamp}}01', '编辑{{description}}', NULL, NULL, 0, NULL, NULL, 2, '{{entityPackage}}:{{tableName}}:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '{{today}} 00:00:00', NULL, NULL, 0, 0, '1', 0);
-- 删除
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('{{timestamp}}04', '{{timestamp}}01', '删除{{description}}', NULL, NULL, 0, NULL, NULL, 2, '{{entityPackage}}:{{tableName}}:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '{{today}} 00:00:00', NULL, NULL, 0, 0, '1', 0);
-- 批量删除
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('{{timestamp}}05', '{{timestamp}}01', '批量删除{{description}}', NULL, NULL, 0, NULL, NULL, 2, '{{entityPackage}}:{{tableName}}:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '{{today}} 00:00:00', NULL, NULL, 0, 0, '1', 0);
-- 导出excel
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('{{timestamp}}06', '{{timestamp}}01', '导出excel_{{description}}', NULL, NULL, 0, NULL, NULL, 2, '{{entityPackage}}:{{tableName}}:exportXls', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '{{today}} 00:00:00', NULL, NULL, 0, 0, '1', 0);
-- 导入excel
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('{{timestamp}}07', '{{timestamp}}01', '导入excel_{{description}}', NULL, NULL, 0, NULL, NULL, 2, '{{entityPackage}}:{{tableName}}:importExcel', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '{{today}} 00:00:00', NULL, NULL, 0, 0, '1', 0);
-- 角色授权admin角色
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('{{timestamp}}08', 'f6817f48af4fb3af11b9e8bf182f618b', '{{timestamp}}01', NULL, '{{today}} 00:00:00', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('{{timestamp}}09', 'f6817f48af4fb3af11b9e8bf182f618b', '{{timestamp}}02', NULL, '{{today}} 00:00:00', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('{{timestamp}}10', 'f6817f48af4fb3af11b9e8bf182f618b', '{{timestamp}}03', NULL, '{{today}} 00:00:00', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('{{timestamp}}11', 'f6817f48af4fb3af11b9e8bf182f618b', '{{timestamp}}04', NULL, '{{today}} 00:00:00', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('{{timestamp}}12', 'f6817f48af4fb3af11b9e8bf182f618b', '{{timestamp}}05', NULL, '{{today}} 00:00:00', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('{{timestamp}}13', 'f6817f48af4fb3af11b9e8bf182f618b', '{{timestamp}}06', NULL, '{{today}} 00:00:00', '127.0.0.1');
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('{{timestamp}}14', 'f6817f48af4fb3af11b9e8bf182f618b', '{{timestamp}}07', NULL, '{{today}} 00:00:00', '127.0.0.1');
```
---
## B. 树表模式差异
树表在单表基础上有以下差异:
### B1. Entity 额外字段
```java
/**父级节点*/
@Excel(name = "父级节点", width = 15)
@Schema(description = "父级节点")
private String pid;
/**是否有子节点*/
@Excel(name = "是否有子节点", width = 15, dicCode = "yn")
@Dict(dicCode = "yn")
@TableField(value = "has_child")
@Schema(description = "是否有子节点")
private String hasChild;
```
### B2. Mapper 额外方法
```java
public interface {{entityName}}Mapper extends BaseMapper<{{entityName}}> {
/**
* 编辑节点状态
*/
@Update("update {{tableName}} set has_child=#{hasChild} where id = #{pid}")
void updateTreeNodeStatus(@Param("pid") String pid, @Param("hasChild") String hasChild);
/**
* 根据pid查询子节点
*/
@Select("select * from {{tableName}} where pid = #{parentId} ${installCondition}")
List<{{entityName}}> queryListByPid(@Param("parentId") String parentId, @Param("installCondition") String installCondition);
}
```
### B3. Service 接口额外方法
```java
public interface I{{entityName}}Service extends IService<{{entityName}}> {
public static final String ROOT_PID_VALUE = "0";
public static final String HASCHILD = "1";
public static final String NOCHILD = "0";
void add{{entityName}}({{entityName}} entity);
void update{{entityName}}({{entityName}} entity);
void delete{{entityName}}(String id) throws JeecgBootException;
List<{{entityName}}> queryTreeListNoPage(QueryWrapper<{{entityName}}> queryWrapper);
List<{{entityName}}> queryListByCode(String code);
List<{{entityName}}> queryListByPid(String pid);
}
```
### B4. ServiceImpl 核心逻辑
```java
@Service
public class {{entityName}}ServiceImpl extends ServiceImpl<{{entityName}}Mapper, {{entityName}}> implements I{{entityName}}Service {
@Override
public void add{{entityName}}({{entityName}} entity) {
if (oConvertUtils.isEmpty(entity.getPid())) {
entity.setPid(I{{entityName}}Service.ROOT_PID_VALUE);
} else {
// 如果当前节点父ID不为空 则设置父节点的hasChild为1
{{entityName}} parent = baseMapper.selectById(entity.getPid());
if (parent != null && !I{{entityName}}Service.HASCHILD.equals(parent.getHasChild())) {
parent.setHasChild(I{{entityName}}Service.HASCHILD);
baseMapper.updateById(parent);
}
}
baseMapper.insert(entity);
}
@Override
public void update{{entityName}}({{entityName}} entity) {
{{entityName}} old = baseMapper.selectById(entity.getId());
if (old != null && !old.getPid().equals(entity.getPid())) {
// 更新新父节点状态
updateOldParentNode(entity.getPid());
// 更新旧父节点状态(检查是否还有子节点)
int childCount = baseMapper.selectCount(new QueryWrapper<{{entityName}}>().eq("pid", old.getPid())).intValue();
if (childCount == 1) {
// 当前是唯一子节点,更新旧父为无子节点
if (!I{{entityName}}Service.ROOT_PID_VALUE.equals(old.getPid())) {
baseMapper.updateTreeNodeStatus(old.getPid(), I{{entityName}}Service.NOCHILD);
}
}
}
baseMapper.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete{{entityName}}(String id) throws JeecgBootException {
// 查询选中节点下所有子节点一起删除
id = TreeUtils.getTreeChildIds(id, (pid) -> baseMapper.queryListByPid(pid, null));
if (id.indexOf(",") > 0) {
baseMapper.deleteBatchIds(Arrays.asList(id.split(",")));
} else {
baseMapper.deleteById(id);
}
// 更新父节点状态
{{entityName}} entity = baseMapper.selectById(id.split(",")[0]);
if (entity != null) {
updateOldParentNode(entity.getPid());
}
}
private void updateOldParentNode(String pid) {
if (!I{{entityName}}Service.ROOT_PID_VALUE.equals(pid)) {
{{entityName}} parent = baseMapper.selectById(pid);
if (parent != null) {
parent.setHasChild(I{{entityName}}Service.HASCHILD);
baseMapper.updateById(parent);
}
}
}
}
```
### B5. Controller 额外端点
```java
/**
* 查询根节点数据
*/
@GetMapping(value = "/rootList")
public Result<IPage<{{entityName}}>> rootList({{entityName}} entity,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
// 判断有没有查询条件,如果有则按条件查全部(不分树层级)
String hasQuery = req.getParameter("hasQuery");
if (oConvertUtils.isNotEmpty(hasQuery) && "true".equals(hasQuery)) {
QueryWrapper<{{entityName}}> queryWrapper = QueryGenerator.initQueryWrapper(entity, req.getParameterMap());
Page<{{entityName}}> page = new Page<>(pageNo, pageSize);
IPage<{{entityName}}> pageList = {{entityName_uncap}}Service.page(page, queryWrapper);
return Result.OK(pageList);
}
// 无查询条件则查根节点
QueryWrapper<{{entityName}}> queryWrapper = QueryGenerator.initQueryWrapper(entity, req.getParameterMap());
queryWrapper.eq("pid", I{{entityName}}Service.ROOT_PID_VALUE);
Page<{{entityName}}> page = new Page<>(pageNo, pageSize);
IPage<{{entityName}}> pageList = {{entityName_uncap}}Service.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 查询子节点
*/
@GetMapping(value = "/childList")
public Result<List<{{entityName}}>> childList({{entityName}} entity,
HttpServletRequest req) {
QueryWrapper<{{entityName}}> queryWrapper = QueryGenerator.initQueryWrapper(entity, req.getParameterMap());
List<{{entityName}}> list = {{entityName_uncap}}Service.list(queryWrapper);
return Result.OK(list);
}
/**
* 批量查询子节点
*/
@GetMapping(value = "/getChildListBatch")
public Result getChildListBatch(@RequestParam("parentIds") String parentIds) {
List<{{entityName}}> list = {{entityName_uncap}}Service.queryListByPid(parentIds);
IPage<{{entityName}}> pageList = new Page<>(1, 10, list.size());
pageList.setRecords(list);
return Result.OK(pageList);
}
```
### B6. 树表前端 API 额外方法
```typescript
enum Api {
list = '/{{entityPackagePath}}/{{entityName_uncap}}/rootList', // 注意是 rootList
// ... 其他同单表
loadTreeData = '/{{entityPackagePath}}/{{entityName_uncap}}/rootList',
getChildList = '/{{entityPackagePath}}/{{entityName_uncap}}/childList',
getChildListBatch = '/{{entityPackagePath}}/{{entityName_uncap}}/getChildListBatch',
}
// 额外导出
export const loadTreeData = (params) => defHttp.get({ url: Api.loadTreeData, params });
export const getChildList = (params) => defHttp.get({ url: Api.getChildList, params });
export const getChildListBatch = (params) => defHttp.get({ url: Api.getChildListBatch, params }, { isTransformResponse: false });
```
### B7. 树表前端 List 页面差异
vue3 封装风格中 `useListPage``tableProps` 增加:
```typescript
tableProps: {
// ... 同单表
isTreeTable: true, // 标记为树表
// list API 改为 rootList
}
```
模板中增加展开/加载子节点逻辑(参考 `src/views/system/category/index.vue`)。
---
## C. 一对多模式差异
一对多在单表基础上有以下差异:
### C1. 子表 Entity
每个子表生成独立的 Entity包含外键字段
```java
/**主表ID外键*/
@Schema(description = "主表ID")
private String {{mainEntityName_uncap}}Id; // 外键字段名默认为 主表实体名(camelCase) + Id
```
注意:子表 Entity 的外键字段不加 `@Excel` 注解(导出时忽略)。
### C2. 子表 Mapper
```java
public interface {{subEntityName}}Mapper extends BaseMapper<{{subEntityName}}> {
/**
* 通过主表id删除子表数据
*/
@Delete("DELETE FROM {{subTableName}} WHERE {{foreignKey}} = #{mainId}")
boolean deleteByMainId(@Param("mainId") String mainId);
/**
* 通过主表id查询子表数据
*/
List<{{subEntityName}}> selectByMainId(@Param("mainId") String mainId);
}
```
### C3. 主表 Service 接口
```java
public interface I{{entityName}}Service extends IService<{{entityName}}> {
/**
* 添加一对多
*/
public void saveMain({{entityName}} entity, List<{{subEntityName}}> subList);
/**
* 修改一对多
*/
public void updateMain({{entityName}} entity, List<{{subEntityName}}> subList);
/**
* 删除一对多
*/
public void delMain(String id);
/**
* 批量删除一对多
*/
public void delBatchMain(Collection<? extends Serializable> idList);
}
```
### C4. 主表 ServiceImpl
```java
@Service
public class {{entityName}}ServiceImpl extends ServiceImpl<{{entityName}}Mapper, {{entityName}}> implements I{{entityName}}Service {
@Autowired
private {{subEntityName}}Mapper {{subEntityName_uncap}}Mapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMain({{entityName}} entity, List<{{subEntityName}}> subList) {
baseMapper.insert(entity);
if (subList != null && subList.size() > 0) {
for ({{subEntityName}} sub : subList) {
sub.set{{mainEntityName}}Id(entity.getId()); // 设置外键
{{subEntityName_uncap}}Mapper.insert(sub);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateMain({{entityName}} entity, List<{{subEntityName}}> subList) {
baseMapper.updateById(entity);
// 先删后增
{{subEntityName_uncap}}Mapper.deleteByMainId(entity.getId());
if (subList != null && subList.size() > 0) {
for ({{subEntityName}} sub : subList) {
sub.set{{mainEntityName}}Id(entity.getId());
{{subEntityName_uncap}}Mapper.insert(sub);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delMain(String id) {
{{subEntityName_uncap}}Mapper.deleteByMainId(id);
baseMapper.deleteById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delBatchMain(Collection<? extends Serializable> idList) {
for (Serializable id : idList) {
{{subEntityName_uncap}}Mapper.deleteByMainId(id.toString());
baseMapper.deleteById(id);
}
}
}
```
### C5. Page VOExcel导入导出用
```java
package org.jeecg.modules.{{entityPackage}}.vo;
import org.jeecg.modules.{{entityPackage}}.entity.{{entityName}};
import org.jeecg.modules.{{entityPackage}}.entity.{{subEntityName}};
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.ExcelCollection;
import java.util.List;
@Data
public class {{entityName}}Page {
// 主表字段(同 Entity不含系统字段
// ...
@ExcelCollection(name = "{{subDescription}}")
private List<{{subEntityName}}> {{subEntityName_uncap}}List;
}
```
### C6. Controller 额外端点
```java
/**
* 查询子表数据
*/
@GetMapping(value = "/query{{subEntityName}}ByMainId")
public Result<List<{{subEntityName}}>> query{{subEntityName}}ByMainId(@RequestParam(name = "id", required = true) String id) {
List<{{subEntityName}}> list = {{subEntityName_uncap}}Mapper.selectByMainId(id);
return Result.OK(list);
}
```
### C7. 前端差异
主要差异在于编辑 Modal/Form 中包含子表的 Tab 页或内嵌表格:
- 主表字段 + 子表 Tab使用 `a-tabs` 切换)
- 子表使用 `JEditableTable``BasicTable` 展示和编辑
- 保存时收集主表 + 子表数据一起提交
---
## D. 字段类型完整映射速查表
| 业务语义 | DB列类型 | Java类型 | @Excel format | vue3 FormSchema component | vue3Native 控件 | 查询组件 |
|----------|---------|----------|---------------|--------------------------|----------------|---------|
| 名称/编码/标题 | varchar(100) | String | - | Input | a-input | JInput |
| 金额/价格 | decimal(10,2) | BigDecimal | - | InputNumber | a-input-number | InputNumber |
| 整数/数量 | int(11) | Integer | - | InputNumber | a-input-number | InputNumber |
| 浮点数 | double | Double | - | InputNumber | a-input-number | InputNumber |
| 状态/类型(字典) | varchar(10) | String | dicCode | JDictSelectTag | JDictSelectTag | JDictSelectTag |
| 单选(字典) | varchar(10) | String | dicCode | JDictSelectTag(type=radio) | a-radio-group | JDictSelectTag |
| 多选(字典) | varchar(200) | String | dicCode | JDictSelectTag(type=checkbox) | a-checkbox-group | - |
| 开关/是否 | varchar(2) | String | - | JSwitch | a-switch | - |
| 日期 | date | Date | yyyy-MM-dd | DatePicker | a-date-picker | DatePicker |
| 日期时间 | datetime | Date | yyyy-MM-dd HH:mm:ss | DatePicker(showTime) | a-date-picker(showTime) | DatePicker(showTime) |
| 长文本/备注 | text | String | - | InputTextArea | a-textarea | - |
| 富文本 | text | String | - | JEditor | JEditor | - |
| Markdown | text | String | - | JMarkdownEditor | JMarkdownEditor | - |
| 图片 | varchar(1000) | String | - | JImageUpload | JImageUpload | - |
| 文件/附件 | varchar(1000) | String | - | JUpload | JUpload | - |
| 用户选择 | varchar(32) | String | dictTable=sys_user | JSelectUserByDept | JSelectUserByDept | - |
| 部门选择 | varchar(32) | String | dictTable=sys_depart | JSelectDept | JSelectDept | - |
| 分类树 | varchar(64) | String | - | JCategorySelect | JCategorySelect | JCategorySelect |
| 搜索选择 | varchar(32) | String | dictTable | JSearchSelect | JSearchSelect | JSearchSelect |
| 省市区 | varchar(200) | String | - | JAreaLinkage | JAreaLinkage | - |
| 排序号 | int(11) | Integer | - | InputNumber | a-input-number | - |
## E. 建表 DDL 模板(如需要自动建表)
```sql
CREATE TABLE `{{tableName}}` (
`id` varchar(36) NOT NULL COMMENT '主键',
-- 业务字段
-- `field_name` varchar(100) DEFAULT NULL COMMENT '字段注释',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建日期',
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新日期',
`sys_org_code` varchar(64) DEFAULT NULL COMMENT '所属部门',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{{description}}';
```
树表额外字段:
```sql
`pid` varchar(36) DEFAULT NULL COMMENT '父级节点',
`has_child` varchar(3) DEFAULT NULL COMMENT '是否有子节点',
```
子表额外字段:
```sql
`{{main_table_name}}_id` varchar(36) DEFAULT NULL COMMENT '主表外键',
```
---
## F. 增量字段修改(加字段/删字段/改字段)
### F1. 定位已有代码文件
增量修改时,必须先找到并读取所有相关文件:
```
后端文件(在后端根目录搜索):
- **/entity/{EntityName}.java → 实体类
- **/controller/{EntityName}Controller.java → 控制器(通常不需要改)
- **/service/I{EntityName}Service.java → Service接口通常不需要改
- **/service/impl/{EntityName}ServiceImpl.java → Service实现通常不需要改
前端文件(在前端 src/views/ 下搜索):
- **/{EntityName}.data.ts → 列定义 + 表单Schema
- **/{EntityName}List.vue → 列表页(通常不需要改)
- **/{EntityName}Modal.vue → 弹窗(通常不需要改)
- **/{EntityName}Form.vue → 表单vue3Native风格需要改
```
### F2. 加字段 — 需要修改的位置
**每加一个字段,需要修改以下文件:**
#### 1) Entity.java — 在业务字段区域末尾追加
根据字段类型选择对应的注解模式(参考 A1 节的业务字段模板):
```java
// String 字段
@Excel(name = "字段注释", width = 15)
@Schema(description = "字段注释")
private String fieldName;
// 带字典的 String 字段
@Excel(name = "字段注释", width = 15, dicCode = "dict_code")
@Dict(dicCode = "dict_code")
@Schema(description = "字段注释")
private String fieldName;
// Integer 字段
@Excel(name = "字段注释", width = 15)
@Schema(description = "字段注释")
private Integer fieldName;
// BigDecimal 字段(需确认 import java.math.BigDecimal 已存在)
@Excel(name = "字段注释", width = 15)
@Schema(description = "字段注释")
private BigDecimal fieldName;
// Date 字段(需确认 import java.util.Date + JsonFormat + DateTimeFormat 已导入)
@Excel(name = "字段注释", width = 15, format = "yyyy-MM-dd")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "字段注释")
private Date fieldName;
// DateTime 字段
@Excel(name = "字段注释", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "字段注释")
private Date fieldName;
```
**注意:** 检查是否需要新增 import 语句(如 BigDecimal、Date、JsonFormat、Dict 等)。
#### 2) *.data.ts — 三处追加
**a) columns 数组末尾追加列定义:**
```typescript
// 普通列
{
title: '字段名称',
align: 'center',
dataIndex: 'fieldName',
},
// 字典列dataIndex 加 _dictText 后缀)
{
title: '状态',
align: 'center',
dataIndex: 'status_dictText',
},
// 图片列
{
title: '图片',
align: 'center',
dataIndex: 'imageField',
customRender: render.renderImage,
},
```
**b) searchFormSchema 数组追加查询条件(仅常用查询字段需要):**
```typescript
{
label: '字段名称',
field: 'fieldName',
component: 'JInput', // 或 JDictSelectTag 等
colProps: { span: 6 },
},
```
**c) formSchema 数组末尾追加(在最后一个字段 `}` 后、`];` 前):**
```typescript
{
label: '字段名称',
field: 'fieldName',
component: 'Input', // 根据字段类型选择组件
componentProps: { placeholder: '请输入字段名称' },
},
```
**d) superQuerySchema 数组追加(如果存在):**
```typescript
{ title: '字段名称', value: 'fieldName', type: 'string' },
```
#### 3) *Form.vue — 仅 vue3Native 风格需要修改
`<a-form>` 中追加表单项:
```vue
<a-form-item label="字段名称" v-bind="validatorRules.fieldName" name="fieldName">
<a-input v-model:value="formData.fieldName" placeholder="请输入字段名称" />
</a-form-item>
```
`formData` reactive 对象中追加初始值:
```typescript
fieldName: '',
```
#### 4) Flyway SQL — 生成 ALTER TABLE
```sql
ALTER TABLE `{{tableName}}` ADD COLUMN `column_name` varchar(100) DEFAULT NULL COMMENT '字段注释';
```
多个字段可合并为一条 ALTER
```sql
ALTER TABLE `{{tableName}}`
ADD COLUMN `field1` varchar(100) DEFAULT NULL COMMENT '注释1',
ADD COLUMN `field2` int DEFAULT NULL COMMENT '注释2';
```
### F3. 删字段 — 需要修改的位置
**从以下位置移除字段相关代码:**
1. **Entity.java** — 删除字段声明及其注解(@Excel@Dict@Schema@JsonFormat 等)
2. ***.data.ts** — 删除 columns 中对应列、searchFormSchema 中对应项、formSchema 中对应项、superQuerySchema 中对应项
3. ***Form.vue**vue3Native— 删除 `<a-form-item>` 和 formData 中对应属性
4. **Flyway SQL** — 生成 `ALTER TABLE \`{{tableName}}\` DROP COLUMN \`column_name\`;`
**注意:** 删除 Entity 字段后检查是否有不再使用的 import如删除了唯一的 BigDecimal 字段,则移除 BigDecimal import
### F4. 改字段 — 需要修改的位置
根据修改内容,可能需要改动:
- **改类型**Entity 字段类型 + data.ts 组件类型 + Form.vue 控件 + ALTER TABLE MODIFY
- **改注释/标题**Entity @Excel name + @Schema description + data.ts title/label
- **加/改字典**Entity @Dict + data.ts 组件改为 JDictSelectTag + columns dataIndex 加 _dictText
- **改必填**data.ts formSchema 中 required 属性
Flyway SQL 示例:
```sql
ALTER TABLE `{{tableName}}` MODIFY COLUMN `column_name` decimal(10,2) DEFAULT NULL COMMENT '新注释';
```
### F5. 增量修改检查清单
每次增量修改完成后,确认:
- [ ] Entity.java — 字段声明 + 注解 + import
- [ ] *.data.ts — columns + searchFormSchema(如需) + formSchema + superQuerySchema(如存在)
- [ ] *Form.vue — 表单控件 + formData 初始值(仅 vue3Native
- [ ] Flyway SQL — ALTER TABLE 语句
- [ ] 无遗漏的 import 增删