From a410fcb67b4f6cf4d43d26d01ffdea2ac0f76960 Mon Sep 17 00:00:00 2001 From: xxMudCloudxx Date: Thu, 26 Feb 2026 18:41:01 +0800 Subject: [PATCH 1/2] fix(sequence-interaction): resolve lifeline penetration issues using SVG masks instead of white overlays --- .../structures/sequence-interaction.tsx | 135 ++++++++++++------ 1 file changed, 89 insertions(+), 46 deletions(-) diff --git a/src/designs/structures/sequence-interaction.tsx b/src/designs/structures/sequence-interaction.tsx index 14f537d5..f9c3a9cb 100644 --- a/src/designs/structures/sequence-interaction.tsx +++ b/src/designs/structures/sequence-interaction.tsx @@ -100,6 +100,7 @@ const DEFAULT_ITEM_HEIGHT = 50; const FONT_SIZE = 14; const ARROW_SIZE = 14; const CORNER_RADIUS_NODE = 6; +const LIFELINE_MASK_GAP = 2; const LANE_PADDING = 60; const BTN_HALF_SIZE = 12; @@ -419,39 +420,6 @@ export const SequenceInteractionFlow: ComponentType< const defsElements: JSXElement[] = []; const btnElements: JSXElement[] = []; - // 绘制生命线 - if (showLifeline) { - lanes.forEach((_lane, laneIndex) => { - const centerX = getLaneCenterX(laneIndex); - const startY = padding + headerOffset; - const endY = totalHeight - padding; - - decorElements.push( - , - ); - - // 绘制生命线末端箭头(实心) - decorElements.push( - ...createArrowElements( - centerX, - endY, - Math.PI / 2, - 'triangle', - colorBorder, - 1, - 10, - ), - ); - }); - } - // 绘制泳道标题 if (showLaneHeader) { lanes.forEach((lane, laneIndex) => { @@ -523,19 +491,6 @@ export const SequenceInteractionFlow: ComponentType< options, ); - // 添加节点背景遮挡层,防止生命线虚线透过半透明节点显示 - // 只在节点中心放置窄条遮挡生命线,避免圆角处露出白色背景 - const maskStripWidth = lifelineWidth + 6; - decorElements.push( - , - ); - // 构造类似 hierarchy-tree 的 _originalIndex const originalIndex = [laneIndex, rowIndex]; // 附加到数据上,确保 Item 组件能正确识别 @@ -631,6 +586,94 @@ export const SequenceInteractionFlow: ComponentType< ); }); + // 绘制生命线(使用 mask 挖空节点区域,避免虚线穿透半透明节点) + if (showLifeline) { + lanes.forEach((_lane, laneIndex) => { + const centerX = getLaneCenterX(laneIndex); + const startY = padding + headerOffset; + const endY = totalHeight - padding; + + // 收集该泳道上所有节点的矩形区域 + const laneNodeRects: { + x: number; + y: number; + width: number; + height: number; + }[] = []; + nodeLayoutById.forEach((layout) => { + if (layout.laneIndex === laneIndex) { + laneNodeRects.push({ + x: layout.x, + y: layout.y, + width: layout.width, + height: layout.height, + }); + } + }); + + // 如果该泳道有节点,创建 mask 来挖空节点区域 + let lifelineMaskAttr: string | undefined; + if (laneNodeRects.length > 0) { + const maskId = `lifeline-mask-${instanceId}-${laneIndex}`; + defsElements.push( + + {/* 白色底:显示所有线条 */} + + {/* 黑色块:在节点位置挖空生命线,上下各留 LIFELINE_MASK_GAP 间距 */} + {laneNodeRects.map((rect) => ( + + ))} + , + ); + lifelineMaskAttr = `url(#${maskId})`; + } + + decorElements.push( + , + ); + + // 绘制生命线末端箭头(实心) + decorElements.push( + ...createArrowElements( + centerX, + endY, + Math.PI / 2, + 'triangle', + colorBorder, + 1, + 10, + ), + ); + }); + } + // 添加新泳道按钮 (最右侧) const lastLaneRightX = getLaneCenterX(lanes.length - 1) + laneWidth / 2; const newLaneX = From dc4f8b99bfa82d31deeddc348b4f19861425737b Mon Sep 17 00:00:00 2001 From: xxMudCloudxx Date: Thu, 26 Feb 2026 18:51:42 +0800 Subject: [PATCH 2/2] perf: pre-group nodes to avoid nested iterations --- .../structures/sequence-interaction.tsx | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/designs/structures/sequence-interaction.tsx b/src/designs/structures/sequence-interaction.tsx index f9c3a9cb..9150200d 100644 --- a/src/designs/structures/sequence-interaction.tsx +++ b/src/designs/structures/sequence-interaction.tsx @@ -588,28 +588,31 @@ export const SequenceInteractionFlow: ComponentType< // 绘制生命线(使用 mask 挖空节点区域,避免虚线穿透半透明节点) if (showLifeline) { + // 预先按泳道分组节点,避免每条泳道都遍历全部节点 + const nodeRectsByLane = new Map< + number, + { x: number; y: number; width: number; height: number }[] + >(); + nodeLayoutById.forEach((layout) => { + let list = nodeRectsByLane.get(layout.laneIndex); + if (!list) { + list = []; + nodeRectsByLane.set(layout.laneIndex, list); + } + list.push({ + x: layout.x, + y: layout.y, + width: layout.width, + height: layout.height, + }); + }); + lanes.forEach((_lane, laneIndex) => { const centerX = getLaneCenterX(laneIndex); const startY = padding + headerOffset; const endY = totalHeight - padding; - // 收集该泳道上所有节点的矩形区域 - const laneNodeRects: { - x: number; - y: number; - width: number; - height: number; - }[] = []; - nodeLayoutById.forEach((layout) => { - if (layout.laneIndex === laneIndex) { - laneNodeRects.push({ - x: layout.x, - y: layout.y, - width: layout.width, - height: layout.height, - }); - } - }); + const laneNodeRects = nodeRectsByLane.get(laneIndex) ?? []; // 如果该泳道有节点,创建 mask 来挖空节点区域 let lifelineMaskAttr: string | undefined;