From 6cc4f11f963dddc47b5493280bb8cc9275ddb770 Mon Sep 17 00:00:00 2001 From: huangmin <2927933426@qq.com> Date: Sat, 6 Jun 2026 16:43:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(workflow):=20=E5=AE=9E=E7=8E=B0=E9=80=89?= =?UTF-8?q?=E4=B8=AD=E5=88=86=E7=BB=84=E6=97=B6=E5=88=9B=E5=BB=BA=E6=96=B0?= =?UTF-8?q?=E7=BB=84=E6=9B=BF=E6=8D=A2=E5=8E=9F=E6=9C=89=E5=88=86=E7=BB=84?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增工作流集成测试验证分组替换逻辑 2. 重构createGroup方法,支持选中分组节点时自动替换为子节点创建新组 3. 修复多选工具栏的定位逻辑,使用react-flow内置方法计算边界 4. 更新多选工具栏显示逻辑,选中分组节点时显示创建组按钮而非取消分组 5. 新增工具栏测试用例覆盖分组创建和定位场景 --- src/components/MultiSelectToolbar.tsx | 34 +++--- .../__tests__/MultiSelectToolbar.test.tsx | 105 +++++++++++++++++- .../workflowStore.integration.test.ts | 34 ++++++ src/store/workflowStore.ts | 31 +++++- 4 files changed, 172 insertions(+), 32 deletions(-) diff --git a/src/components/MultiSelectToolbar.tsx b/src/components/MultiSelectToolbar.tsx index 1f8ab7cc..f20f4a49 100644 --- a/src/components/MultiSelectToolbar.tsx +++ b/src/components/MultiSelectToolbar.tsx @@ -17,13 +17,18 @@ const STACK_GAP = 20; export function MultiSelectToolbar() { const { t } = useI18n(); const { nodes, onNodesChange, createGroup, removeNodesFromGroup } = useWorkflowStore(); - const { getViewport } = useReactFlow(); + const { flowToScreenPosition, getNodesBounds } = useReactFlow(); const selectedNodes = useMemo( () => nodes.filter((node) => node.selected), [nodes] ); + const hasSelectedGroupNode = useMemo( + () => selectedNodes.some((node) => node.type === "group"), + [selectedNodes] + ); + // Check if any selected nodes are in a group const selectedNodeGroups = useMemo(() => { const groupIds = new Set(selectedNodes.map((n) => n.groupId).filter(Boolean)); @@ -36,27 +41,14 @@ export function MultiSelectToolbar() { const toolbarPosition = useMemo(() => { if (selectedNodes.length < 2) return null; - const viewport = getViewport(); - - // Find bounding box of selected nodes - let minX = Infinity; - let minY = Infinity; - let maxX = -Infinity; - - selectedNodes.forEach((node) => { - const nodeWidth = (node.style?.width as number) || node.measured?.width || 220; - minX = Math.min(minX, node.position.x); - minY = Math.min(minY, node.position.y); - maxX = Math.max(maxX, node.position.x + nodeWidth); + const bounds = getNodesBounds(selectedNodes.map((node) => node.id)); + const topCenter = flowToScreenPosition({ + x: bounds.x + bounds.width / 2, + y: bounds.y, }); - // Convert flow coordinates to screen coordinates - const centerX = (minX + maxX) / 2; - const screenX = centerX * viewport.zoom + viewport.x; - const screenY = minY * viewport.zoom + viewport.y - 50; // 50px above the top - - return { x: screenX, y: screenY }; - }, [selectedNodes, getViewport]); + return { x: topCenter.x, y: topCenter.y - 50 }; + }, [flowToScreenPosition, getNodesBounds, selectedNodes]); const handleStackHorizontally = () => { if (selectedNodes.length < 2) return; @@ -260,7 +252,7 @@ export function MultiSelectToolbar() {
{/* Group/Ungroup buttons */} - {someInGroup ? ( + {someInGroup && !hasSelectedGroupNode ? (