From 86c9ade93e61d1cbb60fe49c8e06be77f1535ed8 Mon Sep 17 00:00:00 2001 From: huangmin <2927933426@qq.com> Date: Mon, 8 Jun 2026 12:37:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(GenerationComposer):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=88=86=E7=BB=84=E8=8A=82=E7=82=B9=E5=86=85=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E7=9A=84=E5=AE=9A=E4=BD=8D=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增getAbsoluteNodePosition函数计算节点基于画布的绝对坐标,替换原有相对父节点的位置计算逻辑,确保分组内节点的编辑器能正确显示在对应位置 --- .../__tests__/GenerationComposer.test.tsx | 30 ++++++++++++++++ .../composer/GenerationComposer.tsx | 34 +++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/components/__tests__/GenerationComposer.test.tsx b/src/components/__tests__/GenerationComposer.test.tsx index b28a1bf7..6941e0ac 100644 --- a/src/components/__tests__/GenerationComposer.test.tsx +++ b/src/components/__tests__/GenerationComposer.test.tsx @@ -427,6 +427,36 @@ describe("GenerationComposer", () => { expect(screen.getByPlaceholderText(/Describe what you want to generate/)).toHaveClass("h-[13.5rem]"); }); + it("positions the selected node editor below grouped nodes using absolute canvas coordinates", () => { + useWorkflowStore.setState({ + nodes: [ + { + id: "group-node-group-1", + type: "group", + position: { x: 400, y: 200 }, + data: { groupId: "group-1" }, + style: { width: 500, height: 360 }, + } as WorkflowNode, + { + ...imageNode("img-1", true), + parentId: "group-node-group-1", + extent: "parent", + expandParent: true, + groupId: "group-1", + position: { x: 100, y: 40 }, + measured: { width: 260, height: 180 }, + } as WorkflowNode, + ], + }); + + render(); + + expect(screen.getByTestId("node-inline-composer")).toHaveStyle({ + left: "108px", + top: "436px", + }); + }); + it("keeps the selected node editor below the node when below would leave the viewport", () => { const originalInnerHeight = window.innerHeight; const originalInnerWidth = window.innerWidth; diff --git a/src/components/composer/GenerationComposer.tsx b/src/components/composer/GenerationComposer.tsx index 4194674b..03d92957 100644 --- a/src/components/composer/GenerationComposer.tsx +++ b/src/components/composer/GenerationComposer.tsx @@ -892,6 +892,30 @@ function getAdapter(nodeType: NodeType | null): ComposerAdapter [item.id, item])); + const visited = new Set(); + + const resolve = (current: WorkflowNode): { x: number; y: number } => { + if (!current.parentId || visited.has(current.id)) return current.position; + visited.add(current.id); + + const parent = nodeById.get(current.parentId); + if (!parent) return current.position; + + const parentPosition = resolve(parent); + return { + x: parentPosition.x + current.position.x, + y: parentPosition.y + current.position.y, + }; + }; + + return resolve(node); +} + function deriveComposerContext(nodes: WorkflowNode[], edges: WorkflowEdge[]): ComposerContext { const selectedNodes = nodes.filter((node) => node.selected); if (selectedNodes.length === 0) { @@ -1702,6 +1726,10 @@ export function GenerationComposer() { () => context.node ? getComposerNodeDimensions(context.node) : null, [context.node] ); + const selectedNodePosition = useMemo( + () => context.node ? getAbsoluteNodePosition(context.node, nodes) : null, + [context.node, nodes] + ); const safeZoom = zoom > 0 ? zoom : 1; const viewportWidth = typeof window === "undefined" ? Number.POSITIVE_INFINITY : window.innerWidth; const visibleLeft = -viewportX / safeZoom; @@ -1709,15 +1737,15 @@ export function GenerationComposer() { const composerWidth = isComposerCollapsed ? COMPOSER_COLLAPSED_WIDTH : COMPOSER_EXPANDED_WIDTH; const composerFlowWidth = composerWidth / safeZoom; const viewportMargin = COMPOSER_VIEWPORT_MARGIN / safeZoom; - const inlineComposerPosition = context.node && selectedNodeDimensions + const inlineComposerPosition = context.node && selectedNodeDimensions && selectedNodePosition ? (() => { - const centeredLeft = context.node.position.x + selectedNodeDimensions.width / 2 - composerFlowWidth / 2; + const centeredLeft = selectedNodePosition.x + selectedNodeDimensions.width / 2 - composerFlowWidth / 2; const minLeft = visibleLeft + viewportMargin; const maxLeft = visibleRight - composerFlowWidth - viewportMargin; const left = maxLeft >= minLeft ? Math.min(Math.max(centeredLeft, minLeft), maxLeft) : minLeft; - const belowTop = context.node.position.y + selectedNodeDimensions.height + viewportMargin; + const belowTop = selectedNodePosition.y + selectedNodeDimensions.height + viewportMargin; return { left,