物料模块

This commit is contained in:
2026-04-08 16:55:15 +08:00
170 changed files with 230035 additions and 9 deletions

View File

@@ -0,0 +1,280 @@
---
name: jeecg-bpmn
description: "Use when user asks to create/generate a BPM workflow, design a Flowable BPMN process, or says \"创建流程\", \"生成流程\", \"新建流程\", \"设计流程\", \"画流程\", \"审批流程\", \"工作流\", \"BPM\", \"BPMN\", \"create flow\", \"create process\", \"new workflow\", \"generate workflow\". Also triggers when user describes an approval chain like \"先经理审批再HR审批\" or mentions process nodes like \"开始→审批→网关→结束\"."
---
# JeecgBoot BPM 流程自动生成器
将自然语言的流程描述转换为 Flowable BPMN 2.0 XML并通过 API 在 JeecgBoot 系统中自动创建流程。
## 前置条件
用户必须提供以下信息(或由 AI 引导确认):
1. **API 地址**JeecgBoot 后端地址(如 `https://api3.boot.jeecg.com`
2. **X-Access-Token**JWT 登录令牌(从浏览器 F12 获取)
如果用户未提供,提示:
> 请提供 JeecgBoot 后端地址和 X-Access-Token从浏览器 F12 → Network → 任意请求的 Request Headers 中复制)。
## 交互流程
### Step 0: 解析用户需求
从用户描述中提取以下信息:
| 信息 | 默认值 | 示例 |
|------|--------|------|
| 流程名称 | 用户指定或自动生成 | "员工请假审批流程" |
| 流程类型 | `oa` | 字典 `bpm_process_type` 的值 |
| 节点列表 | 从描述中解析 | 开始→员工提交→经理审批→HR审批→结束 |
| 网关逻辑 | 从描述中解析 | "通过→下一步,拒绝→结束" |
| 审批人配置 | 从描述中解析 | assignee/candidateUsers/candidateGroups/表达式 |
### Step 1: 识别节点并构建流程结构
**支持的节点类型:**
| 用户描述关键词 | BPMN 节点类型 | XML 元素 |
|---------------|---------------|----------|
| 开始 | 开始事件 | `startEvent` |
| 结束 | 结束事件 | `endEvent` |
| 审批/审核/处理/提交 | 用户任务 | `userTask` |
| 条件判断/分支/通过或拒绝 | 排他网关 | `exclusiveGateway` |
| 同时/并行 | 并行网关 | `parallelGateway` |
| 条件并行/部分并行 | 包含网关 | `inclusiveGateway` |
| 子流程/嵌套 | 内嵌子流程 | `subProcess` |
| 调用子流程/主子流程 | 调用子流程 | `callActivity` |
| 会签子流程 | 调用子流程+多实例 | `callActivity` + `multiInstance` |
**审批人配置映射:**
| 用户描述 | BPMN 属性 | 示例 |
|----------|-----------|------|
| "发起人/申请人" | `flowable:assignee="${applyUserId}"` | 流程发起人自动填充 |
| "admin/指定用户名" | `flowable:assignee="admin"` | 固定指定人 |
| "经理角色/角色组" | `flowable:candidateGroups="manager"` + `groupType="role"` | 系统角色候选 |
| "审批角色" | `flowable:candidateGroups="xxx"` + `groupType="approvalRole"` | 审批专用角色 |
| "张三或李四" | `flowable:candidateUsers="zhangsan,lisi"` | 多候选人 |
| "某部门审批" | `flowable:candidateGroups="部门ID"` + `groupType="dept"` | 部门审批 |
| "某岗位审批" | `flowable:candidateGroups="岗位ID"` + `groupType="deptPosition"` | 部门岗位审批 |
| "职级审批" | 表达式 + `groupType="position"` | 职务级别审批 |
| "部门负责人(表达式)" | `flowable:assignee="${deptLeader}"` | 表达式动态 |
| "上一节点指派" | `isAssignedByPreviousNode=true` | 上一审批人选择 |
| "草稿/驳回发起人" | `flowable:assignee="${applyUserId}"` + `sameMode=2` | 草稿节点 |
| "会签/多人同时审批" | `flowable:countersignRule` + 多实例 | 并行/顺序会签 |
| "表单字段选人" | `groupType="formData"` | 从表单动态获取 |
**审批人数据查询:** 当用户提到具体角色/用户/部门名称时,可查数据库获取准确编码:
- 角色编码:`SELECT role_code, role_name FROM sys_role`
- 用户名:`SELECT username, realname FROM sys_user`
- 部门/岗位ID`SELECT id, depart_name, org_category FROM sys_depart`org_category: 1=公司, 2=部门, 3=岗位, 4=子公司)
### Step 2: 展示流程摘要并确认
**必须展示以下内容,等待用户确认后再执行:**
```
## 流程摘要
- 流程名称:员工请假审批流程
- 流程类型oa
- 目标环境https://api3.boot.jeecg.com
### 流程节点
| 序号 | 节点名称 | 类型 | 审批人 |
|------|---------|------|--------|
| 1 | 开始 | startEvent | - |
| 2 | 员工提交申请 | userTask | ${applyUserId} |
| 3 | 部门经理审批 | userTask | manager (角色组) |
| 4 | 审批结果 | exclusiveGateway | 条件分支 |
| 5 | HR审批 | userTask | hr (角色组) |
| 6 | 结束 | endEvent | - |
### 连线与条件
开始 → 员工提交申请 → 部门经理审批 → 审批结果
├─ 通过 (result==1) → HR审批 → 结束
└─ 拒绝 (result==0) → 结束
确认以上信息正确?(y/n)
```
### Step 3: 生成 BPMN XML 并调用 API
用户确认后,执行以下步骤:
#### 3.1 生成唯一标识
```python
import time
ts = str(int(time.time() * 1000))
process_key = f"process_{ts}"
```
#### 3.2 构造 BPMN XML
阅读以下参考文件(按需):
- `references/bpmn-xml-skeleton.md` — XML 骨架 + 基本节点模板(必读)
- `references/bpmn-assignee-types.md` — 审批人配置 + 表达式审批人 + groupType速查必读
- `references/bpmn-layout.md` — 节点ID命名 + 布局计算(必读)
- `references/bpmn-countersign.md` — 会签配置(需要会签时读)
- `references/bpmn-task-extend.md` — taskExtendJson + 监听器完整清单(配置节点行为时读)
- `references/bpmn-examples.md` — 完整示例 + Python脚本 + 流程模式
- `references/bpmn-advanced.md` — 条件表达式 + 抄送 + 按钮 + 服务任务 + API
- `references/bpmn-subprocess-gateway.md` — 网关 + 子流程(子流程/网关时读)
- `references/bpmn-db-config.md` — 数据库配置表
- `references/example/*.bpmn`**生产环境真实流程示例**(生成前先阅读最相似的示例学习写法)
核心要点:
- 使用 `bpmn2:` 命名空间前缀(新版设计器规范)
- 必须包含流程结束监听器和任务创建监听器
- 必须包含 `bpmndi:BPMNDiagram` 图形布局信息
- 节点 ID 使用有意义的命名(如 `task_apply``gateway_result`
#### 3.3 构造 nodes 参数
从所有 `userTask` 节点中提取,格式:
```
id=<节点ID>###nodeName=<节点名称>@@@id=<节点ID>###nodeName=<节点名称>@@@
```
#### 3.4 使用 Python 调用 API必须用 Python不要用 curl
**重要限制(实战踩坑):**
1. **Windows 环境下 curl 发送中文会乱码**,必须使用 Python 的 urllib 确保 UTF-8 编码
2. **禁止使用 `python3 -c "..."` 内联方式**,因为 BPMN XML 中的 `${applyUserId}` 等表达式会被 bash 当作 shell 变量展开,导致 `unexpected EOF` 错误
3. **必须先用 Write 工具写入 `.py` 临时文件,再用 Bash 执行,最后删除临时文件**
**执行步骤:**
```
1. Write 工具 → 写入 create_process.py项目根目录
2. Bash 工具 → python create_process.py
3. Bash 工具 → rm create_process.py清理
```
**Python f-string 中的转义要点:**
- BPMN 表达式 `${applyUserId}` → f-string 中写作 `${{applyUserId}}`(双花括号转义)
- taskExtendJson 的 `value` 属性用**单引号**包裹 JSON → `value='{{"sameMode":2,...}}'`(避免与 XML 双引号冲突,同时 JSON 花括号也需双花括号转义)
- `flowable:candidateUsers="${flowNodeExecution.getDepartLeaders(execution)}"` → f-string 中写作 `${{flowNodeExecution.getDepartLeaders(execution)}}`
**Python 脚本模板:**
```python
import urllib.request
import urllib.parse
import json
import time
API_BASE = '{用户提供的后端地址}'
TOKEN = '{用户提供的 X-Access-Token}'
ts = str(int(time.time() * 1000))
process_key = f'process_{ts}'
process_name = '流程名称'
# 用 f-string 构造 XML注意 ${{}} 双花括号转义
bpmn_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions ...>
<bpmn2:process id="{process_key}" name="{process_name}">
...
<bpmn2:userTask id="task_apply" name="申请人填写" flowable:assignee="${{applyUserId}}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value='{{"sameMode":2,...}}' />
...
</bpmn2:extensionElements>
</bpmn2:userTask>
...
</bpmn2:process>
<bpmndi:BPMNDiagram>...</bpmndi:BPMNDiagram>
</bpmn2:definitions>'''
nodes_str = 'id=task_apply###nodeName=申请人填写@@@...'
data = {
'processDefinitionId': '0',
'processName': process_name,
'processkey': process_key,
'typeid': 'oa',
'lowAppId': '',
'params': '',
'nodes': nodes_str,
'processDescriptor': bpmn_xml,
'realProcDefId': '',
'startType': 'manual'
}
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.Request(
f'{API_BASE}/act/designer/api/saveProcess',
data=encoded_data,
headers={
'X-Access-Token': TOKEN,
'X-Sign': '00000000000000000000000000000000',
'X-Tenant-Id': '1',
'X-Timestamp': str(int(time.time() * 1000)),
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
method='POST'
)
resp = urllib.request.urlopen(req)
result = json.loads(resp.read().decode('utf-8'))
print(json.dumps(result, ensure_ascii=False, indent=2))
print(f'\\nProcess Key: {process_key}')
```
#### 3.5 检查结果
- `success: true` → 流程创建成功,记录返回的 `obj`流程ID
- `success: false` → 输出错误信息,检查 processkey 是否重复等
### Step 4: 输出结果
```
## 流程创建成功
- 流程ID{obj}
- 流程名称:{processName}
- 流程Key{processkey}
- 目标环境:{API_BASE}
请在流程设计器中查看:打开 JeecgBoot 后台 → 流程管理 → 流程设计 → 找到该流程
```
---
## 编辑已有流程
如果用户要修改已有流程,需提供 `processDefinitionId`流程数据表ID调用同一接口`processDefinitionId` 改为实际ID 即可。
---
## 错误处理
| 错误 | 解决方案 |
|------|---------|
| Token 过期401/认证失败) | 提示用户重新获取 X-Access-Token |
| `流程ID重复` | 重新生成时间戳作为 processkey |
| `不是最新版本` | 先查询最新的 processDefinitionId 再保存 |
| 中文乱码 | 确认使用 Python urllib不要用 curl |
| 连接超时 | 确认后端地址可达,检查网络 |
## 数据库配置表
流程创建后,可通过以下数据库表进一步配置节点行为、表单绑定和字段权限:
| 表名 | 说明 | 用途 |
|------|------|------|
| `ext_act_process` | 流程主表 | 流程属性、XML、发起方式、催办/撤回/通知等 |
| `ext_act_process_form` | 表单绑定 | 流程与业务表单关联标题表达式表单类型1=Online/2=DesForm/3=自定义) |
| `ext_act_process_node` | 节点配置 | 每个审批节点的功能开关(编辑/抄送/转办/加签/驳回等) |
| `ext_act_process_node_auth` | 字段权限 | 每个节点上表单字段的显示/隐藏/可编辑/禁用控制 |
详细的字段说明和取值参见 `references/bpmn-db-config.md`
## 参考文档
- 阅读 `references/bpmn-templates.md` 获取参考文件索引(已拆分为 8 个子文件)

View File

@@ -0,0 +1,199 @@
# JeecgBoot BPM 流程自动生成 — Skills 使用指南
> 通过自然语言描述AI 自动生成 Flowable BPMN 2.0 XML 并调用 API 创建/修改流程。
## 前置准备
使用前需准备两样东西:
| 信息 | 获取方式 | 示例 |
|------|---------|------|
| **后端地址** | 运维/开发提供 | `https://api3.boot.jeecg.com` |
| **X-Access-Token** | 浏览器 F12 → Network → 任意请求 Headers | `eyJhbGciOiJIUzI1NiJ9...` |
---
## 实战案例:车辆出差申请流程
以下是一个完整的流程创建和迭代修改过程,展示了 skill 的实际使用方式。
### 第一步:创建基础流程
**用户输入:**
```
创建一个车辆出差申请流程
```
**AI 交互过程:**
1. AI 询问后端地址和 Token
2. 用户提供凭据后AI 设计流程并展示摘要:
```
## 流程摘要
- 流程名称:车辆出差申请流程
- 流程类型oa
### 流程节点
| 序号 | 节点名称 | 类型 | 审批人 |
|------|---------|------|--------|
| 1 | 开始 | startEvent | - |
| 2 | 申请人填写 | userTask (草稿) | ${applyUserId} |
| 3 | 部门负责人审批 | userTask | 表达式(部门负责人) |
| 4 | 分管领导审批 | userTask | 表达式(上一级部门负责人) |
| 5 | 车辆调度确认 | userTask | 上一节点指派 |
| 6 | 结束 | endEvent | - |
确认以上信息正确?(y/n)
```
3. 用户确认 `y`AI 自动生成 BPMN XML 并调用 API
4. 返回结果:
```
## 流程创建成功
- 流程ID2032497475959439362
- 流程Keyprocess_1773420125267
```
---
### 第二步:增加审批节点
**用户输入:**
```
修改刚才的流程,加入一个总经理审批节点,最后一个节点
```
**AI 交互过程:**
1. AI 基于已有流程信息,展示修改后的摘要(标注变更项):
```
| 序号 | 节点名称 | 类型 | 审批人 | 变更 |
|------|---------|------|--------|------|
| ... | (原有节点) | ... | ... | |
| 6 | 总经理审批 | userTask | 表达式(上两级部门负责人) | 新增 |
| 7 | 结束 | endEvent | - | |
```
2. 用户确认后AI 使用相同的 `processDefinitionId``processkey` 调用 API 更新流程
3. 流程修改成功
---
### 第三步:增加条件分支
**用户输入:**
```
在部门负责人审批节点后面增加分支条件如果用车时间超过3天则需要人力审批然后再流向车辆调度确认
```
**AI 交互过程:**
1. AI 识别出需要排他网关exclusiveGateway展示分支逻辑
```
开始 → 申请人填写 → 部门负责人审批 → 用车天数判断
├─ ≤3天 ──────────────→ 分管领导审批 → 车辆调度确认 → 总经理审批 → 结束
└─ >3天 → 人力审批(hr角色) ↗
```
2. 用户确认后API 更新流程
**最终流程图效果:**
```
○ 开始
[申请人填写] ← 草稿节点,首次自动跳过
[部门负责人审批] ← 表达式: getDepartLeaders
◇ 用车天数判断
↙ ↘
≤3天 >3天
↓ ↓
│ [人力审批] ← hr 角色组
↓ ↙
[分管领导审批] ← 表达式: getLevel1DepartLeaders
[车辆调度确认] ← 上一节点指派
[总经理审批] ← 表达式: getLevel2DepartLeaders
● 结束
```
---
## 使用技巧
### 1. 描述流程的多种方式
```
# 简单描述
创建一个请假审批流程先经理审批再HR审批
# 指定审批人
创建流程:开始 → admin审批 → hr角色组审批 → 结束
# 描述条件分支
报销流程金额大于1万需要总监审批否则经理审批即可
# 描述会签
创建合同审批流程,需要法务部和财务部同时会签
```
### 2. 修改已有流程
```
# 增加节点
在经理审批后面加一个总监审批
# 增加条件分支
在提交节点后加一个金额判断超过5000走总监审批
# 修改审批人
把部门经理审批改成角色组 manager
# 删除节点
去掉HR审批节点
```
### 3. 支持的审批人类型
| 说法 | AI 识别为 |
|------|----------|
| "admin审批" | 固定指定人 |
| "发起人/申请人" | `${applyUserId}` 表达式 |
| "部门负责人" | `getDepartLeaders` 表达式 |
| "分管领导" | `getLevel1DepartLeaders` 表达式 |
| "hr角色" / "角色组" | `candidateGroups` + `groupType="role"` |
| "上一节点指派" | `isAssignedByPreviousNode=true` |
| "张三或李四" | `candidateUsers` 候选人 |
| "会签/多人同时审批" | 多实例 + `countersignRule` |
### 4. 支持的条件分支
| 说法 | AI 生成 |
|------|--------|
| "金额大于1万" | `${amount > 10000}` |
| "天数超过3天" | `${use_days > 3}` |
| "通过/拒绝" | `${result == 1}` / `${result == 0}` |
| "部长以上职务" | `${oaUtil.branchConditionByPost(...)}` |
> 条件变量名需与业务表单字段名一致。
---
## 注意事项
1. **Token 有效期**JWT Token 有过期时间,过期后需重新从浏览器获取
2. **流程字段绑定**:条件分支中的变量名(如 `use_days`)需与关联表单的字段名一致
3. **同一会话内可连续修改**AI 会记住当前流程的 ID 和 Key无需重复提供
4. **修改是覆盖式的**:每次修改会提交完整的 BPMN XML不是增量更新
5. **创建后需绑定表单**:流程创建后,需在 JeecgBoot 后台绑定业务表单才能正常发起

View File

@@ -0,0 +1,155 @@
# 条件表达式、抄送、按钮与服务任务
## 1. 条件表达式系统(来自设计器源码)
### 1.1 系统内置变量(可用于网关条件)
| 变量名 | 含义 | 用法示例 |
|--------|------|---------|
| `applyUserId` | 发起人用户名 | `${applyUserId == 'admin'}` |
| `applyUserDept` | 发起人部门 | `${applyUserDept == '部门ID'}` |
| `applyUserDeptManager` | 发起部门负责人 | `${applyUserDeptManager == 'username'}` |
| `applyUserParentDeptManager` | 上级部门负责人 | 同上 |
| `lastAssignee` | 上个节点处理人 | `${lastAssignee == 'admin'}` |
| `applyUserPostLevel` | 发起人职级 | `${applyUserPostLevel == '职级ID'}` |
| `applyUserApprovalRole` | 发起人审批角色 | `${applyUserApprovalRole == '角色ID'}` |
| `applyDate` | 发起日期 | `${applyDate > '2026-01-01'}` |
| `result` | 审批结果 | `${result == 1}` 通过 / `${result == 0}` 拒绝 |
### 1.2 条件运算符
| 运算符 | 含义 | 适用类型 |
|--------|------|---------|
| `eq` / `==` | 等于 | 字符串、数字、日期 |
| `ne` / `!=` | 不等于 | 字符串、数字、日期 |
| `gt` / `>` | 大于 | 数字、日期 |
| `gte` / `>=` | 大于等于 | 数字、日期 |
| `lt` / `<` | 小于 | 数字、日期 |
| `lte` / `<=` | 小于等于 | 数字、日期 |
| `in` | 在列表中 | 字符串、数字 |
| `not_in` | 不在列表中 | 字符串、数字 |
| `contains` | 包含 | 字符串 |
| `is_empty` | 为空 | 字符串、数字、文件 |
| `is_not_empty` | 不为空 | 字符串、数字、文件 |
| `is_department_manager` | 是部门负责人 | applyUserId, lastAssignee |
| `is_not_department_manager` | 不是部门负责人 | applyUserId, lastAssignee |
---
## 2. 抄送配置CcConfigJson
用户任务可配置抄送人,通知相关人员但不影响审批流程:
```xml
<bpmn2:userTask id="task_xxx" name="审批">
<bpmn2:extensionElements>
<flowable:CcConfigJson value="${BASE64_ENCODED_JSON}" />
</bpmn2:extensionElements>
</bpmn2:userTask>
```
### 抄送类型
| 类型 | 说明 |
|------|------|
| `candidateUsers` | 指定人 |
| `candidateRoles` | 指定角色 |
| `candidateDeptPositions` | 指定岗位 |
| `submitter_user` | 提交人本人 |
| `submitter_dept_leader` | 提交人部门负责人 |
| `submitter_parent_dept_leader` | 上级部门负责人 |
| `dept_members` | 本部门成员 |
| `dept_leader` | 部门负责人 |
---
## 3. 自定义按钮Button
用户任务可配置自定义操作按钮:
```xml
<bpmn2:userTask id="task_xxx" name="审批">
<bpmn2:extensionElements>
<flowable:Button id="btn_1" name="同意" code="approve" isHide="0" next="task_next" sort="1" />
<flowable:Button id="btn_2" name="拒绝" code="reject" isHide="0" next="end" sort="2" />
<flowable:Button id="btn_3" name="转办" code="transfer" isHide="0" sort="3" />
</bpmn2:extensionElements>
</bpmn2:userTask>
```
| 属性 | 说明 |
|------|------|
| `id` | 按钮唯一ID |
| `name` | 显示名称 |
| `code` | 按钮编码标识 |
| `isHide` | 是否隐藏0=显示1=隐藏) |
| `next` | 点击后跳转的目标节点ID |
| `sort` | 显示排序 |
---
## 4. 服务任务ServiceTask
### 4.1 API 服务任务
自动调用外部 HTTP 接口:
```xml
<bpmn2:serviceTask id="service_api" name="调用外部接口"
flowable:class="org.jeecg.modules.extbpm.listener.service.ApiServiceTaskDelegate">
<bpmn2:extensionElements>
<flowable:ApiServiceTaskConfig value="${BASE64_ENCODED_JSON}" />
</bpmn2:extensionElements>
</bpmn2:serviceTask>
```
ApiServiceTaskConfig JSON 结构:
```json
{
"apiUrl": "https://api.example.com/endpoint",
"method": "GET|POST",
"headers": {},
"parameters": {},
"timeout": 30000,
"retryCount": 0
}
```
### 4.2 AI 服务任务
调用 AI 大模型进行智能处理:
```xml
<bpmn2:serviceTask id="service_ai" name="AI智能处理"
flowable:class="org.jeecg.modules.extbpm.listener.service.AigcServiceTaskDelegate">
<bpmn2:extensionElements>
<flowable:AiServiceTaskConfig value="${BASE64_ENCODED_JSON}" />
</bpmn2:extensionElements>
</bpmn2:serviceTask>
```
AiServiceTaskConfig JSON 结构:
```json
{
"aiFlowId": "AI对话流ID",
"inputParams": {},
"outputParams": {}
}
```
---
## 5. 设计器 API 端点一览
| API 路径 | 用途 |
|----------|------|
| `act/designer/api/saveProcess` | 保存/新建流程 |
| `act/designer/api/getProcessXml` | 获取流程 XML |
| `act/designer/api/getTypes` | 获取流程类型列表 |
| `act/designer/api/getPageUsers` | 获取用户列表(审批人选择) |
| `act/designer/api/getGroups` | 获取角色/组列表 |
| `act/designer/api/getRoleNameByCodes` | 根据角色编码获取名称 |
| `act/designer/api/getExpressions` | 获取可用表达式列表 |
| `act/designer/api/getListenersByType` | 获取监听器列表 |
| `sys/sysDepart/queryDepartAndPostTreeSync` | 获取部门+岗位树 |
| `sys/position/list` | 获取职级列表 |

View File

@@ -0,0 +1,356 @@
# 审批人配置详解
JeecgBoot BPM 支持 7 种审批人配置方式,每种对应不同的 XML 属性组合。
## 1. 固定指定人assignee
最简单的方式,固定指定一个用户处理:
```xml
<bpmn2:userTask id="task_xxx" name="固定人审批" flowable:assignee="admin" />
```
## 2. 发起人/流程变量(表达式)
通过表达式动态获取审批人:
```xml
<!-- 流程发起人 -->
<bpmn2:userTask id="task_xxx" name="发起人提交" flowable:assignee="${applyUserId}" />
<!-- 部门负责人(自定义变量) -->
<bpmn2:userTask id="task_xxx" name="部门领导审批" flowable:assignee="${deptLeader}" />
```
## 3. 候选人(多人选一)— candidateUsers
多人中任一人可认领处理:
```xml
<bpmn2:userTask id="task_xxx" name="多人审批" flowable:candidateUsers="admin,jeecg,zhangsan" />
```
## 4. 角色审批 — candidateGroups + groupType="role"
按角色编码分配,角色下所有用户都可处理:
```xml
<bpmn2:userTask id="task_xxx" name="角色审批"
flowable:candidateGroups="admin,jeecg"
flowable:groupType="role" />
```
带跳过审批人配置taskExtendJson
```xml
<bpmn2:userTask id="task_xxx" name="审批角色"
flowable:candidateGroups="vue3,jeecg,admin"
flowable:groupType="role">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{
&quot;sameMode&quot;:0,
&quot;isSkipAssigneeEmpty&quot;:false,
&quot;isSkipAssigneeOnePersion&quot;:true,
&quot;isSkipApproval&quot;:false,
&quot;isAssignedByPreviousNode&quot;:false,
&quot;isEmptyAssignedByPreviousNode&quot;:true
}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
```
## 5. 部门审批 — candidateGroups + groupType="dept"
按部门 ID 分配,部门下所有用户都可处理:
```xml
<bpmn2:userTask id="task_xxx" name="部门审批"
flowable:candidateGroups="6d35e179cd814e3299bd588ea7daed3f,c6d7cb4deeac411cb3384b1b31278596"
flowable:groupType="dept">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:true,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:true,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
```
## 6. 部门岗位审批 — candidateGroups + groupType="deptPosition"
按部门岗位 ID 分配审批人:
```xml
<bpmn2:userTask id="task_xxx" name="候选岗位审批"
flowable:candidateGroups="2032392253970890754,2032395269063098370"
flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:true,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:true,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
```
## 7. 上一节点指派 — isAssignedByPreviousNode
由上一审批节点在完成时选择下一步处理人:
```xml
<bpmn2:userTask id="task_xxx" name="分配经理">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{
&quot;sameMode&quot;:0,
&quot;isSkipAssigneeEmpty&quot;:false,
&quot;isSkipAssigneeOnePersion&quot;:false,
&quot;isSkipApproval&quot;:false,
&quot;isAssignedByPreviousNode&quot;:true,
&quot;isEmptyAssignedByPreviousNode&quot;:false
}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
```
## 8. 职务级别审批 — groupType="position" + 表达式
通过表达式动态获取指定职务级别的用户:
```xml
<bpmn2:userTask id="task_xxx" name="职务级别审批"
flowable:assignee="${assigneeUserId}"
flowable:candidateUsers="${oaFlowExpression.getApplyUserDeptPositionLevel(sys_org_code, applyUserId, '岗位ID')}"
flowable:groupType="position"
flowable:countersignRule="countersign_proportion">
...
</bpmn2:userTask>
```
## 9. 表达式审批人 — Spring Bean 动态获取
系统提供 3 个 Spring Bean 用于通过表达式动态获取审批人,在 `flowable:candidateUsers``flowable:assignee` 中使用。
### 9.1 `flowNodeExecution` — 部门层级表达式
Bean 类:`org.jeecg.modules.expression.FlowNodeExecutionExpression`
| 表达式 | 说明 | 依赖变量 |
|--------|------|---------|
| `${flowNodeExecution.getDepartLeaders(execution)}` | 发起部门的部门负责人 | `sys_org_code` |
| `${flowNodeExecution.getLevel1DepartLeaders(execution)}` | 上一级部门负责人 | `sys_org_code` |
| `${flowNodeExecution.getLevel2DepartLeaders(execution)}` | 上二级部门负责人 | `sys_org_code` |
| `${flowNodeExecution.getLevel3DepartLeaders(execution)}` | 上三级部门负责人 | `sys_org_code` |
| `${flowNodeExecution.getUserSuperPositionLevel1(execution)}` | 发起人上一级岗位人员 | `applyUserId` + `sys_org_code` |
| `${flowNodeExecution.getUserSuperPositionLevel2(execution)}` | 发起人上两级岗位人员 | `applyUserId` + `sys_org_code` |
| `${flowNodeExecution.getUserSuperPositionLevel3(execution)}` | 发起人上三级岗位人员 | `applyUserId` + `sys_org_code` |
**用法示例:**
```xml
<bpmn2:userTask id="task_dept_leader" name="部门负责人审批"
flowable:candidateUsers="${flowNodeExecution.getDepartLeaders(execution)}">
</bpmn2:userTask>
```
### 9.2 `oaUtil` — 流程上下文表达式
Bean 类:`org.jeecg.modules.expression.OaUtilExpression`
**办理人表达式:**
| 表达式 | 说明 |
|--------|------|
| `${oaUtil.getLastTaskAssignee(execution)}` | 获取上一节点处理人用户名(排除加签节点,支持驳回场景) |
| `${oaUtil.getLastTaskAssigneePositionLevel1(execution)}` | 上一节点处理人的上一级岗位人员(支持会签节点) |
**条件分支表达式(用于 sequenceFlow 的 conditionExpression**
| 表达式 | 说明 | 操作符 |
|--------|------|--------|
| `${oaUtil.branchConditionByPost('eq',applyUserId,'部长')}` | 按发起人职务判断 | eq=等于, ne=不等于 |
| `${oaUtil.branchConditionByOrg('eq',sys_org_code,'A01A01')}` | 按发起人部门编码判断 | eq=等于, ne=不等于 |
**上一节点处理人条件判断写法:**
```xml
<!-- 等于 -->
<conditionExpression>${oaUtil.getLastTaskAssignee(execution) == 'zhangsan'}</conditionExpression>
<!-- 不等于 -->
<conditionExpression>${oaUtil.getLastTaskAssignee(execution) != 'zhangsan'}</conditionExpression>
<!-- in属于多人之一 -->
<conditionExpression>${'zhangsan,lisi'.contains(oaUtil.getLastTaskAssignee(execution))}</conditionExpression>
<!-- not in -->
<conditionExpression>${!'zhangsan,lisi'.contains(oaUtil.getLastTaskAssignee(execution))}</conditionExpression>
```
### 9.3 `oaFlowExpression` — 职级审批表达式
Bean 类:`org.jeecg.modules.extbpm.process.common.expression.OaFlowExpression`
| 表达式 | 说明 |
|--------|------|
| `${oaFlowExpression.getApplyUserDeptPositionLevel(sys_org_code, applyUserId, 'targetPositionId')}` | 按职级查找审批人见下方4种规则 |
| `${oaFlowExpression.getUserSuperPositionLevel1(applyUserId)}` | 发起人上一级岗位人员(按用户名,不需要 execution |
| `${oaFlowExpression.getUserSuperPositionLevel2(applyUserId)}` | 发起人上两级岗位人员 |
| `${oaFlowExpression.getUserSuperPositionLevel3(applyUserId)}` | 发起人上三级岗位人员 |
**`getApplyUserDeptPositionLevel` 4种规则**
| 发起人职级 | 目标职级 | 行为 |
|-----------|---------|------|
| 职员层(职员/部长/副部长) | 职员层 | 查发起人所在部门下对应职级人员 |
| 职员层 | 领导层(董事长/总经理/副总经理) | 查发起人所在公司领导班子中对应职级 |
| 领导层 | 职员层 | 返回空(领导不需要职员审批) |
| 领导层 | 领导层 | 查当前领导班子中对应职级 |
**用法示例(配合 groupType="position"**
```xml
<bpmn2:userTask id="task_position" name="职级审批"
flowable:candidateUsers="${oaFlowExpression.getApplyUserDeptPositionLevel(sys_org_code, applyUserId, '1958471111989067778')}"
flowable:groupType="position">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:true,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
```
### 9.4 条件评估工具类
`org.jeecg.modules.expression.util.EvalConditionUtils` 提供条件评估:
| 操作符 | 含义 | 支持类型 |
|--------|------|---------|
| `eq` | 等于(忽略大小写) | String / Set<String>(任一匹配) |
| `ne` | 不等于(忽略大小写) | String / Set<String>(全部不匹配) |
---
## 10. groupType 完整速查表
| groupType 值 | 含义 | candidateGroups 值 | 说明 |
|-------------|------|-------------------|------|
| `role` | 系统角色 | 角色编码(逗号分隔) | `jeecg,noticeReviewer` |
| `approvalRole` | 审批角色 | 审批角色编码(逗号分隔) | 独立于系统角色的审批专用角色 |
| `dept` | 部门 | 部门ID逗号分隔 | `6d35e179cd814e3299bd588ea7daed3f` |
| `deptPosition` | 部门岗位 | 岗位ID逗号分隔 | `2032392253970890754,2032395269063098370` |
| `position` | 职务级别 | 通过表达式获取 | 配合 `oaFlowExpression` 使用 |
| `formData` | 表单数据 | 从表单字段动态获取 | 用户选择控件的值作为审批人 |
| _(无)_ | 指定人/候选人 | 不使用 candidateGroups | 用 `assignee``candidateUsers` |
## 11. 审批人数据来源表
生成流程时如需查询实际的角色编码、用户名、部门ID可查询以下系统表
| 数据 | 表名 | 关键字段 | 查询示例 |
|------|------|---------|---------|
| 角色编码 | `sys_role` | `role_code` | `SELECT role_code, role_name FROM sys_role` |
| 用户名 | `sys_user` | `username` | `SELECT username, realname FROM sys_user` |
| 部门ID | `sys_depart` | `id` | `SELECT id, depart_name FROM sys_depart` |
**sys_depart.org_category 机构类别:**
| 值 | 含义 | 用途 |
|----|------|------|
| `1` | 公司 | 顶级组织 |
| `2` | 部门 | `groupType="dept"` 时使用部门ID |
| `3` | 岗位 | `groupType="deptPosition"` 时使用岗位ID |
| `4` | 子公司 | 子组织 |
## 12. 草稿节点(驳回到发起人)— assignee + sameMode:2
草稿节点用于"驳回到发起人"场景:流程被驳回后,发起人可修改数据并重新提交。
**核心特征:**
- `flowable:assignee="${applyUserId}"` — 指定发起人
- `sameMode: 2` — 特殊模式,标记为草稿/首节点
- `TaskCreatedAutoSubmitListener` — 首次提交时自动跳过此节点(首节点自动提交),驳回后才需要手动操作
- `TaskSkipApprovalListener` — 标准跳过审批监听器
```xml
<bpmn2:userTask id="task_draft" name="草稿" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:2,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:false,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="9c3064baa7074eab62e3c5b3b5458691" />
</bpmn2:extensionElements>
</bpmn2:userTask>
```
> **使用场景:** 在"驳回到发起人"流程模式中,将此节点放在开始事件之后。首次发起流程时,此节点会被 `TaskCreatedAutoSubmitListener` 自动跳过;当审批被驳回到此节点时,发起人需要手动修改数据后重新提交。
## 13. 完整参考示例 — 所有审批人类型串联
以下是一个包含所有审批人配置类型的完整流程 XML来自生产环境
```
开始 → 指定人(jeecg) → 候选多人(admin,jeecg) → 角色(jeecg,noticeReviewer)
→ 部门(两个部门ID) → 候选岗位(两个岗位ID) → 职级(表达式)
→ 会签固定人员(jeecg,qinfeng, 全部通过) → 结束
```
**关键 XML 片段:**
```xml
<!-- 1. 指定人 -->
<bpmn2:userTask id="Task_04woce4" name="指定人" flowable:assignee="jeecg" />
<!-- 2. 候选多人 -->
<bpmn2:userTask id="Task_1k76zxi" name="指定多人(候选多人)" flowable:candidateUsers="admin,jeecg">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:true,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:true,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 3. 角色 -->
<bpmn2:userTask id="Task_0gm5nm0" name="角色"
flowable:candidateGroups="jeecg,noticeReviewer" flowable:groupType="role">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:true,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:true,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 4. 部门 -->
<bpmn2:userTask id="Task_0hre67h" name="部门"
flowable:candidateGroups="6d35e179cd814e3299bd588ea7daed3f,c6d7cb4deeac411cb3384b1b31278596"
flowable:groupType="dept">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:true,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:true,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 5. 候选岗位 -->
<bpmn2:userTask id="Task_0d9ubtz" name="候选岗位"
flowable:candidateGroups="2032392253970890754,2032395269063098370"
flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:true,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:true,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 6. 职级 -->
<bpmn2:userTask id="Task_1euf9po" name="职级"
flowable:candidateUsers="${oaFlowExpression.getApplyUserDeptPositionLevel(sys_org_code, applyUserId, '1958471111989067778')}"
flowable:groupType="position">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:true,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:true,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 7. 会签固定人员(全部通过) -->
<bpmn2:userTask id="Task_1tj90vc" name="会签固定人员"
flowable:assignee="${assigneeUserId}" flowable:countersignRule="countersign_all">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:false,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:false,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskCountersignExtendJson value="eyJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVVc2VycyIsImF1ZGl0b3JVc2VySWRzIjpbImplZWNnIiwicWluZmVuZyJdLCJ0aW1lc3RhbXAiOjE3NzM0MTU4NTkwMTZ9" />
</bpmn2:extensionElements>
<bpmn2:multiInstanceLoopCharacteristics
flowable:collection="${flowUtil.stringToList('jeecg,qinfeng')}"
flowable:elementVariable="assigneeUserId">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
${nrOfCompletedInstances/nrOfInstances==1}
</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
```

View File

@@ -0,0 +1,242 @@
# 会签配置详解
会签(多实例任务):一个审批节点同时由多人处理,根据完成比例决定是否通过。
**重要前提:** 不启动多实例(不加 `multiInstanceLoopCharacteristics`),则只会创建一个任务,默认不启动。不启动多实例时,会签相关配置都无效。
## 1. 会签通用结构
```xml
<bpmn2:userTask id="task_xxx" name="会签节点"
flowable:assignee="${assigneeUserId}"
flowable:countersignRule="${RULE}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{...}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskCountersignExtendJson value="${BASE64_CONFIG}" />
</bpmn2:extensionElements>
<bpmn2:multiInstanceLoopCharacteristics isSequential="false"
flowable:collection="${COLLECTION_EXPR}"
flowable:elementVariable="assigneeUserId">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
${COMPLETION_CONDITION}
</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
```
## 2. 会签规则countersignRule
| 规则值 | 说明 | completionCondition |
|--------|------|---------------------|
| `countersign_all` | 全部通过 | `${nrOfCompletedInstances/nrOfInstances==1}` |
| `countersign_one` | 一人通过即可 | `${nrOfCompletedInstances/nrOfInstances>0}` |
| `countersign_half` | 半数通过 | `${nrOfCompletedInstances/nrOfInstances>=0.5}` |
| `countersign_proportion` | 按比例通过(自定义比例) | `${nrOfCompletedInstances/nrOfInstances>=0.N}` |
| `countersign_custom` | 自定义选人 | 自定义 |
> **注意:** XML 中 `>=` 需要写成 `&gt;=`,如 `${nrOfCompletedInstances/nrOfInstances&gt;=0.6}`
## 3. 顺序会签 vs 并行会签
| 对比项 | 并行会签isSequential="false" | 顺序会签isSequential="true" |
|--------|-------------------------------|-------------------------------|
| 任务创建 | 同时为所有审批人创建任务 | 按顺序逐个创建,前一个完成才创建下一个 |
| nrOfActiveInstances | 等于尚未完成的审批人数 | 始终为 1 |
| 适用场景 | 多人同时审批,互不影响 | 按层级逐级审批 |
| 完成条件触发 | 每完成一个就检查条件 | 每完成一个就检查条件 |
```xml
<!-- 并行会签(默认,所有人同时收到任务) -->
<bpmn2:multiInstanceLoopCharacteristics isSequential="false" ...>
<!-- 顺序会签(按顺序逐个审批) -->
<bpmn2:multiInstanceLoopCharacteristics isSequential="true" ...>
```
## 4. 多实例内置流程变量
多实例自动创建以下 3 个流程变量,用于 `completionCondition` 表达式:
| 变量名 | 类型 | 说明 |
|--------|------|------|
| `nrOfInstances` | int | 实例总数(审批人总数) |
| `nrOfActiveInstances` | int | 当前活跃的(未完成的)实例数。**顺序会签时此值始终为 1** |
| `nrOfCompletedInstances` | int | 已完成的实例个数 |
**完成条件示例:**
```
${nrOfCompletedInstances/nrOfInstances==1} → 全部完成
${nrOfCompletedInstances/nrOfInstances>0} → 至少一人完成
${nrOfCompletedInstances/nrOfInstances>=0.5} → 50%完成
${nrOfCompletedInstances/nrOfInstances>=0.6} → 60%完成时,删除其他未完成任务,继续下一步
```
## 5. 会签工具 Bean — flowUtil
`flowUtil` 是系统暴露的 Spring Bean提供会签人员集合获取方法
| 方法 | 说明 | 用于 |
|------|------|------|
| `${flowUtil.stringToList('user1,user2')}` | 将逗号分隔字符串转为 List 集合 | 固定人员会签candidateUsers |
| `${flowUtil.getAssigneeUsers(execution,'BASE64配置')}` | 根据 Base64 编码的配置动态获取审批人列表 | 部门/岗位/表单字段会签 |
**`stringToList` 的变量写法:**
```xml
<!-- 固定人员 -->
flowable:collection="${flowUtil.stringToList('admin,jeecg,zhangsan')}"
<!-- 使用流程变量(动态) -->
flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}"
```
**`getAssigneeUsers` 的 Base64 配置:** 将 taskCountersignExtendJson 的 JSON去掉 timestamp 和 countersignProportion进行 Base64 编码后作为参数传入。
## 4. 会签审批人类型auditorUserType
taskCountersignExtendJson 是 Base64 编码的 JSON解码后结构如下
### 人员会签candidateUsers
```json
{
"auditorUserType": "candidateUsers",
"auditorUserIds": ["jeecg", "admin"],
"countersignProportion": "0.2",
"timestamp": 1758257673121
}
```
对应 XML collection
```xml
flowable:collection="${flowUtil.stringToList('jeecg,admin')}"
```
### 部门会签candidateDepts
```json
{
"auditorUserType": "candidateDepts",
"auditorDeptIds": ["部门ID1", "部门ID2"],
"timestamp": 1758257664644
}
```
对应 XML collection
```xml
flowable:collection="${flowUtil.getAssigneeUsers(execution,'BASE64编码的配置')}"
```
### 职务会签candidatePosts
```json
{
"auditorUserType": "candidatePosts",
"auditorPostIds": ["职务ID1", "职务ID2"],
"timestamp": 1758202368122
}
```
对应 XML collection
```xml
flowable:collection="${flowUtil.getAssigneeUsers(execution,'BASE64编码的配置')}"
```
### 表单字段会签formData
从表单中的用户选择控件动态获取会签人:
```json
{
"auditorUserType": "formData",
"auditorCountersignFormField": "select_user_xxx",
"auditorCountersignFormFieldType": "select-user",
"timestamp": 1758257668105
}
```
对应 XML collection
```xml
flowable:collection="${flowUtil.getAssigneeUsers(execution,'BASE64编码的配置')}"
```
## 7. 完整会签示例 — 人员会签(并行,全部通过)
```xml
<bpmn2:userTask id="task_countersign" name="部门会签"
flowable:assignee="${assigneeUserId}"
flowable:countersignRule="countersign_all">
<bpmn2:extensionElements>
<flowable:taskCountersignExtendJson value="eyJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVVc2VycyIsImF1ZGl0b3JVc2VySWRzIjpbImFkbWluIiwiamVlY2ciXSwidGltZXN0YW1wIjoxNzU4MjU3NjczMTIxfQ==" />
</bpmn2:extensionElements>
<bpmn2:multiInstanceLoopCharacteristics
flowable:collection="${flowUtil.stringToList('admin,jeecg')}"
flowable:elementVariable="assigneeUserId">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
${nrOfCompletedInstances/nrOfInstances==1}
</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
```
## 8. 完整会签示例 — 人员比例通过并行20%
```xml
<bpmn2:userTask id="task_vote" name="投票表决"
flowable:assignee="${assigneeUserId}"
flowable:countersignRule="countersign_proportion"
flowable:countersignProportion="0.2">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:false,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:false,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskCountersignExtendJson value="eyJjb3VudGVyc2lnblByb3BvcnRpb24iOiIwLjIiLCJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVVc2VycyIsImF1ZGl0b3JVc2VySWRzIjpbImplZWNnIl0sInRpbWVzdGFtcCI6MTc1ODI1NzY3MzEyMX0=" />
</bpmn2:extensionElements>
<bpmn2:multiInstanceLoopCharacteristics
flowable:collection="${flowUtil.stringToList('jeecg')}"
flowable:elementVariable="assigneeUserId">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
${nrOfCompletedInstances/nrOfInstances&gt;=0.2}
</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
```
## 9. 完整会签示例 — 岗位会签顺序比例20%
来自生产环境的岗位candidatePosts顺序会签示例使用 `flowUtil.getAssigneeUsers` 动态获取审批人:
**taskCountersignExtendJson Base64 解码后:**
```json
{
"countersignProportion": "0.2",
"auditorUserType": "candidatePosts",
"auditorPostIds": ["2032387176954642433", "1958471111989067778"],
"timestamp": 1773418938149
}
```
**getAssigneeUsers 参数 Base64 解码后(不含 timestamp 和 countersignProportion**
```json
{
"auditorUserType": "candidatePosts",
"auditorPostIds": ["2032387176954642433", "1958471111989067778"]
}
```
```xml
<bpmn2:userTask id="task_dept_mgr" name="部门经理审批"
flowable:assignee="${assigneeUserId}"
flowable:countersignRule="countersign_proportion">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:false,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:false,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskCountersignExtendJson value="eyJjb3VudGVyc2lnblByb3BvcnRpb24iOiIwLjIiLCJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVQb3N0cyIsImF1ZGl0b3JQb3N0SWRzIjpbIjIwMzIzODcxNzY5NTQ2NDI0MzMiLCIxOTU4NDcxMTExOTg5MDY3Nzc4Il0sInRpbWVzdGFtcCI6MTc3MzQxODkzODE0OX0=" />
</bpmn2:extensionElements>
<bpmn2:multiInstanceLoopCharacteristics isSequential="true"
flowable:collection="${flowUtil.getAssigneeUsers(execution,'eyJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVQb3N0cyIsImF1ZGl0b3JQb3N0SWRzIjpbIjIwMzIzODcxNzY5NTQ2NDI0MzMiLCIxOTU4NDcxMTExOTg5MDY3Nzc4Il19')}"
flowable:elementVariable="assigneeUserId" />
</bpmn2:userTask>
```
**关键差异:**
- `isSequential="true"` — 顺序逐个审批
- 没有 `completionCondition` — 当 countersignRule 为 proportion 但未写 completionCondition 时,系统通过 countersignProportion 属性和 taskCountersignExtendJson 中的 countersignProportion 值自动处理
- `flowUtil.getAssigneeUsers(execution, 'BASE64')` — 运行时根据岗位ID动态获取用户列表

View File

@@ -0,0 +1,176 @@
# 流程配置数据库表ext_act_process 系列)
流程除了 BPMN XML 外,还需要在 4 张配置表中存储流程属性、节点配置、表单绑定和字段权限。
## 1. ext_act_process — 流程主表
| 字段 | 说明 | 取值 |
|------|------|------|
| `id` | 流程ID主键 | 新建时返回 |
| `process_key` | 流程定义Key | `process_{timestamp}` |
| `process_name` | 流程名称 | 用户定义 |
| `process_type` | 流程分类 | 字典 `bpm_process_type` 的值,如 `oa``test` |
| `process_status` | 发布状态 | 0=未发布, 1=已发布 |
| `process_xml` | BPMN XMLlongblob | 完整 XML 内容 |
| `start_type` | 发起方式 | 见下表 |
| `urge_status` | 允许催办 | `0`=关, `1`=开 |
| `back_status` | 允许撤回 | `0`=关, `1`=开 |
| `graphic_status` | 显示流程图 | `0`=关, `1`=开 |
| `auto_submit_status` | 自动提交 | `0`=关, `1`=开 |
| `notify_way` | 通知方式 | 系统消息/邮件/钉钉/企微 |
| `open_status` | 启用状态 | 0=关, 1=开 |
| `run_concurrent_mode` | 并发模式 | 控制同一数据多次发起 |
| `iz_supervise` | 督办标记 | 0=否, 1=是 |
**start_type 发起方式:**
| 值 | 说明 |
|----|------|
| `manual` | 手工发起流程(默认) |
| `tableEvent` | 工作表触发(数据新增/修改时自动发起) |
| `buttonEvent` | 按钮事件触发(自定义按钮触发) |
| `timerEvent` | 定时触发 |
| `dateFieldEvent` | 根据表日期字段触发 |
| `userEvent` | 人员事件触发(如员工离职) |
| `subEvent` | 子流程触发 |
## 2. ext_act_process_form — 流程表单绑定
将流程与业务表单关联:
| 字段 | 说明 | 取值 |
|------|------|------|
| `relation_code` | 关联编码(唯一) | `onl_{表名}` / `desform_{表名}` / `dev_{表名}_001` |
| `biz_name` | 业务名称 | 可选 |
| `process_id` | 关联流程ID | 外键 |
| `form_table_name` | 表单表名 | 业务表名 |
| `form_type` | 表单类型 | 见下表 |
| `title_exp` | 标题表达式 | 如 `请假申请【${name}】` |
| `form_deal_style` | 表单处理风格 | `default` |
| `flow_status_col` | 流程状态字段 | 通常为 `bpm_status` |
| `trigger_action` | 触发动作 | `add`/`update`/`add\|update`tableEvent 时用) |
| `report_print_url` | 打印模板URL | 可选 |
**form_type 表单类型:**
| 值 | 说明 | relation_code 格式 |
|----|------|-------------------|
| `1` | Online表单低代码表单 | `onl_{tableName}` |
| `2` | 表单设计器DesForm | `desform_{formCode}` |
| `3` | 自定义开发表单 | `dev_{code}_001` |
**title_exp 标题表达式语法:** 使用 `${变量名}` 引用表单字段值,如 `请假【${name}】提交于${create_time}`
## 3. ext_act_process_node — 节点配置
控制每个审批节点的行为:
| 字段 | 说明 | 取值 |
|------|------|------|
| `process_id` | 关联流程ID | 外键 |
| `process_node_code` | 节点ID对应 XML 中的 userTask id | 如 `task_apply` |
| `process_node_name` | 节点名称 | 如 `部门经理审批` |
| `model_and_view` | PC端表单路径 | 如 `super/bpm/process/components/OnlineFormOpt` |
| `model_and_view_mobile` | 移动端表单路径 | 可选 |
| `node_timeout` | 超时提醒(小时) | 0=不提醒 |
| `form_edit_status` | 表单是否可编辑 | `0`=只读, `1`=可编辑 |
| `cc_status` | 允许抄送 | `0`=关, `1`=开 |
| `selnext_user_status` | 允许选择下一步处理人 | `0`=关, `1`=开 |
| `msg_status` | 消息通知 | `0`=关, `1`=开 |
| `transfer_status` | 允许转办 | `0`=关, `1`=开 |
| `add_sign_status` | 允许加签 | `0`=关, `1`=开 |
| `smart_back_status` | 允许智能回退 | `0`=关, `1`=开 |
| `reject_status` | 允许驳回 | `0`=关, `1`=开 |
| `allow_counter_sign_add_user` | 会签允许加人 | `0`=关, `1`=开 |
| `node_config_json` | 节点扩展配置JSON | 包含通知设置、审批人配置等 |
## 4. ext_act_process_node_auth — 字段权限配置
控制每个节点上表单字段的可见性和可编辑性:
| 字段 | 说明 | 取值 |
|------|------|------|
| `process_id` | 关联流程ID | 外键 |
| `process_node_code` | 节点ID | 如 `task_apply` |
| `rule_code` | 字段编码 | 表单字段名 |
| `rule_name` | 字段名称 | 显示名称 |
| `rule_type` | 策略类型 | `1`=显示, `2`=禁用 |
| `status` | 效果模式 | `1`=正向有效, `0`=反向有效 |
| `required` | 是否必填 | `0`=否, `1`=是 |
| `form_type` | 表单类型 | `1`/`2`/`3` 同上 |
| `form_biz_code` | 表单业务编码 | 表名或表单编码 |
| `desform_com_key` | DesForm 组件Key | DesForm 专用 |
**rule_type + status 组合效果:**
| rule_type | status | 效果 |
|-----------|--------|------|
| `1`(显示) | `1`(正向) | 字段**可见** |
| `1`(显示) | `0`(反向) | 字段**隐藏** |
| `2`(禁用) | `1`(正向) | 字段**禁用**(只读) |
| `2`(禁用) | `0`(反向) | 字段**可编辑** |
## 5. node_config_json 完整结构
节点扩展配置 JSON 包含以下关键字段(来自生产数据分析):
```json
{
// 审批人配置
"approverGroups": {
"approverType": "candidateUser",
"assigneeType": "assigneeByName|assigneeByExp",
"approverIds": ["admin", "jeecg"],
"approverNames": ["管理员", "jeecg"],
"roleIds": [], "roleNames": [],
"deptIds": [], "deptNames": [],
"postIds": [], "postNames": [],
"expressionsIds": ["${applyUserId}"],
"expressionsNames": ["获取发起人"],
"formTableType": "",
"levelMode": "1",
"variableContent": "", "variableTitle": "[]"
},
// 审批行为
"sameMode": 0, // 相同处理人: 0=不跳过, 1=跳过, 2=草稿
"skipApproval": 0, // 0=不跳过, 1=跳过审批
"assigneeIsEmpty": 0, // 审批人为空时: 0=不跳过
"approvalEnabled": true, // 启用审批
"approvalMethod": "1", // 审批方式
// 节点功能开关
"formEditStatus": false, // 表单可编辑
"ccStatus": true, // 抄送
"selnextUserStatus": true, // 选择下一步处理人
"msgStatus": false, // 消息通知
"transferStatus": true, // 转办
"rejectStatus": true, // 驳回
"allowAddSign": true, // 加签
"allowCountersignAddUser": false, // 会签加人
// 会签配置
"isSequential": false, // 顺序/并行
"collection": "${flowUtil.stringToList(assigneeUserIdList)}",
"elementVariable": "assigneeUserId",
// 分支相关
"approveResultBranch": false, // 审批结果分支
"hasResultBranch": false,
// 发起人节点标记
"applyUserNode": true, // 是否为发起人节点
// 超时配置
"timeType": "timeDate",
"level": "1",
// 更新记录(服务节点)
"expressionType": "delegateExpression",
"expressionValue": "${updateRecordDelegate}",
"formTableCode": "table_name",
"formTableSourceTaskId": "start",
"formTableSourceNodeType": "table",
"updateFields": "[...]"
}
```

View File

@@ -0,0 +1,394 @@
# 完整示例、Python 脚本与流程模式
## 1. 完整示例 — 请假审批流程
**流程描述:** 开始 → 员工提交 → 经理审批 → 排他网关(通过/拒绝) → HR审批 → 结束
### 1.1 节点定义
```xml
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId" />
<bpmn2:userTask id="task_apply" name="员工提交申请" flowable:assignee="${applyUserId}" />
<bpmn2:userTask id="task_manager" name="部门经理审批" flowable:assignee="manager" />
<bpmn2:exclusiveGateway id="gateway_result" name="审批结果" />
<bpmn2:userTask id="task_hr" name="HR审批" flowable:assignee="hr" />
<bpmn2:endEvent id="end" name="结束" />
```
### 1.2 连线定义
```xml
<bpmn2:sequenceFlow id="flow_1" name="" sourceRef="start" targetRef="task_apply" />
<bpmn2:sequenceFlow id="flow_2" name="" sourceRef="task_apply" targetRef="task_manager" />
<bpmn2:sequenceFlow id="flow_3" name="" sourceRef="task_manager" targetRef="gateway_result" />
<bpmn2:sequenceFlow id="flow_approve" name="通过" sourceRef="gateway_result" targetRef="task_hr">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 1}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_reject" name="拒绝" sourceRef="gateway_result" targetRef="end">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 0}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_4" name="" sourceRef="task_hr" targetRef="end" />
```
### 1.3 布局计算
```
节点列表(按垂直顺序):
start: type=startEvent, y=30, h=36 → bottom=66
task_apply: type=userTask, y=106, h=60 → bottom=166
task_manager: type=userTask, y=206, h=60 → bottom=266
gateway_result: type=exclusiveGateway, y=306, h=50 → bottom=356
task_hr: type=userTask, y=396, h=60 → bottom=456
end: type=endEvent, y=496, h=36 → bottom=532
```
### 1.4 nodes 参数
```
id=task_apply###nodeName=员工提交申请@@@id=task_manager###nodeName=部门经理审批@@@id=task_hr###nodeName=HR审批@@@
```
## 2. Python 调用脚本模板
```python
import urllib.request
import urllib.parse
import json
import time
def create_bpm_process(api_base, token, process_name, process_type, bpmn_xml, nodes_str, tenant_id="1"):
"""
创建 JeecgBoot BPM 流程
Args:
api_base: 后端地址,如 "https://api3.boot.jeecg.com"
token: X-Access-Token
process_name: 流程名称
process_type: 流程类型(如 "oa"
bpmn_xml: 完整 BPMN XML 字符串
nodes_str: nodes 参数字符串
tenant_id: 租户ID默认 "1"
Returns:
dict: API 返回结果
"""
ts = str(int(time.time() * 1000))
process_key = f"process_{ts}"
# 替换 XML 中的占位符
bpmn_xml = bpmn_xml.replace("${PROCESS_KEY}", process_key)
bpmn_xml = bpmn_xml.replace("${PROCESS_NAME}", process_name)
data = {
"processDefinitionId": "0",
"processName": process_name,
"processkey": process_key,
"typeid": process_type,
"lowAppId": "",
"params": "",
"nodes": nodes_str,
"processDescriptor": bpmn_xml,
"realProcDefId": "",
"startType": "manual"
}
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.Request(
f"{api_base}/act/designer/api/saveProcess",
data=encoded_data,
headers={
"X-Access-Token": token,
"X-Sign": "00000000000000000000000000000000",
"X-Tenant-Id": tenant_id,
"X-Timestamp": ts,
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
},
method="POST"
)
resp = urllib.request.urlopen(req)
result = json.loads(resp.read().decode('utf-8'))
return result, process_key
def update_bpm_process(api_base, token, process_id, process_key, process_name, process_type, bpmn_xml, nodes_str, tenant_id="1"):
"""
更新已有 JeecgBoot BPM 流程
Args:
api_base: 后端地址
token: X-Access-Token
process_id: 流程数据表ID新建时返回的 obj
process_key: 流程定义ID
process_name: 流程名称
process_type: 流程类型
bpmn_xml: 完整 BPMN XML 字符串
nodes_str: nodes 参数字符串
tenant_id: 租户ID默认 "1"
Returns:
dict: API 返回结果
"""
ts = str(int(time.time() * 1000))
data = {
"processDefinitionId": process_id,
"processName": process_name,
"processkey": process_key,
"typeid": process_type,
"lowAppId": "",
"params": "",
"nodes": nodes_str,
"processDescriptor": bpmn_xml,
"startType": "manual"
}
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.Request(
f"{api_base}/act/designer/api/saveProcess",
data=encoded_data,
headers={
"X-Access-Token": token,
"X-Sign": "00000000000000000000000000000000",
"X-Tenant-Id": tenant_id,
"X-Timestamp": ts,
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
},
method="POST"
)
resp = urllib.request.urlopen(req)
result = json.loads(resp.read().decode('utf-8'))
return result
```
## 3. 常见流程模式速查
### 模式A简单审批线性
```
开始 → 提交 → 审批 → 结束
```
### 模式B多级审批线性
```
开始 → 提交 → 经理审批 → 总监审批 → HR审批 → 结束
```
### 模式C条件分支排他网关
```
开始 → 提交 → 审批 → 网关
├─ 通过 → 下一步 → 结束
└─ 拒绝 → 结束
```
### 模式D金额条件分支
```
开始 → 提交 → 网关(金额判断)
├─ ≤1000 → 经理审批 → 结束
├─ ≤10000 → 总监审批 → 结束
└─ >10000 → CEO审批 → 结束
```
### 模式E并行会签
```
开始 → 提交 → 并行网关(fork)
├─ 部门A审批
└─ 部门B审批
并行网关(join) → 结束
```
### 模式F审批+驳回到发起人(草稿节点模式)
```
开始 → 草稿(自动跳过) → 审批 → 网关
├─ 通过 → 结束
└─ 驳回 → 草稿(发起人修改后重新提交)
```
**关键节点 XML**
```xml
<!-- 草稿节点:首次自动跳过,驳回后发起人手动操作 -->
<bpmn2:userTask id="task_draft" name="草稿" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:2,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:false,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="9c3064baa7074eab62e3c5b3b5458691" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 驳回连线:从网关回到草稿节点 -->
<bpmn2:sequenceFlow id="flow_reject" name="驳回" sourceRef="gateway_result" targetRef="task_draft">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 0}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
```
### 模式G多级审批 + 表达式审批人 + 上一节点指派(实战验证)
```
开始 → 申请人填写(草稿) → 部门负责人审批(表达式) → 分管领导审批(表达式) → 指派确认(上一节点指派) → 结束
```
**已验证成功的完整 Python 脚本f-string 方式构造 XML**
```python
import urllib.request
import urllib.parse
import json
import time
API_BASE = '{后端地址}'
TOKEN = '{X-Access-Token}'
ts = str(int(time.time() * 1000))
process_key = f'process_{ts}'
process_name = '车辆出差申请流程'
# 注意f-string 中 ${xxx} 写作 ${{xxx}}JSON 花括号也需 {{}}
bpmn_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:flowable="http://flowable.org/bpmn"
id="sample-diagram"
targetNamespace="http://bpmn.io/schema/bpmn"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="{process_key}" name="{process_name}">
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId" />
<bpmn2:userTask id="task_apply" name="申请人填写" flowable:assignee="${{applyUserId}}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value='{{"sameMode":2,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":false,"isSkipApproval":false,"isAssignedByPreviousNode":false,"isEmptyAssignedByPreviousNode":false}}' />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="auto_submit_1" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:userTask id="task_dept_leader" name="部门负责人审批"
flowable:candidateUsers="${{flowNodeExecution.getDepartLeaders(execution)}}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value='{{"sameMode":0,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":true,"isSkipApproval":false,"isAssignedByPreviousNode":false,"isEmptyAssignedByPreviousNode":true}}' />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:userTask id="task_leader" name="分管领导审批"
flowable:candidateUsers="${{flowNodeExecution.getLevel1DepartLeaders(execution)}}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value='{{"sameMode":0,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":true,"isSkipApproval":false,"isAssignedByPreviousNode":false,"isEmptyAssignedByPreviousNode":true}}' />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:userTask id="task_dispatch" name="车辆调度确认">
<bpmn2:extensionElements>
<flowable:taskExtendJson value='{{"sameMode":0,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":false,"isSkipApproval":false,"isAssignedByPreviousNode":true,"isEmptyAssignedByPreviousNode":false}}' />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:endEvent id="end" name="结束" />
<bpmn2:sequenceFlow id="flow_1" name="" sourceRef="start" targetRef="task_apply" />
<bpmn2:sequenceFlow id="flow_2" name="" sourceRef="task_apply" targetRef="task_dept_leader" />
<bpmn2:sequenceFlow id="flow_3" name="" sourceRef="task_dept_leader" targetRef="task_leader" />
<bpmn2:sequenceFlow id="flow_4" name="" sourceRef="task_leader" targetRef="task_dispatch" />
<bpmn2:sequenceFlow id="flow_5" name="" sourceRef="task_dispatch" targetRef="end" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="{process_key}">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="200" y="30" width="36" height="36" />
<bpmndi:BPMNLabel><dc:Bounds x="207" y="73" width="22" height="14" /></bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_task_apply" bpmnElement="task_apply">
<dc:Bounds x="168" y="106" width="100" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_task_dept_leader" bpmnElement="task_dept_leader">
<dc:Bounds x="168" y="206" width="100" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_task_leader" bpmnElement="task_leader">
<dc:Bounds x="168" y="306" width="100" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_task_dispatch" bpmnElement="task_dispatch">
<dc:Bounds x="168" y="406" width="100" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_end" bpmnElement="end">
<dc:Bounds x="200" y="506" width="36" height="36" />
<bpmndi:BPMNLabel><dc:Bounds x="207" y="549" width="22" height="14" /></bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="218" y="66" /><di:waypoint x="218" y="106" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="218" y="166" /><di:waypoint x="218" y="206" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="218" y="266" /><di:waypoint x="218" y="306" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="218" y="366" /><di:waypoint x="218" y="406" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="218" y="466" /><di:waypoint x="218" y="506" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>'''
nodes_str = 'id=task_apply###nodeName=申请人填写@@@id=task_dept_leader###nodeName=部门负责人审批@@@id=task_leader###nodeName=分管领导审批@@@id=task_dispatch###nodeName=车辆调度确认@@@'
data = {
'processDefinitionId': '0',
'processName': process_name,
'processkey': process_key,
'typeid': 'oa',
'lowAppId': '',
'params': '',
'nodes': nodes_str,
'processDescriptor': bpmn_xml,
'realProcDefId': '',
'startType': 'manual'
}
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.Request(
f'{API_BASE}/act/designer/api/saveProcess',
data=encoded_data,
headers={
'X-Access-Token': TOKEN,
'X-Sign': '00000000000000000000000000000000',
'X-Tenant-Id': '1',
'X-Timestamp': str(int(time.time() * 1000)),
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
method='POST'
)
resp = urllib.request.urlopen(req)
result = json.loads(resp.read().decode('utf-8'))
print(json.dumps(result, ensure_ascii=False, indent=2))
print(f'\\nProcess Key: {process_key}')
```
**实战要点:**
1. 此脚本必须先写入 `.py` 文件再执行(不能 `python3 -c` 内联bash 会展开 `${}` 导致报错)
2. f-string 中所有 `${xxx}` 都写作 `${{xxx}}`
3. taskExtendJson 的 `value` 用单引号包裹 JSONJSON 花括号用 `{{}}` 转义
4. 执行完毕后删除临时 `.py` 文件

View File

@@ -0,0 +1,125 @@
# 节点 ID 命名与图形布局规则
## 1. 节点 ID 命名规范
| 节点类型 | ID 前缀 | 示例 |
|----------|---------|------|
| 开始事件 | `start` | `start` |
| 结束事件 | `end` | `end` |
| 用户任务 | `task_` | `task_apply`, `task_manager`, `task_hr` |
| 排他网关 | `gateway_` | `gateway_result`, `gateway_amount` |
| 并行网关 | `pgw_` | `pgw_fork`, `pgw_join` |
| 连线 | `flow_` | `flow_1`, `flow_approve`, `flow_reject` |
## 2. 图形布局计算规则
### 2.1 尺寸常量
| 元素 | 宽度(width) | 高度(height) |
|------|------------|-------------|
| startEvent | 36 | 36 |
| endEvent | 36 | 36 |
| userTask | 100 | 60 |
| exclusiveGateway | 50 | 50 |
| parallelGateway | 50 | 50 |
### 2.2 布局策略 — 垂直主轴
所有节点沿 **垂直方向Y轴** 从上到下排列,中心线 X 固定。
**基准参数:**
- 主轴中心 X = `218`(所有节点以此为中心对齐)
- 起始 Y = `30`
- 节点间垂直间距 = `40`(节点底部到下一节点顶部的距离)
**计算公式:**
```python
CENTER_X = 218
START_Y = 30
VERTICAL_GAP = 40
# 节点尺寸
SIZES = {
"startEvent": {"w": 36, "h": 36},
"endEvent": {"w": 36, "h": 36},
"userTask": {"w": 100, "h": 60},
"exclusiveGateway": {"w": 50, "h": 50},
"parallelGateway": {"w": 50, "h": 50},
}
def layout_nodes(nodes):
"""计算每个节点的 Bounds (x, y, width, height)"""
y = START_Y
positions = []
for node in nodes:
size = SIZES[node["type"]]
x = CENTER_X - size["w"] / 2
positions.append({
"id": node["id"],
"x": x, "y": y,
"w": size["w"], "h": size["h"],
"center_x": CENTER_X,
"center_y": y + size["h"] / 2,
"bottom_y": y + size["h"]
})
y += size["h"] + VERTICAL_GAP
return positions
```
### 2.3 Shape XML 生成
```xml
<!-- startEvent / endEvent -->
<bpmndi:BPMNShape id="shape_{id}" bpmnElement="{id}">
<dc:Bounds x="{x}" y="{y}" width="{w}" height="{h}" />
<bpmndi:BPMNLabel>
<dc:Bounds x="{x+7}" y="{y+h+7}" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<!-- userTask -->
<bpmndi:BPMNShape id="shape_{id}" bpmnElement="{id}">
<dc:Bounds x="{x}" y="{y}" width="{w}" height="{h}" />
</bpmndi:BPMNShape>
<!-- gateway (isMarkerVisible="true" 用于排他网关) -->
<bpmndi:BPMNShape id="shape_{id}" bpmnElement="{id}" isMarkerVisible="true">
<dc:Bounds x="{x}" y="{y}" width="{w}" height="{h}" />
<bpmndi:BPMNLabel>
<dc:Bounds x="{x+w+10}" y="{center_y-7}" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
```
### 2.4 Edge XML 生成
**直线连接(垂直方向、上下相邻节点):**
```xml
<bpmndi:BPMNEdge id="edge_{flow_id}" bpmnElement="{flow_id}">
<di:waypoint x="{source.center_x}" y="{source.bottom_y}" />
<di:waypoint x="{target.center_x}" y="{target.y}" />
</bpmndi:BPMNEdge>
```
**分支连线(排他网关的非主路径,从右侧绕行):**
当网关有拒绝/回退路径需要连接到非相邻节点时,使用右侧绕行:
```xml
<bpmndi:BPMNEdge id="edge_{flow_id}" bpmnElement="{flow_id}">
<di:waypoint x="{gateway.center_x + 25}" y="{gateway.center_y}" />
<di:waypoint x="{gateway.center_x + 132}" y="{gateway.center_y}" />
<di:waypoint x="{gateway.center_x + 132}" y="{target.center_y}" />
<di:waypoint x="{target.center_x + target.w/2}" y="{target.center_y}" />
</bpmndi:BPMNEdge>
```
**并行分支连线(从左侧出发):**
```xml
<bpmndi:BPMNEdge id="edge_{flow_id}" bpmnElement="{flow_id}">
<di:waypoint x="{gateway.center_x - 25}" y="{gateway.center_y}" />
<di:waypoint x="{target.center_x - target.w/2 - 50}" y="{gateway.center_y}" />
<di:waypoint x="{target.center_x - target.w/2 - 50}" y="{target.center_y}" />
<di:waypoint x="{target.center_x - target.w/2}" y="{target.center_y}" />
</bpmndi:BPMNEdge>
```

View File

@@ -0,0 +1,325 @@
# 子流程与网关类型详解
## 1. 网关类型一览
| 用户描述 | BPMN 类型 | XML 元素 | 说明 |
|----------|----------|----------|------|
| 分支/条件判断/通过或拒绝 | 排他网关 | `exclusiveGateway` | 只走一条满足条件的路径 |
| 并行/同时 | 并行网关 | `parallelGateway` | 所有路径同时执行,全部完成后汇聚 |
| 包含/部分并行 | 包含网关 | `inclusiveGateway` | 满足条件的路径都走,全部完成后汇聚 |
> **注意:** JeecgBoot 设计器不支持事件网关eventBasedGateway实际只用上面三种。
---
## 2. 排他网关exclusiveGateway
只走一条路径,适用于"通过/拒绝"、"金额判断"等场景。
```xml
<bpmn2:exclusiveGateway id="gateway_result" name="审批结果" />
<!-- 条件连线 -->
<bpmn2:sequenceFlow id="flow_approve" name="通过" sourceRef="gateway_result" targetRef="task_next">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 1}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_reject" name="拒绝" sourceRef="gateway_result" targetRef="end">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 0}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
```
**BPMNShape** 排他网关需要 `isMarkerVisible="true"`
```xml
<bpmndi:BPMNShape id="shape_gateway" bpmnElement="gateway_result" isMarkerVisible="true">
<dc:Bounds x="715" y="205" width="50" height="50" />
</bpmndi:BPMNShape>
```
---
## 3. 并行网关parallelGateway
所有路径同时执行必须成对使用fork + join。连线**不需要条件表达式**。
```xml
<!-- 并行分支fork -->
<bpmn2:parallelGateway id="pgw_fork" name="并行开始" />
<!-- 并行任务 -->
<bpmn2:userTask id="task_a" name="部门A审批" flowable:assignee="admin" />
<bpmn2:userTask id="task_b" name="部门B审批" flowable:assignee="admin" />
<!-- 并行汇聚join -->
<bpmn2:parallelGateway id="pgw_join" name="并行汇聚" />
<!-- 连线(无条件) -->
<bpmn2:sequenceFlow id="flow_fork_a" sourceRef="pgw_fork" targetRef="task_a" />
<bpmn2:sequenceFlow id="flow_fork_b" sourceRef="pgw_fork" targetRef="task_b" />
<bpmn2:sequenceFlow id="flow_join_a" sourceRef="task_a" targetRef="pgw_join" />
<bpmn2:sequenceFlow id="flow_join_b" sourceRef="task_b" targetRef="pgw_join" />
```
---
## 4. 包含网关inclusiveGateway
满足条件的路径都会执行,也必须成对使用(分支 + 汇聚)。**连线需要条件表达式**。
### 4.1 基本用法(来自生产环境:包含网关测试)
```
开始 → 领取体检单 → 包含网关1分支
├─ 普通员工 (user_type=='1') → 常规体检 ──┐
├─ 全部 (user_type=='1'||'2') → 抽血化验 → 领取早餐 ──┤→ 包含网关2汇聚→ 结束
└─ 领导 (user_type=='2') → 深度体检 ──┘
```
```xml
<!-- 包含网关分支 -->
<bpmn2:inclusiveGateway id="igw_fork" name="包含网关分支" />
<!-- 带条件的连线 -->
<bpmn2:sequenceFlow id="flow_1" name="普通员工" sourceRef="igw_fork" targetRef="task_normal">
<bpmn2:conditionExpression xsi:type="tFormalExpression">${user_type=='1'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_2" name="全部" sourceRef="igw_fork" targetRef="task_blood">
<bpmn2:conditionExpression xsi:type="tFormalExpression">${user_type=='1' || user_type=='2'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_3" name="领导" sourceRef="igw_fork" targetRef="task_deep">
<bpmn2:conditionExpression xsi:type="tFormalExpression">${user_type=='2'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<!-- 包含网关汇聚 -->
<bpmn2:inclusiveGateway id="igw_join" name="包含网关汇聚" />
<!-- 所有分支汇聚到同一个网关 -->
<bpmn2:sequenceFlow id="flow_join_1" sourceRef="task_normal" targetRef="igw_join" />
<bpmn2:sequenceFlow id="flow_join_2" sourceRef="task_breakfast" targetRef="igw_join" />
<bpmn2:sequenceFlow id="flow_join_3" sourceRef="task_deep" targetRef="igw_join" />
```
### 4.2 包含网关 + 直通路径(来自生产环境:督办流程)
包含网关的一个分支可以直接连到汇聚网关(不经过任何任务),实现"无风险时跳过"的效果:
```
部门负责人审核 → 包含网关(分支)
├─ 有风险 (iz_danger=='1') → 风控审计负责 ──┐
├─ 有风险 (iz_danger=='1') → 部门分管领导 ──┤→ 包含网关(汇聚)→ 结束
└─ 无风险 (iz_danger=='0') ──────────────────┘
```
```xml
<bpmn2:inclusiveGateway id="Gateway_fork" />
<bpmn2:inclusiveGateway id="Gateway_join" />
<!-- 有风险:走两条并行路径 -->
<bpmn2:sequenceFlow id="flow_risk1" name="有风险" sourceRef="Gateway_fork" targetRef="task_audit">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${iz_danger== '1' }</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_risk2" name="有风险" sourceRef="Gateway_fork" targetRef="task_leader">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${iz_danger== '1' }</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<!-- 无风险:直接到汇聚网关 -->
<bpmn2:sequenceFlow id="flow_norisk" name="无风险" sourceRef="Gateway_fork" targetRef="Gateway_join">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${iz_danger=='0'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<!-- 汇聚 -->
<bpmn2:sequenceFlow id="flow_j1" sourceRef="task_audit" targetRef="Gateway_join" />
<bpmn2:sequenceFlow id="flow_j2" sourceRef="task_leader" targetRef="Gateway_join" />
```
### 4.3 包含网关 vs 排他网关 vs 并行网关
| 特性 | 排他网关 | 并行网关 | 包含网关 |
|------|---------|---------|---------|
| 执行路径 | 只走1条 | 全部走 | 满足条件的都走 |
| 条件表达式 | 必需 | 不需要 | 必需 |
| 汇聚行为 | 等1个 | 等全部 | 等所有已激活的 |
| 适用场景 | 二选一/多选一 | 同时并行 | 条件并行 |
---
## 5. 内嵌子流程subProcess
内嵌子流程在主流程 XML 内部定义,拥有自己的开始和结束事件。适用于将一组相关节点打包为一个整体。
### 5.1 基本结构
```xml
<bpmn2:subProcess id="subprocess_1" name="审批子流程">
<bpmn2:incoming>Flow_in</bpmn2:incoming>
<bpmn2:outgoing>Flow_out</bpmn2:outgoing>
<!-- 子流程内部的开始事件 -->
<bpmn2:startEvent id="sub_start" />
<!-- 子流程内部的用户任务 -->
<bpmn2:userTask id="sub_task1" name="经理审批" flowable:assignee="admin" />
<bpmn2:userTask id="sub_task2" name="财务审批" flowable:candidateGroups="finance" />
<!-- 子流程内部的结束事件 -->
<bpmn2:endEvent id="sub_end" />
<!-- 子流程内部的连线 -->
<bpmn2:sequenceFlow id="sub_flow1" sourceRef="sub_start" targetRef="sub_task1" />
<bpmn2:sequenceFlow id="sub_flow2" sourceRef="sub_task1" targetRef="sub_task2" />
<bpmn2:sequenceFlow id="sub_flow3" sourceRef="sub_task2" targetRef="sub_end" />
</bpmn2:subProcess>
```
### 5.2 完整示例(来自生产环境:测试嵌套子流程)
```
开始 → 入职(admin) → [内嵌子流程: 开始 → 经理(qinfeng) → 财务(vue3角色) → 结束] → 人力(admin角色) → 结束
```
**布局说明:** 内嵌子流程在图形上展开显示(`isExpanded="true"`),需要为整个子流程框指定 Bounds。
```xml
<!-- 子流程 Shape展开模式 -->
<bpmndi:BPMNShape id="shape_subprocess" bpmnElement="subprocess_1" isExpanded="true">
<dc:Bounds x="280" y="45" width="650" height="185" />
</bpmndi:BPMNShape>
```
---
## 6. 调用子流程callActivity— 主子流程
调用子流程引用**另一个独立部署的流程定义**,主流程和子流程各自独立管理。
### 6.1 基本结构
```xml
<bpmn2:callActivity id="call_sub" name="调用子流程" calledElement="子流程的processKey">
<bpmn2:extensionElements>
<!-- 传入变量:主流程 → 子流程 -->
<flowable:in source="applyUserId" target="applyUserId" />
<flowable:in source="JG_LOCAL_PROCESS_ID" target="JG_SUB_MAIN_PROCESS_ID" />
<!-- 传出变量:子流程 → 主流程 -->
<flowable:out source="applyUserId" target="applyUserId" />
</bpmn2:extensionElements>
</bpmn2:callActivity>
```
### 6.2 必传变量
| 变量 | 方向 | 说明 |
|------|------|------|
| `applyUserId` | in + out | 流程发起人,子流程需要知道谁发起的 |
| `JG_LOCAL_PROCESS_ID``JG_SUB_MAIN_PROCESS_ID` | in | 主流程ID子流程需要关联回主流程 |
### 6.3 完整示例(来自生产环境:出差申请主子流程)
```
开始 → 主管领导 → 部门领导 → 排他网关
├─ 预支借款 (travel_expenses_type=='1') → 借款申请 → callActivity(调用joa_loan子流程) → 归档 → 结束
└─ 个人垫付 (travel_expenses_type=='2') → 归档 → 结束
```
```xml
<callActivity id="callSubProcess" name="" calledElement="joa_loan">
<extensionElements>
<flowable:in source="apply_no" target="id" />
<flowable:in source="applyUserId" target="applyUserId" />
<flowable:in source="JG_LOCAL_PROCESS_ID" target="JG_SUB_MAIN_PROCESS_ID" />
<flowable:out source="applyUserId" target="applyUserId" />
</extensionElements>
</callActivity>
```
---
## 7. 会签子流程callActivity + multiInstance
会签子流程 = 调用子流程 + 多实例循环。每个审批人各执行一次完整的子流程。
### 7.1 结构
```xml
<bpmn2:callActivity id="call_countersign" name="会签子流程" calledElement="子流程processKey">
<bpmn2:extensionElements>
<flowable:in source="assigneeUserId" target="assigneeUserId" />
<flowable:in source="applyUserId" target="applyUserId" />
<flowable:in source="JG_LOCAL_PROCESS_ID" target="JG_SUB_MAIN_PROCESS_ID" />
<flowable:out source="applyUserId" target="applyUserId" />
</bpmn2:extensionElements>
<!-- 多实例循环:每个审批人执行一次子流程 -->
<bpmn2:multiInstanceLoopCharacteristics
isSequential="true"
flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}"
flowable:elementVariable="assigneeUserId" />
</bpmn2:callActivity>
```
### 7.2 完整示例(来自生产环境:主流程会签主子流程)
```
开始 → 主流程经理审批(候选人) → callActivity(会签子流程, 顺序执行) → 主流程总监审批 → 结束
```
```xml
<callActivity id="callSubProcess" name="" calledElement="subflow">
<extensionElements>
<flowable:in source="assigneeUserId" target="assigneeUserId" />
<flowable:in source="applyUserId" target="applyUserId" />
<flowable:in source="JG_LOCAL_PROCESS_ID" target="JG_SUB_MAIN_PROCESS_ID" />
<flowable:out source="applyUserId" target="applyUserId" />
</extensionElements>
<multiInstanceLoopCharacteristics
isSequential="true"
flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}"
flowable:elementVariable="assigneeUserId" />
</callActivity>
```
**关键区别:**
- 普通子流程:没有 `multiInstanceLoopCharacteristics`,只执行一次
- 会签子流程:有 `multiInstanceLoopCharacteristics`,按 `assigneeUserIdList` 中的人数循环执行
---
## 8. 分支条件表达式配置
### 8.1 排他网关条件
排他网关的每条出线都需要条件表达式(除了默认路径):
```xml
<bpmn2:sequenceFlow id="flow_1" name="通过" sourceRef="gateway" targetRef="task_next">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 1}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
```
### 8.2 包含网关条件
包含网关的每条出线也需要条件表达式,可以多条同时满足:
```xml
<bpmn2:sequenceFlow id="flow_1" name="普通员工" sourceRef="igw" targetRef="task_a">
<bpmn2:conditionExpression xsi:type="tFormalExpression">${user_type=='1'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_2" name="全部" sourceRef="igw" targetRef="task_b">
<bpmn2:conditionExpression xsi:type="tFormalExpression">${user_type=='1' || user_type=='2'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
```
### 8.3 常用条件表达式写法
| 场景 | 表达式 |
|------|--------|
| 审批通过 | `${result == 1}` |
| 审批拒绝 | `${result == 0}` |
| 字段等于值 | `${field_name == 'value'}` |
| 字段不等于 | `${field_name != 'value'}` |
| 数值比较 | `${amount > 10000}` |
| 多条件 OR | `${type=='1' \|\| type=='2'}` |
| 多条件 AND | `${type=='1' && level=='high'}` |
| 布尔判断 | `${iz_danger == '1'}` |
> **注意:** `conditionExpression` 中使用 `xsi:type="tFormalExpression"`(排他网关)或 `xsi:type="bpmn2:tFormalExpression"`(新版设计器),两种都可用。

View File

@@ -0,0 +1,187 @@
# taskExtendJson 与监听器配置
## 1. taskExtendJson 配置说明
taskExtendJson 控制审批节点的行为,以 JSON 字符串存储在 extensionElements 中:
```xml
<flowable:taskExtendJson value="{JSON内容}" />
```
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `sameMode` | int | `0` | 相同处理人模式0=不跳过1=跳过2=草稿节点(首节点自动提交) |
| `isSkipAssigneeEmpty` | bool | `false` | 审批人为空时是否自动跳过 |
| `isSkipAssigneeOnePersion` | bool | `false` | 只有一人时是否自动跳过(常用于发起人=审批人场景) |
| `isSkipApproval` | bool | `false` | 是否跳过审批(自动通过) |
| `isAssignedByPreviousNode` | bool | `false` | 是否由上一节点指派审批人 |
| `isEmptyAssignedByPreviousNode` | bool | `false` | 上一节点未指派时是否允许空 |
| `isSkipApprovedOnCountersignReturn` | bool | `false` | 会签驳回时是否跳过已审批的人 |
使用 taskExtendJson 时,通常需要配合跳过审批监听器:
```xml
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
```
---
## 2. 必需的监听器汇总
### 2.1 流程级监听器(写在 process > extensionElements 中)
```xml
<bpmn2:extensionElements>
<!-- 流程结束监听器(必需,所有流程都要有) -->
<flowable:executionListener
class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener"
event="end" />
<!-- 任务创建全局监听器(必需,新版设计器标配) -->
<flowable:eventListener
class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
```
### 2.2 节点级监听器(写在 userTask > extensionElements 中)
```xml
<!-- 跳过审批监听器(使用 taskExtendJson 时必需) -->
<flowable:taskListener
class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener"
event="create" />
<!-- 完成时更新表单数据监听器(按需) -->
<flowable:taskListener
class="org.jeecg.modules.extbpm.listener.task.TaskUpdateFormDataListener"
event="complete" />
<!-- 首节点自动提交监听器(草稿节点专用) -->
<!-- 首次发起流程时自动跳过此节点,驳回后才需手动操作 -->
<flowable:taskListener
class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener"
event="create"
id="9c3064baa7074eab62e3c5b3b5458691" />
```
### 2.3 结束节点监听器(按需)
```xml
<bpmn2:endEvent id="End_reject">
<bpmn2:extensionElements>
<!-- 拒绝时触发业务逻辑 -->
<flowable:executionListener
expression="${myBizListener.onReject(execution)}"
event="start" />
</bpmn2:extensionElements>
</bpmn2:endEvent>
```
---
## 3. 系统预置监听器一览ext_act_listener 表)
系统在 `ext_act_listener` 表中预置了所有可用的监听器,设计器界面从此表加载监听器列表供用户选择。
**listener_type** 1=执行监听器executionListener2=任务监听器taskListener
**listener_value_type** `javaClass`=Java类`expression`=表达式,`delegateExpression`=Spring委托表达式
### 3.1 执行监听器type=1用于流程级/节点级)
| 名称 | 事件 | 类型 | 完整类路径 |
|------|------|------|-----------|
| 平台通用流程结束监听 | end | javaClass | `org.jeecg.modules.extbpm.listener.execution.ProcessEndListener` |
| 公文收文分发 | end | javaClass | `org.jeecg.modules.listener.easyoa.ReveicedStartListener` |
| 子流程会签开始监听 | start | javaClass | `org.jeecg.modules.extbpm.listener.execution.SubProcessHqStartListener` |
| 子流程开始监听 | start | javaClass | `org.jeecg.modules.extbpm.listener.execution.SubProcessStartListener` |
| 信号启动流程监听 | start | javaClass | `org.jeecg.modules.extbpm.listener.execution.SignalProcessStartListener` |
| 表单设计器生成自动编号 | start | javaClass | `org.jeecg.modules.designform.listener.DesformAutoNumberListener` |
| 公文表单监听 | start | javaClass | `org.jeecg.modules.listener.easyoa.OaOfficialdocStartListener` |
### 3.2 任务监听器type=2用于 userTask 级)
| 名称 | 事件 | 类型 | 完整类路径/表达式 |
|------|------|------|-----------------|
| 用户审核节点跳过规则监听 | create | javaClass | `org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener` |
| 首任务节点自动提交监听 | create | javaClass | `org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener` |
| 子流程 | create | expression | `${subProcessListener}` |
| spring表达式监听 | create | delegateExpression | `${someJavaDelegateBean}` |
| 节点监听更新业务数据到流程变量 | complete | javaClass | `org.jeecg.modules.extbpm.listener.task.TaskUpdateFormDataListener` |
| 公告审核监听 | complete | javaClass | `org.jeecg.modules.listener.announcement.NoticeReviewEndListener` |
| 新闻审核监听 | complete | javaClass | `org.jeecg.modules.listener.easyoa.NewsReviewEndListener` |
| 公文发文分发 | complete | javaClass | `org.jeecg.modules.listener.easyoa.DistributeIssuedEndListener` |
### 3.3 常用监听器使用场景
| 监听器 | 使用场景 | 是否必需 |
|--------|---------|---------|
| `ProcessEndListener` | 所有流程都要挂,流程结束时更新业务状态 | 必需 |
| `TaskSkipApprovalListener` | 使用 taskExtendJson 时挂载,实现跳过审批逻辑 | 配合 taskExtendJson 必需 |
| `TaskCreatedAutoSubmitListener` | 草稿节点sameMode=2首次自动跳过 | 草稿节点必需 |
| `TaskUpdateFormDataListener` | 节点完成时将表单数据同步到流程变量 | 按需 |
| `SubProcessStartListener` | 子流程启动时的初始化 | 子流程必需 |
| `SubProcessHqStartListener` | 子流程会签场景 | 子流程会签必需 |
| `DesformAutoNumberListener` | DesForm 表单自动编号 | DesForm 自动编号时需要 |
---
## 4. 源码级监听器完整清单
以下从源码中扫描所有 `implements TaskListener``implements ExecutionListener` 的实现类。
### 4.1 TaskListener 实现类11个
#### 平台核心jeecg-boot-module-bpm-flowable
| 完整类路径 | 事件 | 说明 |
|-----------|------|------|
| `org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener` | create | 节点审批自动跳过(发起人=审批人跳过、审批人为空跳过、已审批免二次等) |
| `org.jeecg.modules.extbpm.listener.task.TaskUpdateFormDataListener` | create | 同步表单字段到流程变量,更新业务标题,触发简流事件 |
| `org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener` | create | 草稿节点首次自动提交sameMode=2自动设置发起人并完成任务 |
| `org.jeecg.modules.extbpm.listener.task.SubProcessListener` | create | 子流程启动时传递主流程标题和业务号 |
#### 简流jeecg-boot-module-mindesflow-flowable
| 完整类路径 | 事件 | 说明 |
|-----------|------|------|
| `org.jeecg.modules.listener.easyoa.TaskApprovalListener` | create | 简流版审批跳过监听 |
| `org.jeecg.modules.extbpm.process.adapter.listener.ApproveResultBranchListener` | create | 审批结果分支标识变量设置 |
| `org.jeecg.modules.extbpm.process.adapter.listener.BeforeEditListener` | create | 填写节点自定义表单数据ID设置 |
#### 业务jeecg-boot-module-joa-flowable
| 完整类路径 | 事件 | 说明 |
|-----------|------|------|
| `org.jeecg.modules.testListenerExpression.TestTaskListener` | create/assign/complete/delete | 测试用监听器,记录各事件日志 |
| `org.jeecg.modules.listener.announcement.NoticeReviewEndListener` | complete | 公告审核完成自动发布 |
| `org.jeecg.modules.listener.easyoa.DistributeIssuedEndListener` | complete | 公文发文分发,按抄送部门创建分发记录 |
| `org.jeecg.modules.listener.easyoa.NewsReviewEndListener` | complete | 新闻审核完成自动发布 |
### 4.2 ExecutionListener 实现类12个
#### 平台核心jeecg-boot-module-bpm-flowable
| 完整类路径 | 事件 | 说明 |
|-----------|------|------|
| `org.jeecg.modules.extbpm.listener.execution.ProcessEndListener` | end | 流程结束更新状态、处理表单数据、清Redis缓存**必需** |
| `org.jeecg.modules.extbpm.listener.execution.SignalProcessStartListener` | start | 信号启动流程初始化变量、获取表单数据、设置业务key |
| `org.jeecg.modules.extbpm.listener.execution.SubProcessStartListener` | start | 子流程启动传递主流程标题、表单key、数据ID |
| `org.jeecg.modules.extbpm.listener.execution.SubProcessHqStartListener` | start | 会签子流程启动传递主流程变量、表单URL和业务数据 |
#### 简流jeecg-boot-module-mindesflow-flowable
| 完整类路径 | 事件 | 说明 |
|-----------|------|------|
| `org.jeecg.modules.minides.listener.ProcessEndRemoveRedisListener` | end | 简流结束清Redis缓存 |
| `org.jeecg.modules.minides.listener.MiniSubProcessStartListener` | start | 简流子流程启动初始化,传递流程变量 |
| `org.jeecg.modules.extbpm.process.adapter.delegate.MiniCallActivityListener` | start | 简流调用活动参数传递,处理系统变量和工作表映射 |
#### 业务jeecg-boot-module-joa-flowable
| 完整类路径 | 事件 | 说明 |
|-----------|------|------|
| `org.jeecg.modules.designform.listener.DesformAutoNumberListener` | start | 表单设计器自动编号 |
| `org.jeecg.modules.testListenerExpression.TestExecutionListener` | start/end | 测试用执行监听器 |
| `org.jeecg.modules.listener.easyoa.ReveicedStartListener` | end | 公文收文分发,根据部门信息分发任务 |
| `org.jeecg.modules.listener.easyoa.OaOfficialdocStartListener` | start | 公文表单监听,校验各节点意见字段 |
| `org.jeecg.modules.extbpm.listener.execution.ProcessEndListener`JOA副本 | end | OA模块流程结束处理与平台核心同类名不同模块 |

View File

@@ -0,0 +1,41 @@
# BPMN 模板参考文档索引
本文档已拆分为多个子文件,方便维护和查阅。按需阅读对应文件:
| 文件 | 内容 | 使用场景 |
|------|------|---------|
| [bpmn-xml-skeleton.md](bpmn-xml-skeleton.md) | XML 骨架模板 + 基本节点模板 | 每次生成流程必读 |
| [bpmn-assignee-types.md](bpmn-assignee-types.md) | 7种审批人配置 + groupType速查 + 数据来源表 + 草稿节点 | 配置审批人时必读 |
| [bpmn-countersign.md](bpmn-countersign.md) | 会签配置(规则/人员类型/完整示例) | 需要会签时阅读 |
| [bpmn-task-extend.md](bpmn-task-extend.md) | taskExtendJson 配置 + 监听器汇总 | 配置节点行为时阅读 |
| [bpmn-layout.md](bpmn-layout.md) | 节点ID命名规范 + 图形布局计算规则 | 生成 XML 布局时必读 |
| [bpmn-examples.md](bpmn-examples.md) | 完整示例 + Python调用脚本 + 6种流程模式速查 | 参考模式和调用API |
| [bpmn-advanced.md](bpmn-advanced.md) | 条件表达式 + 抄送 + 按钮 + 服务任务 + API端点 | 高级配置时阅读 |
| [bpmn-subprocess-gateway.md](bpmn-subprocess-gateway.md) | 3种网关 + 内嵌子流程 + 调用子流程 + 会签子流程 | 子流程/网关时必读 |
| [bpmn-db-config.md](bpmn-db-config.md) | 4张配置数据库表 + node_config_json结构 | 数据库配置时阅读 |
## 生产环境 BPMN 示例文件
`references/example/` 目录下包含从生产环境导出的真实流程 BPMN XML 文件,可作为生成流程时的参考模板:
| 文件 | 流程类型 | 包含特性 |
|------|---------|---------|
| `合同付款审批单.bpmn` | 审批流程 | 多级审批 |
| `采购申请单.bpmn` | 审批流程 | 多级审批 |
| `用章申请建设.bpmn` | 审批流程 | 多级审批 |
| `车辆维修保养审批单.bpmn` | 审批流程 | 多级审批 |
| `档案查借阅审批表.bpmn` | 审批流程 | 多级审批 |
| `合同审批单.bpmn` | 审批流程 | 多级审批 |
| `顺序会签流程.bpmn` | 会签 | 顺序会签isSequential=true |
| `并行会签测试.bpmn` | 会签 | 并行会签isSequential=false |
| `借款申请(子流程).bpmn` | 子流程 | 被调用的子流程定义 |
| `出差申请(主子流程).bpmn` | 主子流程 | callActivity 调用子流程 |
| `督办流程.bpmn` | 包含网关 | inclusiveGateway + 条件分支 |
**使用方式:** 生成流程前,先阅读与目标流程最相似的示例文件,学习其节点结构、审批人配置、监听器写法和布局坐标,然后参照生成新流程。
## 推荐阅读顺序
**基本流程生成:** `bpmn-xml-skeleton.md``bpmn-assignee-types.md``bpmn-layout.md``bpmn-examples.md`
**高级功能:** `bpmn-subprocess-gateway.md``bpmn-countersign.md``bpmn-task-extend.md``bpmn-advanced.md``bpmn-db-config.md`

View File

@@ -0,0 +1,121 @@
# BPMN XML 骨架与基本节点模板
## 1. XML 骨架模板
所有生成的 BPMN XML 必须使用以下骨架:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:flowable="http://flowable.org/bpmn"
id="sample-diagram"
targetNamespace="http://bpmn.io/schema/bpmn"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="${PROCESS_KEY}" name="${PROCESS_NAME}">
<!-- 必需的监听器 -->
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<!-- 节点定义区 -->
${NODES}
<!-- 连线定义区 -->
${SEQUENCE_FLOWS}
</bpmn2:process>
<!-- 图形布局区 -->
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${PROCESS_KEY}">
${SHAPES_AND_EDGES}
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
```
## 2. 节点 XML 模板
### 2.1 开始节点(必需)
```xml
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId" />
```
### 2.2 结束节点(必需)
**普通结束节点:**
```xml
<bpmn2:endEvent id="end" name="结束" />
```
**带监听器的结束节点(如需在结束时触发业务逻辑):**
```xml
<bpmn2:endEvent id="End_reject">
<bpmn2:extensionElements>
<flowable:executionListener expression="${myListener.onReject(execution)}" event="start" />
</bpmn2:extensionElements>
</bpmn2:endEvent>
```
> 一个流程可以有多个结束节点(如"同意结束"和"拒绝结束"各一个),每个可挂不同监听器。
### 2.3 用户任务userTask
```xml
<!-- 指定人 -->
<bpmn2:userTask id="task_xxx" name="节点名称" flowable:assignee="username" />
<!-- 候选人(多人) -->
<bpmn2:userTask id="task_xxx" name="节点名称" flowable:candidateUsers="user1,user2" />
<!-- 候选角色组 -->
<bpmn2:userTask id="task_xxx" name="节点名称" flowable:candidateGroups="roleCode" />
<!-- 表达式(动态) -->
<bpmn2:userTask id="task_xxx" name="节点名称" flowable:assignee="${variableName}" />
<!-- 发起人 -->
<bpmn2:userTask id="task_xxx" name="节点名称" flowable:assignee="${applyUserId}" />
```
### 2.4 排他网关exclusiveGateway
```xml
<bpmn2:exclusiveGateway id="gateway_xxx" name="网关名称" />
```
带条件的连线:
```xml
<bpmn2:sequenceFlow id="flow_xxx" name="通过" sourceRef="gateway_xxx" targetRef="task_yyy">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 1}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_xxx" name="拒绝" sourceRef="gateway_xxx" targetRef="end">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 0}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
```
### 2.5 并行网关parallelGateway
并行网关需要成对使用(分支 + 汇聚):
```xml
<!-- 并行分支 -->
<bpmn2:parallelGateway id="gateway_fork" name="并行开始" />
<!-- 并行汇聚 -->
<bpmn2:parallelGateway id="gateway_join" name="并行汇聚" />
```
### 2.6 普通连线sequenceFlow
```xml
<bpmn2:sequenceFlow id="flow_xxx" name="" sourceRef="nodeA" targetRef="nodeB" />
```

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="http://www.jeecg.org">
<process id="mainflow" name="主流程(会签主子流程)">
<documentation>流程描述</documentation>
<extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" id="402880e54803a496014805e5d9190012" />
</extensionElements>
<startEvent id="start1" name="开始节点" flowable:initiator="applyUserId" />
<endEvent id="end" name="结束节点" />
<userTask id="task1568615412011" name="主流程经理审批" flowable:candidateUsers="zhoujf,admin" />
<userTask id="task1568615414124" name="主流程总监审批" flowable:assignee="admin" />
<callActivity id="callSubProcess1568615439083" name="" calledElement="subflow">
<extensionElements>
<flowable:in source="assigneeUserId" target="assigneeUserId" />
<flowable:in source="applyUserId" target="applyUserId" />
<flowable:in source="JG_LOCAL_PROCESS_ID" target="JG_SUB_MAIN_PROCESS_ID" />
<flowable:out source="applyUserId" target="applyUserId" />
</extensionElements>
<multiInstanceLoopCharacteristics flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}" flowable:elementVariable="assigneeUserId" />
</callActivity>
<sequenceFlow id="flow1568615431826" name="" sourceRef="start1" targetRef="task1568615412011" />
<sequenceFlow id="flow1568615434460" name="" sourceRef="task1568615414124" targetRef="end" />
<sequenceFlow id="flow1568615440940" name="" sourceRef="task1568615412011" targetRef="callSubProcess1568615439083" />
<sequenceFlow id="flow1568615449144" name="" sourceRef="callSubProcess1568615439083" targetRef="task1568615414124" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_mainflow">
<bpmndi:BPMNPlane id="BPMNPlane_mainflow" bpmnElement="mainflow">
<bpmndi:BPMNEdge id="BPMNEdge_flow1568615449144" bpmnElement="flow1568615449144">
<omgdi:waypoint x="480" y="241" />
<omgdi:waypoint x="480" y="300" />
<omgdi:waypoint x="268" y="300" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1568615440940" bpmnElement="flow1568615440940">
<omgdi:waypoint x="265" y="160" />
<omgdi:waypoint x="480" y="160" />
<omgdi:waypoint x="480" y="211" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1568615434460" bpmnElement="flow1568615434460">
<omgdi:waypoint x="218" y="333" />
<omgdi:waypoint x="218" y="398" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1568615431826" bpmnElement="flow1568615431826">
<omgdi:waypoint x="215" y="79" />
<omgdi:waypoint x="215" y="128" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_start1" bpmnElement="start1">
<omgdc:Bounds x="200" y="49" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_end" bpmnElement="end">
<omgdc:Bounds x="203" y="398" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1568615412011" bpmnElement="task1568615412011">
<omgdc:Bounds x="170" y="133" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1568615414124" bpmnElement="task1568615414124">
<omgdc:Bounds x="173" y="273" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_callSubProcess1568615439083" bpmnElement="callSubProcess1568615439083">
<omgdc:Bounds x="465" y="211" width="30" height="30" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="http://www.jeecg.org">
<process id="joa_loan" name="借款申请(子流程)">
<documentation>流程描述</documentation>
<extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" id="402880e54803a496014805e5d9190012" />
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.SubProcessStartListener" event="start" id="64d675c1a3adcb514ea5f9835093c29b" />
</extensionElements>
<startEvent id="start1" name="开始节点" flowable:initiator="applyUserId" />
<endEvent id="end" name="结束节点" />
<userTask id="task1554898063090" name="部门领导审批" flowable:assignee="admin" />
<userTask id="task1554898171984" name="会计审批" flowable:assignee="admin" />
<userTask id="task1554898182628" name="出纳放款" flowable:assignee="admin" />
<sequenceFlow id="flow1554898068323" name="" sourceRef="start1" targetRef="task1554898063090" />
<sequenceFlow id="flow1554898196767" name="" sourceRef="task1554898171984" targetRef="task1554898182628" />
<sequenceFlow id="flow1554898198653" name="" sourceRef="task1554898182628" targetRef="end" />
<sequenceFlow id="flow1554898455196" name="" sourceRef="task1554898063090" targetRef="task1554898171984" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_joa_loan">
<bpmndi:BPMNPlane id="BPMNPlane_joa_loan" bpmnElement="joa_loan">
<bpmndi:BPMNEdge id="BPMNEdge_flow1554898455196" bpmnElement="flow1554898455196">
<omgdi:waypoint x="358" y="142" />
<omgdi:waypoint x="406" y="142" />
<omgdi:waypoint x="406" y="141" />
<omgdi:waypoint x="454" y="141" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554898198653" bpmnElement="flow1554898198653">
<omgdi:waypoint x="746" y="141" />
<omgdi:waypoint x="786" y="141" />
<omgdi:waypoint x="786" y="142" />
<omgdi:waypoint x="825" y="142" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554898196767" bpmnElement="flow1554898196767">
<omgdi:waypoint x="544" y="141" />
<omgdi:waypoint x="656" y="141" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554898068323" bpmnElement="flow1554898068323">
<omgdi:waypoint x="175" y="142" />
<omgdi:waypoint x="268" y="142" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_start1" bpmnElement="start1">
<omgdc:Bounds x="145" y="127" width="30" height="30" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="138" y="157" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_end" bpmnElement="end">
<omgdc:Bounds x="825" y="127" width="30" height="30" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="818" y="157" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554898063090" bpmnElement="task1554898063090">
<omgdc:Bounds x="268" y="115" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554898171984" bpmnElement="task1554898171984">
<omgdc:Bounds x="454" y="114" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554898182628" bpmnElement="task1554898182628">
<omgdc:Bounds x="656" y="114" width="90" height="55" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="http://www.jeecg.org">
<process id="joa_bustrip" name="出差申请(主子流程)">
<documentation>流程描述</documentation>
<extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" id="402880e54803a496014805e5d9190012" />
</extensionElements>
<startEvent id="start1" name="开始节点" flowable:initiator="applyUserId" />
<endEvent id="end" name="结束节点" />
<userTask id="task1554897424379" name="主管领导" flowable:assignee="admin" />
<userTask id="task1554897440265" name="部门领导" flowable:assignee="admin" />
<userTask id="task1554897494372" name="借款申请" flowable:assignee="${applyUserId}" />
<userTask id="task1554897568627" name="归档" flowable:assignee="admin" />
<exclusiveGateway id="exclusiveGateway1554897478398" name="" />
<callActivity id="callSubProcess1554897534417" name="" calledElement="joa_loan">
<extensionElements>
<flowable:in source="apply_no" target="id" />
<flowable:in source="applyUserId" target="applyUserId" />
<flowable:in source="JG_LOCAL_PROCESS_ID" target="JG_SUB_MAIN_PROCESS_ID" />
<flowable:out source="applyUserId" target="applyUserId" />
</extensionElements>
</callActivity>
<sequenceFlow id="flow1554897446155" name="" sourceRef="start1" targetRef="task1554897424379" />
<sequenceFlow id="flow1554897480649" name="" sourceRef="task1554897440265" targetRef="exclusiveGateway1554897478398" />
<sequenceFlow id="flow1554897483571" name="" sourceRef="task1554897424379" targetRef="task1554897440265" />
<sequenceFlow id="flow1554897522558" name="预支借款" sourceRef="exclusiveGateway1554897478398" targetRef="task1554897494372">
<conditionExpression xsi:type="tFormalExpression">${travel_expenses_type=='1'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow1554897536153" name="" sourceRef="task1554897494372" targetRef="callSubProcess1554897534417" />
<sequenceFlow id="flow1554897581547" name="" sourceRef="callSubProcess1554897534417" targetRef="task1554897568627" />
<sequenceFlow id="flow1554897591480" name="" sourceRef="task1554897568627" targetRef="end" />
<sequenceFlow id="flow1554897613063" name="个人垫付" sourceRef="exclusiveGateway1554897478398" targetRef="task1554897568627">
<conditionExpression xsi:type="tFormalExpression">${travel_expenses_type=='2'}</conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_joa_bustrip">
<bpmndi:BPMNPlane id="BPMNPlane_joa_bustrip" bpmnElement="joa_bustrip">
<bpmndi:BPMNEdge id="BPMNEdge_flow1554897613063" bpmnElement="flow1554897613063">
<omgdi:waypoint x="670" y="158" />
<omgdi:waypoint x="670" y="302" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="638" y="214" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554897591480" bpmnElement="flow1554897591480">
<omgdi:waypoint x="625" y="329" />
<omgdi:waypoint x="477" y="329" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554897581547" bpmnElement="flow1554897581547">
<omgdi:waypoint x="875" y="329" />
<omgdi:waypoint x="715" y="329" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554897536153" bpmnElement="flow1554897536153">
<omgdi:waypoint x="890" y="266" />
<omgdi:waypoint x="890" y="314" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554897522558" bpmnElement="flow1554897522558">
<omgdi:waypoint x="690" y="138" />
<omgdi:waypoint x="890" y="138" />
<omgdi:waypoint x="890" y="211" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="829" y="143" width="45" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554897483571" bpmnElement="flow1554897483571">
<omgdi:waypoint x="305" y="138" />
<omgdi:waypoint x="405" y="138" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554897480649" bpmnElement="flow1554897480649">
<omgdi:waypoint x="495" y="138" />
<omgdi:waypoint x="650" y="138" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554897446155" bpmnElement="flow1554897446155">
<omgdi:waypoint x="125" y="138" />
<omgdi:waypoint x="215" y="138" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_start1" bpmnElement="start1">
<omgdc:Bounds x="95" y="123" width="30" height="30" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="89" y="153" width="43" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_end" bpmnElement="end">
<omgdc:Bounds x="447" y="314" width="30" height="30" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="440" y="344" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554897424379" bpmnElement="task1554897424379">
<omgdc:Bounds x="215" y="111" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554897440265" bpmnElement="task1554897440265">
<omgdc:Bounds x="405" y="111" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554897494372" bpmnElement="task1554897494372">
<omgdc:Bounds x="845" y="211" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554897568627" bpmnElement="task1554897568627">
<omgdc:Bounds x="625" y="302" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_exclusiveGateway1554897478398" bpmnElement="exclusiveGateway1554897478398" isMarkerVisible="true">
<omgdc:Bounds x="650" y="118" width="40" height="40" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="631" y="80.5" width="85" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_callSubProcess1554897534417" bpmnElement="callSubProcess1554897534417">
<omgdc:Bounds x="875" y="314" width="30" height="30" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="http://www.jeecg.com">
<process id="process1609125744738" name="包含网关测试">
<documentation>流程描述</documentation>
<extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" id="402880e54803a496014805e5d9190012" />
</extensionElements>
<startEvent id="start" name="开始节点" flowable:initiator="applyUserId" />
<endEvent id="end" name="结束节点" />
<userTask id="task1609125750431" name="领取体检单" flowable:assignee="admin" />
<userTask id="task1609125786551" name="常规体检" flowable:assignee="admin" />
<userTask id="task1609125793898" name="抽血化验" flowable:assignee="admin" />
<userTask id="task1609125799296" name="深度体检" flowable:assignee="admin" />
<userTask id="task1609125820935" name="领取早餐" flowable:assignee="admin" />
<inclusiveGateway id="inclusiveGateway1609125773518" name="" />
<inclusiveGateway id="inclusiveGateway1609125834234" name="" />
<sequenceFlow id="flow1609125751935" name="" sourceRef="start" targetRef="task1609125750431" />
<sequenceFlow id="flow1609125775311" name="" sourceRef="task1609125750431" targetRef="inclusiveGateway1609125773518" />
<sequenceFlow id="flow1609125788617" name="普通员工" sourceRef="inclusiveGateway1609125773518" targetRef="task1609125786551">
<conditionExpression xsi:type="tFormalExpression">${user_type=='1'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow1609125802774" name="全部" sourceRef="inclusiveGateway1609125773518" targetRef="task1609125793898">
<conditionExpression xsi:type="tFormalExpression">${user_type=='1' || user_type=='2'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow1609125805530" name="领导" sourceRef="inclusiveGateway1609125773518" targetRef="task1609125799296">
<conditionExpression xsi:type="tFormalExpression">${user_type=='2'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow1609125822376" name="" sourceRef="task1609125793898" targetRef="task1609125820935" />
<sequenceFlow id="flow1609125836653" name="" sourceRef="task1609125786551" targetRef="inclusiveGateway1609125834234" />
<sequenceFlow id="flow1609125838174" name="" sourceRef="task1609125820935" targetRef="inclusiveGateway1609125834234" />
<sequenceFlow id="flow1609125840922" name="" sourceRef="task1609125799296" targetRef="inclusiveGateway1609125834234" />
<sequenceFlow id="flow1609125859731" name="" sourceRef="inclusiveGateway1609125834234" targetRef="end" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_process1609125744738">
<bpmndi:BPMNPlane id="BPMNPlane_process1609125744738" bpmnElement="process1609125744738">
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125859731" bpmnElement="flow1609125859731">
<omgdi:waypoint x="648" y="553" />
<omgdi:waypoint x="648" y="616" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125840922" bpmnElement="flow1609125840922">
<omgdi:waypoint x="839" y="404" />
<omgdi:waypoint x="839" y="533" />
<omgdi:waypoint x="668" y="533" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125838174" bpmnElement="flow1609125838174">
<omgdi:waypoint x="648" y="474" />
<omgdi:waypoint x="648" y="513" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125836653" bpmnElement="flow1609125836653">
<omgdi:waypoint x="468" y="401" />
<omgdi:waypoint x="468" y="533" />
<omgdi:waypoint x="628" y="533" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125822376" bpmnElement="flow1609125822376">
<omgdi:waypoint x="648" y="357" />
<omgdi:waypoint x="648" y="409" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125805530" bpmnElement="flow1609125805530">
<omgdi:waypoint x="668" y="218" />
<omgdi:waypoint x="839" y="218" />
<omgdi:waypoint x="839" y="339" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125802774" bpmnElement="flow1609125802774">
<omgdi:waypoint x="648" y="238" />
<omgdi:waypoint x="648" y="292" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125788617" bpmnElement="flow1609125788617">
<omgdi:waypoint x="628" y="218" />
<omgdi:waypoint x="468" y="218" />
<omgdi:waypoint x="468" y="336" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125775311" bpmnElement="flow1609125775311">
<omgdi:waypoint x="648" y="161" />
<omgdi:waypoint x="648" y="198" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1609125751935" bpmnElement="flow1609125751935">
<omgdi:waypoint x="648" y="58" />
<omgdi:waypoint x="648" y="96" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_start" bpmnElement="start">
<omgdc:Bounds x="633" y="28" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_end" bpmnElement="end">
<omgdc:Bounds x="633" y="616" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1609125750431" bpmnElement="task1609125750431">
<omgdc:Bounds x="603" y="101" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1609125786551" bpmnElement="task1609125786551">
<omgdc:Bounds x="423" y="341" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1609125793898" bpmnElement="task1609125793898">
<omgdc:Bounds x="603" y="297" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1609125799296" bpmnElement="task1609125799296">
<omgdc:Bounds x="794" y="344" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1609125820935" bpmnElement="task1609125820935">
<omgdc:Bounds x="603" y="414" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_inclusiveGateway1609125773518" bpmnElement="inclusiveGateway1609125773518">
<omgdc:Bounds x="628" y="198" width="40" height="40" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_inclusiveGateway1609125834234" bpmnElement="inclusiveGateway1609125834234">
<omgdc:Bounds x="628" y="513" width="40" height="40" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

View File

@@ -0,0 +1,444 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:flowable="http://flowable.org/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="process_1756775525804_Copy1773212842337" name="合同付款审批单(港口建设)">
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId">
<bpmn2:outgoing>Flow_194ke75</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:exclusiveGateway id="Gateway_1hewqcs">
<bpmn2:incoming>Flow_1hfpycx</bpmn2:incoming>
<bpmn2:outgoing>Flow_1xvhzh0</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0305r4s</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:userTask id="Task_0xmeul8" name="部门负责人意见1">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:true,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0z0d1ip</bpmn2:incoming>
<bpmn2:outgoing>Flow_0iht4oy</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1xvhzh0" name="拟稿部门为&#39;控股集团/天津临港建设开发有限公司/综合业务部&#39;" sourceRef="Gateway_1hewqcs" targetRef="Task_039xymg">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImluIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjE5NjA5NjI4NDQ5MzIwODM3MTMifV19XQ==', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="Task_1mu35nc" name="请款部门负责人意见" flowable:candidateUsers="${flowNodeExecution.getDepartLeaders(execution)}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:4,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0305r4s</bpmn2:incoming>
<bpmn2:outgoing>Flow_1q3qzvz</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0305r4s" name="拟稿部门不为&#39;控股集团/天津临港建设开发有限公司/综合业务部&#39;" sourceRef="Gateway_1hewqcs" targetRef="Task_1mu35nc">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6Im5vdF9pbiIsImZpZWxkIjoiYXBwbHlVc2VyRGVwdCIsImV4cGVjdGVkVmFsdWUiOiIxOTYwOTYyODQ0OTMyMDgzNzEzIn1dfV0=', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="Task_0dmir1x" name="合约审核" flowable:assignee="wangpinglikg">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0iht4oy</bpmn2:incoming>
<bpmn2:incoming>Flow_1q3qzvz</bpmn2:incoming>
<bpmn2:outgoing>Flow_1efh09l</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0iht4oy" sourceRef="Task_0xmeul8" targetRef="Task_0dmir1x" />
<bpmn2:sequenceFlow id="Flow_1q3qzvz" sourceRef="Task_1mu35nc" targetRef="Task_0dmir1x" />
<bpmn2:userTask id="Task_13o510x" name="合约部长意见" flowable:assignee="songzhenhuajs">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1efh09l</bpmn2:incoming>
<bpmn2:outgoing>Flow_19j5ppn</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1efh09l" sourceRef="Task_0dmir1x" targetRef="Task_13o510x" />
<bpmn2:userTask id="Task_19afnod" name="财务部审核" flowable:assignee="chenyuqinggx">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_19j5ppn</bpmn2:incoming>
<bpmn2:outgoing>Flow_0yojr3f</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_19j5ppn" sourceRef="Task_13o510x" targetRef="Task_19afnod" />
<bpmn2:userTask id="Task_0ov26ij" name="财务部部长意见" flowable:candidateGroups="1960962847985537025" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0yojr3f</bpmn2:incoming>
<bpmn2:outgoing>Flow_0dy83mm</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0yojr3f" sourceRef="Task_19afnod" targetRef="Task_0ov26ij" />
<bpmn2:userTask id="Task_1iv6eda" name="财务部分管意见" flowable:candidateGroups="1968884577609592833" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0dy83mm</bpmn2:incoming>
<bpmn2:outgoing>Flow_1yp3e5y</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0dy83mm" sourceRef="Task_0ov26ij" targetRef="Task_1iv6eda" />
<bpmn2:exclusiveGateway id="Gateway_1ol0mu2" name="财务部分管条件">
<bpmn2:incoming>Flow_1yp3e5y</bpmn2:incoming>
<bpmn2:outgoing>Flow_0lgv1a0</bpmn2:outgoing>
<bpmn2:outgoing>Flow_1tjmne2</bpmn2:outgoing>
<bpmn2:outgoing>Flow_1y9an67</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0suiubw</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:userTask id="Task_0k698zx" name="分发分管" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1tjmne2</bpmn2:incoming>
<bpmn2:outgoing>Flow_1ttkksl</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_1lh31tb" name="请款分管(部长)" flowable:candidateUsers="${flowNodeExecution.getUserSuperPositionLevel1(execution)}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0suiubw</bpmn2:incoming>
<bpmn2:outgoing>Flow_1tmacbb</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0cemvm9" name="董事长意见" flowable:candidateGroups="1966071463369428994" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1y9an67</bpmn2:incoming>
<bpmn2:incoming>Flow_1xw77jf</bpmn2:incoming>
<bpmn2:incoming>Flow_1tmacbb</bpmn2:incoming>
<bpmn2:incoming>Flow_1skcnfe</bpmn2:incoming>
<bpmn2:outgoing>Flow_1vssh8x</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0wj5cc8" name="分发分管" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0lgv1a0</bpmn2:incoming>
<bpmn2:outgoing>Flow_0cr0plz</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_1cvoviy" name="归档" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0hq3im2</bpmn2:incoming>
<bpmn2:outgoing>Flow_09ydmz6</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0sjwgg6" name="财务出纳归档" flowable:assignee="lingyunlongjs">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_09ydmz6</bpmn2:incoming>
<bpmn2:outgoing>Flow_0cuolg2</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_09ydmz6" sourceRef="Task_1cvoviy" targetRef="Task_0sjwgg6" />
<bpmn2:endEvent id="End_0zgs7h0">
<bpmn2:incoming>Flow_0cuolg2</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="Flow_0cuolg2" sourceRef="Task_0sjwgg6" targetRef="End_0zgs7h0" />
<bpmn2:userTask id="Task_0ho8mjr" name="请款分管领导意见1">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:true,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0cr0plz</bpmn2:incoming>
<bpmn2:outgoing>Flow_1xw77jf</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1yp3e5y" sourceRef="Task_1iv6eda" targetRef="Gateway_1ol0mu2" />
<bpmn2:sequenceFlow id="Flow_0lgv1a0" name="拟稿部门是 &#39;控股集团/天津临港建设开发有限公司/综合业务部&#39;" sourceRef="Gateway_1ol0mu2" targetRef="Task_0wj5cc8">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImluIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjE5NjA5NjI4NDQ1NjI5ODQ5NjIsMTk2MDk2Mjg0NDkzMjA4MzcxMyJ9XX1d', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_1tjmne2" name="拟稿人职级不为“部长”;拟稿部门不是&#39;控股集团/天津临港建设开发有限公司/综合管理部、综合业务部&#39;" sourceRef="Gateway_1ol0mu2" targetRef="Task_0k698zx">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6Im5vdF9jb250YWlucyIsImZpZWxkIjoiYXBwbHlVc2VyUG9zdExldmVsIiwiZXhwZWN0ZWRWYWx1ZSI6IjE5NTg0NzA5MTIyMTQzNjgyNTgifSx7Im9wZXJhdG9yIjoibm90X2luIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjE5NjA5NjI4NDQ1NjI5ODQ5NjIsMTk2MDk2Mjg0NDkzMjA4MzcxMywxOTYwOTYyODQ2NDUwNDIxNzYyIn1dfV0=', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_1y9an67" name="拟稿部门是 &#39;控股集团/天津临港建设开发有限公司/综合管理部&#39;" sourceRef="Gateway_1ol0mu2" targetRef="Task_0cemvm9">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImluIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjE5NjA5NjI4NDY0NTA0MjE3NjIifV19XQ==', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0suiubw" name="拟稿人职级是“部长”;拟稿部门不是&#39;控股集团/天津临港建设开发有限公司/综合管理部、综合业务部&#39;" sourceRef="Gateway_1ol0mu2" targetRef="Task_1lh31tb">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImNvbnRhaW5zIiwiZmllbGQiOiJhcHBseVVzZXJQb3N0TGV2ZWwiLCJleHBlY3RlZFZhbHVlIjoiMTk1ODQ3MDkxMjIxNDM2ODI1OCJ9LHsib3BlcmF0b3IiOiJub3RfaW4iLCJmaWVsZCI6ImFwcGx5VXNlckRlcHQiLCJleHBlY3RlZFZhbHVlIjoiMTk2MDk2Mjg0NDkzMjA4MzcxMywxOTYwOTYyODQ2NDUwNDIxNzYyIn1dfV0=', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0cr0plz" sourceRef="Task_0wj5cc8" targetRef="Task_0ho8mjr" />
<bpmn2:sequenceFlow id="Flow_1xw77jf" sourceRef="Task_0ho8mjr" targetRef="Task_0cemvm9" />
<bpmn2:sequenceFlow id="Flow_1tmacbb" sourceRef="Task_1lh31tb" targetRef="Task_0cemvm9" />
<bpmn2:userTask id="Task_1r80a8r" name="管理公司总经理意见" flowable:assignee="yuanfangjs">
<bpmn2:incoming>Flow_1vssh8x</bpmn2:incoming>
<bpmn2:outgoing>Flow_0gvx3zp</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1vssh8x" sourceRef="Task_0cemvm9" targetRef="Task_1r80a8r" />
<bpmn2:userTask id="Task_19muqhk" name="管理公司董事长意见" flowable:assignee="liushuchaozd">
<bpmn2:incoming>Flow_0gvx3zp</bpmn2:incoming>
<bpmn2:outgoing>Flow_0hq3im2</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0gvx3zp" sourceRef="Task_1r80a8r" targetRef="Task_19muqhk" />
<bpmn2:sequenceFlow id="Flow_0hq3im2" sourceRef="Task_19muqhk" targetRef="Task_1cvoviy" />
<bpmn2:userTask id="Task_0de4dzb" name="请款分管领导意见">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:true,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1ttkksl</bpmn2:incoming>
<bpmn2:outgoing>Flow_1skcnfe</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1ttkksl" sourceRef="Task_0k698zx" targetRef="Task_0de4dzb" />
<bpmn2:sequenceFlow id="Flow_1skcnfe" sourceRef="Task_0de4dzb" targetRef="Task_0cemvm9" />
<bpmn2:userTask id="Task_0vdkqa0" name="拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="9c3064baa7074eab62e3c5b3b5458691" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_194ke75</bpmn2:incoming>
<bpmn2:outgoing>Flow_1hfpycx</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_194ke75" sourceRef="start" targetRef="Task_0vdkqa0" />
<bpmn2:sequenceFlow id="Flow_1hfpycx" sourceRef="Task_0vdkqa0" targetRef="Gateway_1hewqcs" />
<bpmn2:userTask id="Task_039xymg" name="分发" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1xvhzh0</bpmn2:incoming>
<bpmn2:outgoing>Flow_0z0d1ip</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0z0d1ip" sourceRef="Task_039xymg" targetRef="Task_0xmeul8" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="process_1756775525804_Copy1773212842337">
<bpmndi:BPMNEdge id="Flow_0z0d1ip_di" bpmnElement="Flow_0z0d1ip">
<di:waypoint x="420" y="30" />
<di:waypoint x="420" y="110" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1hfpycx_di" bpmnElement="Flow_1hfpycx">
<di:waypoint x="210" y="230" />
<di:waypoint x="235" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_194ke75_di" bpmnElement="Flow_194ke75">
<di:waypoint x="68" y="230" />
<di:waypoint x="110" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1skcnfe_di" bpmnElement="Flow_1skcnfe">
<di:waypoint x="1350" y="220" />
<di:waypoint x="1350" y="290" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ttkksl_di" bpmnElement="Flow_1ttkksl">
<di:waypoint x="1180" y="200" />
<di:waypoint x="1180" y="180" />
<di:waypoint x="1300" y="180" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0hq3im2_di" bpmnElement="Flow_0hq3im2">
<di:waypoint x="1690" y="330" />
<di:waypoint x="1750" y="330" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0gvx3zp_di" bpmnElement="Flow_0gvx3zp">
<di:waypoint x="1540" y="330" />
<di:waypoint x="1590" y="330" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1vssh8x_di" bpmnElement="Flow_1vssh8x">
<di:waypoint x="1400" y="330" />
<di:waypoint x="1440" y="330" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1tmacbb_di" bpmnElement="Flow_1tmacbb">
<di:waypoint x="1235" y="440" />
<di:waypoint x="1350" y="440" />
<di:waypoint x="1350" y="370" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1xw77jf_di" bpmnElement="Flow_1xw77jf">
<di:waypoint x="1400" y="60" />
<di:waypoint x="1460" y="60" />
<di:waypoint x="1460" y="250" />
<di:waypoint x="1370" y="250" />
<di:waypoint x="1370" y="290" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0cr0plz_di" bpmnElement="Flow_0cr0plz">
<di:waypoint x="1230" y="60" />
<di:waypoint x="1300" y="60" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0suiubw_di" bpmnElement="Flow_0suiubw">
<di:waypoint x="865" y="130" />
<di:waypoint x="930" y="130" />
<di:waypoint x="930" y="440" />
<di:waypoint x="1125" y="440" />
<bpmndi:BPMNLabel>
<dc:Bounds x="985" y="360" width="89" height="80" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1y9an67_di" bpmnElement="Flow_1y9an67">
<di:waypoint x="865" y="130" />
<di:waypoint x="930" y="130" />
<di:waypoint x="930" y="330" />
<di:waypoint x="1300" y="330" />
<bpmndi:BPMNLabel>
<dc:Bounds x="985" y="273" width="89" height="53" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1tjmne2_di" bpmnElement="Flow_1tjmne2">
<di:waypoint x="865" y="130" />
<di:waypoint x="930" y="130" />
<di:waypoint x="930" y="240" />
<di:waypoint x="1130" y="240" />
<bpmndi:BPMNLabel>
<dc:Bounds x="985" y="153" width="89" height="93" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0lgv1a0_di" bpmnElement="Flow_0lgv1a0">
<di:waypoint x="840" y="105" />
<di:waypoint x="840" y="60" />
<di:waypoint x="1130" y="60" />
<bpmndi:BPMNLabel>
<dc:Bounds x="976" y="3" width="89" height="53" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1yp3e5y_di" bpmnElement="Flow_1yp3e5y">
<di:waypoint x="840" y="220" />
<di:waypoint x="840" y="155" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0cuolg2_di" bpmnElement="Flow_0cuolg2">
<di:waypoint x="1800" y="170" />
<di:waypoint x="1800" y="128" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_09ydmz6_di" bpmnElement="Flow_09ydmz6">
<di:waypoint x="1800" y="290" />
<di:waypoint x="1800" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0dy83mm_di" bpmnElement="Flow_0dy83mm">
<di:waypoint x="745" y="260" />
<di:waypoint x="790" y="260" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1446" y="212" width="89" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0yojr3f_di" bpmnElement="Flow_0yojr3f">
<di:waypoint x="695" y="170" />
<di:waypoint x="695" y="220" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1191" y="212" width="78" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_19j5ppn_di" bpmnElement="Flow_19j5ppn">
<di:waypoint x="590" y="130" />
<di:waypoint x="645" y="130" />
<bpmndi:BPMNLabel>
<dc:Bounds x="926" y="212" width="89" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1efh09l_di" bpmnElement="Flow_1efh09l">
<di:waypoint x="540" y="220" />
<di:waypoint x="540" y="170" />
<bpmndi:BPMNLabel>
<dc:Bounds x="667" y="212" width="86" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1q3qzvz_di" bpmnElement="Flow_1q3qzvz">
<di:waypoint x="420" y="300" />
<di:waypoint x="420" y="260" />
<di:waypoint x="490" y="260" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0iht4oy_di" bpmnElement="Flow_0iht4oy">
<di:waypoint x="420" y="190" />
<di:waypoint x="420" y="260" />
<di:waypoint x="490" y="260" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0305r4s_di" bpmnElement="Flow_0305r4s">
<di:waypoint x="260" y="255" />
<di:waypoint x="260" y="340" />
<di:waypoint x="370" y="340" />
<bpmndi:BPMNLabel>
<dc:Bounds x="265" y="283" width="89" height="53" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1xvhzh0_di" bpmnElement="Flow_1xvhzh0">
<di:waypoint x="260" y="205" />
<di:waypoint x="260" y="-10" />
<di:waypoint x="340" y="-10" />
<bpmndi:BPMNLabel>
<dc:Bounds x="149" y="43.000000000000284" width="81" height="53" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_01ydzqe_di" bpmnElement="start">
<dc:Bounds x="32" y="212" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="39" y="255" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1hewqcs_di" bpmnElement="Gateway_1hewqcs" isMarkerVisible="true">
<dc:Bounds x="235" y="205" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="299" y="223" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0xmeul8_di" bpmnElement="Task_0xmeul8">
<dc:Bounds x="370" y="110" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1mu35nc_di" bpmnElement="Task_1mu35nc">
<dc:Bounds x="370" y="300" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0dmir1x_di" bpmnElement="Task_0dmir1x">
<dc:Bounds x="490" y="220" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_13o510x_di" bpmnElement="Task_13o510x">
<dc:Bounds x="490" y="90" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_19afnod_di" bpmnElement="Task_19afnod">
<dc:Bounds x="645" y="90" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0ov26ij_di" bpmnElement="Task_0ov26ij">
<dc:Bounds x="645" y="220" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1iv6eda_di" bpmnElement="Task_1iv6eda">
<dc:Bounds x="790" y="220" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1ol0mu2_di" bpmnElement="Gateway_1ol0mu2" isMarkerVisible="true">
<dc:Bounds x="815" y="105" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="727.5" y="123" width="77" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0k698zx_di" bpmnElement="Task_0k698zx">
<dc:Bounds x="1130" y="200" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1lh31tb_di" bpmnElement="Task_1lh31tb">
<dc:Bounds x="1125" y="400" width="110" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0cemvm9_di" bpmnElement="Task_0cemvm9">
<dc:Bounds x="1300" y="290" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0wj5cc8_di" bpmnElement="Task_0wj5cc8">
<dc:Bounds x="1130" y="20" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1cvoviy_di" bpmnElement="Task_1cvoviy">
<dc:Bounds x="1750" y="290" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0sjwgg6_di" bpmnElement="Task_0sjwgg6">
<dc:Bounds x="1750" y="170" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0zgs7h0_di" bpmnElement="End_0zgs7h0">
<dc:Bounds x="1782" y="92" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0ho8mjr_di" bpmnElement="Task_0ho8mjr">
<dc:Bounds x="1300" y="20" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1r80a8r_di" bpmnElement="Task_1r80a8r">
<dc:Bounds x="1440" y="290" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_19muqhk_di" bpmnElement="Task_19muqhk">
<dc:Bounds x="1590" y="290" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0de4dzb_di" bpmnElement="Task_0de4dzb">
<dc:Bounds x="1300" y="140" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0vdkqa0_di" bpmnElement="Task_0vdkqa0">
<dc:Bounds x="110" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_039xymg_di" bpmnElement="Task_039xymg">
<dc:Bounds x="350" y="-50" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@@ -0,0 +1,398 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:flowable="http://flowable.org/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="process_1757598885161_Copy1769744302823" name="合同审批单(津保农科)">
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId">
<bpmn2:outgoing>Flow_0yb0cn8</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:userTask id="Task_0iow9ab" name="选择部长" flowable:assignee="${applyUserId}">
<bpmn2:incoming>Flow_1hbdhol</bpmn2:incoming>
<bpmn2:outgoing>Flow_1xbxp0f</bpmn2:outgoing>
<bpmn2:outgoing>Flow_1og68yt</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:endEvent id="End_13hztj7">
<bpmn2:incoming>Flow_0gtibrb</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:userTask id="Task_1deyg2s" name="部门副部长" flowable:candidateUsers="${oaFlowExpression.getApplyUserDeptPositionLevel(sys_org_code, applyUserId, &#39;1958470865577902082&#39;)}" flowable:groupType="position">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1xbxp0f</bpmn2:incoming>
<bpmn2:outgoing>Flow_0xvcnmm</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1xbxp0f" name="部门副部长" sourceRef="Task_0iow9ab" targetRef="Task_1deyg2s" />
<bpmn2:userTask id="Task_0bpjtwx" name="部门负责人" flowable:candidateUsers="${oaFlowExpression.getApplyUserDeptPositionLevel(sys_org_code, applyUserId, &#39;1958470912214368258&#39;)}" flowable:groupType="position">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1og68yt</bpmn2:incoming>
<bpmn2:incoming>Flow_0xvcnmm</bpmn2:incoming>
<bpmn2:outgoing>Flow_0r60abv</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1og68yt" name="部门负责人" sourceRef="Task_0iow9ab" targetRef="Task_0bpjtwx" />
<bpmn2:sequenceFlow id="Flow_0xvcnmm" sourceRef="Task_1deyg2s" targetRef="Task_0bpjtwx" />
<bpmn2:userTask id="Task_0xx203f" name="成本采购部长" flowable:candidateGroups="1960962870865465345" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0r60abv</bpmn2:incoming>
<bpmn2:outgoing>Flow_1e5tjly</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0r60abv" sourceRef="Task_0bpjtwx" targetRef="Task_0xx203f" />
<bpmn2:userTask id="Task_0a0p4gn" name="法律顾问" flowable:candidateGroups="1960962862476857345" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1e5tjly</bpmn2:incoming>
<bpmn2:outgoing>Flow_05uouf2</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1e5tjly" sourceRef="Task_0xx203f" targetRef="Task_0a0p4gn" />
<bpmn2:userTask id="Task_0g460ja" name="公司法务" flowable:assignee="sunjingyu">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_05uouf2</bpmn2:incoming>
<bpmn2:outgoing>Flow_0awgo05</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_05uouf2" sourceRef="Task_0a0p4gn" targetRef="Task_0g460ja" />
<bpmn2:userTask id="Task_075tij5" name="风控部长意见" flowable:candidateGroups="1962405428149764097" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0awgo05</bpmn2:incoming>
<bpmn2:outgoing>Flow_1q9efh1</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0awgo05" sourceRef="Task_0g460ja" targetRef="Task_075tij5" />
<bpmn2:userTask id="Task_17iuxyd" name="选择协办" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1q9efh1</bpmn2:incoming>
<bpmn2:outgoing>Flow_1o3qg32</bpmn2:outgoing>
<bpmn2:outgoing>Flow_1sdeni7</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1q9efh1" sourceRef="Task_075tij5" targetRef="Task_17iuxyd" />
<bpmn2:userTask id="Task_1mx9pdx" name="协办主管意见" flowable:assignee="${assigneeUserId}" flowable:candidateUsers="${flowUtil.getUsersByFormData(execution,&#39;select_user_1757424592515_554890&#39;,&#39;select-user&#39;)}" flowable:groupType="formData" flowable:countersignRule="countersign_all">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskCountersignExtendJson value="eyJhdWRpdG9yVXNlclR5cGUiOiJmb3JtRGF0YSIsImF1ZGl0b3JDb3VudGVyc2lnbkZvcm1GaWVsZCI6InNlbGVjdF91c2VyXzE3NTc0MjQ1OTI1MTVfNTU0ODkwIiwiYXVkaXRvckNvdW50ZXJzaWduRm9ybUZpZWxkVHlwZSI6InNlbGVjdC11c2VyIiwidGltZXN0YW1wIjoxNzY5NzQzNzM2ODczfQ==" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1o3qg32</bpmn2:incoming>
<bpmn2:outgoing>Flow_0o6i6r8</bpmn2:outgoing>
<bpmn2:multiInstanceLoopCharacteristics flowable:collection="${flowUtil.getAssigneeUsers(execution,&#39;eyJhdWRpdG9yVXNlclR5cGUiOiJmb3JtRGF0YSIsImF1ZGl0b3JDb3VudGVyc2lnbkZvcm1GaWVsZCI6InNlbGVjdF91c2VyXzE3NTc0MjQ1OTI1MTVfNTU0ODkwIiwiYXVkaXRvckNvdW50ZXJzaWduRm9ybUZpZWxkVHlwZSI6InNlbGVjdC11c2VyIiwiZGVzZm9ybUNvZGUiOiJndW9qdS1oZV90b25nX3NoZW5fcGlfZGFuX2ppbmJhb3poaWtlIn0=&#39;)}" flowable:elementVariable="assigneeUserId">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">${nrOfCompletedInstances/nrOfInstances==1}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1o3qg32" name="协办主管" sourceRef="Task_17iuxyd" targetRef="Task_1mx9pdx" />
<bpmn2:userTask id="Task_19z078t" name="财务部长意见" flowable:candidateGroups="1960962863265386498" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1sdeni7</bpmn2:incoming>
<bpmn2:incoming>Flow_0o6i6r8</bpmn2:incoming>
<bpmn2:outgoing>Flow_12wit1t</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1sdeni7" name="财务部长" sourceRef="Task_17iuxyd" targetRef="Task_19z078t" />
<bpmn2:sequenceFlow id="Flow_0o6i6r8" sourceRef="Task_1mx9pdx" targetRef="Task_19z078t" />
<bpmn2:exclusiveGateway id="Gateway_1wx2g6v">
<bpmn2:incoming>Flow_12wit1t</bpmn2:incoming>
<bpmn2:outgoing>Flow_0otfktt</bpmn2:outgoing>
<bpmn2:outgoing>Flow_1wblljm</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="Flow_12wit1t" sourceRef="Task_19z078t" targetRef="Gateway_1wx2g6v" />
<bpmn2:userTask id="Task_0ka84im" name="分管领导(员工)" flowable:candidateUsers="${flowNodeExecution.getUserSuperPositionLevel2(execution)}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0otfktt</bpmn2:incoming>
<bpmn2:outgoing>Flow_0bvgove</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0otfktt" name="拟稿人职位级别不是部长" sourceRef="Gateway_1wx2g6v" targetRef="Task_0ka84im">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6Im5lIiwiZmllbGQiOiJhcHBseVVzZXJQb3N0TGV2ZWwiLCJleHBlY3RlZFZhbHVlIjoiMTk1ODQ3MDkxMjIxNDM2ODI1OCJ9XX1d', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="Task_17lx9tw" name="分管领导(部长)" flowable:candidateUsers="${flowNodeExecution.getUserSuperPositionLevel1(execution)}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1wblljm</bpmn2:incoming>
<bpmn2:outgoing>Flow_0hnodme</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1wblljm" name="拟稿人职位级别是部长" sourceRef="Gateway_1wx2g6v" targetRef="Task_17lx9tw">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImVxIiwiZmllbGQiOiJhcHBseVVzZXJQb3N0TGV2ZWwiLCJleHBlY3RlZFZhbHVlIjoiMTk1ODQ3MDkxMjIxNDM2ODI1OCJ9XX1d', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="Task_19h0git" name="成本采购副总" flowable:candidateGroups="1962787693681852417" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0bvgove</bpmn2:incoming>
<bpmn2:incoming>Flow_0hnodme</bpmn2:incoming>
<bpmn2:outgoing>Flow_0mut7pk</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0bvgove" sourceRef="Task_0ka84im" targetRef="Task_19h0git" />
<bpmn2:sequenceFlow id="Flow_0hnodme" sourceRef="Task_17lx9tw" targetRef="Task_19h0git" />
<bpmn2:userTask id="Task_0xvphxx" name="总经理意见" flowable:candidateGroups="1960962863860977666" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0mut7pk</bpmn2:incoming>
<bpmn2:outgoing>Flow_11emv9k</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0mut7pk" sourceRef="Task_19h0git" targetRef="Task_0xvphxx" />
<bpmn2:exclusiveGateway id="Gateway_01ih3l6">
<bpmn2:incoming>Flow_11emv9k</bpmn2:incoming>
<bpmn2:outgoing>Flow_1gq2o33</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0kso016</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="Flow_11emv9k" sourceRef="Task_0xvphxx" targetRef="Gateway_01ih3l6" />
<bpmn2:userTask id="Task_04yadz6" name="董事长意见" flowable:candidateGroups="1960962864179744770" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0kso016</bpmn2:incoming>
<bpmn2:outgoing>Flow_1nd4qv7</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0zkraig" name="通知拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:incoming>Flow_1gq2o33</bpmn2:incoming>
<bpmn2:incoming>Flow_1nd4qv7</bpmn2:incoming>
<bpmn2:outgoing>Flow_0gtibrb</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1gq2o33" name="合同金额小于等于1000000" sourceRef="Gateway_01ih3l6" targetRef="Task_0zkraig">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6Imx0ZSIsImZpZWxkIjoibW9uZXlfMTc1NzQyMTQ3Mjc5N183NDYwMTYiLCJmaWVsZFR5cGUiOiJtb25leSIsImZpZWxkTmFtZSI6IuWQiOWQjOS7t+asvuWwj+WGmSIsImV4cGVjdGVkVmFsdWUiOiIxMDAwMDAwIn1dfV0=', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0kso016" name="合同金额大于1000000" sourceRef="Gateway_01ih3l6" targetRef="Task_04yadz6">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6Imd0IiwiZmllbGQiOiJtb25leV8xNzU3NDIxNDcyNzk3Xzc0NjAxNiIsImZpZWxkVHlwZSI6Im1vbmV5IiwiZmllbGROYW1lIjoi5ZCI5ZCM5Lu35qy+5bCP5YaZIiwiZXhwZWN0ZWRWYWx1ZSI6IjEwMDAwMDAifV19XQ==', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_1nd4qv7" sourceRef="Task_04yadz6" targetRef="Task_0zkraig" />
<bpmn2:sequenceFlow id="Flow_0gtibrb" sourceRef="Task_0zkraig" targetRef="End_13hztj7" />
<bpmn2:userTask id="Task_0z2ffc7" name="拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="9c3064baa7074eab62e3c5b3b5458691" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0yb0cn8</bpmn2:incoming>
<bpmn2:outgoing>Flow_1hbdhol</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0yb0cn8" sourceRef="start" targetRef="Task_0z2ffc7" />
<bpmn2:sequenceFlow id="Flow_1hbdhol" sourceRef="Task_0z2ffc7" targetRef="Task_0iow9ab" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="process_1757598885161_Copy1769744302823">
<bpmndi:BPMNEdge id="Flow_1hbdhol_di" bpmnElement="Flow_1hbdhol">
<di:waypoint x="330" y="230" />
<di:waypoint x="390" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0yb0cn8_di" bpmnElement="Flow_0yb0cn8">
<di:waypoint x="280" y="138" />
<di:waypoint x="280" y="190" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0gtibrb_di" bpmnElement="Flow_0gtibrb">
<di:waypoint x="300" y="650" />
<di:waypoint x="300" y="712" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1nd4qv7_di" bpmnElement="Flow_1nd4qv7">
<di:waypoint x="300" y="510" />
<di:waypoint x="300" y="570" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0kso016_di" bpmnElement="Flow_0kso016">
<di:waypoint x="420" y="505" />
<di:waypoint x="420" y="470" />
<di:waypoint x="350" y="470" />
<bpmndi:BPMNLabel>
<dc:Bounds x="393" y="476" width="85" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1gq2o33_di" bpmnElement="Flow_1gq2o33">
<di:waypoint x="420" y="555" />
<di:waypoint x="420" y="610" />
<di:waypoint x="350" y="610" />
<bpmndi:BPMNLabel>
<dc:Bounds x="391" y="580" width="88" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_11emv9k_di" bpmnElement="Flow_11emv9k">
<di:waypoint x="500" y="530" />
<di:waypoint x="445" y="530" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0mut7pk_di" bpmnElement="Flow_0mut7pk">
<di:waypoint x="640" y="530" />
<di:waypoint x="600" y="530" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0hnodme_di" bpmnElement="Flow_0hnodme">
<di:waypoint x="810" y="610" />
<di:waypoint x="775" y="610" />
<di:waypoint x="775" y="530" />
<di:waypoint x="740" y="530" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0bvgove_di" bpmnElement="Flow_0bvgove">
<di:waypoint x="810" y="460" />
<di:waypoint x="775" y="460" />
<di:waypoint x="775" y="530" />
<di:waypoint x="740" y="530" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1wblljm_di" bpmnElement="Flow_1wblljm">
<di:waypoint x="950" y="555" />
<di:waypoint x="950" y="610" />
<di:waypoint x="910" y="610" />
<bpmndi:BPMNLabel>
<dc:Bounds x="927" y="580" width="76" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0otfktt_di" bpmnElement="Flow_0otfktt">
<di:waypoint x="950" y="505" />
<di:waypoint x="950" y="460" />
<di:waypoint x="910" y="460" />
<bpmndi:BPMNLabel>
<dc:Bounds x="912" y="476" width="76" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_12wit1t_di" bpmnElement="Flow_12wit1t">
<di:waypoint x="1020" y="530" />
<di:waypoint x="975" y="530" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0o6i6r8_di" bpmnElement="Flow_0o6i6r8">
<di:waypoint x="1190" y="610" />
<di:waypoint x="1165" y="610" />
<di:waypoint x="1165" y="530" />
<di:waypoint x="1120" y="530" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1sdeni7_di" bpmnElement="Flow_1sdeni7">
<di:waypoint x="1190" y="470" />
<di:waypoint x="1160" y="470" />
<di:waypoint x="1160" y="530" />
<di:waypoint x="1120" y="530" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1138" y="483" width="43" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1o3qg32_di" bpmnElement="Flow_1o3qg32">
<di:waypoint x="1240" y="510" />
<di:waypoint x="1240" y="570" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1217" y="537" width="45" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1q9efh1_di" bpmnElement="Flow_1q9efh1">
<di:waypoint x="1240" y="380" />
<di:waypoint x="1240" y="430" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0awgo05_di" bpmnElement="Flow_0awgo05">
<di:waypoint x="1130" y="340" />
<di:waypoint x="1190" y="340" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_05uouf2_di" bpmnElement="Flow_05uouf2">
<di:waypoint x="970" y="340" />
<di:waypoint x="1030" y="340" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1e5tjly_di" bpmnElement="Flow_1e5tjly">
<di:waypoint x="810" y="340" />
<di:waypoint x="870" y="340" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0r60abv_di" bpmnElement="Flow_0r60abv">
<di:waypoint x="660" y="340" />
<di:waypoint x="710" y="340" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0xvcnmm_di" bpmnElement="Flow_0xvcnmm">
<di:waypoint x="610" y="270" />
<di:waypoint x="610" y="300" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1og68yt_di" bpmnElement="Flow_1og68yt">
<di:waypoint x="490" y="230" />
<di:waypoint x="520" y="230" />
<di:waypoint x="520" y="340" />
<di:waypoint x="560" y="340" />
<bpmndi:BPMNLabel>
<dc:Bounds x="492" y="293" width="56" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1xbxp0f_di" bpmnElement="Flow_1xbxp0f">
<di:waypoint x="490" y="230" />
<di:waypoint x="560" y="230" />
<bpmndi:BPMNLabel>
<dc:Bounds x="498" y="212" width="54" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_01ydzqe_di" bpmnElement="start">
<dc:Bounds x="262" y="102" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="268" y="78" width="23" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0iow9ab_di" bpmnElement="Task_0iow9ab">
<dc:Bounds x="390" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_13hztj7_di" bpmnElement="End_13hztj7">
<dc:Bounds x="282" y="712" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1deyg2s_di" bpmnElement="Task_1deyg2s">
<dc:Bounds x="560" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0bpjtwx_di" bpmnElement="Task_0bpjtwx">
<dc:Bounds x="560" y="300" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0xx203f_di" bpmnElement="Task_0xx203f">
<dc:Bounds x="710" y="300" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0a0p4gn_di" bpmnElement="Task_0a0p4gn">
<dc:Bounds x="870" y="300" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0g460ja_di" bpmnElement="Task_0g460ja">
<dc:Bounds x="1030" y="300" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_075tij5_di" bpmnElement="Task_075tij5">
<dc:Bounds x="1190" y="300" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_17iuxyd_di" bpmnElement="Task_17iuxyd">
<dc:Bounds x="1190" y="430" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1mx9pdx_di" bpmnElement="Task_1mx9pdx">
<dc:Bounds x="1190" y="570" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_19z078t_di" bpmnElement="Task_19z078t">
<dc:Bounds x="1020" y="490" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1wx2g6v_di" bpmnElement="Gateway_1wx2g6v" isMarkerVisible="true">
<dc:Bounds x="925" y="505" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0ka84im_di" bpmnElement="Task_0ka84im">
<dc:Bounds x="810" y="420" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_17lx9tw_di" bpmnElement="Task_17lx9tw">
<dc:Bounds x="810" y="570" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_19h0git_di" bpmnElement="Task_19h0git">
<dc:Bounds x="640" y="490" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0xvphxx_di" bpmnElement="Task_0xvphxx">
<dc:Bounds x="500" y="490" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_01ih3l6_di" bpmnElement="Gateway_01ih3l6" isMarkerVisible="true">
<dc:Bounds x="395" y="505" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_04yadz6_di" bpmnElement="Task_04yadz6">
<dc:Bounds x="250" y="430" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0zkraig_di" bpmnElement="Task_0zkraig">
<dc:Bounds x="250" y="570" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0z2ffc7_di" bpmnElement="Task_0z2ffc7">
<dc:Bounds x="230" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="http://www.jeecg.org">
<process id="subflow" name="子流程(会签主子流程)">
<documentation>流程描述</documentation>
<extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" id="402880e54803a496014805e5d9190012" />
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.SubProcessHqStartListener" event="start" id="1177167770459070465" />
</extensionElements>
<startEvent id="start1" name="开始节点" flowable:initiator="applyUserId" />
<endEvent id="end" name="结束节点" />
<userTask id="task1568615166625" name="子流程任务1" flowable:assignee="${assigneeUserId}" />
<userTask id="task1568615170581" name="子流程任务2" flowable:assignee="${assigneeUserId}" />
<sequenceFlow id="flow1568615185359" name="" sourceRef="start1" targetRef="task1568615166625" />
<sequenceFlow id="flow1568615187311" name="" sourceRef="task1568615166625" targetRef="task1568615170581" />
<sequenceFlow id="flow1568615188931" name="" sourceRef="task1568615170581" targetRef="end" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_subflow">
<bpmndi:BPMNPlane id="BPMNPlane_subflow" bpmnElement="subflow">
<bpmndi:BPMNEdge id="BPMNEdge_flow1568615188931" bpmnElement="flow1568615188931">
<omgdi:waypoint x="220" y="343" />
<omgdi:waypoint x="220" y="394" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1568615187311" bpmnElement="flow1568615187311">
<omgdi:waypoint x="220" y="233" />
<omgdi:waypoint x="220" y="278" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1568615185359" bpmnElement="flow1568615185359">
<omgdi:waypoint x="220" y="98" />
<omgdi:waypoint x="220" y="168" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_start1" bpmnElement="start1">
<omgdc:Bounds x="205" y="68" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_end" bpmnElement="end">
<omgdc:Bounds x="205" y="394" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1568615166625" bpmnElement="task1568615166625">
<omgdc:Bounds x="175" y="173" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1568615170581" bpmnElement="task1568615170581">
<omgdc:Bounds x="175" y="283" width="90" height="55" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="http://www.jeecg.org">
<process id="joa_onl_bthuiq" name="并行会签测试">
<documentation>流程描述</documentation>
<extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" id="402880e54803a496014805e5d9190012" />
</extensionElements>
<startEvent id="start1" name="开始节点" flowable:initiator="applyUserId" />
<endEvent id="end" name="结束节点" />
<userTask id="task1557561204791" name="选择会签人员" flowable:assignee="${applyUserId}" />
<userTask id="task1557561208150" name="会签审阅" flowable:assignee="${assigneeUserId}">
<multiInstanceLoopCharacteristics flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}" flowable:elementVariable="assigneeUserId" />
</userTask>
<sequenceFlow id="flow1557561259013" name="" sourceRef="start1" targetRef="task1557561204791" />
<sequenceFlow id="flow1557561260674" name="" sourceRef="task1557561204791" targetRef="task1557561208150" />
<sequenceFlow id="flow1557561265853" name="" sourceRef="task1557561208150" targetRef="end" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_joa_onl_bthuiq">
<bpmndi:BPMNPlane id="BPMNPlane_joa_onl_bthuiq" bpmnElement="joa_onl_bthuiq">
<bpmndi:BPMNEdge id="BPMNEdge_flow1557561265853" bpmnElement="flow1557561265853">
<omgdi:waypoint x="215" y="337" />
<omgdi:waypoint x="215" y="400" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1557561260674" bpmnElement="flow1557561260674">
<omgdi:waypoint x="215" y="206" />
<omgdi:waypoint x="215" y="272" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1557561259013" bpmnElement="flow1557561259013">
<omgdi:waypoint x="215" y="80" />
<omgdi:waypoint x="215" y="141" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_start1" bpmnElement="start1">
<omgdc:Bounds x="200" y="50" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_end" bpmnElement="end">
<omgdc:Bounds x="200" y="400" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1557561204791" bpmnElement="task1557561204791">
<omgdc:Bounds x="170" y="146" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1557561208150" bpmnElement="task1557561208150">
<omgdc:Bounds x="170" y="277" width="90" height="55" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

View File

@@ -0,0 +1,283 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:flowable="http://flowable.org/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="process_1770171465054" name="档案查借阅审批表财务_津保建工">
<bpmn2:documentation>辛圣-488</bpmn2:documentation>
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId">
<bpmn2:outgoing>Flow_1lfex13</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:exclusiveGateway id="Gateway_0iodhu1" name="拟稿人部门判断">
<bpmn2:incoming>Flow_1oy9cwp</bpmn2:incoming>
<bpmn2:outgoing>Flow_1ukgpce</bpmn2:outgoing>
<bpmn2:outgoing>Flow_1p7q68w</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:userTask id="Task_1axmobm" name="部长" flowable:candidateUsers="${flowNodeExecution.getDepartLeaders(execution)}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1ukgpce</bpmn2:incoming>
<bpmn2:outgoing>Flow_00sne96</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1ukgpce" name="不等于财务部" sourceRef="Gateway_0iodhu1" targetRef="Task_1axmobm">
<bpmn2:documentation>拟稿部门 不等于 '控股集团/天津临港建设开发有限公司/财务部' AND 拟稿部门 不等于 '控股集团/天津临港建设开发有限公司/综合业务部'</bpmn2:documentation>
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6Im5lIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjIwMDQwNzQ4NzgxNTg0MzQzMDUifV19XQ==', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="Task_0q5x40k" name="财务部长审批" flowable:candidateGroups="2004447454765576194" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1p7q68w</bpmn2:incoming>
<bpmn2:incoming>Flow_1oazprh</bpmn2:incoming>
<bpmn2:incoming>Flow_1hz4oth</bpmn2:incoming>
<bpmn2:outgoing>Flow_0txpi3x</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1p7q68w" name="等于财务部中心" sourceRef="Gateway_0iodhu1" targetRef="Task_0q5x40k">
<bpmn2:documentation>拟稿人是财务部</bpmn2:documentation>
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImVxIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjIwMDQwNzQ4NzgxNTg0MzQzMDUifV19XQ==', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="Task_0u5r15n" name="综合管理部分管领导" flowable:candidateGroups="2004447105202712577" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1442ka9</bpmn2:incoming>
<bpmn2:outgoing>Flow_1hz4oth</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0s10sxf" name="分管领导" flowable:candidateUsers="${flowUtil.getUsersByApprRole(execution,&#39;2004720656484859905&#39;)}" flowable:groupType="approvalRole">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0vc1pbk</bpmn2:incoming>
<bpmn2:outgoing>Flow_1oazprh</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_00sne96" sourceRef="Task_1axmobm" targetRef="Gateway_04s5k5c" />
<bpmn2:sequenceFlow id="Flow_1oazprh" sourceRef="Task_0s10sxf" targetRef="Task_0q5x40k" />
<bpmn2:sequenceFlow id="Flow_1hz4oth" sourceRef="Task_0u5r15n" targetRef="Task_0q5x40k" />
<bpmn2:userTask id="Task_0b2oy6g" name="财务分管领导" flowable:candidateGroups="2004447353945911297" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0txpi3x</bpmn2:incoming>
<bpmn2:outgoing>Flow_1le9mul</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0txpi3x" sourceRef="Task_0q5x40k" targetRef="Task_0b2oy6g" />
<bpmn2:userTask id="Task_0et8d23" name="总经理" flowable:candidateGroups="2004447190382993410" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_16asn4s</bpmn2:incoming>
<bpmn2:outgoing>Flow_09oe88o</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0kjzelu" name="档案管理员" flowable:candidateUsers="${flowUtil.getUsersByApprRole(execution,&#39;2032291035773501441&#39;)}" flowable:groupType="approvalRole">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true,&#34;isSkipApprovedOnCountersignReturn&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_09oe88o</bpmn2:incoming>
<bpmn2:incoming>Flow_0sof4uo</bpmn2:incoming>
<bpmn2:outgoing>Flow_0uf4o1g</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_09oe88o" sourceRef="Task_0et8d23" targetRef="Task_0kjzelu" />
<bpmn2:userTask id="Task_01bwpxc" name="通知拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0uf4o1g</bpmn2:incoming>
<bpmn2:outgoing>Flow_0pe9z7z</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0uf4o1g" sourceRef="Task_0kjzelu" targetRef="Task_01bwpxc" />
<bpmn2:endEvent id="End_1l91ykf">
<bpmn2:incoming>Flow_0pe9z7z</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="Flow_0pe9z7z" sourceRef="Task_01bwpxc" targetRef="End_1l91ykf" />
<bpmn2:userTask id="Task_1bvodcd" name="拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="9c3064baa7074eab62e3c5b3b5458691" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1lfex13</bpmn2:incoming>
<bpmn2:outgoing>Flow_1oy9cwp</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1lfex13" sourceRef="start" targetRef="Task_1bvodcd" />
<bpmn2:sequenceFlow id="Flow_1oy9cwp" sourceRef="Task_1bvodcd" targetRef="Gateway_0iodhu1" />
<bpmn2:exclusiveGateway id="Gateway_05hr90o">
<bpmn2:incoming>Flow_1le9mul</bpmn2:incoming>
<bpmn2:outgoing>Flow_16asn4s</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0sof4uo</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="Flow_1le9mul" sourceRef="Task_0b2oy6g" targetRef="Gateway_05hr90o" />
<bpmn2:sequenceFlow id="Flow_16asn4s" name="借阅" sourceRef="Gateway_05hr90o" targetRef="Task_0et8d23">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImVxIiwiZmllbGQiOiJzZWxlY3RfMTc1NjQyODcxOTk5OV83OTc0NDgiLCJmaWVsZFR5cGUiOiJzZWxlY3QiLCJmaWVsZE5hbWUiOiLnsbvlnosiLCJleHBlY3RlZFZhbHVlIjoi5YCf6ZiFIn1dfV0=', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0sof4uo" name="查阅" sourceRef="Gateway_05hr90o" targetRef="Task_0kjzelu">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImVxIiwiZmllbGQiOiJzZWxlY3RfMTc1NjQyODcxOTk5OV83OTc0NDgiLCJmaWVsZFR5cGUiOiJzZWxlY3QiLCJmaWVsZE5hbWUiOiLnsbvlnosiLCJleHBlY3RlZFZhbHVlIjoi5p+l6ZiFIn1dfV0=', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:exclusiveGateway id="Gateway_04s5k5c" name="部门判断">
<bpmn2:incoming>Flow_00sne96</bpmn2:incoming>
<bpmn2:outgoing>Flow_1442ka9</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0vc1pbk</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="Flow_1442ka9" name="等于综合管理部" sourceRef="Gateway_04s5k5c" targetRef="Task_0u5r15n">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImVxIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjE5NjA5NjI4NDQ5MzIwODM3MTMifV19XQ==', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0vc1pbk" name="不等于综合管理部" sourceRef="Gateway_04s5k5c" targetRef="Task_0s10sxf">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6Im5lIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjIwMDQwNzQ4MTg0NTY3MTExNzAifV19XQ==', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="process_1770171465054">
<bpmndi:BPMNEdge id="Flow_0vc1pbk_di" bpmnElement="Flow_0vc1pbk">
<di:waypoint x="440" y="205" />
<di:waypoint x="440" y="160" />
<di:waypoint x="720" y="160" />
<di:waypoint x="720" y="280" />
<bpmndi:BPMNLabel>
<dc:Bounds x="536" y="142" width="88" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1442ka9_di" bpmnElement="Flow_1442ka9">
<di:waypoint x="465" y="230" />
<di:waypoint x="580" y="230" />
<di:waypoint x="580" y="280" />
<bpmndi:BPMNLabel>
<dc:Bounds x="484" y="212" width="77" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0sof4uo_di" bpmnElement="Flow_0sof4uo">
<di:waypoint x="810" y="445" />
<di:waypoint x="810" y="350" />
<di:waypoint x="930" y="350" />
<bpmndi:BPMNLabel>
<dc:Bounds x="819" y="393" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_16asn4s_di" bpmnElement="Flow_16asn4s">
<di:waypoint x="835" y="470" />
<di:waypoint x="930" y="470" />
<bpmndi:BPMNLabel>
<dc:Bounds x="869" y="443" width="21" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1le9mul_di" bpmnElement="Flow_1le9mul">
<di:waypoint x="750" y="470" />
<di:waypoint x="785" y="470" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1oy9cwp_di" bpmnElement="Flow_1oy9cwp">
<di:waypoint x="30" y="230" />
<di:waypoint x="75" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1lfex13_di" bpmnElement="Flow_1lfex13">
<di:waypoint x="-20" y="312" />
<di:waypoint x="-20" y="270" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0pe9z7z_di" bpmnElement="Flow_0pe9z7z">
<di:waypoint x="1030" y="230" />
<di:waypoint x="1062" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0uf4o1g_di" bpmnElement="Flow_0uf4o1g">
<di:waypoint x="980" y="310" />
<di:waypoint x="980" y="270" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_09oe88o_di" bpmnElement="Flow_09oe88o">
<di:waypoint x="980" y="430" />
<di:waypoint x="980" y="390" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0txpi3x_di" bpmnElement="Flow_0txpi3x">
<di:waypoint x="460" y="470" />
<di:waypoint x="650" y="470" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1hz4oth_di" bpmnElement="Flow_1hz4oth">
<di:waypoint x="580" y="360" />
<di:waypoint x="580" y="400" />
<di:waypoint x="430" y="400" />
<di:waypoint x="430" y="430" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1oazprh_di" bpmnElement="Flow_1oazprh">
<di:waypoint x="720" y="360" />
<di:waypoint x="720" y="400" />
<di:waypoint x="430" y="400" />
<di:waypoint x="430" y="430" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_00sne96_di" bpmnElement="Flow_00sne96">
<di:waypoint x="350" y="230" />
<di:waypoint x="415" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1p7q68w_di" bpmnElement="Flow_1p7q68w">
<di:waypoint x="100" y="255" />
<di:waypoint x="100" y="470" />
<di:waypoint x="360" y="470" />
<bpmndi:BPMNLabel>
<dc:Bounds x="207" y="443" width="77" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ukgpce_di" bpmnElement="Flow_1ukgpce">
<di:waypoint x="125" y="230" />
<di:waypoint x="250" y="230" />
<bpmndi:BPMNLabel>
<dc:Bounds x="156" y="212" width="66" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_01ydzqe_di" bpmnElement="start">
<dc:Bounds x="-38" y="312" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="-31" y="355" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0iodhu1_di" bpmnElement="Gateway_0iodhu1" isMarkerVisible="true">
<dc:Bounds x="75" y="205" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="62" y="181" width="77" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1axmobm_di" bpmnElement="Task_1axmobm">
<dc:Bounds x="250" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0q5x40k_di" bpmnElement="Task_0q5x40k">
<dc:Bounds x="360" y="430" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0u5r15n_di" bpmnElement="Task_0u5r15n">
<dc:Bounds x="530" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0s10sxf_di" bpmnElement="Task_0s10sxf">
<dc:Bounds x="670" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0b2oy6g_di" bpmnElement="Task_0b2oy6g">
<dc:Bounds x="650" y="430" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0et8d23_di" bpmnElement="Task_0et8d23">
<dc:Bounds x="930" y="430" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0kjzelu_di" bpmnElement="Task_0kjzelu">
<dc:Bounds x="930" y="310" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_01bwpxc_di" bpmnElement="Task_01bwpxc">
<dc:Bounds x="930" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1l91ykf_di" bpmnElement="End_1l91ykf">
<dc:Bounds x="1062" y="212" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1bvodcd_di" bpmnElement="Task_1bvodcd">
<dc:Bounds x="-70" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_05hr90o_di" bpmnElement="Gateway_05hr90o" isMarkerVisible="true">
<dc:Bounds x="785" y="445" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_04s5k5c_di" bpmnElement="Gateway_04s5k5c" isMarkerVisible="true">
<dc:Bounds x="415" y="205" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="418" y="181" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@@ -0,0 +1,299 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:flowable="http://flowable.org/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="process_1757312337590_Copy1770686679981" name="用章申请建设(测试)">
<bpmn2:documentation>辛圣-494</bpmn2:documentation>
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId">
<bpmn2:outgoing>Flow_0sye7h6</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:userTask id="Task_076yim1" name="财务部部长" flowable:candidateGroups="1960962847985537025" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:true,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0zje6mx</bpmn2:incoming>
<bpmn2:outgoing>Flow_0t1dicm</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0q5k3ss" name="财务部分管领导" flowable:candidateGroups="1960962848656625665" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0lcmkhr" name="总经理1" flowable:candidateGroups="1960962848568545281" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1taf8qs</bpmn2:incoming>
<bpmn2:outgoing>Flow_1a6f0q0</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0c54gz0" name="董事长" flowable:candidateGroups="1960962848744706050" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1gr9954</bpmn2:incoming>
<bpmn2:outgoing>Flow_0pv1dn1</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0q2j4cs" name="财务管理中心负责人" flowable:candidateGroups="1961318320341422081" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:outgoing>Flow_0m6zmyi</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_1p5kogt" name="控股财务分管领导" flowable:candidateGroups="1960962855795331073" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0m6zmyi</bpmn2:incoming>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0m6zmyi" sourceRef="Task_0q2j4cs" targetRef="Task_1p5kogt" />
<bpmn2:userTask id="Task_1o5iu31" name="通知拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1nj1flm</bpmn2:incoming>
<bpmn2:incoming>Flow_0pv1dn1</bpmn2:incoming>
<bpmn2:incoming>Flow_1a6f0q0</bpmn2:incoming>
<bpmn2:outgoing>Flow_1avwi93</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:endEvent id="End_0038yc9">
<bpmn2:incoming>Flow_1avwi93</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="Flow_1avwi93" sourceRef="Task_1o5iu31" targetRef="End_0038yc9" />
<bpmn2:userTask id="Task_10tyj1y" name="拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="9c3064baa7074eab62e3c5b3b5458691" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0sye7h6</bpmn2:incoming>
<bpmn2:outgoing>Flow_14r6jea</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0sye7h6" sourceRef="start" targetRef="Task_10tyj1y" />
<bpmn2:userTask id="Task_09us30l" name="部门负责人" />
<bpmn2:userTask id="Task_1g0ku03" name="分管领导1">
<bpmn2:incoming>Flow_1cuat3d</bpmn2:incoming>
<bpmn2:outgoing>Flow_1taf8qs</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:exclusiveGateway id="Gateway_0re23c5" name="是否需要法律顾问">
<bpmn2:incoming>Flow_14r6jea</bpmn2:incoming>
<bpmn2:outgoing>Flow_1dmn5sk</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0ertfyv</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="Flow_14r6jea" sourceRef="Task_10tyj1y" targetRef="Gateway_0re23c5" />
<bpmn2:userTask id="Task_1k7tqhu" name="法律顾问">
<bpmn2:incoming>Flow_1dmn5sk</bpmn2:incoming>
<bpmn2:outgoing>Flow_1fejo5s</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1dmn5sk" name="是" sourceRef="Gateway_0re23c5" targetRef="Task_1k7tqhu" />
<bpmn2:exclusiveGateway id="Gateway_04mzo8k" name="用章类型">
<bpmn2:incoming>Flow_04klp00</bpmn2:incoming>
<bpmn2:outgoing>Flow_1cuat3d</bpmn2:outgoing>
<bpmn2:outgoing>Flow_16xz1k0</bpmn2:outgoing>
<bpmn2:outgoing>Flow_1nj1flm</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0zje6mx</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:userTask id="Task_00fvrl8" name="部长">
<bpmn2:incoming>Flow_0ertfyv</bpmn2:incoming>
<bpmn2:incoming>Flow_1fejo5s</bpmn2:incoming>
<bpmn2:outgoing>Flow_04klp00</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0ertfyv" sourceRef="Gateway_0re23c5" targetRef="Task_00fvrl8" />
<bpmn2:sequenceFlow id="Flow_1fejo5s" sourceRef="Task_1k7tqhu" targetRef="Task_00fvrl8" />
<bpmn2:sequenceFlow id="Flow_04klp00" sourceRef="Task_00fvrl8" targetRef="Gateway_04mzo8k" />
<bpmn2:sequenceFlow id="Flow_1cuat3d" name="公章" sourceRef="Gateway_04mzo8k" targetRef="Task_1g0ku03" />
<bpmn2:sequenceFlow id="Flow_16xz1k0" name="法人章" sourceRef="Gateway_04mzo8k" targetRef="Task_1t7oi9c" />
<bpmn2:sequenceFlow id="Flow_1nj1flm" name="部门章" sourceRef="Gateway_04mzo8k" targetRef="Task_1o5iu31" />
<bpmn2:sequenceFlow id="Flow_0zje6mx" name="财务章" sourceRef="Gateway_04mzo8k" targetRef="Task_076yim1" />
<bpmn2:sequenceFlow id="Flow_1taf8qs" sourceRef="Task_1g0ku03" targetRef="Task_0lcmkhr" />
<bpmn2:userTask id="Task_1t7oi9c" name="分管领导2">
<bpmn2:incoming>Flow_16xz1k0</bpmn2:incoming>
<bpmn2:outgoing>Flow_1jbmurf</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_1rfaocq" name="总经理2">
<bpmn2:incoming>Flow_1jbmurf</bpmn2:incoming>
<bpmn2:outgoing>Flow_1gr9954</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1jbmurf" sourceRef="Task_1t7oi9c" targetRef="Task_1rfaocq" />
<bpmn2:userTask id="Task_15d69it" name="财务部分管领导">
<bpmn2:incoming>Flow_0t1dicm</bpmn2:incoming>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0t1dicm" sourceRef="Task_076yim1" targetRef="Task_15d69it" />
<bpmn2:sequenceFlow id="Flow_1gr9954" sourceRef="Task_1rfaocq" targetRef="Task_0c54gz0" />
<bpmn2:sequenceFlow id="Flow_0pv1dn1" sourceRef="Task_0c54gz0" targetRef="Task_1o5iu31" />
<bpmn2:sequenceFlow id="Flow_1a6f0q0" sourceRef="Task_0lcmkhr" targetRef="Task_1o5iu31" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="process_1757312337590_Copy1770686679981">
<bpmndi:BPMNEdge id="Flow_1a6f0q0_di" bpmnElement="Flow_1a6f0q0">
<di:waypoint x="730" y="50" />
<di:waypoint x="980" y="50" />
<di:waypoint x="980" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0pv1dn1_di" bpmnElement="Flow_0pv1dn1">
<di:waypoint x="890" y="160" />
<di:waypoint x="930" y="160" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1gr9954_di" bpmnElement="Flow_1gr9954">
<di:waypoint x="730" y="160" />
<di:waypoint x="790" y="160" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0t1dicm_di" bpmnElement="Flow_0t1dicm">
<di:waypoint x="570" y="280" />
<di:waypoint x="630" y="280" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1jbmurf_di" bpmnElement="Flow_1jbmurf">
<di:waypoint x="570" y="160" />
<di:waypoint x="630" y="160" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1taf8qs_di" bpmnElement="Flow_1taf8qs">
<di:waypoint x="570" y="50" />
<di:waypoint x="630" y="50" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zje6mx_di" bpmnElement="Flow_0zje6mx">
<di:waypoint x="100" y="255" />
<di:waypoint x="100" y="280" />
<di:waypoint x="470" y="280" />
<bpmndi:BPMNLabel>
<dc:Bounds x="292" y="265" width="33" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1nj1flm_di" bpmnElement="Flow_1nj1flm">
<di:waypoint x="100" y="255" />
<di:waypoint x="100" y="350" />
<di:waypoint x="980" y="350" />
<di:waypoint x="980" y="200" />
<bpmndi:BPMNLabel>
<dc:Bounds x="127" y="332" width="33" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_16xz1k0_di" bpmnElement="Flow_16xz1k0">
<di:waypoint x="100" y="205" />
<di:waypoint x="100" y="160" />
<di:waypoint x="470" y="160" />
<bpmndi:BPMNLabel>
<dc:Bounds x="239" y="143" width="33" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1cuat3d_di" bpmnElement="Flow_1cuat3d">
<di:waypoint x="100" y="205" />
<di:waypoint x="100" y="50" />
<di:waypoint x="470" y="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="248" y="33" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_04klp00_di" bpmnElement="Flow_04klp00">
<di:waypoint x="40" y="230" />
<di:waypoint x="75" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1fejo5s_di" bpmnElement="Flow_1fejo5s">
<di:waypoint x="-30" y="130" />
<di:waypoint x="-10" y="130" />
<di:waypoint x="-10" y="190" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0ertfyv_di" bpmnElement="Flow_0ertfyv">
<di:waypoint x="-135" y="230" />
<di:waypoint x="-60" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1dmn5sk_di" bpmnElement="Flow_1dmn5sk">
<di:waypoint x="-160" y="205" />
<di:waypoint x="-160" y="130" />
<di:waypoint x="-130" y="130" />
<bpmndi:BPMNLabel>
<dc:Bounds x="-150" y="163" width="11" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_14r6jea_di" bpmnElement="Flow_14r6jea">
<di:waypoint x="-230" y="230" />
<di:waypoint x="-185" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0sye7h6_di" bpmnElement="Flow_0sye7h6">
<di:waypoint x="-392" y="230" />
<di:waypoint x="-330" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1avwi93_di" bpmnElement="Flow_1avwi93">
<di:waypoint x="1030" y="160" />
<di:waypoint x="1072" y="160" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0m6zmyi_di" bpmnElement="Flow_0m6zmyi">
<di:waypoint x="940" y="770" />
<di:waypoint x="1000" y="770" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_01ydzqe_di" bpmnElement="start">
<dc:Bounds x="-428" y="212" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="-421" y="255" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_076yim1_di" bpmnElement="Task_076yim1">
<dc:Bounds x="470" y="240" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0q5k3ss_di" bpmnElement="Task_0q5k3ss">
<dc:Bounds x="570" y="730" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0lcmkhr_di" bpmnElement="Task_0lcmkhr">
<dc:Bounds x="630" y="10" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0c54gz0_di" bpmnElement="Task_0c54gz0">
<dc:Bounds x="790" y="120" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0q2j4cs_di" bpmnElement="Task_0q2j4cs">
<dc:Bounds x="840" y="730" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1p5kogt_di" bpmnElement="Task_1p5kogt">
<dc:Bounds x="1000" y="730" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1o5iu31_di" bpmnElement="Task_1o5iu31">
<dc:Bounds x="930" y="120" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0038yc9_di" bpmnElement="End_0038yc9">
<dc:Bounds x="1072" y="142" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_10tyj1y_di" bpmnElement="Task_10tyj1y">
<dc:Bounds x="-330" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_09us30l_di" bpmnElement="Task_09us30l">
<dc:Bounds x="710" y="730" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1g0ku03_di" bpmnElement="Task_1g0ku03">
<dc:Bounds x="470" y="10" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0re23c5_di" bpmnElement="Gateway_0re23c5" isMarkerVisible="true">
<dc:Bounds x="-185" y="205" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="-154" y="193" width="88" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1k7tqhu_di" bpmnElement="Task_1k7tqhu">
<dc:Bounds x="-130" y="90" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_04mzo8k_di" bpmnElement="Gateway_04mzo8k" isMarkerVisible="true">
<dc:Bounds x="75" y="205" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="108" y="193" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_00fvrl8_di" bpmnElement="Task_00fvrl8">
<dc:Bounds x="-60" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1t7oi9c_di" bpmnElement="Task_1t7oi9c">
<dc:Bounds x="470" y="120" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1rfaocq_di" bpmnElement="Task_1rfaocq">
<dc:Bounds x="630" y="120" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_15d69it_di" bpmnElement="Task_15d69it">
<dc:Bounds x="630" y="240" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:flowable="http://flowable.org/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="process_1753949287150" name="督办流程">
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId">
<bpmn2:outgoing>Flow_1c1lhf3</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:userTask id="Task_1bhxpt0" name="发起人填写" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskUpdateFormDataListener" event="complete" id="1491326598389403649" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1c1lhf3</bpmn2:incoming>
<bpmn2:outgoing>Flow_0c2l5vy</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1c1lhf3" sourceRef="start" targetRef="Task_1bhxpt0" />
<bpmn2:userTask id="Task_0ky3mjv" name="合规管理员审核" flowable:assignee="${assigneeUserId}" flowable:countersignRule="countersign_custom">
<bpmn2:extensionElements>
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskUpdateFormDataListener" event="complete" id="1491326598389403649" />
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0c2l5vy</bpmn2:incoming>
<bpmn2:outgoing>Flow_03py5rl</bpmn2:outgoing>
<bpmn2:multiInstanceLoopCharacteristics flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}" flowable:elementVariable="assigneeUserId">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">${nrOfCompletedInstances/nrOfInstances&gt;=1}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0c2l5vy" sourceRef="Task_1bhxpt0" targetRef="Task_0ky3mjv" />
<bpmn2:userTask id="Task_0stqdxz" name="部门负责人审核" flowable:candidateUsers="${oaFlowExpression.getApplyUserDeptPositionLevel(sys_org_code, applyUserId, &#39;1958470912214368258&#39;)}" flowable:groupType="position">
<bpmn2:extensionElements>
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskUpdateFormDataListener" event="complete" id="1491326598389403649" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_03py5rl</bpmn2:incoming>
<bpmn2:outgoing>Flow_1sznlmk</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_03py5rl" sourceRef="Task_0ky3mjv" targetRef="Task_0stqdxz" />
<bpmn2:userTask id="Task_0cev5o9" name="风控审计负责" flowable:assignee="admin">
<bpmn2:incoming>Flow_0jclra9</bpmn2:incoming>
<bpmn2:outgoing>Flow_08t2045</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:endEvent id="End_14rgup0">
<bpmn2:incoming>Flow_0wvfsc5</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="Flow_1sznlmk" sourceRef="Task_0stqdxz" targetRef="Gateway_1pymmuq" />
<bpmn2:sequenceFlow id="Flow_0jclra9" name="有风险" sourceRef="Gateway_1pymmuq" targetRef="Task_0cev5o9">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${iz_danger== '1' }</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0xohdkk" name="无风险" sourceRef="Gateway_1pymmuq" targetRef="Gateway_1yzffyx">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${iz_danger=='0'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="Task_0xphdf0" name="部门分管领导" flowable:assignee="admin">
<bpmn2:incoming>Flow_0iktgdi</bpmn2:incoming>
<bpmn2:outgoing>Flow_00dut1a</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:inclusiveGateway id="Gateway_1pymmuq">
<bpmn2:incoming>Flow_1sznlmk</bpmn2:incoming>
<bpmn2:outgoing>Flow_0jclra9</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0xohdkk</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0iktgdi</bpmn2:outgoing>
</bpmn2:inclusiveGateway>
<bpmn2:sequenceFlow id="Flow_0iktgdi" name="有风险" sourceRef="Gateway_1pymmuq" targetRef="Task_0xphdf0">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${iz_danger== '1' }</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_08t2045" sourceRef="Task_0cev5o9" targetRef="Gateway_1yzffyx" />
<bpmn2:inclusiveGateway id="Gateway_1yzffyx">
<bpmn2:incoming>Flow_08t2045</bpmn2:incoming>
<bpmn2:incoming>Flow_00dut1a</bpmn2:incoming>
<bpmn2:incoming>Flow_0xohdkk</bpmn2:incoming>
<bpmn2:outgoing>Flow_0wvfsc5</bpmn2:outgoing>
</bpmn2:inclusiveGateway>
<bpmn2:sequenceFlow id="Flow_00dut1a" sourceRef="Task_0xphdf0" targetRef="Gateway_1yzffyx" />
<bpmn2:sequenceFlow id="Flow_0wvfsc5" sourceRef="Gateway_1yzffyx" targetRef="End_14rgup0" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="process_1753949287150">
<bpmndi:BPMNEdge id="Flow_0wvfsc5_di" bpmnElement="Flow_0wvfsc5">
<di:waypoint x="1175" y="233" />
<di:waypoint x="1312" y="233" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_00dut1a_di" bpmnElement="Flow_00dut1a">
<di:waypoint x="1020" y="233" />
<di:waypoint x="1125" y="233" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_08t2045_di" bpmnElement="Flow_08t2045">
<di:waypoint x="1020" y="130" />
<di:waypoint x="1150" y="130" />
<di:waypoint x="1150" y="208" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0iktgdi_di" bpmnElement="Flow_0iktgdi">
<di:waypoint x="765" y="230" />
<di:waypoint x="920" y="230" />
<bpmndi:BPMNLabel>
<dc:Bounds x="826" y="212" width="34" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0xohdkk_di" bpmnElement="Flow_0xohdkk">
<di:waypoint x="740" y="255" />
<di:waypoint x="740" y="370" />
<di:waypoint x="1150" y="370" />
<di:waypoint x="1150" y="263" />
<bpmndi:BPMNLabel>
<dc:Bounds x="766" y="383" width="34" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0jclra9_di" bpmnElement="Flow_0jclra9">
<di:waypoint x="740" y="205" />
<di:waypoint x="740" y="130" />
<di:waypoint x="920" y="130" />
<bpmndi:BPMNLabel>
<dc:Bounds x="738" y="165" width="34" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1sznlmk_di" bpmnElement="Flow_1sznlmk">
<di:waypoint x="650" y="230" />
<di:waypoint x="715" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_03py5rl_di" bpmnElement="Flow_03py5rl">
<di:waypoint x="490" y="230" />
<di:waypoint x="550" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0c2l5vy_di" bpmnElement="Flow_0c2l5vy">
<di:waypoint x="330" y="230" />
<di:waypoint x="390" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1c1lhf3_di" bpmnElement="Flow_1c1lhf3">
<di:waypoint x="178" y="230" />
<di:waypoint x="230" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_01ydzqe_di" bpmnElement="start">
<dc:Bounds x="142" y="212" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="149" y="255" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1bhxpt0_di" bpmnElement="Task_1bhxpt0">
<dc:Bounds x="230" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0ky3mjv_di" bpmnElement="Task_0ky3mjv">
<dc:Bounds x="390" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0stqdxz_di" bpmnElement="Task_0stqdxz">
<dc:Bounds x="550" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0cev5o9_di" bpmnElement="Task_0cev5o9">
<dc:Bounds x="920" y="90" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_14rgup0_di" bpmnElement="End_14rgup0">
<dc:Bounds x="1312" y="215" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0xphdf0_di" bpmnElement="Task_0xphdf0">
<dc:Bounds x="920" y="195" width="100" height="75" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_00i8d2v_di" bpmnElement="Gateway_1pymmuq">
<dc:Bounds x="715" y="205" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1dcidyu_di" bpmnElement="Gateway_1yzffyx">
<dc:Bounds x="1125" y="208" width="50" height="50" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="http://www.jeecg.org">
<process id="joa_leave_cp1670333" name="示例测排他网关">
<documentation>流程描述</documentation>
<extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" id="402880e54803a496014805e5d9190012" />
</extensionElements>
<startEvent id="start1" name="开始节点" flowable:initiator="applyUserId" />
<endEvent id="end" name="结束节点" />
<userTask id="task1554878137217" name="部门领导审批" flowable:assignee="admin" />
<exclusiveGateway id="exclusiveGateway1554878161805">
<outgoing>Flow_163pv7t</outgoing>
</exclusiveGateway>
<userTask id="task1554878168361" name="总经理审批" flowable:assignee="admin">
<incoming>Flow_1o8rizw</incoming>
</userTask>
<userTask id="task1554878176409" name="行政审批" flowable:assignee="admin" />
<sequenceFlow id="flow1554878182573" name="大于三天" sourceRef="exclusiveGateway1554878161805" targetRef="task1554878168361">
<conditionExpression xsi:type="tFormalExpression">${total&gt;3}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow1554878185509" name="小于等于三天" sourceRef="exclusiveGateway1554878161805" targetRef="task1554878176409">
<conditionExpression xsi:type="tFormalExpression">${total&lt;=3}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow1554878187307" name="" sourceRef="task1554878137217" targetRef="exclusiveGateway1554878161805" />
<sequenceFlow id="flow1554878190715" name="" sourceRef="start1" targetRef="task1554878137217" />
<sequenceFlow id="flow1554878266602" name="" sourceRef="task1554878168361" targetRef="task1554878176409" />
<sequenceFlow id="flow1554878268869" name="" sourceRef="task1554878176409" targetRef="end" />
<userTask id="Task_0kktl0p" name="3天审批" flowable:assignee="admin">
<incoming>Flow_163pv7t</incoming>
<outgoing>Flow_1o8rizw</outgoing>
</userTask>
<sequenceFlow id="Flow_163pv7t" sourceRef="exclusiveGateway1554878161805" targetRef="Task_0kktl0p">
<conditionExpression xsi:type="tFormalExpression">${total==3}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="Flow_1o8rizw" sourceRef="Task_0kktl0p" targetRef="task1554878168361" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_joa_leave_Copy1670333057204">
<bpmndi:BPMNPlane id="BPMNPlane_joa_leave_Copy1670333057204" bpmnElement="joa_leave_cp1670333">
<bpmndi:BPMNEdge id="Flow_1o8rizw_di" bpmnElement="Flow_1o8rizw">
<omgdi:waypoint x="557" y="100" />
<omgdi:waypoint x="557" y="136" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_163pv7t_di" bpmnElement="Flow_163pv7t">
<omgdi:waypoint x="390" y="208" />
<omgdi:waypoint x="390" y="60" />
<omgdi:waypoint x="507" y="60" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554878268869" bpmnElement="flow1554878268869">
<omgdi:waypoint x="557" y="370" />
<omgdi:waypoint x="557" y="455" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554878266602" bpmnElement="flow1554878266602">
<omgdi:waypoint x="557" y="213" />
<omgdi:waypoint x="557" y="285" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554878190715" bpmnElement="flow1554878190715">
<omgdi:waypoint x="124" y="228" />
<omgdi:waypoint x="214" y="228" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554878187307" bpmnElement="flow1554878187307">
<omgdi:waypoint x="310" y="228" />
<omgdi:waypoint x="370" y="228" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554878185509" bpmnElement="flow1554878185509">
<omgdi:waypoint x="390" y="248" />
<omgdi:waypoint x="390" y="312" />
<omgdi:waypoint x="512" y="312" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="375" y="271" width="67" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1554878182573" bpmnElement="flow1554878182573">
<omgdi:waypoint x="390" y="208" />
<omgdi:waypoint x="390" y="163" />
<omgdi:waypoint x="512" y="163" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="383" y="176" width="45" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_start1" bpmnElement="start1">
<omgdc:Bounds x="94" y="213" width="30" height="30" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="87" y="243" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_end" bpmnElement="end">
<omgdc:Bounds x="542" y="455" width="30" height="30" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="535" y="485" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554878137217" bpmnElement="task1554878137217">
<omgdc:Bounds x="214" y="201" width="96" height="84" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_exclusiveGateway1554878161805" bpmnElement="exclusiveGateway1554878161805" isMarkerVisible="true">
<omgdc:Bounds x="370" y="208" width="40" height="40" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554878168361" bpmnElement="task1554878168361">
<omgdc:Bounds x="512" y="136" width="90" height="77" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1554878176409" bpmnElement="task1554878176409">
<omgdc:Bounds x="512" y="285" width="90" height="85" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0kktl0p_di" bpmnElement="Task_0kktl0p">
<omgdc:Bounds x="507" y="20" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

View File

@@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:flowable="http://flowable.org/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="process_1770095700158" name="车辆维修保养审批单(津保建工)">
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId">
<bpmn2:outgoing>Flow_1jz2g6e</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:userTask id="Task_1ci2ng6" name="拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:true,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="9c3064baa7074eab62e3c5b3b5458691" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1jz2g6e</bpmn2:incoming>
<bpmn2:outgoing>Flow_1hfu0pz</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1jz2g6e" sourceRef="start" targetRef="Task_1ci2ng6" />
<bpmn2:userTask id="Task_0ezmxip" name="车辆管理员" flowable:candidateUsers="${flowUtil.getUsersByApprRole(execution,&#39;2028664690455707650&#39;)}" flowable:groupType="approvalRole">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1hfu0pz</bpmn2:incoming>
<bpmn2:outgoing>Flow_02kwl5l</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1hfu0pz" sourceRef="Task_1ci2ng6" targetRef="Task_0ezmxip" />
<bpmn2:userTask id="Task_14tej44" name="综合管理部负责人" flowable:candidateGroups="2004446846302089217" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_02kwl5l</bpmn2:incoming>
<bpmn2:outgoing>Flow_1aquqfe</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_02kwl5l" sourceRef="Task_0ezmxip" targetRef="Task_14tej44" />
<bpmn2:exclusiveGateway id="Gateway_0gk94sl">
<bpmn2:incoming>Flow_1aquqfe</bpmn2:incoming>
<bpmn2:outgoing>Flow_16ojsk8</bpmn2:outgoing>
<bpmn2:outgoing>Flow_09uj66v</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="Flow_1aquqfe" sourceRef="Task_14tej44" targetRef="Gateway_0gk94sl" />
<bpmn2:userTask id="Task_07ew4xw" name="其他部门分管领导" flowable:candidateUsers="${flowUtil.getUsersByApprRole(execution,&#39;2004720656484859905&#39;)}" flowable:groupType="approvalRole">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_16ojsk8</bpmn2:incoming>
<bpmn2:outgoing>Flow_0k7nc33</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_16ojsk8" name="其他部门" sourceRef="Gateway_0gk94sl" targetRef="Task_07ew4xw">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6Im5lIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjIwMDQwNzQ4MTg0NTY3MTExNzAifV19XQ==', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="Task_0n91lwg" name="综合管理部分管领导" flowable:candidateGroups="2004447105202712577" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_09uj66v</bpmn2:incoming>
<bpmn2:outgoing>Flow_0266loo</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_09uj66v" name="综合管理部" sourceRef="Gateway_0gk94sl" targetRef="Task_0n91lwg">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImVxIiwiZmllbGQiOiJhcHBseVVzZXJEZXB0IiwiZXhwZWN0ZWRWYWx1ZSI6IjIwMDQwNzQ4MTg0NTY3MTExNzAifV19XQ==', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="Task_0lyoju5" name="通知拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:false,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0k7nc33</bpmn2:incoming>
<bpmn2:incoming>Flow_0266loo</bpmn2:incoming>
<bpmn2:outgoing>Flow_0zaaewh</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0k7nc33" sourceRef="Task_07ew4xw" targetRef="Task_0lyoju5" />
<bpmn2:userTask id="Task_0wy6ktd" name="归档" flowable:candidateUsers="${flowUtil.getUsersByApprRole(execution,&#39;2026853145785352193&#39;)}" flowable:groupType="approvalRole">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0zaaewh</bpmn2:incoming>
<bpmn2:outgoing>Flow_0rk36g9</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0zaaewh" sourceRef="Task_0lyoju5" targetRef="Task_0wy6ktd" />
<bpmn2:endEvent id="End_03g9hew">
<bpmn2:incoming>Flow_0rk36g9</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="Flow_0rk36g9" sourceRef="Task_0wy6ktd" targetRef="End_03g9hew" />
<bpmn2:sequenceFlow id="Flow_0266loo" sourceRef="Task_0n91lwg" targetRef="Task_0lyoju5" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="process_1770095700158">
<bpmndi:BPMNEdge id="Flow_0266loo_di" bpmnElement="Flow_0266loo">
<di:waypoint x="680" y="350" />
<di:waypoint x="760" y="350" />
<di:waypoint x="760" y="270" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0rk36g9_di" bpmnElement="Flow_0rk36g9">
<di:waypoint x="890" y="270" />
<di:waypoint x="890" y="322" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zaaewh_di" bpmnElement="Flow_0zaaewh">
<di:waypoint x="810" y="230" />
<di:waypoint x="840" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0k7nc33_di" bpmnElement="Flow_0k7nc33">
<di:waypoint x="680" y="230" />
<di:waypoint x="710" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_09uj66v_di" bpmnElement="Flow_09uj66v">
<di:waypoint x="510" y="255" />
<di:waypoint x="510" y="350" />
<di:waypoint x="580" y="350" />
<bpmndi:BPMNLabel>
<dc:Bounds x="512" y="333" width="55" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_16ojsk8_di" bpmnElement="Flow_16ojsk8">
<di:waypoint x="535" y="230" />
<di:waypoint x="580" y="230" />
<bpmndi:BPMNLabel>
<dc:Bounds x="528" y="212" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1aquqfe_di" bpmnElement="Flow_1aquqfe">
<di:waypoint x="460" y="230" />
<di:waypoint x="485" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_02kwl5l_di" bpmnElement="Flow_02kwl5l">
<di:waypoint x="410" y="310" />
<di:waypoint x="410" y="270" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1hfu0pz_di" bpmnElement="Flow_1hfu0pz">
<di:waypoint x="330" y="350" />
<di:waypoint x="360" y="350" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1jz2g6e_di" bpmnElement="Flow_1jz2g6e">
<di:waypoint x="280" y="248" />
<di:waypoint x="280" y="310" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_01ydzqe_di" bpmnElement="start">
<dc:Bounds x="262" y="212" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="269" y="188" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1ci2ng6_di" bpmnElement="Task_1ci2ng6">
<dc:Bounds x="230" y="310" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0ezmxip_di" bpmnElement="Task_0ezmxip">
<dc:Bounds x="360" y="310" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_14tej44_di" bpmnElement="Task_14tej44">
<dc:Bounds x="360" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0gk94sl_di" bpmnElement="Gateway_0gk94sl" isMarkerVisible="true">
<dc:Bounds x="485" y="205" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_07ew4xw_di" bpmnElement="Task_07ew4xw">
<dc:Bounds x="580" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0n91lwg_di" bpmnElement="Task_0n91lwg">
<dc:Bounds x="580" y="310" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0lyoju5_di" bpmnElement="Task_0lyoju5">
<dc:Bounds x="710" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0wy6ktd_di" bpmnElement="Task_0wy6ktd">
<dc:Bounds x="840" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_03g9hew_di" bpmnElement="End_03g9hew">
<dc:Bounds x="872" y="322" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:flowable="http://flowable.org/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="process_1773198067483" name="采购申请单(铁路)">
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId">
<bpmn2:outgoing>Flow_1hsrr7l</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:userTask id="Task_1t9i19k" name="拟稿人" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="9c3064baa7074eab62e3c5b3b5458691" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1hsrr7l</bpmn2:incoming>
<bpmn2:outgoing>Flow_1qti4ia</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1hsrr7l" sourceRef="start" targetRef="Task_1t9i19k" />
<bpmn2:userTask id="Task_1pb8vkv" name="部门负责人" flowable:candidateUsers="${flowNodeExecution.getDepartLeaders(execution)}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true,&#34;isSkipApprovedOnCountersignReturn&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1qti4ia</bpmn2:incoming>
<bpmn2:outgoing>Flow_03c2u7y</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1qti4ia" sourceRef="Task_1t9i19k" targetRef="Task_1pb8vkv" />
<bpmn2:userTask id="Task_0mh0bzt" name="关联部门负责人" flowable:candidateUsers="${flowUtil.getUsersByFormData(execution,&#39;select_user_1773283169997_557924&#39;,&#39;select-user&#39;)}" flowable:groupType="formData" flowable:countersignRule="countersign_custom">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true,&#34;isSkipApprovedOnCountersignReturn&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0achk6n</bpmn2:incoming>
<bpmn2:outgoing>Flow_0x92f56</bpmn2:outgoing>
<bpmn2:multiInstanceLoopCharacteristics flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}" flowable:elementVariable="assigneeUserId">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">${nrOfCompletedInstances/nrOfInstances&gt;=1}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0x7t6mg" name="申请部门分管领导" flowable:candidateUsers="${flowUtil.getUsersByApprRole(execution,&#39;1977974561067126786&#39;)}" flowable:groupType="approvalRole">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true,&#34;isSkipApprovedOnCountersignReturn&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1dxtd7r</bpmn2:incoming>
<bpmn2:outgoing>Flow_0ns8ob6</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_16dcemd" name="关联分管领导意见" flowable:candidateUsers="${flowUtil.getUsersByFormData(execution,&#39;select_user_1773283205332_942723&#39;,&#39;select-user&#39;)}" flowable:groupType="formData" flowable:countersignRule="countersign_custom">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true,&#34;isSkipApprovedOnCountersignReturn&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0x92f56</bpmn2:incoming>
<bpmn2:outgoing>Flow_1dxtd7r</bpmn2:outgoing>
<bpmn2:multiInstanceLoopCharacteristics flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}" flowable:elementVariable="assigneeUserId">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">${nrOfCompletedInstances/nrOfInstances&gt;=1}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
<bpmn2:userTask id="Task_0st5yv4" name="总经理" flowable:candidateGroups="1960962884220129282" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true,&#34;isSkipApprovedOnCountersignReturn&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0d5tzbd</bpmn2:incoming>
<bpmn2:incoming>Flow_0ns8ob6</bpmn2:incoming>
<bpmn2:outgoing>Flow_1qrb9ky</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="Task_1gymdtu" name="董事长" flowable:candidateGroups="1962716087018209281" flowable:groupType="deptPosition">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true,&#34;isSkipApprovedOnCountersignReturn&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1qrb9ky</bpmn2:incoming>
<bpmn2:outgoing>Flow_0effwrq</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1qrb9ky" sourceRef="Task_0st5yv4" targetRef="Task_1gymdtu" />
<bpmn2:userTask id="Task_0p2sdi0" name="归档" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:false,&#34;isSkipApprovedOnCountersignReturn&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0effwrq</bpmn2:incoming>
<bpmn2:outgoing>Flow_0zcndas</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0effwrq" sourceRef="Task_1gymdtu" targetRef="Task_0p2sdi0" />
<bpmn2:endEvent id="End_0myyr3w">
<bpmn2:incoming>Flow_0zcndas</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="Flow_0zcndas" sourceRef="Task_0p2sdi0" targetRef="End_0myyr3w" />
<bpmn2:exclusiveGateway id="Gateway_1068ktr">
<bpmn2:incoming>Flow_03c2u7y</bpmn2:incoming>
<bpmn2:outgoing>Flow_1h7xgjs</bpmn2:outgoing>
<bpmn2:outgoing>Flow_0achk6n</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="Flow_03c2u7y" sourceRef="Task_1pb8vkv" targetRef="Gateway_1068ktr" />
<bpmn2:userTask id="Task_0et9vrx" name="申请部门分管领导2" flowable:candidateUsers="${flowUtil.getUsersByApprRole(execution,&#39;1977974561067126786&#39;)}" flowable:groupType="approvalRole">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&#34;sameMode&#34;:0,&#34;isSkipAssigneeEmpty&#34;:false,&#34;isSkipAssigneeOnePersion&#34;:true,&#34;isSkipApproval&#34;:false,&#34;isAssignedByPreviousNode&#34;:false,&#34;isEmptyAssignedByPreviousNode&#34;:true,&#34;isSkipApprovedOnCountersignReturn&#34;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_1h7xgjs</bpmn2:incoming>
<bpmn2:outgoing>Flow_0d5tzbd</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_1h7xgjs" sourceRef="Gateway_1068ktr" targetRef="Task_0et9vrx">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImVxIiwiZmllbGQiOiJyYWRpb18xNzczMjgzMTI3NDYwXzUzMjgwMyIsImZpZWxkVHlwZSI6InJhZGlvIiwiZmllbGROYW1lIjoi5piv5ZCm6ZyA6KaB5YWz6IGU6YOo6Zeo6LSf6LSj5Lq677yM5Y+K5YWz6IGU6YOo6Zeo5YiG566h6aKG5a+8IiwiZXhwZWN0ZWRWYWx1ZSI6IuWQpiJ9XX1d', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0d5tzbd" sourceRef="Task_0et9vrx" targetRef="Task_0st5yv4" />
<bpmn2:sequenceFlow id="Flow_0achk6n" sourceRef="Gateway_1068ktr" targetRef="Task_0mh0bzt">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${flowUtil.evaluateExpression(execution, 'W3sibG9naWMiOiJhbmQiLCJjb25kaXRpb25zIjpbeyJvcGVyYXRvciI6ImVxIiwiZmllbGQiOiJyYWRpb18xNzczMjgzMTI3NDYwXzUzMjgwMyIsImZpZWxkVHlwZSI6InJhZGlvIiwiZmllbGROYW1lIjoi5piv5ZCm6ZyA6KaB5YWz6IGU6YOo6Zeo6LSf6LSj5Lq677yM5Y+K5YWz6IGU6YOo6Zeo5YiG566h6aKG5a+8IiwiZXhwZWN0ZWRWYWx1ZSI6IuaYryJ9XX1d', 'and')}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_0x92f56" sourceRef="Task_0mh0bzt" targetRef="Task_16dcemd" />
<bpmn2:sequenceFlow id="Flow_1dxtd7r" sourceRef="Task_16dcemd" targetRef="Task_0x7t6mg" />
<bpmn2:sequenceFlow id="Flow_0ns8ob6" sourceRef="Task_0x7t6mg" targetRef="Task_0st5yv4" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="process_1773198067483">
<bpmndi:BPMNEdge id="Flow_0ns8ob6_di" bpmnElement="Flow_0ns8ob6">
<di:waypoint x="980" y="100" />
<di:waypoint x="1085" y="100" />
<di:waypoint x="1085" y="230" />
<di:waypoint x="1190" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1dxtd7r_di" bpmnElement="Flow_1dxtd7r">
<di:waypoint x="790" y="100" />
<di:waypoint x="880" y="100" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0x92f56_di" bpmnElement="Flow_0x92f56">
<di:waypoint x="620" y="100" />
<di:waypoint x="690" y="100" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0achk6n_di" bpmnElement="Flow_0achk6n">
<di:waypoint x="420" y="205" />
<di:waypoint x="420" y="100" />
<di:waypoint x="520" y="100" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0d5tzbd_di" bpmnElement="Flow_0d5tzbd">
<di:waypoint x="610" y="230" />
<di:waypoint x="1190" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1h7xgjs_di" bpmnElement="Flow_1h7xgjs">
<di:waypoint x="445" y="230" />
<di:waypoint x="510" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_03c2u7y_di" bpmnElement="Flow_03c2u7y">
<di:waypoint x="350" y="230" />
<di:waypoint x="395" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zcndas_di" bpmnElement="Flow_0zcndas">
<di:waypoint x="1610" y="230" />
<di:waypoint x="1672" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0effwrq_di" bpmnElement="Flow_0effwrq">
<di:waypoint x="1450" y="230" />
<di:waypoint x="1510" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1qrb9ky_di" bpmnElement="Flow_1qrb9ky">
<di:waypoint x="1290" y="230" />
<di:waypoint x="1350" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1qti4ia_di" bpmnElement="Flow_1qti4ia">
<di:waypoint x="210" y="230" />
<di:waypoint x="250" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1hsrr7l_di" bpmnElement="Flow_1hsrr7l">
<di:waypoint x="58" y="230" />
<di:waypoint x="110" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_01ydzqe_di" bpmnElement="start">
<dc:Bounds x="22" y="212" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="29" y="255" width="23" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1t9i19k_di" bpmnElement="Task_1t9i19k">
<dc:Bounds x="110" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1pb8vkv_di" bpmnElement="Task_1pb8vkv">
<dc:Bounds x="250" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0mh0bzt_di" bpmnElement="Task_0mh0bzt">
<dc:Bounds x="520" y="60" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0x7t6mg_di" bpmnElement="Task_0x7t6mg">
<dc:Bounds x="880" y="60" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_16dcemd_di" bpmnElement="Task_16dcemd">
<dc:Bounds x="690" y="60" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0st5yv4_di" bpmnElement="Task_0st5yv4">
<dc:Bounds x="1190" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1gymdtu_di" bpmnElement="Task_1gymdtu">
<dc:Bounds x="1350" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0p2sdi0_di" bpmnElement="Task_0p2sdi0">
<dc:Bounds x="1510" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0myyr3w_di" bpmnElement="End_0myyr3w">
<dc:Bounds x="1672" y="212" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1068ktr_di" bpmnElement="Gateway_1068ktr" isMarkerVisible="true">
<dc:Bounds x="395" y="205" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0et9vrx_di" bpmnElement="Task_0et9vrx">
<dc:Bounds x="510" y="190" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="http://www.jeecg.org">
<process id="joa_onl_sxhuiq" name="顺序会签流程">
<documentation>流程描述</documentation>
<extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" id="402880e54803a496014805e5d9190012" />
</extensionElements>
<startEvent id="start1" name="开始节点" flowable:initiator="applyUserId" />
<endEvent id="end" name="结束节点" />
<userTask id="task1557560183848" name="选择会签人员" flowable:assignee="${applyUserId}" />
<userTask id="task1557560206142" name="会签审阅" flowable:assignee="${assigneeUserId}">
<multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}" flowable:elementVariable="assigneeUserId" />
</userTask>
<sequenceFlow id="flow1557560225476" name="" sourceRef="start1" targetRef="task1557560183848" />
<sequenceFlow id="flow1557560227264" name="" sourceRef="task1557560183848" targetRef="task1557560206142" />
<sequenceFlow id="flow1557560228916" name="" sourceRef="task1557560206142" targetRef="end" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_joa_onl_sxhuiq">
<bpmndi:BPMNPlane id="BPMNPlane_joa_onl_sxhuiq" bpmnElement="joa_onl_sxhuiq">
<bpmndi:BPMNEdge id="BPMNEdge_flow1557560228916" bpmnElement="flow1557560228916">
<omgdi:waypoint x="215" y="336" />
<omgdi:waypoint x="215" y="400" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1557560227264" bpmnElement="flow1557560227264">
<omgdi:waypoint x="215" y="203" />
<omgdi:waypoint x="215" y="271" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow1557560225476" bpmnElement="flow1557560225476">
<omgdi:waypoint x="215" y="80" />
<omgdi:waypoint x="215" y="138" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_start1" bpmnElement="start1">
<omgdc:Bounds x="200" y="50" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_end" bpmnElement="end">
<omgdc:Bounds x="200" y="400" width="30" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1557560183848" bpmnElement="task1557560183848">
<omgdc:Bounds x="170" y="143" width="90" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_task1557560206142" bpmnElement="task1557560206142">
<omgdc:Bounds x="170" y="276" width="90" height="55" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

View File

@@ -0,0 +1,76 @@
# JeecgBoot 代码生成 SkillClaude Code 专用)
将自然语言需求转换为 JeecgBoot 全套 CRUD 代码(后端 Java + 前端 Vue3 + 菜单权限 SQL
## 功能特性
- 单表 / 树表 / 一对多(主子表)三种模式
- 已有表反向生成(自动读取数据库 DDL
- 新建表智能推导(自然语言 → 字段类型 + 控件)
- 增量字段修改(加字段/删字段/改字段,无需重新生成)
- 字典智能匹配(自动关联系统 `sys_dict` 字典)
- Flyway 版本号自动递增
- 菜单权限 SQL 自动生成(真实时间戳 ID
## 安装方法
`SKILL.md``codegen-reference.md` 两个文件复制到 Claude Code 的 skills 目录:
```bash
# Windows
mkdir %USERPROFILE%\.claude\skills\jeecg-codegen
copy SKILL.md %USERPROFILE%\.claude\skills\jeecg-codegen\
copy codegen-reference.md %USERPROFILE%\.claude\skills\jeecg-codegen\
# macOS / Linux
mkdir -p ~/.claude/skills/jeecg-codegen
cp SKILL.md ~/.claude/skills/jeecg-codegen/
cp codegen-reference.md ~/.claude/skills/jeecg-codegen/
```
## 使用前配置
安装后需要根据实际项目修改 `SKILL.md` 中的以下配置:
| 配置项 | 位置 | 说明 |
|--------|------|------|
| 后端根路径 | "项目路径"章节 | 改为你的后端项目绝对路径 |
| 前端根路径 | "项目路径"章节 | 改为你的前端项目绝对路径 |
| 数据库连接 | "数据库连接"章节 | 改为你的 MySQL 地址/端口/用户名/密码/数据库名 |
| Flyway SQL 目录 | "Flyway 版本号规则"章节 | 如果目录不同需要调整 |
## 触发方式
在 Claude Code 中直接说以下关键词即可触发:
- `代码生成` / `生成代码` / `创建模块` / `新增功能` / `建表`
- `加字段` / `增加字段` / `新增字段` / `修改字段` / `删除字段`
- `generate code` / `new entity` / `add field`
## 使用示例
```
# 单表
生成一个商品管理模块,字段:商品名、价格、库存、状态、图片、描述
# 一对多
生成一个采购单模块,主表是采购单(单号、供应商、日期、总金额),子表是采购明细(商品名、数量、单价、小计)
# 已有表
给 biz_customer 表生成代码
# 增量修改
给采购单模块加一个"备注"字段
```
## 文件说明
| 文件 | 说明 |
|------|------|
| `SKILL.md` | Skill 入口,定义触发规则、交互流程、字段推导、字典匹配等 |
| `codegen-reference.md` | 完整代码模板骨架,包含 Entity/Controller/Service/Mapper/Vue3 等全部模板 |
## 适用版本
- JeecgBoot 3.xSpring Boot 3 + Jakarta + MyBatis-Plus
- Vue3 + TypeScript + Vite + Ant Design Vue 4

View File

@@ -0,0 +1,256 @@
---
name: jeecg-codegen
description: "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\""
---
# JeecgBoot 代码生成器
将自然语言需求转换为 JeecgBoot 全套 CRUD 代码(后端 Java + 前端 Vue3 + 菜单权限 SQL并支持对已生成模块的增量字段修改。
## 交互流程
### 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. **扫描已有代码文件**:在后端和前端目录中搜索已生成的文件
```bash
# 搜索后端 Entity 文件
find {后端项目根目录} -name "{EntityName}.java" -path "*/entity/*"
# 搜索前端 data.ts 文件
find {前端项目根目录}/src/views -name "{EntityName}.data.ts"
```
3. **读取全部已有文件**Entity.java、*.data.ts、*List.vue、*Modal.vue如有 Form.vue 也读取)
4. **解析当前字段列表**:从 Entity.java 解析已有字段
5. **推导新字段属性**:用"智能字段推导"规则推导 DB 类型、Java 类型、前端控件
6. **展示修改摘要**,等待用户确认后再修改
**增量修改的操作类型:**
- **加字段**:在所有文件中追加新字段定义
- **删字段**:从所有文件中移除指定字段定义
- **改字段**:修改指定字段的类型、控件、注释等
**判断表类型:**
- 提到"分类/层级/树/上下级" → **树表**
- 提到"主子表/明细/一对多/订单+商品" → **一对多**
- 默认 → **单表**
### 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 规则
**判断条件:** 数据库连接地址为 `127.0.0.1` 或 `localhost`(即本地开发环境)。
**自动执行方式:** 生成 Flyway SQL 文件后,同时通过 Bash 工具直接执行菜单权限 SQL
```bash
# 先检查菜单是否已存在,避免重复插入
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -e "SELECT id FROM sys_permission WHERE id='{timestamp}01'"
# 不存在则执行全部菜单 + 角色授权 SQL
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 < {flyway_sql_file_path}
```
**注意事项:**
- 仅在本地环境127.0.0.1/localhost自动执行远程环境只生成 Flyway 文件
- 执行前先检查主菜单 ID 是否已存在,避免重复插入
- 如果 MySQL 执行失败,提示用户手动执行 Flyway SQL不中断整体流程
- 输出结果中标注 `菜单 SQL已自动执行 ✓`
## 数据库连接
**已有表场景必须先查数据库!** 通过以下方式获取精确 DDL
```bash
# 读取项目数据库配置
# 配置文件: jeecg-boot-framework-2026/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
# 默认连接: mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3
# 查询表 DDL
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -e "SHOW CREATE TABLE 表名\G"
# 查询字段注释
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -e "SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT, COLUMN_KEY, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='jeecgboot3' AND TABLE_NAME='表名' ORDER BY ORDINAL_POSITION"
```
如果无法连接数据库,回退方案:在项目 SQL 文件中搜索表定义(`grep -r "CREATE TABLE.*表名"` 在 docs/db/ 目录下)。
## Flyway 版本号规则
**生成 Flyway SQL 前必须检查已有版本号,自动递增避免冲突!**
```bash
# Flyway SQL 目录
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 生成
**必须使用真实时间戳确保唯一性!** 通过以下命令获取:
```bash
date +%s%3N # 输出13位毫秒级时间戳如 1741704000123
```
用这个时间戳作为基础 ID依次拼接 01-14
- 主菜单: `{timestamp}01`
- 添加按钮: `{timestamp}02`
- 编辑按钮: `{timestamp}03`
- ... 以此类推
## 字典智能匹配
**用户选择"读取系统字典"后,执行以下查询获取全部可用字典:**
```bash
# 查询所有字典编码及其选项值
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -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` 使用 `JDictSelectTag``componentProps: { dictCode: 'matched_dict_code' }`
- data.ts searchFormSchema: 同样使用 `JDictSelectTag` 组件
**展示格式:** 在 Step 3 表结构摘要中,匹配到字典的字段标注字典编码和选项值,如:
```
| 字段名 | 类型 | 控件 | 字典 |
| status | varchar(10) | JDictSelectTag | order_status (待付款=0, 已付款=1, 已完成=2) |
```
## 项目路径
| 类别 | 路径 |
|------|------|
| 后端根 | `{后端项目根目录}` |
| 前端根 | `{前端项目根目录}` |
| 后端代码 | `{module}/src/main/java/org/jeecg/modules/{entityPackage}/` |
| 前端代码 | `src/views/{viewDir}/` |
| Flyway SQL | `jeecg-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`
## 智能字段推导
**用于新建表场景(从自然语言推导),或已有表但字段无注释时的补充推导:**
| 语义关键词 | dbType | Java 类型 | vue3 组件 | vue3Native 组件 |
|-----------|--------|----------|----------|----------------|
| 名称/标题/编码 | varchar(100) | String | Input | a-input |
| 金额/价格/费用 | decimal(10,2) | BigDecimal | InputNumber | a-input-number |
| 数量/数目/个数 | int | Integer | InputNumber | a-input-number |
| 状态/类型/级别 | varchar(10) | String | JDictSelectTag | JDictSelectTag |
| 是否/开关 | varchar(2) | String | Switch | a-switch |
| 日期/生日 | date | Date | DatePicker | a-date-picker |
| 时间/日期时间 | datetime | Date | DatePicker(showTime) | a-date-picker(showTime) |
| 备注/描述/说明 | text | String | InputTextArea | a-textarea |
| 内容/富文本 | text | String | JEditor | JEditor |
| 图片/头像/照片 | varchar(1000) | String | JImageUpload | JImageUpload |
| 文件/附件 | varchar(1000) | String | JUpload | JUpload |
| 用户/负责人 | varchar(32) | String | JSelectUserByDept | JSelectUserByDept |
| 部门/组织 | varchar(32) | String | JSelectDept | JSelectDept |
| 排序/序号 | int | Integer | InputNumber | a-input-number |
**已有表场景的 DB类型→控件 映射(当字段无注释时使用):**
| DB列类型 | Java类型 | 默认前端控件 |
|---------|---------|-----------|
| varchar(n) n<=200 | String | Input |
| varchar(n) n>200 | String | InputTextArea |
| text / longtext | String | InputTextArea |
| int / tinyint | Integer | InputNumber |
| bigint | Long | InputNumber |
| decimal / double / float | BigDecimal | InputNumber |
| date | Date | DatePicker |
| datetime / timestamp | Date | DatePicker(showTime) |
## 主键策略(根据已有表结构自适应)
| 表DDL中的主键定义 | Java类型 | @TableId | 说明 |
|------------------|---------|----------|------|
| `int AUTO_INCREMENT` | Integer | `@TableId(type = IdType.AUTO)` | int自增主键 |
| `bigint AUTO_INCREMENT` | Long | `@TableId(type = IdType.AUTO)` | bigint自增主键 |
| `varchar(36)` / `varchar(32)` 无AUTO_INCREMENT | String | `@TableId(type = IdType.ASSIGN_ID)` | JeecgBoot标准字符串主键 |
| `bigint` 无AUTO_INCREMENT | Long | `@TableId(type = IdType.ASSIGN_ID)` | 雪花ID |
**注意:** 当主键为 Integer/Long 类型时Controller 中 `delete` 和 `queryById` 的参数类型也要对应调整。
## 系统字段(按实际表结构判断)
**不是所有表都有系统字段!** 生成前必须检查表是否实际包含这些字段,**只生成表中存在的字段**
| 字段 | 说明 | 不存在时的处理 |
|------|------|--------------|
| `create_by` | 创建人 | 不生成该属性 |
| `create_time` | 创建时间 | 不生成该属性 |
| `update_by` | 更新人 | 不生成该属性 |
| `update_time` | 更新时间 | 不生成该属性 |
| `sys_org_code` | 所属部门 | 不生成该属性 |
如果是**新建表**(用户自然语言描述需求),则默认添加全部系统字段。
如果是**已有表**(用户指定了表名且数据库中已存在),则必须根据实际 DDL 来决定。
树表额外字段:`pid`、`has_child`(同样需检查是否实际存在)。
## 参考文件
生成代码前,**必须读取** 同目录下的 `codegen-reference.md` 获取完整代码模板骨架。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,357 @@
# jeecg-codegen Skills 使用指南
> 通过 Claude Code 以自然语言描述业务需求,自动生成 JeecgBoot 全套 CRUD 代码(后端 Java + 前端 Vue3 + 建表 SQL + 菜单 SQL
> 无需预建表结构AI 根据语义自动推导表结构、字段类型与前端控件;也支持基于已有表或多表关联生成代码。覆盖单表、树表、一对多等模型,可一次性生成整个业务模块。主键策略自适应,字典字段自动匹配系统已有编码——不只是模板填充,而是从业务需求到可运行代码的端到端生成。
## 与传统代码生成器的区别
- **无需预建表结构**只需用自然语言描述业务需求AI 即可自动推导出表结构与字段属性,省去手动建表的前置工作
- **表结构驱动同样支持**:也可以直接基于已有的数据库表或多表关联关系生成代码,兼容传统代码生成器的使用习惯
- **模块级批量生成**:支持一次性生成整个业务模块的全部代码文件(涵盖多张表、多个功能点),而非逐表逐模型地单独生成
- **主键策略自适应**AI 会参照项目中已有表的主键定义,自动选用合适的 MyBatis-Plus 主键策略,不局限于 String 类型的雪花 ID
- **字典智能匹配**:自动读取系统字典表(`sys_dict` + `sys_dict_item`),为字段精准匹配已有的字典编码,免去手动查找和指定的繁琐步骤
- **代码自动归位**:生成的前后端代码自动落入项目对应目录,建表与菜单 SQL 自动追加至 Flyway 升级脚本,无需手动搬运,生成即就绪
---
## 触发方式
在 Claude Code 对话中,用自然语言描述你要创建的功能,包含以下任意关键词即可自动触发:
```
代码生成 / 生成代码 / 创建模块 / 新增功能 / 建表 / 加字段 / 增加字段 / 新增字段 / 修改字段 / 删除字段
```
## 示例用法
### 1. 一句话描述(最简方式)
```
帮我生成一个商品管理模块,包含商品名称、价格、库存、状态、图片、描述
```
AI 会自动推导:表名 `biz_goods`、字段类型价格→BigDecimal、库存→Integer、图片→JImageUpload 等)。
### 2. 指定表名和字典
```
创建一个订单管理功能:
- 表名 biz_order
- 字段:订单编号、客户名称、下单日期、金额、状态(待付款/已付款/已发货/已完成)、备注
- 状态用字典 order_status
```
### 3. 树表(带层级关系)
```
建一个部门分类的树表,包含分类名称和分类编码
```
提到"分类/层级/树/上下级"等关键词AI 自动识别为树表模式。
### 4. 一对多(主子表)
```
生成一个采购单模块,主表是采购单(单号、供应商、日期、总金额),
子表是采购明细(商品名、数量、单价、小计)
```
提到"主子表/明细/一对多"等关键词AI 自动识别为一对多模式。
### 5. 指定后端模块
```
在 jeecg-module-demo 模块下生成一个公告管理,包含标题、内容(富文本)、发布时间、状态
```
### 6. 已有表反向生成(给表名即可)
```
生成 tmp_tables 这个表的代码
```
AI 会自动连接数据库查询 DDL解析主键类型、全部字段、系统字段然后生成匹配的代码。无需手动描述字段。
### 7. 增量修改(给已有模块加/改/删字段)
```
给表信息管理加一个备注字段
```
```
给 tmp_tables 增加两个字段:排序号和状态(启用/停用)
```
```
把商品管理的价格字段从 int 改成 decimal
```
```
删除商品管理的描述字段
```
AI 会自动定位已生成的全部代码文件Entity、data.ts、Form.vue 等),精确修改每个文件,并生成 ALTER TABLE 的 Flyway SQL无需重新生成整个模块。
## 交互流程
### 全量生成流程
```
Step 0 判断操作类型:全量生成 or 增量修改?
Step 1 解析需求,判断场景:
· 场景A — 已有表 → 查数据库获取 DDL自动解析字段
· 场景B — 新建表 → 从自然语言推导表结构
Step 2 询问 4 个选项(都有默认值,说"确认"即可):
① 后端模块 — 默认 jeecg-module-system/jeecg-system-biz
② 前端风格 — vue3封装风格或 vue3Native原生风格
③ 前端目录 — 默认按 entityPackage 值
④ 是否读取系统字典 — 默认是,自动匹配已有字典编码
Step 3 展示表结构摘要(含匹配到的字典),等待确认
Step 4 确认后,自动生成全部文件写入项目目录
Step 5 输出生成文件清单 + 后续操作说明
```
### 增量修改流程
```
Step 0 识别增量修改关键词(加字段/删字段/修改字段/给XX加...
Step 1 定位目标模块,扫描并读取已有代码文件
Entity.java / *.data.ts / *List.vue / *Modal.vue / *Form.vue
Step 2 解析已有字段,推导新字段属性
Step 3 展示修改摘要(每个文件的具体变更内容),等待确认
Step 4 确认后,用 Edit 精确修改每个文件 + 生成 ALTER TABLE SQL
Step 5 输出修改文件清单 + 后续操作说明
```
## 生成产物
### 单表模式11 个文件)
| 类别 | 文件 | 说明 |
|------|------|------|
| **后端** | `Entity.java` | 实体类,含 MyBatis-Plus / AutoPoi / Dict 注解 |
| | `Controller.java` | REST 控制器,继承 JeecgController含权限注解 |
| | `IService.java` | Service 接口 |
| | `ServiceImpl.java` | Service 实现 |
| | `Mapper.java` | MyBatis Mapper 接口 |
| | `Mapper.xml` | MyBatis XML 映射 |
| **前端** | `*.api.ts` | API 接口定义list/save/edit/delete/export/import |
| | `*.data.ts` | 列定义 + 查询表单 + 编辑表单 Schema |
| | `*List.vue` | 列表页面(表格 + 查询 + 操作按钮) |
| | `*Modal.vue` | 编辑弹窗 |
| | `*Form.vue` | 表单组件(仅 vue3Native 风格) |
| **SQL** | `V*__.sql` | Flyway 迁移建表DDL + 菜单 + 7个按钮权限 + 角色授权 |
### 树表模式
在单表基础上增加:
- Entity 额外字段:`pid`(父节点)、`has_child`(是否有子节点)
- Controller 额外接口:`rootList``childList``getChildListBatch`
- Service 额外方法:树节点的增删改逻辑
- 前端额外接口:树数据加载、子节点查询
### 一对多模式
在单表基础上增加:
- 子表完整的 Entity / Mapper / Service 各一套
- 主表 Service 包含联合保存/更新/删除
- Page VO 用于 Excel 主子表导入导出
- 前端 Tab 页展示子表数据
## 两种前端风格
| | vue3 封装风格 | vue3Native 原生风格 |
|---|-------------|-------------------|
| **表单** | `BasicForm` + FormSchema 配置驱动 | `a-form` + `a-form-item` 模板直接写控件 |
| **弹窗** | `BasicModal` + `useModal` Hook | `JModal` + `ref` + `defineExpose` |
| **表格** | `BasicTable` + `useTable` + `formConfig` 内置查询 | `BasicTable` + 手写查询表单区域 |
| **数据文件** | columns + searchFormSchema + formSchema | columns + superQuerySchema表单在模板中 |
| **优点** | 代码量少,配置化,统一风格 | 灵活度高,可深度定制交互 |
| **适合** | 标准 CRUD 页面 | 需要复杂交互或自定义布局的场景 |
## 智能字段推导
AI 根据字段语义自动推导类型和控件:
| 语义关键词 | 数据库类型 | Java 类型 | 前端控件 |
|-----------|----------|----------|---------|
| 名称/标题/编码 | varchar(100) | String | Input / a-input |
| 金额/价格/费用 | decimal(10,2) | BigDecimal | InputNumber / a-input-number |
| 数量/个数 | int | Integer | InputNumber / a-input-number |
| 状态/类型/级别 | varchar(10) | String | JDictSelectTag |
| 是否/开关 | varchar(2) | String | Switch / a-switch |
| 日期/生日 | date | Date | DatePicker / a-date-picker |
| 时间/日期时间 | datetime | Date | DatePicker(showTime) |
| 备注/描述/说明 | text | String | InputTextArea / a-textarea |
| 内容/富文本 | text | String | JEditor |
| 图片/头像/照片 | varchar(1000) | String | JImageUpload |
| 文件/附件 | varchar(1000) | String | JUpload |
| 用户/负责人 | varchar(32) | String | JSelectUserByDept |
| 部门/组织 | varchar(32) | String | JSelectDept |
| 排序/序号 | int | Integer | InputNumber / a-input-number |
当然你也可以在描述中明确指定字段类型AI 会优先使用你的指定。
### 已有表的 DB 类型→控件自动映射
对于已有表场景如果字段没有注释AI 根据数据库列类型自动推导前端控件:
| DB 列类型 | Java 类型 | 默认前端控件 |
|----------|----------|-----------|
| varchar(n) n<=200 | String | Input |
| varchar(n) n>200 | String | InputTextArea |
| text / longtext | String | InputTextArea |
| int / tinyint | Integer | InputNumber |
| bigint | Long | InputNumber |
| decimal / double / float | BigDecimal | InputNumber |
| date | Date | DatePicker |
| datetime / timestamp | Date | DatePicker(showTime) |
## 字典智能匹配
生成代码时AI 可选择读取系统字典表(`sys_dict` + `sys_dict_item`),自动为字段匹配已有的字典编码。
**匹配优先级:**
1. **用户明确指定**`"状态用字典 order_status"`,直接使用
2. **字段名精确匹配** — 字段名 `status` 与字典编码 `status` 一致
3. **语义关键词匹配** — 字段注释含"状态",搜索字典名称含"状态"的字典
4. **不匹配** — 找不到合适字典时,不使用字典注解,按普通 Input 处理
**效果示例:**
假设系统中已有字典 `order_status`(待付款=0, 已付款=1, 已完成=2当你描述字段"订单状态"时:
| 位置 | 自动生成内容 |
|------|-------------|
| Entity.java | `@Dict(dicCode = "order_status")` |
| columns | `dataIndex: 'orderStatus_dictText'` |
| formSchema | `component: 'JDictSelectTag', componentProps: { dictCode: 'order_status' }` |
| searchFormSchema | `component: 'JDictSelectTag', componentProps: { dictCode: 'order_status' }` |
在 Step 3 表结构摘要中会展示匹配结果,确认前可以修改或取消字典关联。
## 已有表反向生成
给定表名时AI 会自动连接数据库获取精确表结构:
1. **查询 DDL**`SHOW CREATE TABLE 表名` 获取完整建表语句
2. **查询字段详情**:从 `information_schema.COLUMNS` 获取每个字段的类型、注释、是否可空、默认值、主键标识
3. **解析主键策略**:根据主键列类型和是否 AUTO_INCREMENT 选择 MyBatis-Plus 注解
4. **识别系统字段**:检查 create_by/create_time/update_by/update_time/sys_org_code 是否存在
5. **推导前端控件**:优先用字段注释语义匹配,无注释时按 DB 类型映射
数据库连接信息:`mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3`(配置文件:`application-dev.yml`
## 增量修改详解
### 支持的操作
| 操作 | 说明 | 影响的文件 |
|------|------|-----------|
| **加字段** | 在所有文件中追加新字段定义 | Entity + data.ts + Form.vue(Native) + ALTER TABLE SQL |
| **删字段** | 从所有文件中移除指定字段 | Entity + data.ts + Form.vue(Native) + DROP COLUMN SQL |
| **改字段** | 修改字段类型/控件/注释等 | Entity + data.ts + Form.vue(Native) + MODIFY COLUMN SQL |
### 修改位置清单
每次增量修改AI 会精确定位并修改以下位置:
1. **Entity.java** — 字段声明 + 注解(@Excel@Dict@Schema@JsonFormat 等)+ 必要的 import
2. ***.data.ts** — columns 列定义 + formSchema 表单项 + searchFormSchema 查询条件(如需) + superQuerySchema如存在
3. ***Form.vue**(仅 vue3Native`<a-form-item>` 控件 + `formData` 初始值
4. **Flyway SQL**`ALTER TABLE ADD/DROP/MODIFY COLUMN` 语句
## Flyway 版本号规则
生成 Flyway SQL 前自动检查已有版本号,递增避免冲突:
- 版本命名:`V{YYYYMMDD}_{序号}__{描述}.sql`
- 检查当天是否已有文件(如 `V20260311_1__xxx.sql`
- 如果有,序号递增(`V20260311_2__xxx.sql`
- 如果没有,从 `_1` 开始
- 菜单 SQL 的 ID 使用 13 位毫秒级真实时间戳(`date +%s%3N`),确保全局唯一
## 主键策略自适应
AI 会根据已有表的 DDL 自动选择正确的主键策略:
| 表DDL中的主键定义 | Java类型 | MyBatis-Plus 注解 |
|------------------|---------|------------------|
| `int AUTO_INCREMENT` | Integer | `@TableId(type = IdType.AUTO)` |
| `bigint AUTO_INCREMENT` | Long | `@TableId(type = IdType.AUTO)` |
| `varchar(36)` 无自增 | String | `@TableId(type = IdType.ASSIGN_ID)` |
| `bigint` 无自增 | Long | `@TableId(type = IdType.ASSIGN_ID)` |
新建表时默认使用 JeecgBoot 标准的 `varchar(36) + ASSIGN_ID`
**注意:** 当主键为 Integer/Long 类型时Controller 中 `delete``queryById` 的参数类型也要对应调整,`deleteBatch` 需要做类型转换。
## 系统字段智能判断
**对于已有表**AI 会检查表结构,只生成表中实际存在的系统字段:
| 字段 | 不存在时 |
|------|---------|
| `create_by` / `create_time` | 不生成对应 Java 属性 |
| `update_by` / `update_time` | 不生成对应 Java 属性 |
| `sys_org_code` | 不生成对应 Java 属性 |
**对于新建表**(自然语言描述需求),默认添加全部系统字段。
树表额外字段:`pid``has_child`(同样需检查是否实际存在)。
## 生成后的操作
### 1. 执行 SQL
生成的 Flyway SQL 文件位于:
```
jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/
```
两种方式:
- **自动执行**:重启后端时 Flyway 自动执行
- **手动执行**:在数据库中手动执行 SQL 文件内容
### 2. 重启后端
```bash
cd jeecg-boot-framework-2026
mvn spring-boot:run -pl jeecg-module-system/jeecg-system-start
```
### 3. 刷新前端
开发服务器(`pnpm dev`)会自动热更新,无需重启。
### 4. 访问功能
登录系统后,新菜单已自动添加(默认授权给 admin 角色),直接可见可用。
## 文件路径说明
| 类别 | 路径 |
|------|------|
| 后端代码 | `jeecg-boot-framework-2026/{module}/src/main/java/org/jeecg/modules/{package}/` |
| 前端代码 | `jeecgboot-vue3-2026/src/views/{viewDir}/` |
| Flyway SQL | `jeecg-boot-framework-2026/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/` |
## 注意事项
1. **每次生成/修改前会展示摘要等你确认**,不会直接写文件,放心使用
2. **已有表按实际结构生成**,不会盲目添加不存在的字段
3. **权限编码规则**`{entityPackage}:{tableName}:add/edit/delete/deleteBatch/exportXls/importExcel`
4. **如果后端模块目录不存在**AI 会提示你先创建 Maven 模块结构
5. **生成的代码可以二次修改**,和手写代码完全一样,没有任何框架锁定
6. **增量修改只改动必要的文件和位置**,不会影响你手动修改过的其他代码
7. **字典匹配可选**,不想自动匹配字典时在 Step 2 选择"否"即可

View File

@@ -0,0 +1,754 @@
---
name: jeecg-desform
description: "Use when user asks to create/generate a form using AI, design a form automatically, or says \"AI设计表单\", \"AI生成表单\", \"自动创建表单\", \"智能表单\", \"生成一个表单\", \"帮我设计表单\", \"创建表单\", \"新建表单\", \"做一个表单\", \"ai form\", \"generate form\", \"create form\", \"design form\". Also triggers when user describes form fields like \"需要姓名、手机号、地址字段\" or mentions form requirements like \"做一个请假表单包含请假天数和原因\". Supports generating forms from screenshots — when user provides a screenshot/image of a form and asks to reproduce it (e.g., \"按照截图生成表单\", \"照着这个图片做表单\", \"根据截图创建表单\", \"generate form from screenshot\", \"recreate this form\")."
---
# JeecgBoot 表单设计器 AI 自动生成器
将自然语言的表单需求描述转换为 desformDesignJson并通过 API 在 JeecgBoot 系统中自动创建表单。
> **重要:本 skill 只处理「设计器表单」desform不涉及 Online 表单。两者是完全独立的表单体系。**
## 前置条件
用户必须提供以下信息(或由 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: 解析用户需求
从用户描述中提取以下信息:
| 信息 | 默认值 | 示例 |
|------|--------|------|
| 表单名称 | 用户指定或自动生成 | "员工请假申请" |
| 表单编码 | 英文命名,模块名前缀 | `oa_leave_apply`(不用拼音) |
| 字段列表 | 从描述中解析 | 姓名(必填)、请假天数(数字)、请假原因(多行文本) |
| 字段属性 | 从描述中推断 | 必填、默认值、选项列表等 |
### Step 1: 识别字段并选择控件类型
**控件类型映射规则:**
| 用户描述关键词 | 控件 type | 说明 |
|---------------|-----------|------|
| 名称/标题/姓名/文本 | `input` | 单行文本 |
| 描述/备注/原因/详情/多行 | `textarea` | 多行文本 |
| 数量/数字/金额(无单位) | `number` | 数字输入 |
| 整数/个数/天数 | `integer` | 整数输入 |
| 金额/费用/价格 | `money` | 金额(带元单位) |
| 单选/性别/是否/状态 | `radio` | 单选框组 |
| 多选/标签/兴趣 | `checkbox` | 多选框组 |
| 下拉/选择/类型/类别 | `select` | 下拉选择框 |
| 日期/生日/入职日期 | `date` | 日期选择器 |
| 时间/几点 | `time` | 时间选择器 |
| 开关/启用/是否激活 | `switch` | 开关 |
| 评分/星级/打分 | `rate` | 评分 |
| 颜色 | `color` | 颜色选择器 |
| 滑块/进度/百分比 | `slider` | 滑块 |
| 手机/电话/手机号 | `phone` | 手机 |
| 邮箱/Email | `email` | 邮箱 |
| 图片/照片/头像 | `imgupload` | 图片上传 |
| 附件/文件/上传 | `file-upload` | 文件上传 |
| 富文本/HTML内容 | `editor` | 富文本编辑器 |
| Markdown | `markdown` | Markdown 编辑器 |
| 省市/地区/地址选择 | `area-linkage` | 省市级联动 |
| 地图/位置(地图) | `map` | 地图 |
| 定位/GPS | `location` | 定位 |
| 条码/二维码 | `barcode` | 条码 |
| 自动编号/流水号 | `auto-number` | 自动编号 |
| 选人/审批人/负责人 | `select-user` | 用户组件 |
| 部门/选部门 | `select-depart` | 部门组件 |
| 岗位/选岗位 | `select-depart-post` | 岗位组件 |
| 分类树/树选择 | `select-tree` | 下拉树 |
| 表字典/弹窗选择 | `table-dict` | 表字典popup或模糊查询 |
| 关联记录/引用 | `link-record` | 关联其他表单的记录 |
| 他表字段/自动填充 | `link-field` | 显示关联记录的字段值 |
| 公式/自动计算 | `formula` | 公式计算(求和/均值/自定义) |
| 手写签名/签字 | `hand-sign` | 手写签名 |
| 大写金额/中文大写 | `capital-money` | 金额转大写 |
| 文本组合 | `text-compose` | 多字段值拼接显示 |
| 分隔符/分区 | `divider` | 表单区域分隔线 |
| 文本识别/OCR | `ocr` | 图片文字识别 |
| 子表/明细/清单 | `sub-table-design` | 设计子表 |
### Step 1.5: 字典数据源配置
对于 radio/select/checkbox 控件,数据源有两种方式:
**方式一:静态选项(默认)**
```json
"options": {
"remote": false,
"options": [
{ "value": "选项1", "itemColor": "#2196F3" },
{ "value": "选项2", "itemColor": "#08C9C9" }
]
}
```
**方式二:系统字典**
当用户描述中提到「字典」、「数据字典」或使用了 JeecgBoot 常见字典编码(如 sex、priority、valid_status 等),使用字典配置:
```json
"options": {
"remote": "dict",
"dictCode": "sex",
"showLabel": true,
"options": [],
"remoteOptions": [],
"props": { "value": "value", "label": "label" }
}
```
同时在**控件顶层**(与 options 同级)添加 `dictOptions`
```json
"dictOptions": [
{ "value": "1", "label": "男" },
{ "value": "2", "label": "女" }
]
```
**常用 JeecgBoot 系统字典编码:**
| 字典编码 | 说明 | 典型值 |
|---------|------|--------|
| `sex` | 性别 | 1=男, 2=女 |
| `priority` | 优先级 | L=低, M=中, H=高 |
| `valid_status` | 有效状态 | 0=无效, 1=有效 |
| `msg_category` | 消息类型 | 1=通知, 2=系统 |
| `send_status` | 发送状态 | 0=未发送, 1=已发送 |
| `yn` | 是否 | Y=是, N=否 |
> **提示:** 当用户指定的字典编码不确定是否存在时,可通过 API `GET /sys/dict/getDictItems/{dictCode}` 查询确认。如果用户只说了"用字典"但未指定编码,需要询问具体的字典编码。
**desform_utils.py 快捷函数使用字典的正确写法:**
> **踩坑警告:** `RADIO`/`SELECT`/`CHECKBOX` 的 `options` 是**必填位置参数**,使用字典时也不能省略。
> 当指定 `dict_code` 时,`options` 参数必须传**字典项列表**`[{value, label}]` 格式),不要传字符串列表。
> **不存在** `dict_options` 关键字参数,不要传它(会报 `unexpected keyword argument` 错误)。
```python
# 正确 ✅ — options 传字典项列表 + dict_code
RADIO('性别', [{'value': '1', 'label': ''}, {'value': '2', 'label': ''}], dict_code='sex')
SELECT('状态', [{'value': '0', 'label': '无效'}, {'value': '1', 'label': '有效'}], dict_code='valid_status')
# 错误 ❌ — 缺少 options 位置参数
RADIO('性别', dict_code='sex')
# 错误 ❌ — 不存在 dict_options 参数
RADIO('性别', ['', ''], dict_code='sex', dict_options=[...])
# 不用字典时options 传字符串列表即可
SELECT('职称', options=['教授', '副教授', '讲师', '助教'])
```
**底层 `make_widget` 函数中字典的实现原理(仅供参考):**
```python
# desform_utils.py 内部处理逻辑:
if dict_code:
opts["remote"] = "dict"
opts["dictCode"] = dict_code
opts["showLabel"] = True
opts["options"] = []
extra["dictOptions"] = options if isinstance(options[0], dict) else []
```
### Step 2: 展示表单摘要并确认
**必须展示以下内容,等待用户确认后再执行:**
```
## 表单摘要
- 表单名称:员工请假申请
- 表单编码yuan_gong_qing_jia_shen_qing
- 目标环境https://boot3.jeecg.com/jeecgboot
### 字段列表
| 序号 | 字段名称 | 控件类型 | 必填 | 说明 |
|------|---------|---------|------|------|
| 1 | 姓名 | input (单行文本) | 是 | 标题字段 |
| 2 | 请假类型 | select (下拉选择) | 是 | 选项:事假/病假/年假 |
| 3 | 开始日期 | date (日期) | 是 | |
| 4 | 结束日期 | date (日期) | 是 | |
| 5 | 请假天数 | integer (整数) | 是 | |
| 6 | 请假原因 | textarea (多行文本) | 否 | |
| 7 | 附件 | file-upload (文件上传) | 否 | |
确认以上信息正确?(y/n)
```
### Step 2.5: 检查表单编码是否已存在(防覆盖规则)
> **重要安全规则:** 在执行创建/保存操作之前,**必须**先通过 `get_form_id(code)` 检查表单编码是否已存在。
**检查方式:** 在临时脚本中调用 `get_form_id(code)` 或在创建脚本中加入检查逻辑。
**如果表单已存在:**
1. **不允许默认覆盖**(不要直接执行 `create_form``update_form`
2. 必须明确告知用户:`表单 {code} 已存在 (ID={id}),是否要覆盖更新?`
3. **只有用户明确确认后**才可以执行覆盖操作(调用 `update_form` 更新设计)
4. 如果用户拒绝覆盖,基于原编码生成 3~5 个新编码供用户选择(如原编码 `oa_leave_apply`,可提供 `oa_leave_apply_v2``oa_leave_request``oa_leave_form``oa_staff_leave` 等),用户选定后再重新生成
**预置脚本的防覆盖:** 所有 `scripts/` 目录下的脚本都内置了 `--force` 参数检查,不加 `--force` 时检测到已存在会自动退出。
**动态脚本的防覆盖:** 手动编写的临时脚本中,在调用 `create_form` 之前加入检查:
```python
existing_id, _ = get_form_id(code)
if existing_id:
print(f'表单 {code} 已存在 (ID={existing_id}),需要用户确认后才能覆盖')
sys.exit(1)
```
### Step 3: 生成 desformDesignJson 并调用 API
用户确认后,执行以下步骤:
#### 3.0 优先使用通用脚本 + JSON 配置(推荐方式)
> **重要:优先使用 `scripts/desform_creator.py` 通用脚本 + JSON 配置文件的方式,只需生成少量 JSON 数据即可创建表单,避免每次编写大量 Python 代码。只有当通用脚本无法满足特殊需求时,才编写自定义临时脚本。**
**脚本位置:** `scripts/desform_creator.py`
**使用步骤:**
1. 根据用户需求生成 JSON 配置文件Write 到工作目录的临时 `.json` 文件)
3. 用 Bash 执行脚本:`python "<skill目录>/scripts/desform_creator.py" --api-base <URL> --token <TOKEN> --config <config.json>`
4. 删除临时 JSON 配置文件
**脚本自动完成:**
- 防覆盖检查(不加 `--force` 时检测到已存在自动退出)
- 根据 JSON 配置构建所有控件
- 调用 `create_form` 创建/保存表单设计
- 输出菜单 SQL如果 JSON 中配置了 `menuParent`
**JSON 配置格式:**
```json
{
"formName": "表单中文名称",
"formCode": "module_form_code",
"layout": "word",
"titleIndex": 0,
"fields": [
{"name": "字段名", "type": "控件类型", ...}
],
"menuParent": "父菜单名称",
"menuIcon": "ant-design:appstore-outlined"
}
```
| JSON 字段 | 必填 | 默认值 | 说明 |
|-----------|------|--------|------|
| `formName` | 是 | - | 表单中文名称 |
| `formCode` | 是 | - | 表单编码(英文,模块名前缀) |
| `layout` | 否 | `"auto"` | 布局模式:`auto`/`half`/`full`/`word` |
| `titleIndex` | 否 | `0` | 标题字段在 fields 中的索引 |
| `fields` | 是 | - | 字段定义数组 |
| `menuParent` | 否 | - | 生成菜单 SQL 的父菜单名称 |
| `menuIcon` | 否 | `ant-design:appstore-outlined` | 父菜单图标 |
**字段定义fields 数组中每个对象):**
每个字段只需 `name` + `type`,其余参数可选:
```json
{"name": "工程名称", "type": "input", "required": true}
{"name": "工程类别", "type": "radio", "options": ["土建", "安装", "装饰"]}
{"name": "验收日期", "type": "date"}
{"name": "金额", "type": "money", "unit": "万元"}
{"name": "自动编号", "type": "auto-number", "prefix": "GCYS"}
{"name": "条码", "type": "barcode"}
{"name": "定位", "type": "location"}
{"name": "签字", "type": "hand-sign", "required": true}
{"name": "---", "type": "divider", "text": "分隔标题"}
{"name": "性别", "type": "radio", "dictCode": "sex",
"options": [{"value": "1", "label": "男"}, {"value": "2", "label": "女"}]}
```
**支持的 type 及可选参数:**
| type | 可选参数 | 说明 |
|------|---------|------|
| `input` | `required`, `placeholder`, `unique` | 单行文本 |
| `textarea` | `required` | 多行文本 |
| `number` | `required`, `unit`, `precision` | 数字 |
| `integer` | `required`, `unit` | 整数 |
| `money` | `required`, `unit` | 金额 |
| `date` | `required`, `fmt` | 日期fmt 默认 `yyyy-MM-dd` |
| `time` | `required` | 时间 |
| `switch` | - | 开关 |
| `slider` | - | 滑块 |
| `rate` | - | 评分 |
| `color` | - | 颜色 |
| `radio` | `options`(必填), `required`, `dictCode` | 单选 |
| `select` | `options`(必填), `required`, `multiple`, `dictCode` | 下拉 |
| `checkbox` | `options`(必填), `required`, `dictCode` | 多选 |
| `select-user` | `required`, `multiple` | 选人 |
| `select-depart` | `required`, `multiple` | 选部门 |
| `phone` | `required` | 手机 |
| `email` | `required` | 邮箱 |
| `area-linkage` | `required` | 省市级联 |
| `file-upload` | `required` | 文件上传 |
| `imgupload` | `required` | 图片上传 |
| `hand-sign` | `required` | 手写签名 |
| `auto-number` | `prefix` | 自动编号 |
| `barcode` | `codeType`(`barcode`/`qrcode`) | 条码 |
| `location` | `required` | 定位 |
| `formula` | `mode`, `expression`, `decimal`, `unit` | 公式 |
| `divider` | `text` | 分隔符name 会被忽略,用 text |
| `editor` | `required` | 富文本 |
| `markdown` | `required` | Markdown |
| `link-record` | `sourceCode`, `titleField`, `showFields`, `showMode`, `showType` | 关联记录 |
| `link-field` | `linkRecordKey`, `showField`, `fieldType`, `fieldOptions` | 他表字段 |
**完整示例(工程竣工验收申请表):**
```json
{
"formName": "工程竣工验收申请表",
"formCode": "eng_completion_acceptance",
"layout": "word",
"fields": [
{"name": "自动编号", "type": "auto-number", "prefix": "GCYS"},
{"name": "条码", "type": "barcode"},
{"name": "工程名称", "type": "input", "required": true},
{"name": "工程编号", "type": "input"},
{"name": "工程类别", "type": "radio", "options": ["土建工程", "安装工程", "装饰工程", "市政工程"]},
{"name": "建设单位", "type": "input"},
{"name": "工程地址", "type": "input"},
{"name": "施工单位", "type": "input"},
{"name": "开工时间", "type": "date"},
{"name": "完工时间", "type": "date"},
{"name": "工程量清单", "type": "textarea"},
{"name": "图片上传", "type": "imgupload"},
{"name": "定位", "type": "location"},
{"name": "验收类别", "type": "radio", "options": ["竣工验收", "分部验收", "专项验收"]},
{"name": "施工单位项目经理签字", "type": "hand-sign"},
{"name": "---", "type": "divider", "text": "广电工程完工验收报告"},
{"name": "工程名称(报告)", "type": "input"},
{"name": "工程编号(报告)", "type": "input"},
{"name": "建设单位(报告)", "type": "input"},
{"name": "施工单位(报告)", "type": "input"},
{"name": "开工时间(报告)", "type": "date"},
{"name": "完工时间(报告)", "type": "date"},
{"name": "验收时间", "type": "time"},
{"name": "验收类别(报告)", "type": "radio", "options": ["竣工验收", "分部验收", "专项验收"]},
{"name": "---", "type": "divider", "text": "竣工项目分项审查情况"},
{"name": "立项手续完整性", "type": "radio", "options": ["合格", "不合格", "整改后合格"]},
{"name": "项目主体组签字(立项)", "type": "hand-sign"},
{"name": "竣工资料完整性", "type": "radio", "options": ["合格", "不合格", "整改后合格"]},
{"name": "项目主体组签字(资料)", "type": "hand-sign"},
{"name": "施工工艺合规性", "type": "radio", "options": ["合格", "不合格", "整改后合格"]},
{"name": "项目主体组签字(工艺)", "type": "hand-sign"},
{"name": "技术指标达标情况", "type": "radio", "options": ["合格", "不合格", "整改后合格"]},
{"name": "项目主体组签字(技术)", "type": "hand-sign"},
{"name": "材料设备核定结果", "type": "radio", "options": ["合格", "不合格", "整改后合格"]},
{"name": "项目主体组签字(材料)", "type": "hand-sign"},
{"name": "工程量核量结果", "type": "radio", "options": ["合格", "不合格", "整改后合格"]},
{"name": "项目主体组签字(核量)", "type": "hand-sign"},
{"name": "验收问题清单", "type": "textarea"},
{"name": "验收结论", "type": "radio", "options": ["合格", "不合格", "整改后复验"]},
{"name": "技术部负责人签字", "type": "hand-sign"},
{"name": "施工单位签字", "type": "hand-sign"},
{"name": "分管领导组签字", "type": "hand-sign"}
],
"menuParent": "工程验收管理"
}
```
**调用示例:**
```bash
# 1. Write 工具生成 JSON 配置文件
# 2. 执行脚本
python "C:/Users/moe/.claude/skills/jeecg-desform/scripts/desform_creator.py" \
--api-base http://192.168.1.233:3100/jeecgboot \
--token eyJhbGciOiJIUzI1NiJ9... \
--config eng_acceptance.json
# 如需覆盖已存在的表单
python "C:/Users/moe/.claude/skills/jeecg-desform/scripts/desform_creator.py" \
--api-base http://192.168.1.233:3100/jeecgboot \
--token eyJhbGciOiJIUzI1NiJ9... \
--config eng_acceptance.json \
--force
# 3. 删除临时 JSON 文件
```
#### 3.1 生成唯一标识
- key 和 model 使用当前时间戳毫秒数 + 6 位随机数
- 格式参见 `references/desform-design-json-schema.md`
#### 3.2 构造 desformDesignJson
阅读以下参考文件(按需):
- `references/desform-design-json-schema.md` — JSON Schema 结构、控件类型清单、通用字段(必读)
- `references/desform-widget-options.md` — 每种控件的完整 options 配置(必读)
- `references/desform-examples.md` — 常见表单模式示例 + Python 脚本模板(必读)
- `references/desform-real-samples.md` — 真实业务表单案例(字典、半行、分区、公式、关联)
核心要点:
- 每个普通控件必须包裹在 `card` 容器中(除了 editor、markdown、divider、map、sub-table-design、link-record(多条/表格模式)、grid、tabs
- `config.titleField` 指向标题字段的 model优先 input也可以是 select-user 等其他控件)
- `config.hasWidgets` 必须列出所有使用到的控件 type包括 card
- key 格式:`{timestamp}_{6位随机数}`(源码中实际是 randomKey但时间戳格式也兼容
- model 格式:`{type}_{timestamp}_{6位随机数}`type 中的 `-` 转为 `_`,如 `link_record_xxx`
- model 必须全局唯一(保存时会检查重复 model
**className / icon 易错控件(实测验证):**
- `link-record`: className=`form-link-record`, icon=**`icon-link`**(不是 `icon-link-record`
- `link-field`: className=`form-link-field`, icon=**`icon-field`**(不是 `icon-link-field`
- `sub-table-design`: className=**`form-sub-table`**, icon=**`icon-table`**(不是 `form-sub-table-design` / `icon-sub-table-design`
**link-record / link-field 关键配置:**
- link-record 的 `advancedSetting.defaultValue.customConfig` 必须为 `true`
- link-record 的 `allowView``allowEdit``allowAdd``allowSelect` 必须全部设为 `true`4 个操作选项默认全部勾选)
- link-record 的 `titleField` 必须填源表真实标题字段 model`showFields` 填源表展示字段 model 列表
- link-field **没有 `advancedSetting`**(与其他控件不同)
- link-field 的 `linkRecordKey` 填 link-record 的 **key**(不是 model
- link-field 的 `fieldType` 必须填源字段的真实控件类型(不能一律写 `"input"`
- link-field 的 `fieldOptions` 需包含源字段类型相关的 options如 select-user 需 `{"multiple": false, "customReturnField": "username"}`
**sub-table-design 关键配置:**
- options 必须包含 `allowAdd: true`,否则子表没有"添加"按钮
- 完整 options 见 `references/desform-widget-options.md`showCheckbox、showNumber、operationMode 等缺一不可)
- 子表内可放 link-record + link-field 实现行级关联选择
**跨表单批量创建流程:**
1. 先创建基础表单 → 2. 查询获取字段 model → 3. 构建业务表单时引用这些 model
#### 3.3 使用 Python 调用 API必须用 Python不要用 curl
**优先使用共通工具库 `desform_utils.py`**(位于 `scripts/desform_utils.py`)。
**使用共通工具库的执行步骤:**
```
1. Write 工具 → 写入业务脚本 create_xxx.pyscripts/ 目录import desform_utils
2. Bash 工具 → cd <skill目录>/scripts && python create_xxx.py
3. Bash 工具 → rm create_xxx.py清理临时脚本
```
**共通工具库使用示例:**
```python
import sys
sys.path.insert(0, r'{后端项目根目录}')
from desform_utils import *
init_api('https://boot3.jeecg.com/jeecgboot', 'your-token')
# 简单表单(含字典用法)
create_form('员工信息', 'employee_info', [
INPUT('姓名', required=True),
RADIO('性别', [{'value': '1', 'label': ''}, {'value': '2', 'label': ''}], dict_code='sex'),
PHONE('电话'),
EMAIL('邮箱'),
DEPART('部门'),
SELECT('职称', options=['教授', '副教授', '讲师', '助教']),
TEXTAREA('备注'),
])
# 带关联的表单
form_id, title = create_form('客户信息', 'customer_info', [
INPUT('客户名称', required=True),
PHONE('电话'),
])
# 查询字段用于关联
tf, fields = get_form_fields('customer_info')
create_form('联系人', 'contact_info', [
INPUT('姓名', required=True),
LINK_RECORD('所属客户', 'customer_info', tf, [fields['客户名称']['model']]),
])
# 菜单SQLID 自动生成 UUID只需传菜单名和子项
print(gen_menu_sql('CRM系统', [
('客户信息', 'customer_info', 1),
('联系人', 'contact_info', 2),
]))
# 查询表单
form = query_form('customer_info')
print(form['id'], form['updateCount'])
# 修改已有表单设计(自动获取 updateCount
update_form('customer_info', [
INPUT('客户名称', required=True),
PHONE('电话'),
EMAIL('邮箱'),
TEXTAREA('备注'),
])
# 删除表单(支持 3 种方式)
delete_form('customer_info') # 传 code自动查找 ID
delete_form('customer_info', '123456789') # 传 code + 已知 ID跳过搜索最快
delete_form('123456789012345678') # 只传 ID
```
**可用的快捷函数(大写命名):**
- 基础: `INPUT`, `TEXTAREA`, `NUMBER`, `INTEGER`, `MONEY`, `DATE`, `TIME`, `SWITCH`, `SLIDER`, `RATE`, `COLOR`
- 选择: `RADIO`, `SELECT`, `CHECKBOX`(支持 dict_code 字典)
- 系统: `USER`, `DEPART`, `PHONE`, `EMAIL`, `AREA`
- 文件: `FILE`, `IMGUPLOAD`, `HANDSIGN`
- 高级: `AUTONUMBER`, `FORMULA`, `LINK_RECORD`, `LINK_FIELD`
- 不需要 card: `DIVIDER`, `EDITOR`, `MARKDOWN`
- 子表内: `SUB_INPUT`, `SUB_INTEGER`, `SUB_NUMBER`, `SUB_MONEY`, `SUB_SELECT`, `SUB_DATE`, `SUB_LINK_RECORD`, `SUB_LINK_FIELD`, `SUB_FORMULA`
- 容器: `make_card`, `make_sub_table`
- API: `init_api`, `create_form`, `update_form`, `delete_form`, `query_form`, `get_form_id`, `get_form_fields`, `find_or_create_form`, `save_design`
> **`create_form` 的 `layout` 参数:**
> - `'auto'`(默认):字段数 >= 6 时自动使用半行两列布局
> - `'half'`:强制半行布局
> - `'full'`:强制整行布局(不做半行处理)
> - `'word'`Word 风格布局(表格边框样式,见下方详细说明)
> - textarea/editor/file-upload/imgupload 等宽控件自动保持整行
>
> **Word 风格表单(`layout='word'`**
>
> Word 风格模拟传统 Word 文档表格样式,适用于审批单、申请表等正式场景。
>
> **实现原理JeecgBoot 表单设计器内置支持):**
> - `formStyle: "word"` — 表单风格设为 Word设计器右侧「表单属性」→「表单风格」→「Word风格」
> - 栅格布局 `grid`className = `form-grid form-grid-word-theme` — 每行一个栅格容器
> - 标签列:独立的 `text` 控件16px、居中放在栅格的第一列
> - 控件列:实际控件设置 `hideTitle: true`(隐藏标题),放在栅格的第二列
> - 顶部标题:独立的 `text` 控件24px、加粗、居中不使用内置 header
> - 外部 CSS加载 `/desform/expand/css/theme-word.css` 提供表格边框样式
> - `showHeaderTitle: false`、`disabledAutoGrid: true`
>
> **栅格 span 分配规则:**
> - 两列行半行控件配对标签1 span=6 + 控件1 span=6 + 标签2 span=4 + 控件2 span=8
> - 单列行textarea/file-upload 等宽控件):标签 span=6 + 控件 span=18
>
> **使用示例:**
> ```python
> create_form('提成申请单', 'oa_commission_apply', [
> USER('申请人', required=True),
> DEPART('部门', required=True),
> DATE('申请日期', required=True),
> INPUT('项目名称', required=True),
> MONEY('合同金额', required=True),
> MONEY('提成金额', required=True),
> TEXTAREA('提成说明'),
> FILE('附件'),
> ], layout='word')
> ```
>
> **注意事项:**
> - `_apply_word_layout` 会自动生成顶部标题 text、栅格行、text 标签
> - hand-sign/textarea/file-upload/divider 等宽控件自动独占一行
> - 标签列 flex 垂直居中对齐
>
> **`gen_menu_sql` 的 `icon` 参数:**
> - 默认值 `'ant-design:appstore-outlined'`,一级菜单自动带图标
> - 可自定义:`gen_menu_sql('费用管理', [...], icon='ant-design:dollar-outlined')`
- 字典: `query_dict(code)` 查询字典项, `search_dict(keyword)` 按名称/编码模糊搜索字典
- SQL: `gen_menu_sql`
**如果共通工具库不存在,则使用以下方式:**
**重要限制(实战踩坑):**
1. **Windows 环境下 curl 发送中文/长JSON会出错**,必须使用 Python 的 urllib/requests 确保 UTF-8 编码
2. **禁止使用 `python3 -c "..."` 内联方式**,因为 JSON 中的特殊字符会被 bash 解析出错
3. **必须先用 Write 工具写入 `.py` 临时文件,再用 Bash 执行,最后删除临时文件**
**执行步骤:**
```
1. Write 工具 → 写入 create_desform.py项目根目录
2. Bash 工具 → python create_desform.py
3. Bash 工具 → rm create_desform.py清理
```
**API 踩坑记录(实战验证):**
> **关键踩坑:**
> 1. `POST /desform/add` 现已直接返回表单实体(含 ID`desform_utils.py` 已优先从返回值获取 ID旧版后端不返回时自动 fallback 到 list 搜索
> 2. `GET /desform/queryByCode` **不可靠**(部分表单查不到),推荐用 `GET /desform/queryByIdOrCode?desformCode={code}`
> 3. `queryByIdOrCode` 对新创建但未保存设计的表单也可能返回失败,此时需通过 list API 全量搜索
> 4. list API 的 `desformCode` 过滤参数**不可靠**(有时匹配不到),必须全量搜索后手动精确匹配
> 5. `PUT /desform/edit` 的 `updateCount` 必须传**当前数据库中的值**(不是 +1后端会自动递增
> 6. `DELETE /desform/deleteBatch` 是**逻辑删除**(放入回收站),表单 code 仍被占用
> 7. `DELETE /desform/recycleBin/deleteByIds` 可彻底删除回收站中的表单,释放 code。`delete_form` 已封装完整流程,支持传 code 或 ID
> 8. `PUT /desform/recycleBin/recoverByIds` 可从回收站恢复表单
> 9. `DELETE /desform/recycleBin/empty` 清空回收站(在演示环境中可能不完全生效)
> 10. **删除后重建时序问题:** 彻底删除表单后code 释放可能有延迟。如果 `add` 返回 `该code已存在`,说明该 code 之前被另一个表单占用(同 code 可能存在多条记录)。此时应通过 list 全量搜索找到占用该 code 的表单,对其执行 `deleteBatch` + `recycleBin/deleteByIds` 彻底删除后再重建
> 11. **`save_design` 报「未找到对应实体」:** 通常是因为使用了已被删除的旧表单 ID。`find_or_create_form` 可能返回旧 ID缓存或竞态此时需通过 list API 重新搜索获取最新有效 ID
>
> **`create_form` vs `save_design` 使用区别:**
> - **推荐始终使用 `create_form`**(一站式:查找/创建 + 保存设计),它会自动解包 tuple、确定标题字段、处理 updateCount
> - `save_design` 是底层函数,签名为 `save_design(form_id, form_code, widgets, title_model, update_count)`
> - `widgets` 参数需要传**解包后的 widget dict 列表**(不是 tupletuple 需先 `[w[0] for w in widgets_tuples]` 解包
> - `title_model` 是标题字段的 model 字符串(不是 index可通过 `widgets_tuples[0][2]` 获取
> - 如需直接调用 `save_design`,务必先通过 `queryByIdOrCode` 获取最新 `updateCount`
>
> **命名规则:**
> - 表单编码使用英文命名(不用拼音),模块名作为前缀
> - 格式:`{模块}__{实体}`,如 `crm_customer`、`crm_contact`、`oa_leave_apply`
> - 同一模块的表单共享前缀,便于分组管理
>
> **find_or_create_form 策略desform_utils.py 中已实现):**
> 1. 先尝试 `POST /desform/add` 创建
> 2. 若 add 成功且返回值含 ID → 直接使用(新版后端已支持)
> 3. 若 add 成功但返回值无 ID → 通过 list API 全量搜索获取 ID旧版兜底
> 4. 若 add 失败code已存在→ 尝试 `queryByIdOrCode` 获取 ID
> 5. 若 queryByIdOrCode 也失败 → 通过 list API 全量搜索获取 ID
#### 3.4 检查结果
- `success: true` → 表单创建成功
- `success: false` → 输出错误信息,检查 desformCode 是否重复等
### Step 4: 输出结果
```
## 表单创建成功
- 表单ID{id}
- 表单名称:{desformName}
- 表单编码:{desformCode}
- 目标环境:{API_BASE}
请在表单设计器中查看:打开 JeecgBoot 后台 → 表单设计器 → 找到该表单
```
**同时输出菜单 + 角色授权 SQL用于将设计器表单加入系统菜单**
`gen_menu_sql` 函数会同时生成 `sys_permission`(菜单)和 `sys_role_permission`(角色授权)的 SQL。
**所有 ID菜单 ID、授权记录 ID均自动生成 32 位无横线 UUID无需手动指定。**
```python
# 调用方式:只需传父菜单名称 + 子菜单列表
sql = gen_menu_sql('物业管理', [
('小区信息', 'pm_community', 1),
('楼栋信息', 'pm_building', 2),
('房屋信息', 'pm_house', 3),
])
print(sql)
```
生成的 SQL 格式(每条 INSERT 都带完整列名,避免列错位):
```sql
-- 父菜单ID 自动生成 UUID
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 ('{uuid}', NULL, '{parentName}', '/{uuid}', 'layouts/RouteView', NULL, NULL, 0, NULL, '1', 1.00, 0, NULL, 1, 0, 0, 0, 0, NULL, '1', 0, 0, 'admin', now(), NULL, NULL, 0);
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip)
VALUES ('{uuid}', '{roleId}', '{parentUuid}', NULL, now(), '127.0.0.1');
-- 子菜单ID 自动生成 UUID
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 ('{uuid}', '{parentUuid}', '{desformName}', '/online/desform/list/{desformCode}', 'super/online/desform/auto/AutoDesformDataList', 'AutoDesformDataList', NULL, 0, NULL, '1', 1.00, 0, NULL, 0, 1, 0, 0, 0, NULL, '1', 0, 0, 'admin', now(), NULL, NULL, 0);
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip)
VALUES ('{uuid}', '{roleId}', '{menuUuid}', NULL, now(), '127.0.0.1');
```
**菜单 SQL 关键字段说明:**
| 字段 | 值 | 说明 |
|------|-----|------|
| id | 自动生成 32 位 UUID | 如 `d0ca42ae976a4dfbbff491e304858fe1` |
| url | `/online/desform/list/{desformCode}` | 设计器表单数据列表路由desformCode 是表单编码 |
| component | `super/online/desform/auto/AutoDesformDataList` | 固定值,设计器表单自动数据列表组件 |
| component_name | `AutoDesformDataList` | 固定值 |
| is_route | `0` | 不走普通路由 |
| is_leaf | `1` | 叶子节点 |
| parent_id | `NULL` 或父菜单UUID | NULL=一级菜单指定父UUID=子菜单 |
**角色授权 SQL 说明:**
| 字段 | 值 | 说明 |
|------|-----|------|
| id | 自动生成 32 位 UUID | 每条授权记录独立 UUID |
| role_id | `f6817f48af4fb3af11b9e8bf182f618b` | 默认角色 IDdesform_utils.py 中 ROLE_ID 常量),可通过参数覆盖 |
| permission_id | 对应的菜单 UUID | 关联 sys_permission.id |
> **重要:输出菜单 SQL 时,必须直接使用 `gen_menu_sql` 函数的完整输出,不要手动缩写或省略列名,否则会因列错位导致执行报错。**
### 本地环境自动执行菜单 SQL 规则
**判断条件:** `init_api` 传入的 api_base 以 `http://127.0.0.1``http://localhost` 开头(不区分大小写)。
**自动执行方式:**`gen_menu_sql` 生成 SQL 后,通过 Bash 工具逐条执行 MySQL 命令:
```bash
# 先检查菜单是否已存在,避免重复插入
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -e "SELECT id FROM sys_permission WHERE id='{menuId}'"
# 不存在则执行插入(包括 sys_permission 和 sys_role_permission
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -e "INSERT INTO sys_permission(...) VALUES (...);"
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -e "INSERT INTO sys_role_permission(...) VALUES (...);"
```
**注意事项:**
-`gen_menu_sql` 的每条 INSERT 语句拆分后逐条通过 MySQL CLI 执行
- 执行前先检查父菜单 ID 是否已存在,避免重复插入
- 如果 MySQL 执行失败,回退为输出 SQL 让用户手动执行,不中断整体流程
- 数据库连接参数默认 `mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3`,与 jeecg-codegen 保持一致
- 输出结果中标注 `菜单 SQL已自动执行 ✓`
---
## 编辑已有表单
如果用户要修改已有表单,需提供表单 ID 或编码,然后:
1. 查询现有表单设计 JSON
2. 根据用户需求修改 JSON
3. 调用 `PUT /desform/edit` 保存(注意带上正确的 `updateCount`
---
## 删除表单
`delete_form` 已封装完整的删除流程(查找 → 逻辑删除 → 物理删除),支持 3 种调用方式:
```python
from desform_utils import *
init_api('https://boot3.jeecg.com/jeecgboot', 'your-token')
# 方式1传 code自动查找 ID优先 queryByIdOrCode 快速查找)
delete_form('edu_teacher')
# 方式2传 code + 已知 ID跳过搜索最快
delete_form('edu_teacher', '2032994312457920514')
# 方式3只传 ID纯数字且长度>15 自动识别为 ID
delete_form('2032994312457920514')
```
**内部执行流程:**
1. 确定表单 ID传了 ID 直接用,传 code 则优先 `queryByIdOrCode` 快速查找,查不到再 fallback 到 list 全量搜索)
2. `DELETE /desform/deleteBatch?ids={id}` — 逻辑删除(放入回收站)
3. `DELETE /desform/recycleBin/deleteByIds?ids={id}` — 物理删除
**删除注意事项:**
- **不能跳过逻辑删除:** `recycleBin/deleteByIds` 只删除 `del_flag=1` 的记录,必须先执行 `deleteBatch`
- **同一 code 可能存在多条记录:** 传 code 时会自动处理多条记录全部删除
- **批量删除时传 ID 更快:** 创建时已获取 ID删除时直接传入可跳过查询
---
## 错误处理
| 错误 | 解决方案 |
|------|---------|
| Token 过期401/认证失败) | 提示用户重新获取 X-Access-Token |
| `该code已存在` | **不要直接覆盖**,提示用户确认是否覆盖(参见 Step 2.5 防覆盖规则),用户确认后再用 `update_form` 更新设计 |
| `未找到对应实体` | 表单数据不一致(存在于 list 但无法编辑),需用 `deleteBatch` + `recycleBin/deleteByIds` 彻底删除后重建 |
| `表单编码过长` | desformCode 缩短到 200 字符以内 |
| `当前版本已过时,请刷新重试` | updateCount 传值错误,必须传当前值(通过 queryByIdOrCode 或 list 获取) |
| `add` 返回 `result: null` | 旧版后端行为,`desform_utils.py` 已自动 fallback 到 list 搜索;新版后端已直接返回实体 |
| `queryByCode` 返回 false | 该接口不可靠,改用 `queryByIdOrCode` 或 list 全量搜索 |
| 中文乱码 | 确认使用 Python urllib不要用 curl |
| 连接超时 | 确认后端地址可达,检查网络 |
## 参考文档
- `scripts/desform_creator.py`**通用表单创建脚本**,优先使用此脚本 + JSON 配置文件
- `scripts/desform_utils.py`**共通工具库**控件工厂、API 封装、布局引擎)
- `references/desform-design-json-schema.md` — JSON Schema 结构、控件类型清单、通用字段
- `references/desform-widget-options.md` — 每种控件的完整 options 配置
- `references/desform-examples.md` — 常见表单模式示例 + Python 脚本模板
- `references/desform-real-samples.md` — 真实业务表单案例(字典、半行、分区、公式、关联)

View File

@@ -0,0 +1,137 @@
# jeecg-aiform 使用指南
## 快速开始
### 1. 自然语言描述表单
直接告诉 AI 你需要什么表单:
```
帮我创建一个请假申请表单,包含:
- 姓名(必填)
- 请假类型(事假/病假/年假,单选)
- 开始日期
- 结束日期
- 请假天数
- 请假原因
- 附件
```
### 2. 提供连接信息
AI 会要求你提供:
- **后端地址**:如 `https://boot3.jeecg.com/jeecgboot`
- **X-Access-Token**:从浏览器 F12 → Network → 任意请求的 Headers 中复制
### 3. 确认后自动创建
AI 会展示表单摘要(字段列表、控件类型),确认后自动调用 API 创建。
---
## 多种描述方式
以下描述方式都会触发 AI 表单生成:
| 描述方式 | 示例 |
|---------|------|
| 直接说创建表单 | "创建一个员工信息表单" |
| 描述字段需求 | "我需要一个表单,有姓名、手机号、地址" |
| 描述业务场景 | "做一个采购单,包含采购明细子表" |
| 简短指令 | "AI设计表单报销申请" |
| 英文指令 | "generate a leave application form" |
---
## 支持的控件类型
### 基础字段
- 单行文本、多行文本、数字、整数、金额
### 选择字段
- 单选框、多选框、下拉选择、下拉树
### 日期时间
- 日期选择器、时间选择器、日期范围
### 交互控件
- 开关、评分、滑块、颜色选择器
### 上传控件
- 图片上传、文件上传
### 联系方式
- 手机、邮箱
### 富文本
- 富文本编辑器、Markdown 编辑器
### 高级字段
- 省市级联动、地图、定位、条码、自动编号、文本组合
### 人员组织
- 用户选择、部门选择、岗位选择、组织角色
### 关联控件
- 关联记录、他表字段、汇总
### 布局控件
- 设计子表(明细表)
---
## 修改已有表单
如果要修改已创建的表单:
```
修改表单 xxx增加一个"审批意见"多行文本字段
```
需要提供表单 ID 或编码。
---
## 常见场景示例
### 员工信息登记
```
创建员工信息登记表单:姓名(必填)、工号(必填)、手机、邮箱、部门、入职日期、备注、头像照片
```
### 请假申请
```
做一个请假申请表单:
- 申请人(用户选择,默认当前登录人)
- 请假类型(单选:事假/病假/年假/调休)
- 开始日期、结束日期
- 请假天数(整数)
- 请假原因(多行文本,必填)
- 附件
```
### 采购申请(带子表)
```
创建采购申请单:
主表字段:采购标题(必填)、采购部门、采购日期、总金额、备注
子表明细:物品名称、规格型号、数量(整数)、单价(金额)、小计(金额)
```
### 客户信息管理
```
创建客户信息表单:
- 客户名称(必填)、联系人、手机、邮箱 — 前两个一行两字段
- 所在地区(省市级联动)
- 客户类型(下拉:潜在客户/意向客户/成交客户)
- 客户等级评分5星
- 备注(富文本编辑器)
```
---
## 注意事项
1. **Token 有效期**X-Access-Token 有过期时间,过期后需重新获取
2. **表单编码唯一**:同一系统中 desformCode 不能重复
3. **设计器表单 vs Online 表单**:本 skill 只处理设计器表单desform不涉及 Online 表单
4. **创建后可在设计器中微调**AI 创建的表单可以在设计器界面中继续编辑完善

View File

@@ -0,0 +1,322 @@
# desformDesignJson Schema 参考
## 顶层结构
```json
{
"list": [ /* */ ],
"config": { /* */ }
}
```
## 全局配置config完整字段
```json
{
"formStyle": "normal",
"titleField": "input_xxx",
"showHeaderTitle": true,
"labelWidth": 100,
"labelPosition": "top",
"size": "small",
"dialogOptions": {
"top": 20,
"width": 1000,
"padding": { "top": 25, "right": 25, "bottom": 30, "left": 25 }
},
"disabledAutoGrid": false,
"designMobileView": false,
"enableComment": true,
"hasWidgets": ["input", "card", "textarea"],
"defaultLoadLargeControls": false,
"expand": { "js": "", "css": "", "url": { "js": "", "css": "" } },
"transactional": true,
"customRequestURL": [{ "url": "" }],
"disableMobileCss": true,
"allowExternalLink": false,
"externalLinkShowData": false,
"headerImgUrl": "",
"externalTitle": "",
"enableNotice": false,
"noticeMode": "external",
"noticeType": "system",
"noticeReceiver": "",
"allowPrint": false,
"allowJmReport": false,
"jmReportURL": "",
"bizRuleConfig": [],
"bigDataMode": false
}
```
**关键字段说明:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `titleField` | String | 是 | 标题字段的 model key列表页显示用通常指向第一个 input |
| `hasWidgets` | String[] | 是 | 已使用的所有控件类型(包括 `card`),自动维护 |
| `labelPosition` | String | 否 | 标签位置:`"top"` / `"left"` / `"right"` |
| `size` | String | 否 | 控件尺寸:`"small"` / `"default"` / `"large"` |
## 控件列表list
list 是控件数组。**大部分控件被包裹在 `card` 容器中**。
### card 容器结构
```json
{
"key": "{timestamp}_{random6}",
"type": "card",
"isAutoGrid": true,
"isContainer": true,
"list": [ /* 1~2 */ ],
"options": {},
"model": "card_{timestamp}_{random6}"
}
```
### 控件通用结构
```json
{
"type": "input",
"name": "字段标签",
"className": "form-input",
"icon": "icon-input",
"hideTitle": false,
"options": { /* */ },
"advancedSetting": {
"defaultValue": {
"type": "compose",
"value": "",
"format": "string",
"allowFunc": true,
"valueSplit": "",
"customConfig": false
}
},
"remoteAPI": { "url": "", "executed": false },
"key": "{timestamp}_{random6}",
"model": "{type}_{timestamp}_{random6}",
"modelType": "main",
"rules": [],
"isSubItem": false
}
```
**通用字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `type` | String | 控件类型标识 |
| `name` | String | 控件显示名称(标签) |
| `className` | String | CSS 类名 |
| `icon` | String | 图标类名 |
| `hideTitle` | Boolean | 是否隐藏标题 |
| `hideLabel` | Boolean | 是否隐藏标签divider、text、buttons 等为 `true` |
| `options` | Object | 控件特有配置 |
| `advancedSetting` | Object | 高级默认值设置 |
| `remoteAPI` | Object | 远程数据源 |
| `key` | String | 唯一标识 |
| `model` | String | 数据绑定 Key |
| `modelType` | String | `"main"``"sub_one2one"` |
| `rules` | Array | 校验规则(必填时加 `[{"required": true, "message": "${title}必须填写"}]` |
| `defaultRules` | Array | 控件自带的默认校验phone/email/rate 等自动生成,无需手动设置) |
| `isSubItem` | Boolean | 是否为子表内控件 |
| `subOptions` | Object | 子表内控件专用:`{"width": "200px", "parentKey": "子表key"}` |
| `jeecg_auth` | Object | 权限控制:`{"enabled": true, "title": "名称", "field": "model值"}` |
| `mobileOptions` | Object | 移动端覆盖配置(可选,同 options 结构,仅移动端生效) |
| `dictOptions` | Array | 字典选项(使用字典数据源时,与 options 同级) |
| `event` | Object | 事件处理buttons 等控件):`{"click": "console.log('hello')"}` |
## key 和 model 生成规则
源码中 key 由 `randomKey()` 生成12-18 位随机字符串model 由 `type + '_' + key` 派生(中划线转下划线)。但 **时间戳+随机数** 格式同样被接受,两种方式都有效:
| 方式 | key 示例 | model 示例 |
|------|---------|------------|
| 源码 randomKey | `nRk92kK92sk` | `input_nRk92kK92sk` |
| 时间戳+随机数 | `1773452631695_489584` | `input_1773452631695_489584` |
**model 生成规则(源码 widgetUtils.js**
```javascript
let model = widget.type + '_' + key
model = model.replace(/-/g, '_') // 中划线 → 下划线
// link-record → link_record_xxx
```
**Python 生成方法(使用时间戳格式,实测可用):**
```python
import time
import random
def gen_key():
ts = int(time.time() * 1000)
rnd = random.randint(100000, 999999)
return f"{ts}_{rnd}"
def gen_model(widget_type):
ts = int(time.time() * 1000)
rnd = random.randint(100000, 999999)
safe_type = widget_type.replace('-', '_')
return f"{safe_type}_{ts}_{rnd}"
```
> 注意:每个控件的 key 和 model 必须全局唯一(保存时会检查重复 model。card 容器的 key/model 与内部控件的 key/model 也必须不同。
## AutoGrid 机制(自动栅格)
设计器中启用自适应(`config.disabledAutoGrid: false`)时,拖入非容器控件会自动包裹一个 `card``isAutoGrid: true`)。
### 不进入 AutoGrid 的控件(不需要 card 包裹)
| 控件 type | 说明 |
|-----------|------|
| `editor` | 富文本编辑器 |
| `markdown` | Markdown 编辑器 |
| `divider` | 分隔符 |
| `map` | 地图 |
| `link-record``showType: "table"``isSubTable: true` | 关联记录表格/子表模式 |
| `sub-table-design` | 设计子表 |
| `grid` | 栅格布局(本身是容器) |
| `card` | 卡片(本身是容器) |
| `tabs` | 选项卡(本身是容器) |
### 需要 card 容器的控件
所有其他控件,包括:
`input`, `textarea`, `number`, `integer`, `money`, `radio`, `checkbox`, `select`, `select-tree`, `date`, `time`, `switch`, `rate`, `color`, `slider`, `phone`, `email`, `imgupload`, `file-upload`, `buttons`, `text`, `area-linkage`, `location`, `capital-money`, `barcode`, `text-compose`, `auto-number`, `formula`, `hand-sign`, `ocr`, `link-record`showMode="single" 且 showType 非 table, `link-field`, `summary`, `select-user`, `select-depart`, `select-depart-post`, `org-role`
## 半行布局
一个 card 内放 2 个控件可实现半行布局,每个控件的 `options.autoWidth` 设为 `50`
```json
{
"type": "card",
"isAutoGrid": true,
"isContainer": true,
"list": [
{ "type": "input", "name": "姓名", "options": { "autoWidth": 50, ... }, ... },
{ "type": "phone", "name": "手机", "options": { "autoWidth": 50, ... }, ... }
],
...
}
```
## advancedSetting 的 format 值
| 控件数据类型 | format 值 |
|-------------|-----------|
| 文本类input、textarea、select、radio 等) | `"string"` |
| 数字类number、integer、money、slider | `"number"` |
| 多选带分隔checkbox、多选 select | `"string"` + `valueSplit: ","` + `customConfig: true` |
## rules 校验规则
**必填字段:**
```json
"rules": [{ "required": true, "message": "${title}必须填写" }]
```
**同时需要将 options 中的 required 设为 true。**
**自带校验的控件(有 defaultRules 字段):**
- `phone` — 自带手机号校验
- `email` — 自带邮箱校验
- `rate` — 自带 validator
## advancedSetting 详解
```json
{
"defaultValue": {
"type": "compose", // compose(静态+组合) | function(函数) | javascript(自定义JS) | linkage(关联查询) | none(无)
"value": "", // 默认值内容
"format": "string", // string | number | boolean
"allowFunc": true, // 是否允许在默认值中使用函数
"valueSplit": ",", // 多选控件的值分割符checkbox, radio, select 多选)
"customConfig": false // 是否需要自定义配置界面
}
}
```
**customConfig 为 true 的控件:** select、radio、checkbox多选场景、link-record、sub-table-design
## 布局容器结构
### grid — 栅格布局
```json
{
"type": "grid",
"isContainer": true,
"columns": [
{
"span": 12,
"options": {
"flex": false,
"flexAlignItems": "flex-start",
"flexJustifyContent": "start"
},
"list": []
},
{ "span": 12, "list": [] }
],
"options": {
"gutter": 8,
"justify": "start",
"align": "top",
"isWordStyle": false,
"hidden": false
}
}
```
### tabs — 选项卡
```json
{
"type": "tabs",
"isContainer": true,
"panes": [
{
"name": "Tab_xxx",
"label": "Tab1",
"rowNum": 1,
"hidden": false,
"hiddenOnAdd": false,
"list": []
}
],
"options": {
"width": "100%",
"activeName": "Tab_xxx",
"type": "border-card",
"position": "top",
"hidden": false
}
}
```
## 完整控件类型清单
### 基础组件
`input`, `textarea`, `number`, `integer`, `money`, `radio`, `checkbox`, `time`, `date`, `rate`, `color`, `select`, `switch`, `slider`
### 高级组件
`phone`, `email`, `imgupload`, `file-upload`, `editor`, `markdown`, `buttons`, `text`, `divider`, `area-linkage`, `map`, `location`, `capital-money`, `barcode`, `text-compose`, `auto-number`, `formula`, `hand-sign`, `ocr`
### 关联组件
`link-record`, `link-field`, `sub-table-design`, `table-dict`, `select-tree`, `summary`
### 布局组件
`grid`, `card`, `tabs`
### 系统组件
`select-user`, `select-depart`, `select-depart-post`, `org-role`
### OA 组件
`oa-approval-comments`, `x_oa_timeout_date`, `x_oa_official_doc_no`, `oa-sign-holiday-select`, `oa-leave-date-select`, `oa-sign-patch-select`

View File

@@ -0,0 +1,798 @@
# 常见表单模式示例
## 模式 A简单信息录入表单
**场景:** 员工信息登记(姓名、手机、邮箱、部门、备注)
```python
import urllib.request
import json
import time
import random
API_BASE = 'https://boot3.jeecg.com/jeecgboot'
TOKEN = 'your-jwt-token-here'
def api_request(path, data=None, method='POST'):
url = f'{API_BASE}{path}'
headers = {
'X-Access-Token': TOKEN,
'X-Sign': '00000000000000000000000000000000',
'X-Tenant-Id': '1',
'X-Timestamp': str(int(time.time() * 1000)),
'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 gen_ids(widget_type):
"""生成 key 和 modeltype 中的 - 转为 _"""
ts = int(time.time() * 1000)
rnd1 = random.randint(100000, 999999)
rnd2 = random.randint(100000, 999999)
rnd3 = random.randint(100000, 999999)
key = f"{ts}_{rnd1}"
safe_type = widget_type.replace('-', '_')
model = f"{safe_type}_{ts}_{rnd2}"
card_key = f"{ts + 1}_{rnd3}"
card_model = f"card_{ts + 1}_{rnd3}"
return key, model, card_key, card_model
def make_card_widget(widget_type, name, class_name, icon, options, required=False, extra_fields=None):
"""创建一个带 card 容器的控件"""
key, model, card_key, card_model = gen_ids(widget_type)
time.sleep(0.002) # 确保时间戳不同
widget = {
"type": widget_type,
"name": name,
"className": class_name,
"icon": icon,
"hideTitle": False,
"options": options,
"advancedSetting": {
"defaultValue": {
"type": "compose",
"value": "",
"format": "string",
"allowFunc": True,
"valueSplit": "",
"customConfig": False
}
},
"remoteAPI": {"url": "", "executed": False},
"key": key,
"model": model,
"modelType": "main",
"rules": [{"required": True, "message": "${title}必须填写"}] if required else [],
"isSubItem": False
}
if extra_fields:
widget.update(extra_fields)
card = {
"key": card_key,
"type": "card",
"isAutoGrid": True,
"isContainer": True,
"list": [widget],
"options": {},
"model": card_model
}
return card, model
# ---- 构建表单字段 ----
widgets = []
used_types = set(["card"])
title_model = None
# 1. 姓名input必填标题字段
card, model = make_card_widget("input", "姓名", "form-input", "icon-input", {
"width": "100%", "defaultValue": "", "required": True,
"dataType": None, "pattern": "", "patternMessage": "",
"placeholder": "", "clearable": False, "readonly": False,
"disabled": False, "fillRuleCode": "", "showPassword": False,
"unique": False, "hidden": False, "hiddenOnAdd": False,
"fieldNote": "", "autoWidth": 100
}, required=True)
widgets.append(card)
used_types.add("input")
title_model = model
# 2. 手机phone
card, _ = make_card_widget("phone", "手机", "form-input-phone", "icon-mobile-phone", {
"width": "300px", "defaultValue": "", "required": False,
"placeholder": "", "readonly": False, "disabled": False,
"unique": False, "hidden": False, "showVerifyCode": False,
"hiddenOnAdd": False, "fieldNote": "", "autoWidth": 100
}, extra_fields={
"defaultRules": [
{"type": "phone", "message": "请输入正确的手机号码"},
{"type": "validator", "message": "", "trigger": "blur"}
]
})
widgets.append(card)
used_types.add("phone")
# 3. 邮箱email
card, _ = make_card_widget("email", "邮箱", "form-input-email", "icon-email", {
"width": "300px", "defaultValue": "", "required": False,
"placeholder": "", "readonly": False, "disabled": False,
"unique": False, "hidden": False, "showVerifyCode": False,
"hiddenOnAdd": False, "fieldNote": "", "autoWidth": 100
}, extra_fields={
"defaultRules": [
{"type": "email", "message": "请输入正确的邮箱地址"},
{"type": "validator", "message": "", "trigger": "blur"}
]
})
widgets.append(card)
used_types.add("email")
# 4. 部门select-depart
card, _ = make_card_widget("select-depart", "部门", "form-select-depart", "icon-depart", {
"keyMaps": [], "defaultValue": "", "defaultLogin": False,
"placeholder": "", "width": "100%", "multiple": False,
"disabled": False, "customReturnField": "id",
"hidden": False, "dataAuthType": "member",
"hiddenOnAdd": False, "required": False, "fieldNote": "", "autoWidth": 100
})
widgets.append(card)
used_types.add("select-depart")
# 5. 备注textarea
card, _ = make_card_widget("textarea", "备注", "form-textarea", "icon-textarea", {
"width": "100%", "defaultValue": "", "required": False,
"disabled": False, "pattern": "", "patternMessage": "",
"placeholder": "", "readonly": False, "unique": False,
"hidden": False, "hiddenOnAdd": False, "fieldNote": "", "autoWidth": 100
})
widgets.append(card)
used_types.add("textarea")
# ---- 构建完整 JSON ----
design_json = {
"list": widgets,
"config": {
"formStyle": "normal",
"titleField": title_model,
"showHeaderTitle": True,
"labelWidth": 100,
"labelPosition": "top",
"size": "small",
"dialogOptions": {
"top": 20, "width": 1000,
"padding": {"top": 25, "right": 25, "bottom": 30, "left": 25}
},
"disabledAutoGrid": False,
"designMobileView": False,
"enableComment": True,
"hasWidgets": sorted(list(used_types)),
"defaultLoadLargeControls": False,
"expand": {"js": "", "css": "", "url": {"js": "", "css": ""}},
"transactional": True,
"customRequestURL": [{"url": ""}],
"disableMobileCss": True,
"allowExternalLink": False,
"externalLinkShowData": False,
"headerImgUrl": "",
"externalTitle": "",
"enableNotice": False,
"noticeMode": "external",
"noticeType": "system",
"noticeReceiver": "",
"allowPrint": False,
"allowJmReport": False,
"jmReportURL": "",
"bizRuleConfig": [],
"bigDataMode": False
}
}
# ---- 创建或查询表单 ----
form_name = "员工信息登记"
form_code = "yuan_gong_xin_xi_deng_ji"
# 先检查是否已存在(避免重复创建报错)
try:
query_result = api_request(f'/desform/queryByCode?desformCode={form_code}', method='GET')
if query_result.get('success') and query_result.get('result'):
form_id = query_result['result']['id']
update_count = query_result['result'].get('updateCount', 1)
print(f'表单已存在ID: {form_id},将更新设计...')
else:
raise Exception('not found')
except:
# 创建新表单注意add 返回的 result 为 null不能直接取 ID
add_result = api_request('/desform/add', {
'desformName': form_name,
'desformCode': form_code
})
if not add_result.get('success'):
print('创建失败!', add_result)
exit(1)
# 必须通过 queryByCode 获取表单 ID
query_result = api_request(f'/desform/queryByCode?desformCode={form_code}', method='GET')
form_id = query_result['result']['id']
update_count = query_result['result'].get('updateCount', 1)
print(f'表单ID: {form_id}')
# ---- 保存设计updateCount 传当前值,后端自动递增) ----
edit_result = api_request('/desform/edit', {
'id': form_id,
'desformDesignJson': json.dumps(design_json, ensure_ascii=False),
'updateCount': update_count,
'autoNumberDesignConfig': {'update': {}, 'current': {}},
'refTableDefaultValDbSync': {'changes': {}, 'removeKeys': []}
}, method='PUT')
print('保存结果:', json.dumps(edit_result, ensure_ascii=False, indent=2))
if edit_result.get('success'):
print(f'\n表单创建成功!')
print(f'表单ID: {form_id}')
print(f'表单名称: {form_name}')
print(f'表单编码: {form_code}')
else:
print('保存失败!')
```
---
## 模式 B带选项的审批表单
**场景:** 请假申请(请假类型单选、日期范围、天数、原因、附件)
在模式 A 基础上,字段构建部分替换为:
```python
# 请假类型radio必填
card, _ = make_card_widget("radio", "请假类型", "form-radio", "icon-radio-active", {
"inline": True, "matrixWidth": 120, "defaultValue": "",
"showType": "default", "showLabel": False, "useColor": False,
"colorIteratorIndex": 3,
"options": [
{"value": "事假", "itemColor": "#2196F3"},
{"value": "病假", "itemColor": "#08C9C9"},
{"value": "年假", "itemColor": "#00C345"},
{"value": "婚假", "itemColor": "#FF9800"}
],
"required": True, "width": "", "remote": False,
"remoteOptions": [], "props": {"value": "value", "label": "label"},
"remoteFunc": "", "disabled": False, "hidden": False,
"hiddenOnAdd": False, "fieldNote": "", "autoWidth": 100
}, required=True)
# radio 的 advancedSetting 需要特殊设置
card["list"][0]["advancedSetting"]["defaultValue"]["valueSplit"] = ","
card["list"][0]["advancedSetting"]["defaultValue"]["customConfig"] = True
widgets.append(card)
used_types.add("radio")
# 开始日期date必填
card, _ = make_card_widget("date", "开始日期", "form-date", "icon-date", {
"defaultValue": "", "defaultValueType": 1,
"readonly": False, "disabled": False, "editable": True,
"clearable": True, "placeholder": "", "startPlaceholder": "",
"endPlaceholder": "", "designType": "date", "type": "date",
"format": "yyyy-MM-dd", "timestamp": True, "required": True,
"width": "", "hidden": False, "hiddenOnAdd": False,
"fieldNote": "", "autoWidth": 50 # 半行
}, required=True)
widgets.append(card)
used_types.add("date")
```
---
## 模式 C半行布局一行两字段
一个 card 内放两个控件,每个 autoWidth 设为 50
```python
ts = int(time.time() * 1000)
card = {
"key": f"{ts + 1}_{random.randint(100000, 999999)}",
"type": "card",
"isAutoGrid": True,
"isContainer": True,
"list": [
{
"type": "input",
"name": "姓名",
"className": "form-input",
"icon": "icon-input",
"hideTitle": False,
"options": {
"width": "100%", "defaultValue": "", "required": True,
"dataType": None, "pattern": "", "patternMessage": "",
"placeholder": "", "clearable": False, "readonly": False,
"disabled": False, "fillRuleCode": "", "showPassword": False,
"unique": False, "hidden": False, "hiddenOnAdd": False,
"fieldNote": "", "autoWidth": 50 # 半行
},
"advancedSetting": {"defaultValue": {"type": "compose", "value": "", "format": "string", "allowFunc": True, "valueSplit": "", "customConfig": False}},
"remoteAPI": {"url": "", "executed": False},
"key": f"{ts}_{random.randint(100000, 999999)}",
"model": f"input_{ts}_{random.randint(100000, 999999)}",
"modelType": "main",
"rules": [{"required": True, "message": "${title}必须填写"}],
"isSubItem": False
},
{
"type": "phone",
"name": "手机",
"className": "form-input-phone",
"icon": "icon-mobile-phone",
"hideTitle": False,
"options": {
"width": "300px", "defaultValue": "", "required": False,
"placeholder": "", "readonly": False, "disabled": False,
"unique": False, "hidden": False, "showVerifyCode": False,
"hiddenOnAdd": False, "fieldNote": "", "autoWidth": 50 # 半行
},
"defaultRules": [
{"type": "phone", "message": "请输入正确的手机号码"},
{"type": "validator", "message": "", "trigger": "blur"}
],
"advancedSetting": {"defaultValue": {"type": "compose", "value": "", "format": "string", "allowFunc": True, "valueSplit": "", "customConfig": False}},
"remoteAPI": {"url": "", "executed": False},
"key": f"{ts}_{random.randint(100000, 999999)}",
"model": f"phone_{ts}_{random.randint(100000, 999999)}",
"modelType": "main",
"rules": [],
"isSubItem": False
}
],
"options": {},
"model": f"card_{ts + 1}_{random.randint(100000, 999999)}"
}
```
---
## 模式 D带子表的表单
**场景:** 采购单主表 + 采购明细子表
子表控件直接放在 list 顶层(不需要 card
```python
sub_table_key = f"{int(time.time() * 1000)}_{random.randint(100000, 999999)}"
sub_table_model = f"sub_table_design_{int(time.time() * 1000)}_{random.randint(100000, 999999)}"
sub_table = {
"type": "sub-table-design",
"name": "采购明细",
"className": "form-sub-table",
"icon": "icon-table",
"hideTitle": False,
"class": ["data-j-editable-design"],
"isContainer": True,
"columns": [
{
"span": 12,
"list": [
{
"type": "input",
"name": "物品名称",
"className": "form-input",
"icon": "icon-input",
"hideTitle": False,
"options": {
"width": "100%", "defaultValue": "", "required": True,
"dataType": None, "pattern": "", "patternMessage": "",
"placeholder": "", "clearable": False, "readonly": False,
"disabled": False, "fillRuleCode": "", "showPassword": False,
"unique": False, "hidden": False, "hiddenOnAdd": False, "fieldNote": ""
},
"advancedSetting": {"defaultValue": {"type": "compose", "value": "", "format": "string", "allowFunc": True, "valueSplit": "", "customConfig": False}},
"remoteAPI": {"url": "", "executed": False},
"key": f"{int(time.time() * 1000)}_{random.randint(100000, 999999)}",
"model": f"input_{int(time.time() * 1000)}_{random.randint(100000, 999999)}",
"modelType": "main",
"rules": [{"required": True, "message": "${title}必须填写"}],
"isSubItem": True,
"subOptions": {"width": "200px", "parentKey": sub_table_key}
},
{
"type": "number",
"name": "数量",
"className": "form-number",
"icon": "icon-number",
"hideTitle": False,
"options": {
"width": "", "required": False, "defaultValue": 0,
"placeholder": "", "controls": False,
"min": 0, "minUnlimited": True, "max": 100, "maxUnlimited": True,
"step": 1, "disabled": False, "controlsPosition": "right",
"unitText": "", "unitPosition": "suffix", "showPercent": False,
"align": "left", "hidden": False, "hiddenOnAdd": False, "fieldNote": ""
},
"advancedSetting": {"defaultValue": {"type": "compose", "value": "", "format": "number", "allowFunc": True, "valueSplit": "", "customConfig": False}},
"remoteAPI": {"url": "", "executed": False},
"key": f"{int(time.time() * 1000)}_{random.randint(100000, 999999)}",
"model": f"number_{int(time.time() * 1000)}_{random.randint(100000, 999999)}",
"modelType": "main",
"rules": [],
"isSubItem": True,
"subOptions": {"width": "200px", "parentKey": sub_table_key}
}
]
},
{
"span": 12,
"list": [
{
"type": "money",
"name": "单价",
"className": "form-money",
"icon": "icon-money",
"hideTitle": False,
"options": {
"width": "180px", "placeholder": "请输入金额",
"required": False, "unitText": "", "unitPosition": "suffix",
"precision": 2, "hidden": False, "disabled": False,
"hiddenOnAdd": False, "fieldNote": ""
},
"advancedSetting": {"defaultValue": {"type": "compose", "value": "", "format": "number", "allowFunc": True, "valueSplit": "", "customConfig": False}},
"remoteAPI": {"url": "", "executed": False},
"key": f"{int(time.time() * 1000)}_{random.randint(100000, 999999)}",
"model": f"money_{int(time.time() * 1000)}_{random.randint(100000, 999999)}",
"modelType": "main",
"rules": [],
"isSubItem": True,
"subOptions": {"width": "200px", "parentKey": sub_table_key}
}
]
}
],
"options": {
"isWordStyle": False, "isWordInnerGrid": False, "gutter": 0,
"columnNumber": 2, "operationMode": 1, "justify": "start", "align": "top",
"defaultValue": [], "subTableName": "", "defaultRows": 0,
"showCheckbox": True, "showNumber": True, "showRowButton": False,
"allowAdd": True, "autoHeight": True, "defaultValType": "none",
"hidden": False, "hiddenOnAdd": False, "required": False, "fieldNote": ""
},
"advancedSetting": {"defaultValue": {"type": "compose", "value": "", "format": "string", "allowFunc": True, "valueSplit": "", "customConfig": True}},
"key": sub_table_key,
"model": sub_table_model,
"modelType": "main",
"rules": [],
"isSubItem": False
}
# 直接加入 list 顶层(不需要 card 容器)
widgets.append(sub_table)
used_types.add("sub-table-design")
```
**子表控件要点:**
1. 子控件 `isSubItem: True`
2. 子控件有 `subOptions: { "width": "200px", "parentKey": "子表的key" }`
3. 子控件的 options 没有 `autoWidth` 字段
4. `columns` 数组中每个元素有 `span`(栅格宽度)和 `list`(控件列表)
---
## 模式 E多子表订单表单
**场景:** 订单表(主表 + 商品明细 + 收款记录 + 发货记录 三个子表)
在模式 A 基础上,使用 `make_widget` + `make_card` + `make_sub_table` 分离构建:
```python
def make_widget(widget_type, name, class_name, icon, options, required=False, is_sub=False, parent_key=None, extra=None):
"""创建控件支持主表和子表控件type 中的 - 自动转 _"""
ts = int(time.time() * 1000)
rnd1 = random.randint(100000, 999999)
rnd2 = random.randint(100000, 999999)
key = f"{ts}_{rnd1}"
safe_type = widget_type.replace('-', '_')
model = f"{safe_type}_{ts}_{rnd2}"
time.sleep(0.003)
fmt = "number" if widget_type in ("number", "integer", "money", "slider") else "string"
custom = widget_type in ("radio", "checkbox", "select", "link-record", "sub-table-design")
w = {
"type": widget_type, "name": name, "className": class_name, "icon": icon,
"hideTitle": False, "options": options,
"remoteAPI": {"url": "", "executed": False},
"key": key, "model": model, "modelType": "main",
"rules": [{"required": True, "message": "${title}必须填写"}] if required else [],
"isSubItem": is_sub
}
# link-field 不需要 advancedSetting其他控件都需要
if widget_type != "link-field":
w["advancedSetting"] = {"defaultValue": {
"type": "compose", "value": "", "format": fmt,
"allowFunc": True, "valueSplit": "," if custom else "", "customConfig": custom
}}
if is_sub and parent_key:
w["subOptions"] = {"width": "200px", "parentKey": parent_key}
if extra:
w.update(extra)
return w, key, model
def make_card(*widgets_list):
"""创建 card 容器,支持放入 1~2 个控件"""
ts = int(time.time() * 1000)
rnd = random.randint(100000, 999999)
time.sleep(0.003)
return {
"key": f"{ts}_{rnd}", "type": "card", "isAutoGrid": True,
"isContainer": True, "list": list(widgets_list),
"options": {}, "model": f"card_{ts}_{rnd}"
}
def make_sub_table(name, sub_widgets):
"""创建子表sub_widgets 为子控件列表"""
ts = int(time.time() * 1000)
rnd1 = random.randint(100000, 999999)
rnd2 = random.randint(100000, 999999)
st_key = f"{ts}_{rnd1}"
st_model = f"sub_table_design_{ts}_{rnd2}"
time.sleep(0.003)
return {
"type": "sub-table-design", "name": name,
"className": "form-sub-table", "icon": "icon-table",
"hideTitle": False, "class": ["data-j-editable-design"],
"isContainer": True,
"columns": [{"span": 24, "list": sub_widgets}],
"options": {
"isWordStyle": False, "isWordInnerGrid": False, "gutter": 0,
"columnNumber": 2, "operationMode": 1, "justify": "start", "align": "top",
"defaultValue": [], "subTableName": "", "defaultRows": 0,
"showCheckbox": True, "showNumber": True, "showRowButton": False,
"allowAdd": True, "autoHeight": True, "defaultValType": "none",
"hidden": False, "hiddenOnAdd": False, "required": False, "fieldNote": ""
},
"advancedSetting": {"defaultValue": {"type": "compose", "value": "", "format": "string", "allowFunc": True, "valueSplit": "", "customConfig": True}},
"key": st_key, "model": st_model, "modelType": "main",
"rules": [], "isSubItem": False
}, st_key
# 构建子表(先创建子表获取 key再创建子控件绑定 parentKey
sub_widgets = []
# 先占位获取子表 key
sub, sub_key = make_sub_table("商品明细", [])
# 创建子控件
w, _, _ = make_widget("input", "商品名称", "form-input", "icon-input",
{...}, required=True, is_sub=True, parent_key=sub_key)
sub_widgets.append(w)
w, _, _ = make_widget("integer", "数量", "form-integer", "icon-integer",
{...}, required=True, is_sub=True, parent_key=sub_key)
sub_widgets.append(w)
# 更新子表的 columns
sub["columns"] = [{"span": 24, "list": sub_widgets}]
all_widgets.append(sub)
```
**多子表要点:**
1. 每个子表独立调用 `make_sub_table()` 获取 `st_key`
2. 子控件创建时传 `is_sub=True, parent_key=st_key`
3. 子控件的 options **没有** `autoWidth` 字段
4. 多个子表按顺序追加到 `all_widgets`(顶层 list
5. `config.hasWidgets` 中只需加一次 `"sub-table-design"`
---
## 执行模板(完整脚本框架)
生成脚本时遵循此框架:
```
1. 导入依赖 (urllib, json, time, random)
2. 配置 API_BASE 和 TOKEN
3. 定义工具函数 (api_request, make_widget, make_card, make_sub_table)
4. 构建各字段控件(主表 + 子表)
5. 组装 design_json (list + config)
6. 检查表单是否已存在GET /desform/queryByCode
7. 不存在则创建POST /desform/add再查询获取 ID
8. 保存设计PUT /desform/editupdateCount 传当前值
9. 输出结果
```
**关键注意事项:**
- `time.sleep(0.003)` 确保每个控件的时间戳不同
- `config.hasWidgets` 必须包含所有使用到的控件 type包括 `card`
- `config.titleField` 必须指向一个实际存在的控件 model
- 必填字段需要同时设置 `options.required = True``rules = [{"required": True, ...}]`
- 数字类控件的 advancedSetting.defaultValue.format 应为 `"number"`
- **`POST /desform/add` 返回 `result: null`**,必须用 `GET /desform/queryByCode` 获取 ID
- **`updateCount` 传当前数据库值**(不是 +1后端自动递增
- **先检查表单是否存在**,避免重复创建报 `该code已存在` 错误
---
## 实战踩坑清单className / icon 易错汇总)
生成表单时最容易出错的是 className 和 icon以下为**实测验证的正确值**
| 控件 type | className | icon | 特殊说明 |
|-----------|-----------|------|----------|
| `link-record` | `form-link-record` | **`icon-link`** | 不是 `icon-link-record` |
| `link-field` | `form-link-field` | **`icon-field`** | 不是 `icon-link-field` |
| `sub-table-design` | **`form-sub-table`** | **`icon-table`** | 不是 `form-sub-table-design` / `icon-sub-table-design` |
| `divider` | `form-divider` | `icon-fengexian` | |
### link-record 踩坑要点
1. **`advancedSetting.defaultValue.customConfig` 必须为 `true`**
2. **`allowView``allowEdit``allowAdd``allowSelect` 必须全部设为 `true`**4 个操作选项默认全部勾选)
3. **`titleField` 必须填源表的真实标题字段 model**(如 `input_xxx`),不能留空
4. **`showFields` 建议填入源表中需要展示的字段 model 列表**
5. **跨表单关联时**,必须先创建被引用的表单,然后查询获取其字段 model再构建引用方的 link-record
### link-field 踩坑要点
1. **link-field 没有 `advancedSetting`** — 与其他控件不同
2. **`linkRecordKey` 填的是 link-record 的 key**(如 `1773457559119_461003`**不是 model**
3. **`fieldType` 必须填来源字段的真实控件类型**(如 `"input"`, `"select-user"`, `"money"`),不能一律写 `"input"`
4. **`fieldOptions` 需包含源字段类型相关的 options 子集**,例如:
- select-user: `{"multiple": false, "customReturnField": "username"}`
- select (多选): `{"multiple": true}`
- 普通 input/money 等: `{}` 即可
### sub-table-design 踩坑要点
1. **options 必须包含 `allowAdd: true`**,否则子表没有"添加"按钮
2. **完整 options 字段(缺一不可):**
```json
{
"isWordStyle": false, "isWordInnerGrid": false, "gutter": 0,
"columnNumber": 2, "operationMode": 1, "justify": "start", "align": "top",
"defaultValue": [], "subTableName": "", "defaultRows": 0,
"showCheckbox": true, "showNumber": true, "showRowButton": false,
"allowAdd": true, "autoHeight": true, "defaultValType": "none",
"hidden": false, "hiddenOnAdd": false, "required": false, "fieldNote": ""
}
```
3. **子表内可以放 link-record + link-field**,实现子表行级关联选择
### 跨表单关联的正确流程(多表单批量创建)
```
1. 先创建基础表单(如商品表、仓库表、供应商表)
2. 查询基础表单的 designJson提取字段 model 和 titleField
3. 构建业务表单(如入库单)时:
a. link-record.options.sourceCode = 基础表单的 desformCode
b. link-record.options.titleField = 基础表单的 titleField model
c. link-record.options.showFields = [基础表单中需要展示的字段 model 列表]
d. link-field.options.linkRecordKey = 同表单中 link-record 控件的 KEY
e. link-field.options.showField = 基础表单中要自动填充的字段 MODEL
f. link-field.options.fieldType = 该字段的实际控件类型
```
### Python 中查询基础表单字段的方法
```python
def get_form_fields(form_code):
"""查询表单设计JSON提取字段 model/key/type"""
# 注意queryByCode 不可靠,推荐用 queryByIdOrCode 或 desform_utils.py 中的 get_form_fields
q = api_request(f'/desform/queryByIdOrCode?desformCode={form_code}', method='GET')
design = json.loads(q['result']['desformDesignJson'])
title_field = design['config']['titleField']
fields = {}
def extract(items):
for item in items:
if item.get('type') == 'card' and 'list' in item:
extract(item['list'])
elif 'model' in item and item.get('type') != 'card':
fields[item['name']] = {
'model': item['model'],
'key': item['key'],
'type': item['type']
}
extract(design.get('list', []))
return title_field, fields
```
## 模式 F使用 desform_utils.py 的跨表单 CRM 系统(推荐)
**场景:** CRM 客户管理系统4 个关联表单),使用共通工具库 `desform_utils.py`
> **命名规则:** 模块名作为前缀,如 `crm_customer`(不是 `customer_crm`
```python
"""CRM系统 - 4个关联表单"""
import sys, time
sys.path.insert(0, r'{后端项目根目录}')
from desform_utils import *
init_api('https://boot3.jeecg.com/jeecgboot', 'your-token')
# ---- 1/4 客户信息(基础表单,被其他表单引用)----
fid1, uc1 = find_or_create_form('客户信息', 'crm_customer')
w0, _, m0 = INPUT('客户名称', required=True)
w1, _, _ = AUTONUMBER('客户编号', prefix='CRM')
w2, _, _ = SELECT('客户类型', ['企业客户', '个人客户'], required=True)
w3, _, _ = SELECT('所属行业', ['IT互联网', '金融', '制造业', '教育', '医疗', '房地产', '零售', '其他'])
w4, _, _ = SELECT('客户来源', ['官网', '转介绍', '电话营销', '展会', '广告', '社交媒体', '其他'])
w5, _, _ = RADIO('客户等级', ['A-重要', 'B-普通', 'C-一般'])
w6, _, _ = RADIO('客户状态', ['潜在客户', '意向客户', '成交客户', '流失客户'])
w7, _, _ = PHONE('联系电话')
w8, _, _ = EMAIL('邮箱')
w9, _, _ = AREA('所在地区')
w10, _, _ = INPUT('详细地址')
w11, _, _ = USER('负责销售', required=True)
w12, _, _ = DEPART('所属部门')
w13, _, _ = TEXTAREA('备注')
save_design(fid1, 'crm_customer', [w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12,w13], m0, uc1)
# 查询客户表单字段,供后续表单关联引用
time.sleep(1)
cust_tf, cust_fields = get_form_fields('crm_customer')
cust_show = [cust_fields['客户编号']['model'], cust_fields['联系电话']['model']]
# ---- 2/4 联系人(关联客户)----
fid2, uc2 = find_or_create_form('联系人', 'crm_contact')
w0, _, m0 = INPUT('联系人姓名', required=True)
w1, _, _ = LINK_RECORD('所属客户', 'crm_customer', cust_tf, cust_show, required=True)
lr_key = w1['list'][0]['key'] # 获取 link-record 的 key供 link-field 引用
w2, _, _ = LINK_FIELD('客户编号', lr_key, cust_fields['客户编号']['model'], field_type='auto-number')
w3, _, _ = INPUT('职务')
w4, _, _ = INPUT('部门')
w5, _, _ = PHONE('手机号码', required=True)
w6, _, _ = EMAIL('邮箱')
w7, _, _ = SWITCH('是否决策人')
w8, _, _ = TEXTAREA('备注')
save_design(fid2, 'crm_contact', [w0,w1,w2,w3,w4,w5,w6,w7,w8], m0, uc2)
# ---- 3/4 商机管理(关联客户 + 分隔符 + 金额 + 滑块)----
fid3, uc3 = find_or_create_form('商机管理', 'crm_opportunity')
w0, _, m0 = INPUT('商机名称', required=True)
w1, _, _ = AUTONUMBER('商机编号', prefix='BIZ')
w2, _, _ = LINK_RECORD('关联客户', 'crm_customer', cust_tf, cust_show, required=True)
lr_key = w2['list'][0]['key']
w3, _, _ = LINK_FIELD('客户编号', lr_key, cust_fields['客户编号']['model'], field_type='auto-number')
w4, _, _ = DIVIDER('商机详情')
w5, _, _ = SELECT('商机阶段', ['初步接触', '需求确认', '方案报价', '商务谈判', '赢单', '输单'], required=True)
w6, _, _ = MONEY('预计金额', required=True)
w7, _, _ = DATE('预计成交日期')
w8, _, _ = USER('负责人', required=True)
w9, _, _ = USER('协作人', multiple=True)
w10, _, _ = SLIDER('赢单概率', show_input=True)
w11, _, _ = TEXTAREA('备注')
save_design(fid3, 'crm_opportunity', [w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11], m0, uc3)
# ---- 4/4 跟进记录(关联客户 + 默认当前用户)----
fid4, uc4 = find_or_create_form('跟进记录', 'crm_follow_up')
w0, _, m0 = INPUT('跟进主题', required=True)
w1, _, _ = LINK_RECORD('关联客户', 'crm_customer', cust_tf, cust_show, required=True)
lr_key = w1['list'][0]['key']
w2, _, _ = LINK_FIELD('客户名称', lr_key, cust_fields['客户名称']['model'], field_type='input')
w3, _, _ = RADIO('跟进方式', ['电话', '拜访', '微信', '邮件', '会议', '其他'], required=True)
w4, _, _ = DATE('跟进日期', required=True)
w5, _, _ = DATE('下次跟进日期')
w6, _, _ = USER('跟进人', required=True, default_login=True)
w7, _, _ = TEXTAREA('跟进内容', required=True)
w8, _, _ = FILE('附件')
save_design(fid4, 'crm_follow_up', [w0,w1,w2,w3,w4,w5,w6,w7,w8], m0, uc4)
# ---- 输出菜单 + 角色授权 SQL ----
print(gen_menu_sql('crm_menu', 'CRM客户管理', [
('crm_customer_menu', '客户信息', 'crm_customer', 1),
('crm_contact_menu', '联系人', 'crm_contact', 2),
('crm_opportunity_menu', '商机管理', 'crm_opportunity', 3),
('crm_follow_up_menu', '跟进记录', 'crm_follow_up', 4),
]))
```
**跨表单关联要点:**
1. 先创建被引用的基础表单 → `save_design` → `time.sleep(1)` → `get_form_fields` 获取字段信息
2. `LINK_RECORD` 需要源表编码、titleField、showFields
3. `LINK_FIELD` 需要 link-record 的 **key**(通过 `w['list'][0]['key']` 获取)和源字段的 model + field_type
4. link-record 的 4 个操作选项allowView/allowEdit/allowAdd/allowSelect已在 desform_utils.py 中默认全部开启

View File

@@ -0,0 +1,288 @@
# 真实表单设计案例参考
> 从 JeecgBoot 演示系统 `design_form` 表中提取的真实表单配置,涵盖 OA、HR、行政等常见业务场景。
---
## 1. 字典翻译示例 (demo_test_dict_transl)
**场景:** 展示 radio/checkbox/select 的三种数据源(静态、字典、远程)
**控件清单18个**
- 单行文本 (input)
- 单选框组_远程数据 (radio) — `remote: true, remoteFunc: "http://..."`
- 单选框组_静态数据 (radio) — `showLabel: true, options: [{value:"1",label:"数学"}, ...]`
- 单选框组 (radio) — **`remote: "dict", dictCode: "sex"`**
- 多选框组 (checkbox) — **`remote: "dict", dictCode: "sex"`**
- 多选框组_静态数据 (checkbox) — 静态选项
- 下拉选择框 (select) — `showLabel: true, options: [{value:"1",label:"选项1"}, ...]`
- 下拉选择框_多选 (select) — `multiple: true`
- 下拉选择框_字典 (select) — **`remote: "dict", dictCode: "urgent_level"`**
- 开关 (switch)
- 省市级联动 (area-linkage)
- 用户组件 (select-user) — `multiple: true`
- 部门组件 (select-depart) — `multiple: true`
- 下拉树_分类字典 (select-tree) — `multiple: true`
- 下拉树_表 (select-tree) — `multiple: true`
- 表字典_popup (table-dict) — `multiple: true`
- 表字典_模糊online (table-dict) — `multiple: true`
- 表字典_模糊表 (table-dict) — `multiple: true`
**关键配置模式 — 字典 radio**
```json
{
"type": "radio",
"name": "单选框组",
"options": {
"inline": true,
"showLabel": true,
"remote": "dict",
"dictCode": "sex",
"options": [
{"value": "选项1", "itemColor": "#e9e9e9"},
{"value": "选项2", "itemColor": "#e9e9e9"}
],
"remoteOptions": [],
"props": {"value": "value", "label": "label"},
"remoteFunc": "",
"useColor": false,
"showType": "default",
"colorIteratorIndex": 0,
"matrixWidth": 120
},
"advancedSetting": {
"defaultValue": {
"type": "compose", "value": "", "format": "string",
"allowFunc": true, "valueSplit": ",", "customConfig": true
}
}
}
```
**关键配置模式 — 静态 radio (showLabel+value/label)**
```json
{
"type": "radio",
"options": {
"showLabel": true,
"remote": false,
"options": [
{"value": "1", "label": "数学", "itemColor": "#e9e9e9"},
{"value": "2", "label": "语文", "itemColor": "#e9e9e9"},
{"value": "3", "label": "自然", "itemColor": "#e9e9e9"}
]
}
}
```
> **注意:** 当 `showLabel: true` 时,选项需要同时有 `value`(存储值)和 `label`(显示文本)。
> 当 `showLabel: false` 时,`value` 既是存储值也是显示文本。
---
## 2. 请假申请 (qing_jia_shen_qing_5qfk)
**场景:** 典型 OA 审批表单
**控件清单12个**
- 姓名 (select-user) — 必填,`defaultLogin: true`
- 所在部门 (select-depart)
- 申请日期 (date)
- 请假类型 (select) — 选项:事假/病假/年假/调休
- 开始日期 (date)
- 结束日期 (date)
- 天数 (number)
- 请假说明 (textarea)
- 审批意见 (radio) — 选项:同意/不同意
- 直属领导 (select-user)
- 审批时间 (date)
- 附件 (file-upload)
**config**
```json
{
"titleField": "select_user_1692952011928_137220",
"hasWidgets": ["select-user", "select-depart", "date", "card", "select", "number", "textarea", "radio", "file-upload"]
}
```
> **要点:** `titleField` 指向 select-user 控件(而非 input说明 titleField 可以指向任何控件类型。
---
## 3. 员工基本信息 (yuan_gong_ji_ben_xin_xi_dnjq)
**场景:** HR 员工档案,展示半行布局
**控件清单8个全部半行两两配对**
- [半行] 姓名 (select-user) | 所在部门 (select-depart)
- [半行] 岗位 (input) | 性别 (select) — 选项:男/女
- [半行] 入职时间 (date) | 参加工作时间 (date)
- [半行] 直属上级 (select-user) | 负责 HR (select-user)
**config**
```json
{
"titleField": "select_user_1692874017319_686764",
"hasWidgets": ["select-user", "select-depart", "card", "input", "select", "date"]
}
```
> **要点:** 整个表单全部使用半行布局(每个 card 内 2 个控件autoWidth: 50
---
## 4. 用车申请 (yong_che_shen_qing_gh3j)
**场景:** 行政用车,展示 area-linkage + formula + link-record + divider
**控件清单21个**
- 申请日期 (date) — 必填
- 申请人 (select-user) — 必填
- 申请部门 (select-depart)
- 用车人数 (integer) — 必填
- 要求用车时间 (date)
- 出发地 (area-linkage)
- 出发地详细地址 (input)
- 目的地 (area-linkage)
- 目的地详细地址 (input)
- 随行司机 (select-user)
- 用车理由 (textarea)
- **分隔符 (divider)**
- 出发时间 (date)
- 返程时间 (date)
- 车牌号码 (link-record) — 关联记录
- 起始公里数 (number)
- 到达公里数 (number)
- **行驶公里数 (formula)** — 公式计算
- 停车费 (money)
- 备注 (textarea)
- 附件 (file-upload)
**config**
```json
{
"hasWidgets": ["date", "select-user", "select-depart", "integer", "card", "area-linkage", "input", "textarea", "divider", "link-record", "number", "formula", "money", "file-upload"]
}
```
> **要点:**
> - 使用 `divider` 分隔"申请信息"和"用车记录"两个区域
> - `formula` 控件自动计算行驶公里数
> - `link-record` 关联车辆信息表
---
## 5. 工资表 (gong_zi_biao_zitx)
**场景:** HR 薪资管理,展示 divider 分区 + formula 计算 + money 字段
**控件清单17个**
- 工资发放时间 (date)
- **分隔符 (divider)** — 基本信息区
- 姓名 (select-user) — 必填
- 部门 (select-depart)
- 手机号码 (phone)
- **分隔符 (divider)** — 收入区
- 基本工资 (money)
- 加班工资 (money)
- 奖金 (money)
- 补贴 (money)
- **分隔符 (divider)** — 扣款区
- 本期扣款 (money)
- 五险一金扣款 (money)
- 个税扣除 (money)
- **实发金额 (formula)** — 公式自动计算
- 备注 (textarea)
- 附件 (file-upload)
**config**
```json
{
"hasWidgets": ["date", "card", "divider", "select-user", "select-depart", "phone", "money", "formula", "textarea", "file-upload"]
}
```
> **要点:**
> - 用多个 `divider` 将表单分为"基本信息"、"收入"、"扣款"三个区域
> - `formula` 控件计算实发金额 = 基本工资+加班+奖金+补贴-扣款-五险一金-个税
> - 大量使用 `money` 控件(带"元"后缀)
---
## 6. 会议预约 (hui_yi_yu_yue_0s2h)
**场景:** 行政会议管理,展示 link-record + link-field 关联
**控件清单14个**
- 预约人 (select-user) — 必填
- 所属部门 (select-depart) — 必填
- 当前时间 (date) — 必填
- 会议名称 (input) — 必填
- 会议室基础表 (link-record) — 关联记录
- 会议室名称 (link-field) — 他表字段(自动填充)
- 会议室编号 (link-field)
- 会议室容纳人数 (link-field)
- 参会人员 (select-user) — `multiple: true`(多选)
- 会议开始时间 (date)
- 会议结束时间 (date)
- 预约时间 (input)
- 备注 (textarea)
- 附件 (file-upload)
**config**
```json
{
"hasWidgets": ["select-user", "select-depart", "date", "card", "input", "link-record", "link-field", "textarea", "file-upload"]
}
```
> **要点:**
> - `link-record` 选择会议室后,`link-field` 自动填充关联数据(名称、编号、容纳人数)
> - `select-user` 支持 `multiple: true` 多选参会人员
---
## 7. 地图/定位/省市联动综合 (ceshi_ditu)
**场景:** 展示地图、表字典、下拉树、多选用户/部门
**控件清单9个**
- 用户组件 (select-user) — `multiple: true`
- 部门组件 (select-depart) — `multiple: true`
- 表字典_popupOL报表 (table-dict)
- 表字典_模糊OL报表 (table-dict)
- 表字典_模糊表 (table-dict)
- 下拉树_分类字典 (select-tree)
- 下拉树_表 (select-tree)
- 性别 (radio) — `remote: "dict", dictCode: "sex"`
- **地图 (map)** — 不需要 card 容器
> **要点:** `map` 控件直接放在顶层 list不需要 card 容器。
---
## 常用字典编码速查
从真实表单中收集到的字典编码:
| dictCode | 说明 | 使用场景 |
|----------|------|---------|
| `sex` | 性别 | radio/checkbox/select |
| `position_rank` | 职级 | select 多选 |
| `urgent_level` | 紧急程度 | select 多选 |
---
## 设计模式总结
| 模式 | 说明 | 示例表单 |
|------|------|---------|
| **字典数据源** | `remote:"dict"` + `dictCode` | 字典翻译示例 |
| **半行布局** | card 内两控件 `autoWidth:50` | 员工基本信息 |
| **分区分隔** | divider 控件分割表单区域 | 工资表、用车申请 |
| **公式计算** | formula 控件自动计算 | 工资表、用车申请 |
| **关联填充** | link-record + link-field | 会议预约 |
| **默认当前用户** | select-user `defaultLogin:true` | 请假申请 |
| **多选人员** | select-user `multiple:true` | 会议预约、字典示例 |
| **titleField 灵活** | 可指向 select-user 等非 input 控件 | 请假申请、员工信息 |

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,261 @@
"""
JeecgBoot 设计器表单通用创建脚本
=================================
通过 JSON 配置文件创建/更新设计器表单,避免每次编写大量 Python 代码。
用法:
python desform_creator.py --api-base <URL> --token <TOKEN> --config <config.json>
python desform_creator.py --api-base <URL> --token <TOKEN> --config <config.json> --force
参数:
--api-base JeecgBoot 后端地址
--token X-Access-Token
--config JSON 配置文件路径
--force 强制覆盖已存在的表单(默认检测到已存在时退出)
JSON 配置格式:
{
"formName": "工程竣工验收申请表",
"formCode": "eng_completion_acceptance",
"layout": "word", // auto|half|full|word默认 auto
"titleIndex": 0, // 标题字段索引,默认 0第一个非分隔符字段
"fields": [
{"name": "自动编号", "type": "auto-number", "prefix": "GCYS"},
{"name": "条码", "type": "barcode"},
{"name": "工程名称", "type": "input", "required": true},
{"name": "工程类别", "type": "radio", "options": ["土建", "安装"]},
{"name": "开工时间", "type": "date"},
{"name": "工程量清单", "type": "textarea"},
{"name": "图片上传", "type": "imgupload"},
{"name": "定位", "type": "location"},
{"name": "签字", "type": "hand-sign"},
{"name": "---", "type": "divider", "text": "第二部分"},
{"name": "金额", "type": "money", "unit": "万元"},
{"name": "状态", "type": "select", "options": ["启用", "禁用"]},
{"name": "性别", "type": "radio", "dictCode": "sex",
"options": [{"value": "1", "label": ""}, {"value": "2", "label": ""}]}
],
"menuParent": "工程管理", // 可选,生成菜单 SQL 的父菜单名称
"menuIcon": "ant-design:tool-outlined" // 可选,父菜单图标
}
支持的 type 值:
基础: input, textarea, number, integer, money, date, time, switch, slider, rate, color
选择: radio, select, checkbox支持 options + dictCode
系统: select-user, select-depart, phone, email, area-linkage
文件: file-upload, imgupload, hand-sign
高级: auto-number, formula, barcode, location, link-record, link-field
布局: divider, editor, markdown
"""
import argparse
import json
import sys
import os
# 修复 Windows 控制台中文乱码
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
# 自动定位 desform_utils.py
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
_SKILL_DIR = os.path.dirname(_SCRIPT_DIR)
for _path in [os.getcwd(), _SCRIPT_DIR]:
if os.path.exists(os.path.join(_path, 'desform_utils.py')):
sys.path.insert(0, _path)
break
from desform_utils import *
# ============================================================
# desform_utils 未内置的控件
# ============================================================
def _BARCODE(name, width=100, **kw):
"""条码控件"""
code_type = kw.pop('codeType', 'barcode')
w, k, m = make_widget("barcode", name, "form-barcode", "icon-tiaoma", {
"maxWidth": 180, "codeType": code_type, "sourceModel": "",
"hidden": False, "hiddenOnAdd": False, "fieldNote": "", "autoWidth": width
})
return make_card(w), k, m
def _LOCATION(name, required=False, width=100, **kw):
"""定位控件"""
w, k, m = make_widget("location", name, "form-location", "icon-location", {
"width": "100%", "defaultValue": "", "defaultCurrent": False,
"showLngLat": False, "showMap": False, "disabled": False,
"hidden": False, "hiddenOnAdd": False, "required": required,
"fieldNote": "", "autoWidth": width
}, required=required)
return make_card(w), k, m
# ============================================================
# type → 工厂函数 映射
# ============================================================
_TYPE_MAP = {
# 基础
'input': INPUT,
'textarea': TEXTAREA,
'number': NUMBER,
'integer': INTEGER,
'money': MONEY,
'date': DATE,
'time': TIME,
'switch': SWITCH,
'slider': SLIDER,
'rate': RATE,
'color': COLOR,
# 选择
'radio': RADIO,
'select': SELECT,
'checkbox': CHECKBOX,
# 系统
'select-user': USER,
'select-depart': DEPART,
'phone': PHONE,
'email': EMAIL,
'area-linkage': AREA,
# 文件
'file-upload': FILE,
'imgupload': IMGUPLOAD,
'hand-sign': HANDSIGN,
# 高级
'auto-number': AUTONUMBER,
'formula': FORMULA,
'barcode': _BARCODE,
'location': _LOCATION,
'link-record': LINK_RECORD,
'link-field': LINK_FIELD,
# 布局
'divider': DIVIDER,
'editor': EDITOR,
'markdown': MARKDOWN,
}
# 需要 options 参数的控件类型options 作为第二个位置参数)
_OPTION_TYPES = {'radio', 'select', 'checkbox'}
# 参数名映射JSON key → 函数参数名
_PARAM_MAP = {
'required': 'required',
'width': 'width',
'prefix': 'prefix',
'unit': 'unit',
'placeholder': 'placeholder',
'multiple': 'multiple',
'dictCode': 'dict_code',
'unique': 'unique',
'precision': 'precision',
'fmt': 'fmt',
'codeType': 'codeType',
# formula
'mode': 'mode',
'expression': 'expression',
'decimal': 'decimal',
# link-record
'sourceCode': 'source_code',
'titleField': 'title_field',
'showFields': 'show_fields',
'showMode': 'show_mode',
'showType': 'show_type',
# link-field
'linkRecordKey': 'link_record_key',
'showField': 'show_field',
'fieldType': 'field_type',
'fieldOptions': 'field_options',
}
def build_widget(field_def):
"""根据 JSON 字段定义构建控件 tuple"""
ftype = field_def['type']
name = field_def['name']
factory = _TYPE_MAP.get(ftype)
if not factory:
raise ValueError(f'未知的控件类型: {ftype}')
# 构建关键字参数
kwargs = {}
for json_key, param_name in _PARAM_MAP.items():
if json_key in field_def:
kwargs[param_name] = field_def[json_key]
# divider 特殊处理text 参数
if ftype == 'divider':
text = field_def.get('text', name)
return factory(text)
# 需要 options 的控件
if ftype in _OPTION_TYPES:
options = field_def.get('options', [])
return factory(name, options, **kwargs)
# auto-number 特殊处理
if ftype == 'auto-number':
return factory(name, **kwargs)
# 其余控件name + kwargs
return factory(name, **kwargs)
def main():
parser = argparse.ArgumentParser(description='JeecgBoot 设计器表单通用创建工具')
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 配置文件路径')
parser.add_argument('--force', action='store_true', help='强制覆盖已存在的表单')
args = parser.parse_args()
with open(args.config, 'r', encoding='utf-8') as f:
config = json.load(f)
form_name = config['formName']
form_code = config['formCode']
layout = config.get('layout', 'auto')
title_index = config.get('titleIndex', 0)
init_api(args.api_base, args.token)
# 防覆盖检查
existing_id, _ = get_form_id(form_code)
if existing_id and not args.force:
print(f'[阻止] 表单 {form_code} 已存在 (ID={existing_id})')
print(f'如需覆盖,请加 --force 参数')
sys.exit(1)
# 构建控件列表
fields = config.get('fields', [])
widgets = []
for fd in fields:
widget = build_widget(fd)
widgets.append(widget)
# 创建表单
form_id, title_model = create_form(form_name, form_code, widgets,
title_index=title_index, layout=layout)
print(f'\n{"=" * 50}')
print(f'表单创建成功')
print(f'{"=" * 50}')
print(f' 表单ID: {form_id}')
print(f' 表单名称: {form_name}')
print(f' 表单编码: {form_code}')
print(f' 标题字段: {title_model}')
print(f' 布局风格: {layout}')
# 生成菜单 SQL
menu_parent = config.get('menuParent')
if menu_parent:
menu_icon = config.get('menuIcon', 'ant-design:appstore-outlined')
sql = gen_menu_sql(menu_parent, [
(form_name, form_code, 1),
], icon=menu_icon)
print(f'\n--- 菜单 SQL ---\n{sql}')
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,805 @@
---
name: jeecg-onlchart
description: "Use when user asks to create/edit Online graph charts, data visualization, or says \"创建图表\", \"生成图表\", \"新建图表\", \"做一个图表\", \"online图表\", \"数据图表\", \"柱状图\", \"折线图\", \"饼图\", \"统计图\", \"可视化\", \"chart\", \"graph\", \"create chart\", \"generate chart\", \"bar chart\", \"line chart\", \"pie chart\". Also triggers when user describes chart requirements like \"做一个销售柱状图\" or mentions data visualization like \"用图表展示男女比例\"."
---
# JeecgBoot Online 图表 AI 自动生成器
将自然语言的图表需求描述转换为 Online 图表配置,并通过 API 在 JeecgBoot 系统中自动创建/编辑图表。
> **重要:本 skill 处理「Online 图表」SQL 驱动的数据可视化图表不涉及「Online 报表」cgreport 数据列表或「Online 表单」cgform。**
## 前置条件
用户必须提供以下信息(或由 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: 新增图表 — 解析需求
从用户描述中提取:
| 信息 | 必填 | 默认值 | 示例 |
|------|------|--------|------|
| 图表编码 (code) | 是 | 自动生成 snake_case | `tj_user_sex` |
| 图表名称 (name) | 是 | 用户指定 | "统计男女比例" |
| SQL 语句 (cgrSql) | 是 | 从需求推导或用户提供 | `select count(*) cout, sex from sys_user group by sex` |
| X 轴字段 (xaxisField) | 是 | 从 SQL 推导 | `sex` |
| Y 轴字段 (yaxisField) | 是 | 从 SQL 推导 | `cout` |
| 图表类型 (graphType) | 是 | `bar` | `bar``line``pie``line,bar` |
| 展示模板 (displayTemplate) | 否 | `tab` | `tab``single` |
| 数据源 (dbSource) | 否 | 空(默认数据源) | `second_db` |
| 数据类型 (dataType) | 否 | `sql` | `sql` |
**X/Y 轴推导规则:**
- **X 轴 (xaxisField)**:通常是分类/维度字段(如 sex、dept、month、category
- **Y 轴 (yaxisField)**:通常是度量/聚合字段(如 count、sum、avg 的结果)
### Step 1B: 编辑图表 — 查询现有配置
1. 用户提供图表 ID 或编码
2. 通过 API 查询现有图表配置(参考 API 列表)
3. 展示现有配置,根据用户需求进行修改
### Step 2: 调用 parseSql 解析字段
**复用报表的 parseSql 接口获取 SQL 的字段列表:**
```
GET /online/cgreport/head/parseSql?sql={urlEncodedSql}&dbKey={dbKey}
```
- `sql`URL 编码后的 SQL 语句
- `dbKey`:数据源编码,默认数据源可不传
**返回结构:**
```json
{
"success": true,
"result": {
"fields": [
{
"id": "2033369959277633538",
"fieldName": "cout",
"fieldTxt": "cout",
"fieldType": "String",
"isShow": 1,
"orderNum": 1
}
],
"params": []
}
}
```
> **注意**parseSql 返回的 `isShow` 是数字 (0/1),但图表接口需要字符串 `"Y"/"N"`,需要转换。
### Step 3: 智能字段配置
#### 3.1 字段属性映射(图表 vs 报表的差异)
**关键差异:图表字段使用 `"Y"/"N"` 字符串,而非数字 0/1。**
| 属性 | 图表 (graphreport) | 报表 (cgreport) | 说明 |
|------|-------------------|-----------------|------|
| 关联头ID | `graphreportHeadId` | `cgrheadId` | 字段名不同 |
| 是否显示 | `isShow`: `"Y"/"N"` | `isShow`: 0/1 | 类型不同 |
| 是否合计 | `isTotal`: `"Y"/"N"` | `isTotal`: `"0"/"1"` 或 null | 类型不同 |
| 是否查询 | `searchFlag`: `"Y"/"N"` | `isSearch`: 0/1 | 字段名和类型都不同 |
| 查询模式 | `searchMode` | `searchMode` | 相同 |
| 字典 | `dictCode` | `dictCode` | 相同 |
| 排序 | `orderNum` | `orderNum` | 相同 |
#### 3.2 字段显示名称 (fieldTxt)
parseSql 返回的 fieldTxt 默认等于 fieldNameAI 需要根据语义翻译为中文:
| 字段名模式 | 推导中文名 |
|-----------|-----------|
| count / cout / cnt | 数量/人数/次数 |
| sum / total / amount | 合计/总额 |
| avg / average | 平均值 |
| sex | 性别 |
| dept / department | 部门 |
| status | 状态 |
| type / category | 类型/分类 |
| month / year / date | 月份/年份/日期 |
| name / title | 名称 |
| age | 年龄 |
| salary | 薪资 |
#### 3.3 是否显示 (isShow)
| 规则 | isShow |
|------|--------|
| 所有字段(默认) | `"Y"`(图表通常字段不多,全部显示) |
| id / 主键字段 | `"N"` |
#### 3.4 是否查询 (searchFlag) + 查询模式 (searchMode)
| 字段类型 | searchFlag | searchMode |
|---------|------------|------------|
| 分类/维度字段 | `"Y"` | `single` |
| 日期/时间字段 | `"Y"` | `range` |
| 度量/聚合字段 | `"N"` | null |
#### 3.5 是否合计 (isTotal)
| 规则 | isTotal |
|------|---------|
| 度量/聚合字段 | `"Y"` |
| 维度/分类字段 | `"N"` |
#### 3.6 字典配置 (dictCode)
同报表,支持两种方式:
**方式一:系统字典编码**
```
"dictCode": "sex"
```
**方式二SQL 字典**
```
"dictCode": "SELECT id as value, name as text FROM sys_category"
```
常用系统字典:`sex`(性别)、`priority`(优先级)、`valid_status`(有效状态)、`yn`(是否)
### Step 4: 图表类型选择
根据数据特征推荐图表类型:
| 数据场景 | 推荐 graphType | 说明 |
|---------|---------------|------|
| 分类对比(如男女人数) | `bar` | 柱状图 |
| 趋势变化(如月度销售) | `line` | 折线图 |
| 占比分布(如部门比例) | `pie` | 饼图 |
| 趋势+对比(如月度销售对比) | `line,bar` | 组合图表 |
**组合图表配置:**
- `graphType`: `"line,bar"`(逗号分隔多种类型)
- `isCombination`: `"combination"`(标记为组合图表)
- 非组合图表 `isCombination` 为 null 或不传
### Step 5: 展示摘要并确认
**必须展示以下内容,等待用户确认后再执行:**
```
## Online 图表配置摘要
- 图表编码tj_user_sex
- 图表名称:统计男女比例
- 图表类型bar柱状图
- X 轴字段sex性别
- Y 轴字段cout人数
- 数据源:默认
- 目标环境https://boot3.jeecg.com/jeecgboot
### SQL 语句
select count(*) cout, sex from sys_user group by sex
### 字段配置
| 序号 | 字段名 | 显示名称 | 类型 | 显示 | 查询 | 字典 | 合计 |
|------|--------|---------|------|------|------|------|------|
| 0 | cout | 人数 | String | Y | N | - | Y |
| 1 | sex | 性别 | String | Y | N | sex | N |
### 参数
(无)
确认以上配置?(y/n)
```
### Step 6: 调用 API 创建/编辑图表
用户确认后执行。
#### 6.1 新增图表 — 请求结构
**`POST /online/graphreport/head/add`**
```json
{
"dbSource": "",
"name": "统计男女比例",
"code": "tj_user_sex",
"displayTemplate": "tab",
"xaxisField": "sex",
"yaxisField": "cout",
"dataType": "sql",
"graphType": "bar",
"cgrSql": "select count(*) cout, sex from sys_user group by sex",
"onlGraphreportItemList": [
{
"id": "前端生成的长数字ID",
"cgrheadId": null,
"fieldName": "cout",
"fieldTxt": "人数",
"fieldWidth": null,
"fieldType": "String",
"searchMode": null,
"isOrder": null,
"isSearch": null,
"dictCode": null,
"fieldHref": null,
"isShow": "Y",
"orderNum": 0,
"replaceVal": null,
"isTotal": null,
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": null,
"groupTitle": null
}
],
"paramsList": []
}
```
> **注意add 接口)**add 时 items 中的关联ID字段名为 `cgrheadId`(值为 null虽然查询/编辑时返回的是 `graphreportHeadId`。
#### 6.2 编辑图表 — 请求结构
**`PUT /online/graphreport/head/edit`**
```json
{
"id": "1290934362649460737",
"name": "统计男女比例",
"code": "tj_user_bysex",
"cgrSql": "select count(*) cout, sex from sys_user group by sex",
"xaxisField": "sex",
"yaxisField": "cout",
"yaxisText": "yaxis_text",
"content": null,
"extendJs": null,
"graphType": "line,bar",
"isCombination": "combination",
"displayTemplate": "tab",
"dataType": "sql",
"dbSource": "",
"tenantId": 0,
"lowAppId": null,
"onlGraphreportItemList": [
{
"id": "1290934166687383554",
"graphreportHeadId": "1290934362649460737",
"fieldName": "cout",
"fieldTxt": "人数",
"isShow": "Y",
"isTotal": "N",
"searchFlag": "N",
"searchMode": null,
"dictCode": "",
"fieldHref": null,
"fieldType": "String",
"orderNum": 0,
"replaceVal": null,
"createBy": "admin",
"createTime": "2020-08-05 17:03:06",
"updateBy": null,
"updateTime": null
}
],
"paramsList": []
}
```
**add 与 edit 字段差异:**
| 字段 | add | edit | 说明 |
|------|-----|------|------|
| `id` (head) | 不传 | 必传 | 图表头ID |
| `yaxisText` | 不传 | 可选 | Y轴标签文字 |
| `content` | 不传 | 可选 | 自定义内容 |
| `extendJs` | 不传 | 可选 | 扩展JS |
| `isCombination` | 不传 | 可选 | 组合图表标记 |
| `tenantId` | 不传 | 传回原值 | 租户ID |
| Item 关联ID字段 | `cgrheadId`: null | `graphreportHeadId`: headId | 字段名不同 |
| Item `isShow` | `"Y"/"N"` | `"Y"/"N"` | 一致 |
| Item `searchFlag` | 不存在,用 `isSearch` | `searchFlag`: `"Y"/"N"` | add 和 edit 可能不同 |
#### 6.3 字段 ID 生成规则
- add 时使用**雪花ID格式**19位数字字符串`"2033369959277633538"`
- 可用 Python 的 `str(int(time.time() * 1000) * 1000000 + random.randint(100000, 999999))` 近似生成
#### 6.4 使用 Python 调用 API
**重要限制:**
1. **Windows 环境下 curl 发送中文/长 JSON 会出错**,必须使用 Python
2. **禁止使用 `python3 -c "..."` 内联方式**
3. **必须先用 Write 工具写入 `.py` 临时文件,再用 Bash 执行,最后删除临时文件**
**完整 Python 脚本模板:**
```python
import urllib.request
import json
import time
import random
import ssl
import urllib.parse
API_BASE = '{用户提供的后端地址}'
TOKEN = '{用户提供的 X-Access-Token}'
# 忽略SSL验证开发环境
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
def api_request(path, data=None, method=None):
"""发送 API 请求"""
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')
if method is None:
method = 'POST'
req = urllib.request.Request(url, data=json_data, headers=headers, method=method)
else:
if method is None:
method = 'GET'
req = urllib.request.Request(url, headers=headers, method=method)
resp = urllib.request.urlopen(req, context=ctx)
return json.loads(resp.read().decode('utf-8'))
def gen_id():
"""生成雪花ID格式的字符串19位数字"""
return str(int(time.time() * 1000) * 1000000 + random.randint(100000, 999999))
# ====== Step 1: 调用 parseSql 解析字段 ======
sql = "select count(*) cout, sex from sys_user group by sex"
encoded_sql = urllib.parse.quote(sql, safe='')
parse_result = api_request(f'/online/cgreport/head/parseSql?sql={encoded_sql}')
print('解析结果:', json.dumps(parse_result, ensure_ascii=False, indent=2))
if not parse_result.get('success'):
print('SQL 解析失败:', parse_result.get('message'))
exit(1)
# ====== Step 2: 构造字段配置 ======
items = [
{
"id": gen_id(), "cgrheadId": None,
"fieldName": "cout", "fieldTxt": "人数",
"fieldWidth": None, "fieldType": "String",
"searchMode": None, "isOrder": None, "isSearch": None,
"dictCode": None, "fieldHref": None,
"isShow": "Y", "orderNum": 0,
"replaceVal": None, "isTotal": None,
"createBy": None, "createTime": None,
"updateBy": None, "updateTime": None, "groupTitle": None
},
{
"id": gen_id(), "cgrheadId": None,
"fieldName": "sex", "fieldTxt": "性别",
"fieldWidth": None, "fieldType": "String",
"searchMode": None, "isOrder": None, "isSearch": None,
"dictCode": "sex", "fieldHref": None,
"isShow": "Y", "orderNum": 1,
"replaceVal": None, "isTotal": None,
"createBy": None, "createTime": None,
"updateBy": None, "updateTime": None, "groupTitle": None
}
]
# ====== Step 3: 构造请求 ======
graph_data = {
"dbSource": "",
"name": "统计男女比例",
"code": "tj_user_sex",
"displayTemplate": "tab",
"xaxisField": "sex",
"yaxisField": "cout",
"dataType": "sql",
"graphType": "bar",
"cgrSql": sql,
"onlGraphreportItemList": items,
"paramsList": []
}
# ====== Step 4: 调用 add API 创建图表 ======
result = api_request('/online/graphreport/head/add', graph_data)
print('创建结果:', json.dumps(result, ensure_ascii=False, indent=2))
if result.get('success'):
print('\n图表创建成功!')
else:
print('\n创建失败:', result.get('message'))
```
**编辑图表脚本差异:**
```python
# 编辑时用 PUT 方法,且 items 使用 graphreportHeadId
graph_data = {
"id": "existing_head_id",
"name": "统计男女比例",
"code": "tj_user_bysex",
"cgrSql": sql,
"xaxisField": "sex",
"yaxisField": "cout",
"yaxisText": "",
"content": None,
"extendJs": None,
"graphType": "line,bar",
"isCombination": "combination",
"displayTemplate": "tab",
"dataType": "sql",
"dbSource": "",
"tenantId": 0,
"lowAppId": None,
"onlGraphreportItemList": [
{
"id": "existing_item_id",
"graphreportHeadId": "existing_head_id",
"fieldName": "cout", "fieldTxt": "人数",
"isShow": "Y", "isTotal": "N",
"searchFlag": "N", "searchMode": None,
"dictCode": "", "fieldHref": None,
"fieldType": "String", "orderNum": 0,
"replaceVal": None
}
],
"paramsList": []
}
result = api_request('/online/graphreport/head/edit', graph_data, method='PUT')
```
### Step 7: 生成菜单 SQL可选
图表创建成功后,可生成菜单 SQL
```python
# 查询刚创建的图表
list_result = api_request(f'/online/graphreport/head/list?code={urllib.parse.quote(report_code)}')
if list_result.get('success') and list_result['result']['records']:
head_id = list_result['result']['records'][0]['id']
report_name = list_result['result']['records'][0]['name']
print(f'\n图表 ID: {head_id}')
print(f'\n### 菜单 SQL可选执行')
print(f"""
INSERT INTO sys_permission (
id, parent_id, name, url, component, component_name,
is_route, is_leaf, keep_alive, hidden, hide_tab, description,
del_flag, rule_flag, status, internal_or_external,
perms_type, sort_no, menu_type, route_redirect
) VALUES (
'{head_id}', NULL, '{report_name}',
'/online/graphreport/{head_id}',
'modules/online/graphreport/auto/OnlGraphreportAutoMain',
NULL,
1, 1, 0, 0, 0, NULL,
0, 0, '1', 0,
'0', 1.0, 1, NULL
);
""")
```
### Step 8: 输出结果
```
## Online 图表创建成功
- 图表编码:{code}
- 图表名称:{name}
- 图表类型:{graphType}
- X 轴:{xaxisField}
- Y 轴:{yaxisField}
- 字段数量:{N} 个
- 目标环境:{API_BASE}
### 菜单 SQL
INSERT INTO sys_permission (...) VALUES (...);
### 后续操作
1. 打开 JeecgBoot 后台 → Online图表
2. 找到该图表,点击「功能测试」预览效果
3. 如需配置菜单,执行上方 SQL 或在后台手动添加
4. 可在「编辑」中调整图表类型、字段等配置
```
---
## 高级功能
### 组合图表
支持在同一图表中展示多种图表类型:
```json
{
"graphType": "line,bar",
"isCombination": "combination"
}
```
组合图表会在同一坐标系中同时展示折线和柱状图。
### Y 轴标签 (yaxisText)
自定义 Y 轴显示文字:
```json
{
"yaxisText": "人数(单位:人)"
}
```
### 扩展 JS (extendJs)
通过自定义 JS 扩展图表行为:
```json
{
"extendJs": "option.tooltip = {trigger: 'axis'};"
}
```
### 自定义内容 (content)
用于自定义渲染模板或说明内容。
### SQL 参数化查询
同报表,支持 Velocity 模板语法的参数:
```sql
SELECT count(*) cout, dept FROM sys_user
WHERE 1=1
${#if($status != '')} AND status = '$status' ${#end}
GROUP BY dept
```
对应的 paramsList 配置:
```json
[
{"paramName": "status", "paramTxt": "状态", "paramValue": "", "orderNum": 1}
]
```
### 动态数据源
查询非默认数据源的数据:
```json
{
"dbSource": "second_db"
}
```
---
## API 完整列表
| 操作 | 方法 | 路径 | 说明 |
|------|------|------|------|
| SQL 解析 | GET | `/online/cgreport/head/parseSql?sql={encodedSql}&dbKey={dbKey}` | 复用报表接口 |
| 新增图表 | POST | `/online/graphreport/head/add` | 创建图表 |
| 编辑图表 | PUT | `/online/graphreport/head/edit` | 修改图表 |
| 查询列表 | GET | `/online/graphreport/head/list?code={code}` | 查询图表列表 |
| 查询详情 | GET | `/online/graphreport/head/queryById?id={headId}` | 按ID查询 |
---
## 与其他 Skill 的区别
| Skill | 产出物 | 适用场景 |
|-------|--------|---------|
| `jeecg-graphreport` | Online 图表配置SQL 驱动,数据可视化) | 柱状图、折线图、饼图等数据可视化 |
| `jeecg-onlreport` | Online 报表配置SQL 驱动,数据列表) | 数据查询报表、统计列表、数据导出 |
| `jeecg-onlform` | Online 表单配置元数据驱动CRUD | 数据录入管理表单 |
| `jeecg-codegen` | Java + Vue3 代码 + SQL | 需要自定义业务逻辑的模块 |
| `jeecg-desform` | 设计器表单 JSON | 数据采集、审批表单 |
---
## 错误处理
| 错误 | 解决方案 |
|------|---------|
| Token 过期401/认证失败) | 提示用户重新获取 X-Access-Token |
| `图表编码已存在` | 换一个 code 或使用 edit 编辑 |
| parseSql 失败 | 检查 SQL 语法是否正确,表是否存在 |
| `SQL注入风险` | 不要在 SQL 中使用 DROP/DELETE/UPDATE 等危险语句 |
| 中文乱码 | 确认使用 Python urllib不要用 curl |
---
## 实测记录
### 实测 1新增图表2026-03-16 验证通过)
**测试场景**:创建「系统登录用户统计分析」图表,按性别统计 sys_user 表用户数量
**配置参数**
- 图表编码:`tj_login_user`
- 图表名称:系统登录用户统计分析
- 图表类型:`bar`(柱状图)
- X 轴:`sex`(性别)
- Y 轴:`cout`(人数)
**SQL**
```sql
select count(*) cout, sex from sys_user group by sex
```
**Step 1 — parseSql 解析**
请求:
```
GET /online/cgreport/head/parseSql?sql=select%20count(*)%20cout%2C%20sex%20from%20sys_user%20group%20by%20sex
```
返回(成功):
```json
{
"success": true,
"code": 200,
"result": {
"fields": [
{
"id": "2033372375880409089",
"fieldName": "cout",
"fieldTxt": "cout",
"fieldType": "String",
"isShow": 1,
"orderNum": 1
},
{
"id": "2033372375880409090",
"fieldName": "sex",
"fieldTxt": "sex",
"fieldType": "String",
"isShow": 1,
"orderNum": 2
}
],
"params": []
}
}
```
**Step 2 — add 创建图表**
请求:
```
POST /online/graphreport/head/add
```
请求体:
```json
{
"dbSource": "",
"name": "系统登录用户统计分析",
"code": "tj_login_user",
"displayTemplate": "tab",
"xaxisField": "sex",
"yaxisField": "cout",
"dataType": "sql",
"graphType": "bar",
"cgrSql": "select count(*) cout, sex from sys_user group by sex",
"onlGraphreportItemList": [
{
"id": "1773628737000000123456",
"cgrheadId": null,
"fieldName": "cout",
"fieldTxt": "人数",
"fieldWidth": null,
"fieldType": "String",
"searchMode": null,
"isOrder": null,
"isSearch": null,
"dictCode": null,
"fieldHref": null,
"isShow": "Y",
"orderNum": 0,
"replaceVal": null,
"isTotal": null,
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": null,
"groupTitle": null
},
{
"id": "1773628737000000654321",
"cgrheadId": null,
"fieldName": "sex",
"fieldTxt": "性别",
"fieldWidth": null,
"fieldType": "String",
"searchMode": null,
"isOrder": null,
"isSearch": null,
"dictCode": "sex",
"fieldHref": null,
"isShow": "Y",
"orderNum": 1,
"replaceVal": null,
"isTotal": null,
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": null,
"groupTitle": null
}
],
"paramsList": []
}
```
返回(成功):
```json
{
"success": true,
"message": "添加成功!",
"code": 200,
"result": null,
"timestamp": 1773628737956
}
```
**关键发现**
1. parseSql 返回的 `fieldTxt` 默认等于 `fieldName`(如 `"cout"`),需 AI 翻译为中文(如 `"人数"`
2. parseSql 返回的 `isShow` 是数字 `1`add 时需转为字符串 `"Y"`
3. parseSql 返回的 `fieldType` 全部是 `"String"`,需根据语义修正
4. add 时 items 的 `orderNum` 从 0 开始正常工作
5. add 时 items 中关联 ID 字段名为 `cgrheadId`(值 null而非 `graphreportHeadId`
6. gen_id() 生成的 19 位数字字符串被 API 正常接受
7. `dictCode: "sex"` 可正确关联系统字典实现值翻译
8. 不需要的字段值传 `null` 即可,不需要传空字符串
### 实测 2编辑图表用户提供的接口数据已验证
**测试场景**:修改已有图表,将单一柱状图改为组合图表(折线+柱状)
**请求**
```
PUT /online/graphreport/head/edit
```
**关键字段变化**
- `graphType``"bar"` 改为 `"line,bar"`
- 新增 `isCombination: "combination"`
- items 中关联 ID 字段名变为 `graphreportHeadId`(与 add 时的 `cgrheadId` 不同)
- items 中使用 `searchFlag``"Y"/"N"`)替代 `isSearch`
**返回**
```json
{
"success": true,
"message": "修改成功!",
"code": 200,
"result": null,
"timestamp": 1773628040311
}
```
**关键发现**
1. edit 使用 `PUT` 方法(非 POST
2. edit 时 items 关联字段名为 `graphreportHeadId`add 时为 `cgrheadId`**这是最容易踩的坑**
3. edit 时查询字段用 `searchFlag``"Y"/"N"`add 时用 `isSearch`
4. 组合图表需同时设置 `graphType: "line,bar"``isCombination: "combination"`
5. edit 时需传回 `tenantId``createTime``createBy` 等系统字段原值

View File

@@ -0,0 +1,292 @@
# JeecgBoot Online 图表 AI 自动生成 — Skills 使用指南
> 通过 Claude Code 以自然语言描述图表需求AI 自动生成 SQL、推导 X/Y 轴字段、配置图表类型,并调用 API 在 JeecgBoot 系统中创建/编辑 Online 图表。
> 无需手动写 SQL 或逐一配置字段AI 从需求描述到可预览图表一步到位。
## 与传统方式的区别
- **无需手写 SQL**描述业务需求AI 自动推导 SQL 语句和字段配置
- **智能推导 X/Y 轴**AI 根据 SQL 字段语义自动识别维度字段X 轴和度量字段Y 轴)
- **自动选择图表类型**:根据数据特征推荐最适合的图表类型(柱状图、折线图、饼图等)
- **字段中文名翻译**:自动将 SQL 字段名翻译为中文显示名称
- **字典自动匹配**:识别常见字段(如 sex、status并关联系统字典
---
## 前置准备
使用前需准备两样东西:
| 信息 | 获取方式 | 示例 |
|------|---------|------|
| **后端地址** | 运维/开发提供 | `https://boot3.jeecg.com/jeecgboot` |
| **X-Access-Token** | 浏览器 F12 → Network → 任意请求 Headers | `eyJhbGciOiJIUzI1NiJ9...` |
---
## 触发方式
在 Claude Code 对话中,用自然语言描述你要创建的图表,包含以下任意关键词即可自动触发:
```
创建图表 / 生成图表 / 新建图表 / 做一个图表 / online图表 / 数据图表
柱状图 / 折线图 / 饼图 / 统计图 / 可视化 / chart / graph
```
---
## 实战案例:用户性别统计图表
以下是一个完整的图表创建过程,展示了 skill 的实际使用方式。
### 第一步:描述需求
**用户输入:**
```
创建一个柱状图,统计系统用户的男女比例
```
**AI 交互过程:**
1. AI 询问后端地址和 Token
2. 用户提供凭据后AI 自动推导 SQL 和字段配置,展示摘要:
```
## Online 图表配置摘要
- 图表编码tj_user_sex
- 图表名称:统计男女比例
- 图表类型bar柱状图
- X 轴字段sex性别
- Y 轴字段cout人数
- 数据源:默认
- 目标环境https://boot3.jeecg.com/jeecgboot
### SQL 语句
select count(*) cout, sex from sys_user group by sex
### 字段配置
| 序号 | 字段名 | 显示名称 | 类型 | 显示 | 查询 | 字典 | 合计 |
|------|--------|---------|------|------|------|------|------|
| 0 | cout | 人数 | String | Y | N | - | Y |
| 1 | sex | 性别 | String | Y | N | sex | N |
确认以上配置?(y/n)
```
3. 用户确认 `y`AI 自动调用 API 创建图表
4. 返回结果:
```
## Online 图表创建成功
- 图表编码tj_user_sex
- 图表名称:统计男女比例
- 图表类型bar柱状图
### 后续操作
1. 打开 JeecgBoot 后台 → Online图表
2. 找到该图表,点击「功能测试」预览效果
```
---
### 第二步:修改图表类型
**用户输入:**
```
把刚才的图表改成折线+柱状组合图
```
**AI 交互过程:**
1. AI 基于已有图表信息,展示修改摘要(标注变更项):
```
### 变更内容
- 图表类型bar → line,bar组合图表
- 新增标记isCombination = combination
```
2. 用户确认后AI 调用 edit API 更新图表
---
## 示例用法
### 1. 一句话描述(最简方式)
```
做一个柱状图,统计各部门的人数
```
AI 会自动推导 SQL`select count(*) cnt, depart_name from sys_depart group by depart_name`
### 2. 指定图表类型
```
用饼图展示系统用户的性别分布
```
AI 识别 "饼图" → `graphType: "pie"`
### 3. 指定 SQL
```
创建图表SQLselect DATE_FORMAT(create_time,'%Y-%m') month, count(*) cnt from sys_user group by month
用折线图展示
```
### 4. 组合图表
```
做一个月度销售分析,同时显示折线图和柱状图
```
AI 自动设置 `graphType: "line,bar"` + `isCombination: "combination"`
### 5. 带查询参数的图表
```
创建一个柱状图统计各部门人数,支持按状态筛选
```
AI 生成带 Velocity 参数的 SQL
```sql
select count(*) cnt, dept from sys_user
where 1=1
${#if($status != '')} AND status = '$status' ${#end}
group by dept
```
### 6. 指定数据源
```
用 second_db 数据源做一个销售统计图表
```
AI 设置 `dbSource: "second_db"`
---
## 支持的图表类型
| 图表类型 | graphType 值 | 适用场景 |
|---------|-------------|---------|
| 柱状图 | `bar` | 分类对比(如男女人数、部门对比) |
| 折线图 | `line` | 趋势变化(如月度销售、访问量趋势) |
| 饼图 | `pie` | 占比分布(如部门比例、状态分布) |
| 组合图表 | `line,bar` | 趋势+对比(同时展示折线和柱状) |
AI 会根据数据特征自动推荐最合适的图表类型:
- 分类对比场景 → 柱状图
- 时间趋势场景 → 折线图
- 占比分布场景 → 饼图
- 多维分析场景 → 组合图表
---
## 智能字段推导
AI 根据 SQL 字段语义自动推导配置:
### X/Y 轴推导
| 字段特征 | 推导为 |
|---------|--------|
| 分类/维度字段sex、dept、month、category | X 轴 |
| 度量/聚合字段count、sum、avg 的结果) | Y 轴 |
### 字段中文名翻译
| 字段名 | 自动翻译 |
|--------|---------|
| count / cout / cnt | 数量/人数 |
| sum / total / amount | 合计/总额 |
| avg / average | 平均值 |
| sex | 性别 |
| dept / department | 部门 |
| month / year / date | 月份/年份/日期 |
### 字典自动关联
| 字段名 | 关联字典 |
|--------|---------|
| sex | `sex`(性别) |
| status | `valid_status`(有效状态) |
| priority | `priority`(优先级) |
---
## 修改已有图表
如果要修改已创建的图表:
```
修改图表 tj_user_sex把图表类型改成饼图
```
```
给图表 tj_user_sex 的 Y 轴加上标签文字"人数(单位:人)"
```
需要提供图表 ID 或编码。AI 会先查询现有配置,再进行修改。
---
## 高级功能
### Y 轴标签
```
创建图表并设置 Y 轴标签为"销售额(万元)"
```
### 扩展 JS
通过自定义 JS 扩展图表行为,适合高级用户:
```
创建图表,加一段扩展 JSoption.tooltip = {trigger: 'axis'};
```
### 动态数据源
查询非默认数据源的数据:
```
用 report_db 数据源创建统计图表
```
---
## 与其他 Skill 的区别
| Skill | 产出物 | 适用场景 |
|-------|--------|---------|
| **jeecg-onlchart** | Online 图表配置SQL 驱动,数据可视化) | 柱状图、折线图、饼图等数据可视化 |
| jeecg-onlreport | Online 报表配置SQL 驱动,数据列表) | 数据查询报表、统计列表 |
| jeecg-onlform | Online 表单配置元数据驱动CRUD | 数据录入管理表单 |
| jeecg-codegen | Java + Vue3 代码 + SQL | 需要自定义业务逻辑的模块 |
| jeecg-desform | 设计器表单 JSON | 数据采集、审批表单 |
| jeecg-bpmn | Flowable BPMN 2.0 XML | 审批流程、工作流 |
**选择建议:**
- 需要数据可视化(图表) → **jeecg-onlchart**
- 需要数据查询列表 → jeecg-onlreport
- 需要简单 CRUD 表单 → jeecg-onlform
- 需要自定义业务逻辑 → jeecg-codegen
- 需要数据采集/审批表单 → jeecg-desform
- 需要审批流程 → jeecg-bpmn
---
## 注意事项
1. **Token 有效期**JWT Token 有过期时间,过期后需重新从浏览器获取
2. **图表编码唯一**:同一系统中 code 不能重复,如已存在需改名或使用编辑功能
3. **SQL 安全**:不要在 SQL 中使用 DROP/DELETE/UPDATE 等危险语句
4. **同一会话内可连续修改**AI 会记住当前图表的 ID 和编码,无需重复提供
5. **创建后可在后台微调**AI 创建的图表可以在 Online 图表编辑页面中继续调整
6. **菜单配置可选**:创建成功后 AI 会提供菜单 SQL可选择执行或手动添加

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()

View File

@@ -0,0 +1,627 @@
---
name: jeecg-onlreport
description: "Use when user asks to create/edit Online reports, SQL reports, data reports, or says \"创建报表\", \"生成报表\", \"新建报表\", \"做一个报表\", \"online报表\", \"SQL报表\", \"数据报表\", \"统计报表\", \"查询报表\", \"create report\", \"generate report\", \"data report\". Also triggers when user describes report requirements like \"做一个销售统计报表\" or mentions SQL-driven data display like \"通过SQL查询生成报表\"."
---
# JeecgBoot Online 报表 AI 自动生成器
将自然语言的报表需求描述转换为 Online 报表配置,并通过 API 在 JeecgBoot 系统中自动创建/编辑报表。
> **重要:本 skill 处理「Online 报表」SQL 驱动的数据报表不涉及「Online 表单」cgform或「设计器表单」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: 新增报表 — 解析需求
从用户描述中提取:
| 信息 | 默认值 | 示例 |
|------|--------|------|
| 报表编码 (code) | 自动生成 snake_case | `sales_report` |
| 报表名称 (name) | 用户指定 | "销售统计报表" |
| SQL 语句 (cgrSql) | 从需求推导或用户提供 | `SELECT ... FROM ...` |
| 数据源 (dbSource) | 空(默认数据源) | `second_db` |
**两种 SQL 来源:**
1. **用户直接提供 SQL** — 直接使用,调用 parseSql 解析字段
2. **用户描述需求AI 推导 SQL** — 需要用户确认数据库表结构或已知表名
### Step 1B: 编辑报表 — 查询现有配置
1. 用户提供报表 ID 或编码
2. 通过 API 查询现有报表配置:`GET /online/cgreport/head/queryById?id={headId}`
3. 通过 API 查询字段列表:`GET /online/cgreport/item/listByHeadId?cgrheadId={headId}`
4. 通过 API 查询参数列表:`GET /online/cgreport/param/listByHeadId?cgrheadId={headId}`
5. 展示现有配置,根据用户需求进行修改
### Step 2: 调用 parseSql 解析字段
**必须先调用 parseSql 接口获取 SQL 的字段和参数列表:**
```
GET /online/cgreport/head/parseSql?sql={urlEncodedSql}&dbKey={dbKey}
```
- `sql`URL 编码后的 SQL 语句
- `dbKey`:数据源编码,默认数据源可不传
**实测返回结构(已验证):**
```json
{
"success": true,
"message": "",
"code": 200,
"result": {
"fields": [
{
"id": "2032684046700560386",
"cgrheadId": null,
"fieldName": "id",
"fieldTxt": "id",
"fieldWidth": null,
"fieldType": "String",
"searchMode": null,
"isOrder": null,
"isSearch": null,
"dictCode": null,
"fieldHref": null,
"isShow": 1,
"orderNum": 1,
"replaceVal": null,
"isTotal": null,
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": null,
"groupTitle": null
}
],
"params": []
},
"timestamp": 1773464616834
}
```
### Step 3: 智能字段配置
根据字段名和业务语义AI 自动推导每个字段的配置:
#### 3.1 字段显示名称 (fieldTxt)
parseSql 返回的 fieldTxt 默认等于 fieldNameAI 需要根据语义翻译为中文:
| 字段名模式 | 推导中文名 |
|-----------|-----------|
| id | ID/主键 |
| name / title | 名称/标题 |
| code / no | 编码/编号 |
| status | 状态 |
| type / category | 类型/分类 |
| amount / money / price | 金额/费用/价格 |
| count / qty / num | 数量 |
| date / time | 日期/时间 |
| create_by | 创建人 |
| create_time | 创建时间 |
| update_by | 更新人 |
| update_time | 更新时间 |
| sex | 性别 |
| age | 年龄 |
| email | 邮箱 |
| phone / mobile / tel | 电话/手机号 |
| address | 地址 |
| remark / content / description | 备注/内容/描述 |
| dept / org | 部门/组织 |
| salary | 薪资 |
| birthday | 生日 |
#### 3.2 是否显示 (isShow)
| 规则 | isShow |
|------|--------|
| 业务字段(默认) | 1显示 |
| id / 主键字段 | 0隐藏— 通常不在报表中展示 |
| create_by / update_by | 0隐藏— 系统字段 |
| create_time / update_time | 视需求而定 |
| sys_org_code / tenant_id | 0隐藏— 系统字段 |
#### 3.3 是否查询 (isSearch) + 查询模式 (searchMode)
| 字段类型 | isSearch | searchMode | 说明 |
|---------|----------|------------|------|
| 名称/标题等文本 | 1 | `like` | 模糊查询 |
| 状态/类型/分类 | 1 | `single` | 精确匹配 |
| 日期/时间 | 1 | `range` | 范围查询(开始~结束) |
| 金额/数量等数值 | 0 | - | 通常不查询 |
| 系统字段 | 0 | - | 不查询 |
#### 3.4 是否排序 (isOrder)
| 规则 | isOrder |
|------|---------|
| 日期/时间字段 | 1 |
| 金额/数量字段 | 1 |
| 其他 | 0 |
#### 3.5 字段类型 (fieldType)
| SQL 列类型 | fieldType |
|-----------|-----------|
| varchar / char / text | String |
| int / tinyint / smallint | Integer |
| bigint | Long |
| decimal / double / float | BigDecimal |
| date | Date |
| datetime / timestamp | Datetime |
> **注意**parseSql 返回的 fieldType 可能都是 StringAI 应根据字段名语义或用户描述修正。
#### 3.6 字典配置 (dictCode)
支持两种方式:
**方式一:系统字典编码**
```
"dictCode": "sex"
```
**方式二SQL 字典**
```
"dictCode": "SELECT id as value, name as text FROM sys_category"
```
常用系统字典:
| 字典编码 | 说明 |
|---------|------|
| `sex` | 性别 (1=男, 2=女) |
| `priority` | 优先级 |
| `valid_status` | 有效状态 |
| `urgent_level` | 紧急程度 |
| `yn` | 是否 |
#### 3.7 取值表达式 (replaceVal)
用于将数据库值替换为显示文本(导出时使用):
```
"replaceVal": "男_1,女_2"
```
格式:`显示文本_数据库值,显示文本_数据库值,...`
#### 3.8 是否合计 (isTotal)
| 规则 | isTotal |
|------|---------|
| 金额/费用/价格字段 | "1"(合计) |
| 数量字段 | "1"(合计) |
| 其他 | "0" 或 null |
#### 3.9 分组表头 (groupTitle)
多个字段可以共用一个分组表头,实现多级表头效果:
```json
{"fieldName": "q1_amount", "groupTitle": "第一季度"},
{"fieldName": "q1_count", "groupTitle": "第一季度"},
{"fieldName": "q2_amount", "groupTitle": "第二季度"},
{"fieldName": "q2_count", "groupTitle": "第二季度"}
```
#### 3.10 字段跳转 (fieldHref)
```
"fieldHref": "/details?id=${id}"
```
支持 `${fieldName}` 变量替换。
### Step 4: SQL 参数配置
SQL 中的 `${paramName}` 会被解析为参数:
```sql
SELECT * FROM sales
WHERE 1=1
${#if($startDate != '')} AND sale_date >= '$startDate' ${#end}
${#if($endDate != '')} AND sale_date <= '$endDate' ${#end}
```
参数配置:
| 属性 | 说明 |
|------|------|
| paramName | 参数名(对应 SQL 中的 ${xxx} |
| paramTxt | 参数显示名称 |
| paramValue | 默认值(可为空) |
| orderNum | 排序序号 |
### Step 5: 展示摘要并确认
**必须展示以下内容,等待用户确认后再执行:**
```
## Online 报表配置摘要
- 报表编码sales_report
- 报表名称:销售统计报表
- 数据源:默认
- 目标环境https://boot3.jeecg.com/jeecgboot
### SQL 语句
SELECT s.id, s.name, s.amount, s.sale_date, s.status
FROM biz_sales s
WHERE 1=1
### 字段配置
| 序号 | 字段名 | 显示名称 | 类型 | 显示 | 查询 | 排序 | 字典 | 合计 |
|------|--------|---------|------|------|------|------|------|------|
| 0 | id | ID | String | 否 | 否 | 否 | - | - |
| 1 | name | 名称 | String | 是 | 是(模糊) | 否 | - | - |
| 2 | amount | 金额 | BigDecimal | 是 | 否 | 是 | - | 是 |
| 3 | sale_date | 销售日期 | Date | 是 | 是(范围) | 是 | - | - |
| 4 | status | 状态 | String | 是 | 是(精确) | 否 | valid_status | - |
### 参数
| 参数名 | 显示名称 | 默认值 |
|--------|---------|--------|
| (无) | | |
确认以上配置?(y/n)
```
### Step 6: 调用 API 创建/编辑报表
用户确认后执行。
#### 6.1 构造请求 JSON
**新增报表 (add)**
```json
{
"head": {
"code": "report_code",
"name": "报表名称",
"cgrSql": "SELECT ... FROM ...",
"dbSource": ""
},
"items": [
{
"id": "前端生成的长数字ID",
"cgrheadId": null,
"fieldName": "field_name",
"fieldTxt": "显示名称",
"fieldWidth": null,
"fieldType": "String",
"searchMode": null,
"isOrder": null,
"isSearch": null,
"dictCode": null,
"fieldHref": null,
"isShow": 1,
"orderNum": 0,
"replaceVal": null,
"isTotal": null,
"groupTitle": null,
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": null
}
],
"params": [
{
"paramName": "paramName",
"paramTxt": "参数名称",
"paramValue": "",
"orderNum": 1
}
]
}
```
**编辑报表 (editAll)**
```json
{
"head": {
"id": "existing_head_id",
"code": "report_code",
"name": "报表名称",
"cgrSql": "SELECT ... FROM ...",
"dbSource": ""
},
"items": [...],
"params": [...],
"deleteItemIds": "item_id1,item_id2",
"deleteParamIds": "param_id1"
}
```
**字段 ID 生成规则:**
- add 时使用**雪花ID格式**19位数字字符串`"2032681654277947394"`
- 可用 Python 的 `str(int(time.time() * 1000) * 1000 + random.randint(0, 999))` 近似生成
#### 6.2 使用 Python 调用 API
**重要限制:**
1. **Windows 环境下 curl 发送中文/长 JSON 会出错**,必须使用 Python
2. **禁止使用 `python3 -c "..."` 内联方式**
3. **必须先用 Write 工具写入 `.py` 临时文件,再用 Bash 执行,最后删除临时文件**
**完整 Python 脚本模板(已实测验证通过):**
以下脚本已在 `https://boot3.jeecg.com/jeecgboot` 环境成功创建报表2026-03-14 验证)。
```python
import urllib.request
import json
import time
import random
import ssl
import urllib.parse
API_BASE = '{用户提供的后端地址}'
TOKEN = '{用户提供的 X-Access-Token}'
# 忽略SSL验证开发环境
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
def api_request(path, data=None, method=None):
"""发送 API 请求"""
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')
if method is None:
method = 'POST'
req = urllib.request.Request(url, data=json_data, headers=headers, method=method)
else:
if method is None:
method = 'GET'
req = urllib.request.Request(url, headers=headers, method=method)
resp = urllib.request.urlopen(req, context=ctx)
return json.loads(resp.read().decode('utf-8'))
def gen_id():
"""生成雪花ID格式的字符串19位数字"""
return str(int(time.time() * 1000) * 1000000 + random.randint(100000, 999999))
# ====== Step 1: 调用 parseSql 解析字段 ======
sql = "SELECT id, table_name, table_txt, table_type, create_time FROM onl_cgform_head WHERE 1=1"
encoded_sql = urllib.parse.quote(sql, safe='')
parse_result = api_request(f'/online/cgreport/head/parseSql?sql={encoded_sql}')
print('解析结果:', json.dumps(parse_result, ensure_ascii=False, indent=2))
if not parse_result.get('success'):
print('SQL 解析失败:', parse_result.get('message'))
exit(1)
# ====== Step 2: 直接构造字段配置(不依赖 parseSql 返回的默认值) ======
# 注意parseSql 返回的 fieldType 全部是 StringAI 需根据语义修正
# 注意parseSql 返回的 fieldTxt 等于 fieldNameAI 需翻译为中文
items = [
{"id": gen_id(), "cgrheadId": None, "fieldName": "id", "fieldTxt": "ID",
"fieldWidth": None, "fieldType": "String", "searchMode": None, "isOrder": None,
"isSearch": None, "dictCode": None, "fieldHref": None, "isShow": 0,
"orderNum": 0, "replaceVal": None, "isTotal": None, "groupTitle": None,
"createBy": None, "createTime": None, "updateBy": None, "updateTime": None},
{"id": gen_id(), "cgrheadId": None, "fieldName": "table_name", "fieldTxt": "表名",
"fieldWidth": None, "fieldType": "String", "searchMode": "like", "isOrder": None,
"isSearch": 1, "dictCode": None, "fieldHref": None, "isShow": 1,
"orderNum": 1, "replaceVal": None, "isTotal": None, "groupTitle": None,
"createBy": None, "createTime": None, "updateBy": None, "updateTime": None},
# ... 继续添加其他字段 ...
]
# ====== Step 3: 构造请求 ======
report_data = {
"head": {
"code": "onl_cgform_list",
"name": "Online表单清单",
"cgrSql": sql,
"dbSource": ""
},
"items": items,
"params": []
}
# ====== Step 4: 调用 add API 创建报表 ======
result = api_request('/online/cgreport/head/add', report_data)
print('创建结果:', json.dumps(result, ensure_ascii=False, indent=2))
if result.get('success'):
print('\n报表创建成功!')
# Step 5: 查询报表 ID 并生成菜单 SQL
list_result = api_request(f'/online/cgreport/head/list?code=onl_cgform_list')
if list_result.get('success') and list_result['result']['records']:
head_id = list_result['result']['records'][0]['id']
print(f'报表 ID: {head_id}')
print(f'\n菜单 SQL:')
print(f"INSERT INTO sys_permission (id, parent_id, name, url, component, component_name, is_route, is_leaf, keep_alive, hidden, hide_tab, description, del_flag, rule_flag, status, internal_or_external, perms_type, sort_no, menu_type, route_redirect) VALUES ('{head_id}', NULL, 'Online表单清单', '/online/cgreport/{head_id}', 'modules/online/cgreport/auto/OnlCgreportAutoMain', NULL, 1, 1, 0, 0, 0, NULL, 0, 0, '1', 0, '0', 1.0, 1, NULL);")
else:
print('\n创建失败:', result.get('message'))
```
### 实测记录2026-03-14
**测试场景**:创建 Online 表单清单报表,查询 `onl_cgform_head`
**SQL**
```sql
SELECT id, table_name, table_txt, table_type, table_version, is_tree, is_page, theme_template, create_time, create_by, update_time
FROM onl_cgform_head WHERE 1=1
```
**验证结果**
1. `parseSql` API 成功解析 11 个字段,所有 fieldType 均返回 String需 AI 修正)
2. `add` API 成功创建报表,返回 `{"success": true, "message": "添加成功!"}`
3. `head/list` API 成功查询到报表 ID: `2032684085556592641`
4. 菜单 SQL 生成正确
**关键发现**
- parseSql 返回的 `orderNum` 从 1 开始,但 add 时 items 的 `orderNum` 从 0 开始也能正常工作
- 不需要的字段值isSearch/isOrder/dictCode 等)传 `null` 即可,不需要传空字符串
- `replaceVal` 格式 `"单表_1,主表_2,附表_3"` 可以替代 dictCode 实现值翻译(导出时有效)
- gen_id() 生成的 19 位数字字符串与前端生成的雪花 ID 格式一致API 接受
### Step 7: 生成菜单 SQL可选
报表创建成功后,需要查询报表 ID 来生成菜单 SQL
```python
# 查询刚创建的报表
import urllib.parse
list_result = api_request(f'/online/cgreport/head/list?code={urllib.parse.quote(report_code)}')
if list_result.get('success') and list_result['result']['records']:
head_id = list_result['result']['records'][0]['id']
report_name = list_result['result']['records'][0]['name']
print(f'\n报表 ID: {head_id}')
print(f'\n### 菜单 SQL可选执行')
print(f"""
INSERT INTO sys_permission (
id, parent_id, name, url, component, component_name,
is_route, is_leaf, keep_alive, hidden, hide_tab, description,
del_flag, rule_flag, status, internal_or_external,
perms_type, sort_no, menu_type, route_redirect
) VALUES (
'{head_id}', NULL, '{report_name}',
'/online/cgreport/{head_id}',
'modules/online/cgreport/auto/OnlCgreportAutoMain',
NULL,
1, 1, 0, 0, 0, NULL,
0, 0, '1', 0,
'0', 1.0, 1, NULL
);
""")
```
**菜单 SQL 字段说明:**
| 字段 | 值 | 说明 |
|------|-----|------|
| id | 报表 headId | 与报表配置关联 |
| parent_id | NULL | 一级菜单,也可设为某个父菜单 ID |
| name | 报表名称 | 菜单显示名 |
| url | `/online/cgreport/{headId}` | 路由路径 |
| component | `modules/online/cgreport/auto/OnlCgreportAutoMain` | 前端组件 |
| is_route | 1 | 是菜单路由 |
| is_leaf | 1 | 叶子节点 |
| menu_type | 1 | 菜单类型1=菜单) |
### Step 8: 输出结果
```
## Online 报表创建成功
- 报表编码:{code}
- 报表名称:{name}
- 字段数量:{N} 个
- 参数数量:{M} 个
- 目标环境:{API_BASE}
### 菜单 SQL
INSERT INTO sys_permission (...) VALUES (...);
### 后续操作
1. 打开 JeecgBoot 后台 → Online报表
2. 找到该报表,点击「功能测试」预览效果
3. 如需配置菜单,执行上方 SQL 或在后台手动添加
4. 可在「编辑」中调整字段显示/查询/排序等配置
```
---
## 高级功能
### SQL 参数化查询
支持在 SQL 中使用 Velocity 模板语法的参数:
```sql
SELECT * FROM biz_sales
WHERE 1=1
${#if($startDate != '')} AND sale_date >= '$startDate' ${#end}
${#if($endDate != '')} AND sale_date <= '$endDate' ${#end}
${#if($status != '')} AND status = '$status' ${#end}
```
对应的 params 配置:
```json
[
{"paramName": "startDate", "paramTxt": "开始日期", "paramValue": "", "orderNum": 1},
{"paramName": "endDate", "paramTxt": "结束日期", "paramValue": "", "orderNum": 2},
{"paramName": "status", "paramTxt": "状态", "paramValue": "", "orderNum": 3}
]
```
### 动态数据源
如果用户需要查询非默认数据源的数据:
```json
{
"head": {
"code": "ext_report",
"name": "外部数据报表",
"cgrSql": "SELECT ...",
"dbSource": "second_db"
}
}
```
`dbSource` 对应 JeecgBoot 后台「数据源管理」中配置的数据源编码。
### 字段宽度 (fieldWidth)
控制表格列宽(像素值):
```json
{"fieldName": "name", "fieldWidth": 200}
{"fieldName": "description", "fieldWidth": 300}
```
---
## 与其他 Skill 的区别
| Skill | 产出物 | 适用场景 |
|-------|--------|---------|
| `jeecg-cgreport` | Online 报表配置SQL 驱动,只读数据展示) | 数据查询报表、统计分析、数据导出 |
| `jeecg-online` | Online 表单配置元数据驱动CRUD | 数据录入管理表单 |
| `jeecg-codegen` | Java + Vue3 代码 + SQL | 需要自定义业务逻辑的模块 |
| `jeecg-desform` | 设计器表单 JSON | 数据采集、审批表单 |
---
## 错误处理
| 错误 | 解决方案 |
|------|---------|
| Token 过期401/认证失败) | 提示用户重新获取 X-Access-Token |
| `报表编码已存在` | 换一个 code 或使用 editAll 编辑 |
| parseSql 失败 | 检查 SQL 语法是否正确,表是否存在 |
| `SQL注入风险` | 不要在 SQL 中使用 DROP/DELETE/UPDATE 等危险语句 |
| `禁止 select *` | 如果系统开启了 disableSelectAll需指定具体字段 |
| 中文乱码 | 确认使用 Python urllib不要用 curl |

View File

@@ -0,0 +1,720 @@
---
name: jimubi-bigscreen
description: "Use when user asks to create/design a big screen (大屏), full-screen data visualization, or says \"创建大屏\", \"生成大屏\", \"新建大屏\", \"设计大屏\", \"做一个大屏\", \"BI大屏\", \"数据大屏\", \"可视化大屏\", \"监控大屏\", \"create big screen\", \"design big screen\", \"BI visualization big screen\". Also triggers when user describes big screen requirements like \"做一个销售数据大屏\" or mentions full-screen display like \"展厅展示\", \"监控室大屏\". Make sure to use this skill for big screens (大屏) — NOT dashboards (仪表盘/看板), which use a completely different layout and styling system."
---
# JeecgBoot 大屏 AI 自动生成器
将自然语言的大屏需求转换为 drag page 配置,并通过 API 自动创建。
> **本 skill 专门处理大屏bigScreen模式**:全屏展示,绝对定位(像素坐标),深色主题,适用于监控室/展厅/展示墙。
> 仪表盘(看板)请使用 `jimubi-dashboard` skill。
## 大屏特征
- **布局**:绝对定位,坐标和尺寸单位为**像素**(如 x=50, y=280, w=860, h=380
- **主题**:默认 `dark`,深色背景,亮色/霓虹文字
- **背景图**:默认 `/img/bg/bg4.png`,支持自定义
- **装饰元素**:常用 JDragBorder边框、JDragDecoration装饰条增强视觉效果
- **典型分辨率**1920×1080
## 前置条件
用户必须提供:
1. **API 地址**JeecgBoot 后端地址(如 `https://api3.boot.jeecg.com`
2. **X-Access-Token**JWT 登录令牌(从浏览器 F12 获取)
## 交互流程
### Step 0: 解析用户需求
| 信息 | 默认值 | 示例 |
|------|--------|------|
| 页面名称 | 用户指定 | "销售数据大屏" |
| 主题 | dark | dark |
| 背景图 | `/img/bg/bg4.png` | 可自定义 |
| 组件列表 | 从描述中解析 | 销售额(数字)、订单趋势(折线图)、区域分布(地图) |
### Step 1: 识别组件并选择类型
阅读 `references/bi-component-types.md` 获取完整组件类型清单。
**常用大屏组件速查:**
| 用户描述关键词 | 组件 component | 说明 |
|---------------|---------------|------|
| 数字/KPI/指标 | `JNumber` | 数字指标卡 |
| 翻牌器/数字动画 | `JCountTo` | 数字翻牌器 |
| 柱状图 | `JBar` | 基础柱状图 |
| 横向柱状图 | `JHorizontalBar` | 水平柱状图 |
| 堆叠柱状图 | `JStackBar` | 堆叠柱状图 |
| 折线图/趋势 | `JLine` | 折线图 |
| 曲线图 | `JSmoothLine` | 平滑曲线 |
| 柱线混合 | `JMixLineBar` | 柱状+折线混合 |
| 饼图 | `JPie` | 饼图 |
| 环形图 | `JRing` | 环形图 |
| 玫瑰图 | `JRose` | 南丁格尔玫瑰图 |
| 仪表盘/表盘 | `JGauge` | 仪表盘表盘 |
| 水球图 | `JLiquid` | 水球图 |
| 进度条 | `JProgress` | 进度条 |
| 雷达图 | `JRadar` | 雷达图 |
| 漏斗图 | `JFunnel` | 漏斗图 |
| 词云 | `JWordCloud` | 词云图 |
| 地图/区域地图 | `JAreaMap` | 区域地图 |
| 飞线地图/迁徙 | `JFlyLineMap` | 飞线地图 |
| 热力地图 | `JHeatMap` | 热力地图 |
| 滚动表格 | `JScrollTable` | 自动滚动表格 |
| 排行榜/排名 | `JScrollRankingBoard` | 滚动排行榜 |
| 文本/标题 | `JText` | 文本显示 |
| 图片 | `JImg` | 图片 |
| 视频 | `JVideoPlay` | 视频播放 |
| 边框/装饰 | `JDragBorder` | 装饰边框13种样式 |
| 装饰条 | `JDragDecoration` | 装饰条12种样式 |
| 时钟 | `JCurrentTime` | 实时时钟 |
### Step 2: 展示设计摘要并确认
**必须展示,等待用户确认后再执行:**
```
## 大屏设计摘要
- 页面名称:销售数据大屏
- 主题dark
- 背景图:/img/bg/bg4.png
### 组件列表
| 序号 | 组件名称 | 组件类型 | 位置(x,y) | 尺寸(w×h) | 数据源 |
|------|---------|---------|-----------|----------|--------|
| 1 | 今日销售额 | JNumber | (50,50) | 400×200 | 静态数据 |
| 2 | 销售趋势 | JLine | (50,280) | 860×380 | 静态数据 |
确认以上信息正确?(y/n)
```
### Step 3: 调用 API 创建大屏
**优先使用共通工具库 `bi_utils.py`**(两个位置均有副本):
- Skills 目录(权威副本):`C:\Users\zhang\.claude\skills\jimubi-bigscreen\references\bi_utils.py`
- 后端项目根目录(运行副本):`{后端项目根目录}\bi_utils.py`
> 如果后端项目根目录没有 `bi_utils.py`,先从 skills 目录复制过去再使用。
**执行步骤:**
```
1. 确认后端项目根目录有 bi_utils.py没有则从 skills 复制)
2. Write 工具 → 写入业务脚本 create_xxx_screen.py项目根目录
3. Bash 工具 → cd {后端项目根目录} && python create_xxx_screen.py
4. Bash 工具 → rm create_xxx_screen.py清理临时脚本
```
---
## 备选方式:从模板复制创建大屏
> **注意:模板复制方式仅作为备选。** 模板 JSON 中的 config 结构复杂且样式耦合严重,批量文本替换容易破坏配置完整性,生成效果往往不理想。仅在需要精确还原某个已有模板的视觉布局时才考虑使用。
### 模板目录
`references/templates/bigScreen/` 下有 40 个大屏模板 JSON 可供选择。
### 模板复制完整流程
```python
import sys, json
sys.path.insert(0, r'{后端项目根目录}')
from bi_utils import *
import bi_utils
init_api('http://api3.boot.jeecg.com', 'your-token')
# 1. 读取模板 JSON
tpl_path = r'C:/Users/zhang/.claude/skills/jimubi-bigscreen/references/templates/bigScreen/集团综合数据大屏_1151069555267260416.json'
with open(tpl_path, 'r', encoding='utf-8') as f:
tpl_data = json.load(f)
template_components = tpl_data.get('template', [])
# 2. 建立旧 ID → 新 ID 映射(关键!)
id_mapping = {}
for comp in template_components:
old_i = comp['i']
id_mapping[old_i] = bi_utils._gen_uuid()
# 3. 更新组件 ID 和清理
for comp in template_components:
comp['i'] = id_mapping[comp['i']]
comp.pop('pageCompId', None)
# config 字符串转 dict
config = comp.get('config', {})
if isinstance(config, str):
try: config = json.loads(config)
except: config = {}
comp['config'] = config
# 4. 更新 JTabToggle 的 compVals 引用(否则页签切换不工作)
for comp in template_components:
if comp['component'] == 'JTabToggle':
for item in comp['config'].get('option', {}).get('items', []):
item['compVals'] = [id_mapping.get(v, v) for v in item.get('compVals', [])]
# 5. 更新 JGroup 内部 props.elements 中的 ID 引用
for comp in template_components:
if comp['component'] == 'JGroup':
props = comp.get('props', {})
elements = props.get('elements', [])
if elements:
el_str = json.dumps(elements, ensure_ascii=False)
for old_id, new_id in id_mapping.items():
el_str = el_str.replace(old_id, new_id)
props['elements'] = json.loads(el_str)
# 6. 创建页面并保存
page_id = create_page('我的大屏', style='bigScreen', theme='dark',
background_image='/img/bg/bg4.png')
bi_utils._page_components[page_id] = template_components
save_page(page_id)
```
### 模板复制踩坑记录
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| **页签切换不工作** | JTabToggle 的 `compVals` 引用了旧组件 ID | 必须建立 ID 映射,更新 `config.option.items[].compVals` |
| **JGroup 内部组件异常** | JGroup 的 `props.elements` 内也有 ID 交叉引用 | 序列化后批量替换旧 ID |
| **新增组件不显示** | config 格式不完整或被头部背景图遮挡 | 用模板中已有的同类组件 config 作参考;设 `orderNum: 300` 提高层级 |
### 替换业务数据
对整个 template JSON 字符串做批量文本替换,可高效替换所有标题、标签、数值:
```python
# 序列化为字符串
tpl_str = json.dumps(tpl_data['template'], ensure_ascii=False)
# 批量替换
replacements = {
'集团业务综合管理平台': '招商银行经营管理驾驶舱',
'新成业务板块': '零售金融业务',
'合同': '业务单',
# ... 更多映射
}
for old, new in replacements.items():
tpl_str = tpl_str.replace(old, new)
# 解析回 list
template_components = json.loads(tpl_str)
```
### 向已有大屏新增组件
> **关键:新增组件的 config 必须从模板中同类组件复制,不要自己拼装。**
```python
# 从其他模板中找到参考组件的 config
# 例如 JWeatherForecast 用 template=11 样式
weather = {
'component': 'JWeatherForecast',
'componentName': '今日天气',
'visible': True,
'i': bi_utils._gen_uuid(),
'x': 15, 'y': 15, 'w': 300, 'h': 50,
'orderNum': 300, # 高层级,不被背景图遮挡
'config': {
'size': {'width': 300, 'height': 50},
'w': 300, 'dataType': 1, 'h': 50,
'option': {
'template': 11, 'bgColor': '', 'city': '',
'num': 1, 'fontSize': 16, 'fontColor': '#ffffff', 'url': '',
},
},
}
# JCurrentTime 用模板中已有的完整 config
# 先从模板中读取:
for comp in tpl_data['template']:
if comp.get('component') == 'JCurrentTime':
ref_config = comp.get('config', {})
break
clock = {
'component': 'JCurrentTime',
'componentName': '实时日期',
'visible': True,
'i': bi_utils._gen_uuid(),
'x': 1580, 'y': 15, 'w': 320, 'h': 40,
'orderNum': 300,
'config': ref_config, # 直接用模板的 config
}
# 查询页面、追加组件、保存
page = query_page(page_id)
tmpl = page.get('template', [])
if isinstance(tmpl, str): tmpl = json.loads(tmpl)
tmpl.append(weather)
tmpl.append(clock)
bi_utils._page_components[page_id] = tmpl
save_page(page_id)
```
---
## 推荐方式:使用默认组件函数创建大屏(效果最佳)
> **重要:优先使用 bi_utils 的默认组件函数add_chart、add_number、add_text、add_ranking 等)逐个添加组件,只填充业务数据。** bi_utils 内置了经过验证的大屏样式预设深色配色、轴标签颜色、card 配置等),生成效果稳定且美观,远优于模板复制后批量替换的方式。
**大屏创建示例:**
```python
import sys
sys.path.insert(0, r'{后端项目根目录}')
from bi_utils import *
init_api('https://api3.boot.jeecg.com', 'your-token')
# 创建大屏style='bigScreen',像素坐标)
page_id = create_page('销售数据大屏', style='bigScreen', theme='dark',
background_image='/img/bg/bg4.png')
# 添加组件(坐标和尺寸单位为像素)
add_number(page_id, '今日销售额', x=50, y=50, w=400, h=200,
value=128560, prefix='¥', suffix='')
add_chart(page_id, 'JLine', '销售趋势', x=50, y=280, w=860, h=380,
categories=['1月','2月','3月','4月','5月','6月'],
series=[{'name':'销售额', 'data':[820,932,901,934,1290,1330]}])
add_chart(page_id, 'JBar', '部门业绩', x=950, y=280, w=860, h=380,
categories=['研发部','销售部','市场部','运营部'],
series=[{'name':'业绩', 'data':[320,302,341,374]}])
add_chart(page_id, 'JPie', '客户来源', x=50, y=700, w=500, h=350,
pie_data=[
{'name':'直接访问', 'value':335},
{'name':'邮件营销', 'value':310},
{'name':'联盟广告', 'value':234},
])
add_table(page_id, '销售明细', x=600, y=700, w=700, h=350,
columns=['日期','客户','金额','状态'],
data=[
{'日期':'2026-03-01','客户':'A公司','金额':'50000','状态':'已完成'},
{'日期':'2026-03-02','客户':'B公司','金额':'32000','状态':'进行中'},
])
# 添加装饰元素
add_border(page_id, x=30, y=30, w=440, h=240, border_type=1, color='#00BAFF')
add_decoration(page_id, x=660, y=20, w=600, h=60, deco_type=5, color='#00BAFF')
save_page(page_id)
print(f'大屏创建成功ID: {page_id}')
```
**大屏样式特点bi_utils.py 自动应用):**
- 背景:透明 `rgba(0,0,0,0)`
- 文字颜色:白色 `#ffffff`
- 轴标签:白色 `#ffffff`
- 网格线:`rgba(255,255,255,0.1)`
- 表格:深色背景 + 白色文字
## 大屏标题规则(重要)
### card.title 必须为空
大屏模式下,所有图表组件的 `option.card.title` 必须为空字符串 `''`。图表标题只通过 `option.title.text` 显示ECharts 内部标题)。
**原因:** card.title 会在组件顶部生成一个单独的卡片头部条(白色背景),与深色大屏背景严重冲突,且与 ECharts 的 option.title 形成双重标题。`bi_utils.py` 已自动处理此逻辑——大屏模式下 `_make_card()` 始终将 card.title 设为空。
### 大屏页面标题用 JText
大屏页面的主标题(如 "CRM 数据大屏")使用 `add_text()` 组件,推荐配置:
- **fontSize**: 40 以上(大屏标题要醒目)
- **fontWeight**: `'bold'`
- **letterSpacing**: 5增加间距提升视觉效果
- **color**: 白色 `#ffffff`
```python
add_text(page_id, 'CRM 数据大屏', x=560, y=15, w=800, h=60,
font_size=42, color='#ffffff', font_weight='bold',
text_align='center', letter_spacing=5)
```
### JText 正确的 config 格式
`add_text()` 内部使用的 config 结构(从真实模板验证):
```python
config = {
'dataType': 1,
'chartData': {'value': '显示文本'}, # 注意:是 dict 不是字符串
'option': {
'body': {
'color': '#ffffff',
'fontSize': 42,
'fontWeight': 'bold',
'letterSpacing': 5,
'text': '',
'marginTop': 0,
'marginLeft': 0,
},
'textAlign': 'center',
'card': {'title': '', ...},
},
}
```
### 不要用 JDragDecoration 做标题装饰
JDragDecoration 的各种 type红色虚线条、红绿色段等与大屏标题区域不搭配。真实的大屏模板中标题区域只用 JText 或 JImg不使用 JDragDecoration。JDragBorder 和 JDragDecoration 适合用在图表区域的边框装饰。
### Step 4: 输出结果
```
## 大屏创建成功
- 页面ID{id}
- 页面名称:{name}
- 模式大屏bigScreen
- 预览地址:{API_BASE}/drag/page/view/{id}
- 组件数量:{count} 个
请在大屏设计器中查看:打开 JeecgBoot 后台 → 大屏设计器 → 找到该页面
```
---
## 数据集管理(动态数据源)
大屏组件支持三种数据类型(`config.dataType`
- `1` — 静态数据(直接写在 `chartData` 中)
- `2` — 动态数据(从数据集获取,支持 SQL / API / JSON / WebSocket
- `4` — 表单数据(从表单关联字段查询)
### 数据集 API 端点
| 端点 | 方法 | 说明 |
|------|------|------|
| `/drag/onlDragDatasetHead/add` | POST | 创建数据集 |
| `/drag/onlDragDatasetHead/edit` | POST | 编辑数据集(需要 `sign` 字段) |
| `/drag/onlDragDatasetHead/delete?id=xxx` | DELETE | 删除数据集 |
| `/drag/onlDragDatasetHead/list` | GET | 分页查询数据集列表 |
| `/drag/onlDragDatasetHead/getAllChartData` | POST | 执行数据集查询(获取图表数据) |
| `/drag/onlDragDatasetHead/queryFieldBySql` | POST | 解析 SQL 返回字段列表 |
| `/drag/onlDragDatasetHead/queryFieldByApi` | POST | 解析 API 返回字段列表 |
### 数据集实体结构OnlDragDatasetHead
```python
{
'name': '数据集名称',
'code': '数据集编码', # 可选,唯一标识
'dataType': 'sql', # sql / api / json / singleFile / FILES
'dbSource': '707437208002265088', # 数据库源 IDSQL 类型必填!)
'querySql': 'SELECT ...', # SQL 语句SQL 类型)或 API 地址API 类型)
'apiMethod': 'get', # HTTP 方法API 类型用)
'izAgent': '0', # 是否代理:'0'=直连, '1'=服务端代理
'content': '', # 描述
'parentId': '', # 父级分类 ID
'datasetItemList': [ # 字段列表(注意:不是 onlDragDatasetItemList
{'fieldName': 'name', 'fieldTxt': '名称', 'fieldType': 'String', 'izShow': 'Y', 'orderNum': 0},
{'fieldName': 'value', 'fieldTxt': '数值', 'fieldType': 'String', 'izShow': 'Y', 'orderNum': 1}
],
'datasetParamList': [ # 参数列表(注意:不是 onlDragDatasetParamList
{'paramName': 'sex', 'paramTxt': '性别', 'paramValue': '1', 'dictCode': 'sex'}
]
}
```
### 创建 SQL 数据集
```python
import sys, json
sys.path.insert(0, r'{后端项目根目录}')
import bi_utils
bi_utils.init_api('http://api3.boot.jeecg.com', 'your-token')
# 创建 SQL 数据集dbSource 必填!)
result = bi_utils._request('POST', '/drag/onlDragDatasetHead/add', data={
'name': '用户男女比例统计',
'code': 'user_sex_ratio',
'dataType': 'sql',
'dbSource': '707437208002265088', # 本地 MySQL 数据源 ID
'querySql': "SELECT sex as name, COUNT(*) AS value FROM demo WHERE sex IS NOT NULL AND sex != '' GROUP BY sex",
'apiMethod': 'GET',
'datasetItemList': [
{'fieldName': 'name', 'fieldTxt': 'name', 'fieldType': 'String', 'izShow': 'Y', 'orderNum': 0},
{'fieldName': 'value', 'fieldTxt': 'value', 'fieldType': 'String', 'izShow': 'Y', 'orderNum': 1}
],
'datasetParamList': []
})
dataset_id = result['result']['id']
# 测试数据集
test = bi_utils._request('POST', '/drag/onlDragDatasetHead/getAllChartData', data={'id': dataset_id})
print(json.dumps(test, ensure_ascii=False))
# 返回: {"success":true, "result":{"data":[{"name":"1","value":6},{"name":"2","value":5}]}}
```
### 创建 API 数据集
```python
result = bi_utils._request('POST', '/drag/onlDragDatasetHead/add', data={
'name': '产品销量排行榜',
'code': 'product_sales',
'dataType': 'api',
'dbSource': None, # API 类型不需要数据库源
'querySql': 'https://api.jeecg.com/mock/31/graphreport/aiproducttest', # API 地址存在 querySql 字段
'apiMethod': 'get',
'izAgent': '0', # '0'=前端直连, '1'=后端代理(跨域时用)
'datasetItemList': [
{'fieldName': 'name', 'fieldTxt': 'name', 'fieldType': 'String', 'izShow': 'Y', 'orderNum': 0},
{'fieldName': 'value', 'fieldTxt': 'value', 'fieldType': 'String', 'izShow': 'Y', 'orderNum': 1}
],
'datasetParamList': []
})
dataset_id = result['result']['id']
```
### 组件绑定数据集dataType=2
组件的 `config` 中需要设置以下字段来绑定数据集:
```python
config = {
'dataType': 2, # 2=动态数据
'dataSetId': dataset_id, # 数据集 ID
'dataSetName': '数据集名称',
'dataSetType': 'sql', # sql / api / json / websocket
'dataSetApi': 'SELECT ...', # SQL 语句或 API 地址
'dataSetMethod': 'get', # HTTP 方法
'dataSetIzAgent': '1', # SQL 类型用 '1'走后端代理API 直连用 '0'
'dataMapping': [ # 字段映射(关键!)
{'filed': '维度', 'mapping': 'name'}, # 注意filed 不是 field系统拼写
{'filed': '数值', 'mapping': 'value'},
# {'filed': '分组', 'mapping': 'type'}, # 多系列图表需要
],
'chartData': '[]', # 动态数据时可为空数组
'option': { ... } # ECharts 配置
}
```
### 标准字段映射规则
| 映射标签filed | 标准字段key | 说明 |
|-------------------|----------------|------|
| `维度` / `名称` | `name` | 图表类目/维度 |
| `数值` | `value` | 图表数值 |
| `分组` | `type` | 多系列区分字段 |
| `文本` | `label` | 文本标签 |
### 组件绑定数据集完整示例SQL 饼图)
```python
pie_comp = {
'component': 'JPie',
'componentName': '男女比例',
'visible': True,
'i': bi_utils._gen_uuid(),
'x': 750, 'y': 700, 'w': 450, 'h': 350,
'orderNum': 300,
'config': {
'dataType': 2,
'w': 450, 'h': 350,
'size': {'width': 450, 'height': 350},
'dataSetId': dataset_id,
'dataSetName': '用户男女比例统计',
'dataSetType': 'sql',
'dataSetApi': "SELECT sex as name, COUNT(*) AS value FROM demo ...",
'dataSetMethod': 'GET',
'dataSetIzAgent': '1',
'dataMapping': [
{'filed': '维度', 'mapping': 'name'},
{'filed': '数值', 'mapping': 'value'}
],
'chartData': '[]',
'option': { ... }
}
}
```
### 组件绑定数据集完整示例API 柱形图)
```python
bar_comp = {
'component': 'JBar',
'componentName': '销量排行',
'visible': True,
'i': bi_utils._gen_uuid(),
'x': 1350, 'y': 700, 'w': 540, 'h': 350,
'orderNum': 300,
'config': {
'dataType': 2,
'w': 540, 'h': 350,
'size': {'width': 540, 'height': 350},
'dataSetType': 'api',
'dataSetApi': 'https://api.jeecg.com/mock/31/graphreport/aiproducttest',
'dataSetMethod': 'get',
'dataSetIzAgent': '0', # API 直连不走代理
'dataMapping': [
{'filed': '维度', 'mapping': 'name'},
{'filed': '数值', 'mapping': 'value'}
],
'chartData': '[]',
'option': { ... }
}
}
```
### 数据集踩坑记录
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| **"数据源不存在"** | SQL 数据集未设置 `dbSource` | 必须指定 `dbSource`(如 `707437208002265088` |
| **字段列表不生效** | 用了 `onlDragDatasetItemList` | 正确字段名是 `datasetItemList` |
| **编辑数据集 510 权限错误** | 缺少 `sign` 字段 | 编辑时需传 `sign: 'E19D6243CB1945AB4F7202A1B00F77D5'` |
| **dataMapping 的 filed 拼写** | 系统中 `filed` 不是 `field` | 必须用 `filed`(少一个 d这是系统设计 |
| **API 类型跨域** | 前端直连外部 API 遇到 CORS | 设置 `izAgent: '1'` 走后端代理 |
| **SQL 参数替换** | 需要动态参数 | SQL 中用 `#{paramName}`(系统变量)或 `${paramName}`FreeMarker |
| **SQL 最大返回 1000 条** | 后端限制 | `getChartData` 方法限制最大 1000 条记录 |
### 数据库源 ID 参考
| 环境 | dbSource / dbCode | 说明 |
|------|-------------------|------|
| api3.boot.jeecg.com 主库 | `707437208002265088` | 默认 MySQL 数据库 |
> **注意**:不同环境的 dbSource ID 不同,部署到新环境时需要通过 `/sys/dataSource/list` 查询可用的数据源列表。
---
## 编辑已有大屏
```python
from bi_utils import *
init_api('https://api3.boot.jeecg.com', 'your-token')
page = query_page(page_id)
print(page['name'], page['updateCount'])
add_chart(page_id, 'JBar', '新增图表', x=0, y=500, w=600, h=300,
categories=['A','B','C'], series=[{'name':'','data':[10,20,30]}])
save_page(page_id)
```
---
## 删除大屏
```python
from bi_utils import *
init_api('https://api3.boot.jeecg.com', 'your-token')
delete_page(page_id) # 软删除
delete_page(page_id, physical=True) # 硬删除
recover_page(page_id) # 恢复
```
---
## 修改组件样式
阅读 `references/bi-comp-option-config.md` 获取每种组件的完整配置项路径。
**关键规则:**
- 颜色使用色值(`#000000`),不用英文单词
- customColor 格式:`[{color1:'#xxx',color:'#xxx'}]`(适用于 JPie/JLine/JBar 等 20+ 组件)
- 柱体颜色:`option.series[index].itemStyle.color`
```python
import sys, json
sys.path.insert(0, r'{后端项目根目录}')
from bi_utils import *
import bi_utils
init_api('https://api3.boot.jeecg.com', 'your-token')
page_id = 'xxx'
page = query_page(page_id)
tmpl = page.get('template', [])
if isinstance(tmpl, str):
tmpl = json.loads(tmpl)
for comp in tmpl:
config_str = comp.get('config', '{}')
config = json.loads(config_str) if isinstance(config_str, str) else config_str
if comp.get('component') == 'JBar':
option = config.get('option', {})
option['series'][0]['itemStyle'] = {'color': '#FF0000'}
config['option'] = option
comp['config'] = json.dumps(config, ensure_ascii=False)
bi_utils._page_components[page_id] = tmpl
save_page(page_id)
```
---
## 可用的快捷函数
**API 初始化:**
- `init_api(api_base, token)` — 初始化 API 地址和 Token
**页面管理:**
- `create_page(name, style='bigScreen', theme='dark', background_image, type_id, design_type)` — 创建大屏
- `query_page(page_id)` — 查询页面详情
- `list_pages(style='bigScreen')` — 列表查询
- `save_page(page_id)` — 保存设计
- `delete_page(page_id, physical)` — 删除
- `recover_page(page_id)` — 恢复
- `copy_page(page_id)` — 复制
**添加组件(像素坐标):**
- `add_number(page_id, title, x, y, w, h, value, prefix, suffix)` — 数字指标
- `add_chart(page_id, chart_type, title, x, y, w, h, categories, series, pie_data)` — 图表
- `add_table(page_id, title, x, y, w, h, columns, data)` — 数据表格
- `add_scroll_table(page_id, title, x, y, w, h, columns, data)` — 滚动表格
- `add_ranking(page_id, title, x, y, w, h, data)` — 排行榜
- `add_text(page_id, title, x, y, w, h, content, font_size, color)` — 文本
- `add_image(page_id, title, x, y, w, h, src)` — 图片
- `add_gauge(page_id, title, x, y, w, h, value, max_val, unit, color)` — 仪表盘表盘
- `add_liquid(page_id, title, x, y, w, h, value, color)` — 水球图
- `add_countdown(page_id, title, x, y, w, h, value, font_size, color)` — 翻牌器
- `add_border(page_id, x, y, w, h, border_type, color)` — 装饰边框
- `add_decoration(page_id, x, y, w, h, deco_type, color)` — 装饰条
- `add_component(page_id, component, title, x, y, w, h, config)` — 通用组件
---
## API 踩坑记录
| 问题 | 说明 |
|------|------|
| `POST /drag/page/add` 返回值 | 返回完整实体含 ID`result.id` 即页面 ID |
| `POST /drag/page/edit` 乐观锁 | 必须传 `updateCount`(当前数据库值) |
| Windows curl 中文问题 | 必须用 Python urllib/requests |
| 坐标单位 | 大屏用**像素**坐标 |
| 组件 config 分离 | config 存在 onl_drag_page_comp 表 |
| **chartData 必须是 JSON 字符串** | `config.chartData` 的值必须是 `json.dumps(...)` 后的字符串,不能是原生 list/dict |
| **图表标题去重** | 大屏和仪表盘的图表组件 `option.card.title` 都应为空字符串,标题仅通过 `option.title.text` 显示 |
| **多系列 chartData 格式** | 多系列图表需要 `type` 字段区分:`[{"name":"1月","value":10,"type":"系列A"}]` |
| **HTTPS 连接问题** | api3.boot.jeecg.com 使用 HTTP 协议,`init_api` 时用 `http://` |
| **模板复制后页签切换失效** | 复制模板时必须建立旧→新 ID 映射,更新 JTabToggle 的 `compVals` 和 JGroup 的 `props.elements` 内引用 |
| **新增组件不显示** | config 不完整或被背景图遮挡。必须从模板中同类组件复制 config并设 `orderNum: 300` 提高层级 |
| **JGroup 子组件存储位置** | JGroup 的子组件在 `comp.props.elements` 数组中(不是 config.chartData 也不是 group 字段) |
## 错误处理
| 错误 | 解决方案 |
|------|---------|
| Token 过期401 | 重新获取 X-Access-Token |
| `updateCount` 不匹配 | 重新查询页面获取最新值 |
| 组件不显示 | 检查 dataType、chartData必须是 JSON 字符串、option 是否完整 |
| 新增组件不显示 | **从模板中复制同类组件的完整 config**,不要自己拼装;设 `orderNum: 300` |
| 布局错乱 | 确认使用像素坐标(不是栅格) |
| 中文乱码 | 使用 Python不要用 curl |
| 页签切换不工作 | 检查 JTabToggle 的 `compVals` 是否指向正确的 JGroup `i` 值 |
## 参考文档
- `references/bi-component-types.md` — 完整组件类型清单
- `references/bi-comp-option-config.md` — 组件样式配置路径
- `references/bi_utils.py` — 工具库源码
- `references/templates/bigScreen/` — 40 个大屏模板 JSON 参考

View File

@@ -0,0 +1,891 @@
# 大屏组件配置修改参考
修改大屏组件样式时,根据组件类型和修改目标,使用对应的配置路径。
## 修改输出格式
只返回需要修改的属性,不包含未修改的配置:
```json
{
"compConfig": {
"option": {
"series": [{ "itemStyle": { "color": "#FFFF00" } }]
}
}
}
```
修改名称/背景等基础属性:
```json
{
"compConfig": {
"name": "京东销量柱形图",
"background": "#000000"
}
}
```
## 颜色修改规则
### customColor 组件列表
以下组件的颜色属性使用 `customColor` 格式修改:
- JRadioButton, JRadialBar, JActiveRing, JRing, JPyramidFunnel, JFunnel
- JBubble, DoubleLineBar, JMultipleLine, JArea, JLine
- JRotatePie, JRose, JPie, JMixLineBar, JPercentBar
- JMultipleBar, JCapsuleChart, JStackBar, JQuadrant
格式:
```json
"customColor": [
{"color1": "#FF0000", "color": "#FF0000"},
{"color1": "#00FF00", "color": "#00FF00"}
]
```
### 柱体颜色
普通柱状图使用 `option.series[${index}].itemStyle.color`
JDynamicBar 等也使用 `option.series[${index}].itemStyle.color`
### 其他组件
不包含 customColor 属性的组件,按照对应组件配置的属性 value 值修改
## 通用规则
- 颜色使用具体色值(如 `#000000`),不使用英文单词(如 black
- 字体粗细可选值:`normal`(默认)、`bold`(粗体)、`lighter`(细体)
- Y轴单位 `option.yAxis.yUnit`:预设值有 `%`(百分比)、`K`(千)、`W`(万)、`M`(亿);自定义单位时设 `yUnit: 'CUSTOM'` 并设 `yCustomUnit: '元'`
## 基础配置 (BasicOption)
| 说明 | 配置路径 |
|------|---------|
| 图层名称 | `name` |
| 图层背景色 | `background` |
| 图层边框线 | `borderColor` |
| 提示语显隐 | `option.tooltip.show` |
| 提示语字体大小 | `option.tooltip.textStyle.fontSize` |
| 提示语字体颜色 | `option.tooltip.textStyle.color` |
## 标题设置 (TitleOption)
| 说明 | 配置路径 |
|------|---------|
| 标题名称 | `option.title.text` |
| 标题字体大小 | `option.title.textStyle.fontSize` |
| 标题字体颜色 | `option.title.textStyle.fontColor` |
| 标题字体粗细 | `option.title.textStyle.fontWeight` |
| 副标题名称 | `option.title.subtextStyle` |
| 副标题字体大小 | `option.title.subtextStyle.fontSize` |
| 副标题字体颜色 | `option.title.subtextStyle.fontColor` |
| 左对齐 | `option.title.left` |
| 垂直居中 | `option.title.top` |
## X轴设置 (XAxisOption)
| 说明 | 配置路径 |
|------|---------|
| X轴名称 | `option.xAxis.name` |
| X轴名称颜色 | `option.xAxis.nameTextStyle.color` |
| X轴名称字体大小 | `option.xAxis.nameTextStyle.fontSize` |
| X轴标签颜色 | `option.xAxis.axisLabel.color` |
| X轴标签角度 | `option.xAxis.axisLabel.rotate` |
| X轴轴线颜色 | `option.xAxis.axisLine.lineStyle.color` |
| X轴类型 | `option.xAxis.type` |
| X轴网格线显隐 | `option.xAxis.splitLine.show` |
| X轴网格线颜色 | `option.xAxis.splitLine.lineStyle.color` |
## Y轴设置 (YAxisOption)
| 说明 | 配置路径 | 备注 |
|------|---------|------|
| Y轴名称 | `option.yAxis.name` | |
| Y轴名称颜色 | `option.yAxis.nameTextStyle.color` | |
| Y轴名称字体大小 | `option.yAxis.nameTextStyle.fontSize` | |
| Y轴标签颜色 | `option.yAxis.axisLabel.color` | |
| Y轴标签角度 | `option.yAxis.axisLabel.rotate` | |
| Y轴轴线颜色 | `option.yAxis.axisLine.lineStyle.color` | |
| Y轴类型 | `option.yAxis.type` | |
| Y轴网格线显隐 | `option.yAxis.splitLine.show` | |
| Y轴网格线颜色 | `option.yAxis.splitLine.lineStyle.color` | |
| Y轴单位 | `option.yAxis.yUnit` | 预设: `%`, `K`, `W`, `M`;自定义: 设为 `CUSTOM` 并设 `yCustomUnit` |
## 图例设置 (LegendOption)
| 说明 | 配置路径 |
|------|---------|
| 图例字体大小 | `option.legend.textStyle.fontSize` |
| 图例排列方向 | `option.legend.orient` |
| 图例上下边距 | `option.legend.t` |
| 图例左右边距 | `option.legend.r` |
## 柱体设置 (BarCylinder)
| 说明 | 配置路径 |
|------|---------|
| 柱体宽度 | `option.series[${index}].barWidth` |
| 柱体圆角 | `option.series[${index}].itemStyle.borderRadius` |
| 柱体颜色 | `option.series[${index}].itemStyle.color` |
| 柱体背景色显隐 | `option.series[${index}].showBackground` |
| 柱体背景色颜色 | `option.series[${index}].backgroundStyle.color` |
## 折线设置 (PolyglineOption)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| 折线类型 | `option.series[${index}].lineType` | `line`(折线), `smooth`(曲线), `area`(面积) |
| 透明度 | `option.series[0].areaStyleOpacity` | |
| 线条宽度 | `option.series[${index}].lineWidth` | |
| 标记点 | `option.series[${index}].symbol` | |
| 点的大小 | `option.series[${index}].symbolSize` | |
## 饼图设置 (pieSettingOption)
| 说明 | 配置路径 |
|------|---------|
| 设置成环形 | `option.isRadius` |
| 内环半径 | `option.innerRadius` |
| 外环半径 | `option.outRadius` |
| 南丁格尔玫瑰 | `option.isRose` |
| 标签显示位置 | `option.pieLabelPosition` |
## 坐标轴边距 (GridOption)
| 说明 | 配置路径 |
|------|---------|
| 左边距 | `option.grid.left` |
| 顶边距 | `option.grid.top` |
| 右边距 | `option.grid.right` |
| 底边距 | `option.grid.bottom` |
## 数值设置 (NumOption)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| 显示数值 | `option.series[${index}].label.show` | |
| 数值位置 | `option.series[${index}].label.position` | `top`(顶部), `""`(中间), `insideBottom`(底部) |
| 数值格式 | `option.label.format` | |
| 数值颜色 | `option.series[${index}].label.color` | |
| 数值字体大小 | `option.series[${index}].label.fontSize` | |
| 数值字体粗细 | `option.series[${index}].label.fontWeight` | |
| 数值单位显隐 | `option.showUnit.show` | |
| 数值单位数量级 | `option.showUnit.numberLevel` | `1`(百分比), `3`(千), `4`(万) |
| 数值单位小数位 | `option.showUnit.decimal` | |
## 文本设置 (TextOption) - JText 组件
| 说明 | 配置路径 |
|------|---------|
| 字体大小 | `option.body.fontSize` |
| 字体间距 | `option.body.letterSpacing` |
| 字体颜色 | `option.body.color` |
| 千分符 | `option.body.thousandSeparator` |
| 水平间距 | `option.body.marginLeft` |
| 垂直间距 | `option.body.marginTop` |
| 跑马灯 | `option.horseLamp` |
| 超链接开关 | `option.isLink` |
| 超链接地址 | `option.openUrl` |
## 翻牌器设置 (CountToTextOption) - JCountTo 组件
| 说明 | 配置路径 |
|------|---------|
| 字体粗细 | `option.fontWeight` |
| 字体颜色 | `option.fontColor` |
| 字体大小 | `option.fontSize` |
| 前缀文本 | `option.prefix` |
| 前缀字体大小 | `option.prefixFontSize` |
| 前缀字体颜色 | `option.prefixColor` |
| 前缀字体粗细 | `option.prefixFontWeight` |
| 前缀对齐方式 | `option.prefixTextAlign` |
| 前缀X间距 | `option.prefixGridX` |
| 前缀Y间距 | `option.prefixGridY` |
| 后缀文本 | `option.suffix` |
| 后缀字体大小 | `option.suffixFontSize` |
| 后缀字体颜色 | `option.suffixColor` |
| 后缀字体粗细 | `option.suffixFontWeight` |
| 后缀对齐方式 | `option.suffixTextAlign` |
| 后缀X间距 | `option.suffixGridX` |
| 后缀Y间距 | `option.suffixGridY` |
## 进度条设置 (CustomProgressOption)
| 说明 | 配置路径 |
|------|---------|
| 目标颜色 | `option.backgroundColor` |
| 进度颜色 | `option.progressColor` |
| 进度条宽度 | `option.barWidth` |
| 边距 | `option.padding` |
| 标题颜色 | `option.titleColor` |
| 标题字体大小 | `option.titleFontSize` |
| 标题位置 | `option.titlePosition` |
| 数值颜色 | `option.valueColor` |
| 数值字体大小 | `option.valueFontSize` |
| 数值位置 | `option.valuePosition` |
| 数值横向偏移 | `option.valueXOffset` |
## 列表进度图设置 (ListProgressOption)
| 说明 | 配置路径 |
|------|---------|
| 行高度 | `option.row.height` |
| 行左边距 | `option.row.marginLeft` |
| 行右边距 | `option.row.marginRight` |
| 行上边距 | `option.row.marginTop` |
| 进度条颜色 | `option.bar.background.color` |
| 进度条填充色 | `option.bar.fill.color` |
| 进度条高度 | `option.bar.height` |
| 进度条圆角 | `option.bar.borderRadius` |
| 指示点大小 | `option.bar.indicatorSize` |
| 指示点颜色 | `option.bar.indicatorColor` |
| 显示边框 | `option.bar.border.enabled` |
| 边框颜色 | `option.bar.border.color` |
| 边框大小 | `option.bar.border.width` |
| 边框边距 | `option.bar.border.padding` |
## 水波图设置 (LiquidPlotOption) - JLiquid 组件
| 说明 | 配置路径 |
|------|---------|
| 显示类型 | `option.liquidType` |
| 波纹颜色 | `option.color` |
| 波纹个数 | `option.count` |
| 波纹长度 | `option.length` |
| 外框颜色 | `option.borderColor` |
| 外框宽度 | `option.borderWidth` |
| 间距 | `option.distance` |
| 透明度 | `option.strokeOpacity` |
| 文本颜色 | `option.textColor` |
| 文本字体大小 | `option.textFontSize` |
## 象形图设置 (PictorialOption)
| 说明 | 配置路径 |
|------|---------|
| 柱体颜色 | `option.barColor` |
| 透明度 | `option.barOpacity` |
| 间距 | `option.count` |
## 仪表盘设置 (GaugeOption)
| 说明 | 配置路径 |
|------|---------|
| 刻度值显隐 | `option.series[0].axisLabel.show` |
| 刻度值颜色 | `option.series[0].axisLabel.color` |
| 刻度值字体大小 | `option.series[0].axisLabel.fontSize` |
| 刻度线显隐 | `option.series[0].axisTick.show` |
| 刻度线长度 | `option.series[0].axisTick.length` |
| 刻度线颜色 | `option.series[0].axisTick.lineStyle.color` |
| 分割线显隐 | `option.series[0].splitLine.show` |
| 分割线长度 | `option.series[0].splitLine.length` |
| 分割线颜色 | `option.series[0].splitLine.lineStyle.color` |
| 指标字号 | `option.series[0].detail.fontSize` |
## 渐变仪表盘设置 (AntvGaugeOption)
| 说明 | 配置路径 |
|------|---------|
| 粗细 | `option.gaugeWidth` |
| 刻度值显隐 | `option.axisLabelShow` |
| 刻度值颜色 | `option.axisLabelColor` |
| 刻度值字体大小 | `option.axisLabelFontSize` |
| 刻度线显隐 | `option.axisTickShow` |
| 刻度线颜色 | `option.lineColor` |
| 文本颜色 | `option.valueColor` |
| 文本字体大小 | `option.valueFontSize` |
| 指针颜色 | `option.indicatorColor` |
| 指针粗细 | `option.indicatorLength` |
## 环形图设置 (ActiveRingPlotOption)
| 说明 | 配置路径 |
|------|---------|
| 颜色 | `option.color` |
| 背景色 | `option.bgColor` |
| 外环半径 | `option.outRadius` |
| 内环半径 | `option.innerRadius` |
| 标题字体大小 | `option.fontSize` |
| 标题字体颜色 | `option.fontColor` |
| 标题字体粗细 | `option.fontWeight` |
| 数值字体大小 | `option.valueFontSize` |
| 数值字体颜色 | `option.valueFontColor` |
| 数值字体粗细 | `option.valueFontWeight` |
## 动态环形图设置 (ActiveRingOption)
| 说明 | 配置路径 |
|------|---------|
| 显示原始值 | `option.showOriginValue` |
| 文字颜色 | `option.textColor` |
| 文字大小 | `option.textFontSize` |
| 线条宽度 | `option.lineWidth` |
| 环半径 | `option.radius` |
| 动态环半径 | `option.activeRadius` |
## 玉珏设置 (RadialBarOption)
| 说明 | 配置路径 |
|------|---------|
| 显示圆角 | `option.radiuShow` |
| 背景显示 | `option.bgShow` |
| 外环半径 | `option.radius` |
| 内环半径 | `option.innerRadius` |
| 最大旋转角 | `option.maxAngle` |
## 矩形图设置 (RectangleOption)
| 说明 | 配置路径 |
|------|---------|
| 文本颜色 | `option.titleColor` |
| 文本字体大小 | `option.titleFontSize` |
| 显示图例 | `option.showLegend` |
## 颜色块设置 (ColorBlockOption)
| 说明 | 配置路径 |
|------|---------|
| 行数 | `option.lineNum` |
| 边距 | `option.padding` |
| X间距 | `option.borderSplitx` |
| Y间距 | `option.borderSplity` |
| 小数位数 | `option.decimals` |
| 字体大小 | `option.fontSize` |
| 字体颜色 | `option.color` |
| 字体粗细 | `option.fontWeight` |
| 对齐方式 | `option.textAlign` |
| 前缀字体颜色 | `option.prefixColor` |
| 前缀字体粗细 | `option.prefixFontWeight` |
| 前缀X间距 | `option.prefixSplitx` |
| 前缀Y间距 | `option.prefixSplity` |
| 后缀字体大小 | `option.suffixFontSize` |
| 后缀字体颜色 | `option.suffixColor` |
| 后缀字体粗细 | `option.suffixFontWeight` |
| 后缀X间距 | `option.suffixSplitx` |
## 字符云设置 (WordCloudOption)
| 说明 | 配置路径 |
|------|---------|
| 字体颜色 | `option.color` |
| 字体间距 | `option.padding` |
| 字体旋转 | `option.rotation` |
| 字体最大值 | `option.minSize` |
| 字体最小值 | `option.maxSize` |
| 形状 | `option.series[0].shape` |
## 闪光云设置 (FlashCloudOption)
| 说明 | 配置路径 |
|------|---------|
| 缩放 | `option.zoom` |
| 字体大小 | `option.textSize` |
| 字体颜色 | `option.textColor` |
## 轮播表格设置 (ScrollBoardOpt)
| 说明 | 配置路径 |
|------|---------|
| 悬浮暂停 | `option.hoverPause` |
| 等待时间 | `option.waitTime` |
| 开启排名 | `option.index` |
| 列宽 | `option.indexWidth` |
| 显示表头 | `option.headShow` |
| 表头颜色 | `option.headerBGC` |
| 表头行高 | `option.headerHeight` |
| 每页行数 | `option.rowNum` |
| 奇行颜色 | `option.oddRowBGC` |
| 偶行颜色 | `option.evenRowBGC` |
## 表格设置 (ScrollTableStyle)
| 说明 | 配置路径 |
|------|---------|
| 开启排名 | `option.ranking` |
| 开启滚动 | `option.scroll` |
| 滚动时间 | `option.scrollTime` |
| 显示表头 | `option.showHead` |
| 表头背景颜色 | `option.headerBgColor` |
| 表头字体颜色 | `option.headerFontColor` |
| 表头字体大小 | `option.fontSize` |
| 行高 | `option.lineHeight` |
| 边框显示 | `option.showBorder` |
| 边框宽度 | `option.borderWidth` |
| 边框颜色 | `option.borderColor` |
| 边框线类型 | `option.borderStyle` |
| 表格字体颜色 | `option.bodyFontColor` |
| 表格字体大小 | `option.bodyFontSize` |
| 奇行颜色 | `option.oddColor` |
| 偶行颜色 | `option.evenColor` |
## 数据表格设置 (TableStyle)
| 说明 | 配置路径 |
|------|---------|
| 表头背景颜色 | `option.headerBgColor` |
| 表头字体大小 | `option.headerFontSize` |
| 表头字体颜色 | `option.headerColor` |
| 内容字体颜色 | `option.bodyColor` |
| 内容字体大小 | `option.bodyFontSize` |
| 内容背景颜色 | `option.bodyBgColor` |
## 列表设置 (ListStyle)
| 说明 | 配置路径 |
|------|---------|
| 显示标题前缀 | `option.showTitlePrefix` |
| 显示时间前缀 | `option.showTimePrefix` |
| 布局 | `option.layout` |
| 标题字体颜色 | `option.titleFontColor` |
| 标题字体粗细 | `option.titleFontWeight` |
| 标题字体大小 | `option.titleFontSize` |
| 图标颜色 | `option.iconColor` |
| 内容颜色 | `option.contentColor` |
| 开启动画 | `option.isEnableAnimation` |
| 轮播时间(ms) | `option.scrollTime` |
## 滚动设置 (ScrollOption)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| 是否排序 | `option.sort` | |
| 轮播方式 | `option.carousel` | `single`(单行), `page`(整页) |
| 显示行数 | `option.rowNum` | |
| 滚动时间(ms) | `option.waitTime` | |
## 历程设置 (DevHistoryOption)
| 说明 | 配置路径 |
|------|---------|
| 缩放 | `option.zoom` |
| 轮播间隔 | `option.waitTime` |
| 背景色 | `option.typeBackColor` |
| 字体颜色 | `option.typeFontColor` |
| 内容字体颜色 | `option.titleColor` |
| 内容字体大小 | `option.titleFontSize` |
## 气泡排名设置 (BubbleRankingStyle)
| 说明 | 配置路径 |
|------|---------|
| 比例 | `option.zoom` |
| 显示提示词 | `option.showTip` |
| 提示词颜色 | `option.titleColor` |
| 提示词宽度 | `option.tipWidth` |
| 提示词内容颜色 | `option.tipFontColor` |
| 提示词内容字体大小 | `option.tipFontSize` |
## 3D金字塔/漏斗设置 (Pyramid3DOption)
| 说明 | 配置路径 |
|------|---------|
| 缩放 | `option.zoom` |
| 尺寸 | `option.size` |
## 环形设置 (RingOption)
| 说明 | 配置路径 |
|------|---------|
| 内半径 | `option.innerRadius` |
| 外半径 | `option.outRadius` |
## 南丁格尔玫瑰设置 (RoseOption)
| 说明 | 配置路径 |
|------|---------|
| 边框宽度 | `option.series[0].itemStyle.borderWidth` |
| 颜色透明度 | `option.series[0].itemStyle.colorOpacity` |
## 胶囊图设置 (CapsuleChartOption)
| 说明 | 配置路径 |
|------|---------|
| 显示数值 | `option.showValue` |
| X轴名称 | `option.unit` |
## 百分比柱状图样式 (PercentBarStyle)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| Y轴刻度颜色 | `option.yNameFontColor` | |
| Y轴刻度字体大小 | `option.yNameFontSize` | |
| X轴刻度颜色 | `option.xNameFontColor` | |
| X轴刻度字体大小 | `option.xNameFontSize` | |
| 图例位置 | `option.legendPosition` | `top`(居上), `bottom`(居下) |
| 图例字体颜色 | `option.legendFontColor` | |
| 图例字体大小 | `option.legendFontSize` | |
## 进度条 ECharts 设置 (ProgressOption)
| 说明 | 配置路径 |
|------|---------|
| 显示标题 | `option.yAxis.axisLabel.show` |
| 标题字体颜色 | `option.yAxis.axisLabel.color` |
| 标题字体大小 | `option.yAxis.axisLabel.fontSize` |
| 数值字体颜色 | `option.series[1].label.color` |
| 数值字体大小 | `option.series[1].label.fontSize` |
| 横向偏移 | `option.valueXOffset` |
| 纵向偏移 | `option.valueYOffset` |
| 柱体宽度 | `option.series[0].barWidth` |
| 进度颜色 | `option.series[0].color` |
| 目标颜色 | `option.series[1].color` |
## 地图设置 (MapOption)
| 说明 | 配置路径 |
|------|---------|
| 显示区域名称 | `option.geo.label.normal.show` |
| 区域名称颜色 | `option.geo.label.normal.color` |
| 区域名称字体大小 | `option.geo.label.normal.fontSize` |
| 开启钻取 | `commonOption.breadcrumb.drillDown` |
| 鼠标缩放 | `option.geo.roam` |
| 缩放比例 | `option.geo.zoom` |
| 长宽比 | `option.geo.aspectScale` |
| 顶边距 | `option.geo.top` |
| 左边距 | `option.geo.left` |
## 地图配色设置 (LineMapColorOption)
| 说明 | 配置路径 |
|------|---------|
| 启用渐变色 | `commonOption.gradientColor` |
| 中心颜色 | `commonOption.areaColor.color1` |
| 边缘颜色 | `commonOption.areaColor.color2` |
| 区域颜色 | `commonOption.areaColor.color1` |
| 区域高亮颜色 | `option.geo.itemStyle.emphasis.areaColor` |
| 区域边界颜色 | `option.geo.itemStyle.normal.borderColor` |
| 阴影大小 | `option.geo.itemStyle.normal.shadowBlur` |
| 阴影水平偏移 | `option.geo.itemStyle.normal.shadowOffsetX` |
| 阴影垂直偏移 | `option.geo.itemStyle.normal.shadowOffsetY` |
| 阴影颜色 | `option.geo.itemStyle.normal.shadowColor` |
## 视觉映射设置 (VisualMapOption)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| 开启视觉映射 | `option.visualMap.show` | |
| 类型 | `option.visualMap.type` | `continuous`, `piecewise` |
| 文本颜色 | `option.visualMap.textStyle.color` | |
| 文本粗细 | `option.visualMap.textStyle.fontWeight` | |
| 文本字体大小 | `option.visualMap.textStyle.fontSize` | |
| 最小值 | `option.visualMap.min` | |
| 最大值 | `option.visualMap.max` | |
## 地图散点设置 (ScatterOption)
| 说明 | 配置路径 |
|------|---------|
| 散点大小 | `option.area.markerSize` |
| 散点形状 | `option.area.markerShape` |
| 散点类型 | `option.area.markerType` |
| 散点颜色 | `option.area.markerColor` |
| 散点文本显示 | `option.area.scatterLabelShow` |
| 散点文本颜色 | `option.area.scatterLabelColor` |
| 散点文本位置 | `option.area.scatterLabelPosition` |
| 散点文本字体大小 | `option.area.scatterFontSize` |
| 散点数量 | `option.area.markerCount` |
| 散点透明度 | `option.area.markerOpacity` |
## 热力地图设置 (HeatOption)
| 说明 | 配置路径 |
|------|---------|
| 热力点大小 | `commonOption.heat.pointSize` |
| 模糊大小 | `commonOption.heat.blurSize` |
| 最大透明度 | `commonOption.heat.maxOpacity` |
## 柱体地图设置 (BarMapOption)
| 说明 | 配置路径 |
|------|---------|
| 柱体大小 | `commonOption.barSize` |
| 柱体左侧颜色 | `commonOption.barColor` |
| 柱体右侧颜色 | `commonOption.barColor2` |
## 飞线地图设置 (FlyLineOption)
| 说明 | 配置路径 |
|------|---------|
| 动画时间 | `commonOption.effect.period` |
| 标记形状 | `commonOption.effect.markerShape` |
| 标记大小 | `commonOption.effect.symbolSize` |
| 标记颜色 | `commonOption.effect.markerColor` |
| 尾迹长度 | `commonOption.effect.trailLength` |
---
## 组件数据格式 (chartData)
### 柱状图/折线图/混合图
JBar, JStackBar, JLine, JSmoothLine, JStepLine, JMultipleLine, JArea, JMixLineBar, DoubleLineBar, JHorizontalBar, JBackgroundBar, JMultipleBar, JNegativeBar, JPercentBar
```json
[{"name": "一月", "value": 820, "type": "系列名"}]
```
双轴图额外字段:`"yAxisIndex": "0"``"1"`
### 饼图/环形图/玫瑰图
JPie, JRose, JRing, JRotatePie, JBreakRing, JActiveRing, JRadialBar, JFunnel, JPyramidFunnel
```json
[{"name": "类别", "value": 800}]
```
### 仪表盘
JGauge, JColorGauge, JAntvGauge
```json
[{"min": 0, "max": 100, "label": "完成率", "value": 76}]
```
### 半圆仪表盘
JSemiGauge
```json
[{"total": 800, "used": 500}]
```
### 水球图
JLiquid值为 0-100前端自动除以100
```json
[{"value": 75}]
```
### 数字指标
JNumber对象格式不是数组
```json
{"value": 128560}
```
### 翻牌器
JCountTo
```json
{"value": 1024}
```
### 文本
JText
```json
{"value": "显示的文字内容"}
```
### 排行榜
JScrollRankingBoard直接数组不要 JSON.stringify
```json
[{"name": "北京", "value": 1200}, {"name": "上海", "value": 1050}]
```
### 滚动表格
JScrollTable数组 + option.fieldMapping
```json
[{"col1": "值1", "col2": "值2"}]
```
option 需配合 `fieldMapping: [{"name": "列名", "key": "col1", "width": "30%"}]`
### 数据表格
JTable, JCommonTable
```json
[
{"fieldTxt": "姓名", "fieldName": "name", "type": "field", "isShow": "Y"},
{"fieldTxt": "年龄", "fieldName": "age", "type": "field", "isShow": "Y"}
]
```
### 数据列表
JList
```json
[{"title": "标题", "date": "2026-03-18", "desc": "描述", "avatar": "url"}]
```
### 词云
JWordCloud, JImgWordCloud, JFlashCloud
```json
[{"name": "关键词", "value": 100}]
```
### 地图组件
JAreaMap, JBubbleMap, JFlyLineMap, JBarMap, JHeatMap
```json
[{"name": "城市名", "value": 199}]
```
### 按钮
JRadioButton, JCustomButton
```json
[{"title": "按钮文字", "value": 0, "href": "https://example.com"}]
```
### 轮播图
JCarousel
```json
[{"src": "https://example.com/1.png"}, {"src": "https://example.com/2.png"}]
```
### 进度条
JProgress
```json
[{"name": "任务A", "value": 80, "total": 100}]
```
### 胶囊图
JCapsuleChart
```json
[{"name": "类目", "value": 500}]
```
### 性别比例
JGender
```json
[{"man": 60, "woman": 40}]
```
### 统计卡片
JStatsSummary
```json
[{"title": "指标名", "value": 1234, "unit": "元", "compare": 12.5, "label": "同比", "state": "up"}]
```
---
## 组件与设置面板映射表
每个组件在设计器右侧面板显示的配置项列表optionList以下为完整映射
### 柱状图系列
| 组件 | 设置面板 |
|------|---------|
| JBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, CustomColorOption, OtherOption |
| JStackBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, CustomColorOption |
| JDynamicBar | BasicOption, TitleOption, XAxisOption, YAxisOption, GridOption, BarCylinder |
| JHorizontalBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, CustomColorOption |
| JBackgroundBar | BasicOption, TitleOption, XAxisOption, YAxisOption, GridOption, NumOption, BarCylinder |
| JMultipleBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, CustomColorOption |
| JNegativeBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, CustomColorOption |
| JPercentBar | BasicOption, PercentBarStyle, CustomColorOption |
| JCapsuleChart | BasicOption, CapsuleChartOption, CustomColorOption |
### 折线/面积图系列
| 组件 | 设置面板 |
|------|---------|
| JLine | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, PolyglineOption, CustomColorOption, OtherOption |
| JSmoothLine | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, PolyglineOption |
| JStepLine | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption |
| JArea | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, PolyglineOption, CustomColorOption |
| JMultipleLine | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, PolyglineOption, CustomColorOption |
### 混合图系列
| 组件 | 设置面板 |
|------|---------|
| JMixLineBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, PolyglineOption, CustomColorOption |
| DoubleLineBar | BasicOption, TitleOption, XAxisOption, YLeftAxisOption, YRightAxisOption, LegendOption, GridOption, NumOption, BarCylinder, PolyglineOption, CustomColorOption |
### 饼图/环形图系列
| 组件 | 设置面板 |
|------|---------|
| JPie | BasicOption, TitleOption, LegendOption, gridPieOption, pieSettingOption, NumOption, CustomColorOption |
| JRose | BasicOption, TitleOption, LegendOption, gridPieOption, RoseOption, NumOption, CustomColorOption |
| JRotatePie | BasicOption, TitleOption, LegendOption, gridPieOption, CustomColorOption |
| JRing | BasicOption, TitleOption, LegendOption, gridPieOption, RingOption, NumOption, CustomColorOption |
| JBreakRing | BasicOption, BreakRingOption |
| JActiveRing | BasicOption, ActiveRingOption, CustomColorOption |
| JRadialBar | BasicOption, RadialBarOption, CustomColorOption |
### 仪表/进度系列
| 组件 | 设置面板 |
|------|---------|
| JGauge | BasicOption, GaugeOption, CustomColorOption |
| JColorGauge | BasicOption, GaugeOption, CustomColorOption |
| JAntvGauge | BasicOption, AntvGaugeOption, CustomColorOption |
| JSemiGauge | BasicOption, SemiGaugeOption |
| JProgress | BasicOption, ProgressOption, CustomColorOption |
| JCustomProgress | BasicOption, CustomProgressOption |
| JListProgress | BasicOption, ListProgressOption |
| JRoundProgress | BasicOption, RoundProgressOption |
| JRingProgress | BasicOption, ActiveRingPlotOption |
| JLiquid | BasicOption, LiquidPlotOption |
### 散点/气泡/漏斗系列
| 组件 | 设置面板 |
|------|---------|
| JScatter | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption |
| JBubble | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, CustomColorOption |
| JQuadrant | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, CustomColorOption |
| JFunnel | BasicOption, TitleOption, LegendOption, NumOption, CustomColorOption |
| JPyramidFunnel | BasicOption, TitleOption, LegendOption, NumOption, CustomColorOption |
| JPyramid3D | BasicOption, Pyramid3DOption, CustomColorOption |
| JRadar | BasicOption, TitleOption, LegendOption, CustomColorOption |
### 文本/数字系列
| 组件 | 设置面板 |
|------|---------|
| JText | BasicOption, TextOption |
| JCountTo | BasicOption, CountToTextOption |
| JNumber | BasicOption |
| JColorBlock | BasicOption, ColorBlockOption |
| JCurrentTime | BasicOption, CountToTextOption |
### 表格/列表系列
| 组件 | 设置面板 |
|------|---------|
| JScrollBoard | BasicOption, ScrollBoardOpt |
| JScrollTable | BasicOption, ScrollTableStyle |
| JCommonTable | BasicOption, TableStyle |
| JTable | BasicOption, TableStyle |
| JList | BasicOption, ListStyle |
| JScrollList | BasicOption, ScrollListOption |
| JScrollRankingBoard | BasicOption, ScrollOption |
| JFlashList | BasicOption |
| JBubbleRank | BasicOption, BubbleRankingStyle |
| JDevHistory | BasicOption, DevHistoryOption |
### 地图系列
| 组件 | 设置面板 |
|------|---------|
| JAreaMap | BasicOption, MapOption, LineMapColorOption, VisualMapOptoin |
| JBubbleMap | BasicOption, MapOption, LineMapColorOption, ScatterOption, VisualMapOptoin |
| JFlyLineMap | BasicOption, MapOption, LineMapColorOption, FlyLineOption, ScatterOption |
| JBarMap | BasicOption, MapOption, LineMapColorOption, BarMapOption |
| JHeatMap | BasicOption, MapOption, LineMapColorOption, HeatOption |
| JTotalFlyLineMap | BasicOption, MapOption, LineMapColorOption, FlyLineOption, ScatterOption, TimeLineOption |
| JTotalBarMap | BasicOption, MapOption, LineMapColorOption, BarMapOption, TimeLineOption |
### 其他组件
| 组件 | 设置面板 |
|------|---------|
| JWordCloud | BasicOption, WordCloudOption |
| JFlashCloud | BasicOption, FlashCloudOption |
| JRadioButton | BasicOption, CustomColorOption |
| JSelectRadio | BasicOption |
| JPictorialBar | BasicOption, TitleOption, XAxisOption, YAxisOption, PictorialOption |
| JGender | BasicOption |
| JStatsSummary | BasicOption |
| JCarousel | BasicOption, CarouselOption |
| JVideoPlay | BasicOption |
| JIframe | BasicOption |
| JRectangle | BasicOption, RectangleOption |
---
## ECharts 与非 ECharts 组件区分
**ECharts 组件**(底层用 ECharts 渲染option 遵循 ECharts 规范 + 扩展属性):
JBar, JStackBar, JDynamicBar, JHorizontalBar, JBackgroundBar, JMultipleBar, JNegativeBar,
JLine, JSmoothLine, JStepLine, JMultipleLine, JArea,
JMixLineBar, DoubleLineBar,
JPie, JRose, JRotatePie, JRing,
JScatter, JBubble, JQuadrant,
JFunnel, JPyramidFunnel,
JRadar, JCircleRadar,
JGauge, JColorGauge,
JProgress, JPictorialBar,
JBar3d, JBarGroup3d,
JWordCloud,
JAreaMap, JBubbleMap, JFlyLineMap, JBarMap, JHeatMap, JTotalFlyLineMap, JTotalBarMap,
JCustomEchart
**非 ECharts 组件**自定义渲染option 使用组件私有属性):
JNumber, JCountTo, JText, JColorBlock, JCurrentTime,
JLiquid, JAntvGauge, JSemiGauge, JCustomProgress, JListProgress, JRoundProgress, JRingProgress,
JActiveRing, JRadialBar, JBreakRing,
JCapsuleChart, JPercentBar,
JScrollBoard, JScrollTable, JCommonTable, JTable, JList, JScrollList, JScrollRankingBoard, JFlashList, JBubbleRank,
JCarousel, JVideoPlay, JImg, JIframe,
JRadioButton, JSelectRadio, JTabToggle, JForm,
JDragBorder, JDragDecoration, JDragEditor,
JPyramid3D, JGender, JStatsSummary,
JFlashCloud, JImgWordCloud, JOrbitRing, JRectangle, JDevHistory

View File

@@ -0,0 +1,469 @@
# 大屏/仪表盘组件类型完整参考
## 组件分类总览
共 142+ 组件,分为以下大类:
---
## 一、图表组件ECharts
### 柱状图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JBar` | 基础柱状图 | 分类数据对比 |
| `JHorizontalBar` | 横向柱状图 | 类目名较长 |
| `JBackgroundBar` | 背景柱状图 | 带背景色柱状图 |
| `JMultipleBar` | 分组柱状图 | 多系列对比 |
| `JNegativeBar` | 正负柱状图 | 正负值对比 |
| `JStackBar` | 堆叠柱状图 | 部分与整体 |
| `JDynamicBar` | 动态柱状图 | 动画排名效果 |
| `JCapsuleChart` | 胶囊柱状图 | 进度/占比展示 |
| `JPercentBar` | 百分比柱状图 | 占比可视化 |
### 折线图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JLine` | 基础折线图 | 趋势变化 |
| `JSmoothLine` | 平滑曲线图 | 柔和趋势展示 |
| `JStepLine` | 阶梯折线图 | 离散变化 |
| `JMultipleLine` | 多折线图 | 多系列趋势 |
### 混合图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JMixLineBar` | 柱线混合图 | 不同量级对比 |
| `DoubleLineBar` | 双轴图 | 双Y轴混合 |
### 饼图/环形图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JPie` | 饼图 | 占比分析 |
| `JRose` | 玫瑰图 | 带大小的占比 |
| `JRing` | 环形图 | 占比(中心可放数字) |
| `JBreakRing` | 断裂环形图 | 特殊视觉效果 |
| `JRotatePie` | 旋转饼图 | 动态展示 |
| `JActiveRing` | 活动环形图 | 动画环形 |
### 仪表/进度系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JGauge` | 仪表盘 | 完成度/达标率 |
| `JColorGauge` | 彩色仪表盘 | 多色阈值 |
| `JAntvGauge` | AntV仪表盘 | G2Plot风格 |
| `JSemiGauge` | 半圆仪表盘 | 半圆展示 |
| `JProgress` | 进度条 | 完成进度 |
| `JCustomProgress` | 自定义进度条 | 自定义样式 |
| `JListProgress` | 列表进度条 | 多项进度对比 |
| `JRoundProgress` | 圆形进度 | 圆形完成度 |
| `JRingProgress` | 环形进度 | 环形完成度 |
| `JLiquid` | 水球图 | 百分比/液位 |
| `JRadialBar` | 径向柱状图 | 环形对比 |
### 散点/气泡系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JScatter` | 散点图 | 分布/相关性 |
| `JBubble` | 气泡图 | 三维数据 |
| `JQuadrant` | 象限图 | 四象限分析 |
| `JBubbleRank` | 气泡排名 | 排名可视化 |
### 漏斗系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JFunnel` | 漏斗图 | 转化分析 |
| `JPyramidFunnel` | 金字塔漏斗 | 层级占比 |
| `JPyramid3D` | 3D金字塔 | 立体效果 |
### 雷达/其他
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JRadar` | 雷达图 | 多维度对比 |
| `JCircleRadar` | 圆形雷达 | 圆形多维 |
| `JRectangle` | 矩形树图 | 层级占比 |
| `JGraphSimple` | 关系图 | 节点关系 |
| `JWordCloud` | 词云 | 关键词频率 |
| `JImgWordCloud` | 图片词云 | 图形词云 |
| `JCustomEchart` | 自定义ECharts | 自定义配置 |
### 地图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JAreaMap` | 区域地图 | 区域数据着色 |
| `JBubbleMap` | 气泡地图 | 地理数据标注 |
| `JFlyLineMap` | 飞线地图 | 迁徙/物流 |
| `JBarMap` | 柱状地图 | 地理柱状 |
| `JHeatMap` | 热力地图 | 密度分布 |
| `JTotalFlyLineMap` | 多节点飞线 | 多点流动 |
| `JTotalBarMap` | 多柱状地图 | 多地理柱状 |
| `JGaoDeMap` | 高德地图 | 实际地图 |
| `JFly3dMap` | 3D飞线地图 | 3D效果 |
### 3D图表
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JBar3d` | 3D柱状图 | 立体柱状 |
| `JBarGroup3d` | 3D分组柱状图 | 立体分组 |
---
## 二、数据展示组件
### 表格系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JTable` | 数据表格 | 详细数据列表 |
| `JCommonTable` | 通用表格 | 基础表格 |
| `JScrollTable` | 滚动表格 | 自动轮播表格 |
| `JPivotTable` | 透视表 | 交叉分析 |
### 列表/排行系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JList` | 列表 | 通用列表 |
| `JScrollBoard` | 滚动看板 | 信息滚动 |
| `JScrollList` | 滚动列表 | 列表轮播 |
| `JScrollRankingBoard` | 排行榜 | 排名展示 |
| `JFlashList` | 闪烁列表 | 动态列表 |
| `JFlashCloud` | 闪烁云 | 标签云 |
| `JRankingList` | 排名列表 | 静态排名 |
| `JDynamicInfo` | 动态信息 | 实时信息流 |
---
## 三、数字/统计卡片
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JNumber` | 数字指标 | KPI核心指标 |
| `JCountTo` | 数字翻牌 | 动画计数 |
| `JCurrentTime` | 实时时钟 | 当前时间 |
| `JColorBlock` | 色块指标 | 颜色+数字 |
| `JGrowCard` | 增长卡片 | 增长率指标 |
| `JSimpleCard` | 简单卡片 | 信息卡片 |
| `JProjectCard` | 项目卡片 | 项目概览 |
| `JCustomCard` | 自定义卡片 | 灵活卡片 |
| `JStatsSummary` | 统计概要 | 多指标汇总 |
---
## 四、交互/容器组件
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JTabs` | 选项卡 | Tab切换 |
| `JTabToggle` | 切换选项卡 | 简化Tab |
| `JGrid` | 栅格布局 | 布局容器 |
| `JQuickNav` | 快捷导航 | 菜单导航 |
| `JRadioButton` | 单选按钮组 | 筛选切换 |
| `JSelectRadio` | 下拉/单选 | 数据筛选 |
| `JFilterQuery` | 筛选查询 | 复合查询 |
| `JCustomButton` | 自定义按钮 | 操作按钮 |
| `JForm` | 表单 | 查询表单 |
| `JGroup` | 组合 | 组件编组(大屏) |
---
## 五、媒体组件
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JImg` | 图片 | 图片展示 |
| `JCarousel` | 轮播 | 图片/内容轮播 |
| `JVideoPlay` | 视频 | 视频播放 |
| `JVideoJs` | Video.js | 高级视频 |
| `JIframe` | 内嵌页 | 嵌入外部页面 |
| `JWeatherForecast` | 天气 | 天气预报 |
---
## 六、文本/装饰组件
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JText` | 文本 | 标题/说明文字 |
| `JOrbitRing` | 轨道环 | 科技感装饰 |
| `JCustomIcon` | 图标 | 自定义图标 |
| `JDragBorder` | 装饰边框 | 13种边框样式 |
| `JDragDecoration` | 装饰条 | 12种装饰样式 |
| `JDragEditor` | 富文本 | TinyMCE编辑器 |
| `JCalendar` | 日历 | 日历展示 |
| `JPermanentCalendar` | 静态日历 | 固定日历 |
---
## 组件通用配置结构
### 大屏模式bigScreen组件 config
```json
{
"w": 450,
"h": 300,
"dataType": 1,
"url": "",
"timeOut": 0,
"turnConfig": {
"url": "",
"type": "_blank"
},
"linkType": "url",
"linkageConfig": [],
"markLineConfig": {
"show": false,
"markLine": []
},
"dataMapping": [
{"filed": "维度", "mapping": ""},
{"filed": "数值", "mapping": ""}
],
"chartData": [],
"option": {}
}
```
### 数据源类型dataType
| dataType | 说明 |
|----------|------|
| 1 | 静态数据chartData 中直接写入) |
| 2 | API 接口url 字段指定) |
| 3 | SQL 查询(数据集配置) |
| 4 | 关联表单数据 |
### JNumber 组件 config 示例
```json
{
"dataType": 4,
"formId": "form_code",
"formName": "表单名称",
"tableName": "table_name",
"formType": "design",
"valueFields": [{
"fieldName": "record_count",
"fieldTxt": "记录数量",
"fieldType": "count",
"widgetType": "text"
}],
"analysis": {
"showData": 1,
"isRawData": true,
"isCompare": false,
"showMode": 1,
"trendType": "1"
},
"filter": {
"conditionMode": "and",
"conditionFields": [],
"queryField": "create_time",
"queryRange": "month"
},
"size": {"height": 500},
"chart": {
"subclass": "JNumber",
"category": "Number"
},
"option": {
"isCompare": false,
"trendType": "1",
"body": {"color": "#000000", "fontWeight": "bold"},
"card": {"size": "small"}
}
}
```
### JBar/JLine 等轴类图表 config 示例
```json
{
"dataType": 1,
"chartData": [
{"name": "一月", "value": 820},
{"name": "二月", "value": 932},
{"name": "三月", "value": 901}
],
"size": {"width": 860, "height": 380},
"chart": {
"subclass": "JBar",
"category": "Bar"
},
"option": {
"title": {"text": "月度销售", "show": true},
"tooltip": {"show": true},
"legend": {"show": true},
"xAxis": {
"type": "category",
"show": true,
"data": ["一月", "二月", "三月"]
},
"yAxis": {"type": "value", "show": true},
"series": [{
"name": "销售额",
"type": "bar",
"data": [820, 932, 901]
}],
"grid": {"left": "10%", "right": "10%", "top": "15%", "bottom": "15%"}
}
}
```
### JPie 饼图 config 示例
```json
{
"dataType": 1,
"chartData": [
{"name": "直接访问", "value": 335},
{"name": "邮件营销", "value": 310},
{"name": "联盟广告", "value": 234}
],
"size": {"width": 500, "height": 350},
"chart": {
"subclass": "JPie",
"category": "Pie"
},
"option": {
"title": {"text": "访问来源", "show": true},
"tooltip": {"show": true},
"legend": {"show": true, "orient": "vertical", "left": "left"},
"series": [{
"name": "来源",
"type": "pie",
"radius": "55%",
"data": [
{"name": "直接访问", "value": 335},
{"name": "邮件营销", "value": 310},
{"name": "联盟广告", "value": 234}
]
}]
}
}
```
### JTable 表格 config 示例
```json
{
"dataType": 1,
"chartData": [
{"fieldTxt": "姓名", "fieldName": "name", "type": "field", "isShow": "Y", "isTotal": "N"},
{"fieldTxt": "年龄", "fieldName": "age", "type": "field", "isShow": "Y", "isTotal": "Y"},
{"fieldTxt": "地址", "fieldName": "address", "type": "field", "isShow": "Y", "isTotal": "N"}
],
"url": "http://api.jeecg.com/mock/42/tableData",
"tableList": "http://api.jeecg.com/mock/42/tableList",
"size": {"width": 700, "height": 350},
"option": {
"bordered": true,
"size": "small"
}
}
```
### JCustomButton 按钮 config 示例
```json
{
"dataType": 1,
"chartData": [{
"btnId": "74591654852155",
"title": "请假申请",
"color": "#ED4B82",
"icon": "ant-design:calendar-twotone",
"operationType": "1",
"openMode": "2",
"worksheet": {
"label": "请假申请",
"value": "form_code",
"key": "form_code"
},
"click": {
"type": "1",
"message": {"title": "确认执行?", "okText": "确认", "cancelText": "取消"}
}
}],
"option": {
"btnDirection": "column",
"btnStyle": "solid",
"rowNum": 5,
"title": "常用操作",
"btnType": "button",
"btnWidth": "custom"
}
}
```
### JCarousel 轮播 config 示例
```json
{
"dataType": 1,
"chartData": "[{\"src\":\"https://example.com/1.png\"},{\"src\":\"https://example.com/2.png\"}]",
"size": {"width": 800, "height": 300},
"option": {
"dots": true,
"autoplay": true,
"dotPosition": "bottom"
}
}
```
---
## 大屏 vs 仪表盘 布局差异
### 大屏bigScreen
- **布局方式**:绝对定位(像素坐标)
- **坐标单位**x/y 为像素值
- **尺寸单位**w/h 为像素值
- **典型画布**1920×1080 像素
- **支持旋转**angle 属性)
- **背景**:深色 + 背景图
### 仪表盘default
- **布局方式**网格布局vue-grid-layout
- **坐标单位**x 为列号0-23y 为行号
- **尺寸单位**w 为列数1-24h 为行数rowHeight=1px
- **支持旋转**:否
- **背景**:浅色/白色
### template 中的坐标字段
| 字段 | 大屏 | 仪表盘 | 说明 |
|------|------|--------|------|
| `x` | 像素 | 栅格列 | 水平位置 |
| `y` | 像素 | 栅格行 | 垂直位置 |
| `w` | 像素 | 列数(1-24) | 宽度 |
| `h` | 像素 | 行数 | 高度 |
| `pcX` | 像素 | — | PC端水平位置 |
| `pcY` | 像素 | — | PC端垂直位置 |
| `pcW` | 像素 | — | PC端宽度 |
---
## 主题配置
### 大屏主题
```json
{
"theme": "dark",
"style": "bigScreen",
"backgroundColor": "",
"backgroundImage": "/img/bg/bg4.png"
}
```
可用背景图:
- `/img/bg/bg1.png` ~ `/img/bg/bg10.png`
### 仪表盘主题
```json
{
"theme": "default",
"style": "default",
"backgroundColor": "#f3f5f8"
}
```
组件主题颜色:`default`, `gray`, `green`, `red`, `blue`, `dark`

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,414 @@
---
name: jimubi-dashboard
description: "Use when user asks to create/design a dashboard (仪表盘/看板), data kanban, or says \"创建仪表盘\", \"生成仪表盘\", \"做一个仪表盘\", \"数据看板\", \"做一个看板\", \"创建看板\", \"数据面板\", \"统计看板\", \"运营看板\", \"create dashboard\", \"generate dashboard\", \"design dashboard\", \"data kanban\", \"KPI dashboard\". Also triggers when user describes dashboard/kanban requirements like \"做一个运营数据看板\" or mentions grid-layout data display like \"统计系统数据\". Make sure to use this skill for dashboards (仪表盘/看板) — NOT big screens (大屏), which use completely different positioning, styling, and component configurations."
---
# JeecgBoot 仪表盘 AI 自动生成器
将自然语言的仪表盘需求转换为 drag page 配置,并通过 API 自动创建。
> **本 skill 专门处理仪表盘default模式**网格布局24列栅格亮色主题带卡片头适用于日常数据看板。
> 大屏请使用 `jimubi-bigscreen` skill。
## 仪表盘特征
- **布局**24 列栅格,坐标和尺寸单位为**栅格单位**(如 x=0, y=0, w=6, h=17
- **主题**:默认 `default`,白色背景,深色文字
- **无背景图**:仪表盘通常不设背景图
- **卡片头**:仪表盘支持卡片头,但图表组件的 `card.title` 应留空(标题由 ECharts `option.title` 显示),避免标题重复
- **颜色体系**:白底 `#FFFFFF`、深灰标题 `#464646`、浅灰轴标签 `#909198`、浅灰网格 `#F3F3F3`
## 仪表盘栅格布局规则
| 组件类型 | 推荐 w | 推荐 h | 说明 |
|---------|--------|--------|------|
| JNumber | 6 | 17 | 数字卡片4 个一行正好 24 列 |
| JLine/JBar/JSmoothLine | 12-14 | 28-35 | 图表,通常半宽或更宽 |
| JPie/JRing/JRose | 10-12 | 28-35 | 饼图/环形图 |
| JHorizontalBar | 12 | 28-35 | 横向柱状图 |
| JTable/JCommonTable | 12 | 30-40 | 数据表格 |
| JScrollTable | 12 | 30-40 | 滚动表格 |
| JScrollRankingBoard | 12 | 30-35 | 排行榜 |
| JGauge | 6-8 | 25-30 | 仪表盘表盘 |
| JLiquid | 6 | 25-30 | 水球图 |
**布局原则:**
- 总宽度 24 列,组件 w 之和不要超过 24
- 第一行通常放 4 个 JNumberw=6×4=24
- 第二行放图表组合(如 JLine w=14 + JPie w=10 = 24
- 第三行放表格/排行等
## 前置条件
用户必须提供:
1. **API 地址**JeecgBoot 后端地址(如 `https://api3.boot.jeecg.com`
2. **X-Access-Token**JWT 登录令牌(从浏览器 F12 获取)
## 交互流程
### Step 0: 解析用户需求
| 信息 | 默认值 | 示例 |
|------|--------|------|
| 页面名称 | 用户指定 | "运营数据看板" |
| 主题 | default | default |
| 组件列表 | 从描述中解析 | 用户总数(数字)、增长趋势(折线)、来源分布(饼图) |
### Step 1: 识别组件并选择类型
阅读 `references/bi-component-types.md` 获取完整组件类型清单。
**常用仪表盘组件速查:**
| 用户描述关键词 | 组件 component | 说明 |
|---------------|---------------|------|
| 数字/KPI/指标/总数 | `JNumber` | 数字指标卡(带卡片头) |
| 柱状图 | `JBar` | 基础柱状图 |
| 横向柱状图 | `JHorizontalBar` | 水平柱状图 |
| 折线图/趋势 | `JLine` | 折线图 |
| 曲线图 | `JSmoothLine` | 平滑曲线 |
| 柱线混合 | `JMixLineBar` | 柱状+折线混合 |
| 饼图 | `JPie` | 饼图 |
| 环形图 | `JRing` | 环形图 |
| 玫瑰图 | `JRose` | 南丁格尔玫瑰图 |
| 表盘 | `JGauge` | 仪表盘表盘 |
| 水球图 | `JLiquid` | 水球图 |
| 进度条 | `JProgress` | 进度条 |
| 雷达图 | `JRadar` | 雷达图 |
| 漏斗图 | `JFunnel` | 漏斗图 |
| 地图 | `JAreaMap` | 区域地图 |
| 数据表格 | `JTable` / `JCommonTable` | 数据表格 |
| 滚动表格 | `JScrollTable` | 自动滚动表格 |
| 排行榜 | `JScrollRankingBoard` | 滚动排行榜 |
| 日历 | `JCalendar` | 日历组件 |
### Step 2: 展示设计摘要并确认
**必须展示,等待用户确认后再执行:**
```
## 仪表盘设计摘要
- 页面名称:运营数据看板
- 主题default
### 组件列表
| 序号 | 组件名称 | 组件类型 | 位置(x,y) | 尺寸(w×h) | 数据源 |
|------|---------|---------|-----------|----------|--------|
| 1 | 总用户数 | JNumber | (0,0) | 6×17 | 静态数据 |
| 2 | 今日活跃 | JNumber | (6,0) | 6×17 | 静态数据 |
| 3 | 用户增长趋势 | JLine | (0,17) | 14×35 | 静态数据 |
| 4 | 用户来源 | JPie | (14,17) | 10×35 | 静态数据 |
确认以上信息正确?(y/n)
```
### Step 3: 调用 API 创建仪表盘
**优先使用共通工具库 `bi_utils.py`**(两个位置均有副本):
- Skills 目录(权威副本):`C:\Users\zhang\.claude\skills\jimubi-dashboard\references\bi_utils.py`
- 后端项目根目录(运行副本):`{后端项目根目录}\bi_utils.py`
> 如果后端项目根目录没有 `bi_utils.py`,先从 skills 目录复制过去再使用。
**执行步骤:**
```
1. 确认后端项目根目录有 bi_utils.py没有则从 skills 复制)
2. Write 工具 → 写入业务脚本 create_xxx_dashboard.py项目根目录
3. Bash 工具 → cd {后端项目根目录} && python create_xxx_dashboard.py
4. Bash 工具 → rm create_xxx_dashboard.py清理临时脚本
```
**仪表盘创建示例:**
```python
import sys
sys.path.insert(0, r'{后端项目根目录}')
from bi_utils import *
init_api('https://api3.boot.jeecg.com', 'your-token')
# 创建仪表盘style='default',栅格坐标)
page_id = create_page('运营数据看板', style='default', theme='default')
# 第一行4 个数字卡片w=6×4=24h=17
add_number(page_id, '总用户数', x=0, y=0, w=6, h=17, value=15890, suffix='')
add_number(page_id, '今日活跃', x=6, y=0, w=6, h=17, value=3256, suffix='')
add_number(page_id, '今日收入', x=12, y=0, w=6, h=17, value=89600, prefix='¥')
add_number(page_id, '转化率', x=18, y=0, w=6, h=17, value=23.5, suffix='%')
# 第二行:折线图 + 饼图
add_chart(page_id, 'JLine', '用户增长趋势', x=0, y=17, w=14, h=35,
categories=['周一','周二','周三','周四','周五','周六','周日'],
series=[{'name':'新增用户', 'data':[120,200,150,80,70,110,130]}])
add_chart(page_id, 'JPie', '用户来源', x=14, y=17, w=10, h=35,
pie_data=[
{'name':'微信','value':40},
{'name':'APP','value':30},
{'name':'网页','value':20},
{'name':'其他','value':10},
])
save_page(page_id)
print(f'仪表盘创建成功ID: {page_id}')
```
**仪表盘样式特点bi_utils.py 自动应用):**
- 背景:白色 `#FFFFFF`
- 边框:浅灰 `#E8E8E8`
- 标题颜色:深灰 `#464646`
- 轴标签:`#909198`
- 网格线:`#F3F3F3`
- 卡片头:白色背景 + 深灰粗体标题(`headColor: '#FFFFFF'`
- 图例:深灰色文字
## 仪表盘标题规则(重要)
### 图表组件card.title 留空,用 option.title 显示
根据真实模板验证,**图表组件**JBar/JLine/JPie/JRing 等)在仪表盘模式下 `card.title` 应为空字符串,标题通过 ECharts `option.title.text` 显示。如果两者都设置,标题会重复出现(卡片头一次 + 图表内部一次)。
`bi_utils.py``add_chart()` 已自动处理:调用 `_make_card(mode, '')` 传入空标题。
**JNumber 等非图表组件**可以使用 `card.title` 显示标题。
### 大屏 vs 仪表盘标题对比
| 特征 | 大屏bigScreen | 仪表盘default |
|------|-------------------|-------------------|
| 图表标题 | `option.title.text`ECharts 内部) | `option.title.text`ECharts 内部) |
| card.title图表 | 必须为空 `''` | 必须为空 `''`(避免重复) |
| card.titleJNumber等 | 为空 `''` | 可填标题 |
| 页面主标题 | JText 组件fontSize 40+ | 不需要 |
### JText 正确的 config 格式
如果仪表盘中需要使用 JText少见config 结构为:
```python
config = {
'dataType': 1,
'chartData': {'value': '显示文本'}, # dict 格式,不是字符串
'option': {
'body': {
'color': '#464646',
'fontSize': 16,
'fontWeight': 'normal',
'letterSpacing': 0,
'text': '',
'marginTop': 0,
'marginLeft': 0,
},
'textAlign': 'left',
'card': {'title': '', ...},
},
}
```
**手动构建组件(用于高级定制,需直接操作 config**
`add_chart` 等快捷函数无法满足需求时(如需要多系列 chartData、自定义 customColor可直接构建组件 config
```python
import json, time, random
import bi_utils
def _key():
return f'{int(time.time()*1000)}_{random.randint(100000,999999)}'
# 仪表盘亮色主题通用样式
CARD = {
'size': 'default',
'headColor': '#FFFFFF',
'textStyle': {'color': '#464646', 'fontSize': 16, 'fontWeight': 'bold'},
'extra': '', 'rightHref': ''
}
# 直接构建折线图组件
line_data = [
{'name': '1月', 'value': 120, 'type': '新增'},
{'name': '1月', 'value': 80, 'type': '流失'},
# ...
]
comp = {
'component': 'JLine',
'x': 0, 'y': 17, 'w': 14, 'h': 35,
'i': _key(),
'config': json.dumps({
'dataType': 1,
'chartData': json.dumps(line_data, ensure_ascii=False),
'background': '#FFFFFF',
'borderColor': '#E8E8E8',
'size': {'width': 700, 'height': 375},
'option': {
'customColor': [
{'color': '#1890FF', 'color1': '#1890FF'},
{'color': '#52C41A', 'color1': '#52C41A'},
],
'title': {'show': True, 'text': '用户变化趋势',
'textStyle': {'color': '#464646'}},
'tooltip': {'show': True},
'legend': {'show': True, 'textStyle': {'fontSize': 12}},
'xAxis': {
'type': 'category',
'axisLabel': {'color': '#909198'},
'axisLine': {'lineStyle': {'color': '#F3F3F3'}},
},
'yAxis': {
'axisLabel': {'color': '#909198'},
'splitLine': {'lineStyle': {'color': '#F3F3F3'}},
},
'grid': {'top': 70, 'left': 60, 'right': 30, 'bottom': 40},
'card': {**CARD, 'title': '用户变化趋势'},
}
}, ensure_ascii=False)
}
bi_utils._page_components[page_id].append(comp)
```
### Step 4: 输出结果
```
## 仪表盘创建成功
- 页面ID{id}
- 页面名称:{name}
- 模式仪表盘default
- 预览地址:{API_BASE}/drag/page/view/{id}
- 组件数量:{count} 个
请在仪表盘设计器中查看:打开 JeecgBoot 后台 → 仪表盘 → 找到该页面
```
---
## 编辑已有仪表盘
```python
from bi_utils import *
init_api('https://api3.boot.jeecg.com', 'your-token')
page = query_page(page_id)
print(page['name'], page['updateCount'])
add_chart(page_id, 'JBar', '新增图表', x=0, y=52, w=12, h=35,
categories=['A','B','C'], series=[{'name':'','data':[10,20,30]}])
save_page(page_id)
```
---
## 删除仪表盘
```python
from bi_utils import *
init_api('https://api3.boot.jeecg.com', 'your-token')
delete_page(page_id) # 软删除
delete_page(page_id, physical=True) # 硬删除
recover_page(page_id) # 恢复
```
---
## 修改组件样式
阅读 `references/bi-comp-option-config.md` 获取每种组件的完整配置项路径。
**仪表盘样式修改关键规则:**
- 颜色使用色值(`#000000`),不用英文单词
- customColor 格式:`[{color1:'#xxx',color:'#xxx'}]`
- 卡片头样式:`option.card.textStyle.color``option.card.headColor`
- 背景色:`config.background`(仪表盘默认 `#FFFFFF`
- 边框色:`config.borderColor`(仪表盘默认 `#E8E8E8`
```python
import sys, json
sys.path.insert(0, r'{后端项目根目录}')
from bi_utils import *
import bi_utils
init_api('https://api3.boot.jeecg.com', 'your-token')
page_id = 'xxx'
page = query_page(page_id)
tmpl = page.get('template', [])
if isinstance(tmpl, str):
tmpl = json.loads(tmpl)
for comp in tmpl:
config_str = comp.get('config', '{}')
config = json.loads(config_str) if isinstance(config_str, str) else config_str
if comp.get('component') == 'JBar':
option = config.get('option', {})
option['series'][0]['itemStyle'] = {'color': '#1890FF'}
config['option'] = option
comp['config'] = json.dumps(config, ensure_ascii=False)
bi_utils._page_components[page_id] = tmpl
save_page(page_id)
```
---
## 可用的快捷函数
**API 初始化:**
- `init_api(api_base, token)` — 初始化 API 地址和 Token
**页面管理:**
- `create_page(name, style='default', theme='default')` — 创建仪表盘
- `query_page(page_id)` — 查询页面详情
- `list_pages(style='default')` — 列表查询
- `save_page(page_id)` — 保存设计
- `delete_page(page_id, physical)` — 删除
- `recover_page(page_id)` — 恢复
- `copy_page(page_id)` — 复制
**添加组件(栅格坐标):**
- `add_number(page_id, title, x, y, w, h, value, prefix, suffix)` — 数字指标
- `add_chart(page_id, chart_type, title, x, y, w, h, categories, series, pie_data)` — 图表
- `add_table(page_id, title, x, y, w, h, columns, data)` — 数据表格
- `add_scroll_table(page_id, title, x, y, w, h, columns, data)` — 滚动表格
- `add_ranking(page_id, title, x, y, w, h, data)` — 排行榜
- `add_text(page_id, title, x, y, w, h, content, font_size, color)` — 文本
- `add_image(page_id, title, x, y, w, h, src)` — 图片
- `add_gauge(page_id, title, x, y, w, h, value, max_val, unit, color)` — 仪表盘表盘
- `add_liquid(page_id, title, x, y, w, h, value, color)` — 水球图
- `add_component(page_id, component, title, x, y, w, h, config)` — 通用组件
---
## API 踩坑记录
| 问题 | 说明 |
|------|------|
| `POST /drag/page/add` 返回值 | 返回完整实体含 ID |
| `POST /drag/page/edit` 乐观锁 | 必须传 `updateCount` |
| Windows curl 中文问题 | 必须用 Python urllib/requests |
| 坐标单位 | 仪表盘用**栅格**坐标24列 |
| 组件 config 分离 | config 存在 onl_drag_page_comp 表 |
| **size 字段必须是像素** | `config.size.width/height` 是像素值,不是栅格单位。仪表盘栅格转像素:`width = w * 75`, `height = h * 11`。如果直接传栅格值,图表会缩成一小团 |
| **chartData 必须是 JSON 字符串** | `config.chartData` 的值必须是 `json.dumps(...)` 后的字符串,不能是原生 list/dict否则图表不渲染或显示异常 |
| **图表标题去重** | 图表组件JBar/JLine/JPie 等)的 `option.card.title` 应为空字符串,标题仅通过 `option.title.text` 显示;否则卡片头和图表内部会重复显示标题 |
| **多系列 chartData 格式** | 多系列图表的 chartData 需要 `type` 字段区分系列:`[{"name":"1月","value":10,"type":"系列A"}, {"name":"1月","value":20,"type":"系列B"}]` |
| **HTTPS 连接问题** | api3.boot.jeecg.com 使用 HTTP 协议(非 HTTPS`init_api` 时用 `http://` 前缀 |
## 错误处理
| 错误 | 解决方案 |
|------|---------|
| Token 过期401 | 重新获取 X-Access-Token |
| `updateCount` 不匹配 | 重新查询页面获取最新值 |
| 组件不显示 | 检查 dataType、chartData必须是 JSON 字符串、option 是否完整 |
| 图表缩成小点 | 检查 `config.size` 是否用了像素值(不是栅格单位),仪表盘需 `w*75` / `h*11` |
| 标题重复显示 | 图表组件的 `option.card.title` 设为空,仅用 `option.title.text` |
| 布局错乱 | 确认使用栅格坐标不是像素w 总和 ≤ 24 |
| 中文乱码 | 使用 Python不要用 curl |
## 参考文档
- `references/bi-component-types.md` — 完整组件类型清单
- `references/bi-comp-option-config.md` — 组件样式配置路径
- `references/bi_utils.py` — 工具库源码
- `references/templates/default/` — 41 个仪表盘模板 JSON 参考

View File

@@ -0,0 +1,891 @@
# 大屏组件配置修改参考
修改大屏组件样式时,根据组件类型和修改目标,使用对应的配置路径。
## 修改输出格式
只返回需要修改的属性,不包含未修改的配置:
```json
{
"compConfig": {
"option": {
"series": [{ "itemStyle": { "color": "#FFFF00" } }]
}
}
}
```
修改名称/背景等基础属性:
```json
{
"compConfig": {
"name": "京东销量柱形图",
"background": "#000000"
}
}
```
## 颜色修改规则
### customColor 组件列表
以下组件的颜色属性使用 `customColor` 格式修改:
- JRadioButton, JRadialBar, JActiveRing, JRing, JPyramidFunnel, JFunnel
- JBubble, DoubleLineBar, JMultipleLine, JArea, JLine
- JRotatePie, JRose, JPie, JMixLineBar, JPercentBar
- JMultipleBar, JCapsuleChart, JStackBar, JQuadrant
格式:
```json
"customColor": [
{"color1": "#FF0000", "color": "#FF0000"},
{"color1": "#00FF00", "color": "#00FF00"}
]
```
### 柱体颜色
普通柱状图使用 `option.series[${index}].itemStyle.color`
JDynamicBar 等也使用 `option.series[${index}].itemStyle.color`
### 其他组件
不包含 customColor 属性的组件,按照对应组件配置的属性 value 值修改
## 通用规则
- 颜色使用具体色值(如 `#000000`),不使用英文单词(如 black
- 字体粗细可选值:`normal`(默认)、`bold`(粗体)、`lighter`(细体)
- Y轴单位 `option.yAxis.yUnit`:预设值有 `%`(百分比)、`K`(千)、`W`(万)、`M`(亿);自定义单位时设 `yUnit: 'CUSTOM'` 并设 `yCustomUnit: '元'`
## 基础配置 (BasicOption)
| 说明 | 配置路径 |
|------|---------|
| 图层名称 | `name` |
| 图层背景色 | `background` |
| 图层边框线 | `borderColor` |
| 提示语显隐 | `option.tooltip.show` |
| 提示语字体大小 | `option.tooltip.textStyle.fontSize` |
| 提示语字体颜色 | `option.tooltip.textStyle.color` |
## 标题设置 (TitleOption)
| 说明 | 配置路径 |
|------|---------|
| 标题名称 | `option.title.text` |
| 标题字体大小 | `option.title.textStyle.fontSize` |
| 标题字体颜色 | `option.title.textStyle.fontColor` |
| 标题字体粗细 | `option.title.textStyle.fontWeight` |
| 副标题名称 | `option.title.subtextStyle` |
| 副标题字体大小 | `option.title.subtextStyle.fontSize` |
| 副标题字体颜色 | `option.title.subtextStyle.fontColor` |
| 左对齐 | `option.title.left` |
| 垂直居中 | `option.title.top` |
## X轴设置 (XAxisOption)
| 说明 | 配置路径 |
|------|---------|
| X轴名称 | `option.xAxis.name` |
| X轴名称颜色 | `option.xAxis.nameTextStyle.color` |
| X轴名称字体大小 | `option.xAxis.nameTextStyle.fontSize` |
| X轴标签颜色 | `option.xAxis.axisLabel.color` |
| X轴标签角度 | `option.xAxis.axisLabel.rotate` |
| X轴轴线颜色 | `option.xAxis.axisLine.lineStyle.color` |
| X轴类型 | `option.xAxis.type` |
| X轴网格线显隐 | `option.xAxis.splitLine.show` |
| X轴网格线颜色 | `option.xAxis.splitLine.lineStyle.color` |
## Y轴设置 (YAxisOption)
| 说明 | 配置路径 | 备注 |
|------|---------|------|
| Y轴名称 | `option.yAxis.name` | |
| Y轴名称颜色 | `option.yAxis.nameTextStyle.color` | |
| Y轴名称字体大小 | `option.yAxis.nameTextStyle.fontSize` | |
| Y轴标签颜色 | `option.yAxis.axisLabel.color` | |
| Y轴标签角度 | `option.yAxis.axisLabel.rotate` | |
| Y轴轴线颜色 | `option.yAxis.axisLine.lineStyle.color` | |
| Y轴类型 | `option.yAxis.type` | |
| Y轴网格线显隐 | `option.yAxis.splitLine.show` | |
| Y轴网格线颜色 | `option.yAxis.splitLine.lineStyle.color` | |
| Y轴单位 | `option.yAxis.yUnit` | 预设: `%`, `K`, `W`, `M`;自定义: 设为 `CUSTOM` 并设 `yCustomUnit` |
## 图例设置 (LegendOption)
| 说明 | 配置路径 |
|------|---------|
| 图例字体大小 | `option.legend.textStyle.fontSize` |
| 图例排列方向 | `option.legend.orient` |
| 图例上下边距 | `option.legend.t` |
| 图例左右边距 | `option.legend.r` |
## 柱体设置 (BarCylinder)
| 说明 | 配置路径 |
|------|---------|
| 柱体宽度 | `option.series[${index}].barWidth` |
| 柱体圆角 | `option.series[${index}].itemStyle.borderRadius` |
| 柱体颜色 | `option.series[${index}].itemStyle.color` |
| 柱体背景色显隐 | `option.series[${index}].showBackground` |
| 柱体背景色颜色 | `option.series[${index}].backgroundStyle.color` |
## 折线设置 (PolyglineOption)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| 折线类型 | `option.series[${index}].lineType` | `line`(折线), `smooth`(曲线), `area`(面积) |
| 透明度 | `option.series[0].areaStyleOpacity` | |
| 线条宽度 | `option.series[${index}].lineWidth` | |
| 标记点 | `option.series[${index}].symbol` | |
| 点的大小 | `option.series[${index}].symbolSize` | |
## 饼图设置 (pieSettingOption)
| 说明 | 配置路径 |
|------|---------|
| 设置成环形 | `option.isRadius` |
| 内环半径 | `option.innerRadius` |
| 外环半径 | `option.outRadius` |
| 南丁格尔玫瑰 | `option.isRose` |
| 标签显示位置 | `option.pieLabelPosition` |
## 坐标轴边距 (GridOption)
| 说明 | 配置路径 |
|------|---------|
| 左边距 | `option.grid.left` |
| 顶边距 | `option.grid.top` |
| 右边距 | `option.grid.right` |
| 底边距 | `option.grid.bottom` |
## 数值设置 (NumOption)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| 显示数值 | `option.series[${index}].label.show` | |
| 数值位置 | `option.series[${index}].label.position` | `top`(顶部), `""`(中间), `insideBottom`(底部) |
| 数值格式 | `option.label.format` | |
| 数值颜色 | `option.series[${index}].label.color` | |
| 数值字体大小 | `option.series[${index}].label.fontSize` | |
| 数值字体粗细 | `option.series[${index}].label.fontWeight` | |
| 数值单位显隐 | `option.showUnit.show` | |
| 数值单位数量级 | `option.showUnit.numberLevel` | `1`(百分比), `3`(千), `4`(万) |
| 数值单位小数位 | `option.showUnit.decimal` | |
## 文本设置 (TextOption) - JText 组件
| 说明 | 配置路径 |
|------|---------|
| 字体大小 | `option.body.fontSize` |
| 字体间距 | `option.body.letterSpacing` |
| 字体颜色 | `option.body.color` |
| 千分符 | `option.body.thousandSeparator` |
| 水平间距 | `option.body.marginLeft` |
| 垂直间距 | `option.body.marginTop` |
| 跑马灯 | `option.horseLamp` |
| 超链接开关 | `option.isLink` |
| 超链接地址 | `option.openUrl` |
## 翻牌器设置 (CountToTextOption) - JCountTo 组件
| 说明 | 配置路径 |
|------|---------|
| 字体粗细 | `option.fontWeight` |
| 字体颜色 | `option.fontColor` |
| 字体大小 | `option.fontSize` |
| 前缀文本 | `option.prefix` |
| 前缀字体大小 | `option.prefixFontSize` |
| 前缀字体颜色 | `option.prefixColor` |
| 前缀字体粗细 | `option.prefixFontWeight` |
| 前缀对齐方式 | `option.prefixTextAlign` |
| 前缀X间距 | `option.prefixGridX` |
| 前缀Y间距 | `option.prefixGridY` |
| 后缀文本 | `option.suffix` |
| 后缀字体大小 | `option.suffixFontSize` |
| 后缀字体颜色 | `option.suffixColor` |
| 后缀字体粗细 | `option.suffixFontWeight` |
| 后缀对齐方式 | `option.suffixTextAlign` |
| 后缀X间距 | `option.suffixGridX` |
| 后缀Y间距 | `option.suffixGridY` |
## 进度条设置 (CustomProgressOption)
| 说明 | 配置路径 |
|------|---------|
| 目标颜色 | `option.backgroundColor` |
| 进度颜色 | `option.progressColor` |
| 进度条宽度 | `option.barWidth` |
| 边距 | `option.padding` |
| 标题颜色 | `option.titleColor` |
| 标题字体大小 | `option.titleFontSize` |
| 标题位置 | `option.titlePosition` |
| 数值颜色 | `option.valueColor` |
| 数值字体大小 | `option.valueFontSize` |
| 数值位置 | `option.valuePosition` |
| 数值横向偏移 | `option.valueXOffset` |
## 列表进度图设置 (ListProgressOption)
| 说明 | 配置路径 |
|------|---------|
| 行高度 | `option.row.height` |
| 行左边距 | `option.row.marginLeft` |
| 行右边距 | `option.row.marginRight` |
| 行上边距 | `option.row.marginTop` |
| 进度条颜色 | `option.bar.background.color` |
| 进度条填充色 | `option.bar.fill.color` |
| 进度条高度 | `option.bar.height` |
| 进度条圆角 | `option.bar.borderRadius` |
| 指示点大小 | `option.bar.indicatorSize` |
| 指示点颜色 | `option.bar.indicatorColor` |
| 显示边框 | `option.bar.border.enabled` |
| 边框颜色 | `option.bar.border.color` |
| 边框大小 | `option.bar.border.width` |
| 边框边距 | `option.bar.border.padding` |
## 水波图设置 (LiquidPlotOption) - JLiquid 组件
| 说明 | 配置路径 |
|------|---------|
| 显示类型 | `option.liquidType` |
| 波纹颜色 | `option.color` |
| 波纹个数 | `option.count` |
| 波纹长度 | `option.length` |
| 外框颜色 | `option.borderColor` |
| 外框宽度 | `option.borderWidth` |
| 间距 | `option.distance` |
| 透明度 | `option.strokeOpacity` |
| 文本颜色 | `option.textColor` |
| 文本字体大小 | `option.textFontSize` |
## 象形图设置 (PictorialOption)
| 说明 | 配置路径 |
|------|---------|
| 柱体颜色 | `option.barColor` |
| 透明度 | `option.barOpacity` |
| 间距 | `option.count` |
## 仪表盘设置 (GaugeOption)
| 说明 | 配置路径 |
|------|---------|
| 刻度值显隐 | `option.series[0].axisLabel.show` |
| 刻度值颜色 | `option.series[0].axisLabel.color` |
| 刻度值字体大小 | `option.series[0].axisLabel.fontSize` |
| 刻度线显隐 | `option.series[0].axisTick.show` |
| 刻度线长度 | `option.series[0].axisTick.length` |
| 刻度线颜色 | `option.series[0].axisTick.lineStyle.color` |
| 分割线显隐 | `option.series[0].splitLine.show` |
| 分割线长度 | `option.series[0].splitLine.length` |
| 分割线颜色 | `option.series[0].splitLine.lineStyle.color` |
| 指标字号 | `option.series[0].detail.fontSize` |
## 渐变仪表盘设置 (AntvGaugeOption)
| 说明 | 配置路径 |
|------|---------|
| 粗细 | `option.gaugeWidth` |
| 刻度值显隐 | `option.axisLabelShow` |
| 刻度值颜色 | `option.axisLabelColor` |
| 刻度值字体大小 | `option.axisLabelFontSize` |
| 刻度线显隐 | `option.axisTickShow` |
| 刻度线颜色 | `option.lineColor` |
| 文本颜色 | `option.valueColor` |
| 文本字体大小 | `option.valueFontSize` |
| 指针颜色 | `option.indicatorColor` |
| 指针粗细 | `option.indicatorLength` |
## 环形图设置 (ActiveRingPlotOption)
| 说明 | 配置路径 |
|------|---------|
| 颜色 | `option.color` |
| 背景色 | `option.bgColor` |
| 外环半径 | `option.outRadius` |
| 内环半径 | `option.innerRadius` |
| 标题字体大小 | `option.fontSize` |
| 标题字体颜色 | `option.fontColor` |
| 标题字体粗细 | `option.fontWeight` |
| 数值字体大小 | `option.valueFontSize` |
| 数值字体颜色 | `option.valueFontColor` |
| 数值字体粗细 | `option.valueFontWeight` |
## 动态环形图设置 (ActiveRingOption)
| 说明 | 配置路径 |
|------|---------|
| 显示原始值 | `option.showOriginValue` |
| 文字颜色 | `option.textColor` |
| 文字大小 | `option.textFontSize` |
| 线条宽度 | `option.lineWidth` |
| 环半径 | `option.radius` |
| 动态环半径 | `option.activeRadius` |
## 玉珏设置 (RadialBarOption)
| 说明 | 配置路径 |
|------|---------|
| 显示圆角 | `option.radiuShow` |
| 背景显示 | `option.bgShow` |
| 外环半径 | `option.radius` |
| 内环半径 | `option.innerRadius` |
| 最大旋转角 | `option.maxAngle` |
## 矩形图设置 (RectangleOption)
| 说明 | 配置路径 |
|------|---------|
| 文本颜色 | `option.titleColor` |
| 文本字体大小 | `option.titleFontSize` |
| 显示图例 | `option.showLegend` |
## 颜色块设置 (ColorBlockOption)
| 说明 | 配置路径 |
|------|---------|
| 行数 | `option.lineNum` |
| 边距 | `option.padding` |
| X间距 | `option.borderSplitx` |
| Y间距 | `option.borderSplity` |
| 小数位数 | `option.decimals` |
| 字体大小 | `option.fontSize` |
| 字体颜色 | `option.color` |
| 字体粗细 | `option.fontWeight` |
| 对齐方式 | `option.textAlign` |
| 前缀字体颜色 | `option.prefixColor` |
| 前缀字体粗细 | `option.prefixFontWeight` |
| 前缀X间距 | `option.prefixSplitx` |
| 前缀Y间距 | `option.prefixSplity` |
| 后缀字体大小 | `option.suffixFontSize` |
| 后缀字体颜色 | `option.suffixColor` |
| 后缀字体粗细 | `option.suffixFontWeight` |
| 后缀X间距 | `option.suffixSplitx` |
## 字符云设置 (WordCloudOption)
| 说明 | 配置路径 |
|------|---------|
| 字体颜色 | `option.color` |
| 字体间距 | `option.padding` |
| 字体旋转 | `option.rotation` |
| 字体最大值 | `option.minSize` |
| 字体最小值 | `option.maxSize` |
| 形状 | `option.series[0].shape` |
## 闪光云设置 (FlashCloudOption)
| 说明 | 配置路径 |
|------|---------|
| 缩放 | `option.zoom` |
| 字体大小 | `option.textSize` |
| 字体颜色 | `option.textColor` |
## 轮播表格设置 (ScrollBoardOpt)
| 说明 | 配置路径 |
|------|---------|
| 悬浮暂停 | `option.hoverPause` |
| 等待时间 | `option.waitTime` |
| 开启排名 | `option.index` |
| 列宽 | `option.indexWidth` |
| 显示表头 | `option.headShow` |
| 表头颜色 | `option.headerBGC` |
| 表头行高 | `option.headerHeight` |
| 每页行数 | `option.rowNum` |
| 奇行颜色 | `option.oddRowBGC` |
| 偶行颜色 | `option.evenRowBGC` |
## 表格设置 (ScrollTableStyle)
| 说明 | 配置路径 |
|------|---------|
| 开启排名 | `option.ranking` |
| 开启滚动 | `option.scroll` |
| 滚动时间 | `option.scrollTime` |
| 显示表头 | `option.showHead` |
| 表头背景颜色 | `option.headerBgColor` |
| 表头字体颜色 | `option.headerFontColor` |
| 表头字体大小 | `option.fontSize` |
| 行高 | `option.lineHeight` |
| 边框显示 | `option.showBorder` |
| 边框宽度 | `option.borderWidth` |
| 边框颜色 | `option.borderColor` |
| 边框线类型 | `option.borderStyle` |
| 表格字体颜色 | `option.bodyFontColor` |
| 表格字体大小 | `option.bodyFontSize` |
| 奇行颜色 | `option.oddColor` |
| 偶行颜色 | `option.evenColor` |
## 数据表格设置 (TableStyle)
| 说明 | 配置路径 |
|------|---------|
| 表头背景颜色 | `option.headerBgColor` |
| 表头字体大小 | `option.headerFontSize` |
| 表头字体颜色 | `option.headerColor` |
| 内容字体颜色 | `option.bodyColor` |
| 内容字体大小 | `option.bodyFontSize` |
| 内容背景颜色 | `option.bodyBgColor` |
## 列表设置 (ListStyle)
| 说明 | 配置路径 |
|------|---------|
| 显示标题前缀 | `option.showTitlePrefix` |
| 显示时间前缀 | `option.showTimePrefix` |
| 布局 | `option.layout` |
| 标题字体颜色 | `option.titleFontColor` |
| 标题字体粗细 | `option.titleFontWeight` |
| 标题字体大小 | `option.titleFontSize` |
| 图标颜色 | `option.iconColor` |
| 内容颜色 | `option.contentColor` |
| 开启动画 | `option.isEnableAnimation` |
| 轮播时间(ms) | `option.scrollTime` |
## 滚动设置 (ScrollOption)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| 是否排序 | `option.sort` | |
| 轮播方式 | `option.carousel` | `single`(单行), `page`(整页) |
| 显示行数 | `option.rowNum` | |
| 滚动时间(ms) | `option.waitTime` | |
## 历程设置 (DevHistoryOption)
| 说明 | 配置路径 |
|------|---------|
| 缩放 | `option.zoom` |
| 轮播间隔 | `option.waitTime` |
| 背景色 | `option.typeBackColor` |
| 字体颜色 | `option.typeFontColor` |
| 内容字体颜色 | `option.titleColor` |
| 内容字体大小 | `option.titleFontSize` |
## 气泡排名设置 (BubbleRankingStyle)
| 说明 | 配置路径 |
|------|---------|
| 比例 | `option.zoom` |
| 显示提示词 | `option.showTip` |
| 提示词颜色 | `option.titleColor` |
| 提示词宽度 | `option.tipWidth` |
| 提示词内容颜色 | `option.tipFontColor` |
| 提示词内容字体大小 | `option.tipFontSize` |
## 3D金字塔/漏斗设置 (Pyramid3DOption)
| 说明 | 配置路径 |
|------|---------|
| 缩放 | `option.zoom` |
| 尺寸 | `option.size` |
## 环形设置 (RingOption)
| 说明 | 配置路径 |
|------|---------|
| 内半径 | `option.innerRadius` |
| 外半径 | `option.outRadius` |
## 南丁格尔玫瑰设置 (RoseOption)
| 说明 | 配置路径 |
|------|---------|
| 边框宽度 | `option.series[0].itemStyle.borderWidth` |
| 颜色透明度 | `option.series[0].itemStyle.colorOpacity` |
## 胶囊图设置 (CapsuleChartOption)
| 说明 | 配置路径 |
|------|---------|
| 显示数值 | `option.showValue` |
| X轴名称 | `option.unit` |
## 百分比柱状图样式 (PercentBarStyle)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| Y轴刻度颜色 | `option.yNameFontColor` | |
| Y轴刻度字体大小 | `option.yNameFontSize` | |
| X轴刻度颜色 | `option.xNameFontColor` | |
| X轴刻度字体大小 | `option.xNameFontSize` | |
| 图例位置 | `option.legendPosition` | `top`(居上), `bottom`(居下) |
| 图例字体颜色 | `option.legendFontColor` | |
| 图例字体大小 | `option.legendFontSize` | |
## 进度条 ECharts 设置 (ProgressOption)
| 说明 | 配置路径 |
|------|---------|
| 显示标题 | `option.yAxis.axisLabel.show` |
| 标题字体颜色 | `option.yAxis.axisLabel.color` |
| 标题字体大小 | `option.yAxis.axisLabel.fontSize` |
| 数值字体颜色 | `option.series[1].label.color` |
| 数值字体大小 | `option.series[1].label.fontSize` |
| 横向偏移 | `option.valueXOffset` |
| 纵向偏移 | `option.valueYOffset` |
| 柱体宽度 | `option.series[0].barWidth` |
| 进度颜色 | `option.series[0].color` |
| 目标颜色 | `option.series[1].color` |
## 地图设置 (MapOption)
| 说明 | 配置路径 |
|------|---------|
| 显示区域名称 | `option.geo.label.normal.show` |
| 区域名称颜色 | `option.geo.label.normal.color` |
| 区域名称字体大小 | `option.geo.label.normal.fontSize` |
| 开启钻取 | `commonOption.breadcrumb.drillDown` |
| 鼠标缩放 | `option.geo.roam` |
| 缩放比例 | `option.geo.zoom` |
| 长宽比 | `option.geo.aspectScale` |
| 顶边距 | `option.geo.top` |
| 左边距 | `option.geo.left` |
## 地图配色设置 (LineMapColorOption)
| 说明 | 配置路径 |
|------|---------|
| 启用渐变色 | `commonOption.gradientColor` |
| 中心颜色 | `commonOption.areaColor.color1` |
| 边缘颜色 | `commonOption.areaColor.color2` |
| 区域颜色 | `commonOption.areaColor.color1` |
| 区域高亮颜色 | `option.geo.itemStyle.emphasis.areaColor` |
| 区域边界颜色 | `option.geo.itemStyle.normal.borderColor` |
| 阴影大小 | `option.geo.itemStyle.normal.shadowBlur` |
| 阴影水平偏移 | `option.geo.itemStyle.normal.shadowOffsetX` |
| 阴影垂直偏移 | `option.geo.itemStyle.normal.shadowOffsetY` |
| 阴影颜色 | `option.geo.itemStyle.normal.shadowColor` |
## 视觉映射设置 (VisualMapOption)
| 说明 | 配置路径 | 可选值 |
|------|---------|--------|
| 开启视觉映射 | `option.visualMap.show` | |
| 类型 | `option.visualMap.type` | `continuous`, `piecewise` |
| 文本颜色 | `option.visualMap.textStyle.color` | |
| 文本粗细 | `option.visualMap.textStyle.fontWeight` | |
| 文本字体大小 | `option.visualMap.textStyle.fontSize` | |
| 最小值 | `option.visualMap.min` | |
| 最大值 | `option.visualMap.max` | |
## 地图散点设置 (ScatterOption)
| 说明 | 配置路径 |
|------|---------|
| 散点大小 | `option.area.markerSize` |
| 散点形状 | `option.area.markerShape` |
| 散点类型 | `option.area.markerType` |
| 散点颜色 | `option.area.markerColor` |
| 散点文本显示 | `option.area.scatterLabelShow` |
| 散点文本颜色 | `option.area.scatterLabelColor` |
| 散点文本位置 | `option.area.scatterLabelPosition` |
| 散点文本字体大小 | `option.area.scatterFontSize` |
| 散点数量 | `option.area.markerCount` |
| 散点透明度 | `option.area.markerOpacity` |
## 热力地图设置 (HeatOption)
| 说明 | 配置路径 |
|------|---------|
| 热力点大小 | `commonOption.heat.pointSize` |
| 模糊大小 | `commonOption.heat.blurSize` |
| 最大透明度 | `commonOption.heat.maxOpacity` |
## 柱体地图设置 (BarMapOption)
| 说明 | 配置路径 |
|------|---------|
| 柱体大小 | `commonOption.barSize` |
| 柱体左侧颜色 | `commonOption.barColor` |
| 柱体右侧颜色 | `commonOption.barColor2` |
## 飞线地图设置 (FlyLineOption)
| 说明 | 配置路径 |
|------|---------|
| 动画时间 | `commonOption.effect.period` |
| 标记形状 | `commonOption.effect.markerShape` |
| 标记大小 | `commonOption.effect.symbolSize` |
| 标记颜色 | `commonOption.effect.markerColor` |
| 尾迹长度 | `commonOption.effect.trailLength` |
---
## 组件数据格式 (chartData)
### 柱状图/折线图/混合图
JBar, JStackBar, JLine, JSmoothLine, JStepLine, JMultipleLine, JArea, JMixLineBar, DoubleLineBar, JHorizontalBar, JBackgroundBar, JMultipleBar, JNegativeBar, JPercentBar
```json
[{"name": "一月", "value": 820, "type": "系列名"}]
```
双轴图额外字段:`"yAxisIndex": "0"``"1"`
### 饼图/环形图/玫瑰图
JPie, JRose, JRing, JRotatePie, JBreakRing, JActiveRing, JRadialBar, JFunnel, JPyramidFunnel
```json
[{"name": "类别", "value": 800}]
```
### 仪表盘
JGauge, JColorGauge, JAntvGauge
```json
[{"min": 0, "max": 100, "label": "完成率", "value": 76}]
```
### 半圆仪表盘
JSemiGauge
```json
[{"total": 800, "used": 500}]
```
### 水球图
JLiquid值为 0-100前端自动除以100
```json
[{"value": 75}]
```
### 数字指标
JNumber对象格式不是数组
```json
{"value": 128560}
```
### 翻牌器
JCountTo
```json
{"value": 1024}
```
### 文本
JText
```json
{"value": "显示的文字内容"}
```
### 排行榜
JScrollRankingBoard直接数组不要 JSON.stringify
```json
[{"name": "北京", "value": 1200}, {"name": "上海", "value": 1050}]
```
### 滚动表格
JScrollTable数组 + option.fieldMapping
```json
[{"col1": "值1", "col2": "值2"}]
```
option 需配合 `fieldMapping: [{"name": "列名", "key": "col1", "width": "30%"}]`
### 数据表格
JTable, JCommonTable
```json
[
{"fieldTxt": "姓名", "fieldName": "name", "type": "field", "isShow": "Y"},
{"fieldTxt": "年龄", "fieldName": "age", "type": "field", "isShow": "Y"}
]
```
### 数据列表
JList
```json
[{"title": "标题", "date": "2026-03-18", "desc": "描述", "avatar": "url"}]
```
### 词云
JWordCloud, JImgWordCloud, JFlashCloud
```json
[{"name": "关键词", "value": 100}]
```
### 地图组件
JAreaMap, JBubbleMap, JFlyLineMap, JBarMap, JHeatMap
```json
[{"name": "城市名", "value": 199}]
```
### 按钮
JRadioButton, JCustomButton
```json
[{"title": "按钮文字", "value": 0, "href": "https://example.com"}]
```
### 轮播图
JCarousel
```json
[{"src": "https://example.com/1.png"}, {"src": "https://example.com/2.png"}]
```
### 进度条
JProgress
```json
[{"name": "任务A", "value": 80, "total": 100}]
```
### 胶囊图
JCapsuleChart
```json
[{"name": "类目", "value": 500}]
```
### 性别比例
JGender
```json
[{"man": 60, "woman": 40}]
```
### 统计卡片
JStatsSummary
```json
[{"title": "指标名", "value": 1234, "unit": "元", "compare": 12.5, "label": "同比", "state": "up"}]
```
---
## 组件与设置面板映射表
每个组件在设计器右侧面板显示的配置项列表optionList以下为完整映射
### 柱状图系列
| 组件 | 设置面板 |
|------|---------|
| JBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, CustomColorOption, OtherOption |
| JStackBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, CustomColorOption |
| JDynamicBar | BasicOption, TitleOption, XAxisOption, YAxisOption, GridOption, BarCylinder |
| JHorizontalBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, CustomColorOption |
| JBackgroundBar | BasicOption, TitleOption, XAxisOption, YAxisOption, GridOption, NumOption, BarCylinder |
| JMultipleBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, CustomColorOption |
| JNegativeBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, CustomColorOption |
| JPercentBar | BasicOption, PercentBarStyle, CustomColorOption |
| JCapsuleChart | BasicOption, CapsuleChartOption, CustomColorOption |
### 折线/面积图系列
| 组件 | 设置面板 |
|------|---------|
| JLine | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, PolyglineOption, CustomColorOption, OtherOption |
| JSmoothLine | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, PolyglineOption |
| JStepLine | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption |
| JArea | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, PolyglineOption, CustomColorOption |
| JMultipleLine | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, PolyglineOption, CustomColorOption |
### 混合图系列
| 组件 | 设置面板 |
|------|---------|
| JMixLineBar | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, NumOption, BarCylinder, PolyglineOption, CustomColorOption |
| DoubleLineBar | BasicOption, TitleOption, XAxisOption, YLeftAxisOption, YRightAxisOption, LegendOption, GridOption, NumOption, BarCylinder, PolyglineOption, CustomColorOption |
### 饼图/环形图系列
| 组件 | 设置面板 |
|------|---------|
| JPie | BasicOption, TitleOption, LegendOption, gridPieOption, pieSettingOption, NumOption, CustomColorOption |
| JRose | BasicOption, TitleOption, LegendOption, gridPieOption, RoseOption, NumOption, CustomColorOption |
| JRotatePie | BasicOption, TitleOption, LegendOption, gridPieOption, CustomColorOption |
| JRing | BasicOption, TitleOption, LegendOption, gridPieOption, RingOption, NumOption, CustomColorOption |
| JBreakRing | BasicOption, BreakRingOption |
| JActiveRing | BasicOption, ActiveRingOption, CustomColorOption |
| JRadialBar | BasicOption, RadialBarOption, CustomColorOption |
### 仪表/进度系列
| 组件 | 设置面板 |
|------|---------|
| JGauge | BasicOption, GaugeOption, CustomColorOption |
| JColorGauge | BasicOption, GaugeOption, CustomColorOption |
| JAntvGauge | BasicOption, AntvGaugeOption, CustomColorOption |
| JSemiGauge | BasicOption, SemiGaugeOption |
| JProgress | BasicOption, ProgressOption, CustomColorOption |
| JCustomProgress | BasicOption, CustomProgressOption |
| JListProgress | BasicOption, ListProgressOption |
| JRoundProgress | BasicOption, RoundProgressOption |
| JRingProgress | BasicOption, ActiveRingPlotOption |
| JLiquid | BasicOption, LiquidPlotOption |
### 散点/气泡/漏斗系列
| 组件 | 设置面板 |
|------|---------|
| JScatter | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption |
| JBubble | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, CustomColorOption |
| JQuadrant | BasicOption, TitleOption, XAxisOption, YAxisOption, LegendOption, GridOption, CustomColorOption |
| JFunnel | BasicOption, TitleOption, LegendOption, NumOption, CustomColorOption |
| JPyramidFunnel | BasicOption, TitleOption, LegendOption, NumOption, CustomColorOption |
| JPyramid3D | BasicOption, Pyramid3DOption, CustomColorOption |
| JRadar | BasicOption, TitleOption, LegendOption, CustomColorOption |
### 文本/数字系列
| 组件 | 设置面板 |
|------|---------|
| JText | BasicOption, TextOption |
| JCountTo | BasicOption, CountToTextOption |
| JNumber | BasicOption |
| JColorBlock | BasicOption, ColorBlockOption |
| JCurrentTime | BasicOption, CountToTextOption |
### 表格/列表系列
| 组件 | 设置面板 |
|------|---------|
| JScrollBoard | BasicOption, ScrollBoardOpt |
| JScrollTable | BasicOption, ScrollTableStyle |
| JCommonTable | BasicOption, TableStyle |
| JTable | BasicOption, TableStyle |
| JList | BasicOption, ListStyle |
| JScrollList | BasicOption, ScrollListOption |
| JScrollRankingBoard | BasicOption, ScrollOption |
| JFlashList | BasicOption |
| JBubbleRank | BasicOption, BubbleRankingStyle |
| JDevHistory | BasicOption, DevHistoryOption |
### 地图系列
| 组件 | 设置面板 |
|------|---------|
| JAreaMap | BasicOption, MapOption, LineMapColorOption, VisualMapOptoin |
| JBubbleMap | BasicOption, MapOption, LineMapColorOption, ScatterOption, VisualMapOptoin |
| JFlyLineMap | BasicOption, MapOption, LineMapColorOption, FlyLineOption, ScatterOption |
| JBarMap | BasicOption, MapOption, LineMapColorOption, BarMapOption |
| JHeatMap | BasicOption, MapOption, LineMapColorOption, HeatOption |
| JTotalFlyLineMap | BasicOption, MapOption, LineMapColorOption, FlyLineOption, ScatterOption, TimeLineOption |
| JTotalBarMap | BasicOption, MapOption, LineMapColorOption, BarMapOption, TimeLineOption |
### 其他组件
| 组件 | 设置面板 |
|------|---------|
| JWordCloud | BasicOption, WordCloudOption |
| JFlashCloud | BasicOption, FlashCloudOption |
| JRadioButton | BasicOption, CustomColorOption |
| JSelectRadio | BasicOption |
| JPictorialBar | BasicOption, TitleOption, XAxisOption, YAxisOption, PictorialOption |
| JGender | BasicOption |
| JStatsSummary | BasicOption |
| JCarousel | BasicOption, CarouselOption |
| JVideoPlay | BasicOption |
| JIframe | BasicOption |
| JRectangle | BasicOption, RectangleOption |
---
## ECharts 与非 ECharts 组件区分
**ECharts 组件**(底层用 ECharts 渲染option 遵循 ECharts 规范 + 扩展属性):
JBar, JStackBar, JDynamicBar, JHorizontalBar, JBackgroundBar, JMultipleBar, JNegativeBar,
JLine, JSmoothLine, JStepLine, JMultipleLine, JArea,
JMixLineBar, DoubleLineBar,
JPie, JRose, JRotatePie, JRing,
JScatter, JBubble, JQuadrant,
JFunnel, JPyramidFunnel,
JRadar, JCircleRadar,
JGauge, JColorGauge,
JProgress, JPictorialBar,
JBar3d, JBarGroup3d,
JWordCloud,
JAreaMap, JBubbleMap, JFlyLineMap, JBarMap, JHeatMap, JTotalFlyLineMap, JTotalBarMap,
JCustomEchart
**非 ECharts 组件**自定义渲染option 使用组件私有属性):
JNumber, JCountTo, JText, JColorBlock, JCurrentTime,
JLiquid, JAntvGauge, JSemiGauge, JCustomProgress, JListProgress, JRoundProgress, JRingProgress,
JActiveRing, JRadialBar, JBreakRing,
JCapsuleChart, JPercentBar,
JScrollBoard, JScrollTable, JCommonTable, JTable, JList, JScrollList, JScrollRankingBoard, JFlashList, JBubbleRank,
JCarousel, JVideoPlay, JImg, JIframe,
JRadioButton, JSelectRadio, JTabToggle, JForm,
JDragBorder, JDragDecoration, JDragEditor,
JPyramid3D, JGender, JStatsSummary,
JFlashCloud, JImgWordCloud, JOrbitRing, JRectangle, JDevHistory

View File

@@ -0,0 +1,469 @@
# 大屏/仪表盘组件类型完整参考
## 组件分类总览
共 142+ 组件,分为以下大类:
---
## 一、图表组件ECharts
### 柱状图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JBar` | 基础柱状图 | 分类数据对比 |
| `JHorizontalBar` | 横向柱状图 | 类目名较长 |
| `JBackgroundBar` | 背景柱状图 | 带背景色柱状图 |
| `JMultipleBar` | 分组柱状图 | 多系列对比 |
| `JNegativeBar` | 正负柱状图 | 正负值对比 |
| `JStackBar` | 堆叠柱状图 | 部分与整体 |
| `JDynamicBar` | 动态柱状图 | 动画排名效果 |
| `JCapsuleChart` | 胶囊柱状图 | 进度/占比展示 |
| `JPercentBar` | 百分比柱状图 | 占比可视化 |
### 折线图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JLine` | 基础折线图 | 趋势变化 |
| `JSmoothLine` | 平滑曲线图 | 柔和趋势展示 |
| `JStepLine` | 阶梯折线图 | 离散变化 |
| `JMultipleLine` | 多折线图 | 多系列趋势 |
### 混合图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JMixLineBar` | 柱线混合图 | 不同量级对比 |
| `DoubleLineBar` | 双轴图 | 双Y轴混合 |
### 饼图/环形图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JPie` | 饼图 | 占比分析 |
| `JRose` | 玫瑰图 | 带大小的占比 |
| `JRing` | 环形图 | 占比(中心可放数字) |
| `JBreakRing` | 断裂环形图 | 特殊视觉效果 |
| `JRotatePie` | 旋转饼图 | 动态展示 |
| `JActiveRing` | 活动环形图 | 动画环形 |
### 仪表/进度系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JGauge` | 仪表盘 | 完成度/达标率 |
| `JColorGauge` | 彩色仪表盘 | 多色阈值 |
| `JAntvGauge` | AntV仪表盘 | G2Plot风格 |
| `JSemiGauge` | 半圆仪表盘 | 半圆展示 |
| `JProgress` | 进度条 | 完成进度 |
| `JCustomProgress` | 自定义进度条 | 自定义样式 |
| `JListProgress` | 列表进度条 | 多项进度对比 |
| `JRoundProgress` | 圆形进度 | 圆形完成度 |
| `JRingProgress` | 环形进度 | 环形完成度 |
| `JLiquid` | 水球图 | 百分比/液位 |
| `JRadialBar` | 径向柱状图 | 环形对比 |
### 散点/气泡系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JScatter` | 散点图 | 分布/相关性 |
| `JBubble` | 气泡图 | 三维数据 |
| `JQuadrant` | 象限图 | 四象限分析 |
| `JBubbleRank` | 气泡排名 | 排名可视化 |
### 漏斗系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JFunnel` | 漏斗图 | 转化分析 |
| `JPyramidFunnel` | 金字塔漏斗 | 层级占比 |
| `JPyramid3D` | 3D金字塔 | 立体效果 |
### 雷达/其他
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JRadar` | 雷达图 | 多维度对比 |
| `JCircleRadar` | 圆形雷达 | 圆形多维 |
| `JRectangle` | 矩形树图 | 层级占比 |
| `JGraphSimple` | 关系图 | 节点关系 |
| `JWordCloud` | 词云 | 关键词频率 |
| `JImgWordCloud` | 图片词云 | 图形词云 |
| `JCustomEchart` | 自定义ECharts | 自定义配置 |
### 地图系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JAreaMap` | 区域地图 | 区域数据着色 |
| `JBubbleMap` | 气泡地图 | 地理数据标注 |
| `JFlyLineMap` | 飞线地图 | 迁徙/物流 |
| `JBarMap` | 柱状地图 | 地理柱状 |
| `JHeatMap` | 热力地图 | 密度分布 |
| `JTotalFlyLineMap` | 多节点飞线 | 多点流动 |
| `JTotalBarMap` | 多柱状地图 | 多地理柱状 |
| `JGaoDeMap` | 高德地图 | 实际地图 |
| `JFly3dMap` | 3D飞线地图 | 3D效果 |
### 3D图表
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JBar3d` | 3D柱状图 | 立体柱状 |
| `JBarGroup3d` | 3D分组柱状图 | 立体分组 |
---
## 二、数据展示组件
### 表格系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JTable` | 数据表格 | 详细数据列表 |
| `JCommonTable` | 通用表格 | 基础表格 |
| `JScrollTable` | 滚动表格 | 自动轮播表格 |
| `JPivotTable` | 透视表 | 交叉分析 |
### 列表/排行系列
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JList` | 列表 | 通用列表 |
| `JScrollBoard` | 滚动看板 | 信息滚动 |
| `JScrollList` | 滚动列表 | 列表轮播 |
| `JScrollRankingBoard` | 排行榜 | 排名展示 |
| `JFlashList` | 闪烁列表 | 动态列表 |
| `JFlashCloud` | 闪烁云 | 标签云 |
| `JRankingList` | 排名列表 | 静态排名 |
| `JDynamicInfo` | 动态信息 | 实时信息流 |
---
## 三、数字/统计卡片
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JNumber` | 数字指标 | KPI核心指标 |
| `JCountTo` | 数字翻牌 | 动画计数 |
| `JCurrentTime` | 实时时钟 | 当前时间 |
| `JColorBlock` | 色块指标 | 颜色+数字 |
| `JGrowCard` | 增长卡片 | 增长率指标 |
| `JSimpleCard` | 简单卡片 | 信息卡片 |
| `JProjectCard` | 项目卡片 | 项目概览 |
| `JCustomCard` | 自定义卡片 | 灵活卡片 |
| `JStatsSummary` | 统计概要 | 多指标汇总 |
---
## 四、交互/容器组件
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JTabs` | 选项卡 | Tab切换 |
| `JTabToggle` | 切换选项卡 | 简化Tab |
| `JGrid` | 栅格布局 | 布局容器 |
| `JQuickNav` | 快捷导航 | 菜单导航 |
| `JRadioButton` | 单选按钮组 | 筛选切换 |
| `JSelectRadio` | 下拉/单选 | 数据筛选 |
| `JFilterQuery` | 筛选查询 | 复合查询 |
| `JCustomButton` | 自定义按钮 | 操作按钮 |
| `JForm` | 表单 | 查询表单 |
| `JGroup` | 组合 | 组件编组(大屏) |
---
## 五、媒体组件
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JImg` | 图片 | 图片展示 |
| `JCarousel` | 轮播 | 图片/内容轮播 |
| `JVideoPlay` | 视频 | 视频播放 |
| `JVideoJs` | Video.js | 高级视频 |
| `JIframe` | 内嵌页 | 嵌入外部页面 |
| `JWeatherForecast` | 天气 | 天气预报 |
---
## 六、文本/装饰组件
| component | 名称 | 适用场景 |
|-----------|------|---------|
| `JText` | 文本 | 标题/说明文字 |
| `JOrbitRing` | 轨道环 | 科技感装饰 |
| `JCustomIcon` | 图标 | 自定义图标 |
| `JDragBorder` | 装饰边框 | 13种边框样式 |
| `JDragDecoration` | 装饰条 | 12种装饰样式 |
| `JDragEditor` | 富文本 | TinyMCE编辑器 |
| `JCalendar` | 日历 | 日历展示 |
| `JPermanentCalendar` | 静态日历 | 固定日历 |
---
## 组件通用配置结构
### 大屏模式bigScreen组件 config
```json
{
"w": 450,
"h": 300,
"dataType": 1,
"url": "",
"timeOut": 0,
"turnConfig": {
"url": "",
"type": "_blank"
},
"linkType": "url",
"linkageConfig": [],
"markLineConfig": {
"show": false,
"markLine": []
},
"dataMapping": [
{"filed": "维度", "mapping": ""},
{"filed": "数值", "mapping": ""}
],
"chartData": [],
"option": {}
}
```
### 数据源类型dataType
| dataType | 说明 |
|----------|------|
| 1 | 静态数据chartData 中直接写入) |
| 2 | API 接口url 字段指定) |
| 3 | SQL 查询(数据集配置) |
| 4 | 关联表单数据 |
### JNumber 组件 config 示例
```json
{
"dataType": 4,
"formId": "form_code",
"formName": "表单名称",
"tableName": "table_name",
"formType": "design",
"valueFields": [{
"fieldName": "record_count",
"fieldTxt": "记录数量",
"fieldType": "count",
"widgetType": "text"
}],
"analysis": {
"showData": 1,
"isRawData": true,
"isCompare": false,
"showMode": 1,
"trendType": "1"
},
"filter": {
"conditionMode": "and",
"conditionFields": [],
"queryField": "create_time",
"queryRange": "month"
},
"size": {"height": 500},
"chart": {
"subclass": "JNumber",
"category": "Number"
},
"option": {
"isCompare": false,
"trendType": "1",
"body": {"color": "#000000", "fontWeight": "bold"},
"card": {"size": "small"}
}
}
```
### JBar/JLine 等轴类图表 config 示例
```json
{
"dataType": 1,
"chartData": [
{"name": "一月", "value": 820},
{"name": "二月", "value": 932},
{"name": "三月", "value": 901}
],
"size": {"width": 860, "height": 380},
"chart": {
"subclass": "JBar",
"category": "Bar"
},
"option": {
"title": {"text": "月度销售", "show": true},
"tooltip": {"show": true},
"legend": {"show": true},
"xAxis": {
"type": "category",
"show": true,
"data": ["一月", "二月", "三月"]
},
"yAxis": {"type": "value", "show": true},
"series": [{
"name": "销售额",
"type": "bar",
"data": [820, 932, 901]
}],
"grid": {"left": "10%", "right": "10%", "top": "15%", "bottom": "15%"}
}
}
```
### JPie 饼图 config 示例
```json
{
"dataType": 1,
"chartData": [
{"name": "直接访问", "value": 335},
{"name": "邮件营销", "value": 310},
{"name": "联盟广告", "value": 234}
],
"size": {"width": 500, "height": 350},
"chart": {
"subclass": "JPie",
"category": "Pie"
},
"option": {
"title": {"text": "访问来源", "show": true},
"tooltip": {"show": true},
"legend": {"show": true, "orient": "vertical", "left": "left"},
"series": [{
"name": "来源",
"type": "pie",
"radius": "55%",
"data": [
{"name": "直接访问", "value": 335},
{"name": "邮件营销", "value": 310},
{"name": "联盟广告", "value": 234}
]
}]
}
}
```
### JTable 表格 config 示例
```json
{
"dataType": 1,
"chartData": [
{"fieldTxt": "姓名", "fieldName": "name", "type": "field", "isShow": "Y", "isTotal": "N"},
{"fieldTxt": "年龄", "fieldName": "age", "type": "field", "isShow": "Y", "isTotal": "Y"},
{"fieldTxt": "地址", "fieldName": "address", "type": "field", "isShow": "Y", "isTotal": "N"}
],
"url": "http://api.jeecg.com/mock/42/tableData",
"tableList": "http://api.jeecg.com/mock/42/tableList",
"size": {"width": 700, "height": 350},
"option": {
"bordered": true,
"size": "small"
}
}
```
### JCustomButton 按钮 config 示例
```json
{
"dataType": 1,
"chartData": [{
"btnId": "74591654852155",
"title": "请假申请",
"color": "#ED4B82",
"icon": "ant-design:calendar-twotone",
"operationType": "1",
"openMode": "2",
"worksheet": {
"label": "请假申请",
"value": "form_code",
"key": "form_code"
},
"click": {
"type": "1",
"message": {"title": "确认执行?", "okText": "确认", "cancelText": "取消"}
}
}],
"option": {
"btnDirection": "column",
"btnStyle": "solid",
"rowNum": 5,
"title": "常用操作",
"btnType": "button",
"btnWidth": "custom"
}
}
```
### JCarousel 轮播 config 示例
```json
{
"dataType": 1,
"chartData": "[{\"src\":\"https://example.com/1.png\"},{\"src\":\"https://example.com/2.png\"}]",
"size": {"width": 800, "height": 300},
"option": {
"dots": true,
"autoplay": true,
"dotPosition": "bottom"
}
}
```
---
## 大屏 vs 仪表盘 布局差异
### 大屏bigScreen
- **布局方式**:绝对定位(像素坐标)
- **坐标单位**x/y 为像素值
- **尺寸单位**w/h 为像素值
- **典型画布**1920×1080 像素
- **支持旋转**angle 属性)
- **背景**:深色 + 背景图
### 仪表盘default
- **布局方式**网格布局vue-grid-layout
- **坐标单位**x 为列号0-23y 为行号
- **尺寸单位**w 为列数1-24h 为行数rowHeight=1px
- **支持旋转**:否
- **背景**:浅色/白色
### template 中的坐标字段
| 字段 | 大屏 | 仪表盘 | 说明 |
|------|------|--------|------|
| `x` | 像素 | 栅格列 | 水平位置 |
| `y` | 像素 | 栅格行 | 垂直位置 |
| `w` | 像素 | 列数(1-24) | 宽度 |
| `h` | 像素 | 行数 | 高度 |
| `pcX` | 像素 | — | PC端水平位置 |
| `pcY` | 像素 | — | PC端垂直位置 |
| `pcW` | 像素 | — | PC端宽度 |
---
## 主题配置
### 大屏主题
```json
{
"theme": "dark",
"style": "bigScreen",
"backgroundColor": "",
"backgroundImage": "/img/bg/bg4.png"
}
```
可用背景图:
- `/img/bg/bg1.png` ~ `/img/bg/bg10.png`
### 仪表盘主题
```json
{
"theme": "default",
"style": "default",
"backgroundColor": "#f3f5f8"
}
```
组件主题颜色:`default`, `gray`, `green`, `red`, `blue`, `dark`

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More