Просмотр исходного кода

feat(FlowChart): 调整消息与子目标的显示顺序并添加弧线连接

调整 expandNode 函数中消息组和子目标的展开顺序,使消息显示在子目标之前
在布局阶段添加三种弧线连接逻辑:
1. 从主节点到第一个子目标节点的消息组弧线
2. 从最后一个子节点到下一个主节点的子节点弧线
3. 补充最后一个子节点内部的直线连接
优化折叠边的处理逻辑,避免重复计算节点偏移
max_liu 3 недель назад
Родитель
Сommit
a89a12ee40
1 измененных файлов с 86 добавлено и 12 удалено
  1. 86 12
      frontend/react-template/src/components/FlowChart/FlowChart.tsx

+ 86 - 12
frontend/react-template/src/components/FlowChart/FlowChart.tsx

@@ -213,17 +213,7 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
       let lastNode = currentNode; // 记录最后一个节点,用于连接到下一个主链节点
       globalY += NODE_HEIGHT; // 更新全局 Y 坐标
 
-      // 递归展开 sub_goals
-      if (goal.sub_goals && goal.sub_goals.length > 0) {
-        goal.sub_goals.forEach((subGoal) => {
-          const subX = x + HORIZONTAL_OFFSET; // 子节点向右偏移
-          const result = expandNode(subGoal, subX, level + 1, nodeId);
-          localNodes.push(...result.nodes);
-          lastNode = result.lastNode; // 更新最后一个节点
-        });
-      }
-
-      // 展开 msgGroup(消息组)
+      // 1. 先展开 msgGroup(消息组)- 按照用户需求,消息显示在 sub_goals 之前
       const messages = msgGroups[nodeId];
       if (messages && messages.length > 0) {
         messages.forEach((msg) => {
@@ -242,6 +232,16 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
         });
       }
 
+      // 2. 再递归展开 sub_goals
+      if (goal.sub_goals && goal.sub_goals.length > 0) {
+        goal.sub_goals.forEach((subGoal) => {
+          const subX = x + HORIZONTAL_OFFSET; // 子节点向右偏移
+          const result = expandNode(subGoal, subX, level + 1, nodeId);
+          localNodes.push(...result.nodes);
+          lastNode = result.lastNode; // 更新最后一个节点
+        });
+      }
+
       return { nodes: localNodes, firstNode: currentNode, lastNode };
     };
 
@@ -308,6 +308,26 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
         const childNodes = current.allNodes.slice(1);
         const directChildren = childNodes.filter((n) => n.parentId === current.firstNode.id);
 
+        // Logic A: Messages Group Arc (A -> s1)
+        // 检查是否有消息节点且后面跟着子目标节点
+        // 如果有,绘制一条蓝色弧线从主节点到第一个子目标节点,包裹所有消息节点
+        const firstSubgoalIndex = directChildren.findIndex((n) => n.type === "goal");
+        if (firstSubgoalIndex > 0) {
+          const target = directChildren[firstSubgoalIndex];
+          const messages = directChildren.slice(0, firstSubgoalIndex);
+          const arcId = `arc-msg-${current.firstNode.id}-${target.id}`;
+          edges.push({
+            id: arcId,
+            source: current.firstNode,
+            target: target,
+            type: "arc",
+            level: 1,
+            collapsible: true,
+            collapsed: collapsedEdges.has(arcId),
+            children: messages,
+          });
+        }
+
         // 连接第一个节点到第一个子节点
         if (directChildren.length > 0) {
           edges.push({
@@ -407,6 +427,50 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
             collapsible: false,
             collapsed: false,
           });
+
+          // Logic C: Last Child Arc (s2 -> B)
+          // 如果最后一个直接子节点是 sub_goal 且有子节点,绘制弧线连接到下一个主节点
+          const lastChild = directChildren[directChildren.length - 1];
+          const lastChildChildren = nodes.filter((n) => n.parentId === lastChild.id);
+
+          if (lastChild.type === "goal" && lastChildChildren.length > 0) {
+            const arcId = `arc-${lastChild.id}-${next.firstNode.id}`;
+            edges.push({
+              id: arcId,
+              source: lastChild,
+              target: next.firstNode,
+              type: "arc",
+              level: 1,
+              collapsible: true,
+              collapsed: collapsedEdges.has(arcId),
+              children: lastChildChildren,
+            });
+
+            // 补充绘制最后一个子节点的内部连线(直线)
+            // 1. 连接 lastChild 到第一个孙节点
+            edges.push({
+              id: `line-${lastChild.id}-${lastChildChildren[0].id}`,
+              source: lastChild,
+              target: lastChildChildren[0],
+              type: "line",
+              level: lastChild.level + 1,
+              collapsible: false,
+              collapsed: false,
+            });
+
+            // 2. 连接孙节点之间
+            for (let k = 0; k < lastChildChildren.length - 1; k++) {
+              edges.push({
+                id: `line-${lastChildChildren[k].id}-${lastChildChildren[k + 1].id}`,
+                source: lastChildChildren[k],
+                target: lastChildChildren[k + 1],
+                type: "line",
+                level: lastChild.level + 1,
+                collapsible: false,
+                collapsed: false,
+              });
+            }
+          }
         }
       } else {
         // 情况 4:没有子节点,直接绘制直线
@@ -455,7 +519,17 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
     const NODE_HEIGHT = 110;
 
     // 1. 找出所有折叠的边
-    const collapsedEdgesList = layoutData.edges.filter((e) => e.collapsed);
+    const allCollapsedEdges = layoutData.edges.filter((e) => e.collapsed);
+
+    // 筛选出“有效”的折叠边:如果一个折叠边被另一个更大的折叠边包含(即其节点被隐藏),则忽略它
+    // 避免重复计算 shiftY
+    const collapsedEdgesList = allCollapsedEdges.filter((edge) => {
+      return !allCollapsedEdges.some((other) => {
+        if (edge === other) return false;
+        // 如果当前边的源节点或目标节点在另一个折叠边的子节点列表中,说明当前边是被包裹在内部的
+        return other.children?.some((child) => child.id === edge.source.id || child.id === edge.target.id);
+      });
+    });
 
     // 2. 过滤节点:隐藏被折叠的节点
     const visibleNodesRaw = layoutData.nodes.filter((node) => {