十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Antdv工作流内核:可嵌入、可调试、钉钉审批深度集成

Antdv工作流内核:可嵌入、可调试、钉钉审批深度集成 简介这是一套面向前端开发者与企业级应用架构师的Vue工作流组件解决方案聚焦业务流程自动化与低门槛流程编排需求适用于审批系统、OA平台、任务调度等场景。资源包含115个文件主体为48个Vue组件文件含流程设计器、节点配置、执行面板等核心模块、21个JS逻辑脚本涵盖流程解析、状态管理、钉钉审批对接等辅以CSS/HTML/JSON配置及图标资源整体包体仅705KB轻量易集成。目前已有1770人学习下载适合中高级Vue开发者深入理解工作流引擎设计、Ant Design Vue企业级UI实践及前后端协同建模。读者可直接复用在线拖拽式流程设计器、完整支持退回/会签/并行/自由流等复杂语义的操作逻辑以及已封装的钉钉审批触发与服务任务调用能力快速构建可落地的工作流管理界面。1. 这不是又一个流程图渲染器它是一套可嵌入、可调试、可对接钉钉审批的 Antdv 工作流内核你见过太多“拖拽画流程图”的 Vue 组件——节点能拖连线能连双击弹窗改个 label导出个 JSON 就算交付。但真把它塞进生产系统里马上卡在三处流程定义后无法回溯版本、审批人字段和钉钉组织架构不联动、并行网关跑起来后任务状态错乱。本项目跳出了 UI 层面的“可视化”直接在ant-design-vue基础上重构了工作流的状态机引擎与服务任务调度层。它把WorkflowDefinition拆成nodes含 type、assigneeRule、serviceKey、edges含 conditionExpr、gateways含 joinStrategy三个可序列化对象所有逻辑不依赖外部 BPMN 解析器而是用 Vue 的响应式系统驱动状态流转。适合中后台系统需要快速集成审批流、且后端已用 Spring Boot 实现 Camunda 或 Flowable 的团队——前端不再只是“画图工具”而是流程执行上下文的实时镜像。如果你正为“流程改了十次前端每次都要重写 handleApprove 逻辑”头疼这个组件就是用来终结这种重复劳动的。2. 从零启动Vue 3 Antdv 4.x 环境下接入工作流组件的完整链路2.1 为什么选 Ant Design Vue 而非 Element Plus 或 Naive UIAntdv 在企业级表单与复杂布局上的稳定性是硬指标。本项目中流程设计器的节点面板采用a-tabsa-drawer组合右侧属性编辑区使用a-form-item嵌套a-select绑定钉钉部门树、a-switch控制自由流开关、a-textarea写 EL 表达式这些组件的v-model双向绑定与change事件触发时机高度可控。对比 Element Plus 的el-select在多级联动时频繁触发visible-change导致表单校验误判或 Naive UI 的n-form-item对disabled状态响应延迟Antdv 的a-form在:model更新后能精确同步validateFields的校验队列。更重要的是Antdv 的a-tree-select支持异步加载子节点这直接支撑了“钉钉审批模式”中动态拉取部门-员工列表的功能——我们不需要预加载全量组织架构而是在用户点击审批人下拉框时才调用getDingTalkDeptUsers(deptId)接口。提示项目默认使用 Antdv 4.3.0该版本修复了a-drawer在placementright时v-if切换导致的宽度计算错误此问题在 4.2.x 中会导致流程节点属性面板闪退。2.2 初始化工作流容器注册全局组件与注入核心 store项目未使用 Pinia 或 Vuex而是基于 Vue 3 的provide/inject构建轻量级状态流。在main.js中需显式注册两个关键组件// main.js import { createApp } from vue import App from ./App.vue import Antd from ant-design-vue import ant-design-vue/dist/reset.css // 引入工作流核心模块 import { WorkflowProvider } from /components/workflow/WorkflowProvider import { WorkflowDesigner } from /components/workflow/WorkflowDesigner import { WorkflowExecutor } from /components/workflow/WorkflowExecutor const app createApp(App) app.use(Antd) // 注册为全局组件避免在每个页面重复 import app.component(WorkflowDesigner, WorkflowDesigner) app.component(WorkflowExecutor, WorkflowExecutor) // 提供工作流上下文包含当前流程定义、运行实例、服务任务处理器 app.provide(workflowContext, new WorkflowProvider()) app.mount(#app)WorkflowProvider类封装了三类核心能力definitionStore管理nodes/edges/gateways的增删改查所有操作均触发onDefinitionChange事件instanceStore维护当前流程实例的currentNodeId、taskList、history支持rollbackTo(nodeId)回退serviceTaskHandler注册服务任务处理器例如serviceKey: sendSms对应handleSendSms(payload)方法。2.2.1 定义服务任务处理器让“调用 API”变成声明式配置服务任务不是简单发个 axios 请求而是需处理超时、重试、失败降级。本项目约定每个node.type serviceTask的节点必须配置serviceKey字段其值映射到serviceTaskHandler中注册的函数// src/components/workflow/serviceTasks.js export const registerServiceTasks (handler) { // 发送短信服务支持钉钉机器人通知 fallback handler.register(sendSms, async (payload) { try { const res await axios.post(/api/sms/send, { phone: payload.receiver, content: payload.message, templateId: payload.templateId }, { timeout: 5000 }) return { success: true, data: res.data } } catch (err) { // 主通道失败自动切钉钉机器人 await axios.post(https://oapi.dingtalk.com/robot/send, { msgtype: text, text: { content: [SMS FAIL] ${err.message} } }, { headers: { Content-Type: application/json }, params: { access_token: import.meta.env.VUE_APP_DINGTALK_TOKEN } }) return { success: false, error: err.message } } }) // 审批结果同步至 ERP 系统 handler.register(syncToErp, async (payload) { // 此处实现 ERP 接口调用逻辑 }) }在WorkflowProvider初始化时调用registerServiceTasks(this.serviceTaskHandler)即可。当流程执行到serviceTask节点时引擎自动提取node.serviceKey并传入payload含上游节点输出、流程变量variables无需在业务组件中写if (node.serviceKey xxx) { ... }。2.3 流程设计器核心拖拽节点与动态连线的 DOM 层控制逻辑设计器不是基于 SVG 或 Canvas而是纯 CSS Grid position: absolute实现。每个节点是一个div其top/left由v-model绑定到node.position!-- WorkflowNode.vue -- template div classworkflow-node :style{ top: ${node.position.y}px, left: ${node.position.x}px, width: 120px, height: 60px } mousedownstartDrag div classnode-header{{ node.label }}/div div classnode-body a-tag v-ifnode.type userTask colorblue人工/a-tag a-tag v-else-ifnode.type serviceTask colorgreen服务/a-tag a-tag v-else-ifnode.type exclusiveGateway colororange分支/a-tag /div /div /template script setup const props defineProps([node]) const emit defineEmits([update:position]) const startDrag (e) { const startX e.clientX const startY e.clientY const startLeft props.node.position.x const startTop props.node.position.y const moveHandler (moveEvent) { const deltaX moveEvent.clientX - startX const deltaY moveEvent.clientY - startY emit(update:position, { x: startLeft deltaX, y: startTop deltaY }) } const upHandler () { window.removeEventListener(mousemove, moveHandler) window.removeEventListener(mouseup, upHandler) } window.addEventListener(mousemove, moveHandler) window.addEventListener(mouseup, upHandler) } /script连线则通过a-tooltip包裹a-button实现连接点点击后触发createEdge(fromNodeId, toNodeId)// 创建连线逻辑 const createEdge (fromId, toId) { // 校验是否允许连接不能自连、不能重复边、目标不能是开始节点 if (fromId toId || edges.some(e e.from fromId e.to toId)) return // 计算两点中心坐标生成贝塞尔曲线路径 const fromNode nodes.find(n n.id fromId) const toNode nodes.find(n n.id toId) const path generateBezierPath( fromNode.position.x 60, fromNode.position.y 30, toNode.position.x, toNode.position.y 30 ) edges.push({ id: edge-${Date.now()}, from: fromId, to: toId, path: path, // SVG path d 属性字符串 conditionExpr: // 默认空双击可编辑 }) }generateBezierPath使用三次贝塞尔曲线公式确保连线避开节点区域且在缩放时保持平滑——这是区别于“直线连线”的关键细节直接影响流程图的专业感。3. 钉钉审批深度集成组织架构同步、审批人自动填充与状态回写3.1 获取钉钉部门树并构建选择器避免全量加载的懒加载策略项目不预加载全部部门而是采用a-tree-select的loadData属性实现按需加载!-- DingTalkUserSelector.vue -- template a-tree-select v-model:valueselectedUserId :tree-datatreeData :load-dataloadTreeData placeholder请选择审批人 stylewidth: 200px / /template script setup import { ref, reactive } from vue import { getDingTalkDepts, getDingTalkDeptUsers } from /api/dingtalk const treeData ref([]) const selectedUserId ref(null) // 第一层根部门企业ID const loadTreeData async (treeNode) { if (treeNode.isLeaf) return // 叶子节点不加载子节点 const deptId treeNode.dataRef?.id || root const depts await getDingTalkDepts(deptId) // 调用钉钉开放平台接口 /topapi/v2/dept/listsub const children depts.map(d ({ title: d.name, key: d.dept_id, isLeaf: d.sub_dept_count 0, dataRef: d })) treeNode.children children treeData.value [...treeData.value] } // 当用户展开部门节点时才请求该部门下的员工 const getDingTalkDeptUsers async (deptId) { const res await axios.get(/api/dingtalk/users, { params: { deptId } }) return res.data.map(u ({ value: u.userid, label: u.name, avatar: u.avatar })) } /scriptgetDingTalkDepts接口返回钉钉部门列表getDingTalkDeptUsers返回指定部门员工。关键点在于a-tree-select的loadData函数只在用户点击三角图标展开时触发而非初始化时全量拉取这对拥有数千部门的企业至关重要。3.2 审批人规则引擎支持表达式、角色映射与多级会签节点的assigneeRule字段不是简单字符串而是结构化对象{ type: expression, expr: variables.applicantDept IT ? [zhangsan, lisi] : [wangwu] }或{ type: role, roleId: approver_group_001 }引擎在流程启动时解析expr调用eval()已做沙箱隔离或查询角色服务获取实际用户 ID 列表。对于会签节点assigneeRule还支持quorum: 2需 2 人同意和timeout: 86400超时秒数// 会签逻辑伪代码 const handleMultiSign async (node, instance) { const assignees resolveAssigneeRule(node.assigneeRule, instance.variables) const taskIds [] for (const userId of assignees) { const taskId await createTask({ nodeId: node.id, instanceId: instance.id, assignee: userId, status: pending }) taskIds.push(taskId) } // 监听所有任务完成事件聚合结果 await waitForQuorum(taskIds, node.quorum || assignees.length) }3.3 状态回写与消息通知钉钉机器人 自定义 Webhook流程状态变更如“已通过”、“被驳回”需同步至钉钉。项目提供DingTalkNotifier类封装两种通道// src/utils/dingtalk-notifier.js export class DingTalkNotifier { static async notifyStatusChange(instanceId, status, message) { // 通道1钉钉群机器人 await axios.post(https://oapi.dingtalk.com/robot/send, { msgtype: actionCard, actionCard: { title: 流程【${instanceId}】状态更新, text: 状态${status}\n 说明${message}\n [查看详情](https://your-app.com/workflow/${instanceId}), btnOrientation: 0, singleBtns: [{ title: 前往处理, actionURL: https://your-app.com/workflow/${instanceId} }] } }, { headers: { Content-Type: application/json }, params: { access_token: import.meta.env.VUE_APP_DINGTALK_ROBOT_TOKEN } }) // 通道2企业自有 Webhook如飞书、企微 if (import.meta.env.VUE_APP_WEBHOOK_URL) { await axios.post(import.meta.env.VUE_APP_WEBHOOK_URL, { event: workflow_status_change, instanceId, status, timestamp: Date.now() }) } } }在WorkflowExecutor的completeTask方法末尾调用DingTalkNotifier.notifyStatusChange(...)确保状态变更 1 秒内触达。4. 流程执行与调试并行网关、退回逻辑与服务任务超时控制4.1 并行网关Parallel Gateway的并发控制与汇聚策略并行网关不是简单“同时发起多个任务”而是需解决三个问题任务并发数限制、子流程异常隔离、汇聚时的变量合并。本项目采用Promise.allSettled 变量快照机制// ParallelGatewayExecutor.js export const executeParallelGateway async (gatewayNode, instance) { const outgoingEdges edges.filter(e e.from gatewayNode.id) const promises [] // 为每条出口边创建独立子流程实例 for (const edge of outgoingEdges) { const childInstanceId child_${instance.id}_${edge.id}_${Date.now()} // 快照当前变量避免子流程修改影响主流程 const childVariables { ...instance.variables } promises.push( startSubProcess({ definitionId: edge.targetDefinitionId, parentId: instance.id, variables: childVariables, initiator: instance.initiator }).catch(err ({ status: rejected, reason: err.message })) ) } // 等待所有子流程完成成功或失败 const results await Promise.allSettled(promises) // 汇聚策略只要有一个成功就继续全部失败则标记网关失败 const hasSuccess results.some(r r.status fulfilled) if (!hasSuccess) { throw new Error(Parallel gateway failed: ${results.map(r r.reason).join(; )}) } // 合并变量取最后一个子流程的 variables可配置为 merge 或 override const lastResult results.find(r r.status fulfilled) instance.variables { ...instance.variables, ...lastResult.value.variables } }注意startSubProcess是封装的子流程启动方法它会创建新WorkflowInstance并监听其completed事件。子流程失败不会中断主流程而是记录日志并继续执行其他分支。4.2 退回Rollback功能的精准节点定位与状态清理退回不是简单instance.currentNodeId targetNodeId而是需清理中间状态// WorkflowInstance.js rollbackTo(targetNodeId) { // 1. 清理 targetNodeId 之后的所有待办任务 this.taskList this.taskList.filter(t t.status ! pending || this.getNodePath(t.nodeId).includes(targetNodeId) ) // 2. 重置 currentNodeId this.currentNodeId targetNodeId // 3. 清空 targetNodeId 之后的 history 记录 const targetIndex this.history.findIndex(h h.nodeId targetNodeId) if (targetIndex -1) { this.history this.history.slice(0, targetIndex 1) } // 4. 触发事件通知 UI 重绘 this.emit(rollback, { from: this.currentNodeId, to: targetNodeId }) }getNodePath方法通过 DFS 遍历edges找出从startNode到targetNodeId的所有可能路径确保退回后任务列表只保留路径上的节点避免出现“已退回却仍有后续任务待处理”的脏状态。4.3 服务任务超时熔断基于 AbortController 的主动终止服务任务若长时间无响应必须主动终止而非无限等待。项目在serviceTaskHandler执行时注入AbortSignal// serviceTasks.js 中的 sendSms 示例增强版 handler.register(sendSms, async (payload, signal) { try { const controller new AbortController() // 设置 8 秒超时signal 由上层传入如流程引擎配置的 timeout 字段 controller.timeout payload.timeout || 8000 controller.signal.addEventListener(abort, () { console.warn(SMS service task aborted due to timeout) }) const res await axios.post(/api/sms/send, payload, { signal: controller.signal, timeout: payload.timeout || 8000 }) return { success: true, data: res.data } } catch (err) { if (axios.isCancel(err)) { return { success: false, error: TIMEOUT } } throw err } })流程引擎在调用serviceTaskHandler.execute(key, payload)时会将AbortSignal作为第二个参数传入确保超时控制权在流程层而非服务层。5. 生产环境验证技巧用 Chrome DevTools 快速定位流程卡点5.1 在 Vue Devtools 中监控 workflowContext 的实时状态打开 Chrome Devtools → Vue 面板 → Components 标签页找到WorkflowDesigner组件在右侧 Props 中展开workflowContext。重点观察definitionStore.nodes.length确认节点数量与设计器显示一致instanceStore.taskList检查status字段是否为pending/completed/failedinstanceStore.variables验证流程变量是否按预期更新如applicantDept是否从表单提交后写入。若发现taskList为空但流程应有任务说明assigneeRule解析失败需检查resolveAssigneeRule函数日志。5.2 利用 Network 面板过滤关键请求识别钉钉集成瓶颈在 Network 面板设置过滤器fetch/XHR→ 输入dingtalk查看getDingTalkDepts和getDingTalkDeptUsers请求耗时fetch/XHR→ 输入workflow关注/api/workflow/start启动、/api/task/complete完成任务、/api/task/rollback退回三个接口的响应时间与 payload特别留意status为500的请求其 response 中通常包含error.stack直接定位服务任务中的 JS 错误。5.3 流程日志导出为标准格式便于后端联合排查组件内置日志导出功能点击设计器右上角「导出日志」按钮生成符合 ISO 8601 时间戳的 CSVtimestamp,nodeId,action,operator,status,duration_ms 2024-06-15T14:22:31.123Z,start,started,admin,success,0 2024-06-15T14:22:35.456Z,task_001,assigned,zhangsan,pending,0 2024-06-15T14:23:12.789Z,task_001,completed,zhangsan,success,37333 2024-06-15T14:23:15.012Z,gateway_001,executed,system,success,223该 CSV 可直接导入 Excel 进行耗时分析或用awk {print $5,$6} log.csv | sort -n快速找出最慢环节。本文还有配套的精品资源点击获取
返回列表