3 个修复,让 LogicFlow 的 BPMN 文件保存回显不再翻车
【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlow
在 LogicFlow 里做 BPMN 保存与回显,很容易撞上这么几件事:导出的文件再打开,节点全漂了;自定义属性没了;连线对不上号。别急着怀疑画布,这三个问题都出在数据转换层,而转换层里每一处都有明确的修法。
一分钟自检
先对号入座,找到你踩的是哪一坑,再跳到对应章节。
| 回显时的现象 | 可能原因 | 对应修复章节 |
|---|---|---|
| 节点整体平移了一段,尺寸越大偏得越远 | 中心坐标与左上角坐标之间没做补偿 | 修复 1 |
| 审批人、超时时间等自定义属性消失或结构错乱 | 导出时没声明 retainedFields,字段被当成 XML 子节点 | 修复 2 |
| 带并行网关的复杂流程,连线错乱或节点"消失" | outgoing 写在了 incoming 前面,引用关系没建起来 | 修复 3 |
为什么存下来的文件会"对不上"
LogicFlow 里一个节点的位置,用的是中心点坐标;而 BPMN 文件里dc:Bounds记录的是左上角坐标。同一份图,两套坐标系。
打个比方:这就像两张地图用了不同的原点。一张以"城市中心"为原点标注位置,另一张以"地图左上角"为原点。你拿着 A 地图的坐标直接去 B 地图上钉图钉,整座城市都会平移一段,而且楼越大(节点越大),钉得越歪。
所以转换必须成对做:导出时把中心坐标减去半个宽高,写成左上角坐标;导入时再加回来。每个节点类型该补偿多少,取决于它的宽高,这些尺寸提前注册在shapeConfigMap里,开始事件、用户任务、网关各有一条。
除了坐标,文件往返还会经过另外两道关卡:自定义属性是写成"属性"还是"子节点",以及节点之间 incoming/outgoing 引用关系的书写顺序。任何一关处理得不对,就会出现"文件能打开,但图全变了"的情况。
修复 1:补上坐标偏移,节点不再漂移
症状:重新打开文件后,所有节点整体平移了半个节点宽高,图往一个方向"飘"。
原因:导入时把 BPMN 的左上角坐标直接当成中心坐标去渲染了(或者导出时忘了反向处理)。两个坐标系没换算,偏差量恰好等于半宽、半高。
// 导入方向:左上角坐标 -> 中心坐标 const shapeConfig = BpmnAdapter.shapeConfigMap.get(type) if (shapeConfig) { x += shapeConfig.width / 2 // 水平方向补偿 y += shapeConfig.height / 2 // 垂直方向补偿 }导出方向是反的:写入dc:Bounds前,x、y 各减去width / 2和height / 2。两个方向都要改,只改一边等于白改。
容易踩的坑:补偿量依赖shapeConfigMap里的登记。你如果扩展了自定义 BPMN 节点类型,却忘了登记它的宽高,这类节点就会用错补偿量——别的可能全对齐,就它独自在画布上漂移。
修复 2:用 retainedFields 留住自定义属性
症状:保存前节点 properties 里明明有assignee、timeout,回显后这些字段丢了,或者变成了一个奇怪的子节点。
原因:转换到 XML 时,字段值如果是对象或数组,默认会被当作 XML 子节点处理,而不是属性。结构一变形,回来时对不上原来的字段。把要保留的字段显式声明出来,它们就会以属性形式安全往返。
// adapterOut 第二参即 retainedFields:对象/数组值也按属性写出 const xml = lf.adapterOut(graphData, ['assignee', 'timeout']) // 内部会与默认的 ['properties','startPoint','endPoint','pointsList'] 合并容易踩的坑:只声明了的字段才受保护。业务字段多的时候,建议一次性把所有字段列全,而不是丢一个再补一个——因为每个漏网字段,都会让某一批流程文件悄悄变脏。
修复 3:先写 incoming 再写 outgoing,连线不乱
症状:含并行网关、多条件分支的复杂流程,重新打开后连线接错对象,个别节点看起来"凭空消失"。
原因:BPMN 要求每个节点声明自己的bpmn:incoming和bpmn:outgoing。如果先写 outgoing,此刻目标节点的 incoming 还没填好,部分解析器就建立不了连接关系,流程结构直接解析错。
// 第一遍:回填目标节点的 incoming(空->写入,单值->升为数组) data.edges.forEach((edge) => { const targetNode = nodeMap.get(edge.targetNodeId) targetNode['bpmn:incoming'] = targetNode['bpmn:incoming'] ?? edge.id // 已是数组则直接 push }) // 第二遍:incoming 全部完成后,再回填源节点的 outgoing完整逻辑见 bpmn-adapter 源码,顺序本身就是关键,改代码时别动它。
容易踩的坑:一个节点有多条入边时,bpmn:incoming必须是数组。如果第二条边进来时没把字符串升级成数组直接覆盖,回显时就只剩最后那条线。
一键导入导出
下面是最小可用的导出 + 导入链路,参考实现见 bpmn 示例页面。
// 导出:取图数据 -> adapterOut(带保留字段)-> 下载为 BPMN 文件 const handleExport = () => { const data = lfRef.current?.getGraphData() const xml = lfRef.current?.adapterOut(data, ['assignee', 'timeout']) download('process.bpmn', xml) } // 导入:读文件 -> lfXml2Json 把 XML 转成 JSON -> 渲染到画布 const handleImport = (file: File) => { const reader = new FileReader() reader.onload = (e) => { const json = lfXml2Json(e.target?.result as string) lfRef.current?.render(json) } reader.readAsText(file) }修完怎么验证
- 同一份文件导出后立刻重新导入,逐节点对比位置是否完全一致,先看画布左上角第一个节点,它偏多少就是补偿错多少。
- 检查
assignee、timeout等自定义属性的值是否原样回到 properties 里。 - 画一条"1 个开始节点 + 2 条分支 + 1 个网关"的流程再导出,确认每条线的起点终点没有互换。
- 随机拖动几个节点、缩放窗口后再保存一次,确认坐标不会累计漂移。
- 把导出的文件上传到官方在线校验工具 demo.bpmn.io,能被标准 BPMN 工具正常打开,说明结构合规。
收尾:一张表记住三处修复
三个修复都发生在转换层,改一次,以后所有文件的保存回显都会受益。
| 知识点 | 关键动作 |
|---|---|
| 中心坐标 vs 左上角坐标 | 导出减半宽高、导入加半宽高,两个方向都补偿 |
| 自定义属性丢失 | adapterOut时传入retainedFields声明保留字段 |
| 连线引用顺序 | 先遍历写incoming,再遍历写outgoing,多值升为数组 |
| 往返链路 | 导出走adapterOut,导入走lfXml2Json+render |
后续如果要用setCustomShape扩展自定义 BPMN 节点,记得同时把它的宽高登记进shapeConfigMap,坐标补偿才能对它生效。
【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考