|
|
@@ -0,0 +1,1090 @@
|
|
|
+# 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. 业务数据状态不正确
|
|
|
+
|
|
|
+**解决**:
|
|
|
+
|
|
|
+1. 检查流程定义是否已发布
|
|
|
+2. 确认 `flowCode` 参数正确
|
|
|
+3. 确保业务数据状态为 `draft`
|
|
|
+
|
|
|
+### 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
|