使用LogicFlow 实现基础的流程图我们上一篇文章已经实现了入口LogicFlow实现流程图那么在这个基础上除了LogicFlow内置的用户任务、系统任务、条件判断的节点类型外我们还需要并行网关和包容网关。但是由于我项目中使用的 LogicFlow 是 v1.1.13 版本在这个版本中没有内置该类型的 BPMN 节点需要自己实现。其实我下载 v2.x 版本看了下依赖包里也没有新增其他类型自定义节点实现1. 安装必要依赖npm install logicflow/core logicflow/extension //指定版本 npm install logicflow/core1.1.13 logicflow/extension1.1.132. 定义组件LogicFlowWidget.tsximport React, { useEffect, useRef, useState } from react import LogicFlow from logicflow/core; import { Control, BpmnElement, SelectionSelect, DndPanel, Snapshot } from logicflow/extension; import logicflow/core/dist/style/index.css; import logicflow/extension/lib/style/index.css; import ./styles.less import TurboAdapter, { toLogicflowData } from ./turboAdapter import { ParallelGateway } from ./ParallelGateway import { InclusiveGateway } from ./InclusiveGateway export const LogicFlowWidget (() { const containerRef useRef(null); const lfRef useRef(null); useEffect(() { if (!containerRef.current) return; const lf new LogicFlow({ container: containerRef.current, // 指定容器ID必传 plugins: [ Control, BpmnElement, SelectionSelect, DndPanel, Snapshot, TurboAdapter ], grid: { visible: true, size: 10, }, keyboard: { enabled: true }, }); lfRef.current lf; // 2. 安全注册函数 const safeRegister (type) { try { // 注册节点 lf.register(type); return true; } catch (error) { console.error(❌ 节点 ${type} 注册失败:, error.message); return false; } }; // 注册自定义节点: 并行网关、包容网关 重要 safeRegister(ParallelGateway) safeRegister(InclusiveGateway) // 设置左侧节点类型 setPatternItems() // 渲染流程图 lfRef.current.render(); return () { lfRef.current?.off(); }; }, []); // 定义节点 const setPatternItems () { lfRef.current.extension.dndPanel.setPatternItems([ { label: 选区, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAOVJREFUOBGtVMENwzAIjKP2026ETdpv10iy7WFbqFyyW6GBywLCv5gIDw2Bluj1znuSjhb99Gkn6QILDY2imo60p8nsnc9bEo3QJAKHfMdZHnl78wyTnyHZD53Zzx73MRSgYvnqgCUHj6gwdck7Zsp1VOrz0Uz8NbKunzAWGu4fYW28bUYutYlzSa7B84Fh7d1kjLwhcSdYAYrdkMQVpsBr5XgDGuXwQfQr0y9zwLdaDUYXLaGKdd2ZTtvbolaO87pdo24hP7ov16N0zArH1ur3iwJpXxmv7oAJNR4JEP8DoAuSFEkYH7cAAAAASUVORK5CYII, callback: () { lfRef.current.extension.selectionSelect.openSelectionSelect(); lfRef.current.once(selection:selected, () { lfRef.current.extension.selectionSelect.closeSelectionSelect(); }); } }, { type: bpmn:startEvent, text: 开始, label: 开始节点, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAnBJREFUOBGdVL1rU1EcPfdGBddmaZLiEhdx1MHZQXApraCzQ7GKLgoRBxMfcRELuihWKcXFRcEWF8HBf0DdDCKYRZpnl7p0svLe9Zzbd29eQhTbC8nv9zf130AT63jvooOGS8Vf9Nt5zxba7sXQwODfkWpkbjTQfCGUd9gIp3uuPP8bZ946g56dYQvnBgb1HB8VIQmMFrazKcKSvFW2dQTxJnJdQ77urmXWOMBCmXM2Rke4S7UAW/8ywwFoewmBps2tu7mbTdp8VMOkIRAkKfrVawalJTtIliclFbaOBqa0M2xImHeVIfd/nKAfVq/LGnPss5Kh00VEdSzfwnBXPUpmykNss4lUI9C1ga8PNrBD5YeqRY2Zz8PhjooIbfJXjowvQJBqkmEkVnktWhwu2SM7SMx7Cj0N9IC0oQXRo8xwAGzQmsxrB/nNSUWVveI48ayrFGyC2E2CaWrZHXvOuzCiV6iycWe1Rd1Q6QUG07nb5SbPrL4426d9E1axKjY3AoRrlEeSQo2Eu0T6BWAAr6COhTcWjRaYfKG5csnvytvUr/WY4rrPMB53Uo7jZRjXaG6/CFfNMaXEu75nG47XoepU7PKJvvzGDY1YLSKHJrK7vFUwXKkaxwhCW3usDFMVrIju54RYYbFKpALZAo7sB6wcKyyrdaBMryMT2gPyD6GsQoRFkGHr14TthZni9ck0zPnmee460mHXbRAypKNy3nuMdrWgVKj8YVV8E7PSzp1BZ9SJnJAsXdryw/h5ctboUVi4AFiCdlQaYMw5z3LGTBKjLQOeUF35k89f58Vv/tGhlPE/wG0rgfIUbZK5AAAAABJRU5ErkJggg, }, { type: bpmn:userTask, label: 用户任务, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0Dg1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQBVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeAYeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQyJjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTbyr31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0HAd2GelJsZz1OJegCxqzlCLVHa/IibuHeJ1HAKzhuDRymNaRFM4jU6UWKXorRmbyqkq/D76FffevwdCpjN3UAN/C9JRVTDuOxC/ohEdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg, }, { type: bpmn:serviceTask, label: 系统任务, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAABgklEQVR4AYyTMTIEQRSG/oTkBFM4AgCApkjqA3EXIBCSiC2ygmIBcsRZBtIHIGqDYTcYPTX7Z95O6a2bNW//d7//vdPvme1K74nTy17cZlxc50hTCXksJv8S09vvVEjKkBqvN3yTE56Iwo7t5Kp889EERM7qXpizR5yPiNXS9GuFM0yU6Ac6/obrIROTEaNkBejAjG0KxJe1vS4bbU5HiowcxcMUJ0sWqruSvZ9LsSLo7yOuxBFer9ZcB5EVI5znH6QVCM4HxjwMDtOqqgdDL3niBQPPCkkD6xgY07vAhF6QSGIDwpj/J8ajjBbFkDEfixlzyKfhDhZfQ8nfPL4GqoyaeHmf1yrHDMmOuJTEYBConGNXk62G60J94Qn0mCxVzhfSDh2wgqKEY3cUgjAE2ngNLjxYPNKgqNuxL5i1Ky7tLxiiBgsV/rMk1SjPBa32CQH4LhvqZ8JNXO8G49XjCjQOMufAYYcADF8BBw1NMAmaDojEsyA4yg071rM4X4AAAD//xNVAsAAAAGSURBVAMAZRDClKgSLb4AAAAASUVORK5CYII, }, { type: bpmn:exclusiveGateway, label: 条件判断, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAAHeEJUAAAAABGdBTUEAALGPC/xhBQAAAvVJREFUOBGNVEFrE0EU/mY3bQoiFlOkaUJrQUQoWMGePLX24EH0IIoHKQiCV0G8iE1covgLiqA/QTzVm1JPogc9tIJYFaQtlhQxqYjSpunuL7JvmUTU3AgmTfvffPNN9WSA1DO182f6xwILzD5btfAoQmwL5KJEwiQyVbSVZ0IgRyV6PTpIJ81E5ZvqfHQR0HUOBHW4L5Et2kQ6Zf7iAOhTFAA8s0pEP7AXO1uAA52SbqGk6h/6J45LaLhO64ByfcUzM39V7ZiAdS2yCePPEIQYvTUHqM/n7dgQNfBKWPjpF4ISk8q3J4nB11qw6X8lFsF3EhlkEMfrjIer3wJTLwS2aCNcj4DbGxXTw00JmAuONi6bBxVUCvS5d9aa04so4pHW5jLTywuXAL7jJD06sl82Sgl2JuVBQn498zkc2bGKxULHjCnSMadBKYDYYHAtsby1EQ5lNGrQd4Y3v4Zo0XdGEmDno46yCM9TkRiJmUYHS/aXHPNTcjxcbTFna000PFJHIVZ5lFRqRpJWk9/QtlOUYJj9HG5pVFEU7zqIYDVsw2sAJaD8wTd2umgSCCyUxgGsS1Y6TBwXQQTFuZaHcd8gAGioE90hlsYwMcs30RduYtxanjMGal8H5dMW67dmT1JFtYUEe8LiQLRsPZ6IIc7A4J5tqco3T0pnv/4u0kyzrYUq7gASuEyI8VXKvB9Odytv6jS/PNaZBln0nioJG/AVQRZvApOdhjj3Jt8QC8Im09SafwdBdvIpztpxWxpeKCCEsFdS8DCyuCn2munFpL7ctHKpXc5cMybeIyMAN33SPL3ZR9QV1XVwLyzHm6Iv0/yeUuUb7PPlZC4D4HZkeu6dpF4v9j9MreGtMbxMMRLIcjJic9yHi7WQ3yVKzZVWUr5UrViJvn1FfUlwe/KYVfYyWRLSGNu16hR01U9IacajXPei0wx/5BqgInvJNMMNtNme7ReU9SBbgntovn0kKHpFg7UogZvaZiOue/q1SBo9ktHzQAAAAASUVORK5CYII, }, { type: bpmn:parallelGateway, label: 并行网关, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAGHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49J7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pg0KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj48dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1wbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz4slJgLAAAC4ElEQVQ4T4WTz29UVRTHPf1jaVia2fECBmYKZay7OCCYokbE3cYNRQTdaPpAClL/gFdQoJuTEykoaQrXNjij0S3Low1lA1lZewMnZnSKLSlqG/5s3jfV3cDjPUSTzJvYtz7/3e7znnzVJok0srYnTExFzSyIWvJ41vs37vNJtu68C4O1OABRXxKmrEbML4rWDxsnDxq/z4p3xiMpq239Bu6KwHCt3ORQXaspdDhVsSOs1afiLUIzVNHQlVCWIdzTqaW0wrIYmYi4WxHHssb0WZ9HW9DhQbILPpiMmPlTHO83pvImWRLmQ3EYexBi85JoOXQi2sxKpHUt9nNWUrWm7Lt1fjZ8yO34lVLmFmQdQWBFnrte5WxGDGWM679P3krFUFeshbNbhwWOR7jVufOIzPGDcLor3rzd75t1fE2cmIubKkMsaP435HHnZUe5KgO5tSfhKuhLGTfPJhgaswWHNiDx8IbmYiYW3Q9fG8Tp5o7IqFtdEKWhOqBS4XDkQL3bB9KjPyaPG7D0HZp0XazqWNr4/51PdhreqrMVgt8BCBpQtrNFT2Bvp/HzmM/bjh9LWKmJLxY7lGnb0iQ6GgvuNaIJTo8qDB7Qg8A3vzy1C//CFOvGp8d84n2WU8XBdm8FdVvDseAfDDeZ8DPe6zfXuNlQ13dmdRDB02vBsfwz3G7cK4r3xiOo2ZJLGoV6jL9Vk15dyuUzSCDadyuURe6Qm7K3v8f4Zmeks0Xx9tX605Fu1SGK3dqqO8DCsjj1dcRcWeSyMJX3OdhrzmvZVBPsdtHJYemRONBjdHfCC89Busco/dNU/mDGmBpNcGSfY/2MRUqBGgy4vd5cWLAmBr1CTbc5Lr3wMg1V05DtA29QRvTloOmDYYD7Vek1Y39YyRC8v/Y9pGVALx4WTETFG8MWDUY7h1r9nY/p1yWqMtEMDfVQf224LwDHJp42beJ937XxCAfwFyluH6QWfjTQAAAABJRU5ErkJggg, }, { type: bpmn:inclusiveGateway, label: 包容网关, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAGHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49J7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pg0KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj48dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1wbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz4slJgLAAAC9klEQVQ4T42UTWxUVRzFf/fN0x0BE4iJLVSkbdCaTkEYsUIQJYgRTQQWhSVjKgJC9i4krARmlLUxM0Qa1wp0oKJMdG0REkMBboojshHOq1lWgalA51p00D7euceF6/ptNMuPMubz83//NxjSSxADJ5sbPVkswIJ9hQYWiLzy7yJReBcArPQDofyjeTVi6B8SackP9SsPvKfHGctgbsF3QSXoyzpFTwbi0KSiJwMNjjg9GHN6rSUQBycVaw7PSjGHKDXsVHsiJFnXHCg7XhzIP3ba/EUgDkxqfXOgdAmZ0bRGfVmxq9XyZ1rUVxvO7PFJZsRvvQ6AN6o8XlluONxu6bwh1lca2uMK54JNTOS1PdQ7G6dIpmGirDsXcitPxaoOOmAzutgQ9bX/L4eGuEposFOmIWJWhbV9IZoZyTjsSlmRaxFYZvt7rcSHAh1/OdY8b9hVF/px/g/H9bvirRqPL3dHOHTOcumWiFUaftzvQxUID4Khc3kndquF0TjhOqaAg3lijoM5ZzqmgLROKHvewoaHnOqPx2IxkltjyQl8yIV1cafjrg89xiwy3w3Ua1nqULylmpnyJoWGtBxY67jiWLTJcNBnc7Xh6l3hOYEfgaf9mZn/jakCTFjwDHgbXzBcTon3EpbsuHh7tQcnO1x3MsXw3cvL872OPBh4se90fFjoTl2oCIlhnMP6NOO7yXEmJTasNrXt9Gs9ZLt7UgmJve9nj9AcR9n1r6e4X0YpQFiNJ6RGx5xvLlV6xpcbwyfYIn3YU6Lw11/5tNR5H34xw/OcCXSkRrYD2FNULjUhEUApWaLBpzstLqXCQG6p9oiWGfZ/ZnqFbUVhvNxn8pl04bMCtp4JHThpawBq9/Fmj0SdH7LjTuuawPrUnAqWG51ZkXmnTI7MKeirQgzGnodzcIvdl55d2ZrXZGJxes6tfbKwyTDm49reILg/XWbV0/p0IBHAv2Mh2eUB4RmIlhkuxH3KZoV0Nv4Dbyo/GLnFyxgAAAAASUVORK5CYII, }, { type: bpmn:endEvent, text: 结束, label: 结束节点, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAA1BJREFUOBFtVE1IVUEYPXOftq40Y3vPcmFIdSjIorWoRG0ERWUgnb5FwVhYQSl72oUoZAboxKNFtWiwKRN0MjpfSzqJAQclHo001tKkjl3emc8V69igP3znzfnO/M9zcDcKT67azmjYWTwl9Vn7Vumeqzj1DVb6cleQY4oAVnIOPbmKAGxQmKI5CWNJ2aLPatxWa3aB9K7/fB/Z0jUF6TmMlFLQqrkECWQzOZxYGjTlOl8eeKaIY5yHnFn486xBustDjWT6dG7pmjHOJd33t0iitTPkK6tEvjxq4h2MozQ6WFSX/LkDUGfFwfhEZj1Auz/U4pyAi5Sznd7uKzznXeVHlI/Aywmk6j7fsUsEuCGADrWARXXwjxWQsUbIupDHJI7kF5dRktg0eN81IbiZXiTESic50iwSt1oJgL83jAiBupLDCQqwziaWSoAFSeIR3P5Xv5az00wyIn35QRYTwdSYbz8pH8fxUUAtxnFvYmEmgI0wYXUXcCCSpeEVpXlsRhBnCEATxWylL9EKCAYhe1NGstUa6356kS9NVvt3DU2fdWtbm/lSbylJqsqkSm9CRhvoJVlvKPvF1RKY/FcPn5j4UfIMLn8D4UYb54BNsilTDXKnF4CfTobA0FpoW/LSp306wkXMXaOJhZaFkcNM82ASNAWMrhrUbRfmyeI1FvRBTpN06WKxa9BK0o2E4Pd3zfBBEwPsv9sQBnmLVbLEIZ/Xe9LYwJu/Er17W6HYVBc7vmuk0xUQpqxdom5Fnp55SiytXLPYoMXNM4u4SNSCFWnrVIzKG3EGyMXo6n/BQOebX3FClY4PwydVhthOZ9NnSntiLh0fxtlUJHAuGaFoVmttpVMeum0p3WEXbcll94l1wM/gZ0Ccczop77VvN2I7TlsZCsuXf1WHvWEhjO8DPtyOVg2/mvK9QqboEth7pD6NUQC1HN/TwvydGBARi9MZSzLE4b8Ru3XhX2PBxf8E1er2A6516o0w4sIAlwURhAON82Kwe2iDAC1Watq4XHaGQ7skLcFOtI5lDxuM2gZe6WFIotPAhbaeYlU4to5cuarF1QrcZ/lwrLaCJl66JBocYZnrNlvm2MBCTmUymPrYZVbjdlr/BxlMjmNmNI3SAAAAAElFTkSuQmCC, } ]); } // 保存图表 const handleSave async () { try { const graphData lfRef.current.getGraphData() console.log(graphData , graphData); const lfData toLogicflowData(graphData) // 将Turbo元素数据转换为LogicFlow数据 console.log(lfData , lfData); // ... 可调接口保存到服务器 } catch (err) { console.error(Failed to save, err); } }; // 清空画布 const handleClearCanvas () { if (!lfRef.current) return; lfRef.current.clearData(); } // 导出JSON const handleExportJSON () { if (!lfRef.current) return; const data lfRef.current.getGraphData(); const blob new Blob([JSON.stringify(data, null, 2)], { type: application/json }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download flowchart.json; a.click(); URL.revokeObjectURL(url); }; // 导出PNG const handleExportPNG () { if (!lfRef.current) return; lfRef.current?.getSnapshot(); } // 导入流程图数据 const handleImport (event) { const file event.target.files[0]; if (!file) return; const reader new FileReader(); reader.onload (e) { try { const data JSON.parse(e.target.result); lfRef.current.render(data); } catch (error) { console.error(导入失败:, error); alert(文件格式错误); } }; reader.readAsText(file); }; return ( div classNamelogic-flow div ref{containerRef} classNameflow-container / div classNametoolbar button onClick{handleClearCanvas}清空画布/button button onClick{() handleSave()}保存/button button onClick{handleExportJSON}导出json/button button onClick{handleExportPNG}导出图片/button input typefile accept.json onChange{handleImport} / /div /div ) })3. 定义并行网关节点 ParallelGateway.tsximport { PolygonNode, PolygonNodeModel, h } from logicflow/core; // 并行网关节点菱形加号 class ParallelGatewayModel extends PolygonNodeModel { initNodeData(data) { super.initNodeData(data); // 设置菱形形状 this.width 50; this.height 50; this.points [ [25, 0], // 上 [50, 25], // 右 [25, 50], // 下 [0, 25] // 左 ]; this.stroke #177af9; this.strokeWidth 2; this.fill white; this.text { value: data.text || 并行网关, x: this.x, y: this.y 45, editable: true, }; // 自定义属性 this.properties { type: bpmn:parallelGateway, direction: data.properties?.direction || diverging, ...data.properties }; } getNodeStyle() { const style super.getNodeStyle(); return { ...style, stroke: this.stroke, fill: this.fill, strokeWidth: this.strokeWidth, }; } } class ParallelGatewayView extends PolygonNode { getShape() { const { model } this.props; const { x, y, points, width, height } model; const style model.getNodeStyle(); // 计算偏移使菱形居中 const offsetX x - width / 2; const offsetY y - height / 2; return h(g, {}, [ /* 菱形主体 */ h(polygon, { ...style, points: points.map(point ${point[0]},${point[1]}).join( ), transform: translate(${offsetX}, ${offsetY}) }), /* 内部加号图标 */ h(line, { x1: x - 12, y1: y, x2: x 12, y2: y, stroke: style.stroke, strokeWidth: 3, strokeLinecap: round, }), h(line, { x1: x, y1: y - 12, x2: x, y2: y 12, stroke: style.stroke, strokeWidth: 3, strokeLinecap: round }), h(text, { x: x, y: y 40, textAnchor: middle, dominantBaseline: middle, fontSize: 12, fill: #333 }) ]) } } export const ParallelGateway { type: bpmn:parallelGateway, //要与Model中properties里的type值保持一致判断节点类型时会用到 view: ParallelGatewayView, model: ParallelGatewayModel, };4. 定义包容网关节点 InclusiveGateway.tsximport { PolygonNode, PolygonNodeModel, h } from logicflow/core; // 包容网关节点菱形圆圈 class InclusiveGatewayModel extends PolygonNodeModel { initNodeData(data) { super.initNodeData(data); this.width 50; this.height 50; this.points [ [25, 0], [50, 25], [25, 50], [0, 25] ]; this.stroke #177af9; this.strokeWidth 2; this.fill white; this.text { value: data.text || 包容网关, x: this.x, y: this.y 45, editable: true, }; this.properties { type: bpmn:inclusiveGateway, direction: data.properties?.direction || diverging, ...data.properties }; } getNodeStyle() { const style super.getNodeStyle(); return { ...style, stroke: this.stroke, fill: this.fill, strokeWidth: this.strokeWidth, }; } } class InclusiveGatewayView extends PolygonNode { getShape() { const { model } this.props; const { x, y, points, width, height } model; const style model.getNodeStyle(); const offsetX x - width / 2; const offsetY y - height / 2; return h(g, {}, [ /* 菱形主体 */ h(polygon, { ...style, points: points.map(point ${point[0]},${point[1]}).join( ), transform: translate(${offsetX}, ${offsetY}) }), /* 内部圆圈图标 */ h(circle, { cx: x, cy: y, r: 10, fill: white, stroke: style.stroke, strokeWidth: 3, }), h(text, { x: x, y: y 40, textAnchor: middle, dominantBaseline: middle, fontSize: 12, fill: #333 }) ]) } } export const InclusiveGateway { type: bpmn:inclusiveGateway, view: InclusiveGatewayView, model: InclusiveGatewayModel, };5. 添加 turboAdapter.tsx文件地址https://github.com/Logic-Flow/turbo-client/blob/master/src/components/turboAdapter.js把该文件内容放到自己的文件夹下并引入。turboAdapter默认将turbo数据转换为bpmn节点在实际项目中可以完全自定义节点而不是使用bpmn节点。把自己注册的新的节点类型加到该文件中不然导出的文件内容格式不对6. 注意事项6.1 View定义图标结构Deepseek中给出的解决方案类似但是有的方案在注册View时的写法是这样的gpolygon.../polygon.../g嵌套标签式的写法我用这个写法渲染不出来所以用的h(标签名, 属性, [子标签])函数的写法6.2 报错报错信息Uncaught Error: [mobx] [ComputedValue InclusiveGatewayModel34.width] It is not possible to assign a new value to a computed value.报错原因这个错误是因为在 LogicFlow v1.x 版本中节点的width、height、x、y等属性都是 MobX 的 computed 属性不能在 getter 或 setter 中直接修改。解决方法把这几个赋值的属性注掉也不影响使用按照以上步骤就可以自定义节点类型了。我依赖的项目是React框架其他框架根据语法自行修改添加后的效果如图
LogicFlow流程图-自定义节点类型(二)
使用LogicFlow 实现基础的流程图我们上一篇文章已经实现了入口LogicFlow实现流程图那么在这个基础上除了LogicFlow内置的用户任务、系统任务、条件判断的节点类型外我们还需要并行网关和包容网关。但是由于我项目中使用的 LogicFlow 是 v1.1.13 版本在这个版本中没有内置该类型的 BPMN 节点需要自己实现。其实我下载 v2.x 版本看了下依赖包里也没有新增其他类型自定义节点实现1. 安装必要依赖npm install logicflow/core logicflow/extension //指定版本 npm install logicflow/core1.1.13 logicflow/extension1.1.132. 定义组件LogicFlowWidget.tsximport React, { useEffect, useRef, useState } from react import LogicFlow from logicflow/core; import { Control, BpmnElement, SelectionSelect, DndPanel, Snapshot } from logicflow/extension; import logicflow/core/dist/style/index.css; import logicflow/extension/lib/style/index.css; import ./styles.less import TurboAdapter, { toLogicflowData } from ./turboAdapter import { ParallelGateway } from ./ParallelGateway import { InclusiveGateway } from ./InclusiveGateway export const LogicFlowWidget (() { const containerRef useRef(null); const lfRef useRef(null); useEffect(() { if (!containerRef.current) return; const lf new LogicFlow({ container: containerRef.current, // 指定容器ID必传 plugins: [ Control, BpmnElement, SelectionSelect, DndPanel, Snapshot, TurboAdapter ], grid: { visible: true, size: 10, }, keyboard: { enabled: true }, }); lfRef.current lf; // 2. 安全注册函数 const safeRegister (type) { try { // 注册节点 lf.register(type); return true; } catch (error) { console.error(❌ 节点 ${type} 注册失败:, error.message); return false; } }; // 注册自定义节点: 并行网关、包容网关 重要 safeRegister(ParallelGateway) safeRegister(InclusiveGateway) // 设置左侧节点类型 setPatternItems() // 渲染流程图 lfRef.current.render(); return () { lfRef.current?.off(); }; }, []); // 定义节点 const setPatternItems () { lfRef.current.extension.dndPanel.setPatternItems([ { label: 选区, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAOVJREFUOBGtVMENwzAIjKP2026ETdpv10iy7WFbqFyyW6GBywLCv5gIDw2Bluj1znuSjhb99Gkn6QILDY2imo60p8nsnc9bEo3QJAKHfMdZHnl78wyTnyHZD53Zzx73MRSgYvnqgCUHj6gwdck7Zsp1VOrz0Uz8NbKunzAWGu4fYW28bUYutYlzSa7B84Fh7d1kjLwhcSdYAYrdkMQVpsBr5XgDGuXwQfQr0y9zwLdaDUYXLaGKdd2ZTtvbolaO87pdo24hP7ov16N0zArH1ur3iwJpXxmv7oAJNR4JEP8DoAuSFEkYH7cAAAAASUVORK5CYII, callback: () { lfRef.current.extension.selectionSelect.openSelectionSelect(); lfRef.current.once(selection:selected, () { lfRef.current.extension.selectionSelect.closeSelectionSelect(); }); } }, { type: bpmn:startEvent, text: 开始, label: 开始节点, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAnBJREFUOBGdVL1rU1EcPfdGBddmaZLiEhdx1MHZQXApraCzQ7GKLgoRBxMfcRELuihWKcXFRcEWF8HBf0DdDCKYRZpnl7p0svLe9Zzbd29eQhTbC8nv9zf130AT63jvooOGS8Vf9Nt5zxba7sXQwODfkWpkbjTQfCGUd9gIp3uuPP8bZ946g56dYQvnBgb1HB8VIQmMFrazKcKSvFW2dQTxJnJdQ77urmXWOMBCmXM2Rke4S7UAW/8ywwFoewmBps2tu7mbTdp8VMOkIRAkKfrVawalJTtIliclFbaOBqa0M2xImHeVIfd/nKAfVq/LGnPss5Kh00VEdSzfwnBXPUpmykNss4lUI9C1ga8PNrBD5YeqRY2Zz8PhjooIbfJXjowvQJBqkmEkVnktWhwu2SM7SMx7Cj0N9IC0oQXRo8xwAGzQmsxrB/nNSUWVveI48ayrFGyC2E2CaWrZHXvOuzCiV6iycWe1Rd1Q6QUG07nb5SbPrL4426d9E1axKjY3AoRrlEeSQo2Eu0T6BWAAr6COhTcWjRaYfKG5csnvytvUr/WY4rrPMB53Uo7jZRjXaG6/CFfNMaXEu75nG47XoepU7PKJvvzGDY1YLSKHJrK7vFUwXKkaxwhCW3usDFMVrIju54RYYbFKpALZAo7sB6wcKyyrdaBMryMT2gPyD6GsQoRFkGHr14TthZni9ck0zPnmee460mHXbRAypKNy3nuMdrWgVKj8YVV8E7PSzp1BZ9SJnJAsXdryw/h5ctboUVi4AFiCdlQaYMw5z3LGTBKjLQOeUF35k89f58Vv/tGhlPE/wG0rgfIUbZK5AAAAABJRU5ErkJggg, }, { type: bpmn:userTask, label: 用户任务, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0Dg1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQBVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeAYeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQyJjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTbyr31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0HAd2GelJsZz1OJegCxqzlCLVHa/IibuHeJ1HAKzhuDRymNaRFM4jU6UWKXorRmbyqkq/D76FffevwdCpjN3UAN/C9JRVTDuOxC/ohEdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg, }, { type: bpmn:serviceTask, label: 系统任务, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAABgklEQVR4AYyTMTIEQRSG/oTkBFM4AgCApkjqA3EXIBCSiC2ygmIBcsRZBtIHIGqDYTcYPTX7Z95O6a2bNW//d7//vdPvme1K74nTy17cZlxc50hTCXksJv8S09vvVEjKkBqvN3yTE56Iwo7t5Kp889EERM7qXpizR5yPiNXS9GuFM0yU6Ac6/obrIROTEaNkBejAjG0KxJe1vS4bbU5HiowcxcMUJ0sWqruSvZ9LsSLo7yOuxBFer9ZcB5EVI5znH6QVCM4HxjwMDtOqqgdDL3niBQPPCkkD6xgY07vAhF6QSGIDwpj/J8ajjBbFkDEfixlzyKfhDhZfQ8nfPL4GqoyaeHmf1yrHDMmOuJTEYBConGNXk62G60J94Qn0mCxVzhfSDh2wgqKEY3cUgjAE2ngNLjxYPNKgqNuxL5i1Ky7tLxiiBgsV/rMk1SjPBa32CQH4LhvqZ8JNXO8G49XjCjQOMufAYYcADF8BBw1NMAmaDojEsyA4yg071rM4X4AAAD//xNVAsAAAAGSURBVAMAZRDClKgSLb4AAAAASUVORK5CYII, }, { type: bpmn:exclusiveGateway, label: 条件判断, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAAHeEJUAAAAABGdBTUEAALGPC/xhBQAAAvVJREFUOBGNVEFrE0EU/mY3bQoiFlOkaUJrQUQoWMGePLX24EH0IIoHKQiCV0G8iE1covgLiqA/QTzVm1JPogc9tIJYFaQtlhQxqYjSpunuL7JvmUTU3AgmTfvffPNN9WSA1DO182f6xwILzD5btfAoQmwL5KJEwiQyVbSVZ0IgRyV6PTpIJ81E5ZvqfHQR0HUOBHW4L5Et2kQ6Zf7iAOhTFAA8s0pEP7AXO1uAA52SbqGk6h/6J45LaLhO64ByfcUzM39V7ZiAdS2yCePPEIQYvTUHqM/n7dgQNfBKWPjpF4ISk8q3J4nB11qw6X8lFsF3EhlkEMfrjIer3wJTLwS2aCNcj4DbGxXTw00JmAuONi6bBxVUCvS5d9aa04so4pHW5jLTywuXAL7jJD06sl82Sgl2JuVBQn498zkc2bGKxULHjCnSMadBKYDYYHAtsby1EQ5lNGrQd4Y3v4Zo0XdGEmDno46yCM9TkRiJmUYHS/aXHPNTcjxcbTFna000PFJHIVZ5lFRqRpJWk9/QtlOUYJj9HG5pVFEU7zqIYDVsw2sAJaD8wTd2umgSCCyUxgGsS1Y6TBwXQQTFuZaHcd8gAGioE90hlsYwMcs30RduYtxanjMGal8H5dMW67dmT1JFtYUEe8LiQLRsPZ6IIc7A4J5tqco3T0pnv/4u0kyzrYUq7gASuEyI8VXKvB9Odytv6jS/PNaZBln0nioJG/AVQRZvApOdhjj3Jt8QC8Im09SafwdBdvIpztpxWxpeKCCEsFdS8DCyuCn2munFpL7ctHKpXc5cMybeIyMAN33SPL3ZR9QV1XVwLyzHm6Iv0/yeUuUb7PPlZC4D4HZkeu6dpF4v9j9MreGtMbxMMRLIcjJic9yHi7WQ3yVKzZVWUr5UrViJvn1FfUlwe/KYVfYyWRLSGNu16hR01U9IacajXPei0wx/5BqgInvJNMMNtNme7ReU9SBbgntovn0kKHpFg7UogZvaZiOue/q1SBo9ktHzQAAAAASUVORK5CYII, }, { type: bpmn:parallelGateway, label: 并行网关, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAGHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49J7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pg0KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj48dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1wbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz4slJgLAAAC4ElEQVQ4T4WTz29UVRTHPf1jaVia2fECBmYKZay7OCCYokbE3cYNRQTdaPpAClL/gFdQoJuTEykoaQrXNjij0S3Low1lA1lZewMnZnSKLSlqG/5s3jfV3cDjPUSTzJvYtz7/3e7znnzVJok0srYnTExFzSyIWvJ41vs37vNJtu68C4O1OABRXxKmrEbML4rWDxsnDxq/z4p3xiMpq239Bu6KwHCt3ORQXaspdDhVsSOs1afiLUIzVNHQlVCWIdzTqaW0wrIYmYi4WxHHssb0WZ9HW9DhQbILPpiMmPlTHO83pvImWRLmQ3EYexBi85JoOXQi2sxKpHUt9nNWUrWm7Lt1fjZ8yO34lVLmFmQdQWBFnrte5WxGDGWM679P3krFUFeshbNbhwWOR7jVufOIzPGDcLor3rzd75t1fE2cmIubKkMsaP435HHnZUe5KgO5tSfhKuhLGTfPJhgaswWHNiDx8IbmYiYW3Q9fG8Tp5o7IqFtdEKWhOqBS4XDkQL3bB9KjPyaPG7D0HZp0XazqWNr4/51PdhreqrMVgt8BCBpQtrNFT2Bvp/HzmM/bjh9LWKmJLxY7lGnb0iQ6GgvuNaIJTo8qDB7Qg8A3vzy1C//CFOvGp8d84n2WU8XBdm8FdVvDseAfDDeZ8DPe6zfXuNlQ13dmdRDB02vBsfwz3G7cK4r3xiOo2ZJLGoV6jL9Vk15dyuUzSCDadyuURe6Qm7K3v8f4Zmeks0Xx9tX605Fu1SGK3dqqO8DCsjj1dcRcWeSyMJX3OdhrzmvZVBPsdtHJYemRONBjdHfCC89Busco/dNU/mDGmBpNcGSfY/2MRUqBGgy4vd5cWLAmBr1CTbc5Lr3wMg1V05DtA29QRvTloOmDYYD7Vek1Y39YyRC8v/Y9pGVALx4WTETFG8MWDUY7h1r9nY/p1yWqMtEMDfVQf224LwDHJp42beJ937XxCAfwFyluH6QWfjTQAAAABJRU5ErkJggg, }, { type: bpmn:inclusiveGateway, label: 包容网关, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAGHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49J7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pg0KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj48dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1wbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz4slJgLAAAC9klEQVQ4T42UTWxUVRzFf/fN0x0BE4iJLVSkbdCaTkEYsUIQJYgRTQQWhSVjKgJC9i4krARmlLUxM0Qa1wp0oKJMdG0REkMBboojshHOq1lWgalA51p00D7euceF6/ptNMuPMubz83//NxjSSxADJ5sbPVkswIJ9hQYWiLzy7yJReBcArPQDofyjeTVi6B8SackP9SsPvKfHGctgbsF3QSXoyzpFTwbi0KSiJwMNjjg9GHN6rSUQBycVaw7PSjGHKDXsVHsiJFnXHCg7XhzIP3ba/EUgDkxqfXOgdAmZ0bRGfVmxq9XyZ1rUVxvO7PFJZsRvvQ6AN6o8XlluONxu6bwh1lca2uMK54JNTOS1PdQ7G6dIpmGirDsXcitPxaoOOmAzutgQ9bX/L4eGuEposFOmIWJWhbV9IZoZyTjsSlmRaxFYZvt7rcSHAh1/OdY8b9hVF/px/g/H9bvirRqPL3dHOHTOcumWiFUaftzvQxUID4Khc3kndquF0TjhOqaAg3lijoM5ZzqmgLROKHvewoaHnOqPx2IxkltjyQl8yIV1cafjrg89xiwy3w3Ua1nqULylmpnyJoWGtBxY67jiWLTJcNBnc7Xh6l3hOYEfgaf9mZn/jakCTFjwDHgbXzBcTon3EpbsuHh7tQcnO1x3MsXw3cvL872OPBh4se90fFjoTl2oCIlhnMP6NOO7yXEmJTasNrXt9Gs9ZLt7UgmJve9nj9AcR9n1r6e4X0YpQFiNJ6RGx5xvLlV6xpcbwyfYIn3YU6Lw11/5tNR5H34xw/OcCXSkRrYD2FNULjUhEUApWaLBpzstLqXCQG6p9oiWGfZ/ZnqFbUVhvNxn8pl04bMCtp4JHThpawBq9/Fmj0SdH7LjTuuawPrUnAqWG51ZkXmnTI7MKeirQgzGnodzcIvdl55d2ZrXZGJxes6tfbKwyTDm49reILg/XWbV0/p0IBHAv2Mh2eUB4RmIlhkuxH3KZoV0Nv4Dbyo/GLnFyxgAAAAASUVORK5CYII, }, { type: bpmn:endEvent, text: 结束, label: 结束节点, icon: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAA1BJREFUOBFtVE1IVUEYPXOftq40Y3vPcmFIdSjIorWoRG0ERWUgnb5FwVhYQSl72oUoZAboxKNFtWiwKRN0MjpfSzqJAQclHo001tKkjl3emc8V69igP3znzfnO/M9zcDcKT67azmjYWTwl9Vn7Vumeqzj1DVb6cleQY4oAVnIOPbmKAGxQmKI5CWNJ2aLPatxWa3aB9K7/fB/Z0jUF6TmMlFLQqrkECWQzOZxYGjTlOl8eeKaIY5yHnFn486xBustDjWT6dG7pmjHOJd33t0iitTPkK6tEvjxq4h2MozQ6WFSX/LkDUGfFwfhEZj1Auz/U4pyAi5Sznd7uKzznXeVHlI/Aywmk6j7fsUsEuCGADrWARXXwjxWQsUbIupDHJI7kF5dRktg0eN81IbiZXiTESic50iwSt1oJgL83jAiBupLDCQqwziaWSoAFSeIR3P5Xv5az00wyIn35QRYTwdSYbz8pH8fxUUAtxnFvYmEmgI0wYXUXcCCSpeEVpXlsRhBnCEATxWylL9EKCAYhe1NGstUa6356kS9NVvt3DU2fdWtbm/lSbylJqsqkSm9CRhvoJVlvKPvF1RKY/FcPn5j4UfIMLn8D4UYb54BNsilTDXKnF4CfTobA0FpoW/LSp306wkXMXaOJhZaFkcNM82ASNAWMrhrUbRfmyeI1FvRBTpN06WKxa9BK0o2E4Pd3zfBBEwPsv9sQBnmLVbLEIZ/Xe9LYwJu/Er17W6HYVBc7vmuk0xUQpqxdom5Fnp55SiytXLPYoMXNM4u4SNSCFWnrVIzKG3EGyMXo6n/BQOebX3FClY4PwydVhthOZ9NnSntiLh0fxtlUJHAuGaFoVmttpVMeum0p3WEXbcll94l1wM/gZ0Ccczop77VvN2I7TlsZCsuXf1WHvWEhjO8DPtyOVg2/mvK9QqboEth7pD6NUQC1HN/TwvydGBARi9MZSzLE4b8Ru3XhX2PBxf8E1er2A6516o0w4sIAlwURhAON82Kwe2iDAC1Watq4XHaGQ7skLcFOtI5lDxuM2gZe6WFIotPAhbaeYlU4to5cuarF1QrcZ/lwrLaCJl66JBocYZnrNlvm2MBCTmUymPrYZVbjdlr/BxlMjmNmNI3SAAAAAElFTkSuQmCC, } ]); } // 保存图表 const handleSave async () { try { const graphData lfRef.current.getGraphData() console.log(graphData , graphData); const lfData toLogicflowData(graphData) // 将Turbo元素数据转换为LogicFlow数据 console.log(lfData , lfData); // ... 可调接口保存到服务器 } catch (err) { console.error(Failed to save, err); } }; // 清空画布 const handleClearCanvas () { if (!lfRef.current) return; lfRef.current.clearData(); } // 导出JSON const handleExportJSON () { if (!lfRef.current) return; const data lfRef.current.getGraphData(); const blob new Blob([JSON.stringify(data, null, 2)], { type: application/json }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download flowchart.json; a.click(); URL.revokeObjectURL(url); }; // 导出PNG const handleExportPNG () { if (!lfRef.current) return; lfRef.current?.getSnapshot(); } // 导入流程图数据 const handleImport (event) { const file event.target.files[0]; if (!file) return; const reader new FileReader(); reader.onload (e) { try { const data JSON.parse(e.target.result); lfRef.current.render(data); } catch (error) { console.error(导入失败:, error); alert(文件格式错误); } }; reader.readAsText(file); }; return ( div classNamelogic-flow div ref{containerRef} classNameflow-container / div classNametoolbar button onClick{handleClearCanvas}清空画布/button button onClick{() handleSave()}保存/button button onClick{handleExportJSON}导出json/button button onClick{handleExportPNG}导出图片/button input typefile accept.json onChange{handleImport} / /div /div ) })3. 定义并行网关节点 ParallelGateway.tsximport { PolygonNode, PolygonNodeModel, h } from logicflow/core; // 并行网关节点菱形加号 class ParallelGatewayModel extends PolygonNodeModel { initNodeData(data) { super.initNodeData(data); // 设置菱形形状 this.width 50; this.height 50; this.points [ [25, 0], // 上 [50, 25], // 右 [25, 50], // 下 [0, 25] // 左 ]; this.stroke #177af9; this.strokeWidth 2; this.fill white; this.text { value: data.text || 并行网关, x: this.x, y: this.y 45, editable: true, }; // 自定义属性 this.properties { type: bpmn:parallelGateway, direction: data.properties?.direction || diverging, ...data.properties }; } getNodeStyle() { const style super.getNodeStyle(); return { ...style, stroke: this.stroke, fill: this.fill, strokeWidth: this.strokeWidth, }; } } class ParallelGatewayView extends PolygonNode { getShape() { const { model } this.props; const { x, y, points, width, height } model; const style model.getNodeStyle(); // 计算偏移使菱形居中 const offsetX x - width / 2; const offsetY y - height / 2; return h(g, {}, [ /* 菱形主体 */ h(polygon, { ...style, points: points.map(point ${point[0]},${point[1]}).join( ), transform: translate(${offsetX}, ${offsetY}) }), /* 内部加号图标 */ h(line, { x1: x - 12, y1: y, x2: x 12, y2: y, stroke: style.stroke, strokeWidth: 3, strokeLinecap: round, }), h(line, { x1: x, y1: y - 12, x2: x, y2: y 12, stroke: style.stroke, strokeWidth: 3, strokeLinecap: round }), h(text, { x: x, y: y 40, textAnchor: middle, dominantBaseline: middle, fontSize: 12, fill: #333 }) ]) } } export const ParallelGateway { type: bpmn:parallelGateway, //要与Model中properties里的type值保持一致判断节点类型时会用到 view: ParallelGatewayView, model: ParallelGatewayModel, };4. 定义包容网关节点 InclusiveGateway.tsximport { PolygonNode, PolygonNodeModel, h } from logicflow/core; // 包容网关节点菱形圆圈 class InclusiveGatewayModel extends PolygonNodeModel { initNodeData(data) { super.initNodeData(data); this.width 50; this.height 50; this.points [ [25, 0], [50, 25], [25, 50], [0, 25] ]; this.stroke #177af9; this.strokeWidth 2; this.fill white; this.text { value: data.text || 包容网关, x: this.x, y: this.y 45, editable: true, }; this.properties { type: bpmn:inclusiveGateway, direction: data.properties?.direction || diverging, ...data.properties }; } getNodeStyle() { const style super.getNodeStyle(); return { ...style, stroke: this.stroke, fill: this.fill, strokeWidth: this.strokeWidth, }; } } class InclusiveGatewayView extends PolygonNode { getShape() { const { model } this.props; const { x, y, points, width, height } model; const style model.getNodeStyle(); const offsetX x - width / 2; const offsetY y - height / 2; return h(g, {}, [ /* 菱形主体 */ h(polygon, { ...style, points: points.map(point ${point[0]},${point[1]}).join( ), transform: translate(${offsetX}, ${offsetY}) }), /* 内部圆圈图标 */ h(circle, { cx: x, cy: y, r: 10, fill: white, stroke: style.stroke, strokeWidth: 3, }), h(text, { x: x, y: y 40, textAnchor: middle, dominantBaseline: middle, fontSize: 12, fill: #333 }) ]) } } export const InclusiveGateway { type: bpmn:inclusiveGateway, view: InclusiveGatewayView, model: InclusiveGatewayModel, };5. 添加 turboAdapter.tsx文件地址https://github.com/Logic-Flow/turbo-client/blob/master/src/components/turboAdapter.js把该文件内容放到自己的文件夹下并引入。turboAdapter默认将turbo数据转换为bpmn节点在实际项目中可以完全自定义节点而不是使用bpmn节点。把自己注册的新的节点类型加到该文件中不然导出的文件内容格式不对6. 注意事项6.1 View定义图标结构Deepseek中给出的解决方案类似但是有的方案在注册View时的写法是这样的gpolygon.../polygon.../g嵌套标签式的写法我用这个写法渲染不出来所以用的h(标签名, 属性, [子标签])函数的写法6.2 报错报错信息Uncaught Error: [mobx] [ComputedValue InclusiveGatewayModel34.width] It is not possible to assign a new value to a computed value.报错原因这个错误是因为在 LogicFlow v1.x 版本中节点的width、height、x、y等属性都是 MobX 的 computed 属性不能在 getter 或 setter 中直接修改。解决方法把这几个赋值的属性注掉也不影响使用按照以上步骤就可以自定义节点类型了。我依赖的项目是React框架其他框架根据语法自行修改添加后的效果如图