新增JeecgBoot BPM流程自动生成器,包含流程创建、修改及审批人配置功能,支持自然语言描述转化为BPMN XML,并通过API与JeecgBoot系统交互。

This commit is contained in:
geht
2026-04-08 16:24:41 +08:00
parent 7c60acd679
commit 67104af7de
168 changed files with 207167 additions and 8 deletions

View File

@@ -0,0 +1,593 @@
---
name: jeecg-onlform
description: "Use when user asks to create/edit Online form tables, design database tables with form controls, or says \"创建Online表单\", \"创建online表\", \"新建表单配置\", \"online表单\", \"在线表单\", \"低代码表单\", \"配置表\", \"建online表\", \"online form\", \"create online form\", \"add online field\". Also triggers when user describes table fields with control types like \"需要一个下拉选择字段\" or mentions online form requirements like \"做一个请假单包含日期选择和用户选择\"."
---
# JeecgBoot Online 表单 AI 自动生成器
将自然语言的表单需求描述转换为 Online 表单配置 JSON并通过 API 在 JeecgBoot 系统中自动创建/编辑表单。
> **重要:本 skill 处理「Online 表单」(元数据驱动,运行时 CRUD不涉及「设计器表单」desform。两者是完全独立的表单体系。**
## 前置条件
用户必须提供以下信息(或由 AI 引导确认):
1. **API 地址**JeecgBoot 后端地址(如 `https://boot3.jeecg.com/jeecgboot`
2. **X-Access-Token**JWT 登录令牌(从浏览器 F12 获取)
如果用户未提供,提示:
> 请提供 JeecgBoot 后端地址和 X-Access-Token从浏览器 F12 → Network → 任意请求的 Request Headers 中复制)。
## 交互流程
### Step 0: 判断操作类型
| 用户意图关键词 | 操作类型 |
|---------------|---------|
| 创建/新建/做一个/生成 | **新增表单** → Step 1A |
| 加字段/增加字段/修改字段/删除字段/改一下 | **编辑表单** → Step 1B |
### Step 1A: 新增表单 — 解析需求
从用户描述中提取:
| 信息 | 默认值 | 示例 |
|------|--------|------|
| 表名 (tableName) | 自动生成 snake_case | `leave_application` |
| 表描述 (tableTxt) | 用户指定 | "请假申请" |
| 表类型 (tableType) | 1=单表 | 提到"主子表"→2/3提到"树形"→1+isTree |
| 字段列表 | 从描述中解析 | 姓名(必填)、请假天数(数字)、日期(范围查询) |
**判断表类型:**
- 提到"分类/层级/树/上下级" → **树表** (tableType=1, isTree='Y')
- 提到"主子表/明细/一对多/订单+商品" → **主子表** (主表 tableType=2, 子表 tableType=3)
- 默认 → **单表** (tableType=1)
### Step 1B: 编辑表单 — 查询现有配置
1. 用户提供表单 ID 或表名
2. 通过 API 查询现有表单配置:`GET /online/cgform/api/getByHead?id={headId}`
3. 解析现有字段列表,展示给用户
4. 根据用户需求进行增/删/改字段
### Step 2: 智能字段推导
**从用户自然语言描述推导字段配置:**
| 用户描述关键词 | fieldShowType | dbType | dbLength | 说明 |
|---------------|--------------|--------|----------|------|
| 名称/标题/编码/文本 | `text` | string | 100 | 单行文本 |
| 密码 | `password` | string | 32 | 密码框 |
| 备注/描述/说明 | `textarea` | string | 500 | 多行文本 |
| 金额/价格/费用 | `text` | BigDecimal | 10(2) | 数字文本框 |
| 数量/个数/数目 | `text` | int | 9 | 整数文本框 |
| 小数/比率/double | `text` | double | 10(2) | 浮点文本框 |
| 日期/生日/入职日期 | `date` | Date | 0 | 日期选择 |
| 日期时间/下单时间 | `datetime` | Datetime | 0 | 日期时间选择 |
| 时间/几点 | `time` | string | 50 | 时间选择 |
| 年 | `date` + picker=year | Date | 0 | 年选择 |
| 月 | `date` + picker=month | Date | 0 | 月选择 |
| 周 | `date` + picker=week | Date | 0 | 周选择 |
| 季度 | `date` + picker=quarter | Date | 0 | 季度选择 |
| 是否/开关/启用 | `switch` | string | 50 | 开关 |
| 状态/类型/级别 (单选) | `radio` | string | 50 | 字典单选 |
| 下拉/选择/类别 | `list` | string | 50 | 字典下拉 |
| 多选/标签/兴趣 | `checkbox` | string | 200 | 字典多选 |
| 下拉多选 | `list_multi` | string | 250 | 字典下拉多选 |
| 下拉搜索/远程搜索 | `sel_search` | string | 50 | 字典表下拉搜索 |
| 图片/头像/照片 | `image` | string | 500 | 图片上传 |
| 文件/附件 | `file` | string | 500 | 文件上传 |
| 富文本/内容/HTML | `umeditor` | Text | 0 | 富文本编辑器 |
| Markdown | `markdown` | Blob | 0 | Markdown编辑器 |
| 用户/负责人/审批人 | `sel_user` | string | 100 | 用户选择 |
| 部门/组织/所属部门 | `sel_depart` | string | 100 | 部门选择 |
| 省市区/地区/地址 | `pca` | string | 100 | 省市区联动 |
| 分类/分类树/树选择 | `cat_tree` | string | 100 | 分类字典树 |
| 自定义树 | `sel_tree` | string | 255 | 自定义树控件 |
| 弹窗选择/popup | `popup` | string | 100 | Popup弹窗 |
| pop字典 | `popup_dict` | string | 100 | Popup字典 |
| 关联记录/引用 | `link_table` | string | 200 | 关联记录 |
| 他表字段/自动填充 | `link_table_field` | string | 32 | 他表字段(不持久化) |
| 联动下拉/级联 | `link_down` | string | 255 | 联动组件 |
### Step 3: 字典配置推导
**字典数据来源有三种方式,按以下优先级选择:**
#### 方式一系统字典dictField 有值dictTable 为空)
用户提到"字典 sex"、"使用 urgent_level 字典" 等:
```json
{ "dictField": "sex", "dictTable": "", "dictText": "" }
```
#### 方式二字典表dictTable 有值)
用户提到"从 sys_user 表取"、"关联部门表"等:
```json
{ "dictTable": "sys_depart", "dictField": "id", "dictText": "depart_name" }
```
#### 方式三:字典表带条件
用户提到"过滤/筛选/where"等:
```json
{ "dictTable": "sys_user where username like '%a%'", "dictField": "username", "dictText": "realname" }
```
**常用 JeecgBoot 系统字典编码:**
| 字典编码 | 说明 | 适用控件 |
|---------|------|---------|
| `sex` | 性别 (1=男, 2=女) | list/radio/checkbox |
| `priority` | 优先级 (L/M/H) | list/radio |
| `valid_status` | 有效状态 (0/1) | list/radio/switch |
| `urgent_level` | 紧急程度 | list/checkbox/list_multi |
| `yn` | 是否 (Y/N) | radio/switch |
### Step 4: 特殊控件配置
#### switch 开关
```json
{
"fieldShowType": "switch",
"fieldExtendJson": "[\"Y\",\"N\"]",
"dictField": "", "dictTable": "", "dictText": ""
}
```
#### date 日期扩展 (年/月/周/季度)
```json
{
"fieldShowType": "date",
"fieldExtendJson": "{\"labelLength\":6,\"picker\":\"year\"}"
}
```
picker 可选值: `year``month``week``quarter`
#### popup 弹窗
dictField 和 dictText 成对映射(逗号分隔):
```json
{
"fieldShowType": "popup",
"dictTable": "report_user",
"dictField": "username,realname",
"dictText": "popup,popback"
}
```
其中 dictText 的值对应本表接收回填的字段名。
#### sel_tree 自定义树
```json
{
"fieldShowType": "sel_tree",
"dictTable": "sys_category",
"dictField": "0",
"dictText": "id,pid,name,has_child"
}
```
dictField 填根节点值dictText 填 `id,pid,显示字段,是否有子节点字段`
#### link_down 联动下拉
dictTable 填 JSON 配置字符串:
```json
{
"fieldShowType": "link_down",
"dictTable": "{\n\ttable: \"sys_category\",\n\ttxt: \"name\",\n\tkey: \"id\",\n\tlinkField: \"field2,field3\",\n\tidField: \"id\",\n\tpidField: \"pid\",\n\tcondition:\"pid = '0'\"\n}",
"dictField": "", "dictText": ""
}
```
#### link_table 关联记录
```json
{
"fieldShowType": "link_table",
"dictTable": "demo_staff",
"dictField": "id",
"dictText": "name,age,sex",
"fieldExtendJson": "{\"showType\":\"card\",\"multiSelect\":false,\"imageField\":\"\"}"
}
```
多选带图片:`{"showType":"card","multiSelect":true,"imageField":"top_pic"}`
#### link_table_field 他表字段
```json
{
"fieldShowType": "link_table_field",
"dictTable": "guanljil",
"dictField": "",
"dictText": "name",
"dbIsPersist": 0
}
```
dictTable 填本表中 link_table 控件的字段名(不是数据库表名)。`dbIsPersist=0` 表示不持久化到数据库。
#### popup_dict Pop字典
```json
{
"fieldShowType": "popup_dict",
"dictTable": "report_user",
"dictField": "id",
"dictText": "realname"
}
```
### Step 5: 展示摘要并确认
**必须展示以下内容,等待用户确认后再执行:**
**重要:必须明确展示 6 个标准系统字段 + 业务字段,让用户清楚看到完整表结构!**
```
## Online 表单配置摘要
- 表名leave_application
- 表描述:请假申请表
- 表类型:单表
- 目标环境https://boot3.jeecg.com/jeecgboot
### 标准系统字段6个每个Online表必须包含
| 序号 | 字段名 | 标签 | DB类型 | 说明 |
|------|--------|------|--------|------|
| 1 | id | 主键 | string(36) | 主键,自动生成 |
| 2 | create_by | 创建人 | string(50) | 系统自动填充 |
| 3 | create_time | 创建时间 | Datetime | 系统自动填充 |
| 4 | update_by | 更新人 | string(50) | 系统自动填充 |
| 5 | update_time | 更新时间 | Datetime | 系统自动填充 |
| 6 | sys_org_code | 所属部门 | string(50) | 系统自动填充 |
### 业务字段N个
| 序号 | 字段名 | 标签 | 控件类型 | DB类型 | 必填 | 查询 | 字典 |
|------|--------|------|---------|--------|------|------|------|
| 7 | name | 姓名 | text | string(100) | 是 | 是(模糊) | - |
| 8 | leave_type | 请假类型 | list | string(50) | 是 | 是(精确) | leave_type |
| 9 | start_date | 开始日期 | date | Date | 是 | 是(范围) | - |
| 10 | end_date | 结束日期 | date | Date | 是 | 否 | - |
| 11 | days | 请假天数 | text | int(9) | 是 | 否 | - |
| 12 | reason | 请假原因 | textarea | string(500) | 否 | 否 | - |
| 13 | attachment | 附件 | file | string(500) | 否 | 否 | - |
| 14 | approver | 审批人 | sel_user | string(100) | 否 | 是(精确) | - |
**合计6 个标准字段 + 8 个业务字段 = 14 个字段**
### 索引
| 索引名 | 字段 | 类型 |
|--------|------|------|
| (无) | | |
确认以上配置?(y/n)
```
### Step 6: 生成配置 JSON 并调用 API
用户确认后,**优先使用持久化脚本** `scripts/onlform_creator.py`(同目录下),只需生成 JSON 配置文件即可完成创建/编辑。
> **重要:优先使用 `scripts/onlform_creator.py` 脚本 + JSON 配置文件的方式,避免每次重头编写 Python 代码。只有当脚本无法满足特殊需求时,才编写自定义脚本。**
#### 6.1 使用持久化脚本(推荐方式)
**脚本位置:** 与本 SKILL.md 同目录下的 `scripts/onlform_creator.py`
**使用步骤:**
1. 根据用户需求生成 JSON 配置文件Write 到工作目录的临时 `.json` 文件)
2. 用 Bash 执行脚本:`python <skill目录>/scripts/onlform_creator.py --api-base <URL> --token <TOKEN> --config <config.json>`
3. 删除临时 JSON 配置文件
**脚本自动完成:**
- 生成6个系统默认字段 + 业务字段
- 调用 addAll/editAll API
- 查询 headId
- 同步数据库
- 输出菜单 SQL
#### 6.2 JSON 配置文件格式
**单表创建示例:**
```json
{
"action": "create",
"tables": [
{
"tableName": "leave_application",
"tableTxt": "请假申请表",
"tableType": 1,
"themeTemplate": "normal",
"fields": [
{"dbFieldName": "name", "dbFieldTxt": "姓名", "fieldShowType": "text", "dbType": "string", "dbLength": 100, "fieldMustInput": "1", "isQuery": 1},
{"dbFieldName": "days", "dbFieldTxt": "请假天数", "fieldShowType": "text", "dbType": "int", "dbLength": 9, "fieldMustInput": "1"},
{"dbFieldName": "reason", "dbFieldTxt": "请假原因", "fieldShowType": "textarea", "dbType": "string", "dbLength": 500}
]
}
]
}
```
**主子表创建示例(主表 tableType=2子表 tableType=3**
```json
{
"action": "create",
"tables": [
{
"tableName": "demo_order_main",
"tableTxt": "订单主表",
"tableType": 2,
"themeTemplate": "erp",
"subTableStr": "demo_order_product",
"fields": [
{"dbFieldName": "order_code", "dbFieldTxt": "订单编号", "fieldShowType": "text", "dbType": "string", "dbLength": 50, "fieldMustInput": "1", "isQuery": 1},
{"dbFieldName": "order_date", "dbFieldTxt": "下单日期", "fieldShowType": "date", "dbType": "Date", "dbLength": 0, "fieldMustInput": "1", "isQuery": 1, "queryMode": "group"},
{"dbFieldName": "customer_name", "dbFieldTxt": "客户名称", "fieldShowType": "text", "dbType": "string", "dbLength": 100, "fieldMustInput": "1", "isQuery": 1},
{"dbFieldName": "total_amount", "dbFieldTxt": "订单总额", "fieldShowType": "text", "dbType": "BigDecimal", "dbLength": 10, "dbPointLength": 2},
{"dbFieldName": "order_status", "dbFieldTxt": "订单状态", "fieldShowType": "list", "dbType": "string", "dbLength": 50, "fieldMustInput": "1", "isQuery": 1, "dictField": "valid_status"},
{"dbFieldName": "remark", "dbFieldTxt": "备注", "fieldShowType": "textarea", "dbType": "string", "dbLength": 500}
]
},
{
"tableName": "demo_order_product",
"tableTxt": "订单商品明细",
"tableType": 3,
"relationType": 0,
"tabOrderNum": 1,
"fields": [
{"dbFieldName": "order_id", "dbFieldTxt": "订单ID", "fieldShowType": "text", "dbType": "string", "dbLength": 36, "fieldMustInput": "1", "isShowForm": 0, "isShowList": 0, "mainTable": "demo_order_main", "mainField": "id"},
{"dbFieldName": "product_name", "dbFieldTxt": "商品名称", "fieldShowType": "text", "dbType": "string", "dbLength": 200, "fieldMustInput": "1"},
{"dbFieldName": "price", "dbFieldTxt": "单价", "fieldShowType": "text", "dbType": "BigDecimal", "dbLength": 10, "dbPointLength": 2, "fieldMustInput": "1"},
{"dbFieldName": "quantity", "dbFieldTxt": "数量", "fieldShowType": "text", "dbType": "int", "dbLength": 9, "fieldMustInput": "1"},
{"dbFieldName": "subtotal", "dbFieldTxt": "小计", "fieldShowType": "text", "dbType": "BigDecimal", "dbLength": 10, "dbPointLength": 2}
]
}
]
}
```
**树表创建示例:**
```json
{
"action": "create",
"tables": [
{
"tableName": "product_category",
"tableTxt": "产品分类",
"tableType": 1,
"isTree": "Y",
"treeParentIdField": "pid",
"treeIdField": "has_child",
"treeFieldname": "name",
"fields": [
{"dbFieldName": "pid", "dbFieldTxt": "父级ID", "fieldShowType": "text", "dbType": "string", "dbLength": 36, "isShowForm": 0, "isShowList": 0},
{"dbFieldName": "has_child", "dbFieldTxt": "是否有子节点", "fieldShowType": "text", "dbType": "string", "dbLength": 10, "isShowForm": 0, "isShowList": 0},
{"dbFieldName": "name", "dbFieldTxt": "分类名称", "fieldShowType": "text", "dbType": "string", "dbLength": 100, "fieldMustInput": "1", "isQuery": 1}
]
}
]
}
```
**编辑表单示例:**
```json
{
"action": "edit",
"headId": "表单的headId",
"addFields": [
{"dbFieldName": "new_field", "dbFieldTxt": "新字段", "fieldShowType": "text", "dbType": "string", "dbLength": 100}
],
"deleteFields": ["old_field_name"],
"modifyFields": [
{"dbFieldName": "existing_field", "dbFieldTxt": "修改后的标签", "dbLength": 200}
]
}
```
#### 6.3 字段配置属性参考
JSON 配置中每个字段对象支持以下属性(未指定的使用默认值):
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| dbFieldName | string | **必填** | 字段名(snake_case) |
| dbFieldTxt | string | **必填** | 字段标签 |
| fieldShowType | string | "text" | 控件类型(text/list/radio/date/file等) |
| dbType | string | "string" | 数据库类型(string/int/BigDecimal/Date/Datetime/Text/Blob等) |
| dbLength | int | 100 | 字段长度 |
| dbPointLength | int | 0 | 小数位数 |
| fieldMustInput | string | "0" | 是否必填("1"=是) |
| isQuery | int | 0 | 是否查询(1=是) |
| queryMode | string | "single" | 查询模式(single=精确/模糊, group=范围) |
| isShowForm | int | 1 | 是否在表单中显示 |
| isShowList | int | 1 | 是否在列表中显示 |
| isReadOnly | int | 0 | 是否只读 |
| dictField | string | "" | 字典编码(系统字典) |
| dictTable | string | "" | 字典表名(字典表) |
| dictText | string | "" | 字典显示字段 |
| fieldExtendJson | string | "" | 扩展配置JSON(switch/date picker等) |
| fieldDefaultValue | string | "" | 默认值 |
| fieldValidType | string | "" | 校验规则(m=手机/e=邮箱/n=数字等) |
| fieldLength | int | 120 | 控件宽度 |
| mainTable | string | "" | 主表名(子表外键字段用) |
| mainField | string | "" | 主表关联字段(子表外键字段用) |
| sortFlag | string | "0" | 是否可排序 |
| queryConfigFlag | string | "0" | 是否个性查询 |
| queryShowType | string | null | 个性查询控件类型 |
| queryDictField | string | "" | 个性查询字典编码 |
| queryDefVal | string | "" | 个性查询默认值 |
#### 6.4 回退方案:手动编写 Python 脚本
`scripts/onlform_creator.py` 无法满足需求时(如需要特殊的字段联动逻辑、复杂的条件判断等),才手动编写 Python 脚本。
**重要限制:**
1. **Windows 环境下 curl 发送中文/长JSON会出错**,必须使用 Python
2. **禁止使用 `python3 -c "..."` 内联方式**
3. **必须先用 Write 工具写入 `.py` 临时文件,再用 Bash 执行,最后删除临时文件**
手动编写时可参考 `scripts/onlform_creator.py` 中的 `make_system_fields()``make_field()``api_request()` 等函数实现。
### Step 7: 输出结果
**创建成功后,脚本会自动执行以下操作:**
1. 调用 addAll 创建表单配置
2. 查询刚创建的表单获取 headId
3. 调用同步数据库 API`GET /online/cgform/api/doDbSynch/{headId}/normal`
4. 输出菜单 SQL
5. **本地环境自动执行菜单 SQL**:如果 API_BASE 以 `http://127.0.0.1``http://localhost` 开头,自动通过 MySQL 执行菜单升级 SQL见下方规则
```
## Online 表单创建成功
- 表名:{tableName}
- 表描述:{tableTxt}
- 表类型:{单表/主子表/树表}
- 字段数量:{N} 个业务字段 + 6 个系统字段
- 目标环境:{API_BASE}
- 数据库同步:已完成 ✓
- 菜单 SQL{已自动执行 ✓ / 需手动执行}
### 菜单 SQL
INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external)
VALUES ('{menuId}', NULL, '{tableTxt}', '/online/cgformList/{headId}', '1', 'OnlineAutoList', NULL, 0, NULL, '1', 0.00, 0, NULL, 0, 1, 0, 0, 0, NULL, '1', 0, 0, 'admin', now(), NULL, NULL, 0);
### 后续操作
1. 点击「功能测试」预览表单效果
2. 如菜单未自动执行,手动执行上方 SQL 或在后台手动「添加菜单」
```
### 同步数据库 API 说明
```
POST /online/cgform/api/doDbSynch/{headId}/{syncType}
```
| 参数 | 说明 |
|------|------|
| headId | 表单配置的 ID从 addAll 后查询获得) |
| syncType | `normal` = 普通同步, `force` = 强制同步(会删除已有表重建) |
### 本地环境自动执行菜单 SQL 规则
**判断条件:** API_BASE 以 `http://127.0.0.1``http://localhost` 开头(不区分大小写)。
**自动执行方式:** 在 Python 脚本中生成菜单 SQL 后,通过 Bash 工具执行 MySQL 命令:
```bash
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -e "INSERT INTO sys_permission(...) VALUES (...);"
```
**注意事项:**
- 执行前先检查菜单是否已存在:`SELECT id FROM sys_permission WHERE id='{menuId}'`,避免重复插入
- 如果 MySQL 执行失败,回退为输出 SQL 让用户手动执行,不要中断整体流程
- 数据库连接参数默认 `mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3`,与 jeecg-codegen 保持一致
### 菜单 SQL 说明
| 字段 | 值 | 说明 |
|------|-----|------|
| url | `/online/cgformList/{headId}` | Online 表单列表页路由 |
| component | `1` | 固定值,表示 Online 组件 |
| component_name | `OnlineAutoList` | 固定值 |
| menu_type | `0` | 0=菜单 |
| is_route | `0` | 0=不路由Online组件特殊处理 |
| is_leaf | `1` | 1=叶子节点 |
---
## 主子表创建流程
使用 `scripts/onlform_creator.py` 创建主子表,只需在 JSON 配置的 `tables` 数组中按顺序定义主表和子表即可。脚本会自动依次创建并同步数据库。
### 关键配置要点
1. **主表**`tableType=2``subTableStr` 填子表名(多个用逗号分隔)
2. **子表**`tableType=3`,必须设置 `relationType`(0=一对多/1=一对一) 和 `tabOrderNum`(排序号)
3. **子表外键字段**:必须包含关联主表的字段,设置 `mainTable``mainField`,且 `isShowForm=0, isShowList=0`
4. **tables 数组顺序**:主表在前,子表在后(脚本按顺序创建)
完整 JSON 配置示例见上方 Step 6.2 中的「主子表创建示例」。
### 主题模板说明
| themeTemplate | 说明 | 适用场景 |
|--------------|------|---------|
| `normal` | 默认主题 | 子表少、字段少 |
| `tab` | TAB页签 | 多个子表 |
| `erp` | ERP风格 | 上方主表+下方明细 |
| `innerTable` | 内嵌子表 | 子表行内编辑 |
---
## 树表创建
使用 `scripts/onlform_creator.py` 创建树表,在表配置中设置 `isTree: "Y"` 及相关树字段即可。
### 关键配置要点
1. `tableType=1``isTree="Y"`
2. 必须设置 `treeParentIdField`(父ID字段)、`treeIdField`(是否有子节点字段)、`treeFieldname`(树展示字段)
3. 字段中必须包含 `pid``has_child` 字段(隐藏,不在表单和列表中显示)
完整 JSON 配置示例见上方 Step 6.2 中的「树表创建示例」。
---
## 查询配置
### 基础查询 (isQuery + queryMode)
| queryMode | 说明 | 适用控件 |
|-----------|------|---------|
| `single` | 精确/模糊匹配 | text, list, radio, sel_search 等 |
| `group` | 范围查询 | date, datetime, time |
### 个性查询 (queryConfigFlag='1')
用于覆盖默认的查询控件和字典:
```python
make_field(6, 'status', '状态', 'text', 'string', 50,
is_query=1, query_mode='single')
# 然后手动添加个性查询配置:
fields[-1]['queryConfigFlag'] = '1'
fields[-1]['queryShowType'] = 'list'
fields[-1]['queryDictField'] = 'sex'
fields[-1]['queryDefVal'] = '1'
```
---
## 索引配置
```python
indexs = [
{
"id": rand_id("idx"),
"indexName": "idx_unique_code",
"indexField": "code",
"indexType": "unique"
},
{
"id": rand_id("idx"),
"indexName": "idx_status",
"indexField": "status",
"indexType": "normal"
}
]
```
---
## 错误处理
| 错误 | 解决方案 |
|------|---------|
| Token 过期401/认证失败) | 提示用户重新获取 X-Access-Token |
| `数据库表[xxx]已存在` | 表已存在,需从数据库导入或使用 editAll |
| `附表必须选择映射关系!` | tableType=3 时必须设置 relationType |
| `附表必须填写排序序号!` | tableType=3 时必须设置 tabOrderNum |
| `未找到对应实体` | editAll 时 head.id 不正确 |
| 中文乱码 | 确认使用 Python urllib不要用 curl |
## 参考文档
- `scripts/onlform_creator.py`**可复用的创建/编辑工具脚本**,优先使用此脚本 + JSON 配置文件
- `references/onlform-api-reference.md` — 完整 JSON 数据结构和字段枚举(手动编写脚本时参考)

View File

@@ -0,0 +1,655 @@
# Online 表单 API 参考文档
本文档是 jeecg-onlform skill 的参考数据,包含完整的 JSON 请求模板和字段枚举。
## 1. addAll 完整请求体模板(单表)
以下是一个包含所有控件类型的完整示例:
```json
{
"head": {
"tableVersion": "1",
"tableName": "表名_snake_case",
"tableTxt": "表描述文本",
"tableType": 1,
"formCategory": "temp",
"idType": "UUID",
"isCheckbox": "Y",
"themeTemplate": "normal",
"formTemplate": "1",
"scroll": 1,
"isPage": "Y",
"isTree": "N",
"extConfigJson": "{\"reportPrintShow\":0,\"reportPrintUrl\":\"\",\"joinQuery\":0,\"modelFullscreen\":0,\"modalMinWidth\":\"\",\"commentStatus\":0,\"tableFixedAction\":1,\"tableFixedActionType\":\"right\",\"formLabelLengthShow\":0,\"formLabelLength\":null,\"enableExternalLink\":0,\"externalLinkActions\":\"add,edit,detail\"}",
"isDesForm": "N",
"desFormCode": ""
},
"fields": [],
"indexs": [],
"deleteFieldIds": [],
"deleteIndexIds": []
}
```
### head 字段说明
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| tableName | string | 是 | - | 数据库表名(snake_case) |
| tableTxt | string | 是 | - | 表描述 |
| tableType | int | 是 | 1 | 1=单表, 2=主表, 3=子表 |
| tableVersion | string | 是 | "1" | 版本号 |
| idType | string | 是 | "UUID" | 主键策略: UUID/SEQUENCE/ID_WORKER |
| formCategory | string | 否 | "temp" | 表单分类 |
| formTemplate | string | 否 | "1" | PC表单模板 1=一列, 2=两列 |
| themeTemplate | string | 否 | "normal" | 主题: normal/erp/innerTable/tab |
| isCheckbox | string | 否 | "Y" | 是否显示复选框 |
| isPage | string | 否 | "Y" | 是否分页 |
| isTree | string | 否 | "N" | 是否树形 |
| scroll | int | 否 | 1 | 是否有横向滚动条 |
| extConfigJson | string | 否 | - | 扩展配置JSON字符串 |
| isDesForm | string | 否 | "N" | 是否用设计器表单 |
| desFormCode | string | 否 | "" | 设计器表单编码 |
### 主子表额外 head 字段
| 字段 | 说明 | 何时需要 |
|------|------|---------|
| subTableStr | 子表名列表(逗号分隔) | 主表(tableType=2) |
| relationType | 0=一对多, 1=一对一 | 子表(tableType=3) **必填** |
| tabOrderNum | 附表排序号 | 子表(tableType=3) **必填** |
### 树表额外 head 字段
| 字段 | 说明 |
|------|------|
| treeParentIdField | 父ID字段名(如 "pid") |
| treeIdField | 是否有子节点字段(如 "has_child") |
| treeFieldname | 树展开显示字段(如 "name") |
---
## 2. 系统默认字段6个每个表必须包含
```json
[
{
"dbFieldName": "id",
"dbFieldTxt": "主键",
"fieldMustInput": "1",
"isShowForm": 0,
"isShowList": 0,
"isReadOnly": 1,
"fieldShowType": "text",
"fieldLength": 120,
"isQuery": 0,
"queryMode": "single",
"dbLength": 36,
"dbPointLength": 0,
"dbType": "string",
"dbIsKey": 1,
"dbIsNull": 0,
"orderNum": 0
},
{
"dbFieldName": "create_by",
"dbFieldTxt": "创建人",
"fieldMustInput": "0",
"isShowForm": 0,
"isShowList": 0,
"sortFlag": "0",
"isReadOnly": 0,
"fieldShowType": "text",
"fieldLength": 120,
"isQuery": 0,
"queryMode": "single",
"dbLength": 50,
"dbPointLength": 0,
"dbType": "string",
"dbIsKey": 0,
"dbIsNull": 1,
"orderNum": 1
},
{
"dbFieldName": "create_time",
"dbFieldTxt": "创建时间",
"fieldMustInput": "0",
"isShowForm": 0,
"isShowList": 0,
"sortFlag": "0",
"isReadOnly": 0,
"fieldShowType": "datetime",
"fieldLength": 120,
"isQuery": 0,
"queryMode": "single",
"dbLength": 50,
"dbPointLength": 0,
"dbType": "Datetime",
"dbIsKey": 0,
"dbIsNull": 1,
"orderNum": 2
},
{
"dbFieldName": "update_by",
"dbFieldTxt": "更新人",
"fieldMustInput": "0",
"isShowForm": 0,
"isShowList": 0,
"sortFlag": "0",
"isReadOnly": 0,
"fieldShowType": "text",
"fieldLength": 120,
"isQuery": 0,
"queryMode": "single",
"dbLength": 50,
"dbPointLength": 0,
"dbType": "string",
"dbIsKey": 0,
"dbIsNull": 1,
"orderNum": 3
},
{
"dbFieldName": "update_time",
"dbFieldTxt": "更新时间",
"fieldMustInput": "0",
"isShowForm": 0,
"isShowList": 0,
"sortFlag": "0",
"isReadOnly": 0,
"fieldShowType": "datetime",
"fieldLength": 120,
"isQuery": 0,
"queryMode": "single",
"dbLength": 50,
"dbPointLength": 0,
"dbType": "Datetime",
"dbIsKey": 0,
"dbIsNull": 1,
"orderNum": 4
},
{
"dbFieldName": "sys_org_code",
"dbFieldTxt": "所属部门",
"fieldMustInput": "0",
"isShowForm": 0,
"isShowList": 0,
"sortFlag": "0",
"isReadOnly": 0,
"fieldShowType": "text",
"fieldLength": 120,
"isQuery": 0,
"queryMode": "single",
"dbLength": 50,
"dbPointLength": 0,
"dbType": "string",
"dbIsKey": 0,
"dbIsNull": 1,
"orderNum": 5
}
]
```
---
## 3. 业务字段完整属性模板
```json
{
"id": "前端生成的短ID",
"dbFieldName": "field_name",
"dbFieldTxt": "字段标签",
"queryShowType": null,
"queryDictTable": "",
"queryDictField": "",
"queryDictText": "",
"queryDefVal": "",
"queryConfigFlag": "0",
"mainTable": "",
"mainField": "",
"fieldHref": "",
"fieldValidType": "",
"fieldMustInput": "0",
"dictTable": "",
"dictField": "",
"dictText": "",
"isShowForm": 1,
"isShowList": 1,
"sortFlag": "0",
"isReadOnly": 0,
"fieldShowType": "text",
"fieldLength": 120,
"isQuery": 0,
"queryMode": "single",
"fieldDefaultValue": "",
"converter": "",
"fieldExtendJson": "",
"dbLength": 100,
"dbPointLength": 0,
"dbType": "string",
"dbIsKey": 0,
"dbIsNull": 1,
"orderNum": 6
}
```
---
## 4. fieldShowType 控件类型完整清单
### 基础控件
| fieldShowType | 说明 | 典型 dbType | 典型 dbLength | 字典配置 |
|--------------|------|------------|--------------|---------|
| `text` | 文本输入框 | string | 100 | 不需要 |
| `password` | 密码框 | string | 32 | 不需要 |
| `textarea` | 多行文本 | string | 500 | 不需要 |
| `date` | 日期选择 | Date | 0 | 不需要 |
| `datetime` | 日期时间 | Datetime | 0 | 不需要 |
| `time` | 时间选择 | string | 50 | 不需要 |
| `switch` | 开关 | string | 50 | fieldExtendJson 配置 |
| `file` | 文件上传 | string | 500 | 不需要 |
| `image` | 图片上传 | string | 500 | 不需要 |
| `umeditor` | 富文本编辑器 | Text | 0 | 不需要 |
| `markdown` | Markdown | Blob | 0 | 不需要 |
| `pca` | 省市区联动 | string | 100 | 不需要 |
### 字典控件(使用系统字典)
| fieldShowType | 说明 | dictField | dictTable | dictText |
|--------------|------|-----------|-----------|----------|
| `list` | 字典下拉 | 字典code | `""` | `""` |
| `radio` | 字典单选 | 字典code | `""` | `""` |
| `checkbox` | 字典多选 | 字典code | `""` | `""` |
| `list_multi` | 字典下拉多选 | 字典code | `""` | `""` |
| `cat_tree` | 分类字典树 | 分类编码 | `""` | `""` |
### 字典表控件(使用数据库表)
| fieldShowType | 说明 | dictTable | dictField | dictText |
|--------------|------|-----------|-----------|----------|
| `list` | 字典表下拉 | 表名 | 存储字段 | 显示字段 |
| `radio` | 字典表单选 | 表名 | 存储字段 | 显示字段 |
| `checkbox` | 字典表多选 | 表名 | 存储字段 | 显示字段 |
| `list_multi` | 字典表下拉多选 | 表名 | 存储字段 | 显示字段 |
| `sel_search` | 字典表下拉搜索 | 表名 | 存储字段 | 显示字段 |
### 特殊选择控件
| fieldShowType | 说明 | dictTable | dictField | dictText |
|--------------|------|-----------|-----------|----------|
| `sel_user` | 用户选择 | `""` | `""` | `""` |
| `sel_depart` | 部门选择 | `""` | `""` | `""` |
| `sel_tree` | 自定义树 | 树表名 | 根节点值 | `"id,pid,name,has_child"` |
| `popup` | Popup弹窗 | 弹窗表名 | 存储字段映射 | 回填字段映射 |
| `popup_dict` | Pop字典 | 弹窗表名 | 存储字段 | 显示字段 |
### 关联控件
| fieldShowType | 说明 | dictTable | dictField | dictText | dbIsPersist |
|--------------|------|-----------|-----------|----------|-------------|
| `link_table` | 关联记录 | 关联表名 | 主键字段 | 显示列(逗号分隔) | 1 |
| `link_table_field` | 他表字段 | 本表link_table字段名 | `""` | 显示字段名 | **0** |
| `link_down` | 联动下拉 | JSON配置 | `""` | `""` | 1 |
---
## 5. 各控件类型的完整字段配置示例
### 5.1 text 文本框
```json
{"dbFieldName": "name", "dbFieldTxt": "姓名", "fieldShowType": "text", "dbType": "string", "dbLength": 100, "dbPointLength": 0, "fieldMustInput": "1", "isQuery": 1, "queryMode": "single", "isShowForm": 1, "isShowList": 1}
```
### 5.2 BigDecimal 金额
```json
{"dbFieldName": "price", "dbFieldTxt": "单价", "fieldShowType": "text", "dbType": "BigDecimal", "dbLength": 10, "dbPointLength": 2, "isShowForm": 1, "isShowList": 1}
```
### 5.3 int 整数
```json
{"dbFieldName": "quantity", "dbFieldTxt": "数量", "fieldShowType": "text", "dbType": "int", "dbLength": 9, "dbPointLength": 0, "isShowForm": 1, "isShowList": 1}
```
### 5.4 password 密码
```json
{"dbFieldName": "mi_ma", "dbFieldTxt": "密码", "fieldShowType": "password", "dbType": "string", "dbLength": 32}
```
### 5.5 list 字典下拉
```json
{"dbFieldName": "status", "dbFieldTxt": "状态", "fieldShowType": "list", "dbType": "string", "dbLength": 50, "dictField": "sex", "dictTable": "", "dictText": "", "isQuery": 1}
```
### 5.6 list 字典表下拉
```json
{"dbFieldName": "depart", "dbFieldTxt": "部门", "fieldShowType": "list", "dbType": "string", "dbLength": 255, "dictTable": "sys_depart", "dictField": "id", "dictText": "depart_name", "fieldLength": 200}
```
### 5.7 list 字典表带条件下拉
```json
{"dbFieldName": "user_select", "dbFieldTxt": "用户", "fieldShowType": "list", "dbType": "string", "dbLength": 255, "dictTable": "sys_user where username like '%a%'", "dictField": "username", "dictText": "realname", "fieldLength": 200}
```
### 5.8 radio 字典单选
```json
{"dbFieldName": "sex", "dbFieldTxt": "性别", "fieldShowType": "radio", "dbType": "string", "dbLength": 50, "dictField": "sex", "dictTable": "", "dictText": ""}
```
### 5.9 checkbox 字典多选
```json
{"dbFieldName": "tags", "dbFieldTxt": "标签", "fieldShowType": "checkbox", "dbType": "string", "dbLength": 200, "dictField": "urgent_level", "dictTable": "", "dictText": ""}
```
### 5.10 list_multi 字典下拉多选
```json
{"dbFieldName": "multi", "dbFieldTxt": "多选", "fieldShowType": "list_multi", "dbType": "string", "dbLength": 250, "dictField": "urgent_level", "dictTable": "", "dictText": ""}
```
### 5.11 switch 开关
```json
{"dbFieldName": "enabled", "dbFieldTxt": "启用", "fieldShowType": "switch", "dbType": "string", "dbLength": 50, "dictField": "", "dictTable": "", "dictText": "", "fieldExtendJson": "[\"Y\",\"N\"]"}
```
### 5.12 date 日期
```json
{"dbFieldName": "start_date", "dbFieldTxt": "开始日期", "fieldShowType": "date", "dbType": "Date", "dbLength": 0, "isQuery": 1, "queryMode": "group"}
```
### 5.13 date 年选择
```json
{"dbFieldName": "year", "dbFieldTxt": "年", "fieldShowType": "date", "dbType": "Date", "dbLength": 0, "fieldExtendJson": "{\"labelLength\":6,\"picker\":\"year\"}", "fieldLength": 200}
```
### 5.14 datetime 日期时间
```json
{"dbFieldName": "order_time", "dbFieldTxt": "下单时间", "fieldShowType": "datetime", "dbType": "Datetime", "dbLength": 0}
```
### 5.15 time 时间
```json
{"dbFieldName": "check_time", "dbFieldTxt": "签到时间", "fieldShowType": "time", "dbType": "string", "dbLength": 50, "isQuery": 1, "queryMode": "group"}
```
### 5.16 file 文件上传
```json
{"dbFieldName": "attachment", "dbFieldTxt": "附件", "fieldShowType": "file", "dbType": "string", "dbLength": 500}
```
### 5.17 image 图片上传
```json
{"dbFieldName": "avatar", "dbFieldTxt": "头像", "fieldShowType": "image", "dbType": "string", "dbLength": 500}
```
### 5.18 textarea 多行文本
```json
{"dbFieldName": "remark", "dbFieldTxt": "备注", "fieldShowType": "textarea", "dbType": "string", "dbLength": 500}
```
### 5.19 umeditor 富文本
```json
{"dbFieldName": "content", "dbFieldTxt": "内容", "fieldShowType": "umeditor", "dbType": "Text", "dbLength": 0, "isShowList": 0}
```
### 5.20 markdown
```json
{"dbFieldName": "doc", "dbFieldTxt": "文档", "fieldShowType": "markdown", "dbType": "Blob", "dbLength": 0, "isShowList": 0}
```
### 5.21 sel_user 用户选择
```json
{"dbFieldName": "approver", "dbFieldTxt": "审批人", "fieldShowType": "sel_user", "dbType": "string", "dbLength": 100, "isQuery": 1}
```
### 5.22 sel_depart 部门选择
```json
{"dbFieldName": "dept", "dbFieldTxt": "所在部门", "fieldShowType": "sel_depart", "dbType": "string", "dbLength": 100, "isQuery": 1}
```
### 5.23 pca 省市区
```json
{"dbFieldName": "address", "dbFieldTxt": "地址", "fieldShowType": "pca", "dbType": "string", "dbLength": 100, "isQuery": 1}
```
### 5.24 sel_search 下拉搜索
```json
{"dbFieldName": "user", "dbFieldTxt": "选择用户", "fieldShowType": "sel_search", "dbType": "string", "dbLength": 50, "dictTable": "sys_user", "dictField": "username", "dictText": "realname", "isQuery": 1}
```
### 5.25 cat_tree 分类字典树
```json
{"dbFieldName": "category", "dbFieldTxt": "分类", "fieldShowType": "cat_tree", "dbType": "string", "dbLength": 100, "dictField": "B02", "dictTable": "", "dictText": ""}
```
### 5.26 sel_tree 自定义树
```json
{"dbFieldName": "tree_node", "dbFieldTxt": "树节点", "fieldShowType": "sel_tree", "dbType": "string", "dbLength": 255, "dictTable": "sys_category", "dictField": "0", "dictText": "id,pid,name,has_child"}
```
### 5.27 popup 弹窗选择
```json
{"dbFieldName": "popup_val", "dbFieldTxt": "弹窗选择", "fieldShowType": "popup", "dbType": "string", "dbLength": 100, "dictTable": "report_user", "dictField": "username,realname", "dictText": "popup_val,popup_back"}
```
注意dictText 中的值是本表接收回填的字段名,需要对应创建 popup_back 字段。
### 5.28 popup_dict Pop字典
```json
{"dbFieldName": "pop_dict", "dbFieldTxt": "Pop字典", "fieldShowType": "popup_dict", "dbType": "string", "dbLength": 100, "dictTable": "report_user", "dictField": "id", "dictText": "realname"}
```
### 5.29 link_table 关联记录(单选)
```json
{"dbFieldName": "related", "dbFieldTxt": "关联记录", "fieldShowType": "link_table", "dbType": "string", "dbLength": 32, "dictTable": "demo_staff", "dictField": "id", "dictText": "name,age,sex", "fieldExtendJson": "{\"showType\":\"card\",\"multiSelect\":false,\"imageField\":\"\"}", "fieldLength": 200}
```
### 5.30 link_table 关联记录(多选带图)
```json
{"dbFieldName": "related_multi", "dbFieldTxt": "关联记录多选", "fieldShowType": "link_table", "dbType": "string", "dbLength": 200, "dictTable": "test_demo", "dictField": "id", "dictText": "name,sex,age", "fieldExtendJson": "{\"showType\":\"card\",\"multiSelect\":true,\"imageField\":\"top_pic\"}", "fieldLength": 200}
```
### 5.31 link_table_field 他表字段
```json
{"dbFieldName": "ta_field", "dbFieldTxt": "他表字段", "fieldShowType": "link_table_field", "dbType": "string", "dbLength": 32, "dictTable": "related", "dictField": "", "dictText": "name", "fieldLength": 200}
```
注意dictTable 填本表中 link_table 控件的**字段名**不是数据库表名dbIsPersist=0。
### 5.32 link_down 联动下拉
```json
{"dbFieldName": "link1", "dbFieldTxt": "联动一", "fieldShowType": "link_down", "dbType": "string", "dbLength": 255, "dictTable": "\n{\n\ttable: \"sys_category\",\n\ttxt: \"name\",\n\tkey: \"id\",\n\tlinkField: \"link2,link3\",\n\tidField: \"id\",\n\tpidField: \"pid\",\n\tcondition:\"pid = '0'\"\n}", "dictField": "", "dictText": ""}
```
被联动的字段link2、link3使用普通 text 控件。
---
## 6. fieldValidType 校验规则
| 值 | 说明 |
|---|------|
| `""` | 无校验 |
| `only` | 唯一校验(需配合唯一索引) |
| `m` | 手机号 |
| `e` | 邮箱 |
| `n` | 数字 |
| `n6-16` | 6-16位数字 |
| `*6-16` | 6-16位任意字符 |
| `money` | 金额格式 |
| `^正则$` | 自定义正则(如 `^[a-z\|A-Z]{2,10}$`) |
---
## 7. fieldDefaultValue 默认值表达式
| 语法 | 说明 |
|------|------|
| `#{sysUserCode}` | 当前用户账号 |
| `#{sysUserName}` | 当前用户姓名 |
| `#{sysOrgCode}` | 当前用户部门编码 |
| `#{date}` | 当前日期 |
| `#{time}` | 当前时间 |
| `${规则编码}` | 编码规则(自动流水号) |
| `{{JS表达式}}` | 前端JS表达式 |
| 纯字符串 | 直接赋值(如 "Y", "10") |
---
## 8. extConfigJson 完整默认配置
```json
{
"reportPrintShow": 0,
"reportPrintUrl": "",
"joinQuery": 0,
"modelFullscreen": 0,
"modalMinWidth": "",
"commentStatus": 0,
"tableFixedAction": 1,
"tableFixedActionType": "right",
"formLabelLengthShow": 0,
"formLabelLength": null,
"enableExternalLink": 0,
"externalLinkActions": "add,edit,detail"
}
```
---
## 9. editAll 与 addAll 的关键差异
| 维度 | addAll (新增) | editAll (编辑) |
|------|--------------|----------------|
| head.id | 不传,服务端生成 | **必传** |
| fields[].id | 前端自定义短ID | 已有字段=32位hex ID新增字段=前端短ID |
| fields[].dbIsPersist | 不传 | 需传(link_table_field=0, 其余=1) |
| fields[].dbDefaultVal | 不传 | 需传 |
| 空值 | 统一用 `""` | 系统字段用 null业务字段用 `""` |
| head 额外字段 | 无 | 含 isDbSynch、createBy、createTime 等 |
| deleteFieldIds | 空数组 | 可含要删除的字段ID |
| deleteIndexIds | 空数组 | 可含要删除的索引ID |
| 版本号 | 固定 "1" | 服务端自动+1 |
### editAll 新增字段识别规则
- 字段 id 为 32 位 hex → 更新已有字段
- 字段 id 不足 32 位 → 新增字段
- 字段 id 为 "_pk" → 跳过
### editAll 删除操作
- 要删除的字段ID放入 `deleteFieldIds`
- 要删除的索引ID放入 `deleteIndexIds`
---
## 10. 查询现有表单 API
### 按ID查询
```
GET /online/cgform/api/getByHead?id={headId}
```
响应:
```json
{
"success": true,
"result": {
"head": { ... },
"fields": [ ... ],
"indexs": [ ... ]
}
}
```
### 查询表单列表
```
GET /online/cgform/head/list?tableName={表名}&pageNo=1&pageSize=10
```
返回 `result.records[0].id` 即为 headId。
---
## 9. 同步数据库 API
创建或编辑表单配置后,需要调用同步数据库 API 将配置同步为真实数据库表。
```
POST /online/cgform/api/doDbSynch/{headId}/{syncType}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| headId | string | 是 | 表单配置的 ID32位hex从 addAll 创建后查询获取) |
| syncType | string | 是 | `normal` = 普通同步(增量,不丢数据),`force` = 强制同步(删表重建,丢数据!) |
**使用流程:**
1. 调用 `addAll` 创建表单配置 → 返回 success
2. 调用 `queryPageList?tableName=xxx` 查询刚创建的表单获取 `headId`
3. 调用 `doDbSynch/{headId}/normal` 同步到数据库
**响应示例:**
```json
{
"success": true,
"message": "同步成功",
"code": 200,
"result": null,
"timestamp": 1773462789925
}
```
**注意事项:**
- `normal` 模式:仅增加新字段、修改字段属性,不删除已有字段,不丢失数据
- `force` 模式:删除原表并重建,所有数据丢失!仅开发环境使用
- 编辑表单editAll后也需要重新同步数据库
---
## 10. 菜单 SQL 模板
将 Online 表单配置为系统菜单,使用户可以在左侧菜单中直接访问。
```sql
INSERT INTO sys_permission(
id, parent_id, name, url, component, component_name, redirect,
menu_type, perms, perms_type, sort_no, always_show, icon,
is_route, is_leaf, keep_alive, hidden, hide_tab,
description, status, del_flag, rule_flag,
create_by, create_time, update_by, update_time, internal_or_external
) VALUES (
'{menuId}', -- 菜单ID32位可用headId或自定义
NULL, -- 父菜单IDNULL=一级菜单填ID=子菜单)
'{tableTxt}', -- 菜单名称(表描述)
'/online/cgformList/{headId}', -- 路由URL固定格式
'1', -- component 固定为 '1'Online组件标识
'OnlineAutoList', -- component_name 固定值
NULL, -- redirect
0, -- menu_type: 0=菜单
NULL, -- perms 权限编码
'1', -- perms_type: 1=可授权
0.00, -- sort_no 排序
0, -- always_show
NULL, -- icon 图标
0, -- is_route: 0Online组件不走路由
1, -- is_leaf: 1=叶子节点
0, -- keep_alive
0, -- hidden: 0=显示
0, -- hide_tab: 0=显示tab
NULL, -- description
'1', -- status: 1=有效
0, -- del_flag: 0=未删除
0, -- rule_flag
'admin', -- create_by
now(), -- create_time
NULL, -- update_by
NULL, -- update_time
0 -- internal_or_external: 0=内部
);
```
**关键字段说明:**
| 字段 | 固定值 | 说明 |
|------|--------|------|
| url | `/online/cgformList/{headId}` | Online 表单路由headId 是表单配置ID |
| component | `'1'` | 固定值,前端识别为 Online 组件 |
| component_name | `'OnlineAutoList'` | 固定值Online 自动列表组件 |
| is_route | `0` | Online 组件不走普通路由 |
| is_leaf | `1` | 必须是叶子节点 |
| parent_id | `NULL` 或父菜单ID | NULL=一级菜单指定父ID=子菜单 |

View File

@@ -0,0 +1,398 @@
"""
JeecgBoot Online 表单创建/编辑工具脚本
用法:
python onlform_creator.py --api-base <URL> --token <TOKEN> --config <config.json>
config.json 格式见下方示例。
支持的操作:
- 单表创建 (tableType=1)
- 主子表创建 (主表 tableType=2 + 子表 tableType=3)
- 树表创建 (tableType=1, isTree='Y')
- 编辑表单 (action='edit')
"""
import urllib.request
import json
import sys
import random
import string
# 修复 Windows 控制台中文乱码
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
import argparse
# ====== 工具函数 ======
def rand_id(prefix=''):
chars = string.ascii_lowercase + string.digits
suffix = ''.join(random.choices(chars, k=8))
return f'{prefix}{suffix}'
def api_request(api_base, token, path, data=None, method='POST'):
url = f'{api_base}{path}'
headers = {
'X-Access-Token': token,
'Content-Type': 'application/json; charset=UTF-8'
}
if data is not None:
json_data = json.dumps(data, ensure_ascii=False).encode('utf-8')
req = urllib.request.Request(url, data=json_data, headers=headers, method=method)
else:
req = urllib.request.Request(url, headers=headers, method=method)
resp = urllib.request.urlopen(req)
return json.loads(resp.read().decode('utf-8'))
def make_system_fields():
"""生成6个系统默认字段"""
return [
{"id": rand_id("id"), "dbFieldName": "id", "dbFieldTxt": "主键", "queryConfigFlag": "0", "fieldMustInput": "1", "isShowForm": 0, "isShowList": 0, "isReadOnly": 1, "fieldShowType": "text", "fieldLength": 120, "isQuery": 0, "queryMode": "single", "dbLength": 36, "dbPointLength": 0, "dbType": "string", "dbIsKey": 1, "dbIsNull": 0, "orderNum": 0},
{"id": rand_id("createby"), "dbFieldName": "create_by", "dbFieldTxt": "创建人", "queryConfigFlag": "0", "fieldMustInput": "0", "isShowForm": 0, "isShowList": 0, "sortFlag": "0", "isReadOnly": 0, "fieldShowType": "text", "fieldLength": 120, "isQuery": 0, "queryMode": "single", "dbLength": 50, "dbPointLength": 0, "dbType": "string", "dbIsKey": 0, "dbIsNull": 1, "orderNum": 1},
{"id": rand_id("createti"), "dbFieldName": "create_time", "dbFieldTxt": "创建时间", "queryConfigFlag": "0", "fieldMustInput": "0", "isShowForm": 0, "isShowList": 0, "sortFlag": "0", "isReadOnly": 0, "fieldShowType": "datetime", "fieldLength": 120, "isQuery": 0, "queryMode": "single", "dbLength": 50, "dbPointLength": 0, "dbType": "Datetime", "dbIsKey": 0, "dbIsNull": 1, "orderNum": 2},
{"id": rand_id("updateby"), "dbFieldName": "update_by", "dbFieldTxt": "更新人", "queryConfigFlag": "0", "fieldMustInput": "0", "isShowForm": 0, "isShowList": 0, "sortFlag": "0", "isReadOnly": 0, "fieldShowType": "text", "fieldLength": 120, "isQuery": 0, "queryMode": "single", "dbLength": 50, "dbPointLength": 0, "dbType": "string", "dbIsKey": 0, "dbIsNull": 1, "orderNum": 3},
{"id": rand_id("updateti"), "dbFieldName": "update_time", "dbFieldTxt": "更新时间", "queryConfigFlag": "0", "fieldMustInput": "0", "isShowForm": 0, "isShowList": 0, "sortFlag": "0", "isReadOnly": 0, "fieldShowType": "datetime", "fieldLength": 120, "isQuery": 0, "queryMode": "single", "dbLength": 50, "dbPointLength": 0, "dbType": "Datetime", "dbIsKey": 0, "dbIsNull": 1, "orderNum": 4},
{"id": rand_id("sysorgco"), "dbFieldName": "sys_org_code", "dbFieldTxt": "所属部门", "queryConfigFlag": "0", "fieldMustInput": "0", "isShowForm": 0, "isShowList": 0, "sortFlag": "0", "isReadOnly": 0, "fieldShowType": "text", "fieldLength": 120, "isQuery": 0, "queryMode": "single", "dbLength": 50, "dbPointLength": 0, "dbType": "string", "dbIsKey": 0, "dbIsNull": 1, "orderNum": 5},
]
def make_field(order, db_name, db_txt, show_type='text', db_type='string', db_length=100,
db_point=0, must_input='0', is_query=0, query_mode='single',
is_show_form=1, is_show_list=1, is_read_only=0, sort_flag='0',
dict_field='', dict_table='', dict_text='',
field_valid_type='', field_default_value='', field_extend_json='',
field_length=120, main_table='', main_field='',
query_config_flag='0', query_show_type=None,
query_dict_field='', query_dict_table='', query_dict_text='', query_def_val=''):
"""生成业务字段配置"""
return {
"id": rand_id(db_name[:8]),
"dbFieldName": db_name,
"dbFieldTxt": db_txt,
"queryShowType": query_show_type,
"queryDictTable": query_dict_table,
"queryDictField": query_dict_field,
"queryDictText": query_dict_text,
"queryDefVal": query_def_val,
"queryConfigFlag": query_config_flag,
"mainTable": main_table,
"mainField": main_field,
"fieldHref": "",
"fieldValidType": field_valid_type,
"fieldMustInput": must_input,
"dictTable": dict_table,
"dictField": dict_field,
"dictText": dict_text,
"isShowForm": is_show_form,
"isShowList": is_show_list,
"sortFlag": sort_flag,
"isReadOnly": is_read_only,
"fieldShowType": show_type,
"fieldLength": field_length,
"isQuery": is_query,
"queryMode": query_mode,
"fieldDefaultValue": field_default_value,
"converter": "",
"fieldExtendJson": field_extend_json,
"dbLength": db_length,
"dbPointLength": db_point,
"dbType": db_type,
"dbIsKey": 0,
"dbIsNull": 1,
"orderNum": order,
}
def make_ext_config():
"""生成默认扩展配置JSON字符串"""
return json.dumps({
"reportPrintShow": 0, "reportPrintUrl": "",
"joinQuery": 0, "modelFullscreen": 0, "modalMinWidth": "",
"commentStatus": 0, "tableFixedAction": 1,
"tableFixedActionType": "right",
"formLabelLengthShow": 0, "formLabelLength": None,
"enableExternalLink": 0, "externalLinkActions": "add,edit,detail"
}, ensure_ascii=False)
def build_fields_from_config(field_configs):
"""从配置列表构建字段数组(含系统字段)"""
fields = make_system_fields()
for i, fc in enumerate(field_configs):
order = 6 + i
fields.append(make_field(
order=order,
db_name=fc['dbFieldName'],
db_txt=fc['dbFieldTxt'],
show_type=fc.get('fieldShowType', 'text'),
db_type=fc.get('dbType', 'string'),
db_length=fc.get('dbLength', 100),
db_point=fc.get('dbPointLength', 0),
must_input=fc.get('fieldMustInput', '0'),
is_query=fc.get('isQuery', 0),
query_mode=fc.get('queryMode', 'single'),
is_show_form=fc.get('isShowForm', 1),
is_show_list=fc.get('isShowList', 1),
is_read_only=fc.get('isReadOnly', 0),
sort_flag=fc.get('sortFlag', '0'),
dict_field=fc.get('dictField', ''),
dict_table=fc.get('dictTable', ''),
dict_text=fc.get('dictText', ''),
field_valid_type=fc.get('fieldValidType', ''),
field_default_value=fc.get('fieldDefaultValue', ''),
field_extend_json=fc.get('fieldExtendJson', ''),
field_length=fc.get('fieldLength', 120),
main_table=fc.get('mainTable', ''),
main_field=fc.get('mainField', ''),
query_config_flag=fc.get('queryConfigFlag', '0'),
query_show_type=fc.get('queryShowType', None),
query_dict_field=fc.get('queryDictField', ''),
query_dict_table=fc.get('queryDictTable', ''),
query_dict_text=fc.get('queryDictText', ''),
query_def_val=fc.get('queryDefVal', ''),
))
return fields
def build_head(table_config):
"""从表配置构建 head 对象"""
head = {
"tableVersion": "1",
"tableName": table_config['tableName'],
"tableTxt": table_config['tableTxt'],
"tableType": table_config.get('tableType', 1),
"formCategory": table_config.get('formCategory', 'temp'),
"idType": table_config.get('idType', 'UUID'),
"isCheckbox": table_config.get('isCheckbox', 'Y'),
"themeTemplate": table_config.get('themeTemplate', 'normal'),
"formTemplate": table_config.get('formTemplate', '1'),
"scroll": table_config.get('scroll', 1),
"isPage": table_config.get('isPage', 'Y'),
"isTree": table_config.get('isTree', 'N'),
"extConfigJson": table_config.get('extConfigJson', make_ext_config()),
"isDesForm": "N",
"desFormCode": ""
}
# 主表额外字段
if table_config.get('tableType') == 2:
head['subTableStr'] = table_config.get('subTableStr', '')
# 子表额外字段
if table_config.get('tableType') == 3:
head['relationType'] = table_config.get('relationType', 0)
head['tabOrderNum'] = table_config.get('tabOrderNum', 1)
# 树表额外字段
if table_config.get('isTree') == 'Y':
head['treeParentIdField'] = table_config.get('treeParentIdField', 'pid')
head['treeIdField'] = table_config.get('treeIdField', 'has_child')
head['treeFieldname'] = table_config.get('treeFieldname', 'name')
return head
def build_indexs(index_configs):
"""从索引配置列表构建索引数组"""
indexs = []
for ic in (index_configs or []):
indexs.append({
"id": rand_id("idx"),
"indexName": ic['indexName'],
"indexField": ic['indexField'],
"indexType": ic.get('indexType', 'normal')
})
return indexs
def create_table(api_base, token, table_config):
"""创建单个表并返回 headId"""
table_name = table_config['tableName']
table_txt = table_config['tableTxt']
print(f'\n{"=" * 50}')
print(f'创建表: {table_name} ({table_txt})')
print(f'{"=" * 50}')
fields = build_fields_from_config(table_config.get('fields', []))
head = build_head(table_config)
indexs = build_indexs(table_config.get('indexs'))
form_data = {
"head": head,
"fields": fields,
"indexs": indexs,
"deleteFieldIds": [],
"deleteIndexIds": []
}
result = api_request(api_base, token, '/online/cgform/api/addAll', form_data)
print(f'创建结果: success={result.get("success")}, message={result.get("message")}')
if not result.get('success'):
print(f'创建失败: {result.get("message")}')
return None
# 查询 headId
list_result = api_request(api_base, token,
f'/online/cgform/head/list?tableName={table_name}&pageNo=1&pageSize=1',
method='GET')
if list_result.get('success') and list_result['result']['records']:
head_id = list_result['result']['records'][0]['id']
print(f'headId: {head_id}')
# 同步数据库
sync = api_request(api_base, token,
f'/online/cgform/api/doDbSynch/{head_id}/normal',
method='POST')
print(f'同步数据库: success={sync.get("success")}, message={sync.get("message")}')
return head_id
else:
print('查询 headId 失败,请手动同步数据库')
return None
def edit_table(api_base, token, edit_config):
"""编辑现有表单"""
head_id = edit_config['headId']
print(f'\n{"=" * 50}')
print(f'编辑表单: headId={head_id}')
print(f'{"=" * 50}')
# 查询现有配置
detail = api_request(api_base, token,
f'/online/cgform/api/getByHead?id={head_id}',
method='GET')
if not detail.get('success'):
print(f'查询失败: {detail.get("message")}')
return None
head = detail['result']['head']
fields = detail['result']['fields']
indexs = detail['result'].get('indexs', [])
delete_field_ids = []
# 添加新字段
for fc in edit_config.get('addFields', []):
new_order = max(f['orderNum'] for f in fields) + 1
fields.append(make_field(
order=new_order,
db_name=fc['dbFieldName'],
db_txt=fc['dbFieldTxt'],
show_type=fc.get('fieldShowType', 'text'),
db_type=fc.get('dbType', 'string'),
db_length=fc.get('dbLength', 100),
db_point=fc.get('dbPointLength', 0),
must_input=fc.get('fieldMustInput', '0'),
is_query=fc.get('isQuery', 0),
query_mode=fc.get('queryMode', 'single'),
is_show_form=fc.get('isShowForm', 1),
is_show_list=fc.get('isShowList', 1),
dict_field=fc.get('dictField', ''),
dict_table=fc.get('dictTable', ''),
dict_text=fc.get('dictText', ''),
field_extend_json=fc.get('fieldExtendJson', ''),
field_length=fc.get('fieldLength', 120),
))
print(f' 新增字段: {fc["dbFieldName"]} ({fc["dbFieldTxt"]})')
# 删除字段
for field_name in edit_config.get('deleteFields', []):
for f in fields:
if f['dbFieldName'] == field_name:
delete_field_ids.append(f['id'])
fields.remove(f)
print(f' 删除字段: {field_name}')
break
# 修改字段
for mc in edit_config.get('modifyFields', []):
target_name = mc['dbFieldName']
for f in fields:
if f['dbFieldName'] == target_name:
for key, val in mc.items():
if key != 'dbFieldName':
f[key] = val
print(f' 修改字段: {target_name}')
break
edit_data = {
"head": head,
"fields": fields,
"indexs": indexs,
"deleteFieldIds": delete_field_ids,
"deleteIndexIds": []
}
result = api_request(api_base, token, '/online/cgform/api/editAll', edit_data, method='PUT')
print(f'编辑结果: success={result.get("success")}, message={result.get("message")}')
if result.get('success'):
# 同步数据库
sync = api_request(api_base, token,
f'/online/cgform/api/doDbSynch/{head_id}/normal',
method='POST')
print(f'同步数据库: success={sync.get("success")}, message={sync.get("message")}')
return head_id
def print_menu_sql(head_id, table_txt):
"""输出菜单SQL"""
menu_id = head_id.replace('-', '')[:32]
print(f"""
--- 菜单 SQL可选---
INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external)
VALUES ('{menu_id}', NULL, '{table_txt}', '/online/cgformList/{head_id}', '1', 'OnlineAutoList', NULL, 0, NULL, '1', 0.00, 0, NULL, 0, 1, 0, 0, 0, NULL, '1', 0, 0, 'admin', now(), NULL, NULL, 0);
""")
def main():
parser = argparse.ArgumentParser(description='JeecgBoot Online 表单创建/编辑工具')
parser.add_argument('--api-base', required=True, help='JeecgBoot 后端地址')
parser.add_argument('--token', required=True, help='X-Access-Token')
parser.add_argument('--config', required=True, help='配置文件路径 (JSON)')
args = parser.parse_args()
with open(args.config, 'r', encoding='utf-8') as f:
config = json.load(f)
action = config.get('action', 'create')
if action == 'create':
# 创建表单(支持单表、主子表、树表)
tables = config.get('tables', [])
if not tables:
print('错误: 配置文件中没有 tables 定义')
sys.exit(1)
head_ids = {}
for table_config in tables:
head_id = create_table(args.api_base, args.token, table_config)
if head_id:
head_ids[table_config['tableName']] = head_id
# 输出汇总
print(f'\n{"=" * 50}')
print('创建完成汇总')
print(f'{"=" * 50}')
for tname, hid in head_ids.items():
print(f' {tname} -> headId: {hid}')
# 为主表输出菜单SQL
main_table = tables[0]
main_head_id = head_ids.get(main_table['tableName'])
if main_head_id:
print_menu_sql(main_head_id, main_table['tableTxt'])
elif action == 'edit':
head_id = edit_table(args.api_base, args.token, config)
if head_id:
print('\n编辑完成!')
else:
print(f'未知操作类型: {action}')
sys.exit(1)
if __name__ == '__main__':
main()