import { useState, useEffect, useCallback, useRef } from "react"; import { useScale } from "@/contexts/ScaleContext"; interface DragState { isDragging: boolean; dragData: T | null; mousePosition: { x: number; y: number }; } /** * 自定义拖拽 Hook * 支持页面 scale 缩放的拖拽功能 */ export function useDragAndDrop() { const { scale, offsetX, offsetY } = useScale(); const [state, setState] = useState>({ isDragging: false, dragData: null, mousePosition: { x: 0, y: 0 }, }); const isDraggingRef = useRef(false); // 开始拖拽 const startDrag = useCallback((data: T, event?: React.MouseEvent) => { if (event) { event.preventDefault(); event.stopPropagation(); // 立即获取鼠标初始位置 isDraggingRef.current = true; setState({ isDragging: true, dragData: data, mousePosition: { x: event.clientX, y: event.clientY }, }); } else { isDraggingRef.current = true; setState({ isDragging: true, dragData: data, mousePosition: { x: 0, y: 0 }, }); } }, []); // 结束拖拽 const endDrag = useCallback(() => { isDraggingRef.current = false; setState({ isDragging: false, dragData: null, mousePosition: { x: 0, y: 0 }, }); }, []); // 监听鼠标移动 - 使用独立的事件监听器 useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isDraggingRef.current) return; setState((prev) => ({ ...prev, mousePosition: { x: e.clientX, y: e.clientY }, })); }; const handleMouseUp = () => { // 拖拽结束的逻辑由外部处理 // 这里只是提供一个安全机制 }; // 始终添加事件监听器,在handleMouseMove内部检查拖拽状态 window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mouseup", handleMouseUp); return () => { window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; }, []); // 空依赖数组,只在组件挂载/卸载时执行 // 将屏幕坐标转换为缩放后的页面坐标 const screenToScaledCoords = useCallback( (screenX: number, screenY: number) => { return { x: (screenX - offsetX) / scale, y: (screenY - offsetY) / scale, }; }, [scale, offsetX, offsetY] ); // 检查点是否在元素内 const isPointInElement = useCallback( (pointX: number, pointY: number, element: HTMLElement | null): boolean => { if (!element) return false; const rect = element.getBoundingClientRect(); return ( pointX >= rect.left && pointX <= rect.right && pointY >= rect.top && pointY <= rect.bottom ); }, [] ); return { isDragging: state.isDragging, dragData: state.dragData, mousePosition: state.mousePosition, startDrag, endDrag, screenToScaledCoords, isPointInElement, }; } /** * 放置区域 Hook * 用于检测拖拽元素是否悬停在放置区域上 */ export function useDropZone() { const dropZoneRef = useRef(null); const [isOver, setIsOver] = useState(false); // 检查鼠标是否在放置区域内 const checkIsOver = useCallback((mouseX: number, mouseY: number) => { if (!dropZoneRef.current) return false; const rect = dropZoneRef.current.getBoundingClientRect(); const over = mouseX >= rect.left && mouseX <= rect.right && mouseY >= rect.top && mouseY <= rect.bottom; // 只有当状态真正改变时才更新 setIsOver((prev) => (prev !== over ? over : prev)); return over; }, []); return { dropZoneRef, isOver, checkIsOver, }; }