227 lines
6.3 KiB
TypeScript
227 lines
6.3 KiB
TypeScript
/**
|
|
* 拖拽处理 Hook
|
|
* 封装素材文件、预览图片、展示图片的拖拽事件处理
|
|
*/
|
|
|
|
import { useState, useCallback } from 'react'
|
|
|
|
/**
|
|
* 拖拽状态
|
|
*/
|
|
export interface DragState {
|
|
/** 素材文件拖拽状态 */
|
|
isDragging: boolean
|
|
/** 预览图片拖拽状态 */
|
|
isPreviewDragging: boolean
|
|
/** 详情页展示图片拖拽状态 */
|
|
isDisplayImageDragging: boolean
|
|
/** 批量模式下每个文件的预览图拖拽状态 */
|
|
batchPreviewDragging: Record<string, boolean>
|
|
}
|
|
|
|
/**
|
|
* 拖拽处理 Hook 参数
|
|
*/
|
|
export interface UseDragHandlersOptions {
|
|
/** 验证是否为有效的素材文件 */
|
|
isValidMainFile: (file: File) => boolean
|
|
/** 处理素材文件拖放 */
|
|
onMaterialFileDrop: (files: File[]) => void
|
|
/** 处理预览图片拖放 */
|
|
onPreviewImageDrop: (files: File[]) => void
|
|
/** 处理展示图片拖放 */
|
|
onDisplayImageDrop: (file: File) => void
|
|
/** 设置错误信息 */
|
|
setError: (error: string) => void
|
|
}
|
|
|
|
/**
|
|
* 判断鼠标是否真正离开了元素(避免经过子元素时闪屏)
|
|
*/
|
|
function isMouseOutsideElement(e: React.DragEvent, element: HTMLElement): boolean {
|
|
const rect = element.getBoundingClientRect()
|
|
const x = e.clientX
|
|
const y = e.clientY
|
|
return x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom
|
|
}
|
|
|
|
/**
|
|
* 拖拽处理 Hook
|
|
*/
|
|
export function useDragHandlers(options: UseDragHandlersOptions) {
|
|
const {
|
|
isValidMainFile,
|
|
onMaterialFileDrop,
|
|
onPreviewImageDrop,
|
|
onDisplayImageDrop,
|
|
setError,
|
|
} = 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((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
setIsDragging(true)
|
|
}, [])
|
|
|
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
if (isMouseOutsideElement(e, e.currentTarget as HTMLElement)) {
|
|
setIsDragging(false)
|
|
}
|
|
}, [])
|
|
|
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
}, [])
|
|
|
|
const handleDrop = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
setIsDragging(false)
|
|
|
|
const files = Array.from(e.dataTransfer.files)
|
|
if (files.length === 0) return
|
|
|
|
// 只处理素材文件(包括压缩包/视频/音频/PDF 视类型而定)
|
|
const materialFiles = files.filter(f => isValidMainFile(f))
|
|
if (materialFiles.length > 0) {
|
|
onMaterialFileDrop(materialFiles)
|
|
} else {
|
|
setError('请拖拽素材文件到此区域,图片请拖拽到对应的图片上传区域')
|
|
}
|
|
}, [isValidMainFile, onMaterialFileDrop, setError])
|
|
|
|
// ========== 预览图片拖拽处理 ==========
|
|
|
|
const handlePreviewDragEnter = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
setIsPreviewDragging(true)
|
|
}, [])
|
|
|
|
const handlePreviewDragLeave = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
if (isMouseOutsideElement(e, e.currentTarget as HTMLElement)) {
|
|
setIsPreviewDragging(false)
|
|
}
|
|
}, [])
|
|
|
|
const handlePreviewDragOver = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
}, [])
|
|
|
|
const handlePreviewDrop = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
setIsPreviewDragging(false)
|
|
|
|
const files = Array.from(e.dataTransfer.files)
|
|
if (files.length === 0) return
|
|
|
|
const imageFiles = files.filter(f => f.type.startsWith('image/'))
|
|
if (imageFiles.length > 0) {
|
|
onPreviewImageDrop(imageFiles)
|
|
} else {
|
|
setError('请拖拽图片文件')
|
|
}
|
|
}, [onPreviewImageDrop, setError])
|
|
|
|
// ========== 详情页展示图片拖拽处理 ==========
|
|
|
|
const handleDisplayImageDragEnter = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
setIsDisplayImageDragging(true)
|
|
}, [])
|
|
|
|
const handleDisplayImageDragLeave = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
if (isMouseOutsideElement(e, e.currentTarget as HTMLElement)) {
|
|
setIsDisplayImageDragging(false)
|
|
}
|
|
}, [])
|
|
|
|
const handleDisplayImageDragOver = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
}, [])
|
|
|
|
const handleDisplayImageDrop = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
setIsDisplayImageDragging(false)
|
|
|
|
const files = Array.from(e.dataTransfer.files)
|
|
if (files.length === 0) return
|
|
|
|
const imageFiles = files.filter(f => f.type.startsWith('image/'))
|
|
if (imageFiles.length > 0) {
|
|
onDisplayImageDrop(imageFiles[0])
|
|
} else {
|
|
setError('请拖拽图片文件')
|
|
}
|
|
}, [onDisplayImageDrop, setError])
|
|
|
|
// ========== 批量模式预览图拖拽处理 ==========
|
|
|
|
const handleBatchPreviewDragEnter = useCallback((itemId: string) => {
|
|
setBatchPreviewDragging(prev => ({ ...prev, [itemId]: true }))
|
|
}, [])
|
|
|
|
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,
|
|
}
|
|
}
|