'use client' import Image from 'next/image' import { useEffect, useMemo, useRef, useState } from 'react' const MAX_FILE_COUNT = 60 const MAX_FILE_BYTES = 200 * 1024 * 1024 function fileKey(file: File) { return `${file.name}-${file.size}-${file.lastModified}` } export function WorkReviewImageDropzone({ files, onChange, disabled, label = '图片', }: { files: File[] onChange: (files: File[]) => void disabled?: boolean label?: string }) { const inputRef = useRef(null) const dragDepthRef = useRef(0) const [dragging, setDragging] = useState(false) const [error, setError] = useState(null) const previewUrls = useMemo(() => files.map((file) => URL.createObjectURL(file)), [files]) useEffect(() => () => { previewUrls.forEach((url) => URL.revokeObjectURL(url)) }, [previewUrls]) const addFiles = (selected: FileList | File[]) => { if (disabled) return const candidates = [...files, ...Array.from(selected)] const deduplicated = candidates.filter((file, index, items) => ( items.findIndex((candidate) => fileKey(candidate) === fileKey(file)) === index )) const invalid = deduplicated.find((file) => !file.type.startsWith('image/')) if (invalid) { setError(`文件「${invalid.name}」不是图片`) return } const oversized = deduplicated.find((file) => file.size > MAX_FILE_BYTES) if (oversized) { setError(`图片「${oversized.name}」超过 200MB`) return } if (deduplicated.length > MAX_FILE_COUNT) { setError(`一次最多选择 ${MAX_FILE_COUNT} 张图片`) return } setError(null) onChange(deduplicated) } return (
{ addFiles(event.currentTarget.files || []) event.currentTarget.value = '' }} /> {error &&
{error}
} {files.length > 0 && (
{files.map((file, index) => (
{file.name} {index + 1}
{file.name}
{file.size < 1024 * 1024 ? `${Math.max(1, Math.round(file.size / 1024))} KB` : `${(file.size / 1024 / 1024).toFixed(1)} MB`}
))}
)}
) }