/** * 通用拖拽状态 Hook。 * 业务组件可以保留各自的文件处理逻辑,但拖拽边界统一使用同一套判断。 */ import { useCallback, useState } from 'react' import { hasDragLeftDropZone, prepareFileDrag, validateImageFiles, } from '@/components/upload/drag-drop-utils' export interface DragState { isDragging: boolean isPreviewDragging: boolean isDisplayImageDragging: boolean batchPreviewDragging: Record } export interface UseDragHandlersOptions { isValidMainFile: (file: File) => boolean onMaterialFileDrop: (files: File[]) => void onPreviewImageDrop: (files: File[]) => void onDisplayImageDrop: (file: File) => void setError: (error: string) => void disabled?: boolean } export function useDragHandlers(options: UseDragHandlersOptions) { const { isValidMainFile, onMaterialFileDrop, onPreviewImageDrop, onDisplayImageDrop, setError, disabled = false, } = options const [isDragging, setIsDragging] = useState(false) const [isPreviewDragging, setIsPreviewDragging] = useState(false) const [isDisplayImageDragging, setIsDisplayImageDragging] = useState(false) const [batchPreviewDragging, setBatchPreviewDragging] = useState>({}) const handleDragEnter = useCallback((event: React.DragEvent) => { prepareFileDrag(event, disabled) if (!disabled) setIsDragging(true) }, [disabled]) const handleDragLeave = useCallback((event: React.DragEvent) => { event.preventDefault() event.stopPropagation() if (hasDragLeftDropZone(event)) setIsDragging(false) }, []) const handleDragOver = useCallback((event: React.DragEvent) => { prepareFileDrag(event, disabled) }, [disabled]) const handleDrop = useCallback((event: React.DragEvent) => { prepareFileDrag(event, disabled) setIsDragging(false) if (disabled) { setError('文件正在上传,请等待完成后再添加') return } const files = Array.from(event.dataTransfer.files) if (files.length === 0) return const materialFiles = files.filter(isValidMainFile) if (materialFiles.length > 0) { const invalidNames = files .filter((file) => !isValidMainFile(file)) .map((file) => file.name) setError( invalidNames.length > 0 ? `以下文件不支持当前素材类型,已忽略:${invalidNames.join('、')}` : '', ) onMaterialFileDrop(materialFiles) } else { setError('请拖拽素材文件到此区域,图片请拖拽到对应的图片上传区域') } }, [disabled, isValidMainFile, onMaterialFileDrop, setError]) const handlePreviewDragEnter = useCallback((event: React.DragEvent) => { prepareFileDrag(event, disabled) if (!disabled) setIsPreviewDragging(true) }, [disabled]) const handlePreviewDragLeave = useCallback((event: React.DragEvent) => { event.preventDefault() event.stopPropagation() if (hasDragLeftDropZone(event)) setIsPreviewDragging(false) }, []) const handlePreviewDragOver = useCallback((event: React.DragEvent) => { prepareFileDrag(event, disabled) }, [disabled]) const handlePreviewDrop = useCallback((event: React.DragEvent) => { prepareFileDrag(event, disabled) setIsPreviewDragging(false) if (disabled) { setError('文件正在上传,请等待完成后再添加预览图') return } const files = Array.from(event.dataTransfer.files) if (files.length === 0) return const { acceptedFiles, errors } = validateImageFiles(files) if (acceptedFiles.length > 0) { setError(errors.join(';')) onPreviewImageDrop(acceptedFiles) } else { setError('请拖拽图片文件') } }, [disabled, onPreviewImageDrop, setError]) const handleDisplayImageDragEnter = useCallback((event: React.DragEvent) => { prepareFileDrag(event, disabled) if (!disabled) setIsDisplayImageDragging(true) }, [disabled]) const handleDisplayImageDragLeave = useCallback((event: React.DragEvent) => { event.preventDefault() event.stopPropagation() if (hasDragLeftDropZone(event)) setIsDisplayImageDragging(false) }, []) const handleDisplayImageDragOver = useCallback((event: React.DragEvent) => { prepareFileDrag(event, disabled) }, [disabled]) const handleDisplayImageDrop = useCallback((event: React.DragEvent) => { prepareFileDrag(event, disabled) setIsDisplayImageDragging(false) if (disabled) { setError('文件正在上传,请等待完成后再更换展示图') return } const files = Array.from(event.dataTransfer.files) if (files.length === 0) return const { acceptedFiles, errors } = validateImageFiles(files, { maxCount: 1 }) if (acceptedFiles[0]) { setError(errors.join(';')) onDisplayImageDrop(acceptedFiles[0]) } else { setError('请拖拽图片文件') } }, [disabled, onDisplayImageDrop, setError]) const handleBatchPreviewDragEnter = useCallback((itemId: string) => { if (disabled) return setBatchPreviewDragging((prev) => ({ ...prev, [itemId]: true })) }, [disabled]) const handleBatchPreviewDragLeave = useCallback((itemId: string) => { setBatchPreviewDragging((prev) => ({ ...prev, [itemId]: false })) }, []) const resetBatchPreviewDragging = useCallback((itemId: string) => { setBatchPreviewDragging((prev) => ({ ...prev, [itemId]: false })) }, []) return { isDragging, isPreviewDragging, isDisplayImageDragging, batchPreviewDragging, handleDragEnter, handleDragLeave, handleDragOver, handleDrop, handlePreviewDragEnter, handlePreviewDragLeave, handlePreviewDragOver, handlePreviewDrop, handleDisplayImageDragEnter, handleDisplayImageDragLeave, handleDisplayImageDragOver, handleDisplayImageDrop, handleBatchPreviewDragEnter, handleBatchPreviewDragLeave, resetBatchPreviewDragging, setBatchPreviewDragging, } }