mcpbeat

Jeecg Codegen

jeecgboot/jeecg-codegen

Use when user asks to generate JeecgBoot CRUD code, create a new module, add/modify fields on existing module, or says "代码生成", "生成代码", "创建模块", "新增功能", "建表", "加字段", "加一个字段", "增加字段", "新增字段", "修改字段", "删除字段", "generate code", "new entity", "add field

116k tokens
context cost
the whole folder, loaded on every use
10
files
instructions only
0
copies elsewhere
how many repositories repackaged it
209
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/jeecgboot/skills --skill jeecg-codegen

What comes with it

300 800 bytes besides the instruction
codegen-reference.md
docs/skill-usage-guide.md
parallel-generation-mode.md
references/ref-d-misc.md
references/ref-menu-sql.md
uniapp/references/code-templates.md
uniapp/references/component-patterns.md
uniapp/references/project-architecture.md

What it tells the agent to use

found in the instruction text
Grep reads your files
Task spawns other agents

The instruction itself

44 sections, as written by the author

JeecgBoot 代码生成器

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

主数据复用规则

> 重要: 生成代码涉及的字典、角色、用户、部门等主数据,必须遵循"先查后建"原则。

> 使用 jeecg-system skill 的 system_utils.py 查询和管理主数据。

> 详见 ../jeecg-system/SKILL.md

⛔ 字典创建必须写入 Flyway SQL,禁止直接走 API 创建

> 代码生成场景下,新建字典的"建"必须落到 Flyway SQL 文件,禁止调用 find_or_create_dict() / create_dict() 等 API 在远程服务器上直接创建。

>

> Why: 代码生成产物(Entity、前端、Flyway SQL)会通过 git 提交并部署到测试/预发/生产环境。如果字典只通过 API 在当前开发环境创建,部署到线上时线上数据库没有该字典,前端下拉框会空白、列表 _dictText 翻译失效。Flyway SQL 跟着代码走,所有环境拉到代码后执行迁移都会自动建上,是唯一能保证环境一致性的方式。

>

> How to apply:

> - 查询字典 → 走 API 或 MySQLjeecg-system skill 的 query-dicts / query-dict),用于"先查后建"中的"查"

> - 创建字典 → 写入当次的 Flyway SQL 文件sys_dict INSERT + sys_dict_item 批量 INSERT),用于"先查后建"中的"建"

> - 禁用find_or_create_dict()create_dict() 等 system_utils 中的字典创建函数(在代码生成 skill 中不能调用)

> - 同理适用于:分类字典 sys_category 节点新建 — 也必须写入 Flyway SQL,禁止走 /sys/category/add API

>

> 例外: 角色、审批角色、用户绑定关系等"运行时主数据",由于跨业务可复用,可走 API 创建(按 jeecg-system 原流程)。字典与分类字典是"配置数据",必须走 SQL。

⛔ 接口禁止猜测规则

> 严格禁止猜测任何 API 接口路径或参数。 AI 不得根据命名惯例、框架约定或已知路径拼凑接口地址后直接调用。

>

> 所有接口调用必须来源于以下之一:

> 1. 用户明确提供的接口文档或地址

> 2. jeecg-system skill 中已记录的接口

> 3. 通过 jeecg-system skill 查询后确认的接口

>

> 违反此规则即使偶然成功也视为错误操作,因为猜测成功不代表行为合规。

⛔ 写文件前的强制自检清单(高频翻车点)

> 以下两条是 AI 凭"框架直觉"最常犯错的地方,文件写出去几乎必现 bug,调试成本极高。每次执行 Step 4 写后端/前端文件之前,必须逐条 self-check。

>

> ### 翻车点 1:每个文件的路径必须与 SKILL/reference 描述完全一致

>

> 写每一个文件之前,必须先在 codegen-reference.md 顶部"文件清单"章节中找到对应文件的路径模板,逐字符比对后再写入。禁止凭"Spring Boot/JeecgBoot 框架直觉"猜测路径。

>

> ### 翻车点 2:FormSchema 必含隐藏 id 字段

>

> 所有 FormSchema(主表 Modal 表单、一对一子表 Form、ERP 风格子表 Form、树表 Modal 表单)首位必须包含:

>

> `typescript

> { label: '', field: 'id', component: 'Input', show: false },

> `

>

> 为什么这是铁律: BasicFormgetFieldsValue() 只返回 schema 中声明过的字段。即使 Modal 打开时通过 setFieldsValue({ ...data.record })id 写入了表单状态,schema 没声明,提交时 getFieldsValue() 也会丢弃它。最终后端收到 entity.id == nullgetById(null) 返回 null,Controller 返回 Result.error("未找到对应数据")编辑功能直接报错。

>

> 位置统一规定:放在 FormSchema 数组首位(不要纠结"最后还是最前",统一首位)。

>

> 这两条规则不需要用户询问、不需要场景判断、不需要选项确认。100% 强制,100% 一致。

生成模式

> 进入交互流程之前,必须先与用户确认本次使用的生成模式。 任何场景下都不要默默选择,必须显式告知用户当前模式;用户回复"确认"即采用默认。

本 skill 提供两种生成模式:

| 模式 | 状态 | 默认 | 说明 |

|------|------|------|------|

| 串行生成(Serial) | Stable | ✅ 默认 | 主 Agent 顺序生成后端 → 前端 → SQL,全程单线执行,稳定可靠 |

| 并行生成(Parallel) | ⚠️ Beta — 可能不稳定 | ❌ | 派发两个 SubAgent 并行生成前端 / 后端代码,主 Agent 负责契约冻结与跨端校验。详见同目录下 parallel-generation-mode.md |

模式确认话术(必须执行)

进入 Step 0 之前,主 Agent 必须先输出类似以下消息,等用户回复后再继续:

本次代码生成将使用【串行生成模式】(默认,稳定)。
如需使用【并行生成模式(Beta)】以缩短耗时,请明确告知。
注意:并行模式当前为 Beta 版本,可能出现前后端字段命名漂移、API URL 不一致、
字典编码错位、FormSchema 隐藏 id 字段遗漏等问题,不确定时建议使用默认串行模式。

模式选择规则

  • 用户未明确要求并行 → 一律走 串行模式,不要主动建议并行。
  • 用户明确要求并行("并行"、"分头生成"、"前后端同时来"、"用 subagent 并行"等关键词) → 进入 并行模式,但必须先复述一遍 Beta 风险并等用户再次确认后才正式启动。
  • 增量字段修改场景(场景 C) → 强制串行,即使用户要求并行也要拒绝并解释(SubAgent 双重压缩会丢失已有代码细节)。
  • 一对多 + ERP / vue3Native / 自定义增强等复杂场景 → 强烈建议串行,需向用户说明风险后由用户决定。

并行模式启动条件(全部满足才进入)

  • 用户明确选择了并行模式。
  • 用户已被告知 Beta 风险并再次确认
  • 操作类型是"全量生成"(场景 A 或 B),不是"增量修改"(场景 C)。
  • 主数据复用前置条件已就绪(字典已查/已建,目标数据库已确认)。

满足后,主 Agent 必须读取 parallel-generation-mode.md 并严格按其规范执行(契约冻结 → 派发 SubAgent → 跨端校验 → 输出清单)。任一环节失败 → 按该文档第 6 节"回退策略"切回串行从头来过。

> 铁律不变: 即使选择并行模式,本章上方的"⛔ 接口禁止猜测"、"⛔ 字典创建必须写入 Flyway SQL"、"⛔ 写文件前的强制自检清单(路径 + FormSchema id)"、以及"⛔ 铁律:Step 2 + Step 3 是不可跳过的硬性停止门" 全部仍然 100% 强制 —— 通过派发 prompt 传达给 SubAgent。

交互流程

> ### ⛔ 铁律:Step 2 + Step 3 是不可跳过的硬性停止门

>

> 全量生成必须严格按顺序执行 Step 0 → Step 1 → Step 2 → 等用户回复 → Step 3 → 等用户确认 → Step 4。

> 在用户明确回复"确认"(或等价表述)之前,绝对禁止开始生成任何代码、创建任何文件、执行任何 SQL。

>

> 以下念头出现时立刻停下,它们都是合理化跳过确认的借口:

>

> | 借口 | 现实 |

> |------|------|

> | "需求描述足够清楚,可以直接推断" | 用户没有确认 ≠ 用户已认可。字段类型、路径、风格都可能偏差。 |

> | "选项都是默认值,不需要问" | 默认值是否适用由用户决定,不由 AI 决定。 |

> | "先生成再改很方便" | 用户不得不事后检查所有文件,浪费双方时间。 |

> | "用户说'Tab风格'已经隐含了风格选择" | 只说明了一个选项,其他9项仍需展示给用户确认。 |

> | "Skill 加载太慢,直接生成更高效" | 效率不是跳过确认的理由。 |

>

> 违反此铁律的代价: 用户发现问题后,所有已生成文件都需要重新生成或逐一修改。

Step 0 前置:判断前端目标(PC 端 / 移动端 / 两者都要)

> 此步骤必须在 Step 0 之前执行。前端目标直接决定 Step 2 中需要询问哪些选项。

识别移动端关键词: "移动端"、"手机端"、"UniApp"、"uniapp"、"APP端"、"小程序"、"H5"、"移动页面"、"APP页面"

根据用户描述判断:

| 用户意图 | 判定结果 | Step 2 调整 |

|---------|---------|------------|

| 明确只要移动端(含以上关键词,无 PC 相关词) | 仅移动端 | 跳过 PC 前端选项(选项2、3、6、8、9),改为询问 UniApp3 项目根路径 |

| 明确只要 PC 端(含 "vue3"、"PC端"、"web端" 等词,无移动端词) | 仅 PC 端 | 按原流程 |

| 两者都提到,或描述模糊(如"前后端代码"、"CRUD代码") | 不确定 | 在 Step 2 前先询问用户:"请问需要生成哪端的前端代码?① 仅 PC 端(Vue3) ② 仅移动端(UniApp3) ③ 两者都要" |

仅移动端时的 Step 2 选项调整:

  • 删除:前端风格(vue3/vue3Native)
  • 删除:PC 前端视图目录
  • 删除:PC 前端项目根路径
  • 删除:一对多布局风格(PC 端特有)
  • 删除:表单列数(PC 端特有)
  • 新增:UniApp3 项目根路径(必填)
  • 保留:后端模块、是否读取系统字典、后端项目根路径、数据库名称

两者都要时: 同时展示 PC 端和移动端的选项,分两组列出。

> ⚠️ 禁止默认跳过任何一端! 无法判断时必须询问用户,不得自行假设"用户可能只要 PC"或"用户只要移动端"。


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

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

如果是增量修改 → 进入 场景C

如果是全量生成 → 进入 场景A场景B

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

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

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

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

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

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

  • 定位目标模块:从用户提到的表名、模块名、实体名中识别目标
  • 扫描已有代码文件:在后端和前端目录中搜索已生成的文件
   # <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"
  • 读取全部已有文件:Entity.java、*.data.ts、*List.vue、*Modal.vue(如有 Form.vue 也读取)
  • 解析当前字段列表:从 Entity.java 解析已有字段
  • 推导新字段属性:用"智能字段推导"规则推导 DB 类型、Java 类型、前端控件
  • 展示修改摘要,等待用户确认后再修改

增量修改的操作类型:

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

判断表类型:

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

全控件生成模式("全控件"关键词触发):

当用户说"全控件"、"覆盖所有控件类型"时,触发全覆盖枚举模式,每张表都必须包含该场景支持的所有组件类型,不得只生成代表性字段:

  • 主表:枚举全部 FormSchema 组件 — Input/InputPassword/InputTextArea/InputNumber(整数+金额)/JDictSelectTag(下拉+radio)/JCheckbox/JSelectMultiple/JSwitch/DatePicker(5个picker变体)/TimePicker/JSelectUser/JSelectDept/JCategorySelect/JTreeSelect/JImageUpload/JUpload/JPopup+回填/JPopupDict/JAreaLinkage
  • 一对一子表:在主表全部控件基础上额外加 JEditor/JMarkdownEditor/联动组件(多级)/关联记录+他表字段/表字典各变体(radio/checkbox/multi/带条件)
  • 一对多子表:枚举全部 JVxeTypes — input/textarea/inputNumber/select(系统字典+表字典)/selectSearch/selectMultiple/checkbox(开关)/date/datetime/time/image/file/popup/departSelect/userSelect/pca
  • 标准触发词全控件覆盖所有 FormSchema 控件覆盖所有 JVxeTypes
  • 标准提示语(用户可直接复制使用):

> 生成全控件主子表,主表+一对一子表覆盖所有 FormSchema 控件,一对多子表覆盖所有 JVxeTypes(含pca),Tab-in-Modal 风格(radio-group 切换)

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

> ⚠️ 严禁假设布局风格! 必须在 Step 2 询问用户,用户未回答前不得擅自选择非默认风格(如 Tab-in-Modal)。

> 过去曾犯错:用户未说明风格,却错误地选了 Tab-in-Modal (C9),导致用户反馈后需要重新生成 Modal.vue。

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

> 重要: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风格"、"tab切换"、"radio切换"、"标题栏切换" | 标准列表(同默认, expandedRowRender) | radio-group 标题栏切换主表/子表,wrapClassName="j-cgform-tab-modal" |

| 内嵌子表 (C12) | "内嵌子表"、"行展开"、"expandedRowRender" | 行展开显示子表(expandedRowRender) | 上面主表 BasicForm + 下面 a-tabs 子表(同默认) |

| ERP (C11) | "ERP风格"、"独立编辑" | 主表单选 + 子表独立 CRUD Tab | 仅主表 BasicForm(子表独立 Modal) |

> ⚠️ 子表外键字段名必须读实体确认,严禁猜测!

> 生成子表 FormSchema 的隐藏外键字段前,必须先 Read 子表 Entity.java,以实体中的 Java 字段名为准。

> 外键字段名因开发者习惯差异很大(companyId / bizCompanyId / mainId / headerId),

> 根据主表实体名推断必然出错,会导致 MySQL Field 'xxx' doesn't have a default value 异常。

> 同样,Modal 中 values.xxx = unref(mainId)xxx 也必须与实体字段名一致。

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: 询问用户选项(仅全量生成需要)

> 重要:必须直接向用户提问,禁止通过 Glob/Bash/Grep 等工具自动搜索 CLAUDE.md 或项目路径!

> Skill 加载完毕后,立刻将以下选项表格输出给用户,等待用户回复,所有路径/数据库名均通过问用户获取。

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

  • 后端模块:默认 jeecg-module-system/jeecg-system-biz
  • 前端风格:默认 vue3(封装风格),可选 vue3Native(原生风格)
  • 前端视图目录:默认用 entityPackage 值
  • 是否读取系统字典:默认 ,读取后可自动为字段匹配已有字典编码(见"字典智能匹配"章节)
  • 后端项目根路径:必填,请用户提供(如 D:/jeecgboot
  • 前端项目根路径:必填,请用户提供(如 D:/jeecgboot-vue3
  • 数据库名称:必填,请用户提供(用于读取字典、执行菜单 SQL)
  • 一对多布局风格(仅有子表时展示):默认原始布局(主表上方+子表 a-tabs),可选 Tab-in-Modal内嵌子表ERP
  • 表单列数(所有含表单的场景均需展示,逐项列出):
  • 单表 / 树表 Modal 表单:默认单列(span:24)
  • 一对多主表 BasicForm:默认单列(span:24)
  • 一对一子表 Form.vue:默认单列(span:24)
  • 用户可对每项单独指定,也可统一回复"全部单列"或"全部双列"

> ⚠️ 第8、9项绝对不能自行假设! 过去曾犯错:未询问直接生成 Tab-in-Modal 风格 + 双列补充信息,用户事后指出才改正。

Step 3: 展示摘要

> ⛔ 展示摘要后必须停止,等待用户明确回复"确认"(或"ok"、"可以"、"没问题"等等价表述)。收到确认前不得进入 Step 4。

  • 全量生成:列出表名、字段清单(名称/类型/控件/校验/字典),等待用户确认后再生成。
  • 若需求包含"生成默认值",摘要表格必须新增"默认值"列,明确列出每个字段的具体预填值(参见规则35),让用户在生成前确认,而不是生成后才发现问题。
  • 增量修改:列出要修改的文件路径 + 每个文件的具体变更内容(新增/删除/修改哪些行),等待用户确认。

> ✅ 只有用户明确确认后,才能进入 Step 4。 用户沉默、未回复、或继续追加需求,都不等于确认。

Step 4: 执行

全量生成流程(根据前端目标选择执行路径):

> 前端目标由 Step 0 前置判断确定:仅 PC 端 / 仅移动端 / 两者都要

  • 并行读取对应子文件(见顶部"参考模板读取规则"),在同一轮 response 中发出全部 Read 调用
  • 分轮并行写入文件——无依赖的文件在同一轮 response 中批量发出 Write 调用,禁止逐文件串行等待
  • 第 1 轮前(强制):对每个待写后端文件,确认其路径与 codegen-reference.md 文件清单一致(譬如Mapper XML)
  • 第 1 轮(并行):Entity + Mapper + IService + ServiceImpl + Controller + Mapper.xml(后端 6 文件)
  • 第 2 轮(PC 端前端,仅"仅PC端"或"两者都要"时执行)
  • 第 2 轮前(强制):①确认路径;②确认 FormSchema 首位有 { field: 'id', show: false }
  • 第 2 轮(并行):data.ts + api.ts + List.vue + Modal.vue + 子表 Vue 文件
  • 第 2 轮(移动端前端,仅"仅移动端"或"两者都要"时执行,可与 PC 端第 2 轮并行)
  • 读取 uniapp/SKILL.mduniapp/references/code-templates.md
  • 生成:{EntityName}List.vue + {EntityName}Form.vue + {EntityName}Data.ts(UniApp3 三件套)
  • 更新 pages.json 注册路由
  • 第 3 轮前(强制):①确认 Flyway SQL 路径正确;②Read references/ref-menu-sql.md 获取菜单权限 SQL 模板,禁止凭记忆生成 SQL
  • 第 3 轮(并行):Flyway 建表 SQL + 菜单权限 SQL(严格按 ref-menu-sql.md 模板填充变量)

增量修改流程:

  • 并行读取所有需修改的文件
  • 并行发出所有 Edit 调用(同一轮 response)
  • 增量修改模板见 references/ref-d-misc.md
  • 若增量是"加字段"且涉及主表 formSchema:再次确认首位仍保留 { field: 'id', show: false },不要被新加的字段挤掉

Step 5: 输出清单

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

Step 6: 询问是否生成移动端代码(仅全量生成时执行)

> ⚠️ 增量修改(场景C)跳过此步骤。

> ⚠️ "仅移动端"场景(Step 0 前置已判定)也跳过此步骤,移动端代码已在 Step 4 中一并生成,无需重复询问。

适用场景: 仅当前端目标为"仅 PC 端"时,文件清单输出完毕后,必须向用户询问:

> "是否同时生成对应的移动端(UniApp3)CRUD 代码?(回复"是"/"y"/"需要"确认,其他内容跳过)"

用户确认后的执行方式:

  • 读取 uniapp/SKILL.md,按其中定义的交互流程执行移动端代码生成
  • 本次已收集的实体信息(实体名、包路径、字段列表、API路径前缀等)直接复用,无需用户重复输入
  • 仍需向用户询问 uniapp/SKILL.md Step 0 中移动端特有的配置项(UniApp3 项目根目录)
  • 后端代码已在本次全量生成中完成,移动端 skill 只生成前端代码,无需重复生成后端

本地环境自动执行菜单 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 前必须先询问用户目标数据库名称,不能自动假设(即使从 application-dev.yml 读到了数据库名,也必须展示给用户并等待确认)
  • 仅在本地环境(127.0.0.1/localhost)自动执行,远程环境只生成 Flyway 文件
  • 执行前先检查主菜单 ID 是否已存在,避免重复插入
  • 执行已有 SQL 文件前必须先读取内容审查,重点检查:主菜单的 is_leaf 必须为 0(有按钮子级时),is_leaf=1 会导致按钮权限在权限管理树中不可见
  • 如果 MySQL 执行失败,提示用户手动执行 Flyway SQL,不中断整体流程
  • 输出结果中标注 菜单 SQL:已自动执行 ✓

数据库连接

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

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

场景A(已有表)— 一条命令取全部信息(DDL + 字段注释 + 字典列表 合并执行):

# 同一条 mysql 命令内完成三件事,减少连接次数
mysql --no-defaults --default-character-set=utf8mb4 -h127.0.0.1 -P3306 -uroot -proot {dbname} \
  -e "SHOW CREATE TABLE 表名\G" \
  -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" \
  -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"

⛔ 数据库不可达时的强制回退路径

> MySQL 连不上时(端口拒绝/账号错误/服务未启动),禁止直接降级到"跳过查询,全部新建"或"凭命名惯例猜测"。必须按以下优先级走 fallback:

>

> 1. 优先:用 jeecg-system skill 的 HTTP API 查询。 jeecg-system 通过 JeecgBoot 后端 REST 接口工作,不依赖数据库直连,只要后端服务在跑(本地或远程)就能用。

> - 调用方式:python <skill目录>/jeecg-system/scripts/system_creator.py --api-base <地址> --token <X-Access-Token> --action query-dicts

> - 需要的两项信息:后端 API 地址(如 http://localhost:8080/jeecg-boot)+ X-Access-Token(用户从浏览器 F12 → Network → Request Headers 复制)

> - 必须主动向用户索取这两项,不得跳过

> 2. 退而求其次:在项目 SQL 文件中搜索表定义grep -r "CREATE TABLE.*表名" 在 docs/db/ 目录下)。仅适用于查 DDL,不能用于字典/角色/用户等主数据查询。

> 3. 最后才考虑跳过查询:上述两条都不可行(用户明确拒绝提供 token、后端服务也不可达),且用户书面确认后,方可在 Flyway SQL 中新建所需字典。

>

> 违反此回退顺序即视为违规,包括"MySQL 连不上 → 直接跳过字典查询 → 全部新建"这种降级方式。

⛔⛔ MySQL 连接失败 → 强制 STOP GATE(铁律,无例外)

> MySQL 命令报 Can't connect/10061/Access denied/ERROR 2002/ERROR 1045 等任何连接错误时,必须立即停止后续所有工作(包括但不限于:搜 SQL 文件、读 application-dev.yml、生成代码、派发 SubAgent、写 Flyway SQL),并向用户输出以下话术等待回复:

>

> `

> ⚠️ MySQL 连接失败({粘贴具体错误信息})。按 SKILL.md "⛔ 数据库不可达时的强制回退路径",

> 在继续之前必须先用 jeecg-system HTTP API 查询。请提供:

> 1. 后端 API 地址(例如 http://localhost:8080/jeecg-boot)

> 2. X-Access-Token(浏览器 F12 → Network → Request Headers 复制)

> 若两者都无法提供,请明确告知,我会再次确认是否接受"基于初始化 SQL 推断(可能与

> 真实库不一致)"作为兜底方案。

> `

>

> 在收到用户对 API 地址 + token 的明确回复之前,禁止执行下方任一动作:

> - 在项目目录下 grep / Grep 搜索字典编码、角色编码、用户、部门

> - 读取 db/jeecgboot-mysql-*.sql 等任何初始化 SQL 文件用于推断主数据存在状态

> - 读取 application-dev.yml / application-prod.yml 寻找其他数据库连接

> - 直接判定字典/角色不存在并准备新建

> - 进入摘要展示(Step 3)

> - 派发 SubAgent

> - 生成 Flyway SQL

❌ 错误降级模式清单(识别后立刻停止)

以下行为在 MySQL 连接失败时全部视为违规,即使表面"看起来合理"或"看起来能完成任务":

| 错误行为 | 为什么是错的 | 正确做法 |

|---------|------------|---------|

| 在 db/jeecgboot-mysql-*.sql 初始化文件中 grep 字典 / 角色 / 用户的存在状态 | 初始化文件只代表系统初始状态。业务团队已通过 Flyway 增量 SQL、运行时 API、生产库迁移添加了新数据,初始化文件与真实数据库早已脱节。靠它判断"字典是否存在"会产出与真实环境矛盾的代码 | 走 STOP GATE 索要 API + token |

| 用初始化 SQL 中的 admin role ID(如 f6817f48af4fb3af11b9e8bf182f618b)直接写菜单授权 SQL | 用户生产库的 admin role ID 可能与初始化文件不同,菜单授权会打到错的 role 上或失败 | 走 STOP GATE 索要 API + token,然后用 jeecg-system 查 admin role |

| 在 flyway/sql/mysql/ 目录下 grep 字典编码看是否被引用过 | grep 命中只能说明"项目代码引用过这个字典",不能证明"运行时数据库当前确实存在该字典" | 同上 |

| 静默跳过字典查询,直接把所有字典都按"新建"写入本次 Flyway SQL | 与真实库已有的同名字典冲突,部署到非本地环境会主键/唯一约束报错 | 走 STOP GATE |

| 看到 Flyway 目录里有 V*_dict.sql 等历史文件就推断字典已建 | 文件存在 ≠ 字典已 INSERT 成功 ≠ 当前未被删除 / 修改 | 走 STOP GATE |

| 用项目其他 SQL 文件中出现的 dict_code(如 valid_status)就断言它"存在" | 仅 DDL 类信息允许从 SQL 文件回退查询;字典 / 角色 / 用户任何主数据状态都不允许靠 grep 推断 | 走 STOP GATE |

任何时候若发现自己即将执行上述清单中的动作,必须立刻停下,回到 STOP GATE 话术。

✅ 用户拒绝提供 API + token 后的处理

只有当用户明确回复"无法提供 token / 后端服务也不可用 / 接受基于初始化 SQL 推断的兜底方案"之后,才允许进入优先级 2 / 3。此时必须再次在摘要中显式标注:

⚠️ 本次字典 / 角色 / 菜单授权基于项目初始化 SQL 推断生成,与你的真实数据库状态可能不一致。
   部署到非本地环境前,请手动核对 sys_dict / sys_role 是否已存在同名记录。

用户回复"确认"后才能继续派发 SubAgent / 生成 SQL。

Flyway 版本号规则

生成 Flyway SQL 前必须检查已有版本号,并同时获取时间戳 — 两条命令并行发出:

# 并行执行(同一轮 response 发出两个 Bash 调用)
ls {后端根路径}/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/ | sort -V | tail -5
date +%s%3N

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

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

菜单 SQL 的 ID 生成

时间戳在 Flyway 版本检查时已并行获取(见上方),直接使用,无需再单独执行 date 命令。

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

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

字典智能匹配

> ⛔ MySQL 不可达时的强制回退:

> 本章节的 mysql 命令是默认方式,但 MySQL 连不上时必须按"数据库连接"章节的"⛔ 数据库不可达时的强制回退路径"执行:

> 1. 先尝试 jeecg-system skill 的 scripts/system_creator.py --action query-dicts(需用户提供 API 地址 + token)

> 2. 不能用"MySQL 连不上"作为跳过字典查询、直接新建字典的理由

>

> 详见 ## 数据库连接 章节末尾的"⛔ 数据库不可达时的强制回退路径"。

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

# 查询所有字典编码及其选项值({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
"

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

  • 用户明确指定 — 用户说"状态用字典 order_status",直接使用
  • 字段名精确匹配 — 字段名(如 status)与 dict_code 完全一致
  • 语义关键词匹配 — 字段注释含"状态/类型/级别/分类"等关键词,搜索 dict_name 包含相同关键词的字典
  • 不匹配 — 找不到合适字典时,不使用字典注解,按普通 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: '请选择学校状态',
    getPopupContainer: () => document.body },
  }

前端 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',
    getPopupContainer: () => document.body },
  }
// 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' }

新增分类字典数据:

> 如果 AI 或用户需要向 sys_category 新增分类节点,必须调用 jeecg-system skill 获取正确的新增接口后再执行,禁止猜测接口路径或直接写库。

新增节点的 code 规则:

> ⛔ 禁止在 add 请求体中传入 code 字段code 由后端自动生成,AI 不得自行填写。

>

> add 接口响应处理流程(需兼容新旧版本):

>

> 1. 调用 add 接口,请求体中不传 code,只传 pidname 等字段

> 2. 检查响应的 result 中是否包含 code 字段:

> - code(新版本):直接使用响应中的 code,继续后续操作

> - code(旧版本兼容):主动调用查询接口,通过 idname 查到该节点,取得 code 后再继续

> 3. 确认拿到 code 后,再进行子级节点的新增或其他依赖 code 的操作

生成 List.vue 时的 initDictConfig 规则:

> 生成 initDictConfig() 时,不得if (!allDictDate[cCode]) 短路判断,必须每次挂载都调用 loadCategoryData 覆盖 store,否则用户新增分类节点后页面翻译不会刷新。

>

> 正确写法:

> `ts

> function initDictConfig() {

> loadCategoryData({ code: cCode }).then((res) => {

> if (res) {

> userStore.setAllDictItems({ ...userStore.getAllDictItems, [cCode]: res });

> }

> });

> }

> initDictConfig();

> `

生成 handleSuccess 时的分类字典刷新规则:

> 含分类字典的 List.vue,handleSuccess 必须先刷新分类 store 再 reload,确保自己或他人新增分类节点后翻译立即生效:

>

> `ts

> function handleSuccess() {

> selectedRowKeys.value = [];

> loadCategoryData({ code: cCode }).then((res) => {

> if (res) userStore.setAllDictItems({ ...userStore.getAllDictItems, [cCode]: res });

> reload();

> });

> }

> `

>

> 禁止使用 (selectedRowKeys.value = []) && reload() 的简写形式(不刷新分类 store)。


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: '请选择归属部门',
    getPopupContainer: () => document.body },
  }
// 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: '请选择归属部门',
    getPopupContainer: () => document.body },
  }
// 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_item | sys_category(树形) | 任意业务表 |

| 后端注解 | @Dict(dicCode = "xxx") | 无需 @Dict | @Dict(dictTable="t", dicText="text", dicCode="code") |

| Excel注解 | dicCode = "xxx" | 无 | dictTable="t", dicText="text", dicCode="code" |

| 列表 columns | dataIndex: 'field_dictText' | customRender: render.renderCategoryTree(text, 'pcode') | dataIndex: 'field_dictText' |

| 表单组件 | JDictSelectTag + dictCode: 'xxx' | JCategorySelect + pcode: 'xxx' | JDictSelectTag + dictCode: '表,text,code' |

| 搜索组件 | JDictSelectTag | JCategorySelect | JDictSelectTagJSearchSelect |

| 高级查询 view | list + dictCode | cat_tree + code | sel_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',
    getPopupContainer: () => document.body },
  }
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,
    getPopupContainer: () => document.body },
  }
// 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(vue3 封装风格)
{ label: '开关', field: 'isEnabled', component: 'JSwitch',
  componentProps: { options: ['1', '0'] } }
// superQuerySchema
isEnabled: { title: '开关', order: 0, view: 'radio', dictCode: 'yn' }

vue3Native 原生风格:

// 导入(script setup 中)
import JSwitch from '/@/components/Form/src/jeecg/components/JSwitch.vue';
<!-- 模板中使用,:options 第一个值为 checked,第二个为 unchecked -->
<JSwitch v-model:value="formData.isEnabled" :options="['1', '0']" />

> 注意: vue3Native 中不要使用 <a-switch checkedValue="1">,要用 <JSwitch> 组件,否则值类型不一致导致保存后回显错误。


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',
    getPopupContainer: () => document.body },
  }
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,
    getPopupContainer: () => document.body },
  }
表字典带条件下拉
// 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: '请选择',
    getPopupContainer: () => document.body },
  }

4. 用户/部门选择

JSelectUser(用户选择)

> ⚠️ 重要:代码生成器已在 2024-01-02(issue/#5711)将用户选择组件从 JSelectUserByDept 修正为 JSelectUser。生成代码必须使用 JSelectUser,不要再使用旧的 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(vue3 封装风格)
{ label: '用户选择', field: 'userId', component: 'JSelectUser',
  componentProps: { labelKey: 'realname' } }
// superQuerySchema
userId: { title: '用户选择', order: 0, view: 'sel_user' }

vue3Native 原生风格:

// 导入(script setup 中)
import JSelectUser from '/@/components/Form/src/jeecg/components/JSelectUser.vue';
<!-- 模板中使用 -->
<JSelectUser v-model:value="formData.userId" placeholder="请选择用户" />
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',
    pidValue: '0',
    multiple: false,
    getPopupContainer: () => document.body },
  }
// 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%',
    getPopupContainer: () => document.body },
  }
// 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%',
    getPopupContainer: () => document.body },
  }
// superQuerySchema
workTime: { title: '年月日时分秒', order: 0, view: 'datetime' }
TimePicker(时间选择)
// 时间类型用 String 存储(如 "14:30:00")
@Excel(name = "时间", width = 15)
private String timeVal;
// formSchema(vue3 封装风格)
{ label: '时间', field: 'timeVal', component: 'TimePicker',
  componentProps: {
    valueFormat: 'HH:mm:ss',
    placeholder: '请选择时间',
    style: 'width:100%',
    getPopupContainer: () => document.body },
  }

vue3Native 原生风格:

// 导入 — 从 ant-design-vue 解构,与 Form 合并在同一行
import { TimePicker, Form } from 'ant-design-vue';
// 如果当前行已有其他 ant-design-vue 导入,直接追加 TimePicker:
// import { TimePicker, RangePicker, Form } from 'ant-design-vue';
<!-- 模板中使用,必须带 value-format 否则返回 dayjs 对象而非字符串 -->
<TimePicker v-model:value="formData.timeVal" value-format="HH:mm:ss"
            placeholder="请选择时间" style="width:100%" />

> 注意: vue3Native 中不要使用 <a-time-picker>(全局注册版),要显式导入 TimePicker 并以大驼峰标签使用,与 Form.useForm 的 resetFields 配合才能正确还原默认值。

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%',
    getPopupContainer: () => document.body },
  }
{ label: '年', field: 'yearVal', component: 'DatePicker',
  componentProps: {
    picker: 'year',
    valueFormat: 'YYYY-MM-DD',
    placeholder: '请选择年',
    style: 'width:100%',
    getPopupContainer: () => document.body },
  }
{ label: '月', field: 'monthVal', component: 'DatePicker',
  componentProps: {
    picker: 'month',
    valueFormat: 'YYYY-MM-DD',
    placeholder: '请选择月',
    style: 'width:100%',
    getPopupContainer: () => document.body },
  }
{ label: '周', field: 'weekVal', component: 'DatePicker',
  componentProps: {
    picker: 'week',
    valueFormat: 'YYYY-MM-DD',
    placeholder: '请选择周',
    style: 'width:100%',
    getPopupContainer: () => document.body },
  }

// 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"

配置流程:

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

常用报表 code 示例:

| code | 名称 | SQL数据源 | 典型字段 |

|------|------|----------|---------|

| report_user | 统计在线用户 | sys_user | id, username, realname, phone, email |

| testbigdata | 测试大数据 | sys_log | id, log_content, userid, username, ip |

| demo | Report Demo | demo | * |

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;
// ===== vue3 封装风格 formSchema — 通过 formActionType 获取 setFieldsValue =====
{ label: 'Popup弹窗', field: 'popup', component: 'JPopup',
  componentProps: ({ formActionType }) => {
    const { setFieldsValue } = formActionType;
    return {
      setFieldsValue,
      code: 'report_user',           // 默认使用 report_user;日志场景用 testbigdata
      fieldConfig: [
        { source: 'username', target: 'popup' },    // 报表字段 → 当前表字段(source=报表列名,target=表单字段名)
        { source: 'realname', target: 'popback' },  // 回填其他字段
      ],
      multi: false,
    };
  } }
// 他表字段(回填字段)— disabled Input,不可手动编辑
{ label: 'Popup回填', field: 'popback', component: 'Input',
  componentProps: {
    disabled: true,
    placeholder: '由Popup自动回填' },
  }
<!-- ===== vue3Native 原生风格 Form.vue 模板写法 ===== -->
<!-- import JPopup from '/@/components/Form/src/jeecg/components/JPopup.vue'; -->

<!-- script 中定义 setFormFieldValue -->
<!-- function setFormFieldValue(values) { Object.assign(formData, values); } -->

<JPopup v-model:value="formData.popup" code="report_user"
  :fieldConfig="[{source:'username',target:'popup'},{source:'realname',target:'popback'}]"
  :setFieldsValue="setFormFieldValue" :multi="false" />
<a-input v-model:value="formData.popback" :disabled="true" placeholder="由Popup自动回填" />

fieldConfig 配置规则:

How to use it

Copy the folder

Take jeecgboot/jeecg-codegen from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.