Files
hzdyjs/docs/开发文档/warm-flow工作流框架开发指南.md
T
2026-10-20 11:50:53 +08:00

34 KiB
Raw Blame History

Warm-Flow 工作流框架开发指南

📋 目录


1. 工作流框架概述

1.1 框架介绍

本项目集成了 warm-flow 工作流框架,这是由 dromara 社区开源的轻量级工作流引擎,具有以下特点:

  • 🚀 轻量级: 核心代码简洁,易于理解和扩展
  • 🔧 可配置: 支持通过配置文件动态开启/关闭工作流功能
  • 🎯 易集成: 与 Spring Boot 无缝集成,支持 MyBatis-Plus
  • 📊 可视化: 内置流程设计器和流程图展示
  • 🔐 权限控制: 集成 Sa-Token 权限认证
  • 📧 消息通知: 支持站内信、邮件、短信通知

1.2 配置开关

工作流功能通过 @ConditionalOnEnable 注解控制,配置如下:

# 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 使用以下核心表:

-- 流程定义表
flow_definition      -- 存储流程定义信息
flow_node           -- 存储流程节点信息
flow_skip           -- 存储节点间跳转关系

-- 流程实例表
flow_instance       -- 存储流程实例信息
flow_task           -- 存储当前待办任务
flow_his_task       -- 存储历史任务记录

-- 扩展表
flow_category       -- 流程分类表
flow_copy           -- 抄送表

3. 核心组件

3.1 后端核心服务

3.1.1 流程定义服务 (IFlwDefinitionService)

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)

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)

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)

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

<!-- 核心功能 -->
<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)

<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: 创建业务实体

// 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 接口和实现

// 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

@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 接口

// 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: 创建列表页面

<!-- 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: 创建编辑页面

<!-- 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 流程变量

// 工作流变量配置
const taskVariables = {
    // leave2/6 使用的流程变量(排他网关条件判断)
    leaveDays: data.leaveDays,

    // leave4/5 使用的流程变量(会签用户列表)
    userList: ['1', '3', '4'],
};

5.3 状态流转

draft(草稿) → waiting(待审批) → complete(已完成)
     ↓              ↓
cancel(已取消)   back(已退回)

6. 前端组件使用

6.1 工作流页面模板

<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 路由跳转工具

// 使用工作流通用路由跳转
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 状态检查工具

// 前端状态检查
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 类基础字段规范

// 所有工作流业务VO类必须包含统一基础字段
public class TestLeaveVo {
    // 业务字段
    private Long id;
    private String leaveType;
    // ...其他业务字段

    // 基础字段(必须包含,避免MyBatis映射错误)
    private Long createBy;
    private Date createTime;
    private Long updateBy;
    private Date updateTime;
}

2. 权限注解规范

// 所有工作流接口必须添加权限检查
@SaCheckPermission("workflow:leave:list")
@GetMapping("/list")
public TableDataInfo<TestLeaveVo> list(TestLeaveBo bo, PageQuery pageQuery) {
    return testLeaveService.queryPageList(bo, pageQuery);
}

3. 条件开启注解

// 所有工作流相关类必须添加条件开启注解
@ConditionalOnEnable
@Service
public class TestLeaveServiceImpl implements ITestLeaveService {
    // 实现逻辑
}

4. 事务管理

// 涉及业务数据和工作流数据的操作需要事务保证
@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. 组件引入规范

<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. 表单验证规范

// 工作流表单必须包含的验证规则
const rules = {
    // 业务字段验证
    leaveType: [{ required: true, message: '请假类型不能为空', trigger: 'blur' }],
    leaveDays: [{ required: true, message: '请假天数不能为空', trigger: 'blur' }],

    // 如果有时间范围,需要自定义验证
    leaveTime: [{ required: true, message: '请假时间不能为空', trigger: 'blur' }],
};

3. 错误处理规范

// 提交表单错误处理
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. 组件懒加载

// 工作流组件按需加载
const SubmitVerify = defineAsyncComponent(() => import('@/components/Process/submitVerify.vue'));
const ApprovalRecord = defineAsyncComponent(() =>
    import('@/components/Process/approvalRecord.vue'),
);

2. 接口优化

// 避免重复请求,使用缓存
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 类包含统一的基础字段

// 正确的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. 组件引入路径错误

解决:

# 确保配置正确
warm-flow:
    enabled: true
// 确保添加条件注解
@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 不应长期存在多条无关待办):

SELECT id, node_name, flow_status, create_time
FROM flow_task
WHERE instance_id = ? AND IFNULL(del_flag, '0') = '0';

9.5 日期格式问题

问题: 日期选择器数据格式不匹配

原因: 前端日期格式与后端不一致

解决:

<!-- 统一使用标准格式 -->
<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