深度解析:Flowable + Vue3 企业级流程架构设计——为什么 若依RuoYi Office 的 BPM 能真正落地?
🌐 文档地址:ruoyioffice.com | 📦 源码1:gitee.com/yqzy1688/ru… |📦 源码2:gitee.com/yqzy1688/ru… |📦 源码3:github.com/yuqing2026/… | 💬 :17156169080(备注「RuoYi Office」)
很多开源项目都说自己"集成了工作流",但真正能在企业场景中跑起来的又有几个?流程设计器只是冰山一角——审批人怎么分配?业务表单怎么集成?状态怎么同步?权限怎么控制? 这些才是 BPM 落地的核心难题。本文从 RuoYi Office 的实际源码出发,带你看懂一套经过生产验证的企业级流程架构。
引言:企业级 BPM 到底难在哪?
如果你只是想跑一个"请假→审批→完成"的 Demo,那任何一个 BPM 框架都能做到。但当你面对真实的企业场景:
- 🤯 几十种业务单据(请假、用印、用车、会议室、采购、合同...)都要走流程
- 🏢 复杂的组织架构:审批人可能是角色、部门负责人、多级领导、发起人自选
- 📋 表单多样性:有些表单简单到拖拽就能生成,有些复杂到必须自定义开发
- 🔄 状态联动:流程审批通过后,业务单据要自动更新状态、触发后续操作
- 🏗️ 微服务架构:BPM 模块和业务模块跨服务部署,如何解耦通信?
这时候你会发现,大部分"集成了工作流"的开源项目,在这些场景面前根本不堪一击。
RuoYi Office 是一个基于 Spring Cloud Alibaba + Vue 3 + Vben Admin 构建的企业管理一体化平台,涵盖 OA、HRM、CRM、ERP 等 14 大业务模块,每个模块都有大量需要走审批流程的业务单据。正是这种复杂的业务需求,倒逼出了一套真正经过实战检验的 BPM 架构。

▲ RuoYi Office 工作台首页:待办任务 99+、我的单据、已办任务、抄送我的,一站式流程工作台
一、架构全景:五层分离的流程引擎设计
1.1 核心引擎选型
| 维度 | 选型 | 为什么这么选 |
|---|---|---|
| 流程引擎 | Flowable 7.0.1 | 轻量、高性能、BPMN 2.0 全兼容,社区活跃度远超 Activiti |
| 集成方式 | flowable-spring-boot-starter-process | Spring Boot Starter 自动配置,开箱即用 |
| 历史级别 | audit | 记录流程活动和任务历史,满足审计需求又不过度存储 |
| 表结构管理 | database-schema-update: true | 自动更新 Flowable 表结构,降低运维成本 |
为什么选 Flowable 而不是 Camunda 或 Activiti?
Flowable 7.x 是 Activiti 核心团队出走后的延续项目,继承了 Activiti 的全部优点并持续进化。相比 Camunda 的商业化路线,Flowable 的社区版功能更完整。与停滞的 Activiti 相比,Flowable 的版本迭代更加活跃,对 Spring Boot 3.x、Jakarta EE 的支持也更及时。
1.2 五层架构模型
RuoYi Office 的 BPM 架构采用严格分层设计,每一层职责明确、边界清晰:
```
关键设计理念:
- BPM API 层作为跨模块通信契约,业务模块(OA、HRM 等)只依赖
bpm-api,不直接依赖bpm-server - 事件驱动解耦:BPM 模块通过 Spring Event 发布状态变更,业务模块监听处理
- 策略模式扩展:审批人分配、流程行为等均通过策略模式实现,新增策略无需修改核心代码
1.3 Flowable 深度定制配置
RuoYi Office 不是简单地引入 Flowable,而是通过 BpmFlowableConfiguration 进行了深度定制:
@Configuration(proxyBeanMethods = false)
public class BpmFlowableConfiguration {
@Bean
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration>
bpmProcessEngineConfigurationConfigurer(
ObjectProvider<FlowableEventListener> listeners,
ObjectProvider<FlowableFunctionDelegate> customFlowableFunctionDelegates,
BpmActivityBehaviorFactory bpmActivityBehaviorFactory) {
return configuration -> {
// 1. 注册全局事件监听器
configuration.setEventListeners(ListUtil.toList(listeners.iterator()));
// 2. 替换默认的 ActivityBehaviorFactory,实现自定义审批人分配
configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
// 3. 注册自定义函数(流程表达式)
configuration.setCustomFlowableFunctionDelegates(
ListUtil.toList(customFlowableFunctionDelegates.stream().iterator()));
};
}
@Bean
public BpmActivityBehaviorFactory bpmActivityBehaviorFactory(
BpmTaskCandidateInvoker bpmTaskCandidateInvoker) {
BpmActivityBehaviorFactory factory = new BpmActivityBehaviorFactory();
factory.setTaskCandidateInvoker(bpmTaskCandidateInvoker);
return factory;
}
@Bean
public BpmTaskCandidateInvoker bpmTaskCandidateInvoker(
List<BpmTaskCandidateStrategy> strategyList,
AdminUserApi adminUserApi) {
return new BpmTaskCandidateInvoker(strategyList, adminUserApi);
}
@Bean
public BpmProcessInstanceEventPublisher processInstanceEventPublisher(
ApplicationEventPublisher publisher) {
return new BpmProcessInstanceEventPublisher(publisher);
}
}
三个核心定制点:
BpmActivityBehaviorFactory:替换 Flowable 默认的任务行为工厂,实现多策略审批人分配- 全局事件监听器:捕获流程实例和任务的生命周期事件
BpmProcessInstanceEventPublisher:将 Flowable 事件转换为 Spring ApplicationEvent,实现跨模块解耦
二、双流程设计器:让专业的人用专业的,让普通人也能用
2.1 设计理念
企业中有两类流程设计需求:
| 需求场景 | 用户角色 | 复杂度 | 举例 |
|---|---|---|---|
| 标准复杂流程 | IT/流程管理员 | 高 | 采购审批(多分支、并行、子流程) |
| 简单审批流 | 业务管理员 | 低 | 请假、用印、用车(串行审批) |
RuoYi Office 提供双设计器满足两种需求:
| 设计器 | 技术方案 | 适用场景 | 特点 |
|---|---|---|---|
| BPMN 设计器 | 基于 bpmn-js | 复杂流程,精细控制 | 标准 BPMN 2.0,功能完备 |
| Simple 设计器 | 仿钉钉/飞书拖拽式 | 简单审批流,快速搭建 | 零门槛,业务人员可自助配置 |


▲ 流程模型管理页面:清晰标注每个流程使用的设计器类型(BPMN / SIMPLE),一目了然
2.2 Simple 设计器的技术实现
Simple 设计器的精妙之处在于——用户看到的是拖拽式界面,底层执行的仍然是标准 BPMN 2.0。
用户拖拽配置 → SimpleModel (JSON 树结构) → SimpleModelUtils.buildBpmnModel() → BPMN Model → Flowable 执行
Simple 设计器使用树形数据结构描述流程,支持丰富的节点类型:
| 节点类型 | 说明 | BPMN 映射 |
|---|---|---|
| 开始节点 | 流程起点 | startEvent |
| 审批人节点 | 人工审批 | userTask |
| 抄送人节点 | 消息抄送 | serviceTask |
| 条件分支 | 排他网关 | exclusiveGateway |
| 并行分支 | 并行网关 | parallelGateway |
| 包容分支 | 包容网关 | inclusiveGateway |
| 延迟器 | 定时等待 | intermediateCatchEvent |
| 触发器 | 自动处理 | serviceTask |
| 结束节点 | 流程终点 | endEvent |
这种设计既让普通用户享受到拖拽式的便利,又保证了底层引擎执行的一致性和标准性。管理员只需要关注"谁审批、什么条件",不需要理解 BPMN 规范。
2.3 Simple 设计器实战:以用印申请流程为例
光说理论不够直观,我们以 OA 模块中的用印申请单为例,看看 Simple 设计器的实际使用效果:

▲ Simple 设计器实战:OA用印申请单的完整审批流程设计
上图展示了一个真实企业场景的用印申请审批流程,每个节点的配置都非常直观:
| 节点 | 类型 | 审批人策略 | 说明 |
|---|---|---|---|
| 发起人 | 开始节点 | 已设置(自动填充) | 流程发起人自动作为第一个节点 |
| 部门负责人审批 | 审批人节点 | 发起人的部门负责人 | 自动找到发起人的直属上级审批 |
| 印章保管员审批 | 审批人节点 | 指定岗位:OA_印章保管员 | 按岗位匹配,人员变动无需改流程 |
| 条件分支 | 条件网关 | — | 根据「用印方式」自动路由 |
| ┣ 借用印章 | 条件分支1 | 表达式:${sealUseMode == 2} | 外借用章需要额外归还确认 |
| ┣ 现场用印 | 条件分支2 | 默认分支(优先级2) | 现场用印直接结束 |
| 申请人归还印章 | 审批人节点 | 发起人自己 | 借出后由申请人确认归还 |
这个例子完美体现了 Simple 设计器的几个核心能力:
- 多种审批人策略混合使用:一个流程中同时使用了「部门负责人」「指定岗位」「发起人自己」三种策略
- 条件分支自动路由:根据表单字段(用印方式
sealUseMode)动态决定流程走向,零代码实现业务逻辑分支 - 业务变量驱动:流程变量直接绑定业务表单字段,无需硬编码
- 可视化配置:整个流程所见即所得,业务管理员无需理解 BPMN XML 即可完成配置
💡 注意看条件分支的设计:现场用印走默认分支直接结束,而借用印章则增加了「申请人归还印章」节点——这就是 Simple 设计器对企业审批复杂度的精准把控。
2.4 创建流程的四步走
步骤 1: 基本信息 → 步骤 2: 表单设计 → 步骤 3: 流程设计 → 步骤 4: 更多设置
| 步骤 | 配置内容 | 说明 |
|---|---|---|
| 基本信息 | 名称、分类、图标、描述 | 定义流程的基础标识 |
| 表单设计 | 选择流程表单或配置业务表单路径 | 决定用拖拽表单还是自定义表单 |
| 流程设计 | BPMN 或 Simple 设计器 | 定义流程走向和审批规则 |
| 更多设置 | 可见范围、自定义打印等 | 高级配置选项 |

▲ 发起流程页面:按 OA 协同办公、人力资源管理、仓库管理等分类展示,员工一键发起
三、表单双模式:拖拽表单 vs 业务表单,灵活适配任何场景
这是 RuoYi Office BPM 架构中最具亮点的设计之一。
3.1 两种表单模式对比
| 维度 | 流程表单(NORMAL) | 业务表单(CUSTOM) |
|---|---|---|
| 定义方式 | 拖拽设计器,生成 JSON 配置 | 自定义开发 Vue 组件 + 后端 Service |
| 存储位置 | bpm_form 表 | 各业务模块自有表 |
| 前端渲染 | form-create 动态渲染 | 自定义 Vue 组件 |
| 适用场景 | 简单表单,无复杂业务逻辑 | 复杂业务逻辑(印章选择、时间冲突校验等) |
| 开发成本 | 零代码 | 需要前后端开发 |
| 灵活性 | 中等 | 极高 |
3.2 流程表单设计器实战:零代码搭建审批表单
RuoYi Office 内置了强大的可视化流程表单设计器,基于 form-create + antd-designer 构建,支持拖拽式配置:

▲ 流程表单设计器:左侧组件面板 + 中间表单预览 + 右侧属性配置,三栏布局一目了然
设计器提供了丰富的表单组件库,覆盖企业审批的常见场景:
| 组件分类 | 组件列表 | 企业场景 |
|---|---|---|
| 基础输入 | 输入框、多行输入框、密码输入框、数字输入框 | 事由描述、金额填写、备注信息 |
| 选择类 | 单选框、多选框、选择器、级联选择器 | 审批类型、印章名称选择、省市区 |
| 日期时间 | 日期、日期区间、时间、时间区间 | 请假时间、用车时间、借用时间 |
| 上传类 | 文件上传、单图上传、多图上传 | 审批附件、证明材料、现场照片 |
| 高级组件 | 富文本框、手写签名、树形选择、穿梭框 | 审批意见、电子签章、部门选择、人员选择 |
| 布局类 | 滑块、开关、评分、网页 iframe | 满意度评分、条件开关、嵌入外部页面 |
设计器核心功能:
- 📐 表单布局配置:支持横向/竖向布局、左对齐/右对齐、标签宽度自定义
- 📱 多尺寸适配:大/中/小三种表单尺寸,适配不同屏幕场景
- 👁️ 实时预览:拖拽即可实时预览表单效果,支持默认值和校验规则配置
- ✅ 必填校验:每个字段可独立设置必填标识,提交时自动校验
- 🔄 表单复用:设计好的表单可以被多个流程模型复用,一次设计多处使用
何时选择流程表单?如果你的审批场景是简单的信息收集(如请假事由+时间+附件),没有复杂的业务逻辑和后端数据交互,那么流程表单就是最优选择——零代码、零部署、即拖即用。
3.3 前端如何智能切换
流程审批详情页(processInstance/detail/index.vue)通过 formType 智能切换两种渲染模式:
async function getApprovalDetail() {
const data = await getApprovalDetailApi(param);
processDefinition.value = data.processDefinition;
// 核心判断:根据表单类型决定渲染方式
if (processDefinition.value.formType === BpmModelFormType.NORMAL) {
// 流程表单:使用 form-create 动态渲染
setConfAndFields2(
detailForm,
processDefinition.value.formConf,
processDefinition.value.formFields,
processInstance.value.formVariables,
);
// 设置字段权限(只读/编辑/隐藏)
nextTick().then(() => {
Object.keys(formFieldsPermission).forEach((item) => {
setFieldPermission(item, formFieldsPermission[item]);
});
});
} else {
// 业务表单:动态加载自定义 Vue 组件
BusinessFormComponent.value = registerComponent(
data?.processDefinition?.formCustomViewPath || '',
);
}
}
这段代码的精妙之处:
- 流程表单走
form-create动态渲染路径,表单配置和字段定义都存储在流程定义中,运行时解析渲染 - 业务表单走动态组件加载路径,通过
formCustomViewPath(如/oa/seal/sealapply/info/index.vue)加载业务模块自己开发的 Vue 组件 - 字段权限控制:每个审批节点可以独立配置字段的只读/编辑/隐藏权限,做到审批人只看到该看的内容
3.4 业务表单集成规范
业务表单组件需要遵循统一的接口契约:
// 业务表单组件的 Props 定义
const props = defineProps<{
id?: number | string; // 单据 ID
isApproval?: boolean; // 是否审批态
isCopy?: boolean; // 是否抄送查看模式
nodeKey?: string; // 当前节点 Key
nodeKeyName?: string; // 当前节点名称
processDefinition?: any; // 流程定义信息
processInstance?: any; // 流程实例信息
activityNodes?: any[]; // 审批节点列表
copyReason?: string; // 抄送意见
}>();
// 暴露给父组件的方法
defineExpose({
beforeApproval, // 审批前校验(可在此做业务验证和数据保存)
loadData, // 加载数据
handleSaveAndSubmit // 保存并提交
});
这套规范确保了任何业务模块开发的表单组件,都能无缝嵌入到统一的审批详情页中。BPM 框架提供基础设施(审批按钮、流程图、审批时间线),业务表单只关注自己的业务逻辑。
四、事件驱动架构:BPM 与业务模块的优雅解耦
4.1 问题背景
在微服务架构下,BPM 模块和业务模块是独立的服务。当流程状态发生变更时(审批通过、驳回、取消),业务模块需要同步更新自己的单据状态。
如果用直接调用:BPM 模块需要知道每个业务模块的接口,产生大量耦合 RuoYi Office 的做法:事件驱动 + 监听器模式,完全解耦
4.2 事件定义
@Data
public class BpmProcessInstanceStatusEvent extends ApplicationEvent {
@NotNull(message = "事件类型不能为空")
private BpmEventTypeEnum eventType; // 事件类型
private LocalDateTime eventTime; // 事件发生时间
@NotNull(message = "流程实例信息不能为空")
private BpmProcessInstanceInfo processInstanceInfo; // 流程实例信息
private BpmTaskInfo taskInfo; // 任务信息(仅任务事件)
private Map<String, Object> extData; // 扩展数据
}
事件类型覆盖了流程和任务的完整生命周期:
| 事件类型 | 说明 |
|---|---|
PROCESS_INSTANCE_STATUS_CHANGED | 流程实例状态变更(审批通过/驳回/取消) |
PROCESS_INSTANCE_DELETED | 流程实例被删除 |
TASK_CREATED | 任务创建 |
TASK_CREATED_REENTER | 任务重新进入开始节点 |
TASK_APPROVED | 任务审批通过 |
TASK_REJECTED | 任务审批拒绝 |
TASK_WITHDRAWN | 任务撤回 |
TASK_TRANSFERRED | 任务转办 |
TASK_DELEGATED | 任务委派 |
4.3 FlowBillService:业务模块的统一接入接口
这是 RuoYi Office BPM 架构中最核心的设计之一。FlowBillService 定义在框架公共层,作为所有业务模块接入流程引擎的统一契约:
public interface FlowBillService<T extends BillTypeEnum> {
/** 获取支持的单据类型 */
T getSupportedBillType();
/** 更新流程状态 — 必须实现 */
void updateProcessStatus(String businessKey, Integer status);
/** 流程审批通过后的业务处理 — 按需重写 */
default void onProcessApproved(String businessKey) { }
/** 流程拒绝后的业务处理 — 按需重写 */
default void onProcessRejected(String businessKey) { }
/** 流程撤回后的业务处理 — 按需重写 */
default void onProcessCancelled(String businessKey) { }
/** 删除业务单据数据 — 按需重写 */
default void deleteBill(String businessKey) { }
}
设计亮点:
- 泛型约束:
<T extends BillTypeEnum>确保每个实现类绑定到具体的单据类型枚举 - 默认实现:通过
default方法,业务模块只需实现必要的方法(updateProcessStatus),回调方法按需重写 - 松耦合:业务模块不需要知道 BPM 内部如何工作,只需要实现这个接口
4.4 通用事件监听器:一套模板服务所有业务模块
@Slf4j
public abstract class AbstractFlowLocalNotificationListener<T extends BillTypeEnum>
implements ApplicationListener<BpmProcessInstanceStatusEvent> {
protected abstract SystemEnum getSystem();
protected abstract FlowBillServiceFactory<T> getFlowBillServiceFactory();
@Override
public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
// 1. 按模块前缀过滤,只处理本模块的流程事件
String processDefinitionKey = event.getProcessDefinitionKey();
if (!FlowProcessPrefixUtils.match(getSystem(), processDefinitionKey)) {
return; // 非本模块流程,跳过
}
// 2. 按事件类型分发处理
BpmEventTypeEnum eventType = event.getEventType();
if (eventType.isProcessInstanceEvent()) {
handleProcessInstanceEvent(event); // 流程实例事件
} else if (eventType.isTaskEvent()) {
handleTaskEvent(event); // 任务事件
}
}
/** 更新单据状态 — 通过工厂模式获取对应的 FlowBillService */
private void updateBillStatus(BpmProcessInstanceStatusEvent message, Integer status) {
FlowBillService<T> flowBillService = getFlowBillServiceFactory()
.getServiceByProcessKey(message.getProcessDefinitionKey());
TenantUtils.executeIgnore(() -> {
flowBillService.updateProcessStatus(
message.getProcessInstanceInfo().getBusinessKey(),
status == null ? message.getProcessInstanceInfo().getStatus() : status);
});
}
}
每个业务模块只需要一个极简的实现类:
@Component
public class OaLocalNotificationListener
extends AbstractFlowLocalNotificationListener<OaBillTypeEnum> {
@Resource
private OaFlowBillServiceFactory flowBillServiceFactory;
@Override
protected SystemEnum getSystem() {
return SystemEnum.OA;
}
@Override
protected FlowBillServiceFactory<OaBillTypeEnum> getFlowBillServiceFactory() {
return flowBillServiceFactory;
}
}
仅仅 15 行代码,OA 模块就完成了与 BPM 的事件对接!HRM、CRM 等模块同理,彻底消除了重复代码。
4.5 事件流转全景图

五、实战案例:印章申请单的完整流程解析
理论说得再好不如看实际代码。下面以用印申请单(OA 模块最典型的业务表单之一)为例,完整展示 BPM 与业务模块的集成过程。
5.1 后端:SealApplyBillServiceImpl
@Service
@Validated
public class SealApplyBillServiceImpl
implements SealApplyBillService, FlowBillService<OaBillTypeEnum> {
@Resource
private SealApplyBillMapper sealApplyBillMapper;
@Resource
private BpmProcessInstanceApi processInstanceApi;
@Override
public Long submitSealApplyBill(SealApplyBillSaveReqVO saveReqVO) {
// 1. 生成单据编号
if (StringUtils.isBlank(saveReqVO.getBillCode())) {
saveReqVO.setBillCode(BillCodeUtils.generateBillCode(
SystemEnum.OA, OaBillTypeEnum.OA_SEAL_APPLY_BILL));
}
// 2. 业务校验(外借用章需校验时间冲突)
if (saveReqVO.getUseMode() != null && saveReqVO.getUseMode() == 2) {
validateTimeConflict(saveReqVO);
}
// 3. 保存业务数据
SealApplyBillDO sealApplyBill = BeanUtils.toBean(saveReqVO, SealApplyBillDO.class)
.setProcessStatus(BpmTaskStatusEnum.RUNNING.getStatus());
sealApplyBillMapper.insertOrUpdate(sealApplyBill);
// 4. 构建流程变量(标准字段 + 业务自定义字段)
Map<String, Object> processInstanceVariables =
BpmProcessVariableUtils.buildBillVariables(saveReqVO);
processInstanceVariables.put(PV_SEAL_USE_MODE, saveReqVO.getUseMode());
// 5. 发起流程实例
String processInstanceId = processInstanceApi.submitProcessInstance(
Long.valueOf(saveReqVO.getCreator()),
new BpmProcessInstanceCreateReqDTO()
.setProcessDefinitionKey(OaBillTypeEnum.OA_SEAL_APPLY_BILL
.getProcessDefinitionKey())
.setVariables(processInstanceVariables)
.setBusinessKey(String.valueOf(sealApplyBill.getId()))
).getCheckedData();
// 6. 回写流程实例 ID 到业务表
sealApplyBillMapper.updateById(
new SealApplyBillDO().setId(sealApplyBill.getId())
.setProcessInstanceId(processInstanceId));
return sealApplyBill.getId();
}
// ==================== FlowBillService 接口实现 ====================
@Override
public OaBillTypeEnum getSupportedBillType() {
return OaBillTypeEnum.OA_SEAL_APPLY_BILL;
}
@Override
public void updateProcessStatus(String businessKey, Integer status) {
Long id = Long.parseLong(businessKey);
SealApplyBillDO updateObj = new SealApplyBillDO().setId(id)
.setProcessStatus(status);
// 审批通过时,自动设置用印状态为待处理
if (APPROVE.getStatus().equals(status)) {
updateObj.setUseStatus(SealUseStatusEnum.PENDING.getStatus());
}
sealApplyBillMapper.updateById(updateObj);
}
@Override
public void deleteBill(String businessKey) {
Long id = Long.parseLong(businessKey);
attachmentService.deleteAttachmentByBusiness(
OaBillTypeEnum.OA_SEAL_APPLY_BILL.getTypeCode(), id);
sealApplyBillMapper.deleteById(id);
}
}
关键设计解读:
implements FlowBillService<OaBillTypeEnum>:声明本服务支持的单据类型,框架自动注册到工厂BpmProcessVariableUtils.buildBillVariables():自动从 VO 中提取标准流程变量(单据编号、事由、部门等)updateProcessStatus():当流程状态变更时,BPM 框架自动回调此方法,业务模块只需更新自己的状态字段- 审批通过联动:当
status == APPROVE时,自动设置用印状态为"待处理",业务逻辑与流程状态完美衔接
5.2 前端:印章申请业务表单
// oa/seal/sealapply/info/index.vue
const props = defineProps<{
id?: number | string;
isApproval?: boolean;
nodeKeyName?: string;
processInstance?: any;
// ... 其他审批相关 props
}>();
/**
* 审批前置校验 — BPM 审批详情页在用户点击"通过"前会调用此方法
* 业务表单可以在此做自定义校验和数据保存
*/
async function beforeApproval(): Promise<boolean> {
// 仅在特定审批节点(如"申请人归还印章")需要校验
if (props.isApproval && props.nodeKeyName === '申请人归还印章'
&& basicFormRef.value) {
const { valid } = await basicFormRef.value.validateForm();
if (!valid) return false;
const formValues = await basicFormRef.value.getFormValues();
const data = { ...formData.value, ...formValues };
await saveSealApplyBill(data); // 审批前自动保存最新数据
}
return true;
}
// 暴露方法给 BPM 审批详情页调用
defineExpose({ beforeApproval, loadData, handleSaveAndSubmit });
注意
beforeApproval的设计:不同的审批节点可能需要不同的操作。比如在"归还印章"节点,审批人需要确认归还信息并保存,而在其他节点则直接通过即可。这种节点级别的业务定制能力,是 RuoYi Office 区别于其他 BPM 系统的重要特征。
5.3 单据上流程结合应用:三标签页详情体验
理解了后端代码后,我们来看看用户实际使用时的体验。在 RuoYi Office 中,所有走流程的业务单据都遵循统一的三标签页详情体验:「单据信息 + 审批信息 + 流程图」。
以人事调动申请单为例(也适用于用印申请、用车申请、请假单等所有业务单据):
标签页一:单据信息 — 业务数据的完整呈现

▲ 单据信息页:顶部单据头(编号/申请人/日期/单位/部门)+ 业务表单主体,支持查看和编辑
单据信息页是业务表单与流程引擎的深度融合体现:
- 单据头区域:统一展示单据编号(
HR203-2026030900001)、申请人、申请日期、所属单位、所属部门,所有单据格式一致 - 业务表单区域:根据
formType动态加载——流程表单走form-create渲染,业务表单走自定义 Vue 组件 - 字段权限控制:不同审批节点看到的字段权限不同——某些字段对当前审批人「只读」,某些字段「可编辑」,某些字段「隐藏」
- 操作按钮:根据当前用户身份和流程状态,智能显示「撤回」「关闭」或「审批中」等操作
上图示例为人事调动申请单,包含「基本信息」(员工、工号、性别、手机号、职位、职务)和「调动信息」(异动类型、原部门/职位、变更为部门/职位),完整展现了复杂业务表单的呈现能力。
标签页二:审批信息 — 审批进度与记录一目了然

▲ 审批信息页:顶部审批进度条 + 底部审批记录表格,实时追踪流程走到哪一步
审批信息页提供了两种维度的审批可视化:
1. 审批进度条(Progress Bar)
以步骤条形式直观展示审批流程的当前进度:
- ✅ 已完成节点(绿色对勾):发起人 — 宇擎源码,已通过
- 🔵 当前节点(蓝色高亮数字):审批人 — bevan,审批中
- ⚪ 待处理节点(灰色数字):审批人 — hr小姐姐、人事,等待中
- 📍 结束节点:流程终点
2. 审批记录表格(Audit Trail)
| 字段 | 说明 | 企业价值 |
|---|---|---|
| 审批节点 | 对应流程设计器中的节点名称 | 定位审批环节 |
| 审批人 | 实际处理人 | 责任追溯 |
| 开始时间 / 结束时间 | 精确到秒 | 效率分析 |
| 审批状态 | 审批通过 / 审批中 / 审批拒绝 | 状态追踪 |
| 审批建议 | 审批意见和备注 | 沟通留痕 |
审计价值:每一条审批记录都是不可篡改的操作日志,满足企业内审和外部合规要求。
标签页三:流程图 — 实时可视化流程走向

▲ 流程图页:以 Simple 设计器视图实时展示流程走向,不同颜色区分节点状态
流程图标签页将流程的当前执行状态以可视化方式呈现,让用户一眼看清:
| 颜色 | 含义 | 示例 |
|---|---|---|
| 🟢 绿色 | 已完成节点 | 发起人(已通过)、审批人(已通过) |
| 🔵 蓝色边框 | 当前进行中节点 | 审批人(发起人连续部门负责人) |
| ⬜ 白色/灰色 | 待处理节点 | 审批人(指定岗位:人力资源)、结束 |
这个三标签页设计的精妙之处:
- 信息分层:单据数据、审批状态、流程全景三个维度分离,各取所需,不信息过载
- 统一体验:无论是 OA 用印、HRM 调动、CRM 合同审批,三标签页体验完全一致
- 动态渲染:业务表单区域根据
formType动态加载,框架无需为每种单据定制页面 - 权限精控:审批人在不同节点看到的表单字段权限由流程设计器配置,无需代码控制
// 核心代码:流程详情页根据 formType 决定渲染方式
if (processDefinition.value.formType === BpmModelFormType.NORMAL) {
// 流程表单 → form-create 动态渲染 JSON 配置
setConfAndFields2(detailForm, formConf, formFields, formVariables);
} else {
// 业务表单 → 动态加载自定义 Vue 组件
BusinessFormComponent.value = registerComponent(formCustomViewPath);
}
💡 这意味着什么? 开发一个新的业务单据(比如「合同审批」),你只需要:① 开发一个 Vue 表单组件;② 实现
FlowBillService接口;③ 在流程模型中配置表单路径。框架会自动把你的表单嵌入到统一的三标签页详情中,审批进度、审批记录、流程图全部自动生成。
5.4 流程变量:打通业务数据和流程引擎
BpmProcessVariableUtils 自动从业务对象中提取标准字段作为流程变量,供流程条件判断和任务列表展示使用:
| 流程变量 | 说明 | 来源 | 用途 |
|---|---|---|---|
BILL_CODE | 单据编号 | 业务 VO 的 billCode | 任务列表显示 |
CAUSE | 事由/说明 | 业务 VO 的 cause | 流程摘要 |
DEPT_NAME | 部门名称 | 业务 VO 的 deptName | 组织维度条件 |
COMPANY_NAME | 公司名称 | 业务 VO 的 companyName | 组织维度条件 |
| 自定义变量 | 业务专属变量 | 手动设置 | 条件分支判断 |
印章申请单额外添加了 PV_SEAL_USE_MODE(用印方式),可以在流程设计中作为条件判断——比如"外借用印需要增加部门领导审批"。
六、14 种审批人策略:覆盖企业级组织架构的所有场景
审批人分配是 BPM 系统的核心能力。RuoYi Office 使用策略模式,内置了 14 种审批人候选策略:
| 策略 | 适用场景 | 举例 |
|---|---|---|
| 按角色 | 固定角色审批 | "财务主管"角色审批报销单 |
| 部门成员 | 部门内审批 | 行政部全员可审批用车申请 |
| 部门负责人 | 直属上级审批 | 请假由部门负责人审批 |
| 多级部门负责人 | 逐级上报 | 金额超过 5 万,需 3 级领导审批 |
| 按岗位 | 岗位职责审批 | "出纳"岗位处理付款 |
| 指定用户 | 特定人员审批 | CEO 签字 |
| 发起人本人 | 自审/确认 | 印章归还需发起人确认 |
| 发起人部门负责人 | 常见上级审批 | 请假找直属领导 |
| 发起人多级部门负责人 | 逐级领导 | 按金额逐级审批 |
| 审批人自选 | 灵活指定 | 审批时动态选择下一个审批人 |
| 发起人自选 | 发起时指定 | 提交时选择具体审批人 |
| 用户组 | 分组审批 | "合同审核组"审批合同 |
| 表单用户字段 | 动态读取 | 表单中的"项目经理"字段值作为审批人 |
| 流程表达式 | 高级自定义 | 复杂规则用表达式计算 |

6.1 策略模式实现
public interface BpmTaskCandidateStrategy {
/** 策略类型 */
BpmTaskCandidateStrategyEnum getStrategy();
/** 计算候选人用户 ID 列表 */
Set<Long> calculateUsers(String param);
/** 是否支持退回 */
default boolean isReturnSupported() { return true; }
}
BpmTaskCandidateInvoker 作为调度器,在任务创建时根据节点配置调用对应策略:
public class BpmTaskCandidateInvoker {
private final Map<BpmTaskCandidateStrategyEnum, BpmTaskCandidateStrategy> strategyMap;
public Set<Long> calculateUsers(DelegateExecution execution) {
// 1. 从节点配置中获取候选人策略类型和参数
Integer strategy = ...; // 从 BPMN 扩展属性获取
String param = ...;
// 2. 调用对应策略计算候选人
return strategyMap.get(BpmTaskCandidateStrategyEnum.valueOf(strategy))
.calculateUsers(param);
}
}
新增审批人策略只需:实现
BpmTaskCandidateStrategy接口 → 标注@Component。无需修改任何核心代码,完美符合开闭原则。
七、任务管理:不只是"通过/驳回"
RuoYi Office 的任务管理远超简单的"通过/驳回",支持企业审批中的各种复杂场景:
| 操作 | 说明 | 企业场景 |
|---|---|---|
| 审批通过 | 同意当前任务 | 常规审批 |
| 审批驳回 | 拒绝当前任务 | 不符合要求,打回 |
| 退回 | 退回到指定节点 | 信息填写有误,退回修改 |
| 委派 | 委派给他人处理后回到自己 | 需要专业人员帮忙审核 |
| 转办 | 转给他人直接处理 | 审批人出差/离职 |
| 加签 | 增加审批人 | 金额较大,增加领导审批 |
| 减签 | 移除审批人 | 流程优化,减少不必要审批 |
| 抄送 | 发送给指定人查看 | 知会相关人员 |
| 撤回 | 审批人撤回已提交的审批 | 发现审批有误 |
任务状态流转:
NOT_START(-1) → WAIT(0) → RUNNING(1) → APPROVE(2) / REJECT(3) / RETURN(5) / CANCEL(4)
→ APPROVING(7) → APPROVE(2)
→ WITHDRAW(10)
SKIP(-2)

▲ 我的流程页面:展示所有发起的流程,支持查看详情、撤回、删除等操作
八、首页任务中心:让审批效率倍增
8.1 四维任务视角
RuoYi Office 的工作台首页集成了 WorkbenchTaskList 组件,提供四个维度的任务视角:
const tabs = computed(() => [
{ key: 'todo', label: '待办任务', count: statistics.value.todo },
{ key: 'myBill', label: '我的单据', count: statistics.value.myBill },
{ key: 'done', label: '已办任务', count: statistics.value.done },
{ key: 'copy', label: '抄送我的', count: statistics.value.copy },
]);
| 维度 | 说明 | 用户价值 |
|---|---|---|
| 待办任务 | 等待我审批的任务 | 快速处理审批,不遗漏 |
| 我的单据 | 我发起的所有流程 | 追踪审批进度 |
| 已办任务 | 我已审批的任务 | 回顾审批历史 |
| 抄送我的 | 抄送给我的流程 | 及时了解相关动态 |
![image.png](openwrite.cn