修复角度调整弹窗拖拽误触底层画布
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-02-15 22:34:12 +08:00
parent 2793582fd1
commit 67c18dd820
3 changed files with 60 additions and 9 deletions

View File

@ -39,6 +39,22 @@ export function AngleEditDialog({
}); });
// 预览 Tab 状态:如果生成了结果,默认显示结果;否则显示原图 // 预览 Tab 状态:如果生成了结果,默认显示结果;否则显示原图
const [activeTab, setActiveTab] = useState<"original" | "result">("original"); const [activeTab, setActiveTab] = useState<"original" | "result">("original");
const stopDialogEventPropagation = useCallback((event: React.SyntheticEvent) => {
event.stopPropagation();
}, []);
const stopDialogEventCapture = useCallback((event: React.SyntheticEvent) => {
event.stopPropagation();
}, []);
const stopDialogWheelPropagation = useCallback((event: React.WheelEvent) => {
event.stopPropagation();
}, []);
const stopDialogWheelCapture = useCallback((event: React.WheelEvent) => {
event.preventDefault();
event.stopPropagation();
}, []);
// 当对话框关闭时重置状态 // 当对话框关闭时重置状态
useEffect(() => { useEffect(() => {
@ -173,6 +189,17 @@ export function AngleEditDialog({
<Dialog open={open} onOpenChange={handleOpenChange}> <Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent <DialogContent
className="max-w-4xl p-0 gap-0 overflow-hidden border-none shadow-2xl bg-background/95 backdrop-blur-xl" className="max-w-4xl p-0 gap-0 overflow-hidden border-none shadow-2xl bg-background/95 backdrop-blur-xl"
onPointerDownCapture={stopDialogEventCapture}
onPointerMoveCapture={stopDialogEventCapture}
onPointerUpCapture={stopDialogEventCapture}
onMouseDown={stopDialogEventPropagation}
onMouseMove={stopDialogEventPropagation}
onMouseUp={stopDialogEventPropagation}
onTouchStart={stopDialogEventPropagation}
onTouchMove={stopDialogEventPropagation}
onTouchEnd={stopDialogEventPropagation}
onWheelCapture={stopDialogWheelCapture}
onWheel={stopDialogWheelPropagation}
onInteractOutside={(e) => { onInteractOutside={(e) => {
// 阻止点击遮罩层关闭对话框(防止意外触发导航) // 阻止点击遮罩层关闭对话框(防止意外触发导航)
if (isProcessing) { if (isProcessing) {
@ -347,4 +374,3 @@ export function AngleEditDialog({
} }
export default AngleEditDialog; export default AngleEditDialog;

View File

@ -171,15 +171,22 @@ export function CanvasWorkspace() {
return !!target.closest("[data-canvas-node-id]"); return !!target.closest("[data-canvas-node-id]");
}, []); }, []);
const isEventFromCanvasContainer = useCallback((target: EventTarget | null) => {
if (!(target instanceof Node) || !containerRef.current) return false;
return containerRef.current.contains(target);
}, []);
// 处理画布点击(取消选择) // 处理画布点击(取消选择)
const handleCanvasClick = useCallback((e: React.MouseEvent) => { const handleCanvasClick = useCallback((e: React.MouseEvent) => {
if (!isEventFromCanvasContainer(e.target)) return;
if (!isPointOnNodeElement(e.target) && !isMarqueeSelecting) { if (!isPointOnNodeElement(e.target) && !isMarqueeSelecting) {
clearSelection(); clearSelection();
} }
}, [clearSelection, isMarqueeSelecting, isPointOnNodeElement]); }, [clearSelection, isEventFromCanvasContainer, isMarqueeSelecting, isPointOnNodeElement]);
// 处理画布平移开始 // 处理画布平移开始
const handleMouseDown = useCallback((e: React.MouseEvent) => { const handleMouseDown = useCallback((e: React.MouseEvent) => {
if (!isEventFromCanvasContainer(e.target)) return;
if (tool === 'pan' || e.button === 1 || (e.button === 0 && e.altKey)) { if (tool === 'pan' || e.button === 1 || (e.button === 0 && e.altKey)) {
setIsPanning(true); setIsPanning(true);
setPanStart({ x: e.clientX - viewport.x, y: e.clientY - viewport.y }); setPanStart({ x: e.clientX - viewport.x, y: e.clientY - viewport.y });
@ -193,10 +200,11 @@ export function CanvasWorkspace() {
setMarqueeStart(start); setMarqueeStart(start);
setMarqueeEnd(start); setMarqueeEnd(start);
} }
}, [isPointOnNodeElement, toCanvasPosition, tool, viewport]); }, [isEventFromCanvasContainer, isPointOnNodeElement, toCanvasPosition, tool, viewport]);
// 处理画布平移 // 处理画布平移
const handleMouseMove = useCallback((e: React.MouseEvent) => { const handleMouseMove = useCallback((e: React.MouseEvent) => {
if (!isEventFromCanvasContainer(e.target)) return;
dragPointerRef.current = toCanvasPosition(e.clientX, e.clientY); dragPointerRef.current = toCanvasPosition(e.clientX, e.clientY);
if (isPanning) { if (isPanning) {
setViewport({ setViewport({
@ -209,10 +217,11 @@ export function CanvasWorkspace() {
if (isMarqueeSelecting) { if (isMarqueeSelecting) {
setMarqueeEnd(toCanvasPosition(e.clientX, e.clientY)); setMarqueeEnd(toCanvasPosition(e.clientX, e.clientY));
} }
}, [isMarqueeSelecting, isPanning, panStart, setViewport, toCanvasPosition]); }, [isEventFromCanvasContainer, isMarqueeSelecting, isPanning, panStart, setViewport, toCanvasPosition]);
// 处理画布平移结束 // 处理画布平移结束
const handleMouseUp = useCallback(() => { const handleMouseUp = useCallback((e?: React.MouseEvent) => {
if (e && !isEventFromCanvasContainer(e.target)) return;
setIsPanning(false); setIsPanning(false);
if (isMarqueeSelecting && marqueeStart && marqueeEnd) { if (isMarqueeSelecting && marqueeStart && marqueeEnd) {
@ -247,7 +256,7 @@ export function CanvasWorkspace() {
setIsMarqueeSelecting(false); setIsMarqueeSelecting(false);
setMarqueeStart(null); setMarqueeStart(null);
setMarqueeEnd(null); setMarqueeEnd(null);
}, [clearSelection, isMarqueeSelecting, marqueeEnd, marqueeStart, nodes, selectNodes, viewport.x, viewport.y, viewport.zoom]); }, [clearSelection, isEventFromCanvasContainer, isMarqueeSelecting, marqueeEnd, marqueeStart, nodes, selectNodes, viewport.x, viewport.y, viewport.zoom]);
// 新增图片后自动居中展示 // 新增图片后自动居中展示
const centerNodeInView = useCallback((nodeId: string) => { const centerNodeInView = useCallback((nodeId: string) => {
@ -284,6 +293,7 @@ export function CanvasWorkspace() {
// 处理滚轮缩放 // 处理滚轮缩放
const handleWheel = useCallback((e: React.WheelEvent) => { const handleWheel = useCallback((e: React.WheelEvent) => {
if (!isEventFromCanvasContainer(e.target)) return;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -308,7 +318,7 @@ export function CanvasWorkspace() {
const newX = mouseX - (mouseX - viewport.x) * scaleChange; const newX = mouseX - (mouseX - viewport.x) * scaleChange;
const newY = mouseY - (mouseY - viewport.y) * scaleChange; const newY = mouseY - (mouseY - viewport.y) * scaleChange;
setViewport({ x: newX, y: newY, zoom: newZoom }); setViewport({ x: newX, y: newY, zoom: newZoom });
}, [isMacPlatform, setViewport, viewport.x, viewport.y, viewport.zoom]); }, [isEventFromCanvasContainer, isMacPlatform, setViewport, viewport.x, viewport.y, viewport.zoom]);
const getImageDimensionsFromFile = useCallback((file: File): Promise<{ const getImageDimensionsFromFile = useCallback((file: File): Promise<{
width: number; width: number;

View File

@ -421,6 +421,8 @@ export function QwenCameraWidget({
}, []); }, []);
const handlePointerDown = useCallback((event: React.MouseEvent | React.TouchEvent) => { const handlePointerDown = useCallback((event: React.MouseEvent | React.TouchEvent) => {
event.preventDefault();
event.stopPropagation();
if (!canvasRef.current || !cameraRef.current) return; if (!canvasRef.current || !cameraRef.current) return;
const clientX = "touches" in event ? event.touches[0].clientX : event.clientX; const clientX = "touches" in event ? event.touches[0].clientX : event.clientX;
@ -446,7 +448,9 @@ export function QwenCameraWidget({
}, [getMousePos]); }, [getMousePos]);
const handlePointerMove = useCallback((event: React.MouseEvent | React.TouchEvent) => { const handlePointerMove = useCallback((event: React.MouseEvent | React.TouchEvent) => {
event.stopPropagation();
if (!canvasRef.current || !cameraRef.current || !isDraggingRef.current) return; if (!canvasRef.current || !cameraRef.current || !isDraggingRef.current) return;
event.preventDefault();
const clientX = "touches" in event ? event.touches[0].clientX : event.clientX; const clientX = "touches" in event ? event.touches[0].clientX : event.clientX;
const clientY = "touches" in event ? event.touches[0].clientY : event.clientY; const clientY = "touches" in event ? event.touches[0].clientY : event.clientY;
@ -496,7 +500,11 @@ export function QwenCameraWidget({
} }
}, [getMousePos, onStateChange]); }, [getMousePos, onStateChange]);
const handlePointerUp = useCallback(() => { const handlePointerUp = useCallback((event?: React.MouseEvent | React.TouchEvent) => {
if (event) {
event.preventDefault();
event.stopPropagation();
}
isDraggingRef.current = false; isDraggingRef.current = false;
dragTargetRef.current = null; dragTargetRef.current = null;
if (canvasRef.current) { if (canvasRef.current) {
@ -504,6 +512,11 @@ export function QwenCameraWidget({
} }
}, []); }, []);
const handleWheel = useCallback((event: React.WheelEvent) => {
event.preventDefault();
event.stopPropagation();
}, []);
// 下拉选择器变化处理 // 下拉选择器变化处理
const handleAzimuthChange = useCallback((value: string) => { const handleAzimuthChange = useCallback((value: string) => {
const val = Number(value); const val = Number(value);
@ -582,7 +595,7 @@ export function QwenCameraWidget({
<div className="relative flex-1 min-h-0 bg-muted/20 rounded-lg overflow-hidden border"> <div className="relative flex-1 min-h-0 bg-muted/20 rounded-lg overflow-hidden border">
<canvas <canvas
ref={canvasRef} ref={canvasRef}
className="w-full h-full block cursor-grab active:cursor-grabbing" className="w-full h-full block cursor-grab active:cursor-grabbing touch-none select-none"
onMouseDown={handlePointerDown} onMouseDown={handlePointerDown}
onMouseMove={handlePointerMove} onMouseMove={handlePointerMove}
onMouseUp={handlePointerUp} onMouseUp={handlePointerUp}
@ -590,6 +603,8 @@ export function QwenCameraWidget({
onTouchStart={handlePointerDown} onTouchStart={handlePointerDown}
onTouchMove={handlePointerMove} onTouchMove={handlePointerMove}
onTouchEnd={handlePointerUp} onTouchEnd={handlePointerUp}
onTouchCancel={handlePointerUp}
onWheel={handleWheel}
/> />
{/* 提示词浮层 - 放在底部 */} {/* 提示词浮层 - 放在底部 */}