gongxiangp2/components/upload/drag-drop-utils.ts
2026-07-24 18:15:27 +08:00

102 lines
3.0 KiB
TypeScript
Raw 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.

import type React from 'react'
const FALLBACK_IMAGE_EXTENSIONS = new Set([
'avif',
'bmp',
'gif',
'jpeg',
'jpg',
'png',
'webp',
])
export interface ImageFileValidationOptions {
maxBytes?: number
maxCount?: number
existingCount?: number
}
export interface ImageFileValidationResult {
acceptedFiles: File[]
errors: string[]
}
export function hasDragLeftDropZone(event: React.DragEvent<Element>): boolean {
const nextTarget = event.relatedTarget as Node | null
if (nextTarget && nextTarget !== event.currentTarget) {
return !event.currentTarget.contains(nextTarget)
}
const rect = event.currentTarget.getBoundingClientRect()
const { clientX, clientY } = event
return clientX <= rect.left || clientX >= rect.right || clientY <= rect.top || clientY >= rect.bottom
}
export function prepareFileDrag(event: React.DragEvent<Element>, disabled = false): void {
event.preventDefault()
event.stopPropagation()
event.dataTransfer.dropEffect = disabled ? 'none' : 'copy'
}
export function isSupportedImageFile(file: File): boolean {
if (file.type) {
return file.type.startsWith('image/')
}
const extension = getFileExtension(file)
return FALLBACK_IMAGE_EXTENSIONS.has(extension)
}
export function getFileExtension(file: File): string {
return file.name.split('.').pop()?.toLowerCase() || ''
}
export function hasAllowedFileExtension(file: File, allowedExtensions: readonly string[]): boolean {
const extension = getFileExtension(file)
return allowedExtensions.some((allowedExtension) => allowedExtension.replace(/^\./, '').toLowerCase() === extension)
}
export function validateImageFiles(
files: FileList | File[],
options: ImageFileValidationOptions = {},
): ImageFileValidationResult {
const { maxBytes, maxCount, existingCount = 0 } = options
const unsupportedNames: string[] = []
const oversizedNames: string[] = []
const validFiles: File[] = []
for (const file of Array.from(files)) {
if (!isSupportedImageFile(file)) {
unsupportedNames.push(file.name)
continue
}
if (maxBytes !== undefined && file.size > maxBytes) {
oversizedNames.push(file.name)
continue
}
validFiles.push(file)
}
const remainingCount = maxCount === undefined
? validFiles.length
: Math.max(0, maxCount - existingCount)
const acceptedFiles = validFiles.slice(0, remainingCount)
const ignoredByCount = Math.max(0, validFiles.length - acceptedFiles.length)
const errors: string[] = []
if (unsupportedNames.length > 0) {
errors.push(`以下文件不是支持的图片格式:${unsupportedNames.join('、')}`)
}
if (oversizedNames.length > 0 && maxBytes !== undefined) {
const maxMegabytes = Math.round(maxBytes / 1024 / 1024)
errors.push(`以下图片超过 ${maxMegabytes}MB:${oversizedNames.join('、')}`)
}
if (ignoredByCount > 0 && maxCount !== undefined) {
errors.push(`最多只能选择 ${maxCount} 张图片,已忽略 ${ignoredByCount} 张`)
}
return { acceptedFiles, errors }
}