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 ? (