☰
deer-flow前端架构与流程设计器实现深度解析
2026/10/21 5:34:57 网站建设 项目流程

1. 先搞清楚deer-flow到底是个什么项目

1.1 它不是又一个工作流引擎,而是一个流程编排工具

第一次刷到deer-flow这个项目时,我下意识认为它又是一款国产工作流框架。仔细翻完文档花了大概半小时,才意识到它跟Activiti、Flowable这类传统工作流引擎的路子不太一样。它给我的整体感受是:不把重点放在严格遵循BPMN 2.0规范上,而是把功夫花在"流程配置轻量化"和"业务落地省事"这两件事上。

传统工作流引擎,说实话,功能确实强大,但上手门槛不低。你要是跟一个刚接触流程开发的同事讲BPMN规范里的边界事件、补偿事件、子流程、泳道,他大概率眼神发直。deer-flow走的是另一条路:把需要用到的流程节点抽象成可配置的组件,用户在前端页面里拖拖拽拽,把节点之间的先后关系连起来,就能产出一个可运行的流程定义。

这个思路本质上是把流程建模从"专业建模师才能干的事"降维成"业务开发或者实施人员动动鼠标就能干的事"。它不追求建模层面的绝对严谨,追求的是落地效率。对于多数企业内部系统——审批流、数据同步流、定时任务链、消息推送链——这种轻量级编排比僵化的BPMN建模反而更实用。

回到标题本身,deer-flow值得单独做前端分析的原因在于:这个项目的前端不是一个管理后台那么简单,它承担了流程设计器、节点配置面板、流程发布管理、实例执行监控等多个核心模块。换句话说,前端不只是展示数据,它还参与了流程定义的生产过程。这种"前端参与生产核心资产"的项目,代码架构和交互设计都值得好好拆一遍。

1.2 前端在整个项目里扮演什么角色

我习惯把这类项目的前端功能分成三个层次来看:

第一层是基础管理功能。比如用户管理、角色权限、流程列表、流程分类。这些跟普通管理后台没什么区别,技术上没太多可说的地方,但它是整个系统能用起来的地基。

第二层是流程设计器。这是整个项目最核心的前端资产。用户在这个画布上拖出节点、连线、配置属性、校验合法性,最终产出一份可被后端解析执行的流程定义JSON。这个模块的交互复杂度和数据模型设计难度,远超普通管理后台。

第三层是流程运行态的可视化。比如某个流程实例现在跑到哪个节点、每个节点的执行状态如何、失败之后怎么重试。这部分在不少开源项目里被弱化,deer-flow的前端做了一个看得见的执行过程面板,算是加分项。

分析前端代码时,我建议你把主要精力放在第二层。因为第一层网上能抄的模板一大把,第二层的设计思路和工程实现才是你真正能学到东西、也最容易踩坑的地方。

2. 前端工程的地基:技术栈与目录结构

2.1 选型这件事:为什么是Vue 3 + TypeScript + Vite

deer-flow前端主技术栈是Vue 3配合TypeScript,工程构建工具选了Vite。这类组合在2025年之后基本已经成了前端新项目的默认配置,但我还是想聊一下为什么这个组合适合流程设计器这类重交互场景。

Vue 3的Composition API对于流程设计器这种组件状态复杂、逻辑复用要求高的场景,比Vue 2的Options API友好得多。你用composables把拖拽逻辑、节点渲染逻辑、配置面板逻辑拆开,每个模块独立维护,代码不会变成一个巨大的单文件组件。这对后续二开非常重要——你八成会改它的节点渲染逻辑,如果代码全堆在一起,改起来会非常痛苦。

TypeScript在这类项目里不是可选加分项,是刚需。流程定义的数据结构非常复杂,一个节点有类型、坐标、入边、出边、属性配置、表单配置等多个维度。没有类型定义,你根本不敢重构。有了TS之后,后端返回的流程定义JSON在编译期就能被约束住,字段拼错、类型不对直接报错。

Vite的地位就不用多说了,项目大了之后Webpack的冷启动速度是硬伤,Vite基于ESM的开发服务器几乎是秒开。流程设计器这种需要反复调试的项目,开发体验差距巨大。

2.2 目录结构解构:拿到代码先看哪几个目录

我不打算把每个目录都列一遍,单说几个关键路径。

src/api目录是前后端接口层,所有后端请求封装都在这里。看这一层你能快速了解前端调了哪些后端能力,也方便你后续接自己的后端。建议先看这里,因为它天然是系统的"能力地图"。

src/views目录是页面级组件,流程管理、实例管理、节点配置这些页面都在里面。这里能看出系统的业务功能边界。

src/components是公共组件目录,重点找流程设计器相关组件。以我的经验,这个目录下通常有NodeItem、BaseNode、Line这类文件,它们就是画布上被拖拽的元素。先看组件怎么划分的,比直接看store有用。

src/store或者src/stores目录里的状态管理模块值得仔细看。流程设计器的状态管理是整个前端最复杂的部分——画布上节点的增删改、边的连接关系、选中态、撤销重做、保存前后的脏数据标记,都需要清晰的状态设计。

2.3 环境配置与工程化的几个细节

工程化方面有几个细节值得注意。一个是路径别名,一般会配置@指向src目录,这个看vite.config.ts就能确认。

另一个是代码规范。deer-flow这种社区项目通常配有ESLint和Prettier,你在拉代码之后先把依赖装好,启动开发服务器之前把所有检查和格式化打开,别在本地留下格式噪音。

还有一个值得看的是它如何管理全局样式变量。流程设计器的画布区域、节点拖动时的阴影、连线选中的高亮色,这些视觉状态如果散落在每个组件里,以后换主题会改到怀疑人生;如果集中抽了变量,改起来就一行事。我拆这个项目时关注了一下,发现它的节点颜色、连线样式这些确实是集中管理的,这个习惯值得借鉴。

# 以npm为例,项目的启动流程基本是 npm install npm run dev

前端跑起来之后,你会看到一个带左侧节点面板、中间画布、右侧属性配置区域的典型三段式布局。这套布局是流程设计器的经典范式,后续所有交互都围绕这个骨架展开。

3. 核心战场:流程设计器是怎么跑起来的

3.1 三段式布局背后的设计逻辑

流程设计器的界面几乎统一遵循一个布局规律:左侧是可选的节点组件面板,中间是画布,右侧是当前选中节点的属性配置面板。这个布局之所以成为事实标准,是因为它把一个流程建模的完整心智模型拆成了三个步骤——选节点、摆位置、调属性。

左侧节点面板用树形或者分组列表展示节点类型。deer-flow里常见的节点有开始节点、审批节点、条件节点、执行节点、结束节点。每个节点对应后端的一种处理器,前端要做的就是把节点的元信息(类型、图标、名称、可配置字段)维护好,让"一个可拖拽的卡片"和"一个可执行的处理器"对应起来。

中间画布是三段式布局中最复杂的一块。画布上的节点需要支持拖拽移动、点击选中、框选、连线、删除、撤销重做这些操作。大多数自研流程设计器会选择基于SVG或者Canvas实现画布,也有的直接用DOM结合transform定位。两种方案各有优劣,DOM方案开发快、样式好写,但节点数量上去了性能会吃力;SVG方案对连线这类图形计算更友好,但也需要更小心地组织渲染逻辑。

deer-flow前端的画布实现,说白了也是从"节点数组+线数组"两个数据结构上做渲染。画布上每新增一个节点,本质上是往节点数组里push一个对象;每连一条线,就是往线数组里push一个包含起点和终点引用的对象。理解到这个层面,你再去翻代码就会豁然开朗。

3.2 拖拽节点的交互设计

拖拽是流程设计器最基础也最容易被做崩的交互。我从实操角度拆一下它需要处理的问题。

第一是拖拽源的判断。用户从左侧面板里按住某个节点类型开始拖,松手时画布要计算出鼠标坐标相对画布坐标系的位置,再把新节点实例插入节点数组。这里最容易踩的坑是坐标系转换,鼠标事件的clientX/clientY是相对浏览器视口的,如果画布有滚动或者缩放,不换算坐标节点就会掉在错误的位置。很多新手写到这里就在这一步翻车,表现为"节点落下去的位置总是偏移一截"。

第二是画布内节点的自由拖拽。节点在画布内拖动需要区分"按下"和"拖动",如果点一下就触发move,那节点选中和拖拽就会互相打架。通常的做法是按下时记录起点,监听mousemove的时候计算位移差,超过一定阈值才认为是一次拖拽。

第三是拖拽过程中的吸附和辅助线。这个属于体验加分项,deer-flow这类轻量级项目不一定会做得特别完整。如果你二开时想要节点自动对齐,需要额外处理节点之间的相对位置计算。

3.3 节点连线的完整链路

连线是流程设计器里仅次于拖拽的第二大复杂度来源。线不是画一条线段那么简单,它涉及从哪个节点出、到哪个节点入、连线路径怎么走、命中区域怎么算、删线怎么操作,一整条链路需要处理好。

节点通常要定义连接桩(port),也就是出线和入线的锚点。如果一个节点只允许一个出边一个入边,逻辑就简单;但如果一个条件节点要能分出多条分支,就要求节点动态支持多个出桩。deer-flow这类工具对分支的支持比较看重,因为它的核心场景就是条件路由。

画布上的线,常见渲染方案是SVG的path或者贝塞尔曲线。两点之间拉一条线很轻松,但节点移动时线不能断掉,需要监听节点位置变化并实时重算线的端点。这就是为什么你会在代码里看到watch节点数组然后触发线的重绘。

线的命中检测也要单独处理,SVG的path是矢量形状,鼠标事件默认只响应图形本身,线条太细的情况下很难点准。通常的方案是渲染一条透明的、加粗的辅助线做命中区域,用户能轻松点到线。这种小细节,是流程设计器好用和难用的重要分水岭。

连线完成后要做环路检测。两个节点A连到B、B又连回A,这在流程定义里往往是无效的死循环。前端在保存之前会把流程定义发给后端做校验,但更友好的做法是前端在建边的瞬间就做一次基础环路判断,至少给用户一个即时警告,而不是等发布接口报错。

3.4 配置项面板与表单模型

右侧属性面板设计得好不好,直接决定这个工具是不是"给业务人员用的活工具"。deer-flow的前端里,每个节点类型都对应一套配置字段。例如审批节点要配置审批人来源、多人审批时的策略;条件节点要配置判断表达式;执行节点要配置调用的服务和处理参数。

这些配置字段在前端表现为动态表单。你在组件代码里会看到类似"节点类型到表单组件映射"的结构:定义一个Map或者Record,键是节点类型,值是该类型对应的表单组件。每次画布上选中节点变化时,右侧面板根据当前节点的类型动态渲染对应表单。

这个映射关系是二开时改动最频繁的地方。比如你要加一个新的节点类型,通常需要注册节点元信息、配置面板字段,再让后端在处理器里识别新的节点类型。前端的工作量主要集中在节点元信息的维护上。

节点数据模型的规范程度,决定了新增节点类型的工作量大小。设计得好的话,加一种节点类型就是加一段配置的事;设计得不好,就要牵扯改一堆硬编码的判断逻辑。

3.5 数据模型的序列化与解析

流程设计器在前端产生的最终产物是一份JSON。这个JSON需要满足两个条件:一是能完整描述画布上的节点和连线,二是后端能基于这份JSON执行流程。

一个流程定义数据大概长这样:

{ "processKey": "demo_process", "processName": "示例流程", "nodes": [ { "id": "start_001", "type": "start", "name": "开始", "x": 100, "y": 120 }, { "id": "approve_001", "type": "approval", "name": "部门审批", "x": 320, "y": 120, "properties": { "assigneeType": "role", "assigneeValue": "dept_manager", "multipleApproval": "AND" } } ], "edges": [ { "id": "edge_001", "sourceNodeId": "start_001", "targetNodeId": "approve_001" } ] }

这种数据结构有几个关键点:节点之间不直接嵌套引用,而是用id通过边来建立关系,这样保存和解析都简单;properties是灵活字段,不同节点类型的配置都塞在里面,后端拿到后可以根据节点类型做针对性解析。

在前后端代码里搜索JSON.parse和JSON.stringify,基本就能找到流程定义的保存和回显逻辑。保存时前端把树形状态拍平成这份JSON发给后端;编辑时后端把JSON传回前端,前端再还原成画布上的节点和线。

这个序列化与反序列化的过程是前后端字段约定最容易出问题的地方。节点ID在前后端必须全局唯一,properties里的字段名必须保持一致,后端新增了某个配置项,前端属性面板必须同步更新。如果你在集成时发现"能画但跑不起来"或者"跑起来但配置丢了",80%的问题都出在数据模型不一致上。

4. 前后端API设计与数据流

4.1 流程定义与实例的两个层面

分析deer-flow前端接口之前,先分清两个概念:流程定义和流程实例。

流程定义是静态的模板,也就是你在设计器里画出来的那份JSON,包含节点、连线、属性配置。它不做具体执行,只是被发布后供后续流程创建时引用。

流程实例是动态的运行态,一次实际的流程运行就是一个实例。它包含了当前执行到哪个节点、各节点的状态、流转过程中产生的业务数据。前端在流程管理页面看到的"运行中的流程",本质上就是实例列表。

接口设计上,deer-flow这类项目会把这两个层面分开:流程定义的增删改查与发布是一组接口,流程实例的发起、查询、终止、重试是另一组接口。前端在路由和API封装上,也会按这两个维度去组织模块。

理解这个区分,你在看前端代码的时候就不会晕。流程管理页面、流程设计器页面主要操作的是流程定义;实例管理页面、流程监控页面操作的是流程实例。两者的数据结构完全不同,前端组件也会分开。

4.2 前端状态管理怎么组织

流程设计器的前端状态管理是理解整个项目结构的钥匙。如果你看到代码里用了pinia(Vue 3生态的官方推荐状态管理库),那核心store大概率是围绕"当前流程定义"来设计的。

我建议你带着下面这些问题去读store代码:

当前打开流程的元信息存在哪里?节点数组和线数组是同一个store还是分开的?画布上的选中状态怎么存?保存之前如何判断流程是否有未提交的变更?

正常情况下,你会看到一个叫做process或flow的store,管理着流程名称、节点列表、连线列表、当前选中的节点ID、画布缩放比例等状态。节点和线放在同一个store里是有道理的,因为连线要引用节点ID,把它们拆开会导致同步问题。

撤销和重做功能通常也会在store层做快照管理。每做一次改动,就把当前的节点线数组快照push进历史栈;撤销时从历史栈弹出上一个快照,用快照覆盖当前数据。这个思路简单,但要注意快照是可以被消费的内存,如果流程特别大,一次快照的拷贝开销就不小。deer-flow这类项目一般不会把历史栈做得特别深,够用就行。

4.3 权限、登录与接口鉴权的常规做法

流程工具在项目里通常不是孤立存在的,它需要接入公司原有的账号体系。deer-flow作为一个可二次开发的项目,前端鉴权这块通常保留了常见的登录态逻辑:登录接口获取token,后续请求在请求头里带上token,后端校验通过才返回数据。

你接自己项目的时候,大概率要把登录逻辑替换成你们公司的统一认证。常见做法是保留前端的登录页面框架,但把登录接口替换为统一认证的对接接口;或者干脆去掉登录页,在项目入口做一个iframe内嵌,从父页面拿登录态。

接口层封装时,HTTP客户端一般会做请求拦截器和响应拦截器。请求拦截器统一加token;响应拦截器统一处理业务码,比如401跳登录、500弹错误提示。你二开时如果要对接自己的后端,把baseURL和token的存取方式改一下就行。这里先给你提个醒:前后端字段命名风格差异(比如前端createTime、后端created_at)是集成时最容易踩的坑,不要想当然。

5. 把前端跑起来:从拉代码到接入自有后端

5.1 本地完整启动流程

这一节按我实际操作的经验,记录一遍从拉代码到界面能用的完整流程。不同版本可能略有差异,但整体思路通用。

第一步,把前后端代码都拉到本地。需要注意deer-flow是典型的前后端分离项目,前端需要在工程里配置代理才能访问后端服务,否则跨域请求根本发不出去。

第二步,初始化后端数据库。通常项目目录下会有sql脚本,先在本地数据库执行,把基础表结构建好。如果后端连不上数据库,前端页面就算能打开,数据也是空的,流程管理列表、节点保存都会报错。

第三步,启动后端服务。确认后端启动成功后,手动访问几个后端接口的地址,确认能正常返回数据。这一步不要跳过,因为前后端分离开发中,前端报错原因里最大的一类就是"后端口径对不上"。

第四步,安装前端依赖并启动开发服务器。启动后用浏览器打开前端地址,进入流程管理页面,新建一个流程,打开设计器。如果画布能正常拖出节点、保存流程、发布流程,说明整条链路已经通了。

# 常见的前端代理配置,vite.config.ts里大致长这样 server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

5.2 跨域与代理配置的几个细节

前后端分离部署最常见的坑就是跨域。开发环境下靠代理解决,生产环境下就要靠Nginx转发或者后端开启跨域策略。

Vite的proxy配置有个容易被忽略的细节:changeOrigin需要设为true,否则后端收到请求的Host头还是前端地址,有些安全校验严格的框架会拒绝请求。另外,如果你的后端接口路径不是统一的/api前缀,代理规则要对应调整。

生产部署时,我一般建议用Nginx把前端静态资源和后端接口放在同一个域名下,通过路径区分。这样用户在浏览器里看到的是同一个来源,不存在跨域问题。配置上只需要把/api路径代理到后端服务地址,其他路径走前端静态资源。

# Nginx里大致长这样 server { listen 80; server_name your-domain.com; root /path/to/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

这里有一个我踩过不止一次的坑:前端路由用的history模式,Nginx如果只配置了root和index,刷新非首页路径时会出现404。需要在Nginx里加一条try_files $uri $uri/ /index.html;,把找不到的路径都重定向到index.html,让前端路由接管。

5.3 接进现有项目的三种方案

如果你的目标不是把deer-flow独立部署,而是把它嵌进自己公司的系统,你会面对三种集成路线。

第一种是在现有项目的空白菜单里内嵌deer-flow前端页面的iframe。这是成本最低的做法,代码侵入小,deer-flow作为独立系统运行,你的主系统通过iframe引用它的地址。缺点是交互比较割裂,token传递和单点登录做起来比较绕,如果主系统和deer-flow不在同一个域名下,localStorage里的登录态还不能直接共享。

第二种是把deer-flow前端的流程管理、设计器、实例管理这几个核心页面作为独立路由模块,直接集成到你的Vue项目里。这个方案要做的工作量比iframe大——需要把deer-flow前端的路由配置、状态管理、公共组件、接口封装全部搬过来,改造成你们项目的命名空间和样式体系。好处是用户体验无缝衔接,登录态共享,后续维护也方便。

第三种是只引流程设计器模块,不要它的管理后台部分。如果你的公司已有完善的流程列表、权限管理,只是缺一个流程可视化设计能力,那你可以把设计器组件抽出来,作为一个独立组件接进自己的页面。这个方案的定制成本最高,因为你得弄清楚设计器的props和事件接口,但可复用性最强。

三选一之前,先想清楚一个问题:你是要"一个流程工具",还是"一个能画流程的组件"。这决定了你花多少精力在工程集成上。

5.4 上线之前必须确认的几个事项

把前端接到自己的后端、联调测试全部跑通之后,上线前还有几个跟前端强相关的点需要确认。

第一,流程定义JSON的兼容性。如果你的版本升级过,线上已有的流程定义可能是老版本数据结构,新前端打开时可能出现字段不识别、节点渲染异常。给前端加一层旧数据兼容解析是稳妥做法。

第二,保存时机。流程设计器里用户画半天没保存,结果浏览器一刷新全没了,这种体验很伤人。确认项目里有没有草稿自动保存或者未保存离开提醒。开源版本不一定自带这功能,二开时可以加一个beforeunload提醒,成本很低但用户感知非常强。

第三,权限控制的粒度。deer-flow这类项目一般带角色权限,但能不能细到"只允许A角色设计流程、B角色只能执行流程",需要看你拉下来的版本实现程度。如果原始版本没有,你就得在路由守卫和按钮级权限上动刀。

6. 二开之前必须知道的坑

6.1 流程定义变更后旧实例怎么办

这是流程类系统里最经典的问题,没有之一。我在实操里见过太多次:流程上线跑了一个月,业务部门说那个审批条件要改,开发同学去设计器里把节点配置改了然后发布,结果发现正在跑的老流程全部乱套。

从原理上讲,流程实例在创建的那一刻应该按照当时的流程定义版本执行。也就是说,你可以让新发起的流程走新定义,但已经在跑的实例应该继续按旧版本定义流转下去。这要求后端在实例创建时对当前的流程定义做快照,或者保留多个版本的定义。

前端在这件事上需要配合的,是在设计器里明确展示当前编辑的是哪个版本。如果你二开时看到版本管理功能不完善,至少要让用户在发布前有二次确认弹窗,提示"发布后将影响后续新流程,存量实例不受影响",避免误操作。前端能做的不多,但一个提示能挡掉大量生产事故。

6.2 大流程节点数量上来之后的性能问题

流程设计器在节点少的时候(十几个)跑起来很流畅,但如果你接入的场景比较复杂,比如达到上百个节点,就会明显感觉到卡顿。卡顿的来源主要有两个。

第一是每次拖拽或移动节点时,状态更新导致整个画布组件的重渲染。如果画布上每个节点都是一个子组件,而且组件没有做合理的响应式拆分,那移动一个节点会连带所有节点一起重新渲染。解决办法是给节点组件做memo或者将画布拆成更细粒度的组件,让移动操作只触发当前节点及其连线的更新。

第二是连线重绘的消耗。每条线都是SVG path,节点一多,path数量就多,每次坐标变化都触发一批path重新计算,性能自然下降。优化思路包括:只对移动到节点的连线做重算、降低线的绘制频率、把静态背景和动态元素分层。

如果你的流程规模特别大,二开时甚至可以考虑把画布渲染方案从DOM+SVG改成Canvas。Canvas对海量节点的绘制性能远高于DOM和SVG,但代价是交互实现成本更高,命中检测、坐标换算、文本编辑都要自己写。我的建议是:先衡量你的真实场景,前端别为了"可能会很复杂"提前上高成本方案。

6.3 自定义业务节点的接入约定

deer-flow能做二次开发最实用的一点是支持自定义节点。每个自定义节点要打通一条链路:前端要注册节点类型和属性面板,后端要注册对应的处理器逻辑。

前端这一侧,你需要确认三处代码:左侧节点面板里的节点类型列表、画布渲染不同节点类型时用的组件、右侧属性面板里节点类型到表单的映射。拉源码后搜索节点类型枚举,把这几处地方标记出来,你就能梳理出"加一个节点类型需要动哪些文件"的清单。

属性配置面板要特别注意,因为业务自定义节点的配置字段往往跟审批、条件这类通用节点完全不同。动态表单的做法是,为每个节点类型维护一份独立的表单模型,把字段定义、校验规则、默认值都集中在一起。这样后端再加字段时,前端只需要改一处表单模型,不用翻页面代码。

最后给你一个实操习惯:自定义节点上线前,把该节点生成的数据JSON在前后端各打一次日志,比对两边的字段名和类型是否一致。这个工作看着笨,但是能帮你快速发现"前端传了userId、后端读的却是assigneeId"这种看起来小、实际坑死人的问题。

6.4 多环境部署与前端资源处理

如果你们公司有开发、测试、生产多套环境,前端这边要提前约定好接口地址的管理方式。不要在代码里把接口地址写死,至少要用环境变量区分,构建时按环境注入。

另外,前端静态资源的缓存策略也要注意。流程设计器这类重交互页面,JS和CSS文件通常体积不小,如果浏览器缓存了旧版本资源,新功能上线后用户看到的还是老界面,排查起来会很头疼。在构建产物的文件名里加上内容哈希(vite build默认会处理),Nginx配置里给带哈希的文件设置长缓存,给index.html设置no-cache,这一套配下来基本就不会有缓存困扰。

我在维护类似项目时,还会在页面上加一个"版本号"显示,放一个不容易被注意到的角落。每次发布后让测试同学确认版本号变了,能省掉很多"我改了为什么没生效"的沟通成本。

流程编排类项目的前端,核心价值不在技术多炫,而在把复杂的数据关系和交互逻辑组织得清晰可用。你把设计器模块啃下来,数据流梳理通,注册自定义节点的链路摸清,后面接任何流程场景都能快速上手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询