尧图精选

Vue3 + AntV X6 流程图编辑器源码解析:从拖拽到保存全流程

🕒 发布时间:2026/9/9 22:53:12 📁 来源:尧图网络
简介基于Vue3tselement-plusAntV X6实现的流程图编辑器源码面向希望掌握现代前端图形编辑技术的中高级开发者展示了Vue3、TypeScript、Element Plus与AntV X6协同构建可视化编辑工具的整体思路对实际项目落地有直接参考价值。资源共56个文件约10.06MB包含20个ts核心逻辑文件、8个vue组件文件、4个json配置以及png、svg、webp等图标素材、css样式和工程配置文件路由、状态管理与静态资源模块划分明确目录结构清晰方便拆解学习。源码覆盖节点拖拽、连线动态调整、画布布局渲染等完整交互流程可借此学习Vue3组合式API的模块化封装、TypeScript类型约束下的数据流设计以及AntV X6中节点与边的自定义和事件响应机制。同时还能从自动导入配置、环境变量文件与构建配置中理解工程化开发的环境切换和依赖优化策略。已有3306人学习适合需要快速上手或二次开发流程编辑器的前端工程师参考。 流程图编辑器这种需求看起来很像官方文档里的“标准示例”但真要在 Vue3 后台管理系统里落地坑远比想象中多。我最近用 Vue3 ts element-plus AntV X6 从零实现了一套流程图编辑器源码拖拽节点、连线、属性配置、撤销重做、JSON 保存一整套流程全都跑通了。这篇文章把源码里的核心模块怎么拆、数据模型怎么定、X6 怎么和 Vue3 组件体系揉合、Element Plus 表单怎么嵌进节点交互以及实际踩坑记录一次性说清楚。如果你正在做审批流、工单流、低代码编排这类需求或者准备在项目里引入 AntV X6可以直接对照文中的结构和思路如果你已经在用其他画布类库也可以从问题排查那部分提前避雷。1. 项目定位与技术选型1.1 流程图编辑器要解决的业务问题真实业务里的流程图编辑器通常不是独立 App而是后台管理系统里的一个模块。用户是运营、审批管理员这类非技术角色他们需要可视化编排流程比如“开始 - 提交申请 - 主管审批 - 条件分支 - 结束”。这类需求拆开看第一版必须解决的点很明确从左侧物料面板拖入节点到画布节点能移动、删除节点之间可以连线连线规则要能限制不能乱接点击节点后右侧能编辑业务字段比如审批人、超时时间、分支条件支持撤销、重做、保存 JSON、导出图片这些需求决定了后续的数据模型和组件边界。如果只是做静态展示用 SVG 直接画就行要做成可编辑、可持久化的工具就必须引入一个成熟的图交互内核不然连“线是否可连接”这种问题都要自己写命中检测成本会失控。1.2 技术选型X6、Vue3、TS、Element Plus 如何搭配当时对比过几种方案最终选了 AntV X6。核心原因是 X6 不只是画图库它更像一个“图编辑器内核”内置了网格、对齐线、框选、小地图、历史记录、拖拽插件很多交互能力不用自己造。LogicFlow 更适合审批流场景但自定义复杂节点时不如 X6 自由vue-flow 轻量但拖拽编辑和连线规则基本要靠自己补jsPlumb 在旧项目里可能有存量但整个编辑器生态已经很老了。库上手成本扩展性内置交互适合场景AntV X6中高强通用图编辑、自定义节点、复杂连线规则LogicFlow低中强审批流、基础图形编辑vue-flow低中弱轻量关系图、树图展示jsPlumb中低弱有历史包袱的旧项目页面框架选了 Vue3 TS Element Plus这套组合在后台管理系统里属于标准搭配。Vue3 的组合式 API 和 X6 这种命令式 API 放在一起很顺手TS 能对节点数据做强约束Element Plus 则直接解决了右侧属性面板、弹窗、表单这些 UI 成本。Vue3 官方生态现在也默认推荐 setup 语法糖和 TS后续招人或维护都更容易。2. 源码结构与数据模型设计2.1 目录结构让编辑器代码不变成一个大杂烩编辑器项目最容易犯的错是“所有 X6 代码都堆在 index.vue 里”。第一次能跑第二次加需求就很痛苦。我把代码按职责拆成了几个独立模块核心目录大概是这样src/ ├── views/ │ └── flow/ │ └── index.vue # 页面入口 ├── components/ │ └── FlowEditor/ │ ├── FlowEditor.vue # 编辑器主组件 │ ├── FlowGraph.ts # X6 Graph 封装 │ ├── NodeMaterial.vue # 左侧物料面板 │ ├── PropertyPanel.vue # 右侧属性面板Element Plus │ ├── Toolbar.vue # 顶部工具栏 │ ├── nodes/ │ │ ├── StartNode.ts # 开始节点注册 │ │ ├── ApproveNode.ts # 审批节点注册 │ │ └── ApproveNodeComponent.vue # 审批节点渲染内容 │ ├── edges/ │ │ └── edgeConfig.ts # 边样式和连线规则 │ └── hooks/ │ ├── useEditor.ts # 编辑器状态 │ └── useHistory.ts # 撤销重做封装 ├── types/ │ └── flow.ts这个目录最大的优点是把 X6 的命令式注册逻辑和 Vue 的声明式模板拆开了。nodes目录下每个文件只负责注册一种节点和它的 Vue 组件FlowGraph.ts只负责 Graph 生命周期页面里不直接写graph.on事件而是通过 hooks 统一管理。后面接新节点类型基本不需要动主组件。2.2 从 TS 类型开始定义流程数据流程图编辑器最怕“数据类型定义混乱”。我把流程数据抽象成三个核心接口节点元数据、边的元数据、整体流程结构。export type NodeKind start | approve | condition | end; export interface FlowNodeData { kind: NodeKind; label: string; description?: string; config: Recordstring, any; } export interface FlowNodeMeta { id: string; shape: string; x: number; y: number; data: FlowNodeData; } export interface FlowEdgeMeta { id: string; source: string; target: string; sourcePortId?: string; targetPortId?: string; label?: string; data?: Recordstring, any; } export interface FlowSchema { nodes: FlowNodeMeta[]; edges: FlowEdgeMeta[]; }config用Recordstring, any是为了让每种节点有差异化的业务配置但在属性面板里必须用 schema 约束它不能真的到处any。保存时我建议维护自己的FlowSchema而不是直接把 X6 的 Cell JSON 原样存库因为 X6 内部结构可能会有版本迁移问题自己维护一份业务模型会安全得多。2.3 X6 画布初始化与常用配置Graph 初始化放在FlowGraph.ts里关键配置如下import { Graph } from antv/x6; import { History } from antv/x6-plugin-history; import { Selection } from antv/x6-plugin-selection; import { Snapline } from antv/x6-plugin-snapline; import { Dnd } from antv/x6-plugin-dnd; export function createFlowGraph(container: HTMLElement): Graph { const graph new Graph({ container, autoResize: true, grid: { visible: true, size: 10 }, panning: true, mousewheel: { enabled: true, modifiers: [ctrl, meta] }, connecting: { snap: { radius: 20 }, allowBlank: false, highlight: true, }, selecting: { enabled: true }, }); graph.use(new History({ enabled: true })); graph.use(new Selection({ rubberband: true })); graph.use(new Snapline({ enabled: true })); return graph; }autoResize会监听容器尺寸变化避免页面缩放后画布大小不对panning配合鼠标滚轮实现类似地图拖拽的体验connecting.snap让连线落点自动吸附。插件这里我用了 History、Selection、Snapline、Dnd后面用的时候再逐个注册会更加稳妥。3. 核心功能实现拖拽、连线、撤销重做3.1 物料面板拖拽生成节点的实现左侧物料面板的基本思路是面板里每一项都是可拖拽标签拖拽开始时创建一个 X6 节点然后交给 Dnd 插件去管理后续的 drop 位置。const dnd new Dnd({ target: graph, scaled: false, }); function onDragStart(kind: NodeKind, event: DragEvent) { const node graph.createNode({ shape: mapShape(kind), data: { kind, label: defaultLabel(kind), config: {}, }, }); dnd.start(node, event); }物料面板里的元素需要设置draggable并在dragstart中调用onDragStart。这里有个细节dnd.start会接管当前拖拽事件节点不会先被插入画布只有拖拽结束才会真正落到画布上。所以不需要额外处理 drop 事件去“再添加一次节点”否则会出现重复节点。3.2 自定义节点与 Vue3 组件揉合X6 默认的矩形、圆形节点很难满足业务需求。我需要审批节点里显示标签、状态、审批人头像等复杂内容所以使用了官方 Vue shape 插件把 Element Plus 组件直接放进节点里。import { register } from antv/x6-vue-shape; import ApproveNodeComponent from ./ApproveNodeComponent.vue; register({ shape: vue-approve-node, width: 200, height: 72, component: ApproveNodeComponent, });对应的ApproveNodeComponent.vue内部可以这样拿节点数据script setup langts import { computed } from vue; import type { Graph, Node } from antv/x6; const props defineProps{ node: Node }(); const data computed(() props.node.getData() as FlowNodeData); /script template div classapprove-node mousedown.stop el-tag sizesmall :typedata.kind approve ? primary : info {{ data.label }} /el-tag span{{ data.description }}/span /div /template最关键的是在节点组件根节点加mousedown.stop否则点节点上任何一个按钮或表单控件都会冒泡到画布触发节点拖拽。我第一次接入el-select时下拉框死活打不开就是被画布的拖拽事件吃掉了。遇到这种问题优先检查冒泡。3.3 连线规则与边样式控制连线不能“从任何端口连到任何端口”我需要限制开始节点只能出线不能进线结束节点只能进线不能出线同一个节点不能自连。X6 提供了validateConnection可以在初始化时配置connecting: { validateConnection({ sourceCell, targetCell, sourceMagnet, targetMagnet }) { if (sourceCell targetCell) return false; if (!sourceMagnet || !targetMagnet) return false; if (sourceCell.shape vue-end-node) return false; if (targetCell.shape vue-start-node) return false; return true; }, }边样式统一配置成曼哈顿路由和平滑连接避免连线直接从节点身体中间穿过graph.setDefaultEdge({ router: manhattan, connector: smooth, attrs: { line: { stroke: #c0c4cc, strokeWidth: 2 }, }, });manhattan路由会自动绕开节点对复杂流程图特别有用。如果你希望连线带箭头可以在defaultEdge里配置sourceMarker和targetMarker让视觉更清晰。3.4 撤销重做与历史记录撤销重做我直接用 X6 的 History 插件它会把节点的增删改、连线的增删等操作自动记录下来用起来也很简单graph.use(new History({ enabled: true })); function undo() { graph.undo(); } function redo() { graph.redo(); } graph.on(history:change, () { // 更新工具栏按钮禁用状态 });但要注意History 插件默认记录的是画布结构变化节点内部业务数据比如node.setData修改配置不一定会被完整记录。如果你修改了某个审批节点的表单字段然后点击撤销画布位置可能没有变化。这种情况建议自己再维护一个业务快照或者在修改节点数据前调用graph.history.beginCommand把业务变更包成一个命令。小规模编辑器可以偷懒用快照方案每次属性面板提交时把FlowSchema推进数组撤销时整体graph.fromJSON恢复。4. 业务集成、性能优化与常见问题排查4.1 Element Plus 属性面板与节点数据同步点击节点后右侧显示属性面板面板里是 Element Plus 表单。实现逻辑很直接监听node:click把当前选中节点 ID 存到响应式变量属性面板根据节点 ID 拿到节点实例和getData()数据表单修改后调用setData()写回节点graph.on(node:click, ({ node }) { selectedNodeId.value node.id; });属性面板内部script setup langts const props defineProps{ graph: Graph | null; nodeId: string; }(); const form refFlowNodeData | null(null); watch([() props.graph, () props.nodeId], () { const node props.graph?.getCellById(props.nodeId); if (node) { form.value JSON.parse(JSON.stringify(node.getData())); } }); function save() { const node props.graph?.getCellById(props.nodeId); node?.setData(form.value); } /script表单先深拷贝一份数据再绑定v-model提交时才写回节点。如果直接绑定节点原始数据每敲一个字符都会触发 X6 节点重绘输入框会卡顿。父组件和属性面板之间就用defineProps和defineEmits正常通信defineEmits用来通知父组件“属性变了”让父组件统一触发保存逻辑这样不会把业务校验散落在各个节点组件里。4.2 大画布和频繁渲染的优化思路流程图节点超过两三百个时自定义 Vue 节点的数量会明显拖垮交互我实测下来最有效的几个手段批量操作时调用graph.freeze()冻结画布操作全部完成后graph.unfreeze()避免中间过程反复重绘不要一开始就创建几百个节点再逐一点亮先用graph.fromJSON整体渲染自定义节点内部尽量减少响应式数据层级节点组件里只读node.getData()不要在外面用reactive包一层编辑状态如果节点卡片里有复杂列表建议用纯字符串简化为标签展示不要把大表格直接塞进 X6 节点autoResize虽然方便但如果页面频繁切换布局建议自己在容器外层用ResizeObserver手动触发graph.resize防止容器监听失效。4.3 常见问题速查表下面这几个问题是我在开发中真实遇到过的整理成表方便对照排查问题现象解决办法Dnd 拖入节点位置偏移节点 drop 后离鼠标位置很远检查容器定位拖拽开始时传原始事件给dnd.startElement Plus 组件在节点内无法点击按钮点击没反应下拉打不开节点组件根节点加mousedown.stop mouseup.stop阻止冒泡Vue shape 注册后刷新白屏页面刷新后自定义节点不渲染确保app.use(ElementPlus)在应用创建前完成必要时把 Vue 实例上下文传给 X6撤销重做后属性面板不刷新节点位置变了表单还是旧数据监听history:change并更新选中节点 ID或给面板加版本号 key导出图片空白导出 PNG 时背景透明或全白先等字体和图片资源加载完再调用导出图片域名要允许跨域TS 语言服务频繁崩溃VSCode 提示“js/ts 语言服务已立即崩溃 5 次”把类型声明按模块拆分别让单个 d.ts 文件变得巨大给工作区指定新版 tsdk导出图片空白的问题特别隐蔽。如果有节点背景用了 CSS 渐变或外部图片canvas 会受跨域资源污染导出时就可能空白。解决方案是把外部图片转成 base64 或 blob 后再作为节点背景。5. 后续扩展与个人实战经验5.1 从流程图编辑器到低代码流程平台这一版源码稳定后我在它基础上继续扩展了几个能力自动布局、小地图和流程校验。自动布局可以直接引入antv/layout的 dagre 布局让老流程重新排列后更规整小地图用 X6 官方antv/x6-plugin-minimap对长流程非常友好流程校验会在保存前检查是否有孤立节点、是否存在没有出口的中间节点。如果打算做成流程平台这些扩展越早设计越好否则后面数据模型再改会很痛苦。5.2 几点实操经验最后分享几个我实际开发中总结的做法不要一开始就做“万能节点”。先定义两三种节点跑通“拖拽-连线-保存-还原”这条主线等闭环稳定了再抽象公共渲染逻辑X6 核心版本和插件版本必须保持一致差一个 minor 版本都可能在graph.use()出诡异问题Element Plus 图标在 Vite 项目里建议用自动注册插件不要手写几十个图标组件逐个 import维护成本低很多graph.fromJSON前先graph.clearCells()避免旧数据残留业务层对 X6 不要封装过度它就是很成熟的编辑器内核薄薄包一层工具函数就够了包太厚新同事维护起来会很崩溃我在多次改版里体会最深的一点是流程图编辑器的难点从来不在“画出节点”而在“数据模型是否稳定、交互状态是否可追踪、扩展是否容易”。把这三件事在架构阶段想清楚后面每个功能做起来都会顺很多。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →