From c75953f3c9330cbc2a1336ad2fb5b404d77d737f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=AC=91=E7=9A=84=E6=AF=9B=E6=AF=9B=E8=99=AB?= <315515767@qq.com> Date: Sat, 8 Aug 2026 18:28:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(canvas):=20=E7=94=BB=E5=B8=83=E8=8A=82?= =?UTF-8?q?=E7=82=B9=20hover=20-=20=E6=B6=88=E9=99=A4=E6=8C=87=E9=92=88?= =?UTF-8?q?=E8=B7=9F=E9=9A=8F=E4=BD=8D=E7=A7=BB=E5=AF=BC=E8=87=B4=E7=9A=84?= =?UTF-8?q?=E6=8A=96=E5=8A=A8=EF=BC=88=E4=BF=AE=E5=A4=8D=20#186=EF=BC=89?= =?UTF-8?q?=20(#190)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ci): 后端测试 - 修复 responses 流式测试数据与 Grok 生图测试 SSRF 放行 * fix(canvas): 画布节点 hover - 消除指针跟随位移导致的抖动(修复 #186) --------- Co-authored-by: zhaoyue --- backend/internal/service/provider_test.go | 3 ++- .../components/canvas/canvas-frame-node.tsx | 8 +++--- web/src/components/canvas/canvas-node.tsx | 16 ++++------- web/src/styles/globals.css | 27 +++++++++++++++---- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/backend/internal/service/provider_test.go b/backend/internal/service/provider_test.go index 321f4a5fd..4e0c217c9 100644 --- a/backend/internal/service/provider_test.go +++ b/backend/internal/service/provider_test.go @@ -57,7 +57,7 @@ func TestWriteMediaPartSanitizesFilenameAndSetsMimeType(t *testing.T) { func TestParseTextEventStreamSupportsResponsesAndChat(t *testing.T) { responses := []byte(`event: response.output_text.delta -data: {"delta":"{\"title\":\"分镜\"}"} +data: {"delta":"{\"title\":\"分镜\""} event: response.output_text.delta data: {"delta":"}"} @@ -159,6 +159,7 @@ func TestVolcengineArkImageRejectsMaskBeforeRequest(t *testing.T) { } func TestRunGrokImageTaskUsesJSONEditContract(t *testing.T) { + t.Setenv("CANVAS_ALLOW_PRIVATE_UPSTREAMS", "true") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/images/edits" { t.Errorf("path = %q, want /v1/images/edits", r.URL.Path) diff --git a/web/src/components/canvas/canvas-frame-node.tsx b/web/src/components/canvas/canvas-frame-node.tsx index 41e35b16d..3ae73b0bf 100644 --- a/web/src/components/canvas/canvas-frame-node.tsx +++ b/web/src/components/canvas/canvas-frame-node.tsx @@ -156,19 +156,17 @@ export const CanvasFrameNode = React.memo(function CanvasFrameNode({ onMouseEnter={() => onHoverStart?.(data.id)} onMouseLeave={() => onHoverEnd?.(data.id)} > + {/* 帧节点同样禁用指针跟随 3D 位移,hover 使用 CSS 静态抬升 */}
diff --git a/web/src/components/canvas/canvas-node.tsx b/web/src/components/canvas/canvas-node.tsx index e32acab68..4e24453dc 100644 --- a/web/src/components/canvas/canvas-node.tsx +++ b/web/src/components/canvas/canvas-node.tsx @@ -149,9 +149,7 @@ export const CanvasNode = React.memo(function CanvasNode({ const mediaBorderColor = isActive ? theme.accent.primary : isRelated && !isBatchChild ? theme.accent.primary : "transparent"; const assetTags = data.metadata?.assetTags?.filter((tag) => tag.trim()) || []; const scriptMinHeight = data.type === CanvasNodeType.Script ? storyboardMinNodeHeight(data.metadata?.storyboardComposerHeight) : null; - const cometDepth = hasMediaContent ? 6.8 : data.type === CanvasNodeType.Script ? 2.8 : 4.6; - const cometTranslate = hasMediaContent ? 6 : data.type === CanvasNodeType.Script ? 2.5 : 4; - const cometDisabled = reduceMediaEffects || Boolean(dragOffset) || isEditingContent || isEditingTitle || isGeneratingNode || scale < 0.32 || batchClosing || batchOpening; + const nodeHoverLocked = reduceMediaEffects || Boolean(dragOffset) || isEditingContent || isEditingTitle || isGeneratingNode || scale < 0.32 || batchClosing || batchOpening; const textareaRef = useRef(null); const resizeRef = useRef({ isResizing: false, @@ -318,13 +316,12 @@ export const CanvasNode = React.memo(function CanvasNode({ onCommit={commitTitle} onCancel={() => { setTitleDraft(data.title); setIsEditingTitle(false); }} /> + {/* 画布节点不启用指针跟随 3D 位移,hover 反馈统一走 CSS 静态抬升,避免鼠标移动形成反馈震荡 */} onMouseDown(event, data.id)} onDoubleClick={(event) => { diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index 9921798d1..67c18eccd 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -647,6 +647,7 @@ --node-active-glow-strong: 0 0 0 6px var(--cn-active-stroke-soft); --node-shadow: var(--shadow-sm); --node-shadow-hover: var(--shadow-md); + --node-hover-lift: var(--space-1); /* 4px 节点 hover 静态抬升 */ --node-shadow-selected: var(--shadow-lg); --node-shadow-selected-primary: var(--shadow-xl); --node-shadow-active: var(--shadow-2xl); @@ -5308,17 +5309,29 @@ transition: transform 160ms var(--motion-ease-out); } - .canvas-node-shell { + .canvas-node-shell, + .canvas-frame-shell { transition: - border-color 160ms ease, - box-shadow 160ms ease, - background-color 160ms ease; + border-color var(--motion-state) var(--ease-aceternity), + box-shadow var(--motion-state) var(--ease-aceternity), + background-color var(--motion-state) var(--ease-aceternity), + transform var(--motion-panel) var(--ease-aceternity); } .canvas-node-shell.aceternity-comet-card { box-shadow: 0 18px 48px rgba(15, 23, 42, 0.1), 0 2px 6px rgba(15, 23, 42, 0.06); } + .canvas-node-shell.aceternity-comet-card:hover, + .canvas-frame-shell:hover { + transform: translateY(var(--node-hover-lift)); + } + + .canvas-node-shell[data-canvas-node-hover-locked="true"], + .canvas-frame-shell[data-canvas-frame-hover-locked="true"] { + transform: none !important; + } + .canvas-node-shell.aceternity-comet-card:hover { box-shadow: 0 30px 80px rgba(15, 23, 42, 0.18), 0 6px 20px rgba(15, 23, 42, 0.1); } @@ -5332,8 +5345,10 @@ } [data-canvas-viewport-interacting="true"] .canvas-node-shell, + [data-canvas-viewport-interacting="true"] .canvas-frame-shell, [data-canvas-viewport-interacting="true"] .aceternity-comet-card { transition: none !important; + transform: none !important; } /* 静止期用最终倍率重新排版 DOM,避免 Windows Chromium 复用交互期的低分辨率合成纹理。 */ @@ -5487,7 +5502,9 @@ .aceternity-comet-card, .aceternity-dock-command, .aceternity-floating-panel, - .canvas-menu-item { + .canvas-menu-item, + .canvas-node-shell, + .canvas-frame-shell { transform: none !important; transition-duration: 0.01ms !important; }