diff --git a/docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md b/docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md index 6fd784f9f..26bc34cb6 100644 --- a/docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md +++ b/docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md @@ -66,6 +66,7 @@ - 底边栏中会在上方弹出二级选项的入口不再依赖点击展开。鼠标悬停到入口即可打开二级面板,鼠标离开入口和二级面板后自动收起;当前范围包括 `生成规范` 和 `生成音乐`。 - 底边栏二级选项面板必须锚定到对应入口按钮本身,不使用屏幕居中或固定底部偏移;移动端窄屏下也应保持跟随入口位置。 - 生成图片和生成视频文本输入框紧贴参考图下方,取消旧网格预留导致的空白高度。 +- 在生成类面板或独立修改弹窗的文本输入框内滚动时,滚轮只用于输入内容或面板自身,不得冒泡平移或缩放画布; - 生成规范类图片固定使用 `16:9·2K · gpt-image-2`。这三个参数在面板底部沿用可编辑参数按钮的胶囊样式展示,但控件保持禁用不可点击,不提供比例、尺寸或模型修改入口。 - 宣发素材的 `游戏首图`、`详情五图`、`运营海报` 固定使用 `gpt-image-2`。面板底部只显示禁用态 `gpt-image-2` 模型胶囊和生成按钮,不出现 `nanobanana2` 选项;后端收到 `publication-material` 旧请求时也必须强制归一为 `gpt-image-2`。 - 图片快速编辑保留一个提示词输入框,并展示与常规图片生成一致的比例 / 尺寸和模型选择;提示词 placeholder 为 `你希望素材如何修改?`,提交按钮显示 `修改`,不展示额外参考图控件。打开面板时优先继承原图关联生成器记录的模型、比例和尺寸;没有关联生成器时使用图层模型,并按原图真实分辨率推导比例和尺寸;模型缺失或已不受支持时回落到当前默认图片模型。切换模型后只展示该模型支持的参数,不兼容的当前值回落到该模型默认值,按钮泥点按选定模型和尺寸同步刷新。提交时必须同时传递 `model / aspectRatio / imageSize`;后端按模型选择 provider 协议:`nanobanana2` 使用 `generateContent + inline_data`,`gpt-image-2` 使用 `/v1/images/edits` multipart,不能把 nanobanana 模型 ID 发往 GPT edits 端点。 diff --git a/src/components/image-editor/useImageCanvasViewportControls.test.tsx b/src/components/image-editor/useImageCanvasViewportControls.test.tsx index 0f0ab01eb..6c4fed9f9 100644 --- a/src/components/image-editor/useImageCanvasViewportControls.test.tsx +++ b/src/components/image-editor/useImageCanvasViewportControls.test.tsx @@ -191,6 +191,27 @@ describe('useImageCanvasViewportControls', () => { }); }); + it('does not pan the canvas when a generation prompt receives wheel input', () => { + const { result, viewportElement } = renderViewportControls(); + const prompt = document.createElement('textarea'); + viewportElement.append(prompt); + const initialViewport = result.current.viewport; + + const wheelEvent = new WheelEvent('wheel', { + bubbles: true, + cancelable: true, + deltaY: 120, + clientX: 260, + clientY: 190, + }); + act(() => { + prompt.dispatchEvent(wheelEvent); + }); + + expect(result.current.viewport).toEqual(initialViewport); + expect(wheelEvent.defaultPrevented).toBe(false); + }); + it('handles native two-axis wheel pan, ctrl wheel zoom and minimap movement', () => { const rafCallbacks: FrameRequestCallback[] = []; vi.spyOn(window, 'requestAnimationFrame').mockImplementation( diff --git a/src/components/image-editor/useImageCanvasViewportControls.ts b/src/components/image-editor/useImageCanvasViewportControls.ts index 2f74c1b8f..dddbe6784 100644 --- a/src/components/image-editor/useImageCanvasViewportControls.ts +++ b/src/components/image-editor/useImageCanvasViewportControls.ts @@ -34,6 +34,15 @@ export const DEFAULT_IMAGE_CANVAS_VIEWPORT: CanvasViewport = { scale: 0.5, }; +function isCanvasWheelInteractionTarget(target: EventTarget | null) { + return ( + target instanceof Element && + target.closest( + 'input, textarea, select, [contenteditable="true"]', + ) !== null + ); +} + type UseImageCanvasViewportControlsOptions = { canvasViewportRef: RefObject; layers: CanvasLayer[]; @@ -253,12 +262,18 @@ export function useImageCanvasViewportControls({ const handleNativeWheel = useCallback( (event: WheelEvent) => { - event.preventDefault(); + // Let form controls and their scrollable panels keep native wheel input. + if (isCanvasWheelInteractionTarget(event.target)) { + return; + } + const viewportElement = canvasViewportRef.current; if (!viewportElement) { return; } + event.preventDefault(); + if (!event.ctrlKey && !event.metaKey) { const useShiftWheelHorizontalFallback = event.shiftKey && event.deltaX === 0;