gongxiangp2/hooks/useDragHandlers.ts
2026-07-24 18:15:27 +08:00

192 lines
5.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 通用拖拽状态 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<string, boolean>
}
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<Record<string, boolean>>({})
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,
}
}