1. 为什么需要前端动态高亮流程图在业务流程管理系统中流程图的可视化展示一直是刚需。传统的Activiti 5.x版本提供了Diagram Viewer组件但在6.0版本中这个组件被移除了。很多团队不得不采用后端生成图片再返回前端的方式这种方式有几个明显的痛点首先图片生成需要额外的服务器资源特别是在高并发场景下服务器压力会明显增大。其次生成的图片往往不够清晰特别是当流程比较复杂时文字和线条容易模糊。最重要的是静态图片无法实现交互效果比如节点悬停查看详情、动态缩放等。bpmn.js这个前端库完美解决了这些问题。它可以直接在浏览器中解析和渲染BPMN 2.0标准的XML文件不需要后端参与图片生成。更重要的是它提供了丰富的API来实现动态效果比如已完成节点的绿色高亮当前活动节点的闪烁提示已流转连线的标记鼠标悬停查看节点详情2. 环境准备与基础集成2.1 安装必要的依赖在Vue或React项目中首先需要安装以下npm包npm install bpmn-js8.7.1 npm install xml-js1.6.11如果是需要属性面板等高级功能可以额外安装npm install bpmn-js-properties-panel npm install camunda-bpmn-moddle2.2 初始化查看器创建一个基础的BPMN查看器非常简单import BpmnViewer from bpmn-js; const viewer new BpmnViewer({ container: #bpmn-container }); // 加载BPMN XML viewer.importXML(xmlString) .then(() { console.log(流程图加载成功); // 自适应视图 viewer.get(canvas).zoom(fit-viewport); }) .catch(err { console.error(流程图加载失败, err); });3. 实现动态高亮效果3.1 理解高亮原理bpmn.js的高亮本质上是给SVG元素添加CSS类。核心方法是canvas.addMarker(elementId, className)已完成节点添加highlight-executed类当前节点添加highlight类已流转线添加highlight-line类对应的CSS样式示例.highlight:not(.djs-connection) .djs-visual :nth-child(1) { fill: #FBE9D1 !important; stroke: #D67E7D !important; animation: pulse 1.5s infinite; } .highlight-executed .djs-visual :nth-child(1) { stroke: #00BE00 !important; fill: #B4F1B4 !important; } .highlight-line .djs-visual :nth-child(1) { stroke: #00BE00 !important; } keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.6; } 100% { opacity: 1; } }3.2 处理特殊网关节点排他网关(Exclusive Gateway)需要特殊处理否则其标记可能不显示。需要在XML中为对应的bpmndi:BPMNShape添加isMarkerVisibletrue属性function processGatewayMarkers(xml) { const gatewayIds []; const regex /exclusiveGateway[^]*?\sid([^])[^]*?/g; let match; while ((match regex.exec(xml)) ! null) { gatewayIds.push(match[1]); } return gatewayIds.reduce((result, id) { return result.replace( bpmnElement${id}, bpmnElement${id} isMarkerVisibletrue ); }, xml); }4. 与后端API对接4.1 数据结构设计后端需要返回以下关键数据public class ProcessHighlightData { // 当前活动节点ID集合 private SetString activeActivityIds; // 已完成的节点ID集合 private SetString executedActivityIds; // 已流转的连线ID集合 private SetString highlightedFlowIds; // 原始BPMN XML private String bpmnXml; // 流程名称 private String processName; }4.2 前端数据处理收到后端数据后前端需要分步骤处理async function renderProcess(instanceId) { // 1. 获取数据 const { data } await axios.get(/api/process/${instanceId}/highlight); // 2. 处理网关标记 const processedXml processGatewayMarkers(data.bpmnXml); // 3. 渲染流程图 await viewer.importXML(processedXml); // 4. 设置高亮 const canvas viewer.get(canvas); data.executedActivityIds.forEach(id canvas.addMarker(id, highlight-executed)); data.highlightedFlowIds.forEach(id canvas.addMarker(id, highlight-line)); data.activeActivityIds.forEach(id canvas.addMarker(id, highlight)); // 5. 自适应视图 canvas.zoom(fit-viewport); }5. 增强交互体验5.1 添加缩放控制在查看器上方添加控制按钮div classtoolbar button clickzoomOut缩小/button span{{ zoomLevel }}%/span button clickzoomIn放大/button button clickresetZoom重置/button /div对应的操作方法const zoomStep 0.1; const minZoom 0.2; const maxZoom 4; function zoomOut() { const newZoom Math.max(viewer.get(canvas).zoom() - zoomStep, minZoom); viewer.get(canvas).zoom(newZoom); } function zoomIn() { const newZoom Math.min(viewer.get(canvas).zoom() zoomStep, maxZoom); viewer.get(canvas).zoom(newZoom); } function resetZoom() { viewer.get(canvas).zoom(fit-viewport); }5.2 实现节点详情展示通过事件总线监听节点悬停事件viewer.get(eventBus).on(element.hover, event { if (event.element.type bpmn:UserTask) { showNodeDetails(event.element.id); } }); viewer.get(eventBus).on(element.out, event { hideNodeDetails(); }); async function showNodeDetails(activityId) { const { data } await axios.get(/api/activities/${activityId}); // 展示详情弹窗... }6. 性能优化技巧6.1 XML缓存策略对于频繁查看的流程定义可以在前端缓存XMLconst xmlCache new Map(); async function getProcessXml(processDefinitionId) { if (xmlCache.has(processDefinitionId)) { return xmlCache.get(processDefinitionId); } const { data } await axios.get(/api/process-definitions/${processDefinitionId}/xml); xmlCache.set(processDefinitionId, data); return data; }6.2 批量DOM操作当需要高亮大量元素时使用requestAnimationFrame分批处理function batchHighlight(elements, className) { let index 0; const batchSize 20; const canvas viewer.get(canvas); function processBatch() { const batch elements.slice(index, index batchSize); batch.forEach(id canvas.addMarker(id, className)); index batchSize; if (index elements.length) { requestAnimationFrame(processBatch); } } processBatch(); }6.3 使用Web Worker处理大型XML对于特别大的流程文件可以在Web Worker中解析XML// worker.js self.onmessage function(e) { const { xml } e.data; // 处理XML... postMessage(processedResult); }; // 主线程 const worker new Worker(worker.js); worker.postMessage({ xml: largeXmlString }); worker.onmessage function(e) { viewer.importXML(e.data); };7. 常见问题解决7.1 字体显示异常如果流程图中的文字显示为方块需要在CSS中引入BPMN字体import ~bpmn-js/dist/assets/bpmn-font/css/bpmn.css; import ~bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css;7.2 元素重叠问题当流程比较复杂时可以使用自动布局import AutoLayoutModule from bpmn-js-auto-layout; const viewer new BpmnViewer({ container: #container, additionalModules: [AutoLayoutModule] });7.3 移动端适配针对移动设备需要特别处理触摸事件viewer.get(canvas)._container.addEventListener(touchstart, handleTouch); viewer.get(canvas)._container.addEventListener(touchmove, handleTouch); function handleTouch(e) { // 阻止默认行为避免页面滚动 e.preventDefault(); // 实现平移逻辑... }在实际项目中我发现动态高亮流程图最关键的三个点是正确的XML预处理、精准的节点标记和流畅的交互体验。特别是对于复杂流程合理的性能优化能显著提升用户体验。
基于bpmn.js与Activiti 6.0实现前端动态高亮流程图渲染
1. 为什么需要前端动态高亮流程图在业务流程管理系统中流程图的可视化展示一直是刚需。传统的Activiti 5.x版本提供了Diagram Viewer组件但在6.0版本中这个组件被移除了。很多团队不得不采用后端生成图片再返回前端的方式这种方式有几个明显的痛点首先图片生成需要额外的服务器资源特别是在高并发场景下服务器压力会明显增大。其次生成的图片往往不够清晰特别是当流程比较复杂时文字和线条容易模糊。最重要的是静态图片无法实现交互效果比如节点悬停查看详情、动态缩放等。bpmn.js这个前端库完美解决了这些问题。它可以直接在浏览器中解析和渲染BPMN 2.0标准的XML文件不需要后端参与图片生成。更重要的是它提供了丰富的API来实现动态效果比如已完成节点的绿色高亮当前活动节点的闪烁提示已流转连线的标记鼠标悬停查看节点详情2. 环境准备与基础集成2.1 安装必要的依赖在Vue或React项目中首先需要安装以下npm包npm install bpmn-js8.7.1 npm install xml-js1.6.11如果是需要属性面板等高级功能可以额外安装npm install bpmn-js-properties-panel npm install camunda-bpmn-moddle2.2 初始化查看器创建一个基础的BPMN查看器非常简单import BpmnViewer from bpmn-js; const viewer new BpmnViewer({ container: #bpmn-container }); // 加载BPMN XML viewer.importXML(xmlString) .then(() { console.log(流程图加载成功); // 自适应视图 viewer.get(canvas).zoom(fit-viewport); }) .catch(err { console.error(流程图加载失败, err); });3. 实现动态高亮效果3.1 理解高亮原理bpmn.js的高亮本质上是给SVG元素添加CSS类。核心方法是canvas.addMarker(elementId, className)已完成节点添加highlight-executed类当前节点添加highlight类已流转线添加highlight-line类对应的CSS样式示例.highlight:not(.djs-connection) .djs-visual :nth-child(1) { fill: #FBE9D1 !important; stroke: #D67E7D !important; animation: pulse 1.5s infinite; } .highlight-executed .djs-visual :nth-child(1) { stroke: #00BE00 !important; fill: #B4F1B4 !important; } .highlight-line .djs-visual :nth-child(1) { stroke: #00BE00 !important; } keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.6; } 100% { opacity: 1; } }3.2 处理特殊网关节点排他网关(Exclusive Gateway)需要特殊处理否则其标记可能不显示。需要在XML中为对应的bpmndi:BPMNShape添加isMarkerVisibletrue属性function processGatewayMarkers(xml) { const gatewayIds []; const regex /exclusiveGateway[^]*?\sid([^])[^]*?/g; let match; while ((match regex.exec(xml)) ! null) { gatewayIds.push(match[1]); } return gatewayIds.reduce((result, id) { return result.replace( bpmnElement${id}, bpmnElement${id} isMarkerVisibletrue ); }, xml); }4. 与后端API对接4.1 数据结构设计后端需要返回以下关键数据public class ProcessHighlightData { // 当前活动节点ID集合 private SetString activeActivityIds; // 已完成的节点ID集合 private SetString executedActivityIds; // 已流转的连线ID集合 private SetString highlightedFlowIds; // 原始BPMN XML private String bpmnXml; // 流程名称 private String processName; }4.2 前端数据处理收到后端数据后前端需要分步骤处理async function renderProcess(instanceId) { // 1. 获取数据 const { data } await axios.get(/api/process/${instanceId}/highlight); // 2. 处理网关标记 const processedXml processGatewayMarkers(data.bpmnXml); // 3. 渲染流程图 await viewer.importXML(processedXml); // 4. 设置高亮 const canvas viewer.get(canvas); data.executedActivityIds.forEach(id canvas.addMarker(id, highlight-executed)); data.highlightedFlowIds.forEach(id canvas.addMarker(id, highlight-line)); data.activeActivityIds.forEach(id canvas.addMarker(id, highlight)); // 5. 自适应视图 canvas.zoom(fit-viewport); }5. 增强交互体验5.1 添加缩放控制在查看器上方添加控制按钮div classtoolbar button clickzoomOut缩小/button span{{ zoomLevel }}%/span button clickzoomIn放大/button button clickresetZoom重置/button /div对应的操作方法const zoomStep 0.1; const minZoom 0.2; const maxZoom 4; function zoomOut() { const newZoom Math.max(viewer.get(canvas).zoom() - zoomStep, minZoom); viewer.get(canvas).zoom(newZoom); } function zoomIn() { const newZoom Math.min(viewer.get(canvas).zoom() zoomStep, maxZoom); viewer.get(canvas).zoom(newZoom); } function resetZoom() { viewer.get(canvas).zoom(fit-viewport); }5.2 实现节点详情展示通过事件总线监听节点悬停事件viewer.get(eventBus).on(element.hover, event { if (event.element.type bpmn:UserTask) { showNodeDetails(event.element.id); } }); viewer.get(eventBus).on(element.out, event { hideNodeDetails(); }); async function showNodeDetails(activityId) { const { data } await axios.get(/api/activities/${activityId}); // 展示详情弹窗... }6. 性能优化技巧6.1 XML缓存策略对于频繁查看的流程定义可以在前端缓存XMLconst xmlCache new Map(); async function getProcessXml(processDefinitionId) { if (xmlCache.has(processDefinitionId)) { return xmlCache.get(processDefinitionId); } const { data } await axios.get(/api/process-definitions/${processDefinitionId}/xml); xmlCache.set(processDefinitionId, data); return data; }6.2 批量DOM操作当需要高亮大量元素时使用requestAnimationFrame分批处理function batchHighlight(elements, className) { let index 0; const batchSize 20; const canvas viewer.get(canvas); function processBatch() { const batch elements.slice(index, index batchSize); batch.forEach(id canvas.addMarker(id, className)); index batchSize; if (index elements.length) { requestAnimationFrame(processBatch); } } processBatch(); }6.3 使用Web Worker处理大型XML对于特别大的流程文件可以在Web Worker中解析XML// worker.js self.onmessage function(e) { const { xml } e.data; // 处理XML... postMessage(processedResult); }; // 主线程 const worker new Worker(worker.js); worker.postMessage({ xml: largeXmlString }); worker.onmessage function(e) { viewer.importXML(e.data); };7. 常见问题解决7.1 字体显示异常如果流程图中的文字显示为方块需要在CSS中引入BPMN字体import ~bpmn-js/dist/assets/bpmn-font/css/bpmn.css; import ~bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css;7.2 元素重叠问题当流程比较复杂时可以使用自动布局import AutoLayoutModule from bpmn-js-auto-layout; const viewer new BpmnViewer({ container: #container, additionalModules: [AutoLayoutModule] });7.3 移动端适配针对移动设备需要特别处理触摸事件viewer.get(canvas)._container.addEventListener(touchstart, handleTouch); viewer.get(canvas)._container.addEventListener(touchmove, handleTouch); function handleTouch(e) { // 阻止默认行为避免页面滚动 e.preventDefault(); // 实现平移逻辑... }在实际项目中我发现动态高亮流程图最关键的三个点是正确的XML预处理、精准的节点标记和流畅的交互体验。特别是对于复杂流程合理的性能优化能显著提升用户体验。