192 lines
5.9 KiB
TypeScript
192 lines
5.9 KiB
TypeScript
/**
|
||
* 通用拖拽状态 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,
|
||
}
|
||
}
|