1101 lines
34 KiB
Markdown
1101 lines
34 KiB
Markdown
# Warm-Flow 工作流框架开发指南
|
||
|
||
## 📋 目录
|
||
|
||
- [1. 工作流框架概述](#1-工作流框架概述)
|
||
- [2. 技术架构](#2-技术架构)
|
||
- [3. 核心组件](#3-核心组件)
|
||
- [4. 工作流开发流程](#4-工作流开发流程)
|
||
- [5. 请假工作流示例](#5-请假工作流示例)
|
||
- [6. 前端组件使用](#6-前端组件使用)
|
||
- [7. 工作流状态管理](#7-工作流状态管理)
|
||
- [8. 最佳实践](#8-最佳实践)
|
||
- [9. 常见问题](#9-常见问题)
|
||
|
||
---
|
||
|
||
## 1. 工作流框架概述
|
||
|
||
### 1.1 框架介绍
|
||
|
||
本项目集成了 **warm-flow** 工作流框架,这是由 dromara 社区开源的轻量级工作流引擎,具有以下特点:
|
||
|
||
- 🚀 **轻量级**: 核心代码简洁,易于理解和扩展
|
||
- 🔧 **可配置**: 支持通过配置文件动态开启/关闭工作流功能
|
||
- 🎯 **易集成**: 与 Spring Boot 无缝集成,支持 MyBatis-Plus
|
||
- 📊 **可视化**: 内置流程设计器和流程图展示
|
||
- 🔐 **权限控制**: 集成 Sa-Token 权限认证
|
||
- 📧 **消息通知**: 支持站内信、邮件、短信通知
|
||
|
||
### 1.2 配置开关
|
||
|
||
工作流功能通过 `@ConditionalOnEnable` 注解控制,配置如下:
|
||
|
||
```yaml
|
||
# application.yml
|
||
warm-flow:
|
||
enabled: true # 设置为 true 时,启用工作流功能
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 技术架构
|
||
|
||
### 2.1 技术栈
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 前端层 (Vue 3) │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ - approvalButton (审批按钮组件) │
|
||
│ - submitVerify (提交审批组件) │
|
||
│ - approvalRecord (审批记录组件) │
|
||
│ - flowChart (流程图组件) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 后端层 (Spring Boot) │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ - FlwTaskController (任务管理控制器) │
|
||
│ - FlwInstanceController (流程实例控制器) │
|
||
│ - FlwDefinitionController(流程定义控制器) │
|
||
│ - TestLeaveController (请假业务控制器) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 服务层 (Service) │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ - IFlwTaskService (任务服务) │
|
||
│ - IFlwInstanceService (实例服务) │
|
||
│ - IFlwDefinitionService (定义服务) │
|
||
│ - IFlwCommonService (通用服务) │
|
||
│ - ITestLeaveService (请假业务服务) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ Warm-Flow 核心引擎 │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ - FlowEngine (工作流引擎) │
|
||
│ - DefService (流程定义服务) │
|
||
│ - InsService (流程实例服务) │
|
||
│ - TaskService (任务服务) │
|
||
│ - NodeService (节点服务) │
|
||
│ - HisTaskService (历史任务服务) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 数据库表结构
|
||
|
||
warm-flow 使用以下核心表:
|
||
|
||
```sql
|
||
-- 流程定义表
|
||
flow_definition -- 存储流程定义信息
|
||
flow_node -- 存储流程节点信息
|
||
flow_skip -- 存储节点间跳转关系
|
||
|
||
-- 流程实例表
|
||
flow_instance -- 存储流程实例信息
|
||
flow_task -- 存储当前待办任务
|
||
flow_his_task -- 存储历史任务记录
|
||
|
||
-- 扩展表
|
||
flow_category -- 流程分类表
|
||
flow_copy -- 抄送表
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 核心组件
|
||
|
||
### 3.1 后端核心服务
|
||
|
||
#### 3.1.1 流程定义服务 (IFlwDefinitionService)
|
||
|
||
```java
|
||
public interface IFlwDefinitionService {
|
||
// 查询流程定义列表
|
||
TableDataInfo<FlowDefinitionVo> queryList(FlowDefinition flowDefinition, PageQuery pageQuery);
|
||
|
||
// 发布流程定义
|
||
boolean publish(Long id);
|
||
|
||
// 导入流程定义
|
||
boolean importJson(MultipartFile file, String category);
|
||
|
||
// 删除流程定义
|
||
boolean removeDef(List<Long> ids);
|
||
}
|
||
```
|
||
|
||
#### 3.1.2 流程实例服务 (IFlwInstanceService)
|
||
|
||
```java
|
||
public interface IFlwInstanceService {
|
||
// 查询运行中的流程实例
|
||
TableDataInfo<FlowInstanceVo> selectRunningInstanceList(FlowInstanceBo bo, PageQuery pageQuery);
|
||
|
||
// 查询已结束的流程实例
|
||
TableDataInfo<FlowInstanceVo> selectFinishInstanceList(FlowInstanceBo bo, PageQuery pageQuery);
|
||
|
||
// 撤销流程
|
||
boolean cancelProcessApply(FlowCancelBo bo);
|
||
|
||
// 作废流程
|
||
boolean invalid(FlowInvalidBo bo);
|
||
}
|
||
```
|
||
|
||
#### 3.1.3 任务服务 (IFlwTaskService)
|
||
|
||
```java
|
||
public interface IFlwTaskService {
|
||
// 启动工作流
|
||
StartProcessReturnDTO startWorkFlow(StartProcessBo startProcessBo);
|
||
|
||
// 办理任务
|
||
boolean completeTask(CompleteTaskBo completeTaskBo);
|
||
|
||
// 查询待办任务
|
||
TableDataInfo<FlowTaskVo> pageByTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery);
|
||
|
||
// 查询已办任务
|
||
TableDataInfo<FlowHisTaskVo> pageByTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery);
|
||
}
|
||
```
|
||
|
||
### 3.2 前端核心组件
|
||
|
||
#### 3.2.1 审批按钮组件 (approvalButton.vue)
|
||
|
||
```vue
|
||
<template>
|
||
<div style="display: flex; justify-content: space-between">
|
||
<div>
|
||
<el-button
|
||
v-if="submitButtonShow"
|
||
:loading="buttonLoading"
|
||
type="info"
|
||
@click="submitForm('draft')"
|
||
>
|
||
暂存
|
||
</el-button>
|
||
<el-button
|
||
v-if="submitButtonShow"
|
||
:loading="buttonLoading"
|
||
type="primary"
|
||
@click="submitForm('submit')"
|
||
>
|
||
提 交
|
||
</el-button>
|
||
<el-button
|
||
v-if="approvalButtonShow"
|
||
:loading="buttonLoading"
|
||
type="primary"
|
||
@click="approvalVerifyOpen"
|
||
>
|
||
审批
|
||
</el-button>
|
||
<el-button v-if="id && status !== 'draft'" type="primary" @click="handleApprovalRecord">
|
||
流程进度
|
||
</el-button>
|
||
</div>
|
||
<div>
|
||
<el-button style="float: right" @click="goBack()">返回</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
**功能特点:**
|
||
|
||
- 根据页面类型和流程状态动态显示按钮
|
||
- 支持暂存、提交、审批、查看流程进度等操作
|
||
- 自动处理按钮的显示/隐藏逻辑
|
||
|
||
#### 3.2.2 提交审批组件 (submitVerify.vue)
|
||
|
||
```vue
|
||
<!-- 核心功能 -->
|
||
<el-form-item label="消息提醒">
|
||
<el-checkbox-group v-model="form.messageType">
|
||
<el-checkbox value="1" disabled>站内信</el-checkbox>
|
||
<el-checkbox value="2">邮件</el-checkbox>
|
||
<el-checkbox value="3">短信</el-checkbox>
|
||
</el-checkbox-group>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="下一步审批人" v-if="nestNodeList?.length > 0">
|
||
<div v-for="item in nestNodeList" :key="item.nodeCode">
|
||
<span>【{{ item.nodeName }}】:</span>
|
||
<el-input placeholder="请选择审批人" readonly v-model="nickName[item.nodeCode]">
|
||
<template v-slot:append>
|
||
<el-button @click="choosePeople(item)" icon="search">选择</el-button>
|
||
</template>
|
||
</el-input>
|
||
</div>
|
||
</el-form-item>
|
||
```
|
||
|
||
**功能特点:**
|
||
|
||
- 支持消息通知配置(站内信、邮件、短信)
|
||
- 支持附件上传
|
||
- 支持抄送人员选择
|
||
- 支持下一步审批人选择
|
||
- 支持审批意见填写
|
||
- 支持多种操作:提交、委托、转办、加签、减签、终止、退回
|
||
|
||
#### 3.2.3 审批记录组件 (approvalRecord.vue)
|
||
|
||
```vue
|
||
<el-tabs v-model="tabActiveName">
|
||
<el-tab-pane label="流程图" name="image">
|
||
<flowChart :ins-id="insId" v-if="insId" />
|
||
</el-tab-pane>
|
||
<el-tab-pane label="审批信息" name="info">
|
||
<el-table :data="historyList" border>
|
||
<el-table-column prop="nodeName" label="任务名称" />
|
||
<el-table-column prop="approveName" label="办理人" />
|
||
<el-table-column prop="flowStatus" label="状态" />
|
||
<el-table-column prop="message" label="审批意见" />
|
||
<el-table-column prop="createTime" label="开始时间" />
|
||
<el-table-column prop="updateTime" label="结束时间" />
|
||
</el-table>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
```
|
||
|
||
**功能特点:**
|
||
|
||
- 流程图可视化展示
|
||
- 详细的审批记录表格
|
||
- 支持附件下载
|
||
- 审批时长统计
|
||
|
||
---
|
||
|
||
## 4. 工作流开发流程
|
||
|
||
### 4.1 后端开发步骤
|
||
|
||
#### 步骤 1: 创建业务实体
|
||
|
||
```java
|
||
// 1. 创建业务Bo类
|
||
@Data
|
||
@EqualsAndHashCode(callSuper = true)
|
||
public class TestLeaveBo extends BaseEntity {
|
||
private Long id;
|
||
private String leaveType;
|
||
private String startDate;
|
||
private String endDate;
|
||
private Integer leaveDays;
|
||
private String remark;
|
||
private String status;
|
||
}
|
||
|
||
// 2. 创建业务Vo类
|
||
@Data
|
||
@ExcelIgnoreUnannotated
|
||
public class TestLeaveVo {
|
||
private Long id;
|
||
private String leaveType;
|
||
private String startDate;
|
||
private String endDate;
|
||
private Integer leaveDays;
|
||
private String remark;
|
||
private String status;
|
||
|
||
// 基础字段(重要:避免MyBatis映射错误)
|
||
private Long createBy;
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date createTime;
|
||
private Long updateBy;
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date updateTime;
|
||
}
|
||
```
|
||
|
||
#### 步骤 2: 创建 Service 接口和实现
|
||
|
||
```java
|
||
// Service接口
|
||
public interface ITestLeaveService {
|
||
TestLeaveVo queryById(Long id);
|
||
TableDataInfo<TestLeaveVo> queryPageList(TestLeaveBo bo, PageQuery pageQuery);
|
||
List<TestLeaveVo> queryList(TestLeaveBo bo);
|
||
TestLeaveVo insertByBo(TestLeaveBo bo);
|
||
TestLeaveVo updateByBo(TestLeaveBo bo);
|
||
Boolean deleteWithValidByIds(List<Long> ids);
|
||
}
|
||
|
||
// Service实现类
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
public class TestLeaveServiceImpl implements ITestLeaveService {
|
||
private final TestLeaveMapper baseMapper;
|
||
|
||
@Override
|
||
public TestLeaveVo insertByBo(TestLeaveBo bo) {
|
||
TestLeave add = BeanUtil.toBean(bo, TestLeave.class);
|
||
// 设置默认状态为草稿
|
||
add.setStatus("draft");
|
||
validEntityBeforeSave(add);
|
||
boolean flag = baseMapper.insert(add) > 0;
|
||
if (flag) {
|
||
bo.setId(add.getId());
|
||
return BeanUtil.toBean(add, TestLeaveVo.class);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// 其他CRUD方法实现...
|
||
}
|
||
```
|
||
|
||
#### 步骤 3: 创建 Controller
|
||
|
||
```java
|
||
@ConditionalOnEnable // 工作流条件开启注解
|
||
@Validated
|
||
@RequiredArgsConstructor
|
||
@RestController
|
||
@RequestMapping("/workflow/leave")
|
||
public class TestLeaveController extends BaseController {
|
||
|
||
private final ITestLeaveService testLeaveService;
|
||
|
||
@SaCheckPermission("workflow:leave:list")
|
||
@GetMapping("/list")
|
||
public TableDataInfo<TestLeaveVo> list(TestLeaveBo bo, PageQuery pageQuery) {
|
||
return testLeaveService.queryPageList(bo, pageQuery);
|
||
}
|
||
|
||
@SaCheckPermission("workflow:leave:add")
|
||
@PostMapping()
|
||
public R<TestLeaveVo> add(@Validated(AddGroup.class) @RequestBody TestLeaveBo bo) {
|
||
return R.ok(testLeaveService.insertByBo(bo));
|
||
}
|
||
|
||
@SaCheckPermission("workflow:leave:edit")
|
||
@PutMapping()
|
||
public R<TestLeaveVo> edit(@Validated(EditGroup.class) @RequestBody TestLeaveBo bo) {
|
||
return R.ok(testLeaveService.updateByBo(bo));
|
||
}
|
||
|
||
// 其他CRUD接口...
|
||
}
|
||
```
|
||
|
||
### 4.2 前端开发步骤
|
||
|
||
#### 步骤 1: 创建 API 接口
|
||
|
||
```typescript
|
||
// api/workflow/leave/types.ts
|
||
export interface LeaveForm extends BaseEntity {
|
||
id?: string | number;
|
||
leaveType?: string;
|
||
startDate?: string;
|
||
endDate?: string;
|
||
leaveDays?: number;
|
||
remark?: string;
|
||
status?: string;
|
||
}
|
||
|
||
export interface LeaveVO extends BaseEntity {
|
||
id?: string | number;
|
||
leaveType?: string;
|
||
startDate?: string;
|
||
endDate?: string;
|
||
leaveDays?: number;
|
||
remark?: string;
|
||
status?: string;
|
||
}
|
||
|
||
// api/workflow/leave/index.ts
|
||
export const listLeave = (query: LeaveQuery): AxiosPromise<LeaveVO[]> => {
|
||
return request({
|
||
url: '/workflow/leave/list',
|
||
method: 'get',
|
||
params: query,
|
||
});
|
||
};
|
||
|
||
export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
|
||
return request({
|
||
url: '/workflow/leave',
|
||
method: 'post',
|
||
data: data,
|
||
});
|
||
};
|
||
```
|
||
|
||
#### 步骤 2: 创建列表页面
|
||
|
||
```vue
|
||
<!-- views/workflow/leave/index.vue -->
|
||
<template>
|
||
<div class="p-2">
|
||
<!-- 搜索表单 -->
|
||
<div v-show="showSearch" class="search">
|
||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||
<el-form-item label="请假天数" prop="startLeaveDays">
|
||
<el-input v-model="queryParams.startLeaveDays" placeholder="请输入请假天数" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<!-- 数据表格 -->
|
||
<el-card shadow="never">
|
||
<template #header>
|
||
<el-row :gutter="10" class="mb8">
|
||
<el-col :span="1.5">
|
||
<el-button
|
||
v-hasPermi="['workflow:leave:add']"
|
||
type="primary"
|
||
plain
|
||
icon="Plus"
|
||
@click="handleAdd"
|
||
>
|
||
新增
|
||
</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
</template>
|
||
|
||
<el-table v-loading="loading" border :data="leaveList">
|
||
<el-table-column type="selection" width="55" align="center" />
|
||
<el-table-column label="请假类型" align="center">
|
||
<template #default="scope">
|
||
<el-tag>{{ getLeaveTypeLabel(scope.row.leaveType) }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="请假天数" align="center" prop="leaveDays" />
|
||
<el-table-column label="流程状态" align="center">
|
||
<template #default="scope">
|
||
<dict-tag :options="wf_business_status" :value="scope.row.status" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" align="center">
|
||
<template #default="scope">
|
||
<!-- 根据状态显示不同操作按钮 -->
|
||
<el-button
|
||
v-if="['draft', 'cancel', 'back'].includes(scope.row.status)"
|
||
type="primary"
|
||
size="small"
|
||
icon="Edit"
|
||
@click="handleUpdate(scope.row)"
|
||
>
|
||
修改
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
icon="View"
|
||
@click="handleView(scope.row)"
|
||
>
|
||
查看
|
||
</el-button>
|
||
<el-button
|
||
v-if="scope.row.status === 'waiting'"
|
||
size="small"
|
||
type="primary"
|
||
icon="Notification"
|
||
@click="handleCancel(scope.row.id)"
|
||
>
|
||
撤销
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { listLeave, delLeave } from '@/api/workflow/leave';
|
||
import { cancelProcessApply } from '@/api/workflow/instance';
|
||
|
||
// 组件逻辑...
|
||
</script>
|
||
```
|
||
|
||
#### 步骤 3: 创建编辑页面
|
||
|
||
```vue
|
||
<!-- views/workflow/leave/leaveEdit.vue -->
|
||
<template>
|
||
<div class="p-2">
|
||
<!-- 审批按钮 -->
|
||
<el-card shadow="never">
|
||
<approvalButton
|
||
@submitForm="submitForm"
|
||
@approvalVerifyOpen="approvalVerifyOpen"
|
||
@handleApprovalRecord="handleApprovalRecord"
|
||
:buttonLoading="buttonLoading"
|
||
:id="form.id"
|
||
:status="form.status"
|
||
:pageType="routeParams.type"
|
||
/>
|
||
</el-card>
|
||
|
||
<!-- 表单内容 -->
|
||
<el-card shadow="never">
|
||
<el-form ref="leaveFormRef" :model="form" :rules="rules" label-width="80px">
|
||
<el-form-item label="请假类型" prop="leaveType">
|
||
<el-select v-model="form.leaveType" placeholder="请选择请假类型">
|
||
<el-option
|
||
v-for="item in options"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="请假时间" required>
|
||
<el-date-picker
|
||
v-model="leaveTime"
|
||
type="daterange"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
@change="changeLeaveTime()"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="请假天数" prop="leaveDays">
|
||
<el-input v-model="form.leaveDays" disabled type="number" />
|
||
</el-form-item>
|
||
|
||
<el-form-item label="请假原因" prop="remark">
|
||
<el-input v-model="form.remark" type="textarea" :rows="3" />
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 工作流组件 -->
|
||
<submitVerify
|
||
ref="submitVerifyRef"
|
||
:task-variables="taskVariables"
|
||
@submit-callback="submitCallback"
|
||
/>
|
||
<approvalRecord ref="approvalRecordRef" />
|
||
|
||
<!-- 流程选择对话框 -->
|
||
<el-dialog v-model="dialogVisible.visible" title="流程定义" width="500">
|
||
<el-select v-model="flowCode" placeholder="Select">
|
||
<el-option
|
||
v-for="item in flowCodeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
<template #footer>
|
||
<el-button @click="handleClose">取消</el-button>
|
||
<el-button type="primary" @click="submitFlow()">确认</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { addLeave, getLeave, updateLeave } from '@/api/workflow/leave';
|
||
import { startWorkFlow } from '@/api/workflow/task';
|
||
import SubmitVerify from '@/components/Process/submitVerify.vue';
|
||
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
|
||
import ApprovalButton from '@/components/Process/approvalButton.vue';
|
||
|
||
// 流程定义选项
|
||
const flowCodeOptions = [
|
||
{ value: 'leave1', label: '请假申请-普通' },
|
||
{ value: 'leave2', label: '请假申请-排他网关' },
|
||
{ value: 'leave3', label: '请假申请-并行网关' },
|
||
{ value: 'leave4', label: '请假申请-会签' },
|
||
{ value: 'leave5', label: '请假申请-并行会签网关' },
|
||
{ value: 'leave6', label: '请假申请-排他并行会签' },
|
||
];
|
||
|
||
// 提交表单
|
||
const submitForm = async (status: string) => {
|
||
// 验证表单
|
||
await leaveFormRef.value?.validate();
|
||
|
||
// 保存业务数据
|
||
let res;
|
||
if (form.value.id) {
|
||
res = await updateLeave(form.value);
|
||
} else {
|
||
res = await addLeave(form.value);
|
||
}
|
||
|
||
// 如果是暂存,直接返回
|
||
if (status === 'draft') {
|
||
proxy?.$modal.msgSuccess('暂存成功');
|
||
return;
|
||
}
|
||
|
||
// 如果是提交,启动工作流
|
||
if (!flowCode.value) {
|
||
dialogVisible.visible = true;
|
||
return;
|
||
}
|
||
|
||
await handleStartWorkFlow(res.data);
|
||
};
|
||
|
||
// 启动工作流
|
||
const handleStartWorkFlow = async (data: LeaveForm) => {
|
||
const submitFormData = {
|
||
flowCode: flowCode.value,
|
||
businessId: data.id,
|
||
variables: {
|
||
leaveDays: data.leaveDays,
|
||
userList: ['1', '3', '4'], // 会签用户列表
|
||
},
|
||
};
|
||
|
||
const resp = await startWorkFlow(submitFormData);
|
||
submitVerifyRef.value.openDialog(resp.data.taskId);
|
||
};
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 请假工作流示例
|
||
|
||
### 5.1 工作流模式
|
||
|
||
系统提供 6 种不同的工作流模式:
|
||
|
||
| 流程编码 | 流程名称 | 特点说明 |
|
||
| -------- | --------------------- | ------------------------ |
|
||
| leave1 | 请假申请-普通 | 简单的线性审批流程 |
|
||
| leave2 | 请假申请-排他网关 | 根据条件选择不同分支 |
|
||
| leave3 | 请假申请-并行网关 | 多个分支并行执行 |
|
||
| leave4 | 请假申请-会签 | 多人会签,所有人都需审批 |
|
||
| leave5 | 请假申请-并行会签网关 | 并行网关+会签组合 |
|
||
| leave6 | 请假申请-排他并行会签 | 排他网关+并行会签组合 |
|
||
|
||
### 5.2 流程变量
|
||
|
||
```typescript
|
||
// 工作流变量配置
|
||
const taskVariables = {
|
||
// leave2/6 使用的流程变量(排他网关条件判断)
|
||
leaveDays: data.leaveDays,
|
||
|
||
// leave4/5 使用的流程变量(会签用户列表)
|
||
userList: ['1', '3', '4'],
|
||
};
|
||
```
|
||
|
||
### 5.3 状态流转
|
||
|
||
```
|
||
draft(草稿) → waiting(待审批) → complete(已完成)
|
||
↓ ↓
|
||
cancel(已取消) back(已退回)
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 前端组件使用
|
||
|
||
### 6.1 工作流页面模板
|
||
|
||
```vue
|
||
<template>
|
||
<div class="p-2">
|
||
<!-- 1. 审批按钮组件 -->
|
||
<el-card shadow="never">
|
||
<approvalButton
|
||
@submitForm="submitForm"
|
||
@approvalVerifyOpen="approvalVerifyOpen"
|
||
@handleApprovalRecord="handleApprovalRecord"
|
||
:buttonLoading="buttonLoading"
|
||
:id="form.id"
|
||
:status="form.status"
|
||
:pageType="routeParams.type"
|
||
/>
|
||
</el-card>
|
||
|
||
<!-- 2. 业务表单 -->
|
||
<el-card shadow="never">
|
||
<el-form ref="formRef" :model="form" :rules="rules">
|
||
<!-- 业务字段 -->
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 3. 工作流组件 -->
|
||
<submitVerify
|
||
ref="submitVerifyRef"
|
||
:task-variables="taskVariables"
|
||
@submit-callback="submitCallback"
|
||
/>
|
||
<approvalRecord ref="approvalRecordRef" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import SubmitVerify from '@/components/Process/submitVerify.vue';
|
||
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
|
||
import ApprovalButton from '@/components/Process/approvalButton.vue';
|
||
|
||
// 必需的ref引用
|
||
const submitVerifyRef = ref();
|
||
const approvalRecordRef = ref();
|
||
|
||
// 必需的事件处理
|
||
const submitForm = (status) => {
|
||
/* 提交表单逻辑 */
|
||
};
|
||
const approvalVerifyOpen = () => {
|
||
/* 打开审批窗口 */
|
||
};
|
||
const handleApprovalRecord = () => {
|
||
/* 查看审批记录 */
|
||
};
|
||
const submitCallback = () => {
|
||
/* 提交回调 */
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### 6.2 路由跳转工具
|
||
|
||
```typescript
|
||
// 使用工作流通用路由跳转
|
||
import workflowCommon from '@/api/workflow/workflowCommon';
|
||
|
||
const handleOpen = (row: FlowTaskVO) => {
|
||
const routerJumpVo = {
|
||
businessId: row.businessId,
|
||
taskId: row.id,
|
||
type: 'approval', // add/update/view/approval
|
||
formCustom: row.formCustom,
|
||
formPath: row.formPath,
|
||
};
|
||
workflowCommon.routerJump(routerJumpVo, proxy);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 工作流状态管理
|
||
|
||
### 7.1 业务状态
|
||
|
||
| 状态值 | 状态名称 | 说明 | 可用操作 |
|
||
| --------- | -------- | ------------ | -------------------- |
|
||
| draft | 草稿 | 新建但未提交 | 编辑、删除、提交 |
|
||
| waiting | 待审批 | 流程进行中 | 查看、撤销 |
|
||
| complete | 已完成 | 流程正常结束 | 查看 |
|
||
| cancel | 已取消 | 申请人撤销 | 编辑、删除、重新提交 |
|
||
| back | 已退回 | 审批人退回 | 编辑、删除、重新提交 |
|
||
| invalid | 已作废 | 管理员作废 | 查看 |
|
||
| terminate | 已终止 | 流程异常终止 | 查看 |
|
||
|
||
### 7.2 任务状态
|
||
|
||
| 状态值 | 状态名称 | 说明 |
|
||
| -------- | -------- | ---------------- |
|
||
| waiting | 待办 | 等待当前用户处理 |
|
||
| complete | 已办 | 当前用户已处理 |
|
||
| reject | 已拒绝 | 当前用户拒绝 |
|
||
| delegate | 已委托 | 当前用户委托他人 |
|
||
| transfer | 已转办 | 当前用户转办他人 |
|
||
|
||
### 7.3 状态检查工具
|
||
|
||
```typescript
|
||
// 前端状态检查
|
||
const canEdit = computed(() => {
|
||
return ['draft', 'cancel', 'back'].includes(form.value.status);
|
||
});
|
||
|
||
const canCancel = computed(() => {
|
||
return form.value.status === 'waiting';
|
||
});
|
||
|
||
const showApprovalButton = computed(() => {
|
||
return routeParams.value.type === 'approval' && form.value.status === 'waiting';
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 最佳实践
|
||
|
||
### 8.1 后端开发最佳实践
|
||
|
||
#### 1. VO 类基础字段规范
|
||
|
||
```java
|
||
// 所有工作流业务VO类必须包含统一基础字段
|
||
public class TestLeaveVo {
|
||
// 业务字段
|
||
private Long id;
|
||
private String leaveType;
|
||
// ...其他业务字段
|
||
|
||
// 基础字段(必须包含,避免MyBatis映射错误)
|
||
private Long createBy;
|
||
private Date createTime;
|
||
private Long updateBy;
|
||
private Date updateTime;
|
||
}
|
||
```
|
||
|
||
#### 2. 权限注解规范
|
||
|
||
```java
|
||
// 所有工作流接口必须添加权限检查
|
||
@SaCheckPermission("workflow:leave:list")
|
||
@GetMapping("/list")
|
||
public TableDataInfo<TestLeaveVo> list(TestLeaveBo bo, PageQuery pageQuery) {
|
||
return testLeaveService.queryPageList(bo, pageQuery);
|
||
}
|
||
```
|
||
|
||
#### 3. 条件开启注解
|
||
|
||
```java
|
||
// 所有工作流相关类必须添加条件开启注解
|
||
@ConditionalOnEnable
|
||
@Service
|
||
public class TestLeaveServiceImpl implements ITestLeaveService {
|
||
// 实现逻辑
|
||
}
|
||
```
|
||
|
||
#### 4. 事务管理
|
||
|
||
```java
|
||
// 涉及业务数据和工作流数据的操作需要事务保证
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public TestLeaveVo insertByBo(TestLeaveBo bo) {
|
||
// 1. 保存业务数据
|
||
TestLeave entity = BeanUtil.toBean(bo, TestLeave.class);
|
||
entity.setStatus("draft");
|
||
baseMapper.insert(entity);
|
||
|
||
// 2. 如果需要同时操作工作流数据,确保在同一事务中
|
||
return BeanUtil.toBean(entity, TestLeaveVo.class);
|
||
}
|
||
```
|
||
|
||
### 8.2 前端开发最佳实践
|
||
|
||
#### 1. 组件引入规范
|
||
|
||
```vue
|
||
<script setup>
|
||
// 工作流组件统一引入
|
||
import SubmitVerify from '@/components/Process/submitVerify.vue';
|
||
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
|
||
import ApprovalButton from '@/components/Process/approvalButton.vue';
|
||
|
||
// API接口引入
|
||
import { addLeave, getLeave, updateLeave } from '@/api/workflow/leave';
|
||
import { startWorkFlow } from '@/api/workflow/task';
|
||
</script>
|
||
```
|
||
|
||
#### 2. 表单验证规范
|
||
|
||
```javascript
|
||
// 工作流表单必须包含的验证规则
|
||
const rules = {
|
||
// 业务字段验证
|
||
leaveType: [{ required: true, message: '请假类型不能为空', trigger: 'blur' }],
|
||
leaveDays: [{ required: true, message: '请假天数不能为空', trigger: 'blur' }],
|
||
|
||
// 如果有时间范围,需要自定义验证
|
||
leaveTime: [{ required: true, message: '请假时间不能为空', trigger: 'blur' }],
|
||
};
|
||
```
|
||
|
||
#### 3. 错误处理规范
|
||
|
||
```javascript
|
||
// 提交表单错误处理
|
||
const submitForm = async (status: string) => {
|
||
try {
|
||
// 表单验证
|
||
await leaveFormRef.value?.validate();
|
||
|
||
// 业务数据保存
|
||
buttonLoading.value = true;
|
||
const res = form.value.id ? await updateLeave(form.value) : await addLeave(form.value);
|
||
|
||
// 工作流处理
|
||
if (status === 'submit') {
|
||
await handleStartWorkFlow(res.data);
|
||
}
|
||
} catch (error) {
|
||
console.error('提交失败:', error);
|
||
proxy?.$modal.msgError('操作失败,请重试');
|
||
} finally {
|
||
buttonLoading.value = false;
|
||
}
|
||
};
|
||
```
|
||
|
||
### 8.3 性能优化
|
||
|
||
#### 1. 组件懒加载
|
||
|
||
```javascript
|
||
// 工作流组件按需加载
|
||
const SubmitVerify = defineAsyncComponent(() => import('@/components/Process/submitVerify.vue'));
|
||
const ApprovalRecord = defineAsyncComponent(() =>
|
||
import('@/components/Process/approvalRecord.vue'),
|
||
);
|
||
```
|
||
|
||
#### 2. 接口优化
|
||
|
||
```javascript
|
||
// 避免重复请求,使用缓存
|
||
const { data: taskInfo, loading } = useCachedRequest(() => getTask(taskId.value), {
|
||
cacheKey: `task_${taskId.value}`,
|
||
ttl: 5 * 60 * 1000, // 5分钟缓存
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 常见问题
|
||
|
||
### 9.1 MyBatis 映射错误
|
||
|
||
**问题**: `Could not set property 'createBy'`
|
||
|
||
**原因**: VO 类缺少基础字段,但 SQL 查询包含这些字段
|
||
|
||
**解决**: 确保所有 VO 类包含统一的基础字段
|
||
|
||
```java
|
||
// 正确的VO类定义
|
||
public class TestLeaveVo {
|
||
// 业务字段
|
||
private Long id;
|
||
private String leaveType;
|
||
|
||
// 必须包含的基础字段
|
||
private Long createBy;
|
||
private Date createTime;
|
||
private Long updateBy;
|
||
private Date updateTime;
|
||
}
|
||
```
|
||
|
||
### 9.2 工作流组件不显示
|
||
|
||
**问题**: 审批按钮或其他工作流组件不显示
|
||
|
||
**原因**:
|
||
|
||
1. 没有添加 `@ConditionalOnEnable` 注解
|
||
2. 配置文件中 `warm-flow.enabled=false`
|
||
3. 组件引入路径错误
|
||
|
||
**解决**:
|
||
|
||
```yaml
|
||
# 确保配置正确
|
||
warm-flow:
|
||
enabled: true
|
||
```
|
||
|
||
```java
|
||
// 确保添加条件注解
|
||
@ConditionalOnEnable
|
||
@RestController
|
||
public class TestLeaveController {
|
||
// 控制器逻辑
|
||
}
|
||
```
|
||
|
||
### 9.3 权限问题
|
||
|
||
**问题**: 接口访问被拒绝
|
||
|
||
**原因**: 缺少权限注解或权限配置不正确
|
||
|
||
**解决**:
|
||
|
||
1. 添加权限注解:`@SaCheckPermission("workflow:leave:list")`
|
||
2. 在菜单管理中配置对应的权限标识
|
||
3. 为用户角色分配相应权限
|
||
|
||
### 9.4 流程启动失败
|
||
|
||
**问题**: 启动工作流时报错
|
||
|
||
**原因**:
|
||
|
||
1. 流程定义未发布
|
||
2. 流程编码不存在
|
||
3. 业务数据状态不正确
|
||
4. **退回后残留多个 `flow_task`**(原审批节点未清、历史 draft 申请人任务),重新提交时取到错误 taskId,移动端统一提示「启动工作流失败」
|
||
|
||
**解决**:
|
||
|
||
1. 检查流程定义是否已发布
|
||
2. 确认 `flowCode` 参数正确
|
||
3. 确保业务数据状态为 `draft` / `back` / `cancel`(可重新提交)
|
||
4. **退回清理(已实现)**:`FlwTaskServiceImpl.backProcess` 在 `skip(REJECT)` 后调用 `cleanResidualTasksAfterBack`,只保留退回目标节点最新有效任务;重新提交时 `healResidualTasksOnRestart` 自愈历史脏数据
|
||
|
||
**排查 SQL 示例**(同一 `instance_id` 不应长期存在多条无关待办):
|
||
|
||
```sql
|
||
SELECT id, node_name, flow_status, create_time
|
||
FROM flow_task
|
||
WHERE instance_id = ? AND IFNULL(del_flag, '0') = '0';
|
||
```
|
||
|
||
### 9.5 日期格式问题
|
||
|
||
**问题**: 日期选择器数据格式不匹配
|
||
|
||
**原因**: 前端日期格式与后端不一致
|
||
|
||
**解决**:
|
||
|
||
```vue
|
||
<!-- 统一使用标准格式 -->
|
||
<el-date-picker
|
||
v-model="leaveTime"
|
||
type="daterange"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||
/>
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 总结
|
||
|
||
warm-flow 工作流框架为本项目提供了完整的工作流解决方案,具有以下优势:
|
||
|
||
1. **轻量级**: 核心简洁,易于理解和维护
|
||
2. **可扩展**: 支持自定义业务逻辑和权限控制
|
||
3. **可视化**: 内置流程设计器和流程图展示
|
||
4. **完整性**: 提供完整的前后端组件和示例
|
||
|
||
通过请假工作流示例,开发者可以快速掌握工作流开发的完整流程,并应用到其他业务场景中。
|
||
|
||
---
|
||
|
||
**文档维护**: 本文档随项目发展持续更新
|
||
**最后更新**: 2025-01-09
|
||
**版本**: v1.0
|