gongxiangp2/hooks/useDragHandlers.ts
chenpei 0ce59be34e 备份:组织架构设计方案讨论前的代码快照
- 包含所有当前修改和新功能
- 准备开始组织架构优化实施方案
2025-12-30 13:20:26 +08:00

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,
}
}