微搭低代码MBA 培训管理系统实战 41——审批中心
前情回顾与本节目标
在上一节中,我们完成了人事管理模块,实现了:
- 员工档案
- 入职管理
- 离职管理
但随着系统扩展,离职、退费、开票、采购、请假等各种业务都需要审批,如果每个业务单独写一套审批逻辑,最终会导致 if/else 无限增长、页面爆炸、流程混乱、难以维护。因此,我们正式进入轻量级流程引擎的设计,核心思想是:业务表只负责业务数据,流程系统只负责流程流转,一套流程内核支撑所有审批业务。
本节核心目标
本节我们将实现:
本节核心目标:
- 四表流程模型:业务表 + 流程实例表 + 任务表 + 日志表
- 统一流程内核:createProcessInstance / completeTask / writeLog / getMyTasks
- 统一审批中心:我的待办 / 我已处理 / 我发起的 / 审批详情 / 流程时间线
- 离职审批完整案例:离职申请 → 部门经理审批 → HR审批 → 流程结束
第一步:数据建模
原来的审批模式我们将审批状态直接存储在业务表中,随着业务扩展(退费、采购、开票等),代码会充斥大量 if/else 判断,导致审批系统与业务严重耦合。企业级系统必须做"流程抽象",将业务与流程彻底解耦。
我们采用四表模型:
业务表(真实业务)
│
│ 1:1
↓
流程实例表(流程主线)
│
│ 1:n
↓
任务表(待办中心)
│
│ 1:n
↓
日志表(审计轨迹)
设计原则:
- 业务表只存业务数据(金额、原因、附件等),不存审批人、审批历史
- 流程实例表是流程主线,连接业务、任务、日志,记录流程当前状态
- 任务表是待办中心,记录"谁现在要处理什么"
- 日志表是审计轨迹,记录全过程操作,包含业务快照保证历史不可变
1.1 离职申请表(MBA_Resignations)
| 字段名称 | 字段标识 | 字段类型 | 说明 |
|---|---|---|---|
| 记录ID | _id | 文本 | 主键 |
| 离职单号 | leave_no | 文本 | 如:RS-20260501-001 |
| 关联员工 | rel_employee_id | 关联关系 | 关联员工表 |
| 离职类型 | leave_type | 枚举 | 1-主动离职、2-协商离职、3-辞退、4-试用期不合格 |
| 拟离职日期 | plan_date | 日期 | 计划离职日期 |
| 交接人 | rel_handover_id | 关联关系 | 工作交接人 |
| 离职原因 | reason | 多行文本 | 离职原因说明 |
| 业务状态 | biz_status | 枚举 | 1-草稿、2-审批中、3-已通过、4-已驳回、5-已取消 |
| 流程实例ID | process_instance_id | 文本 | 关联流程实例 |
| 创建人 | created_by | 关联关系 | 发起人 |
| 创建时间 | created_at | 日期时间 | 自动生成 |
| 更新时间 | updated_at | 日期时间 | 自动更新 |
1.2 流程实例表(MBA_ProcessInstances)
| 字段名称 | 字段标识 | 字段类型 | 说明 |
|---|---|---|---|
| 实例ID | _id | 文本 | 主键 |
| 流程KEY | process_key | 文本 | 如:resignation_process、refund_process |
| 流程名称 | process_name | 文本 | 如:离职审批流程 |
| 业务表名 | biz_table | 文本 | 如:MBA_Resignations |
| 业务记录ID | biz_record_id | 文本 | 关联业务表记录 |
| 业务编号 | biz_no | 文本 | 如:RS-20260501-001 |
| 审批标题 | title | 文本 | 审批标题(如:张三的离职申请) |
| 发起人 | starter_id | 关联关系 | 关联用户表 |
| 当前节点 | current_node | 文本 | 如:dept_manager、hr_review |
| 当前任务ID | current_task_id | 文本 | 当前待办任务ID |
| 流程状态 | status | 枚举 | running-运行中、approved-已通过、rejected-已驳回、cancel-已取消 |
| 开始时间 | start_time | 日期时间 | 流程发起时间 |
| 结束时间 | end_time | 日期时间 | 流程完成时间 |
| 创建时间 | created_at | 日期时间 | 自动生成 |
1.3 任务表(MBA_ProcessTasks)
| 字段名称 | 字段标识 | 字段类型 | 说明 |
|---|---|---|---|
| 任务ID | _id | 文本 | 主键 |
| 流程实例ID | instance_id | 关联关系 | 关联流程实例 |
| 节点KEY | node_key | 文本 | 如:dept_manager、hr_review |
| 节点名称 | node_name | 文本 | 如:部门经理审批、HR审核 |
| 审批人 | assignee_id | 关联关系 | 关联用户表 |
| 任务类型 | task_type | 枚举 | approve-审批、copy-抄送、read-阅知 |
| 任务状态 | status | 枚举 | pending-待处理、done-已完成、reject-已驳回 |
| 审批动作 | action | 文本 | approve-通过、reject-驳回、transfer-转交 |
| 审批意见 | comment | 多行文本 | 审批意见 |
| 接收时间 | received_at | 日期时间 | 任务接收时间 |
| 完成时间 | completed_at | 日期时间 | 任务完成时间 |
| 创建时间 | created_at | 日期时间 | 自动生成 |
1.4 日志表(MBA_ProcessLogs)
| 字段名称 | 字段标识 | 字段类型 | 说明 |
|---|---|---|---|
| 日志ID | _id | 文本 | 主键 |
| 流程实例ID | instance_id | 关联关系 | 关联流程实例 |
| 任务ID | task_id | 关联关系 | 来源任务 |
| 操作人 | operator_id | 关联关系 | 关联用户表 |
| 操作动作 | action | 文本 | submit-提交、approve-通过、reject-驳回、transfer-转交、cancel-取消 |
| 来源节点 | from_node | 文本 | 操作前节点 |
| 去向节点 | to_node | 文本 | 操作后节点 |
| 审批意见 | comment | 多行文本 | 审批意见 |
| 创建时间 | created_at | 日期时间 | 操作时间 |
第二步:统一流程内核
抽象3个核心方法,所有审批业务共用,在全局方法里创建:
2.1 createProcessInstance - 创建流程实例
统一创建流程实例、第一个任务、写入日志。
export default async function createProcessInstance(params) {
const {
processKey, processName, bizTable, bizRecordId, bizNo, title,
starterId, firstNode, firstNodeName, firstAssigneeId
} = params;
const now = Date.now();
// 1. 创建流程实例
const instanceRes = await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessInstances',
methodName: 'wedaCreateV2',
params: {
data: {
process_key: processKey, process_name: processName,
biz_table: bizTable, biz_record_id: bizRecordId, biz_no: bizNo,
title: title, starter_id: { _id: starterId },
current_node: firstNode, status: 'running',
start_time: now, created_at: now
}
}
});
const instanceId = instanceRes.id;
// 2. 创建第一个任务
const taskRes = await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessTasks',
methodName: 'wedaCreateV2',
params: {
data: {
instance_id: { _id: instanceId }, node_key: firstNode,
node_name: firstNodeName, assignee_id: { _id: firstAssigneeId },
task_type: 'approve', status: 'pending',
received_at: now, created_at: now
}
}
});
// 3. 更新流程实例的当前任务ID
await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessInstances',
methodName: 'wedaUpdateV2',
params: {
filter: { where: { _id: { $eq: instanceId } } },
data: { current_task_id: taskRes.id, updated_at: now }
}
});
// 4. 写日志
await app.common.writeLog({
instanceId, operatorId: starterId, action: 'submit',
fromNode: '', toNode: firstNode, comment: '提交申请', snapshot: {}
});
return { instanceId, taskId: taskRes.id };
}
2.2 completeTask - 完成任务流转
完成当前任务、创建下一任务、更新流程状态、写日志。
export default async function completeTask(params) {
const { taskId, action, comment, nextNode, nextNodeName, nextAssigneeId, snapshot } = params;
const now = Date.now();
// 1. 查询任务信息
const taskRes = await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessTasks',
methodName: 'wedaGetItemV2',
params: { filter: { where: { _id: { $eq: taskId } } }, select: { $master: true, instance_id: true } }
});
if (!taskRes) throw new Error('任务不存在');
const task = taskRes;
const instanceId = task.instance_id?._id || task.instance_id;
const currentNode = task.node_key;
const operatorId = $w.app.dataset.state.currentUser._id;
// 2. 更新任务状态
await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessTasks',
methodName: 'wedaUpdateV2',
params: {
filter: { where: { _id: { $eq: taskId } } },
data: { status: action === 'reject' ? 'reject' : 'done', action, comment: comment || '', completed_at: now, updated_at: now }
}
});
// 3. 处理流程流转
let toNode = '', newTaskId = null;
if (action === 'approve') {
if (nextNode) {
const newTaskRes = await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessTasks',
methodName: 'wedaCreateV2',
params: {
data: {
instance_id: { _id: instanceId }, node_key: nextNode,
node_name: nextNodeName, assignee_id: { _id: nextAssigneeId },
task_type: 'approve', status: 'pending', received_at: now, created_at: now
}
}
});
newTaskId = newTaskRes.id; toNode = nextNode;
await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessInstances',
methodName: 'wedaUpdateV2',
params: {
filter: { where: { _id: { $eq: instanceId } } },
data: { current_node: nextNode, current_task_id: newTaskId, updated_at: now }
}
});
} else {
toNode = 'end';
await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessInstances',
methodName: 'wedaUpdateV2',
params: {
filter: { where: { _id: { $eq: instanceId } } },
data: { status: 'approved', current_node: 'end', current_task_id: '', end_time: now, updated_at: now }
}
});
}
} else if (action === 'reject') {
toNode = 'rejected';
await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessInstances',
methodName: 'wedaUpdateV2',
params: {
filter: { where: { _id: { $eq: instanceId } } },
data: { status: 'rejected', current_node: 'rejected', current_task_id: '', end_time: now, updated_at: now }
}
});
}
// 4. 写日志
await app.common.writeLog({ instanceId, taskId, operatorId, action, fromNode: currentNode, toNode, comment: comment || '', snapshot: snapshot || {} });
return { success: true, action, newTaskId, isEnd: action === 'reject' || !nextNode };
}
2.3 writeLog - 写入审计日志
统一记录所有操作,包含业务快照保证历史不可变。
export default async function writeLog(params) {
const { instanceId, taskId, operatorId, action, fromNode, toNode, comment, snapshot } = params;
await $w.cloud.callDataSource({
dataSourceName: 'MBA_ProcessLogs',
methodName: 'wedaCreateV2',
params: {
data: {
instance_id: { _id: instanceId },
task_id: taskId ? { _id: taskId } : undefined,
operator_id: { _id: operatorId }, action,
from_node: fromNode || '', to_node: toNode || '',
comment: comment || '', snapshot: snapshot || {}, created_at: Date.now()
}
}
});
}
第三歩:离职申请模块
3.1 创建页面
切换到布局设计,点击新建布局
选择左侧导航布局,将布局重命名为OA布局
点击创建页面图标,输入离职申请,选择OA布局
切换到布局管理,选择OA布局,添加平级菜单,添加"离职申请"菜单
3.2 离职列表搭建
在OA布局的内容插槽下添加布局组件,修改标题为离职申请
在布局内容里添加数据表格组件
选择离职申请数据模型,勾选所有场景
配置筛选条件
3.2 提交离职申请
点击提交后,系统依次执行:创建业务记录 → 创建流程实例 → 创建待办任务 → 写日志 → 更新员工状态为"离职中"。
export default async function submitResignation({ event, data }) {
try {
$w.utils.showLoading({ title: '提交中...' });
const formData = $w.form2.value;
const currentUserId = $w.app.dataset.state.currentUser._id||$w.query1.data._id;
console.log(formData)
// 验证必填
if (!formData.rel_employee_id || !formData.leave_type || !formData.plan_date || !formData.rel_handover_id || !formData.reason) {
$w.utils.hideLoading();
return $w.utils.showToast({ title: '请填写完整信息', icon: 'error' });
}
// 1. 根据当前用户ID查询员工信息
const starterRes = await $w.cloud.callDataSource({
dataSourceName: 'MBA_Employees',
methodName: 'wedaGetRecordsV2',
params: { filter: { where: { rel_user_id: { $eq: currentUserId } } },
select: { $master: true } }
});
const starterEmployee = starterRes.records?.[0];
if (!starterEmployee) {
$w.utils.hideLoading();
return $w.utils.showToast({ title: '未找到您的员工信息', icon: 'error' });
}
// 2. 创建业务记录
const bizRes = await $w.cloud.callDataSource({
dataSourceName: 'MBA_Resignations',
methodName: 'wedaCreateV2',
params: {
data: {
rel_employee_id: { _id: formData.rel_employee_id },
leave_type: formData.leave_type, plan_date: formData.plan_date,
rel_handover_id: { _id: formData.rel_handover_id }, reason: formData.reason,
biz_status: '2', created_by: { _id: starterEmployee._id },
}
}
});
// 3. 查询获取自动生成的业务编号
const bizDetailRes = await $w.cloud.callDataSource({
dataSourceName: 'MBA_Resignations',
methodName: 'wedaGetItemV2',
params: { filter: { where: { _id: { $eq: bizRes.id } } },
select: { $master: true } }
});
const leaveNo = bizDetailRes.leave_no;
// 4. 获取离职员工信息
const empRes = await $w.cloud.callDataSource({
dataSourceName: 'MBA_Employees',
methodName: 'wedaGetItemV2',
params: { filter: { where: { _id: { $eq: formData.rel_employee_id } } },
select: { $master: true } }
});
const empName = empRes?.name || '未知';
// 5. 创建流程实例(调用公共方法)
const processRes = await app.common.createProcessInstance({
processKey: 'resignation_process', processName: '离职审批流程',
bizTable: 'MBA_Resignations', bizRecordId: bizRes.id, bizNo: leaveNo,
title: `${empName}的离职申请`, starterId: starterEmployee._id,
firstNode: 'dept_manager', firstNodeName: '部门经理审批',
firstAssigneeId: empRes?.rel_supervisor_id?._id || empRes?.rel_supervisor_id
});
// 6. 更新业务记录关联流程实例
await $w.cloud.callDataSource({
dataSourceName: 'MBA_Resignations',
methodName: 'wedaUpdateV2',
params: { filter: { where: { _id: { $eq: bizRes.id } } },
data: { process_instance_id:{_id:processRes.instanceId} } }
});
// 7. 更新员工状态为"离职中"
await $w.cloud.callDataSource({
dataSourceName: 'MBA_Employees',
methodName: 'wedaUpdateV2',
params: { filter: { where: { _id: { $eq: formData.rel_employee_id } } },
data: { status: '2' } }
});
// 8. 写日志(调用公共方法)
await app.common.writeLog({
instanceId: processRes.instanceId, taskId: processRes.taskId,
operatorId: starterEmployee._id, action: 'submit',
fromNode: '', toNode: 'dept_manager', comment: '提交离职申请', snapshot: {}
});
$w.utils.hideLoading();
$w.utils.showToast({ title: '提交成功', icon: 'success' });
$w.modal2.close({});
$w.table1.refresh()
} catch (error) {
console.error('提交离职申请失败:', error);
$w.utils.hideLoading();
$w.utils.showToast({ title: '提交失败,请重试', icon: 'error' });
}
}
修改表单的提交方法,改为调用我们的自定义方法
第四章:审批中心
4.1 创建页面
点击创建页面的图标,输入审批中心,布局选择OA布局
切换到页面布局,添加审批中心的菜单
4.2 搭建页面布局
在OA布局下添加布局组件,修改标题为审批中心
4.2 待办列表
添加数据表格组件,数据模型选择任务表
将操作列的按钮改为办理
4.3 办理审批
添加弹窗组件,修改弹窗标题,改为流程办理
里边添加表单容器,场景选择查看,数据模型选择离职申请表
添加单行输入组件,标题改为审批意见
底部按钮改为"通过"和"驳回"。
给操作列的办理按钮配置点击事件,打开弹窗,传入所在行数据
给表单容器绑定数据标识,绑定弹窗入参的业务标识字段
最终效果
员工在离职申请模块提交申请
审批人在审批中心处理离职申请
总结
本节完成了轻量级流程引擎的核心设计,通过四表模型(业务表、流程实例表、任务表、日志表)实现业务与流程解耦。统一流程内核(createProcessInstance、completeTask、writeLog、getMyTasks)让所有审批共享一套流程能力,新增审批类型无需修改流程内核,实现真正的平台化。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)