Token导航 LogoToken导航TokenDH.com
研究检索需要联网github未标认证来源可访问许可证需确认审计异常

jeecg-codegenjeecg 代码生成器

Agent Skill

jeecg-codegen 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

612

周安装

25

GitHub Stars

82

下载量

196
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:jeecg-codegen(jeecg 代码生成器)
来源仓库:https://github.com/jeecgboot/skills
仓库路径:skills/jeecg-codegen
安装命令:
npx skills add https://github.com/jeecgboot/skills --skill jeecg-codegen
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/jeecgboot/skills --skill jeecg-codegen

简介

jeecg-codegen 用于查找、检索和筛选相关信息。

  • 适合在 Codex、Claude、Cursor、Gemini CLI 中根据关键词、任务场景或来源线索快速定位候选结果。
  • 通过 npx skills add 命令从指定 GitHub 仓库安装并使用。
  • 安装前需确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写操作。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

JeecgBoot 代码生成器

将自然语言需求转换为 JeecgBoot 全套 CRUD 代码(后端 Java + 前端 Vue3 + 菜单权限 SQL),并支持对已生成模块的增量字段修改。

主数据复用规则

重要: 生成代码涉及的字典、角色、用户、部门等主数据,必须遵循"先查后建"原则。 使用 jeecg-system skill 的 system_utils.py 查询和管理主数据。 详见 ../jeecg-system/SKILL.md

交互流程

Step 0: 判断操作类型 — 全量生成 or 增量修改?

识别增量修改的关键词: "加字段"、"增加字段"、"新增字段"、"加一个XX字段"、"删除字段"、"修改字段"、"改一下XX"、"给XX模块加"、"给XX表加"

如果是增量修改 → 进入 场景C 如果是全量生成 → 进入 场景A场景B

Step 1: 全量生成 — 判断场景

场景A — 已有表(用户给了表名):

  1. 通过数据库查询获取精确 DDL(见"数据库连接"章节)
  2. 从 DDL 中解析:主键类型、全部字段(名称/类型/注释/是否nullable)、是否有系统字段
  3. 根据字段类型和注释自动推导前端控件类型
  4. 用户无需描述字段,AI 全部自动推导

场景B — 新建表(用户用自然语言描述需求):

  1. 从用户描述中提取:表名、实体名、功能描述、字段列表
  2. 用"智能字段推导"规则推导 DB 类型和前端控件
  3. 默认添加全部系统字段(create_by/create_time/update_by/update_time/sys_org_code)
  4. 生成建表 DDL 写入 Flyway SQL

场景C — 增量修改(给已有模块加/改/删字段):

  1. 定位目标模块:从用户提到的表名、模块名、实体名中识别目标
  2. 扫描已有代码文件:在后端和前端目录中搜索已生成的文件 # <project_root>/<project_vue_root>:后端/前端项目根目录,使用前需向用户确认 # 搜索后端 Entity 文件 find <project_root> -name "{EntityName}.java" -path "*/entity/*" # 搜索前端 data.ts 文件 find <project_vue_root>/src/views -name "{EntityName}.data.ts"
  3. 读取全部已有文件:Entity.java、*.data.ts、*List.vue、*Modal.vue(如有 Form.vue 也读取)
  4. 解析当前字段列表:从 Entity.java 解析已有字段
  5. 推导新字段属性:用"智能字段推导"规则推导 DB 类型、Java 类型、前端控件
  6. 展示修改摘要,等待用户确认后再修改

增量修改的操作类型:

  • 加字段:在所有文件中追加新字段定义
  • 删字段:从所有文件中移除指定字段定义
  • 改字段:修改指定字段的类型、控件、注释等

判断表类型:

  • 提到"分类/层级/树/上下级" → 树表
  • 提到"主子表/明细/一对多/订单+商品" → 一对多
  • 默认 → 单表

一对多表的前端布局风格:

一对多表有三种前端布局风格,用户未指定时默认使用原始布局风格

重要:vue3 封装风格和 vue3Native 原生风格的一对多架构完全不同! vue3 封装风格使用 useJvxeMethod,vue3Native 原生风格使用 useValidateAntFormAndTable。详见 codegen-reference.md 的 C9-C12(vue3)和 C13(vue3Native)

vue3 封装风格布局选项:

风格关键词列表页Modal 布局
默认/原始布局"默认风格"、"默认"、未指定风格标准列表(无 expandedRowRender)上面主表 BasicForm + 下面 a-tabs 子表
Tab-in-Modal (C9)"tab切换"、"radio切换"、"标题栏切换"标准列表radio-group 标题栏切换主表/子表,wrapClassName="j-cgform-tab-modal"
内嵌子表 (C12)"内嵌子表"、"行展开"、"expandedRowRender"行展开显示子表(expandedRowRender)上面主表 BasicForm + 下面 a-tabs 子表(同默认)
ERP (C11)"ERP风格"、"独立编辑"主表单选 + 子表独立 CRUD Tab仅主表 BasicForm(子表独立 Modal)

vue3Native 原生风格(C13)— 架构完全不同:

  • Modal 是薄包装器(BasicModal + useModalInner),只调 formComponent.submitForm()/edit()/add()
  • Form.vue 是核心组件,包含主表 a-form + 子表 a-tabs + 提交逻辑
  • 使用 useValidateAntFormAndTable hook(不是 useJvxeMethod
  • 子表 API 导出为函数(不是 URL 字符串)
  • saveOrUpdate 不用 isTransformResponse: false
  • 一对一子表用原生 a-form + Form.useForm,暴露 isForm = true
  • 一对一子表 initFormData(mainId) 直接传主表 ID(不传 URL 字符串)
  • 一对一子表 getFormData() 返回对象(不是数组)
  • 需要额外的 queryDataById API 函数
  • List.vue 使用 useModal + openModal(true, {...}) 模式

vue3 封装风格 — 默认/原始布局的关键特征:

  • Modal 结构:BasicForm(主表)始终显示在上方 + <a-tabs> 包裹子表在下方
  • wrapClassName="j-cgform-tab-modal" #title 插槽的 radio-group
  • refKeys 只包含子表 key(不包含主表 key),如 ['subMany', 'subOne']
  • 一对多子表用 <JVxeTable>,一对一子表抽成独立 Form.vue 组件(必须用 defineComponent,不能用 <script setup>
  • 列表页为标准 BasicTable,无 expandedRowRender
  • useJvxeMethod 的第6个参数 validateSubForm 用于校验一对一子表
  • validateForm(index) 的 index 对应 refKeys 中的位置(0=第一个子表,1=第二个子表)
  • tableRefs 只能包含 JVxeTable 的 ref,禁止包含 Form 组件 ref(否则 resetScrollTop 报错)

内嵌子表 (C12) 的关键特征(Modal 与默认布局完全一致,仅 List 不同):

  • List.vue 使用 expandedRowRender 行展开显示 SubTable 组件,需额外创建 subTables/ 目录
  • Modal.vue 结构与默认布局完全一致useJvxeMethod 6参数 + classifyIntoFormData + validateSubForm
  • 后端 子表查询必须返回 Result<IPage<T>>(不是 Result<List<T>>),SubTable 前端通过 res.result.records 获取数据
  • api.ts 每个子表需要双导出:URL 字符串(供 Modal)+ API 函数(供 SubTable,isTransformResponse:false
  • data.ts 一对多子表需要双列定义:BasicColumn[](SubTable 展示)+ JVxeColumn[](Modal 编辑)
  • 详见规则18-24.5

Step 2: 询问用户选项(仅全量生成需要)

一次性展示所有选项及默认值,用户说"确认"即可全部采用默认值,或只说需要改的:

  1. 后端模块:默认 jeecg-module-system/jeecg-system-biz
  2. 前端风格:默认 vue3(封装风格),可选 vue3Native(原生风格)
  3. 前端视图目录:默认用 entityPackage 值
  4. 是否读取系统字典:默认 ,读取后可自动为字段匹配已有字典编码(见"字典智能匹配"章节)

Step 3: 展示摘要

  • 全量生成:列出表名、字段清单(名称/类型/控件/校验/字典),等待用户确认后再生成。
  • 增量修改:列出要修改的文件路径 + 每个文件的具体变更内容(新增/删除/修改哪些行),等待用户确认。

Step 4: 执行

  • 全量生成:读取 codegen-reference.md 获取完整模板模式,按顺序生成全部文件。
  • 增量修改:使用 Edit 工具精确修改每个文件,读取 codegen-reference.md 的 Section F 获取增量修改模板。

Step 5: 输出清单

列出所有生成/修改的文件路径 + 后续操作说明(执行SQL、重启后端等)。

本地环境自动执行菜单 SQL 规则

前置条件(必须):执行任何 SQL 之前,必须先询问用户要执行到哪个数据库。 不要自动假设目标数据库名称,即使配置文件中有默认值。用户本机可能有多个数据库实例。

判断条件: 数据库连接地址为 127.0.0.1localhost(即本地开发环境)。

自动执行方式: 确认目标数据库后,生成 Flyway SQL 文件后,同时通过 Bash 工具直接执行菜单权限 SQL:

# 先询问用户目标数据库名,假设用户确认为 {dbname}
# 先检查菜单是否已存在,避免重复插入
mysql --no-defaults --default-character-set=utf8mb4 -h127.0.0.1 -P3306 -uroot -proot {dbname} -e "SELECT id FROM sys_permission WHERE id='{timestamp}01'"
# 不存在则执行全部菜单 + 角色授权 SQL
mysql --no-defaults --default-character-set=utf8mb4 -h127.0.0.1 -P3306 -uroot -proot {dbname} < {flyway_sql_file_path}

注意事项:

  • 执行 SQL 前必须先询问用户目标数据库名称,不能自动假设
  • 仅在本地环境(127.0.0.1/localhost)自动执行,远程环境只生成 Flyway 文件
  • 执行前先检查主菜单 ID 是否已存在,避免重复插入
  • 如果 MySQL 执行失败,提示用户手动执行 Flyway SQL,不中断整体流程
  • 输出结果中标注 菜单 SQL:已自动执行 ✓

数据库连接

已有表场景必须先查数据库! 通过以下方式获取精确 DDL:

重要:执行任何 SQL 之前,必须先询问用户要执行到哪个数据库。 不要自动假设数据库名称。先读取 application-dev.yml 获取配置中的数据库名,然后向用户确认是否使用该数据库。

# 1. 先读取项目数据库配置,获取数据库名
# 配置文件: jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml

# 2. 向用户确认目标数据库名后,再执行查询(假设确认为 {dbname})

# 查询表 DDL
mysql --no-defaults --default-character-set=utf8mb4 -h127.0.0.1 -P3306 -uroot -proot {dbname} -e "SHOW CREATE TABLE 表名\G"

# 查询字段注释
mysql --no-defaults --default-character-set=utf8mb4 -h127.0.0.1 -P3306 -uroot -proot {dbname} -e "SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT, COLUMN_KEY, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='{dbname}' AND TABLE_NAME='表名' ORDER BY ORDINAL_POSITION"

如果无法连接数据库,回退方案:在项目 SQL 文件中搜索表定义(grep -r "CREATE TABLE.*表名" 在 docs/db/ 目录下)。

Flyway 版本号规则

生成 Flyway SQL 前必须检查已有版本号,自动递增避免冲突!

# Flyway SQL 目录(路径以 CLAUDE.md 中的后端根路径为准)
ls {后端根路径}/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/ | sort -V | tail -5

版本命名规则:V{YYYYMMDD}_{序号}__{描述}.sql

  • 检查当天是否已有文件(如 V20260311_1__xxx.sql
  • 如果有,序号递增(V20260311_2__xxx.sql
  • 如果没有,从 _1 开始

菜单 SQL 的 ID 生成

必须使用真实时间戳确保唯一性! 通过以下命令获取:

date +%s%3N  # 输出13位毫秒级时间戳,如 1741704000123

用这个时间戳作为基础 ID,依次拼接 01-14:

  • 主菜单: {timestamp}01
  • 添加按钮: {timestamp}02
  • 编辑按钮: {timestamp}03
  • ... 以此类推

字典智能匹配

用户选择"读取系统字典"后,执行以下查询获取全部可用字典:

# 查询所有字典编码及其选项值({dbname} 需替换为用户确认的数据库名)
mysql --no-defaults --default-character-set=utf8mb4 -h127.0.0.1 -P3306 -uroot -proot {dbname} -e "
SELECT d.dict_code, d.dict_name, GROUP_CONCAT(i.item_text, '=', i.item_value ORDER BY i.sort_order SEPARATOR ', ') AS items
FROM sys_dict d
LEFT JOIN sys_dict_item i ON d.id = i.dict_id AND i.status = 1
WHERE d.del_flag = 0
GROUP BY d.dict_code, d.dict_name
ORDER BY d.dict_code
"

匹配规则: 拿到字典列表后,按以下优先级为字段匹配字典:

  1. 用户明确指定 — 用户说"状态用字典 order_status",直接使用
  2. 字段名精确匹配 — 字段名(如 status)与 dict_code 完全一致
  3. 语义关键词匹配 — 字段注释含"状态/类型/级别/分类"等关键词,搜索 dict_name 包含相同关键词的字典
  4. 不匹配 — 找不到合适字典时,不使用字典注解,按普通 Input 处理

匹配成功后的效果:

  • Entity: 自动添加 @Dict(dicCode = "matched_dict_code")
  • data.ts columns: dataIndex 使用 fieldName_dictText 后缀
  • data.ts formSchema: component 使用 JDictSelectTagcomponentProps: {dictCode: 'matched_dict_code'}
  • data.ts searchFormSchema: 同样使用 JDictSelectTag 组件

展示格式: 在 Step 3 表结构摘要中,匹配到字典的字段标注字典编码和选项值,如:

| 字段名 | 类型 | 控件 | 字典 |
| status | varchar(10) | JDictSelectTag | order_status (待付款=0, 已付款=1, 已完成=2) |

三种字典控件完整用法

JeecgBoot 支持三种字典类型,每种在后端 Entity、前端 data.ts 的 columns/formSchema/searchFormSchema/superQuerySchema 中的写法不同。

1. 系统字典(从 sys_dict 表获取)

适用场景:固定枚举值(状态、类型、级别等),值存储在 sys_dict + sys_dict_item 表中。

查询可用字典:

mysql ... -e "
SELECT d.dict_code, d.dict_name, GROUP_CONCAT(i.item_text, '=', i.item_value ORDER BY i.sort_order SEPARATOR ', ') AS items
FROM sys_dict d LEFT JOIN sys_dict_item i ON d.id = i.dict_id AND i.status = 1
WHERE d.del_flag = 0 GROUP BY d.dict_code, d.dict_name ORDER BY d.dict_code"

后端 Entity:

@Excel(name = "学校状态", width = 15, dicCode = "valid_status")
@Dict(dicCode = "valid_status")
private String schoolStatus;

前端 data.ts — columns:

{ title: '学校状态', align: 'center', dataIndex: 'schoolStatus_dictText' }
// 注意:列表展示用 _dictText 后缀,后端自动翻译字典值为文本

前端 data.ts — formSchema:

{ label: '学校状态', field: 'schoolStatus', component: 'JDictSelectTag',
  componentProps: { dictCode: 'valid_status', placeholder: '请选择学校状态' } }

前端 data.ts — searchFormSchema:

{ label: '学校状态', field: 'schoolStatus', component: 'JDictSelectTag',
  componentProps: { dictCode: 'valid_status' }, colProps: { span: 6 } }

前端 data.ts — superQuerySchema:

schoolStatus: { title: '学校状态', order: 0, view: 'list', dictCode: 'valid_status' }

Controller 查询规则(下拉/多选字段需添加):

Map<String, QueryRuleEnum> customeRuleMap = new HashMap<>();
customeRuleMap.put("schoolStatus", QueryRuleEnum.LIKE_WITH_OR);
QueryWrapper<EduSchool> queryWrapper = QueryGenerator.initQueryWrapper(eduSchool, req.getParameterMap(), customeRuleMap);

2. 分类字典(从 sys_category 表获取,树形结构)

适用场景:树形分类数据(省市区、物料分类、部门分类等),数据存储在 sys_category 表中,通过 pid 构成树。

查询可用分类:

mysql ... -e "SELECT id, code, name, pid FROM sys_category WHERE pid = '0' OR pid IS NULL OR pid = '' ORDER BY code"
# 查看某分类的子项:
mysql ... -e "SELECT id, code, name, pid FROM sys_category WHERE code = 'B03' OR pid IN (SELECT id FROM sys_category WHERE code = 'B03') ORDER BY code"

后端 Entity:

// 分类字典不使用 @Dict 注解,由前端 JCategorySelect 组件和 renderCategoryTree 处理翻译
@Excel(name = "所在区域", width = 15)
private String schoolArea;

前端 data.ts — columns:

{ title: '所在区域', align: 'center', dataIndex: 'schoolArea',
  customRender: ({ text }) => { return render.renderCategoryTree(text, 'B03'); } }
// 'B03' 是分类字典的顶级 code,renderCategoryTree 会自动翻译 id 为分类名称路径

前端 data.ts — formSchema:

{ label: '所在区域', field: 'schoolArea', component: 'JCategorySelect',
  componentProps: { pcode: 'B03' } }
// pcode 指定分类字典的顶级 code,组件自动渲染树形选择

前端 data.ts — searchFormSchema:

// 分类字典一般不放在搜索栏,如需要则使用 JCategorySelect
{ label: '所在区域', field: 'schoolArea', component: 'JCategorySelect',
  componentProps: { pcode: 'B03' }, colProps: { span: 6 } }

前端 data.ts — superQuerySchema:

schoolArea: { title: '所在区域', order: 0, view: 'cat_tree', code: 'B03' }

3. 表字典(从任意业务表获取)

适用场景:关联其他业务表的数据作为下拉选项(如从 sys_depart 表选部门、从 sys_user 表选用户等)。

后端 Entity:

@Excel(name = "归属部门", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")
@Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")
private String departId;
// dictTable: 关联的表名
// dicText: 作为显示文本的字段
// dicCode: 作为存储值的字段(通常是主键)

前端 data.ts — columns:

{ title: '归属部门', align: 'center', dataIndex: 'departId_dictText' }
// 与系统字典一样,列表展示用 _dictText 后缀

前端 data.ts — formSchema(下拉选择):

{ label: '归属部门', field: 'departId', component: 'JDictSelectTag',
  componentProps: { dictCode: 'sys_depart,depart_name,id', placeholder: '请选择归属部门' } }
// dictCode 格式: '表名,显示字段,值字段'
// 可追加条件: 'sys_depart,depart_name,id,status=1' (第四段为 WHERE 条件)

前端 data.ts — formSchema(搜索选择,大数据量推荐):

{ label: '归属部门', field: 'departId', component: 'JSearchSelect',
  componentProps: { dict: 'sys_depart,depart_name,id', placeholder: '请选择归属部门' } }
// JSearchSelect 支持远程搜索,适合数据量大的表

前端 data.ts — searchFormSchema:

{ label: '归属部门', field: 'departId', component: 'JDictSelectTag',
  componentProps: { dictCode: 'sys_depart,depart_name,id' }, colProps: { span: 6 } }

前端 data.ts — superQuerySchema:

departId: { title: '归属部门', order: 0, view: 'sel_search',
  dictTable: 'sys_depart', dictCode: 'id', dictText: 'depart_name' }

Controller 查询规则:

customeRuleMap.put("departId", QueryRuleEnum.LIKE_WITH_OR);

三种字典对比速查表

维度系统字典分类字典表字典
数据来源sys_dict + sys_dict_itemsys_category(树形)任意业务表
后端注解@Dict(dicCode = "xxx")无需 @Dict@Dict(dictTable="t", dicText="text", dicCode="code")
Excel注解dicCode = "xxx"dictTable="t", dicText="text", dicCode="code"
列表 columnsdataIndex: 'field_dictText'customRender: render.renderCategoryTree(text, 'pcode')dataIndex: 'field_dictText'
表单组件JDictSelectTag + dictCode: 'xxx'JCategorySelect + pcode: 'xxx'JDictSelectTag + dictCode: '表,text,code'
搜索组件JDictSelectTagJCategorySelectJDictSelectTagJSearchSelect
高级查询 viewlist + dictCodecat_tree + codesel_search + dictTable/dictCode/dictText
Controller 规则QueryRuleEnum.LIKE_WITH_OR无需特殊处理QueryRuleEnum.LIKE_WITH_OR
适用场景固定枚举(状态、类型)树形分类(地区、物料)关联其他表(部门、用户)

全组件控件完整用法

除三种字典外,JeecgBoot 还支持以下所有组件类型。每种组件在后端 Entity、前端 data.ts 的 columns/formSchema/searchFormSchema/superQuerySchema、Controller 中各有固定写法。


1. 基础输入类

Input(文本框)

// Entity
@Excel(name = "文本", width = 15)
@Schema(description = "文本")
private String name;
// columns
{ title: '文本', align: 'center', dataIndex: 'name' }
// formSchema
{ label: '文本', field: 'name', component: 'Input', componentProps: { placeholder: '请输入' } }
// superQuerySchema
name: { title: '文本', order: 0, view: 'text' }

InputPassword(密码框)

@Excel(name = "密码", width = 15)
@Schema(description = "密码")
private String password;
// formSchema — 密码框一般不在 columns 和 searchFormSchema 中展示
{ label: '密码', field: 'password', component: 'InputPassword',
  componentProps: { placeholder: '请输入密码' } }

InputTextArea(多行文本)

@Excel(name = "备注", width = 15)
@Schema(description = "备注")
private String remark;
// formSchema
{ label: '备注', field: 'remark', component: 'InputTextArea',
  componentProps: { placeholder: '请输入备注', rows: 4 } }

InputNumber(数字输入)

// BigDecimal 类型(金额/单价)
@Excel(name = "单价", width = 15)
@Schema(description = "单价")
private BigDecimal amount;

// Integer 类型(整数/排序号)
@Excel(name = "排序", width = 15)
@Schema(description = "排序")
private Integer sortOrder;
// formSchema
{ label: '单价', field: 'amount', component: 'InputNumber',
  componentProps: { placeholder: '请输入', style: 'width:100%' } }
// superQuerySchema
amount: { title: '单价', order: 0, view: 'number' }

2. 系统字典扩展控件

基本的 JDictSelectTag 下拉见"三种字典控件"章节,此处仅列出扩展用法。

JDictSelectTag type='radio'(字典单选)

// formSchema
{ label: '性别', field: 'sex', component: 'JDictSelectTag',
  componentProps: { dictCode: 'sex', type: 'radio' } }

JCheckbox(字典多选 checkbox)

// Entity — 存储多个值用逗号分隔
@Excel(name = "颜色多选", width = 15, dicCode = "demo_color")
@Dict(dicCode = "demo_color")
private String demoColor;
// formSchema
{ label: '颜色多选', field: 'demoColor', component: 'JCheckbox',
  componentProps: { dictCode: 'demo_color' } }
// superQuerySchema — view 使用 list_multi
demoColor: { title: '颜色多选', order: 0, view: 'list_multi', dictCode: 'demo_color' }

JSelectMultiple(字典下拉多选框)

@Excel(name = "字典下拉多选", width = 15, dicCode = "urgent_level")
@Dict(dicCode = "urgent_level")
private String dictMultiSelect;
// formSchema
{ label: '字典下拉多选', field: 'dictMultiSelect', component: 'JSelectMultiple',
  componentProps: { dictCode: 'urgent_level', triggerChange: true } }
// superQuerySchema
dictMultiSelect: { title: '字典下拉多选', order: 0, view: 'list_multi', dictCode: 'urgent_level' }
// Controller
customeRuleMap.put("dictMultiSelect", QueryRuleEnum.LIKE_WITH_OR);

JSwitch(开关)

@Excel(name = "开关", width = 15, replace = {"是_1", "否_0"})
private String isEnabled;
// columns — 使用 renderSwitch 自定义渲染
{ title: '开关', align: 'center', dataIndex: 'isEnabled',
  customRender: ({ text }) => render.renderSwitch(text, [{ text: '是', value: '1' }, { text: '否', value: '0' }]) }
// formSchema
{ label: '开关', field: 'isEnabled', component: 'JSwitch',
  componentProps: { options: ['1', '0'] } }
// superQuerySchema
isEnabled: { title: '开关', order: 0, view: 'radio', dictCode: 'yn' }

3. 表字典扩展控件

基本的表字典下拉和搜索见"三种字典控件"章节,此处仅列出扩展用法。

JDictSelectTag type='radio'(表字典单选)

@Excel(name = "表字典单选", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")
@Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")
private String tableDictRadio;
// formSchema
{ label: '表字典单选', field: 'tableDictRadio', component: 'JDictSelectTag',
  componentProps: { dictCode: 'sys_depart,depart_name,id', type: 'radio' } }

JCheckbox(表字典多选)

@Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")
private String tableDictCheckbox;
// formSchema
{ label: '表字典多选', field: 'tableDictCheckbox', component: 'JCheckbox',
  componentProps: { dictCode: 'sys_depart,depart_name,id' } }

JSelectMultiple(表字典下拉多选)

@Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")
private String tableDictMultiSelect;
// formSchema
{ label: '表字典下拉多选', field: 'tableDictMultiSelect', component: 'JSelectMultiple',
  componentProps: { dictCode: 'sys_depart,depart_name,id', triggerChange: true } }

表字典带条件下拉

// Entity — dictTable 中直接拼 WHERE 条件
@Excel(name = "表字典带条件", width = 15, dictTable = "sys_user where username like '%a%'", dicText = "realname", dicCode = "username")
@Dict(dictTable = "sys_user where username like '%a%'", dicText = "realname", dicCode = "username")
private String tableDictCondition;
// formSchema — dictCode 字符串中拼条件
{ label: '表字典带条件', field: 'tableDictCondition', component: 'JDictSelectTag',
  componentProps: { dictCode: "sys_user where username like '%a%',realname,username", placeholder: '请选择' } }

4. 用户/部门选择

JSelectUserByDept(用户选择)

@Excel(name = "用户选择", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username")
@Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
private String userId;
// columns
{ title: '用户选择', align: 'center', dataIndex: 'userId_dictText' }
// formSchema
{ label: '用户选择', field: 'userId', component: 'JSelectUserByDept',
  componentProps: { labelKey: 'realname' } }
// superQuerySchema
userId: { title: '用户选择', order: 0, view: 'sel_user' }

JSelectDept(部门选择)

@Excel(name = "部门选择", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")
@Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")
private String deptId;
// columns
{ title: '部门选择', align: 'center', dataIndex: 'deptId_dictText' }
// formSchema
{ label: '部门选择', field: 'deptId', component: 'JSelectDept' }
// superQuerySchema
deptId: { title: '部门选择', order: 0, view: 'sel_depart' }

5. 自定义树(JTreeSelect)

@Excel(name = "自定义树", width = 15, dictTable = "sys_category", dicText = "name", dicCode = "id")
@Dict(dictTable = "sys_category", dicText = "name", dicCode = "id")
private String treeSelect;
// columns
{ title: '自定义树', align: 'center', dataIndex: 'treeSelect_dictText' }
// formSchema
{ label: '自定义树', field: 'treeSelect', component: 'JTreeSelect',
  componentProps: { dict: 'sys_category,name,id', pidField: 'pid', multiple: false } }
// superQuerySchema
treeSelect: { title: '自定义树', order: 0, view: 'sel_search',
  dictTable: 'sys_category', dictCode: 'id', dictText: 'name' }
// Controller
customeRuleMap.put("treeSelect", QueryRuleEnum.LIKE_WITH_OR);

6. 日期时间类

DatePicker(日期)

@Excel(name = "日期", width = 15, format = "yyyy-MM-dd")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
// columns — 截取前10位防止时间部分显示
{ title: '日期', align: 'center', dataIndex: 'birthday',
  customRender: ({ text }) => (!text ? '' : (text.length > 10 ? text.substr(0, 10) : text)) }
// formSchema
{ label: '日期', field: 'birthday', component: 'DatePicker',
  componentProps: { showTime: false, valueFormat: 'YYYY-MM-DD', placeholder: '请选择日期', style: 'width:100%' } }
// superQuerySchema
birthday: { title: '日期', order: 0, view: 'date' }

DatePicker showTime(年月日时分秒)

@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 workTime;
// formSchema
{ label: '年月日时分秒', field: 'workTime', component: 'DatePicker',
  componentProps: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss', placeholder: '请选择', style: 'width:100%' } }
// superQuerySchema
workTime: { title: '年月日时分秒', order: 0, view: 'datetime' }

TimePicker(时间选择)

// 时间类型用 String 存储(如 "14:30:00")
@Excel(name = "时间", width = 15)
private String timeVal;
// formSchema
{ label: '时间', field: 'timeVal', component: 'TimePicker',
  componentProps: { valueFormat: 'HH:mm:ss', placeholder: '请选择时间', style: 'width:100%' } }

DatePicker picker 变体(季度/年/月/周)

共同规则: DB 类型均为 date,Java 类型均为 Date,注解统一用 yyyy-MM-dd 格式。前端通过 picker 属性区分。

// Entity — 季度/年/月/周写法完全一致,只改注释
@Excel(name = "季度", width = 15, format = "yyyy-MM-dd")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date quarterVal;
// formSchema — 通过 picker 区分
{ label: '季度', field: 'quarterVal', component: 'DatePicker',
  componentProps: { picker: 'quarter', valueFormat: 'YYYY-MM-DD', placeholder: '请选择季度', style: 'width:100%' } }
{ label: '年', field: 'yearVal', component: 'DatePicker',
  componentProps: { picker: 'year', valueFormat: 'YYYY-MM-DD', placeholder: '请选择年', style: 'width:100%' } }
{ label: '月', field: 'monthVal', component: 'DatePicker',
  componentProps: { picker: 'month', valueFormat: 'YYYY-MM-DD', placeholder: '请选择月', style: 'width:100%' } }
{ label: '周', field: 'weekVal', component: 'DatePicker',
  componentProps: { picker: 'week', valueFormat: 'YYYY-MM-DD', placeholder: '请选择周', style: 'width:100%' } }

// columns — 必须使用 getWeekMonthQuarterYear 翻译(需 import { getWeekMonthQuarterYear } from '/@/utils')
{ title: '季度', dataIndex: 'quarterVal',
  customRender: ({ text }) => {
    text = !text ? '' : (text.length > 10 ? text.substr(0, 10) : text);
    return text ? getWeekMonthQuarterYear(text)['quarter'] : text;
  } }
{ title: '年', dataIndex: 'yearVal',
  customRender: ({ text }) => {
    text = !text ? '' : (text.length > 10 ? text.substr(0, 10) : text);
    return text ? getWeekMonthQuarterYear(text)['year'] : text;
  } }
// 月用 ['month'],周用 ['week'],写法相同

// ✅ 季度/年/月/周可以作为查询条件,需配合 List.vue 中的 fieldPickers + getDateByPicker(见 searchFormSchema 生成规则)

7. Popup/弹窗类

报表 Code 数据源(重要前置知识)

JPopup、JPopupDict、关联记录三个组件都依赖在线报表 code,code 来源于 onl_cgreport_head 表。生成代码时必须先查询可用报表,再配置到组件中。

查询可用报表 code:

mysql ... -e "SELECT code, name, cgr_sql FROM onl_cgreport_head ORDER BY create_time DESC"

查询报表的字段列表(用于配置 fieldConfig 的 source):

mysql ... -e "SELECT field_name, field_txt FROM onl_cgreport_item WHERE cgrhead_id = (SELECT id FROM onl_cgreport_head WHERE code='报表code') ORDER BY order_num"

配置流程:

  1. 查询 onl_cgreport_head 获取所有可用报表 code 和 SQL
  2. 根据业务需求选择合适的报表(如需大数据测试选 testbigdata,需用户数据选 report_user
  3. 查询该报表的 onl_cgreport_item 获取可用字段名(field_name
  4. codefield_name 配置到前端组件的 codefieldConfig.sourcedictCode

常用报表 code 示例:

code名称SQL数据源典型字段
report_user统计在线用户sys_userid, username, realname, phone, email
testbigdata测试大数据sys_logid, log_content, userid, username, ip
demoReport Demodemo*

JPopup(Popup弹窗 + 他表字段回填)

JPopup 用于弹窗选择记录并回填多个字段值。code 对应 onl_cgreport_head 表中的报表编码。

// Entity — popup 存储选中值,popback 存储回填值
@Excel(name = "popup弹窗", width = 15)
private String popup;

@Excel(name = "popup回填", width = 15)
private String popback;
// formSchema — JPopup 组件通过 fieldConfig 定义回填映射
{ label: 'Popup弹窗', field: 'popup', component: 'JPopup',
  componentProps: ({ formActionType }) => {
    const { setFieldsValue } = formActionType;
    return {
      setFieldsValue,
      code: 'testbigdata',           // 报表编码,对应 onl_cgreport_head.code
      fieldConfig: [
        { source: 'log_content', target: 'popup' },   // 报表字段 → 当前表字段
        { source: 'userid', target: 'popback' },       // 回填其他字段
      ],
      multi: false,                   // 是否多选
    };
  } }
// 他表字段(回填字段)— disabled Input,不可手动编辑
{ label: 'Popup回填(他表字段)', field: 'popback', component: 'Input',
  componentProps: { disabled: true, placeholder: '由Popup自动回填' } }

fieldConfig 配置规则:

  • source:报表中的字段名(来自 onl_cgreport_item.field_name
  • target:当前表单中的字段名(field 值)
  • 可配置多组回填映射,选中一条记录后同时回填多个字段

JPopupDict(Popup字典)

JPopupDict 是简化版的 Popup,直接通过 dictCode 指定报表和显示/值字段,无需配置 fieldConfig。

@Excel(name = "popup字典", width = 15)
private String popDict;
// formSchema
{ label: 'Popup字典', field: 'popDict', component: 'JPopupDict',
  componentProps: { dictCode: 'testbigdata,log_content,id', multi: false } }
// dictCode 格式: '报表code,显示字段,值字段'
// 报表code: onl_cgreport_head.code
// 显示字段: 报表中用于展示的 field_name
// 值字段: 报表中用于存储的 field_name(通常是 id)

关联记录 + 他表字段(JPopup 关联业务表)

关联记录本质是 JPopup,选中后自动回填关联表的其他字段(他表字段)。后端用 @Dict 实现列表翻译。

// Entity — 关联记录字段带 @Dict 翻译
@Excel(name = "关联记录", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username")
@Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
private String relatedUser;

// 他表字段 — 不需要 @Dict,由前端 JPopup 回填
@Excel(name = "他表字段", width = 15)
private String relatedUsername;
// columns — 关联记录用 _dictText 翻译,他表字段直接展示
{ title: '关联记录', align: 'center', dataIndex: 'relatedUser_dictText' }
{ title: '他表字段', align: 'center', dataIndex: 'relatedUsername' }

// formSchema — 关联记录用 JPopup,他表字段用 disabled Input
{ label: '关联记录', field: 'relatedUser', component: 'JPopup',
  componentProps: ({ formActionType }) => {
    const { setFieldsValue } = formActionType;
    return {
      setFieldsValue,
      code: 'testbigdata',
      fieldConfig: [
        { source: 'userid', target: 'relatedUser' },
        { source: 'username', target: 'relatedUsername' },
      ],
      multi: false,
    };
  } }
{ label: '他表字段', field: 'relatedUsername', component: 'Input',
  componentProps: { disabled: true, placeholder: '由关联记录自动回填' } }

// superQuerySchema — 关联记录
relatedUser: { title: '关联记录', order: 0, view: 'sel_search',
  dictTable: 'sys_user', dictCode: 'username', dictText: 'realname' }

// Controller
customeRuleMap.put("relatedUser", QueryRuleEnum.LIKE_WITH_OR);

Popup 组件生成流程总结

生成含 Popup/关联记录/Popup字典的字段时,按以下步骤操作:

  1. 查询报表列表SELECT code, name, cgr_sql FROM onl_cgreport_head
  2. 选择合适报表:根据业务场景匹配(用户→report_user,日志→testbigdata 等)
  3. 查询报表字段SELECT field_name, field_txt FROM onl_cgreport_item WHERE cgrhead_id=...
  4. 配置 code:JPopup 的 code 和 JPopupDict 的 dictCode 第一段都填报表 code
  5. 配置字段映射:JPopup 的 fieldConfigsource 填报表字段名,JPopupDict 的 dictCode 后两段填显示字段和值字段
  6. 如果没有合适报表:提示用户先在"在线报表"中创建报表配置

8. 省市区/联动

JAreaLinkage(省市区三级联动)

// Entity — 需要 import/export 转换方法
@Excel(name = "省市区", width = 15, exportConvert = true, importConvert = true)
private String provinceCityArea;

// 必须添加这两个转换方法(Excel导入导出用)
public String convertisProvinceCityArea() {
    return SpringContextUtils.getBean(ProvinceCityArea.class).getText(provinceCityArea);
}
public void convertsetProvinceCityArea(String text) {
    this.provinceCityArea = SpringContextUtils.getBean(ProvinceCityArea.class).getCode(text);
}
// 需要 import:
// import org.jeecg.common.constant.ProvinceCityArea;
// import org.jeecg.common.util.SpringContextUtils;
// formSchema
{ label: '省市区', field: 'provinceCityArea', component: 'JAreaLinkage' }

联动组件(多级自定义联动)

联动组件由多个独立字段组成,通过前端逻辑实现级联关系。后端存储各级选中值。

@Excel(name = "联动组件一", width = 15)
private String linkOne;
@Excel(name = "联动组件二", width = 15)
private String linkTwo;
@Excel(name = "联动组件三", width = 15)
private String linkThree;
// formSchema — 每级用不同的字典/数据源
{ label: '联动组件一', field: 'linkOne', component: 'JDictSelectTag',
  componentProps: { dictCode: 'org_category', placeholder: '请选择一级' } }
{ label: '联动组件二', field: 'linkTwo', component: 'JSearchSelect',
  componentProps: { dict: 'sys_depart,depart_name,id', placeholder: '请选择二级' } }
{ label: '联动组件三', field: 'linkThree', component: 'JSearchSelect',
  componentProps: { dict: 'sys_user,realname,username', placeholder: '请选择三级' } }

9. 文件/上传类

JImageUpload(图片上传)

@Excel(name = "图片", width = 15)
private String avatar;
// columns — 使用 renderImage 渲染缩略图
{ title: '图片', align: 'center', dataIndex: 'avatar', customRender: render.renderImage }
// formSchema
{ label: '图片', field: 'avatar', component: 'JImageUpload' }

JUpload(文件上传)

@Excel(name = "文件", width = 15)
private String attachment;
// columns — 使用 bodyCell 插槽渲染下载按钮
// 在 List.vue 的 <template v-slot:bodyCell="{ column, record, index, text }"> 中:
<template v-if="column.dataIndex==='attachment'">
  <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>
// List.vue 需要 import:
import { downloadFile } from '/@/utils/common/renderUtils';

// formSchema
{ label: '文件', field: 'attachment', component: 'JUpload' }

10. 编辑器类

JEditor(富文本编辑器)

@Excel(name = "富文本", width = 15)
private String content;
// columns — 使用 bodyCell 插槽渲染 HTML
// 在 List.vue 的 bodyCell 中:
<template v-if="column.dataIndex==='content'">
  <div v-html="text"></div>
</template>
// formSchema
{ label: '富文本', field: 'content', component: 'JEditor' }

JMarkdownEditor(Markdown编辑器,blob存储)

Markdown 使用 byte[] (blob) 存储以支持大文本,需要特殊的 getter/setter 模式:

// Entity — 注意 transient 和 byte[] 的双字段模式
@Excel(name = "markdown", width = 15)
private transient String markdownString;  // 前端交互用,不写入DB

private byte[] markdown;  // 实际存储到DB的blob字段

public byte[] getMarkdown() {
    if (markdownString == null) { return null; }
    try { return markdownString.getBytes("UTF-8"); }
    catch (UnsupportedEncodingException e) { e.printStackTrace(); }
    return null;
}

public String getMarkdownString() {
    if (markdown == null || markdown.length == 0) { return ""; }
    try { return new String(markdown, "UTF-8"); }
    catch (UnsupportedEncodingException e) { e.printStackTrace(); }
    return "";
}
// 需要 import java.io.UnsupportedEncodingException;
-- DB字段类型必须是 blob
ALTER TABLE xxx ADD COLUMN `markdown` blob COMMENT 'markdown内容';
// formSchema — 前端字段名用 markdownString(对应 transient 字段)
{ label: 'Markdown编辑器', field: 'markdownString', component: 'JMarkdownEditor' }

全组件速查表

组件前端组件名DB类型Java类型需要@Dictcolumns特殊处理Controller规则
文本框InputvarcharString
密码框InputPasswordvarcharString不展示
多行文本InputTextAreatextString
数字InputNumberdecimal/intBigDecimal/Integer
字典下拉JDictSelectTagvarcharString系统字典_dictTextLIKE_WITH_OR
字典单选JDictSelectTag(radio)varcharString系统字典_dictTextLIKE_WITH_OR
字典多选JCheckboxvarcharString系统字典_dictTextLIKE_WITH_OR
下拉多选JSelectMultiplevarcharString系统字典_dictTextLIKE_WITH_OR
开关JSwitchvarchar(2)StringrenderSwitch
表字典下拉JDictSelectTagvarcharString表字典_dictTextLIKE_WITH_OR
表字典搜索JSearchSelectvarcharString表字典_dictTextLIKE_WITH_OR
表字典单选JDictSelectTag(radio)varcharString表字典_dictTextLIKE_WITH_OR
表字典多选JCheckboxvarcharString表字典_dictTextLIKE_WITH_OR
表字典下拉多选JSelectMultiplevarcharString表字典_dictTextLIKE_WITH_OR
表字典带条件JDictSelectTagvarcharString表字典(带WHERE)_dictTextLIKE_WITH_OR
用户选择JSelectUserByDeptvarcharString表字典(sys_user)_dictText
部门选择JSelectDeptvarcharString表字典(sys_depart)_dictText
分类字典JCategorySelectvarcharStringrenderCategoryTree
自定义树JTreeSelectvarcharString表字典_dictTextLIKE_WITH_OR
日期DatePickerdateDatesubstr(0,10)
日期时间DatePicker(showTime)datetimeDate
时间TimePickervarcharString
季度DatePicker(quarter)dateDategetWeekMonthQuarterYear['quarter']
DatePicker(year)dateDategetWeekMonthQuarterYear['year']
DatePicker(month)dateDategetWeekMonthQuarterYear['month']
DatePicker(week)dateDategetWeekMonthQuarterYear['week']
Popup弹窗JPopupvarcharString
Popup回填Input(disabled)varcharString
Popup字典JPopupDictvarcharString
关联记录JPopupvarcharString表字典_dictTextLIKE_WITH_OR
他表字段Input(disabled)varcharString
省市区JAreaLinkagevarcharString
联动组件JDictSelectTag/JSearchSelectvarcharString按级别
图片上传JImageUploadvarchar(1000)StringrenderImage
文件上传JUploadvarchar(1000)StringbodyCell下载按钮
富文本JEditortextStringbodyCell v-html
MarkdownJMarkdownEditorblobbyte[]+transient

JVxeTable 子表列配置(一对多子表 JVxeColumn)

一对多子表使用 JVxeTable 组件,列定义使用 JVxeColumn[] 类型,不是 FormSchema

引入方式:

import { JVxeTypes, JVxeColumn } from '/@/components/jeecg/JVxeTable/types';

重要:JVxeTable 的组件类型与 FormSchema 完全不同! 不能使用 FormSchema 的组件名(如 JDictSelectTagJCheckbox 等),必须使用 JVxeTypes 枚举。

JVxeTypes 组件类型对照表(FormSchema → JVxeColumn):

FormSchema 组件JVxeTypes 类型关键属性说明
InputJVxeTypes.input文本输入
InputTextAreaJVxeTypes.textarea多行文本
InputNumberJVxeTypes.inputNumberprops.max/min/precision数字输入
JDictSelectTagJVxeTypes.selectoptions:[], dictCode字典下拉(系统字典 + 表字典)
JDictSelectTag(radio)JVxeTypes.selectoptions:[], dictCodeJVxe无单独radio,用select代替
JCheckboxJVxeTypes.selectoptions:[], dictCodeJVxe无checkbox字典,用select代替
JSelectMultipleJVxeTypes.selectMultipleoptions:[], dictCode下拉多选
JSearchSelectJVxeTypes.selectSearchdictCode下拉搜索
JSwitchJVxeTypes.checkboxcustomValue:['Y','N']开关(用checkbox实现)
DatePickerJVxeTypes.date日期
DatePicker(showTime)JVxeTypes.datetime日期时间
TimePickerJVxeTypes.time时间
JImageUploadJVxeTypes.imagetoken:true, responseName:'message'图片上传
JUploadJVxeTypes.filetoken:true, responseName:'message'文件上传
JPopupJVxeTypes.popuppopupCode, fieldConfig, propsPopup弹窗
JSelectDeptJVxeTypes.departSelect部门选择
JSelectUserByDeptJVxeTypes.userSelect用户选择

JVxeColumn 通用属性:

{
  title: '标题',           // 列标题
  key: 'fieldName',       // 字段名(对应 entity 属性名)
  type: JVxeTypes.xxx,    // 组件类型
  width: '200px',         // 列宽(字符串,带px)
  placeholder: '请输入${title}',  // 占位提示,${title}自动替换为列标题
  defaultValue: '',       // 默认值
  disabled: false,        // 是否禁用
  validateRules: [],      // 校验规则
}

校验规则(validateRules):

validateRules: [
  { required: true, message: '${title}不能为空' },           // 必填
  { pattern: '^1[3456789]\\d{9}$', message: '${title}格式不正确' },  // 正则
  { pattern: 'only', message: '${title}不能重复' },           // 唯一值
  { pattern: '*6-16', message: '${title}格式不正确' },        // 6-16位任意字符
]

常用示例(覆盖主要类型):

export const subTableColumns: JVxeColumn[] = [
  { title: '文本', key: 'name', type: JVxeTypes.input, width: '200px', placeholder: '请输入${title}', defaultValue: '',
    validateRules: [{ required: true, message: '${title}不能为空' }] },
  { title: '数字', key: 'amount', type: JVxeTypes.inputNumber, width: '200px', defaultValue: '' },
  { title: '系统字典', key: 'status', type: JVxeTypes.select, options: [], dictCode: 'valid_status', width: '200px', defaultValue: '' },
  { title: '表字典', key: 'deptId', type: JVxeTypes.select, options: [], dictCode: 'sys_depart,depart_name,id', width: '200px', defaultValue: '' },
  { title: '表字典搜索', key: 'userId', type: JVxeTypes.selectSearch, dictCode: 'sys_user,realname,username', width: '200px', defaultValue: '' },
  { title: '下拉多选', key: 'multiVal', type: JVxeTypes.selectMultiple, options: [], dictCode: 'org_category', width: '250px', defaultValue: '' },
  { title: '开关', key: 'switchVal', type: JVxeTypes.checkbox, customValue: ['Y', 'N'], width: '200px', defaultValue: 'N' },
  { title: '日期', key: 'dateVal', type: JVxeTypes.date, width: '200px', defaultValue: '' },
  { title: '日期时间', key: 'datetimeVal', type: JVxeTypes.datetime, width: '200px', defaultValue: '' },
  { title: '图片', key: 'imageVal', type: JVxeTypes.image, token: true, responseName: 'message', number: 1, width: '200px', defaultValue: '' },
  { title: '文件', key: 'fileVal', type: JVxeTypes.file, token: true, responseName: 'message', number: 1, width: '200px', defaultValue: '' },
  { title: 'popup', key: 'popupVal', type: JVxeTypes.popup, popupCode: 'report_user',
    fieldConfig: [{ source: 'realname', target: 'popupVal' }, { source: 'username', target: 'popupBack' }],
    props: { multi: true }, width: '200px', defaultValue: '' },
  { title: '部门选择', key: 'deptId', type: JVxeTypes.departSelect, width: '200px', defaultValue: '' },
  { title: '用户选择', key: 'userId', type: JVxeTypes.userSelect, width: '200px', defaultValue: '' },
];

searchFormSchema 生成规则

生成查询条件(searchFormSchema)时,必须遵循以下三条规则:

规则1:label 必须与 formSchema 一致 查询条件的 label 必须与编辑表单(formSchema)中同字段的 label 完全一致,不能用字典描述或简写替代。

// ✅ 正确 — label 与 formSchema 一致
formSchema:       { label: '字典下拉(Select)', field: 'status', ... }
searchFormSchema: { label: '字典下拉(Select)', field: 'status', ... }

// ❌ 错误 — 用字典描述当 label
formSchema:       { label: '字典多选(Checkbox)', field: 'demoColor', ... }
searchFormSchema: { label: '颜色多选', field: 'demoColor', ... }  // 不应用字典名称

规则2:多选字段在查询条件中使用 JSelectMultiple(下拉多选),不使用 JCheckbox 表单中使用 JCheckbox 的多选字段,在查询条件中统一改为 JSelectMultiple 下拉多选,因为 Checkbox 在查询栏中占空间过大且体验不佳。

// ✅ 正确 — 查询条件用下拉多选
formSchema:       { label: '字典多选(Checkbox)', field: 'demoColor', component: 'JCheckbox',
                    componentProps: { dictCode: 'demo_color' } }
searchFormSchema: { label: '字典多选(Checkbox)', field: 'demoColor', component: 'JSelectMultiple',
                    componentProps: { dictCode: 'demo_color', triggerChange: true }, colProps: { span: 6 } }

// ❌ 错误 — 查询条件用 Checkbox
searchFormSchema: { label: '字典多选(Checkbox)', field: 'demoColor', component: 'JCheckbox', ... }

规则3:不适合作为查询条件的组件应排除 以下组件类型不应出现在 searchFormSchema 中:

  • 密码框(InputPassword)
  • 多行文本(InputTextArea)
  • 富文本(JEditor)
  • Markdown(JMarkdownEditor)
  • 图片上传(JImageUpload)
  • 文件上传(JUpload)
  • Popup回填/他表字段(disabled Input,由关联记录回填)
DatePicker picker 变体(quarter/year/month/week)可以作为查询条件,但需要在 List.vue 中配合 fieldPickers + getDateByPicker 使用(见规则7)。

规则4:JPopup 作为查询条件时必须使用 formActionType JPopup 在 searchFormSchema 中必须通过 componentProps: ({formActionType}) => {} 获取 setFieldsValue,否则选中后无法回显值。

// ✅ 正确 — 使用 formActionType 获取 setFieldsValue
{ label: 'Popup弹窗', field: 'popup', component: 'JPopup',
  componentProps: ({ formActionType }) => {
    const { setFieldsValue } = formActionType;
    return {
      setFieldsValue,
      code: 'fz_sql',
      fieldConfig: [{ source: 'username', target: 'popup' }],
      multi: true,
    };
  }, colProps: { span: 6 } }

// ❌ 错误 — 静态 componentProps,选中后不回显
{ label: 'Popup弹窗', field: 'popup', component: 'JPopup',
  componentProps: { code: 'fz_sql', fieldConfig: [...], multi: false }, colProps: { span: 6 } }

规则5:JAreaLinkage 必须加 saveCode JAreaLinkage 组件在 formSchema 和 searchFormSchema 中都要加 saveCode: 'region',否则保存/查询的数据格式不一致。

{ label: '省市区', field: 'provinceCityArea', component: 'JAreaLinkage',
  componentProps: { saveCode: 'region' } }

规则6:JPopupDict 字段后端需要 @Dict 翻译 JPopupDict 存储的是 ID 值,列表展示时需要 _dictText 后缀翻译。后端 Entity 必须加 @Dict 注解:

// Entity
@Dict(dictTable = "sys_user", dicText = "realname", dicCode = "id")
private String popDict;
// columns — 使用 _dictText
{ title: 'popup字典', align: 'center', dataIndex: 'popDict_dictText' }

searchFormSchema 组件映射速查表:

formSchema 组件searchFormSchema 组件说明
InputJInput查询用 JInput 支持模糊匹配
InputNumberInputNumber保持不变
JDictSelectTagJDictSelectTag保持不变
JDictSelectTag(radio)JDictSelectTag查询不需要 radio 样式
JCheckboxJSelectMultiple多选改为下拉多选
JSelectMultipleJSelectMultiple保持不变
JSwitchJDictSelectTag(yn)查询用下拉选 是/否
JSearchSelectJSearchSelect保持不变
JSelectUserByDeptJSelectUserByDept保持不变
JSelectDeptJSelectDept保持不变
JCategorySelectJCategorySelect保持不变
JTreeSelectJTreeSelect保持不变
DatePicker(日期)RangePicker范围查询,配合 fieldMapToTime
DatePicker(日期时间)DatePicker精确查询保持不变
DatePicker(quarter/year/month/week)DatePicker(同picker)可用,需配合 List.vue fieldPickers(见规则7)
TimePickerRangeTime范围查询,配合 fieldMapToNumber
JPopupJPopup(formActionType)必须用 formActionType 回显值
JPopupDictJPopupDict保持不变
JAreaLinkageJAreaLinkage(saveCode)必须加 saveCode: 'region'

规则7:季度/年/月/周需要在查询和保存时都做 getDateByPicker 预处理 DatePicker 的 quarter/year/month/week 变体发送的是精确日期值(如选2025年发2025-01-01),需要用 getDateByPicker 转换为该时段的起始日期。查询和保存两处都需要处理。

// ===== List.vue 查询预处理 =====
import { getDateByPicker } from '/@/utils';

const fieldPickers = reactive({
  quarterVal: 'quarter',
  yearVal: 'year',
  monthVal: 'month',
  weekVal: 'week',
});

// useListPage 的 beforeFetch 中添加
beforeFetch: (params) => {
  if (params && fieldPickers) {
    for (let key in fieldPickers) {
      if (params[key]) {
        params[key] = getDateByPicker(params[key], fieldPickers[key]);
      }
    }
  }
  return Object.assign(params, queryParam);
},

// ===== Modal.vue 保存预处理 =====
import { getDateByPicker } from '/@/utils';

const fieldPickers = reactive({
  quarterVal: 'quarter',
  yearVal: 'year',
  monthVal: 'month',
  weekVal: 'week',
});

const changeDateValue = (formData) => {
  if (formData && fieldPickers) {
    for (let key in fieldPickers) {
      if (formData[key]) {
        formData[key] = getDateByPicker(formData[key], fieldPickers[key]);
      }
    }
  }
};

// handleSubmit 中,validate() 之后、saveOrUpdate() 之前调用
let values = await validate();
changeDateValue(values);  // ← 提交前预处理
await saveOrUpdate(values, isUpdate.value);

规则8:省市区列翻译需要 List.vue 中使用 getAreaTextByCode 插槽 省市区组件保存的是区域编码(如340000/340100/340104),列表中需要翻译为中文名。不能用 customRender,需要在 List.vue 的 <template v-slot:bodyCell> 中添加插槽:

<!-- List.vue template -->
<template v-if="column.dataIndex==='provinceCityArea'">
  {{ getAreaTextByCode(text) }}
</template>
// List.vue script
import { getAreaTextByCode } from '/@/components/Form/src/utils/Area';

规则9:分类字典列翻译需要 List.vue 中手动加载分类数据到字典缓存 render.renderCategoryTree(text, 'pcode') 依赖 getDictItemsByCode(pcode) 从字典缓存读取分类数据,但系统登录时不会自动加载分类字典。需要在 List.vue 中手动调用 loadCategoryData 加载到 store:

// List.vue script
import { loadCategoryData } from '/@/api/common/api';
import { useUserStore } from '/@/store/modules/user';

const userStore = useUserStore();

function initDictConfig() {
  loadCategoryData({ code: 'B03' }).then((res) => {
    if (res) {
      const allDictDate = userStore.getAllDictItems;
      if (!allDictDate['B03']) {
        userStore.setAllDictItems({ ...allDictDate, 'B03': res });
      }
    }
  });
}
initDictConfig();

规则10:范围查询使用 RangePicker/RangeTime + fieldMapToTime/fieldMapToNumber 日期字段适合范围查询时使用 RangePicker(选择开始/结束日期),时间字段使用 RangeTime。同时在 List.vue 的 formConfig 中配置拆分映射:

// searchFormSchema
{ label: '日期(范围查询)', field: 'birthday', component: 'RangePicker',
  componentProps: { valueType: 'Date', placeholder: ['开始日期', '结束日期'] }, colProps: { span: 6 } },
{ label: '时间(范围查询)', field: 'timeVal', component: 'RangeTime', colProps: { span: 6 } },

// List.vue formConfig
fieldMapToTime: [
  ['birthday', ['birthday_begin', 'birthday_end'], 'YYYY-MM-DD'],
],
fieldMapToNumber: [
  ['timeVal', ['timeVal_begin', 'timeVal_end']],
],
后端 QueryGenerator 会自动识别 _begin/_end 后缀生成 >=<= 条件,无需后端特殊处理。

规则11:formSchema 字段校验规则(dynamicRules) 使用 dynamicRules 替代简单的 required,可组合多种校验:

import { rules } from '/@/utils/helper/validator';

// 1. 必填 + 长度校验
{ label: '文本', field: 'name', component: 'Input',
  dynamicRules: ({ model, schema }) => {
    return [
      { required: true, message: '请输入文本!' },
      { pattern: /^.{2,50}$/, message: '请输入2到50位字符!' },
    ];
  } },

// 2. 唯一校验(duplicateCheckRule)
{ label: '编码', field: 'code', component: 'Input',
  dynamicRules: ({ model, schema }) => {
    return [
      { required: true, message: '请输入编码!' },
      { ...rules.duplicateCheckRule('表名', '字段名', model, schema)[0] },
    ];
  } },

// 3. 金额格式校验
{ label: '单价', field: 'amount', component: 'InputNumber',
  dynamicRules: ({ model, schema }) => {
    return [
      { required: false },
      { pattern: /^\d{1,10}$|^(?=\d+\.\d+)[\d.]{2,12}$/, message: '请输入正确的金额!' },
    ];
  } },

// 4. 常用正则模式
// 手机号: /^1[3456789]\d{9}$/
// 邮箱:   /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/
// 网址:   /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/
// 字母:   /^[a-z|A-Z]{2,10}$/
// 密码:   /^.{6,20}$/

规则12:label 只显示业务含义,不要附带组件类型名称 生成的 label 只写业务字段名称,禁止在 label 后面括号标注组件类型。这条规则适用于 formSchema、searchFormSchema、columns 等所有 label/title。

// ✅ 正确
{ label: '课程名称', field: 'courseName', component: 'Input' }
{ label: '课程状态', field: 'status', component: 'JDictSelectTag' }
{ label: '开课日期', field: 'courseDate', component: 'DatePicker' }
{ label: '省市区', field: 'provinceCityArea', component: 'JAreaLinkage' }
{ label: '课程封面', field: 'courseImage', component: 'JImageUpload' }

// ❌ 错误 — 不要在 label 后面加括号标注组件类型
{ label: '课程名称(Input)', ... }
{ label: '课程状态(Select)', ... }
{ label: '开课日期(DatePicker)', ... }
{ label: '省市区(联动)', ... }
{ label: '课程封面(ImageUpload)', ... }
{ label: '关联记录(JPopup)', ... }
{ label: 'Popup回填(他表字段)', ... }

规则13:ERP 风格一对多 — 主表编辑不能用 updateMain ERP 风格下主表和子表各自独立保存。主表 Modal 只提交主表字段,禁止调用 updateMain(),否则会先删除所有子表记录再重新插入空列表,导致子表数据全部丢失。

// ❌ 错误 — ERP 模式使用 updateMain 会清空子表
demoCourseService.updateMain(demoCourse, page.getDemoCourseDetailList(), page.getDemoCourseStudentList());

// ✅ 正确 — ERP 模式使用 updateById 仅更新主表
demoCourseService.updateById(demoCourse);

后端需新增 /addMain(调用 service.save())和 /editMain(调用 service.updateById())两个接口,前端 api.ts 的 save/edit 指向这两个新接口。原有的 /add/edit(saveMain/updateMain)保留给 Tab-in-Modal 风格使用。

规则14:ERP 风格一对多 — handleSuccess 不能清空 selectedRowKeys ERP 风格主表使用单选(radio),选中后通过 provide/inject 传递主表 ID 给子表。子表通过 watch(mainId) 监听变化并重载数据。如果 handleSuccess 清空了 selectedRowKeys,会触发子表 watch 重载为空列表,造成编辑后列表显空,需要刷新才恢复

// ❌ 错误 — 清空选中会触发子表 watch,子表显示空
function handleSuccess() {
  (selectedRowKeys.value = []) && reload();
}

// ✅ 正确 — 只 reload 刷新数据,保持选中状态
function handleSuccess() {
  reload();
}

规则15:ERP 风格一对多 — 子表 FormSchema 必须包含隐藏的外键字段 ERP 模式下子表使用独立 Modal 编辑。FormSchema 中必须包含隐藏的 id 和外键字段(如 demoCourseId),否则新增时无法关联主表,编辑时无法定位记录。

export const subFormSchema: FormSchema[] = [
  { label: '', field: 'id', component: 'Input', show: false },
  { label: '', field: 'demoCourseId', component: 'Input', show: false },  // 外键字段
  // ... 业务字段
];

子表 Modal 的 handleSubmit 中,新增时自动设置外键值:

const mainId = inject('demoCourseId') || '';
// 提交时
if (unref(mainId)) {
  values.demoCourseId = unref(mainId);
}

规则16:ERP 风格一对多 — 子表 searchInfo 必须用 reactive 子表通过 searchInfo 向 BasicTable 传递主表 ID 过滤条件。如果 searchInfo 是普通对象 {},编辑后调用 reload() 时 BasicTable 可能取不到最新的过滤值,导致列表不刷新或显示空数据。必须使用 reactive 包裹。

// ❌ 错误 — 普通对象,编辑后 reload 可能取不到 demoCourseId
const searchInfo = {};

// ✅ 正确 — reactive 对象,确保 BasicTable 能响应数据变化
const searchInfo = reactive<Record<string, any>>({});

同时 watch 中赋值用点语法(与 reactive 风格一致):

watch(demoCourseId, () => {
  searchInfo.demoCourseId = unref(demoCourseId);  // ✅ 点语法
  reload();
  setSelectedRowKeys([]);
});

规则17:ERP 风格一对一子表 — 必须限制只能添加一条记录 一对一子表在列表中只允许存在一条记录。新增前必须检查是否已有数据,已有数据时禁用新增按钮并给出提示。通过 afterFetch 回调跟踪记录数:

const hasDetailRecord = ref(false);

// tableProps 中配置 afterFetch
afterFetch: (data) => {
  hasDetailRecord.value = data && data.length > 0;
  return data;
},

// 新增按钮绑定 :disabled="hasDetailRecord"
<a-button type="primary" @click="handleCreate" :disabled="hasDetailRecord">新增</a-button>

// handleCreate 中二次校验
function handleCreate() {
  if (isEmpty(unref(demoCourseId))) {
    $message.createMessage.warning('请选择一条主表记录');
    return;
  }
  if (hasDetailRecord.value) {
    $message.createMessage.warning('一对一子表只能有一条记录,请编辑现有记录');
    return;
  }
  openModal(true, { isUpdate: false, showFooter: true });
}

规则17.1:ERP 风格子表 — useTable 只返回两个元素,rowSelection 必须手动管理 useTable 返回 [registerTable, methods] 两个元素,不支持第三个解构。子表的 selectedRowKeysrowSelection 必须手动定义,不能从 useTable 解构获取。

// ❌ 错误 — useTable 没有第三个返回值,页面报错
const [registerTable, { reload, setSelectedRowKeys }, { rowSelection, selectedRowKeys }] = useTable({...});

// ✅ 正确 — 手动管理 selectedRowKeys 和 rowSelection
const selectedRowKeys = ref<any[]>([]);
const rowSelection = { type: 'checkbox', selectedRowKeys, onChange: (keys) => { selectedRowKeys.value = keys; } };
const [registerTable, { reload }] = useTable({...});

// watch 中清空选中也要用直接赋值
watch(demoOrderId, () => {
  searchInfo.demoOrderId = unref(demoOrderId);
  reload();
  selectedRowKeys.value = [];  // ✅ 直接赋值,不用 setSelectedRowKeys
});

规则17.2:actionColumn 必须包含 title、dataIndex 和 slots actionColumn 必须同时设置 title: '操作'dataIndex: 'action'slots: {customRender: 'action'},三者缺一不可,否则模板中的 #action 插槽无法匹配,操作按钮不会渲染。此规则适用于所有使用 useTable 的列表页(不仅限于 ERP 风格)。

// ❌ 错误 — 缺少 slots,操作列不显示按钮
actionColumn: {
  title: '操作',
  dataIndex: 'action',
  width: 120,
  fixed: 'right',
}

// ✅ 正确 — 必须包含 title、dataIndex 和 slots
actionColumn: {
  title: '操作',
  dataIndex: 'action',
  width: 120,
  fixed: 'right',
  slots: { customRender: 'action' },
}

规则17.3:ERP 风格 — API 的 saveOrUpdate 必须加 successMessageMode: 'none' defHttp.post 默认会根据后端返回的 message 自动弹一次成功提示。如果 Modal 的 handleSubmit 里又手动调了 $message.success(),会出现两次成功提示。ERP 风格(以及所有风格)的 api.ts 中,saveOrUpdate 函数必须关闭默认提示:

// ✅ 正确 — 关闭 defHttp 自动提示,由 Modal 手动控制
export const saveOrUpdate = (params, isUpdate) => {
  let url = isUpdate ? Api.edit : Api.save;
  return defHttp.post({ url: url, params }, { successMessageMode: 'none' });
};

// 子表独立保存也一样
export const subSaveOrUpdate = (params, isUpdate) => {
  let url = isUpdate ? Api.subEdit : Api.subSave;
  return defHttp.post({ url: url, params }, { successMessageMode: 'none' });
};

// ❌ 错误 — 不加 successMessageMode,加上 Modal 里的手动提示 = 弹两次
return defHttp.post({ url: url, params });

此规则适用于所有表类型(单表、一对多各风格),只要 Modal 中手动调了 $message.success,API 就必须加 {successMessageMode: 'none'}

规则17.4:ERP 风格 — 前端文件结构 ERP 风格与其他一对多风格的核心差异是子表有独立的列表页和 Modal,不使用 JVxeTable。

views/{viewDir}/
├── {EntityName}List.vue              # ERP主页面(主表radio单选 + 下方Tab子表列表)
├── {SubEntity}List.vue               # 子表独立列表组件(每个子表一个)
├── {EntityName}.api.ts               # 全部API(主表/addMain+/editMain + 子表独立CRUD)
├── {EntityName}.data.ts              # 主表 columns/formSchema + 子表 BasicColumn[]/FormSchema[]
└── components/
    ├── {EntityName}Modal.vue          # 主表Modal(仅主表字段)
    └── {SubEntity}Modal.vue           # 子表Modal(inject外键 + 设FK)

ERP 前端关键模式:

  • 主表列表rowSelection: {type: 'radio'}provide('mainId', computed(() =>...))@row-click 选中行
  • 子表列表inject<ComputedRef<string>>('mainId')reactive searchInfowatch(mainId) 刷新,getSubList 空ID保护
  • 子表 Modalinject('mainId'),新增时 values.foreignKey = unref(mainId)
  • data.ts:子表用 BasicColumn[] + FormSchema[](不是 JVxeColumn[]),FormSchema 包含隐藏的 id 和外键字段
  • api.ts:主表 save/edit 指向 /addMain+/editMain(调用 service.save/updateById),子表有完整独立 CRUD

ERP 子表 getSubList 空ID保护:

async function getSubList(params) {
  if (isEmpty(unref(mainId))) {
    return [];  // ← 无主表选中时返回空数组,避免请求报错
  }
  return subTableList({ ...params, foreignKey: unref(mainId) });
}

规则17.5:Flyway SQL 必须包含建表 DDL(新建表场景) 场景B(新建表)生成的 Flyway SQL 文件必须同时包含建表 DDL 和菜单权限 SQL,不能只有菜单 SQL。DDL 放在文件最前面,菜单 SQL 放在后面。

-- ========== 建表 DDL ==========
CREATE TABLE IF NOT EXISTS `xxx` (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='xxx';
-- 子表(如有)
CREATE TABLE IF NOT EXISTS `xxx_detail` (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='xxx';

-- ========== 菜单权限 ==========
INSERT INTO sys_permission ...
INSERT INTO sys_role_permission ...

同时,执行 SQL 到数据库时也必须确保建表 DDL 被执行。 如果分开执行(先建表再菜单),需要两步都完成。

规则17.6:前端 data.ts 字段必须与后端 Entity 完全一致 生成前端 data.ts 时,columns/formSchema/searchFormSchema 的字段必须与后端 Entity 的属性一一对应。不能出现 Entity 没有的字段(如前端有 gdp 但 Entity 没有),也不能遗漏 Entity 有的字段(如 Entity 有 province 但前端没有)。

生成流程: 先生成后端 Entity → 再以 Entity 为基准生成前端 data.ts。如果分步生成,在生成前端时必须先重新读取 Entity 文件确认字段列表。

规则18:内嵌子表风格(expandedRowRender) — 三类数据导出 内嵌子表风格在列表页通过行展开(expandedRowRender)直接显示子表数据,同时在 Modal 中使用 JVxeTable/BasicForm 编辑子表。这要求 data.ts 和 api.ts 中对每个子表都有三类导出

data.ts — 每个子表需要两种列定义:

// 1. BasicColumn[](SubTable 行展开展示用)
export const xxxColumns: BasicColumn[] = [
  { title: '字段', align: 'center', dataIndex: 'field' },
  { title: '字典字段', align: 'center', dataIndex: 'dictField_dictText' },
];

// 2. JVxeColumn[](一对多子表 Modal 中 JVxeTable 内嵌编辑用)
export const xxxJVxeColumns: JVxeColumn[] = [
  { title: '字段', key: 'field', type: JVxeTypes.input, width: '200px' },
];

// 3. FormSchema[](一对一子表 Modal 中 BasicForm 编辑用)
export const xxxFormSchema: FormSchema[] = [
  { label: '字段', field: 'field', component: 'Input' },
];

api.ts — 每个子表需要两种导出:

// 1. URL 字符串(供 Modal 中 requestSubTableData / defHttp.get 使用)
export const queryXxxByMainId = Api.xxxList;

// 2. 函数导出(供 SubTable 组件直接调用,isTransformResponse:false 返回完整响应)
export const xxxListApi = (params) =>
  defHttp.get({ url: Api.xxxList, params }, { isTransformResponse: false });

规则19:内嵌子表风格 — SubTable 组件必须用 watchEffect 加载数据 SubTable 组件通过 props.id 接收主表 ID,使用 watchEffect 监听变化自动加载。API 使用 isTransformResponse: false,返回完整响应对象。后端返回 Result<IPage<T>>,数据在 res.result.records 中,不是 res.result

// subTables/XxxSubTable.vue
const props = defineProps({ id: { type: String, default: '' } });
const loading = ref(false);
const dataSource = ref([]);

watchEffect(() => {
  props.id && loadData(props.id);
});

function loadData(id) {
  dataSource.value = [];
  loading.value = true;
  xxxListApi({ id })
    .then((res) => {
      if (res.success) {
        dataSource.value = res.result.records;  // ✅ 后端返回 IPage<T>,数据在 records 中
      }
    })
    .finally(() => { loading.value = false; });
}

规则20:内嵌子表风格 — List.vue 必须配置 expandedRowKeys + handleExpand 列表页需要 expandedRowKeys 控制展开状态(同时只展开一行),handleExpand 处理展开/收起事件。SubTable 组件通过 v-if="expandedRowKeys.includes(record.id)" 条件渲染,避免未展开时加载数据:

<BasicTable @register="registerTable" :expandedRowKeys="expandedRowKeys" @expand="handleExpand">
  <template #expandedRowRender="{ record }">
    <a-tabs tabPosition="top">
      <a-tab-pane tab="子表一" key="sub1" forceRender>
        <XxxSubTable v-if="expandedRowKeys.includes(record.id)" :id="record.id" />
      </a-tab-pane>
    </a-tabs>
  </template>
</BasicTable>
const expandedRowKeys = ref<any[]>([]);

function handleExpand(expanded, record) {
  expandedRowKeys.value = [];
  if (expanded === true) {
    expandedRowKeys.value.push(record.id);
  }
}

规则21:三种 Modal 布局风格区分

风格Modal 布局标题栏子表组织wrapClassNamerefKeys
Tab-in-Modal (C9)radio-group 标题栏切换,主表/子表同级用 v-show#title 插槽 + a-radio-groupv-show 切换j-cgform-tab-modal包含主表 key
内嵌子表 (C12)上面主表 BasicForm + 下面子表 a-tabs普通 :title 属性a-tabs + a-tab-pane只有子表 key
ERP (C11)仅主表 BasicForm普通 :title 属性无(子表独立 CRUD)

只有 Tab-in-Modal (C9) 使用 radio-group 标题栏切换!内嵌子表和 ERP 都使用原始布局(上面主表,下面子表)。

内嵌子表 (C12) 的 Modal 混合场景(一对一 + 一对多)同样使用 useJvxeMethod + validateSubForm,但注意 refKeys 只包含子表 key,不包含主表 key,因此 validateForm(index) 的 index 从 0 开始计数子表位置。

规则22:内嵌子表风格 — 前端文件结构

views/{viewDir}/
├── {EntityName}List.vue              # 主列表(含 expandedRowRender)
├── {EntityName}.api.ts               # 全部API
├── {EntityName}.data.ts              # 列定义 + 表单配置
├── components/
│   └── {EntityName}Modal.vue         # 编辑弹窗(主表 + Tabs子表)
└── subTables/
    ├── {SubEntity}SubTable.vue       # 子表展示组件(每个子表一个)
    └── ...

规则23:内嵌子表风格 — 子表提交字段名必须与后端 VO 属性名一致 Modal 提交时 JSON 中子表数据的 key 必须与后端 Page VO(如 XxxPage)中的 List<SubEntity> 属性名一致,不能用 API URL 的变量名。

// ❌ 错误 — 用了 API URL 变量名
{
  ...mainValues,
  queryDemoCourseDetail: [detailValues],    // 后端无法反序列化
  queryDemoCourseStudent: studentTableData, // 后端收到 null
}

// ✅ 正确 — 与后端 VO 属性名一致
{
  ...mainValues,
  demoCourseDetailList: [detailValues],     // 对应 DemoCoursePage.demoCourseDetailList
  demoCourseStudentList: studentTableData,  // 对应 DemoCoursePage.demoCourseStudentList
}

命名规则: 子表字段名 = {子表实体名首字母小写}List,如 DemoCourseDetaildemoCourseDetailList后果: 字段名不匹配时后端反序列化得到 null,saveMain/updateMain 会先删除旧子表记录再插入空列表,子表数据全部丢失

规则24:内嵌子表风格 — handleSuccess 必须清空 expandedRowKeys 列表刷新后如果 expandedRowKeys 仍保留旧值,SubTable 的 v-if="expandedRowKeys.includes(record.id)" 仍为 true 但 id prop 没变化,watchEffect 不会重新触发,导致展示旧数据。

function handleSuccess() {
  expandedRowKeys.value = [];  // 必须清空,确保刷新后重新展开时 SubTable 重新加载
  (selectedRowKeys.value = []) && reload();
}

规则24.1:内嵌子表风格 — 后端子表查询必须返回 Result<IPage<T>>,不能返回 Result<List<T>> SubTable 前端通过 res.result.records 访问数据(见规则19),因此后端 Controller 的子表查询接口必须List<T> 包装成 IPage<T> 再返回。如果直接返回 Result<List<T>>,前端 res.result.records 为 undefined,子表数据不显示。

// ✅ 正确 — 返回 IPage<T>,前端通过 res.result.records 获取数据
@GetMapping(value = "/queryXxxByMainId")
public Result<IPage<Xxx>> queryXxxListByMainId(@RequestParam(name = "id", required = true) String id) {
    List<Xxx> list = xxxService.selectByMainId(id);
    IPage<Xxx> page = new Page<>();
    page.setRecords(list);
    page.setTotal(list.size());
    return Result.OK(page);
}

// ❌ 错误 — 返回 List<T>,前端 res.result.records 为 undefined
@GetMapping(value = "/queryXxxByMainId")
public Result<List<Xxx>> queryXxxListByMainId(@RequestParam(name = "id") String id) {
    List<Xxx> list = xxxService.selectByMainId(id);
    return Result.OK(list);  // 前端拿到的 res.result 是数组,没有 .records 属性
}

此规则同时适用于一对多和一对一子表的查询接口。 一对一子表的 Form 组件 initFormData 也通过 res.result.records[0] 获取数据。

规则24.2:内嵌子表风格 — tableRefs 只能包含 JVxeTable 的 ref,禁止包含 Form 组件 ref useJvxeMethod 返回的 handleChangeTabs 内部会调用 tableRefs[key]?.value?.resetScrollTop(0) 重置滚动位置。如果 tableRefs 包含了一对一子表的 Form 组件 ref,resetScrollTop 方法不存在,会报错 resetScrollTop is not a function

// ✅ 正确 — tableRefs 只包含 JVxeTable 的 ref
const demoProjTask = ref();          // JVxeTable ref
const demoProjBudgetForm = ref();    // Form 组件 ref(不放入 tableRefs)
const tableRefs = { demoProjTask };  // 只有 JVxeTable

// ❌ 错误 — tableRefs 包含 Form ref,切换 tab 时报错 resetScrollTop is not a function
const tableRefs = { demoProjBudgetForm, demoProjTask };  // Form 组件没有 resetScrollTop 方法

规则24.3:内嵌子表风格 — 一对一子表 Form 必须用 defineComponent(Options API),禁止用 <script setup> 一对一子表 Form 组件(如 XxxForm.vue)需要被父组件通过 ref 调用 initFormData/getFormData/validateForm/resetFields 方法。使用 <script setup> + defineExpose 暴露方法时,父组件调用 xxxForm.value.validateForm() 会报 validateForm is not a function必须使用 defineComponent + setup() + return {} 模式。

// ✅ 正确 — defineComponent + setup return(方法可被父组件通过 ref 调用)
export default defineComponent({
    name: "XxxForm",
    components: { BasicForm },
    emits: ['register'],
    props: {
        disabled: { type: Boolean, default: false }
    },
    setup(props, { emit }) {
        const [registerForm, { setProps, resetFields, setFieldsValue, getFieldsValue, validate, scrollToField }] = useForm({
            labelWidth: 150,
            schemas: xxxFormSchema,
            showActionButtonGroup: false,
            baseColProps: { span: 24 }
        });

        function initFormData(url, id) {
            if (id) {
                defHttp.get({ url, params: { id } }, { isTransformResponse: false }).then(res => {
                    res.success && res.result.records && res.result.records.length > 0 && setFieldsValue({ ...res.result.records[0] });
                })
            }
            setProps({ disabled: props.disabled })
        }

        function getFormData() {
            let formData = getFieldsValue();
            Object.keys(formData).map(k => {
                if (formData[k] instanceof Array) { formData[k] = formData[k].join(',') }
            });
            return [formData];  // 返回数组!后端用 List<Entity> 接收
        }

        function validateForm(index) {
            return new Promise((resolve, reject) => {
                validate().then(() => { return resolve() })
                    .catch(({ errorFields }) => {
                        return reject({ error: VALIDATE_FAILED, index, errorFields, scrollToField })
                    })
            })
        }

        return { registerForm, resetFields, initFormData, getFormData, validateForm }
    }
})

// ❌ 错误 — <script setup> + defineExpose(父组件调用报 xxx is not a function)
// <script lang="ts" setup>
// const { initFormData, getFormData, validateForm, resetFields } = useXxx();
// defineExpose({ initFormData, getFormData, validateForm, resetFields });
// </script>

规则24.4:内嵌子表风格 — Modal 必须使用 useJvxeMethod 6参数模式 内嵌子表 (C12) 的 Modal 与默认/原始布局风格完全一致,同样使用 useJvxeMethod 的 6 参数模式。不要自己编写 handleSubmituseJvxeMethod 返回的 handleSubmit 已内置表单校验、子表数据收集、validateSubForm 调用等完整逻辑。

// Modal.vue 关键结构
const refKeys = ref(['demoProjBudget', 'demoProjTask']);  // 只有子表 key,不含主表
const activeKey = ref('demoProjBudget');
const demoProjBudgetForm = ref();    // 一对一子表 Form ref
const demoProjTask = ref();          // 一对多子表 JVxeTable ref
const tableRefs = { demoProjTask };  // ⚠️ 只包含 JVxeTable ref!

const demoProjTaskTable = reactive({
    loading: false,
    dataSource: [],
    columns: demoProjTaskJVxeColumns
});

// ✅ 6参数调用 useJvxeMethod
const [handleChangeTabs, handleSubmit, requestSubTableData, formRef] = useJvxeMethod(
    requestAddOrEdit, classifyIntoFormData, tableRefs, activeKey, refKeys, validateSubForm
);

// classifyIntoFormData — 组装提交数据
function classifyIntoFormData(allValues) {
    let main = Object.assign({}, allValues.formValue)
    return {
        ...main,
        demoProjBudgetList: demoProjBudgetForm.value.getFormData(),     // 一对一:调用 Form 的 getFormData()
        demoProjTaskList: allValues.tablesValue[0].tableData,           // 一对多:从 tablesValue 取(index 对应 tableRefs 中的顺序)
    }
}

// validateSubForm — 校验所有一对一子表
function validateSubForm(allValues) {
    return new Promise((resolve, reject) => {
        Promise.all([
            demoProjBudgetForm.value.validateForm(0),  // index 对应 refKeys 中的位置
        ]).then(() => {
            resolve(allValues)
        }).catch(e => {
            if (e.error === VALIDATE_FAILED) {
                activeKey.value = e.index == null ? unref(activeKey) : refKeys.value[e.index]
                if (e.errorFields) {
                    const firstField = e.errorFields[0];
                    if (firstField) { e.scrollToField(firstField.name, { behavior: 'smooth', block: 'center' }); }
                }
            } else { console.error(e) }
        })
    })
}

// Modal 打开时加载子表数据
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
    await reset();
    setModalProps({ confirmLoading: false, showCancelBtn: data?.showFooter, showOkBtn: data?.showFooter });
    isUpdate.value = !!data?.isUpdate;
    formDisabled.value = !data?.showFooter;
    if (unref(isUpdate)) {
        await setFieldsValue({ ...data.record });
        // 一对一子表:调用 Form 的 initFormData(传 URL 字符串 + 主表 id)
        demoProjBudgetForm.value.initFormData(queryDemoProjBudgetByMainId, data?.record?.id);
        // 一对多子表:调用 requestSubTableData(传 URL 字符串 + 参数 + reactive 表对象)
        requestSubTableData(queryDemoProjTaskByMainId, { id: data?.record?.id }, demoProjTaskTable);
    }
    setProps({ disabled: !data?.showFooter });
});

// reset — 重置所有表单和子表数据
async function reset() {
    await resetFields();
    activeKey.value = 'demoProjBudget';
    demoProjBudgetForm.value.resetFields();       // 一对一子表重置
    demoProjTaskTable.dataSource = [];             // 一对多子表清空数据
}

规则24.5:内嵌子表风格 — api.ts 双导出模式(URL字符串 + API函数) 每个子表需要两种导出方式,分别供 Modal 和 SubTable 使用:

// api.ts
enum Api {
  // ...
  demoProjBudgetList = '/demo/demoProj/queryDemoProjBudgetByMainId',
  demoProjTaskList = '/demo/demoProj/queryDemoProjTaskByMainId',
}

// 1. URL 字符串导出 — 供 Modal 中 requestSubTableData / initFormData 使用
//    这些函数内部用 defHttp.get 默认 isTransformResponse:true,拿到的是 result 部分
export const queryDemoProjBudgetByMainId = Api.demoProjBudgetList;
export const queryDemoProjTaskByMainId = Api.demoProjTaskList;

// 2. 函数导出 — 供 SubTable 组件使用,isTransformResponse:false 返回完整 {success, result, ...}
export const demoProjBudgetListApi = (params) =>
  defHttp.get({ url: Api.demoProjBudgetList, params }, { isTransformResponse: false });
export const demoProjTaskListApi = (params) =>
  defHttp.get({ url: Api.demoProjTaskList, params }, { isTransformResponse: false });

为什么需要两种?

  • Modal 中 requestSubTableData(url, params, tableObj) 内部执行 defHttp.get({url, params}) 默认 isTransformResponse:true,拿到的直接是 result 对象(即 IPage),然后取 res.records || res 赋值给 tableObj.dataSource
  • Modal 中 initFormData(url, id) 内部执行 defHttp.get({url,params:{id}},{isTransformResponse:false}),拿到完整响应,通过 res.result.records[0] 取第一条数据
  • SubTable 中 xxxListApi({id})isTransformResponse:false,通过 res.result.records 取数据数组

规则25:Tab-in-Modal 风格 — Modal 必须用 radio-group 标题栏,不是 a-tabs Tab-in-Modal 风格的 Modal 使用 a-radio-group + a-radio-button 在标题栏切换主表/子表区域,各区域用 v-show 显隐(不是 a-tab-pane)。必须设置 wrapClassName="j-cgform-tab-modal" 启用专属样式,contentArea div 包裹所有表单区域。

<BasicModal ref="modalRef" destroyOnClose wrapClassName="j-cgform-tab-modal" ...>
  <template #title>
    <div class="titleArea">
      <div class="title">{{ title }}</div>
      <div class="right">
        <a-radio-group v-model:value="activeKey">
          <a-radio-button v-for="item in tabNav" :value="item.tableName">{{ item.tableTxt }}</a-radio-button>
        </a-radio-group>
      </div>
    </div>
  </template>
  <div class="contentArea">
    <BasicForm v-show="activeKey == refKeys[0]" ... />
    <JVxeTable v-show="activeKey == 'subTableKey'" ... />
    <XxxForm v-show="activeKey == 'subOneKey'" ... />
  </div>
</BasicModal>

规则26:Tab-in-Modal 风格 — 一对一子表必须抽成独立 Form.vue 组件 一对一子表不能用内联 BasicForm(useForm + registerDetailForm),必须抽成独立的 Vue 组件(如 XxxForm.vue),使用 Options API(defineComponent)暴露以下方法:

  • initFormData(url, id) — 加载数据,内部用 defHttp.get({url,params:{id}},{isTransformResponse:false})
  • getFormData() — 返回 [formData] 数组(后端用 List<Entity> 接收)
  • validateForm(index) — 校验表单,index 对应 refKeys 数组位置
  • resetFields() — 重置表单

规则27:Tab-in-Modal 风格 — useJvxeMethod 第6个参数 validateSubForm 有一对一子表时,useJvxeMethod 必须传入第6个参数 validateSubForm,该函数在 handleSubmit 中自动调用。不需要自写 handleSubmit

const [handleChangeTabs, handleSubmit, requestSubTableData, formRef] = useJvxeMethod(
  requestAddOrEdit, classifyIntoFormData, tableRefs, activeKey, refKeys, validateSubForm
);
// validateSubForm 内部调用所有一对一子表的 validateForm(index)

规则28:Tab-in-Modal 风格 — refKeys 和 JVxeTable ref 名必须一致 refKeys 数组的值必须与 v-show 条件、JVxeTable 的 ref 名、activeKey 的值完全一致。refKeys[0] 固定为主表 key。

const refKeys = ref(['mainKey', 'subManyKey', 'subOneKey']);
// JVxeTable: ref="subManyKey"   v-show="activeKey == 'subManyKey'"
// Form:      ref="subOneForm"   v-show="activeKey == 'subOneKey'"

规则29:树表 — Mapper 接口参数名必须与 XML 参数名一致 树表 Mapper 接口和 XML 的参数命名必须严格对齐,参考 JeecgBoot 代码生成器的标准输出:

// Mapper 接口
void updateTreeNodeStatus(@Param("id") String id, @Param("status") String status);
List<SelectTreeModel> queryListByPid(@Param("pid") String pid, @Param("query") Map<String, String> query);
<!-- XML 中对应的参数名 -->
<update id="updateTreeNodeStatus">
    update {{tableName}} set has_child = #{status} where id = #{id}
</update>
<select id="queryListByPid" resultType="org.jeecg.common.system.vo.SelectTreeModel">
    select id as "key", {{displayField}} as "title",
           (case when has_child = '1' then 0 else 1 end) as isLeaf,
           {{pidField}} as parentId
    from {{tableName}} where {{pidField}} = #{pid}
    <if test="query != null">
        <foreach collection="query.entrySet()" item="value" index="key">
            and ${key} = #{value}
        </foreach>
    </if>
</select>

规则30:树表 — Modal 的 updateSchema 禁止传递 treeData JTreeSelect 组件通过 dict 配置(如 "demo_category,category_name,id")自行从后端加载树数据。updateSchema 只需传递 hiddenNodeKey禁止传递 treeData 到 componentProps,否则会导致下拉框显示异常(label 变 value、内容不全)。

// ❌ 错误 — 传递 treeData 会干扰 JTreeSelect 内部数据管理
updateSchema([{ field: 'pid', componentProps: { treeData, hiddenNodeKey: data.record.id } }]);

// ✅ 正确 — 只传 hiddenNodeKey
updateSchema([{ field: 'pid', componentProps: { hiddenNodeKey: data.record.id } }]);

treeData 变量仅用于 Modal 内部的 getExpandKeysByPid 函数(计算展开路径),不用于 JTreeSelect 组件。

规则31:树表 — handleSuccess 必须遵循参考代码的刷新策略 树表 handleSuccess 回调接收 {isUpdate, values, expandedArr, changeParent} 四个参数,刷新策略如下:

async function handleSuccess({isUpdate, values, expandedArr, changeParent}) {
  if (isUpdate) {
    if (changeParent) {
      reload();  // 父节点变更,全量刷新
    } else {
      // 父节点未变,重新查询单条记录(含 _dictText 翻译)
      let data = await list({ id: values.id, pageSize: 1, pageNo: 1, pid: values['pid'] });
      if (data && data.records && data.records.length > 0) {
        updateTableDataRecord(values.id, data.records[0]);
      } else {
        updateTableDataRecord(values.id, values);
      }
    }
  } else {
    if (!values['id'] || !values['pid']) {
      reload();  // 新增根节点,全量刷新
    } else {
      // 新增子节点,逐级展开
      expandedRowKeys.value = [];  // ← 必须先清空!
      for (let key of unref(expandedArr)) {
        await expandTreeNode(key);
      }
    }
  }
}

关键点:

  1. 编辑后必须重新查询单条数据(list({id,...})),不能直接用 form values 更新,否则字典列不显示翻译文本
  2. 新增子节点前必须先清空 expandedRowKeysexpandedRowKeys.value = []),否则展开逻辑出错
  3. 新增根节点判断条件是 !values['id'] ||!values['pid'](form 提交后 id 为空因为是后端自动生成)

规则32:树表 — getDataByResult 的 loading 占位节点必须使用正确的显示字段 getDataByResult 为有子节点的数据添加 loading 占位,占位节点的显示字段名必须与表的树节点主显示列一致:

// 如果 columns 第一列 dataIndex 是 'categoryName'
let loadChild = { id: item.id + '_loadChild', categoryName: 'loading...', isLoading: true }
// 如果 columns 第一列 dataIndex 是 'name'
let loadChild = { id: item.id + '_loadChild', name: 'loading...', isLoading: true }

使用错误的字段名(如统一用 name)会导致 loading 文本不显示。

规则33:树表 — 前端代码必须完全对齐参考代码结构 树表前端与单表有根本性差异,生成时必须严格按以下清单:

List.vue 必须包含:

  • isTreeTable: true in tableProps
  • expandedRowKeys ref + @expand="handleExpand" + @fetch-success="onFetchSuccess"
  • beforeFetch 必须添加 params.hasQuery = "true"
  • queryParam reactive + superQueryConfig + 高级查询组件
  • v-auth 权限指令在所有操作按钮上
  • getTableAction 主操作(编辑 + 添加下级)+ getDropDownAction 下拉操作(详情 + 删除)
  • actionColumn 必须设置 width: 240fixed: 'right'
  • handleExpand 中必须处理 result.recordsresult = result.records? result.records: result
  • handleAddSub 传递 {pidField: record.id} 作为 record(pidField 即 pid 字段名)
  • batchHandleDelete 必须过滤 loadChild 占位:ids.filter(item =>!item.includes('loadChild'))

Modal.vue 必须包含:

  • let model: Nullable<Recordable> = null 保存编辑前数据
  • treeData ref 用于 getExpandKeysByPid(不传给 JTreeSelect)
  • setModalProps({showOkBtn:!!!data?.hideFooter}) 详情模式隐藏确认按钮
  • setProps({disabled:!!data?.hideFooter}) 详情模式禁用表单
  • isDetail.value =!!data?.showFooter(注意是 showFooter 不是 hideFooter)
  • 编辑时 updateSchema 只传 hiddenNodeKey
  • handleSubmitemit('success', {isUpdate, values, expandedArr, changeParent})
  • changeParent 判断:model!= null && (model['pid']!= values['pid'])
  • scrollToField 在 catch 块中处理校验错误定位
  • baseRowStyle: {padding: "0 20px"} 表单行内边距

data.ts 必须包含:

  • id 隐藏字段放在 formSchema 最后
  • pid 字段用 JTreeSelect 组件,配置 dict/pidField/pidValue/hasChildField
  • 树表主显示列的 align 必须是 'left'(便于显示层级缩进)
  • superQuerySchema 每项需包含 type 字段

api.ts 必须包含:

  • rootList(不是 list)、childListgetChildListBatchloadTreeData 四个树表专用接口
  • getChildListBatch 必须使用 {isTransformResponse: false} 返回完整 Result 对象

智能字段推导中的字典关键词

当用户描述字段时,按以下关键词自动推导使用哪种字典:

用户描述关键词推导字典类型推荐控件
"状态"、"类型"、"级别"、"优先级"系统字典(先搜索 sys_dict 匹配)JDictSelectTag
"区域"、"地区"、"分类"、"类目"、"树形选择"分类字典(搜索 sys_category 匹配)JCategorySelect
"部门"、"组织"、"归属"表字典(关联 sys_depart)JDictSelectTag(表字典)
"用户"、"负责人"、"经办人"用户选择组件(非字典)JSelectUserByDept

项目路径

重要: 以下为默认路径。实际路径以 CLAUDE.md 和 memory 中的配置为准,优先使用它们。
类别路径
后端根读取 CLAUDE.md 中的 Backend 路径
前端根读取 CLAUDE.md 中的 Frontend 路径
后端代码{module}/src/main/java/org/jeecg/modules/{entityPackage}/
前端代码src/views/{viewDir}/
Flyway SQLjeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/

命名约定

  • 表名:snake_case(如 biz_goods
  • 实体名:表名转 PascalCase(如 BizGoods
  • entityPackage:表名前缀或用户指定(如 biz
  • bussiPackage 固定:org.jeecg.modules
  • 权限编码{entityPackage}:{tableName}:add/edit/delete/deleteBatch/exportXls/importExcel

智能字段推导

用于新建表场景(从自然语言推导),或已有表但字段无注释时的补充推导:

语义关键词dbTypeJava 类型vue3 组件vue3Native 组件
名称/标题/编码varchar(100)StringInputa-input
金额/价格/费用decimal(10,2)BigDecimalInputNumbera-input-number
数量/数目/个数intIntegerInputNumbera-input-number
状态/类型/级别varchar(10)StringJDictSelectTagJDictSelectTag
是否/开关varchar(2)StringSwitcha-switch
日期/生日dateDateDatePickera-date-picker
时间/日期时间datetimeDateDatePicker(showTime)a-date-picker(showTime)
备注/描述/说明textStringInputTextAreaa-textarea
内容/富文本textStringJEditorJEditor
图片/头像/照片varchar(1000)StringJImageUploadJImageUpload
文件/附件varchar(1000)StringJUploadJUpload
用户/负责人varchar(32)StringJSelectUserByDeptJSelectUserByDept
部门/组织varchar(32)StringJSelectDeptJSelectDept
排序/序号intIntegerInputNumbera-input-number

已有表场景的 DB类型→控件 映射(当字段无注释时使用):

DB列类型Java类型默认前端控件
varchar(n) n<=200StringInput
varchar(n) n>200StringInputTextArea
text / longtextStringInputTextArea
int / tinyintIntegerInputNumber
bigintLongInputNumber
decimal / double / floatBigDecimalInputNumber
dateDateDatePicker
datetime / timestampDateDatePicker(showTime)

主键策略(根据已有表结构自适应)

表DDL中的主键定义Java类型@TableId说明
int AUTO_INCREMENTInteger@TableId(type = IdType.AUTO)int自增主键
bigint AUTO_INCREMENTLong@TableId(type = IdType.AUTO)bigint自增主键
varchar(36) / varchar(32) 无AUTO_INCREMENTString@TableId(type = IdType.ASSIGN_ID)JeecgBoot标准字符串主键
bigint 无AUTO_INCREMENTLong@TableId(type = IdType.ASSIGN_ID)雪花ID

注意: 当主键为 Integer/Long 类型时,Controller 中 deletequeryById 的参数类型也要对应调整。

系统字段(按实际表结构判断)

不是所有表都有系统字段! 生成前必须检查表是否实际包含这些字段,只生成表中存在的字段

字段说明不存在时的处理
create_by创建人不生成该属性
create_time创建时间不生成该属性
update_by更新人不生成该属性
update_time更新时间不生成该属性
sys_org_code所属部门不生成该属性

如果是新建表(用户自然语言描述需求),则默认添加全部系统字段。 如果是已有表(用户指定了表名且数据库中已存在),则必须根据实际 DDL 来决定。

树表额外字段:pidhas_child(同样需检查是否实际存在)。

参考文件

生成代码前,必须读取 同目录下的 codegen-reference.md 获取完整代码模板骨架。

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

34.57%
按下载量换算68

Claude

30.64%
按下载量换算60

Cursor

16.71%
按下载量换算33

Gemini CLI

8.25%
按下载量换算16

安全审计

Gen Agent Trust Hub

可疑

Socket

通过

Snyk

未通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills