你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流

0 阅读9分钟

一、审批流做到一半,产品经理说"要像钉钉那样"

钉钉的审批界面人人都见过:点个"+"加一级审批,拉个条件分支,再把单子抄送给谁——不用看文档,会用手机就会配。

轮到给自己的系统做审批流,路通常只有两条,都不舒服:

  • 上画布类流程设计器(LogicFlow、BPMN 那种):功能是强,但对普通用户太"工程师"了——拖节点、连线、调坐标,产品经理看完演示说:用户不会用;
  • 硬编码:审批链写死在代码里,加一个审批节点就是一次发版。

还有第三条路:把钉钉那套交互做成开源 Vue3 组件,谁都能装。这不是设想——mldong-flow-designer-dingtalk,npm 装包即用。本篇不讲原理,直接实录:从 npm install 到画出第一条审批流,再到把它喂给一个开源工作流引擎真跑一遍。

二、为什么钉钉不用流程图画布

先想一个问题:审批流的真实形态是什么?

九成场景,它就是"一条主干 + 偶尔的分支 + 少量会签抄送"。审批人头脑里没有"节点和边"的概念,只有三个朴素的疑问:谁先审、谁后审、什么情况走另一条路。

画布把流程建模工具直接交给了用户;钉钉的思路是把流程折叠成一棵:主干一条线走到底,分支收进"条件分支"容器里,连线、坐标这些概念全部消失。所以钉钉的审批设计器,业务人员零学习成本。

我把这套交互实现成了 Vue3 组件,发在 npm 上,两个包:

  • mldong-flow-designer-dingtalk(钉钉精简包):只有钉钉树形设计器。dependencies 为空——没有 LogicFlow、没有 antd/element,peerDependencies 只要求 vue >= 3.4
  • mldong-flow-designer-plus(双模式包):mode 属性一键切换 canvas 画布 / 钉钉树形两种模式,两套视图吃同一份 JSON。

不是 demo 玩具:mldong 系框架的前端(vben5-wf)生产环境在用,开源工作流引擎 jeeflow 的官方前端 jeeflow-ui,流程设计页用的也是它。

三、10 分钟实录:装包到画出第一条审批流

第 1 步:起一个 Vue3 工程,装包

npm create vite@latest my-flow -- --template vue
cd my-flow
npm install mldong-flow-designer-dingtalk

我的真机(Windows + Node 22):整个工程连同 Vite 一起装完 32 个包,5 秒。设计器自身零依赖,不会往你项目里拖一棵前端 UI 框架的依赖树。

第 2 步:注册组件,三行

import { createApp } from 'vue'
import FlowDesigner from 'mldong-flow-designer-dingtalk'
import 'mldong-flow-designer-dingtalk/lib/style.css'
import App from './App.vue'

createApp(App).use(FlowDesigner).mount('#app')

第 3 步:喂一份 JSON

设计器是个受控组件,v-model:value 进、@on-save 出,都是同一份 JSON。我先喂一条报销审批流:

<script setup>
import { ref } from 'vue'
import FlowDesigner from 'mldong-flow-designer-dingtalk'

const graphData = ref({
  name: 'expense',
  displayName: '报销审批',
  type: 'approval',
  nodes: [
    { id: 'start', type: 'snaker:start', x: 100, y: 200, properties: { width: 50, height: 50 }, text: { value: '开始' } },
    { id: 'apply', type: 'snaker:task', x: 200, y: 200, properties: { form: 'apply-form', assignee: 'applicant', taskType: 0, performType: 0 }, text: { value: '发起申请' } },
    { id: 'leader', type: 'snaker:task', x: 320, y: 200, properties: { form: 'expense-form', assignee: 'leader', taskType: 0, performType: 0 }, text: { value: '部门经理审批' } },
    { id: 'decision1', type: 'snaker:decision', x: 450, y: 200, properties: { width: 80, height: 50 }, text: { value: '金额>1000?' } },
    { id: 'manager', type: 'snaker:task', x: 580, y: 120, properties: { form: 'expense-form', assignee: 'manager' }, text: { value: '总经理审批' } },
    { id: 'end', type: 'snaker:end', x: 700, y: 200, properties: { width: 50, height: 50 }, text: { value: '结束' } }
  ],
  edges: [
    { id: 'e0', sourceNodeId: 'start', targetNodeId: 'apply', properties: {} },
    { id: 'e1', sourceNodeId: 'apply', targetNodeId: 'leader', properties: {} },
    { id: 'e2', sourceNodeId: 'leader', targetNodeId: 'decision1', properties: {} },
    { id: 'e3', sourceNodeId: 'decision1', targetNodeId: 'manager', properties: { expr: 'amount > 1000' }, text: { value: '金额>1000' } },
    { id: 'e4', sourceNodeId: 'decision1', targetNodeId: 'end', properties: { expr: 'amount <= 1000' }, text: { value: '否则' } },
    { id: 'e5', sourceNodeId: 'manager', targetNodeId: 'end', properties: {} }
  ]
})

function onSave(data) {
  // 保存:data 就是这份 JSON(外加一个 mode 标记),POST 给后端即可
}
</script>

<template>
  <FlowDesigner v-model:value="graphData" mode="dingtalk" @on-save="onSave" />
</template>

打开页面,钉钉同款界面就出来了——绿色"开始"胶囊、带"✓ 审批人"标签的节点卡、条件分支双列容器:

本机实录:钉钉风格树形设计器

注意这份 JSON 里的 xy 不是给树形视图用的——它们是画布坐标,钉钉视图只是同一份数据的另一种呈现(组件内部在图结构和树结构之间做双向转换)。存储格式始终是图 JSON,这是后面"直接喂引擎"的伏笔。

加节点:点"+",不拖拽

树形设计器里没有拖拽。节点之间的"+"点开,是钉钉同款的节点选择器:审批人、自定义节点、子流程、条件分支、并行分支:

点"+"加节点:钉钉同款交互

配节点:审批人怎么定、会不会签

点任意节点卡,右侧弹出属性抽屉。表单绑定、参与人、候选用户/候选用户组、任务类型、会签类型、会签完成条件、操作按钮(同意/拒绝/退回)都在这里配:

节点属性抽屉:审批人、会签、候选用户

顺带一提:canvas 画布模式

如果你的系统还要兼容传统"自由画布"流程,换成双模式包 mldong-flow-designer-plusmode="canvas"mode="dingtalk" 一字切换,两套视图消费同一份 JSON。钉钉模式的对外 API(FDDesignerAPI)是刻意按 LogicFlow 实例的命名对齐实现的——updateTextsetPropertiesgetProperties 这些方法两边都有,二次开发代码基本零修改就能双模式复用。

不想装包?官方在线演示站可以直接玩:flow-designer.mldong.com,首页"案例3: 钉钉模式"还带一个"模拟审批"按钮,能看到单子沿树一级级走过去的高亮效果。

四、画完的图去哪了:一份 JSON,喂给开源引擎直接跑

到这里,设计器部分说完了。但真正有意思的是下一件事:

设计器吐出的这份 JSON,就是一个工作流引擎能直接执行的流程定义。 snaker:start/task/decision/end 节点、边上挂 expr 条件表达式——这不是我为了配合设计器发明的格式,它就是开源工作流引擎 jeeflow 吃的定义格式。设计器相当于这个格式的官方可视化编辑器。

有三个细节值得说:

  1. @on-save 的产物多了个 mode: "dingtalk" 字段,不用清洗。引擎侧反序列化时未知字段直接忽略(Java 侧 Jackson 关掉了 FAIL_ON_UNKNOWN_PROPERTIES,Go 侧 struct 只映射已知字段),多一个 key 引擎连看都不看;
  2. jeeflow 官方前端 jeeflow-ui 就是这么接的:设计器 @on-saveupdateDefine 存设计稿 → deploy 生成流程定义(版本号 +1)→ 业务侧 startAndExecute 发起。三步之后,这张图就是一条活流程;
  3. 同一张图,八门语言引擎都能跑。Java/Go/Python/Node/PHP/Rust/MoonBit/C# 的引擎实现消费同一份流程定义——这也是 jeeflow 这个项目的核心玩法。

光说不练假把式,我把这条链路在 jeeflow 的开源演示站(jeeflow-demo.mldong.com,免登录,右上角可切八门语言后端,本节用的是 Go 引擎)真机走了一遍。

第一步,导入设计稿。 演示站的"流程设计"页支持直接导入 JSON——就是第三步那份。导入后设计器把它渲染成钉钉树:

演示站:设计稿导入后渲染成钉钉树形

第二步,保存并发布。 后端生成流程定义,版本 +1,这张图从"一张图"变成了"一条可执行流程"。

第三步,发起。 以张三的身份发起(金额 1500),接口信封:

{ "code": 0, "msg": "成功", "data": { "processInstanceId": "102" } }

申请节点自动由发起人办结(jeeflow 的 applicant 约定),单子落到部门经理李四的待办里。李四打开办理抽屉——流程图上,已办节点绿色打勾,当前节点橙色高亮,走到哪一目了然:

李四办理中:当前节点橙色高亮

第四步,条件分支真实路由。 金额 1500,命中"金额>1000"分支,单子继续走到总经理王五;王五同意后流程到达结束节点。张三在"我发起的"里看到:已完成,流程图全路径绿色:

已完成:全路径绿色高亮

设计器画的图、引擎跑的流程、前端回显的高亮,自始至终是同一份 JSON。

一个真实的坑:条件分支不动了

诚实起见,第一次跑这条链路时我翻车了。

第一版我把分支条件写成了 days > 3(按请假天数走分支)。发起、审批都正常,但单子在部门经理审批之后就停住了——不报错,也不往下走。

排查结论:jeeflow 引擎核心是零依赖设计,表达式求值是一个 SPIIExpressionEvaluator),引擎本身不内置任何表达式引擎——你把 Aviator、SpEL、expr-lang 还是自己写的求值器注入进来,引擎就用谁。演示站为了演示,内置了一个极简的桩求值器,只认识 amount 的几种比较;days > 3 它不认识,按约定返回 false。而引擎对决策节点的语义很克制:逐条试边,第一条成立的走;一条都不成立,就地等待,绝不乱走。所以"卡住"不是 bug,是引擎在诚实地说"没有一条路能走"。

换回求值器认识的 amount 条件,全链路立刻走通。这个坑反而让我更喜欢这个设计:表达式引擎的选择权在你手里,代价是你得知道"表达式是 SPI 注入的,不是引擎自带的"。顺带一个细节:从 jeeflow-ui 发起申请时,表单变量会统一加 f_ 前缀(比如 f_amount)——表达式里用哪个变量名,取决于你给引擎喂了什么,这也是接引擎时最容易想当然的地方。

五、什么时候别用:树形设计器的边界

树形设计器不是银弹,它的舒适区是 OA 审批语义:串级审批、条件分支、会签、并行、抄送。超出这个形态的需求,别硬上:

场景建议
审批流 / 业务单据流转(绝大多数后台系统)钉钉精简包,就是为这个生的
网状流程、深层子流程嵌套、自由布局诉求双模式包的 canvas 画布模式
存量画布流程要兼容,又要上钉钉模式双模式包,mode 切换,同一份 JSON

两个包自 3.1.0 起同版本联动发布;3.1.0 这个版本做了三件事:砍掉 ant-design-vue / element-plus 依赖(内置 UI 全部自研)、补齐工作流属性(流程级 +8:字段权限/抄送人/发起时选人等,任务级 +6:候选用户/会签类型/会签完成条件等)、钉钉模式视觉优化。

如果你的系统正打算做审批流,希望这条 10 分钟的路能帮你省掉一次"自研设计器"的排期。有兴趣聊工作流引擎的,看看文末的 jeeflow 系列。

参考资料