简介:vue-super-flow 是一款基于 Vue 的流程图编辑器组件,面向需要可视化流程设计的 Vue 开发者,适合在项目管理、低代码平台、业务编排等场景中快速集成可拖拽的流程图编辑能力。压缩包仅 118KB,共 23 个文件,其中以 9 个 JS 逻辑文件、6 个 Vue 组件文件和 2 个 HTML 示例页为核心,JS 负责交互与数据逻辑,Vue 组件负责节点和连线的渲染,HTML 为可直接运行的演示入口;另有说明文档、许可证、依赖锁定等工程辅助文件,整体轻量且结构清晰。这一版本已被 16499 人学习下载,是 Vue 流程图中颇具热度的开源方案。组件提供 draggable、linkAddable、linkEditable、hasMarkLine 等属性,支持节点拖拽、快捷连线、连线编辑与参考线对齐,可通过 npm 直接安装并注册使用;同时附带示例工程和含菜单、标记线等模块的组件化源码,便于深入理解图、节点、连线之间的数据流转关系,快速定制节点样式或扩展事件处理,适合直接落地到实际项目中。
1. 定位与适用场景:为什么需要一个自带拖拽与连线的流程图组件
接到需求要在后台管理里加一个工单流转编排页,十几个状态节点要能拖、能连、能保存。最先想到的是找现成的开源组件,翻了几个发现要么依赖太重、要么文档绕,把组件接进现有 Vue 项目比预想费劲。后来落到 vue-super-flow 这个流程图组件上,核心是给 Vue 项目提供一块可交互的流程图画布:节点拖拽、锚点连线、框选、缩放平移这些基础交互都用 ES6 JavaScript 实现,不需要额外引入大型第三方库。你能拿它画审批流、状态机、拓扑图,也可以承载带自定义数据的业务编排图。适合正在给业务加可视化编辑页的前端同学,也适合想弄明白「数据怎么渲染成一张能拖的画布」的开发者。
2. 组件架构与设计:store、viewport、renderer 三层各管一摊
拆组件之前我习惯先把目录铺开,流程图组件最容易写成一团浆糊——节点、连线、画布、事件全塞在同一个文件里,改一个地方崩三处。vue-super-flow 的模块划分属于清晰的那种,核心可以抽象成三层:store 管数据,viewport 管坐标系,renderer 管渲染。理解这三层,后面读源码和改源码都会顺很多。
2.1 模块划分:数据层、视口层、渲染层各管一摊
store 是整个图的数据中枢。所有节点数组、连线数组、选中节点集合、当前 hover 的节点 id,都集中存放在一个响应式对象里。这么做的直接好处是,任何位置的代码要读图状态,不需要层层传参,直接拿 store;要改图,也走同一套 action 方法,不会出现某个组件自己偷偷改了别的组件的状态。
viewport 是另一层很容易被忽视的模块。它维护画布的缩放比例 scale 和平移偏移量 translateX、translateY。拖拽、连线、框选这些交互拿到的都是屏幕坐标,必须先经过 viewport 换算成画布坐标,再写回 store。流程图组件的诡异 bug 大多出在这一层换算没做对。渲染层出问题最多是视觉歪,viewport 换算出错则会导致拖拽、命中、框选全面失控。
renderer 负责把 store 里的数据变成真正可见的页面元素。节点渲染器把每个 node 渲染成定位的 DOM 卡片,边渲染器把每条 edge 渲染成 SVG path,锚点渲染器生成节点上的连接圆点。它只管「数据到视图」的映射,不负责交互逻辑,交互事件由外层绑定后再调用 store 的 action 完成。
这三层串起来的链路是:用户拖拽节点,触发鼠标事件,事件处理器把屏幕坐标换到画布坐标,调用 store 更新节点位置,store 触发响应式更新,renderer 重绘节点样式。数据流向是单向的,排查问题时顺着这条链找,一般很快能定位到是坐标算错还是数据没写对。
2.2 数据模型:用 JSON 描述一张完整的流程图
流程图组件的输入输出都是纯 JSON,这是它好集成的一个点。一张图由 nodes 和 edges 两个数组组成,节点对象的结构大致是这样:
{ id: 'node-1', // 唯一标识,连线的 source/target 都引用它 type: 'default', // 节点类型,可注册自定义渲染类型 x: 120, // 节点左上角在画布坐标系中的 x 位置 y: 80, // 节点左上角在画布坐标系中的 y 位置 width: 180, // 节点宽度,连线计算锚点时要用 height: 60, // 节点高度 data: { // 业务自定义数据,不参与渲染逻辑 label: '发起审批', status: 'pending' } }连线对象的结构:
{ id: 'edge-1', source: 'node-1', // 起始节点的 id target: 'node-2', // 目标节点的 id sourceHandle: 'output', // 起始锚点的标识,默认节点右侧 targetHandle: 'input' // 目标锚点的标识,默认节点左侧 }这里有个容易被新手看漏的约定:节点的位置坐标是相对画布坐标系,而不是相对屏幕。画布做过缩放或平移之后,屏幕上看到的节点位置和 store 里的 x、y 对不上,这是正常的。锚点的位置不是显式存储的,而是渲染时根据节点坐标、尺寸和 handle 方向动态计算的。这样做的好处是节点被拖走之后,连线的端点会自动跟着走,不需要手动同步。
数据从后端读出来的时候,建议在初始化前先做一层校验。最常见的问题是历史数据里节点缺少 width、height 字段,渲染出来全是零尺寸节点。给 store 初始化加一个 normalize 函数,缺省字段补默认值,能避免大量低级报错。这个函数放在 store 的创建方法里,每次新建实例都会经过它。
2.3 渲染方案:为什么用 SVG 而不用 Canvas
流程图组件几乎都会在 SVG 和 Canvas 之间二选一。vue-super-flow 这类以交互为主的组件用 SVG 更合理。SVG 的每个节点、每条连线都是独立 DOM 元素,可以直接绑定事件、设置样式、做动画,开发体验对前端友好。调试的时候打开浏览器控制台能看到每个元素的完整属性,这种可观测性在 Canvas 方案里很难达到。
Canvas 的优势要等数据量大之后才体现。几千个节点同时渲染时,SVG 的 DOM 数量会明显拖慢首屏和拖拽响应;Canvas 用一段绘制指令把所有图形画在同一个画布上,渲染性能更稳,但代价是命中检测要自己算、事件要手动分发、文本和图形的样式处理也更啰嗦。
我的建议是:业务图控制在几百个节点以内的,用 SVG 方案能省大量开发时间;如果你的场景明确会有上千节点、需要持续高频刷新,再考虑基于 Canvas 的实现。vue-super-flow 的数据模型和服务端最终保存的 JSON 与渲染方案是解耦的,将来从 SVG 迁到 Canvas,store 和 viewport 两层可以原样保留,只替换 renderer 这一层。
2.4 坐标系换算:所有交互正确性的地基
流程图组件里存在两套坐标:屏幕坐标和画布坐标。屏幕坐标是鼠标事件直接返回的 clientX/clientY,画布坐标是节点存储的 x/y。两者之间隔着一个 viewport 变换——画布可能被缩放、被平移。换算公式不复杂:
// 把屏幕坐标换算成画布坐标 function screenToFlow(e) { const rect = canvasRef.value.getBoundingClientRect(); return { x: (e.clientX - rect.left - viewport.translateX) / viewport.scale, y: (e.clientY - rect.top - viewport.translateY) / viewport.scale }; }这段代码每个字段都有明确意义:rect.left 解决画布在页面里不在左上角的问题,减去 translateX 抵消画布平移,除以 scale 抵消缩放。三个缺一不可,少一个都会在特定操作下出现明显偏移。
这个函数建议写成纯函数,不要在函数里读组件实例属性,方便单独做单元测试。坐标换算逻辑一旦被验证过,后面所有交互出问题时就能放心排除这一层。拖拽节点、绘制连线、框选判断,凡是涉及鼠标位置的逻辑,第一步都是先算出画布坐标,再拿这个坐标去和 store 里的数据比较。这个函数写对了,后面能省一半的排查时间。
3. 核心实现拆解:节点渲染、连线绘制与拖拽交互的代码链路
架构那章把地基铺完了,这章钻到具体实现里。我会按一条真实操作链路来拆:先在画布上渲染出节点,再渲染连接节点的边,最后让节点能被拖走、被框选。这三段代码覆盖了组件最核心的交互闭环,理解之后改样式、加交互都有下手点。
3.1 节点渲染:从数据到卡片的响应式映射
节点的渲染核心是一个接收 node 对象、输出定位 DOM 的组件。关键点有两个:外层容器负责把 x/y 转成绝对定位,内层插槽负责展示节点内容。看一段简化的实现:
// FlowNode.js export default { props: { node: { type: Object, required: true } }, setup(props) { // 直接读取 store,节点位置变化时自动触发重渲染 const store = useStore(); const style = computed(() => ({ left: props.node.x + 'px', top: props.node.y + 'px', width: props.node.width + 'px', height: props.node.height + 'px', position: 'absolute' })); return { style }; }, template: ` <div class="flow-node" :style="style" :data-node-id="node.id"> <div class="flow-node__content">{{ node.data.label }}</div> </div> ` }这段代码把 x/y 直接映射为 CSS 的 left/top,这是流程图组件最基本的定位方式。节点坐标是相对画布,所以节点必须放在一个相对定位的画布容器里,用绝对定位把每个卡片放到指定位置。
注意事项:节点定位不要依赖 transform: translate。连线计算锚点时读的是 x/y 数值,如果渲染层额外用 transform 做位移而 store 里的 x/y 没变,连线的端点和卡片的视觉位置就会错位。看到节点和连线对不齐时,先查有没有人把位移写进了 transform 里。
3.2 连线绘制:算出起点终点,交给一条 SVG path
连线逻辑是整个组件里技术含量比较高的部分。一条边从一个节点连到另一个,需要先确定起终点坐标,再生成一条贝塞尔曲线路径。SVG 的 path 只需要一个 d 属性就能描述整条线,难度全在坐标计算上:
// computeEdgePath.js export function computeEdgePath(edge, nodeMap) { const sourceNode = nodeMap.get(edge.source); const targetNode = nodeMap.get(edge.target); // 从节点四边的中点取锚点坐标 const sourcePos = getHandlePosition(sourceNode, edge.sourceHandle || 'output'); const targetPos = getHandlePosition(targetNode, edge.targetHandle || 'input'); const dx = targetPos.x - sourcePos.x; const dy = targetPos.y - sourcePos.y; // 根据方向计算贝塞尔控制点,让曲线看起来自然 const curvature = Math.min(40, Math.abs(dx) * 0.5); const c1x = sourcePos.x + (dx > 0 ? curvature : -curvature); const c2x = targetPos.x + (dx > 0 ? -curvature : curvature); return { d: `M ${sourcePos.x} ${sourcePos.y} C ${c1x} ${sourcePos.y}, ${c2x} ${targetPos.y}, ${targetPos.x} ${targetPos.y}` }; }getHandlePosition的逻辑是按 handle 名称返回节点四条边的中点坐标,例如左侧锚点就是{ x: node.x, y: node.y + node.height / 2 }。这段代码里最值得说的一句是控制点计算:dx的正负决定曲线往哪个方向弯,Math.min(40, Math.abs(dx) * 0.5)限制控制点偏移幅度,防止两个节点离得很近时曲线过度扭曲。
实际工程里还会处理两个细节。一是连线的样式要区分选中、hover 状态,通常给 path 加 class 并监听节点的选中状态重新计算样式。二是箭头的实现,在 SVG 里定义一个 marker 元素然后把 marker-end 挂在 path 上,比用手绘三角形可靠得多。
3.3 拖拽交互:mousedown 三件套加坐标换算
拖拽是所有交互里最容易写翻车的。标准做法是 mousedown 时记录起点,mousemove 时算位移更新位置,mouseup 时结束并派发事件。这里一个关键细节:mousemove 和 mouseup 必须绑定在 document 上,而不是绑定在节点元素上,否则拖快一点鼠标出了节点区域就不会触发移动,节点会原地卡住。
// useDrag.js export function useDrag(node, onMove) { let startX = 0, startY = 0; let dragState = null; function onMouseDown(e) { e.stopPropagation(); dragState = { nodeId: node.id }; // 记录起始点,换算成画布坐标再减掉节点自身的偏移 const flowPos = screenToFlow(e); startX = flowPos.x - node.x; startY = flowPos.y - node.y; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } function onMouseMove(e) { if (!dragState) return; const flowPos = screenToFlow(e); // 新位置 = 鼠标画布坐标 - 按下时鼠标相对节点左上角的偏移 onMove(dragState.nodeId, { x: flowPos.x - startX, y: flowPos.y - startY }); } function onMouseUp() { dragState = null; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } return { onMouseDown }; }这行startX = flowPos.x - node.x是关键,容易被忽略。鼠标点下去的位置一般不在节点左上角,而是在节点中间某个位置。如果不记这个偏移量,直接拿鼠标坐标当节点新坐标,节点会瞬间跳一下,让左上角贴到鼠标上。记下按下瞬间鼠标相对节点的偏移,拖动过程中一直保持相对关系,视觉上才顺滑。
注意:mousemove 和 mouseup 的绑定和解绑要成对出现,漏掉 mouseup 解绑会导致其他画布操作都被上一次拖拽的移事件干扰。
另外每次 mousemove 都触发一次 store 更新,频率取决于鼠标移动。实测中不用手动做节流,Vue 的响应式更新足以跟上鼠标事件频率,强行节流反而让掉帧更明显。
3.4 框选与状态联动:矩形命中判断与选中集合更新
框选在流程图组件里属于「有它好用、没它也能跑」的功能,实现不复杂。画布空白处按下鼠标记录起点,拖动时画一个半透明的蓝色矩形,mouseup 时遍历所有节点做矩形相交判断,命中的节点加入选中列表。
// 判断矩形相交 function isRectIntersect(a, b) { return !( a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y ); } // 框选结束时批量更新选中状态 function finishSelect(selectionRect) { nodes.value.forEach(node => { const isSelected = isRectIntersect(selectionRect, node); updateSelection(node.id, isSelected); }); }矩形相交判断的四个条件,本质是排除四种不存在重叠的情况:选择框完全在节点左边、右边、上边、下边。四个条件都不满足说明两个矩形在横纵方向都有重叠区域,就认为选中。这里的 selectionRect 也需要用 screenToFlow 换算成画布坐标,否则缩放后框选区域和实际鼠标轨迹对不上。
框选之外还有一层状态联动的细节:Shift 键状态下点击节点,是把当前点击的节点加入或移出选中集合,支持多次点击多选;不带 Shift 点击,则清空之前的选中集合再选中当前节点。这块逻辑放在 store 的 action 里,渲染层只负责调用,避免节点组件之间互相改状态。
4. 快速运行:安装、最小 Demo 与常用配置速查
架构和核心代码看完了,现在让它跑起来。这一章按实际接项目的节奏走:先装包,再搭最小 Demo,然后是自定义类型和常用配置,最后给几个容易犹豫的落地决策。
4.1 安装与包结构
包本身不依赖额外的大型第三方库,和 Vue 项目集成比较直接。在项目根目录执行安装命令:
npm install vue-super-flow装完之后先翻一下 node_modules 里的包结构,别急着写页面。重点关注这几个文件:入口文件负责导出组件和默认配置;核心模块文件里有组件注册逻辑;样式目录中的核心样式文件包含节点默认样式和画布容器样式。示例目录通常会有一个最小 Demo,直接运行能最快看到效果。
4.2 最小 Demo:跑通一张三节点两连线的图
安装完成后写一个最小的画布。最简做法是引入组件并提供 nodes 和 edges 两个数据:
// App.js import { FlowCanvas } from 'vue-super-flow'; export default { components: { FlowCanvas }, data() { return { nodes: [ { id: 'a', type: 'default', x: 100, y: 100, width: 160, height: 60, data: { label: '开始' } }, { id: 'b', type: 'default', x: 400, y: 100, width: 160, height: 60, data: { label: '处理中' } }, { id: 'c', type: 'default', x: 400, y: 300, width: 160, height: 60, data: { label: '结束' } } ], edges: [ { id: 'e1', source: 'a', target: 'b', sourceHandle: 'output', targetHandle: 'input' }, { id: 'e2', source: 'b', target: 'c', sourceHandle: 'output', targetHandle: 'input' } ] }; } };模板部分只需要一个组件标签:
<FlowCanvas :nodes="nodes" :edges="edges" @node-drag="onNodeDrag" />这几个节点的数据里,最容易写错的是 handle 名称。默认节点有四个锚点,分别叫 input(左)、output(右)、top(上)、bottom(下),连线里 sourceHandle 和 targetHandle 必须和节点暴露的锚点名对得上,否则连线会渲染成一条起点错位的曲线。
能看能拖之后,下一步就是监听事件把变化同步回来。node-drag 事件在拖拽结束时触发,回调参数里是节点 id 和新的坐标,把值写回数据就是一个完整的编辑闭环。
4.3 注册自定义节点类型
流程图组件内置的节点只能展示文本,真实业务一定需要自定义外观。类型注册的入口在 config 里挂一个 nodeTypes 映射:
// 注册表方式 const nodeTypes = { staff: StaffNode, // 审批人节点 condition: ConditionNode // 条件节点 }; <FlowCanvas :nodes="nodes" :edges="edges" :node-types="nodeTypes" />节点对象的 type 字段值和注册表的 key 一一对应,渲染器会根据 type 找到对应组件去渲染。如果节点 type 在注册表里找不到,通常会退回到默认样式,这也是排查「自定义节点为什么不显示」的第一个检查点。
4.4 常用配置与事件速查
集成的下一步是摸清配置项。组件提供的配置基本围绕「能不能拖、能不能缩放、节点长什么样」展开。常用配置如下:
| 配置项 | 作用 | 典型值 |
|---|---|---|
| nodes | 节点数组 | [{ id, type, x, y, width, height, data }] |
| edges | 连线数组 | [{ id, source, target, sourceHandle, targetHandle }] |
| node-types | 自定义节点类型注册表 | { staff: StaffNode } |
| edge-types | 自定义连线类型注册表 | { smooth: SmoothEdgeComponent } |
| selectable | 是否允许选中节点 | true / false |
| draggable | 是否允许拖拽节点 | true / false |
| zoom-on-scroll | 滚轮缩放开关 | true |
| pan-on-drag | 空白处拖拽平移画布 | true |
事件方面,核心的几个包括 node-drag(节点拖拽结束)、node-click(点击节点)、edge-click(点击连线)、node-dblclick(双击节点,常用于编辑文本或打开配置面板)。集成时把回调函数传给组件即可,参数都包含操作对象和当前画布坐标。
4.5 接进真实业务时的两个决策点
第一是数据如何持久化。画布产生的是 JSON 数据,后端只需要一个字段存字符串。常见做法是监听 node-drag 和 edge-click 之后把 nodes、edges 整体序列化存到服务端,页面加载时反序列化回传。这里建议整体读写而不是逐条同步,图数据是一个整体,逐条同步很容易出现节点已删但连线还在引用的情况。
第二是组件与弹窗的集成。如果流程图放在弹窗或抽屉里使用,需要注意画布初始化时的容器宽度。有些组件在容器还是 display:none 时初始化,拿到宽度为 0,节点会渲染到错误位置泄漏出滚动条。常见处理是等弹窗动画结束之后再创建画布实例,或者手动调用一次重绘方法,避免首帧就歪掉。
5. 常见问题排查:坐标错位、拖拽失灵与事件丢失的五个坑
流程图组件是出 bug 最「玄学」的一类组件,很多问题看着像随机发生,实际都指向同一批根因。下面五条是集成和自用过程中反复踩过的坑,每一条都按现象、原因、解决三部分说明,对照排查能省不少时间。
5.1 现象:节点拖到一半突然弹回原位
拖拽过程中节点先跟着鼠标走,松开鼠标瞬间弹回拖前位置。原因多半是 node-drag 事件发出的位置数据没有被正确写回数据源,或者写回了但组件没有感知到更新。常见场景是父组件里做了浅拷贝,把整个 nodes 数组重新赋值,导致子组件持有的引用和新数组脱节。
解决方法是拖拽结束回调里先深拷贝一份 nodes 数据,更新对应节点坐标后再整体赋值回去:
function onNodeDrag({ id, position }) { // 整体替换节点对象,而不是原地改 n.x nodes.value = nodes.value.map(n => n.id === id ? { ...n, ...position } : n ); }检查时可以打开控制台在 node-drag 回调里打日志确认坐标值有没有变,如果变了但视图没跟着变,问题出在响应式链路;如果根本没触发回调,问题在事件绑定。
5.2 现象:连线不在两个节点之间,跑到画布左上角
连线端点指向(0,0)附近,或者起点终点都堆在奇怪的位置。通常不是 edge 数据写错,而是渲染连线时拿的是原始坐标,没有经过 viewport 的换算。缩放或平移过画布之后,屏幕坐标和画布坐标不再等价,而连线锚点位置是基于画布坐标算出来的,和经过 transform 变换后的画布节点位置不一致,视觉上就错位了。
解决方法是检查渲染链路的坐标有没有统一到一个坐标系。节点用画布坐标定位,连线锚点也必须用同一套坐标,渲染层通过给画布容器加 transform 实现缩放平移,所有子元素本身不感知缩放。排查时把 viewport.scale 临时改成 1、translate 改成 0,如果连线立刻对齐,就是换算问题。
5.3 现象:缩放之后,点击节点没有任何反应
画布缩小到某个比例后,点击节点事件失灵。大部分情况不是事件丢了,而是点击位置没算对。节点渲染时经过缩放,视觉位置变了,但命中区域是按未缩放坐标计算,鼠标实际点到的地方不在命中区域内。本质还是坐标换算未闭合。
解决方法是把鼠标事件的 clientX/clientY 统一用 screenToFlow 换算到画布坐标,再和节点的 x/y 做命中判断。给命中检测补齐换算公式之后,这类问题基本一次性消失,不会再因为缩放比例不同时好时坏。
5.4 现象:框选的虚线和真实选中区域错位
按住鼠标拖框时,框选矩形显示在一个位置,实际选中的是屏幕另一侧的节点。这属于 selectionRect 没有做坐标系换算,直接把屏幕坐标投到了画布坐标系里。缩放比例越大错位越明显,不缩放时反而正常,是典型的坐标系混用故障。
解决方式是框选的起点、实时更新点、结束判定全部走同一套坐标。框选矩形本身用屏幕坐标绘制没问题,但拿它和节点坐标比较之前必须换算成画布坐标。判断时打印 selectionRect 和节点坐标各打一份,对比数值是不是同一个坐标系下的值。
5.5 现象:删除节点后,连线的端点还留在原地
删除节点后与之相连的连线没有自动消失,或者继承删除节点的位置变成一条残线。根因是 store 里只删了节点,没有同步清理引用该节点 id 的连线,edge 在渲染时通过 source 找不到节点,退回到了坐标兜底值。
解决方法是删除节点时级联删除相关连线。写一个 removeNode 的 action,先遍历 edges 把 source 或 target 等于该节点 id 的边全部移除,再删除节点本身。同时建议在数据初始化时做一次校验,清理所有引用不存在的边的数据,把修复放在数据入口,而不是渲染出口。
这五条坑归纳起来其实就是一件事:坐标系换算要统一、删除数据要整体考虑关联。排查时先分清现象发生在哪个环节,再对着这条链找,大多能在十分钟内定位。
6. 二次开发扩展:自定义节点与双击编辑的落地姿势
组件能用只是开始,真放进业务一定会需要定制。最常改的两个点是节点外观和交互行为,分别给方案。
6.1 自定义节点:注册一个业务类型的渲染组件
自定义节点是流程图组件最常用到的能力。做法是写一个组件,接收 node 对象作为 props,然后挂进 nodeTypes 注册表:
// StaffNode.js export default { props: { node: { type: Object, required: true } }, template: ` <div class="staff-node" :class="'status-' + node.data.status"> <span>{{ node.data.label }}</span> <span class="staff-node__status">{{ node.data.status }}</span> </div> ` };自定义节点组件自己负责背景、边框、内部业务字段展示,数据来源在 node.data 里,样式可以完全脱离默认样式。注意保持节点外层尺寸和 store 里 node 对象的宽高一致,否则锚点位置和连线会对不齐。
6.2 交互扩展:双击编辑与事件回调的落地姿势
交互定制里最常见的需求是双击编辑名称。实现思路是给节点根元素绑定 dblclick,进入编辑模式后用 input 替换文本,失焦时把新值写回 node.data 并退出编辑模式。这个功能完全在自定义节点内部完成,不需要改组件核心。
二次开发里我一般坚持一条原则:画布相关的通用功能(拖拽、缩放、选中)尽量用组件内置能力,业务相关的能力(节点内容、右键菜单、数据校验)放在自定义节点和事件回调里。两条线分清楚,后续升级组件版本时自定义部分不会大面积冲突。
从那以后,我每次在项目里引入这类可视化组件,第一件事就是把缩放、平移、拖拽、框选这几个基础交互各写一个冒烟用例,确认坐标换算闭环是通的,再往上叠业务。希望帮到你。
本文还有配套的精品资源,点击获取