一、审批流做到一半,产品经理说"要像钉钉那样"
钉钉的审批界面人人都见过:点个"+"加一级审批,拉个条件分支,再把单子抄送给谁------不用看文档,会用手机就会配。
轮到给自己的系统做审批流,路通常只有两条,都不舒服:
- 上画布类流程设计器(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 工程,装包
shell
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 步:注册组件,三行
ts
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。我先喂一条报销审批流:
vue
<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 里的 x、y 不是给树形视图用的------它们是画布坐标,钉钉视图只是同一份数据的另一种呈现(组件内部在图结构和树结构之间做双向转换)。存储格式始终是图 JSON,这是后面"直接喂引擎"的伏笔。
加节点:点"+",不拖拽
树形设计器里没有拖拽。节点之间的"+"点开,是钉钉同款的节点选择器:审批人、自定义节点、子流程、条件分支、并行分支:

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

顺带一提:canvas 画布模式
如果你的系统还要兼容传统"自由画布"流程,换成双模式包 mldong-flow-designer-plus,mode="canvas" 和 mode="dingtalk" 一字切换,两套视图消费同一份 JSON。钉钉模式的对外 API(FDDesignerAPI)是刻意按 LogicFlow 实例的命名对齐实现的------updateText、setProperties、getProperties 这些方法两边都有,二次开发代码基本零修改就能双模式复用。
不想装包?官方在线演示站可以直接玩:flow-designer.mldong.com,首页"案例3: 钉钉模式"还带一个"模拟审批"按钮,能看到单子沿树一级级走过去的高亮效果。
四、画完的图去哪了:一份 JSON,喂给开源引擎直接跑
到这里,设计器部分说完了。但真正有意思的是下一件事:
设计器吐出的这份 JSON,就是一个工作流引擎能直接执行的流程定义。 snaker:start/task/decision/end 节点、边上挂 expr 条件表达式------这不是我为了配合设计器发明的格式,它就是开源工作流引擎 jeeflow 吃的定义格式。设计器相当于这个格式的官方可视化编辑器。
有三个细节值得说:
@on-save的产物多了个mode: "dingtalk"字段,不用清洗 。引擎侧反序列化时未知字段直接忽略(Java 侧 Jackson 关掉了FAIL_ON_UNKNOWN_PROPERTIES,Go 侧 struct 只映射已知字段),多一个 key 引擎连看都不看;- jeeflow 官方前端 jeeflow-ui 就是这么接的 :设计器
@on-save→updateDefine存设计稿 →deploy生成流程定义(版本号 +1)→ 业务侧startAndExecute发起。三步之后,这张图就是一条活流程; - 同一张图,八门语言引擎都能跑。Java/Go/Python/Node/PHP/Rust/MoonBit/C# 的引擎实现消费同一份流程定义------这也是 jeeflow 这个项目的核心玩法。
光说不练假把式,我把这条链路在 jeeflow 的开源演示站(jeeflow-demo.mldong.com,免登录,右上角可切八门语言后端,本节用的是 Go 引擎)真机走了一遍。
第一步,导入设计稿。 演示站的"流程设计"页支持直接导入 JSON------就是第三步那份。导入后设计器把它渲染成钉钉树:

第二步,保存并发布。 后端生成流程定义,版本 +1,这张图从"一张图"变成了"一条可执行流程"。
第三步,发起。 以张三的身份发起(金额 1500),接口信封:
json
{ "code": 0, "msg": "成功", "data": { "processInstanceId": "102" } }
申请节点自动由发起人办结(jeeflow 的 applicant 约定),单子落到部门经理李四的待办里。李四打开办理抽屉------流程图上,已办节点绿色打勾,当前节点橙色高亮,走到哪一目了然:

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

设计器画的图、引擎跑的流程、前端回显的高亮,自始至终是同一份 JSON。
一个真实的坑:条件分支不动了
诚实起见,第一次跑这条链路时我翻车了。
第一版我把分支条件写成了 days > 3(按请假天数走分支)。发起、审批都正常,但单子在部门经理审批之后就停住了------不报错,也不往下走。
排查结论:jeeflow 引擎核心是零依赖设计,表达式求值是一个 SPI (IExpressionEvaluator),引擎本身不内置任何表达式引擎------你把 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 系列。
参考资料
- npm(钉钉精简包):www.npmjs.com/package/mld...
- npm(双模式包):www.npmjs.com/package/mld...
- 设计器在线演示(含钉钉模式案例):flow-designer.mldong.com/
- jeeflow 开源演示站(本篇审批实录,可切八门语言后端):jeeflow-demo.mldong.com/?lang=go
- jeeflow-ui 前端仓库:github.com/mldong/jeef...
- 国内镜像:
npm install mldong-flow-designer-dingtalk --registry=https://registry.npmmirror.com