Flow Editor 重构需求与安全迁移方案
1. 背景
现有项目 vue-flow-editor 已经长期稳定承载业务,但底层技术栈混杂、构建链老旧,继续在原基座上升级的风险越来越高。当前代码同时存在以下问题:
- 图编辑内核基于 G6 3.x 时代的能力与自定义 behavior/shape 体系。
- 工程基座仍是 Vue CLI 风格,难以承接后续现代化构建与测试。
- UI 侧存在
element-ui与element-plus混用痕迹。 - 编辑器能力并不只是“画图”,还包含命令系统、插槽扩展、右侧属性面板、预览、业务节点配置等完整交互闭环。
因此本项目不是“升级依赖”,而是一次有严格兼容目标的重构:在新仓库 vue-flow-editor2 中,以 Vue 3 + Element Plus + LogicFlow 2.0 + Vite 重建流程编辑器,并通过自动化测试最大限度保住现有业务行为。
2. 项目目标
2.1 核心目标
- 新建一个可持续维护的流程图编辑器项目,技术栈统一为 Vue 3、Element Plus、LogicFlow 2.0、Vite。
- 对老项目关键业务行为保持一致,优先保证“用户怎么操作、系统怎么响应”不变。
- 在迁移前期先建立自动化回归护栏,再开始大规模功能迁移。
- 新项目在任一阶段都应可运行、可验证,而不是等到全部完成后再一起联调。
2.2 非目标
- 不追求源码结构 1:1 复刻。
- 不追求一次性把所有业务定制表单都重写完再测试。
- 不把这次重构变成 UI 风格大改版项目。
- 不在没有测试护栏的情况下直接全量替换老编辑器。
3. 兼容范围定义
本次迁移应以“行为兼容”作为验收标准,至少覆盖以下维度。
3.1 对外 Props 能力
老项目当前已暴露的重要输入包括:
datagridminiMapdisabledDragEdgedisabledUndoheighttoolbarHeightmenuWidthmodelWidthonReftoolbarButtonHandlerloadingmultipleSelectbeforeDeleteafterDeletebeforeAddafterAddactivityConfigcontrolConfig
3.2 对外事件
老项目已对宿主页面透出的关键事件包括:
click-canvasdrag-canvasclick-node-mousedownclick-nodedblclick-nodedragend-nodeclick-edgedblclick-edgeselect-change
3.3 对外实例 API
通过 onRef 暴露的能力至少包括:
editorStatecommanderopenModelcloseModeladdNodeupdateModelopenPreviewreadclearStates
3.4 用户操作能力
根据 README 与现有实现,必须优先保留的用户操作有:
- 工具栏控制:网格、缩略图、适应画布、实际尺寸、放大、缩小、删除、预览、撤销、重做
- 画布选择:单选、多选、框选、全选
- 节点操作:拖拽添加、拖拽移动、双击编辑、删除
- 连线操作:拖拽创建边、边选择、边删除
- 右侧编辑面板:宿主自定义表单内容展示与关闭/打开
- 预览能力:按当前图数据输出预览
4. 对旧项目的审计结论
目前可以明确的结论如下:
- 老项目是“组件库 + demo 文档页”的形式,不是单页应用直接 hardcode。
- 编辑器稳定性的核心不在样式,而在
commander + graph event + before/after hook这一套行为层。 -
src/editor/editor.ts已经将关键操作抽成命令:undo、redo、switchGrid、switchMiniMap、fitView、actualView、zoomIn、zoomOut、delete、drag、selectAll、addEdge、addNode、update。 -
src/behavior负责交互行为编排,包括拖拽节点、点击选择、锚点悬浮、拖拽连线、框选、拖动画布。 -
doc/index.vue是一个高价值基线样板页,已经串起了onRef、事件监听、增删前后钩子、菜单插槽、复杂右侧表单等真实用法,适合作为自动化基线回归页面。
结论:新项目最应该保护的是“命令语义 + 事件语义 + 宿主接入语义”,而不是老实现里的具体 G6 细节。
5. 可选迁移路径对比
方案 A:直接从老代码重写到新栈
做法:
- 先搭新仓库。
- 直接按老功能清单一口气实现 LogicFlow 版本。
- 最后统一联调和回归。
优点:
- 前期推进快,文档负担最轻。
缺点:
- 风险最高。
- 等到最后才发现偏差时,返工成本极大。
- 无法证明“老功能没有丢”。
结论:不推荐。
方案 B:先搭新壳子,再边迁边手工点
做法:
- 先搭 Vite + Vue3 + Element Plus + LogicFlow。
- 每迁一个能力,人工自己点一遍。
优点:
- 进度感较强。
缺点:
- 依赖人工回归,且用户明确没有办法做大量点击验证。
- 随着功能变多,测试深度必然衰减。
结论:不推荐。
方案 C:先抽行为基线测试,再分阶段替换内核
做法:
- 先对老项目建立可复用的基线测试和 fixture。
- 再在新项目中按“数据层、命令层、交互层、业务层”分批迁移。
- 每迁完一层,就跑同一套或共享大部分逻辑的测试。
优点:
- 风险最低。
- 可以做到“先迁测试,再迁功能”。
- 每一阶段都有明确的退出条件。
缺点:
- 前期准备时间更多。
结论:推荐,且这是本项目最安全的路线。
6. 推荐总体策略
推荐采用“基线优先 + 分层迁移 + 双轨验证”的方式推进。
核心原则
- 先定义兼容契约,再写新实现。
- 先建立自动化回归样板,再迁移核心行为。
- 每一阶段只替换一个主要风险面。
- 数据结构适配优先于交互细节重写。
- 优先保护宿主可感知行为,而不是内部实现完全一致。
双轨验证
双轨验证指:
- 轨道 1:老项目基线测试,负责定义“过去正确是什么”。
- 轨道 2:新项目迁移测试,负责验证“现在是否仍然正确”。
理想状态下,同一份 fixture、同一份操作脚本、同一份断言语义,分别跑在旧实现和新实现上,只允许底层引擎不同。
7. 目标架构建议
新项目建议仍然保留“编辑器组件库 + 示例宿主页”的结构,而不是只做一个业务页面。推荐分层如下:
7.1 工程层
-
apps/demo- 迁移自旧项目
doc/index.vue的演示/基线页面
- 迁移自旧项目
-
packages/flow-editor- 真正的编辑器组件库
-
tests- 跨项目共享的 fixture、回归脚本、视觉快照、契约测试
7.2 编辑器内部层次
-
core/adapter- 旧数据结构与 LogicFlow 数据结构适配
-
core/commands- 命令总线、撤销重做、行为封装
-
core/events- 统一对外事件派发与兼容转换
-
core/schema- 节点/边/锚点/属性 schema
-
features/canvas- 画布初始化、缩放、视图控制、grid、miniMap
-
features/selection- 单选、多选、框选、全选
-
features/edge- 连线创建、校验、edge hook
-
features/node- 节点创建、更新、拖拽、删除
-
features/panel- 右侧属性面板、预览面板、插槽封装
-
compat- 老 props、老事件名、老
onRefAPI 兼容层
- 老 props、老事件名、老
7.3 最重要的设计要求
新项目不要让宿主业务代码直接依赖 LogicFlow 实例。宿主继续依赖兼容层暴露出来的编辑器 API,这样后续底层再演进时成本最低。
8. 分阶段迁移计划
阶段 0:建立旧项目行为基线
目标:
- 把老项目的“正确行为”变成自动化脚本和 fixture。
产出:
- 老项目示例页可稳定启动
- 回归样例数据集
- 第一批 Playwright 场景
- 第一批截图快照
- 第一批命令层契约说明
优先测试场景:
- 初始图渲染
- 工具栏按钮可见性与禁用状态
- 网格开关
- 缩略图开关
- 缩放、适应画布、实际尺寸
- 选择节点/边
- 多选与框选
- 删除节点/边
- 撤销/重做
- 双击节点打开编辑面板
- 拖拽添加节点
- 拖拽创建连线
-
beforeAdd/afterAdd与beforeDelete/afterDelete是否触发 -
onRef暴露方法是否可调用
退出条件:
- 至少 70% 的核心主路径操作已有自动化覆盖
- 至少 3 套真实 fixture 图数据可重复运行
- 老项目测试结果可稳定复现
阶段 1:新项目基础架构与测试底座
目标:
- 在新仓库搭出标准工程骨架与测试设施,但暂不急于追求全部功能。
产出:
- Vite + Vue3 + TypeScript + Element Plus 基础工程
- LogicFlow 2.0 集成验证页
- Vitest + Vue Test Utils
- Playwright E2E
- 统一 fixture 目录
- 统一截图基线目录
- CI 执行脚本
退出条件:
- 新项目示例页可启动
- 测试命令可在本地一键执行
- 至少有 1 个冒烟场景打通
阶段 2:数据兼容与静态渲染
目标:
- 先让新项目能正确读出旧数据并画出来。
产出:
- 旧 G6 数据到 LogicFlow 数据的适配器
- 节点类型、边类型、文本属性映射
- 静态渲染一致性检查
优先验证:
- 读入老 fixture 不报错
- 节点数量、边数量一致
- 关键 label、坐标、类型、业务字段保留
- 预览数据导出格式可控
退出条件:
- 旧 fixture 在新项目中可稳定渲染
- 核心数据往返读写可验证
阶段 3:命令系统与基础交互迁移
目标:
- 优先重建最核心的命令语义和用户主操作。
产出:
- 新版
commander undo/redozoomIn/zoomOutfitView/actualViewdeleteselectAll- 节点拖拽移动
- 单选/多选/框选
验证重点:
- 命令是否能回放和撤销
- 选择状态变化是否正确发出
select-change - 删除前后钩子是否触发且支持阻断
退出条件:
- 主工具栏行为全部通过自动化测试
- 命令栈回退行为稳定
阶段 4:新增节点、连线与 hook 迁移
目标:
- 打通完整编辑链路。
产出:
- 左侧菜单拖入创建节点
- 锚点拖拽创建边
beforeAdd/afterAddupdateModelreadclearStates
验证重点:
- 节点新增后数据正确
- 边新增后数据正确
- 新增失败或校验失败时状态可恢复
- 撤销/重做对新增和连线同样有效
退出条件:
- “新建节点 -> 连线 -> 编辑 -> 删除 -> 撤销/重做”全链路通过
阶段 5:右侧表单、预览和宿主集成迁移
目标:
- 迁移复杂业务接入能力,而不是只保留基础画图。
产出:
-
model插槽 -
menu插槽 -
foot插槽 - 预览弹窗
-
onRef实例 API 兼容 - 示例页迁移版
验证重点:
- 双击节点可打开对应业务表单
- 宿主可通过
onRef调用实例方法 - 工具栏按钮可通过
toolbarButtonHandler定制
退出条件:
- 示例页具备替代旧文档页的主要能力
阶段 6:切换与收尾
目标:
- 从“功能可用”进入“可替换旧项目”的状态。
产出:
- 迁移文档
- 差异说明
- 已知不兼容点清单
- 发布与回滚方案
退出条件:
- 核心回归用例通过
- 关键业务场景通过
- 已知差异可被接受或已补齐
9. 测试迁移策略
这是本项目最关键的部分。推荐将测试分成四层,而且必须先做前两层再做大规模迁移。
9.1 第一层:行为基线端到端测试
工具建议:
- Playwright
用途:
- 在旧项目 demo 页上执行真实点击、拖拽、键盘操作
- 记录主路径行为
- 截图关键状态
为什么必须先做:
- 用户无法手工重复大量点击验证
- 这是最接近真实使用方式的回归定义
9.2 第二层:共享 fixture + 结果断言
内容:
- 建立
fixtures/graphs/*.json - 建立
fixtures/operations/*.json - 把“图数据 + 操作序列 + 预期结果”沉淀成通用规格
示例:
base-approval-flow.jsonmulti-branch-flow.jsonedge-validation-flow.json
预期断言:
- 节点/边数量
- 当前选中项
- 缩放等级
- 钩子触发次数
- 导出图数据结构
意义:
- 未来旧项目和新项目都能吃同一批数据
9.3 第三层:命令与适配器单元测试
工具建议:
- Vitest
范围:
commander- 数据适配器
- hook 包装器
- 事件兼容转换器
重点:
-
undo/redo的队列行为 -
delete/add/update的前后状态恢复 - 旧数据结构到新结构的兼容转换
9.4 第四层:视觉与布局快照
工具建议:
- Playwright screenshot
- 必要时加像素容差比对
范围:
- 默认画布
- 节点选中态
- 多选框选态
- 预览弹窗
注意:
- 视觉快照只做辅助,不作为唯一正确性判断
10. “先迁测试再迁功能”的具体执行法
推荐按下面的顺序:
- 在老项目上补一个最小可运行的自动化测试工程。
- 先把
doc/index.vue作为官方基线页固定下来。 - 先写 8 到 12 条核心操作回归脚本。
- 把核心图数据样本沉淀成 fixture。
- 新项目初始化后,先把这些 fixture 和脚本目录直接复制或共享过来。
- 先让新项目通过静态渲染和简单命令测试,再逐条恢复复杂交互。
- 每迁完一个命令簇,就同时补对应单元测试和 E2E。
这里的关键不是“测试写得多”,而是“测试写得可迁移”。测试必须尽量面向:
- 用户操作
- 对外 API
- 输出数据
- 关键视觉状态
不要让测试过度绑定旧 DOM 结构,否则到了 LogicFlow 后会全部失效。
11. 风险清单与规避方案
风险 1:LogicFlow 与 G6 交互语义不完全一致
规避:
- 通过适配层屏蔽底层事件差异
- 将兼容目标定为用户可感知行为一致,而非引擎内部事件完全一致
风险 2:拖拽、框选、连线是最容易出现细微偏差的区域
规避:
- 优先对这些能力建立 Playwright 操作脚本
- 每次迁移只处理一类交互
风险 3:复杂业务表单耦合在 demo 页面中,容易阻塞底层迁移
规避:
- 将“业务表单展示能力”与“编辑器内核能力”拆开迁移
- 先迁插槽和实例 API,再迁具体表单逻辑
风险 4:撤销/重做在重构后容易悄悄失真
规避:
- 将命令栈行为做独立单元测试
- 针对 add/delete/update/drag 各自建立回放测试
风险 5:想一次做完所有节点类型和业务配置
规避:
- 先做最小节点集
- 用 fixture 优先覆盖真实高频业务节点
- 按业务价值排序,而不是按代码文件排序
12. 第一阶段建议交付物
建议我们下一步先做这几件事,而不是直接开写新编辑器:
- 初始化新项目文档和计划文件。
- 在老项目建立 Playwright 基线测试。
- 提取第一批 fixture:
- 单节点流程
- 线性审批流程
- 带分支流程
- 梳理旧项目对外契约并固化成 checklist。
- 再开始新项目工程初始化。
13. 初始验收标准
只要以下条件满足,就说明这次升级是在“安全轨道”上:
- 老项目已有可重复执行的自动化回归脚本
- 新项目已完成基础工程和测试底座
- 新项目已能渲染老 fixture
- 新项目已恢复核心命令链:选择、缩放、删除、撤销、重做
- 新项目已恢复核心新增链:新增节点、新增边、更新节点
- 新项目已恢复宿主接入链:插槽、事件、
onRef
14. 结论
这次最安全的升级方式不是“直接用新栈重写”,而是:
- 先把老项目变成可测试的行为基线
- 再用新栈分阶段替换底层
- 每一阶段都以自动化测试作为继续推进的前提
如果按这个方案执行,虽然前期会比“直接开写”慢一点,但整体成功率和可控性会高很多,尤其适合当前“业务不能出问题、人工回归能力又有限”的情况。