
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 Vitedeer-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 前端状态管理怎么组织流程设计器的前端状态管理是理解整个项目结构的钥匙。如果你看到代码里用了piniaVue 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重新计算性能自然下降。优化思路包括只对移动到节点的连线做重算、降低线的绘制频率、把静态背景和动态元素分层。如果你的流程规模特别大二开时甚至可以考虑把画布渲染方案从DOMSVG改成Canvas。Canvas对海量节点的绘制性能远高于DOM和SVG但代价是交互实现成本更高命中检测、坐标换算、文本编辑都要自己写。我的建议是先衡量你的真实场景前端别为了可能会很复杂提前上高成本方案。6.3 自定义业务节点的接入约定deer-flow能做二次开发最实用的一点是支持自定义节点。每个自定义节点要打通一条链路前端要注册节点类型和属性面板后端要注册对应的处理器逻辑。前端这一侧你需要确认三处代码左侧节点面板里的节点类型列表、画布渲染不同节点类型时用的组件、右侧属性面板里节点类型到表单的映射。拉源码后搜索节点类型枚举把这几处地方标记出来你就能梳理出加一个节点类型需要动哪些文件的清单。属性配置面板要特别注意因为业务自定义节点的配置字段往往跟审批、条件这类通用节点完全不同。动态表单的做法是为每个节点类型维护一份独立的表单模型把字段定义、校验规则、默认值都集中在一起。这样后端再加字段时前端只需要改一处表单模型不用翻页面代码。最后给你一个实操习惯自定义节点上线前把该节点生成的数据JSON在前后端各打一次日志比对两边的字段名和类型是否一致。这个工作看着笨但是能帮你快速发现前端传了userId、后端读的却是assigneeId这种看起来小、实际坑死人的问题。6.4 多环境部署与前端资源处理如果你们公司有开发、测试、生产多套环境前端这边要提前约定好接口地址的管理方式。不要在代码里把接口地址写死至少要用环境变量区分构建时按环境注入。另外前端静态资源的缓存策略也要注意。流程设计器这类重交互页面JS和CSS文件通常体积不小如果浏览器缓存了旧版本资源新功能上线后用户看到的还是老界面排查起来会很头疼。在构建产物的文件名里加上内容哈希vite build默认会处理Nginx配置里给带哈希的文件设置长缓存给index.html设置no-cache这一套配下来基本就不会有缓存困扰。我在维护类似项目时还会在页面上加一个版本号显示放一个不容易被注意到的角落。每次发布后让测试同学确认版本号变了能省掉很多我改了为什么没生效的沟通成本。流程编排类项目的前端核心价值不在技术多炫而在把复杂的数据关系和交互逻辑组织得清晰可用。你把设计器模块啃下来数据流梳理通注册自定义节点的链路摸清后面接任何流程场景都能快速上手。