134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
|
|
import { useCallback, useState, useEffect, useRef } from 'react';
|
||
|
|
import { Upload, X, MousePointerClick, FileUp, ClipboardPaste } from 'lucide-react';
|
||
|
|
|
||
|
|
interface UploadModalProps {
|
||
|
|
onClose: () => void;
|
||
|
|
onUpload: (files: File[]) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function UploadModal({ onClose, onUpload }: UploadModalProps) {
|
||
|
|
const [dragActive, setDragActive] = useState(false);
|
||
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const handlePaste = (e: ClipboardEvent) => {
|
||
|
|
const items = e.clipboardData?.items;
|
||
|
|
if (!items) return;
|
||
|
|
|
||
|
|
const files: File[] = [];
|
||
|
|
for (let i = 0; i < items.length; i++) {
|
||
|
|
if (items[i].type.startsWith('image/') || items[i].type === 'application/pdf') {
|
||
|
|
const file = items[i].getAsFile();
|
||
|
|
if (file) files.push(file);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (files.length > 0) {
|
||
|
|
onUpload(files);
|
||
|
|
onClose();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
document.addEventListener('paste', handlePaste);
|
||
|
|
return () => document.removeEventListener('paste', handlePaste);
|
||
|
|
}, [onUpload, onClose]);
|
||
|
|
|
||
|
|
const handleDrag = useCallback((e: React.DragEvent) => {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
if (e.type === 'dragenter' || e.type === 'dragover') {
|
||
|
|
setDragActive(true);
|
||
|
|
} else if (e.type === 'dragleave') {
|
||
|
|
setDragActive(false);
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const handleDrop = useCallback((e: React.DragEvent) => {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
setDragActive(false);
|
||
|
|
|
||
|
|
const files = Array.from(e.dataTransfer.files).filter((file) =>
|
||
|
|
file.type.startsWith('image/') || file.type === 'application/pdf'
|
||
|
|
);
|
||
|
|
|
||
|
|
if (files.length > 0) {
|
||
|
|
onUpload(files);
|
||
|
|
onClose();
|
||
|
|
}
|
||
|
|
}, [onUpload, onClose]);
|
||
|
|
|
||
|
|
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
|
|
if (e.target.files) {
|
||
|
|
const files = Array.from(e.target.files).filter((file) =>
|
||
|
|
file.type.startsWith('image/') || file.type === 'application/pdf'
|
||
|
|
);
|
||
|
|
|
||
|
|
if (files.length > 0) {
|
||
|
|
onUpload(files);
|
||
|
|
onClose();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||
|
|
<div className="bg-white rounded-xl shadow-xl max-w-2xl w-full p-6">
|
||
|
|
<div className="flex justify-between items-center mb-6">
|
||
|
|
<h2 className="text-2xl font-bold text-gray-900">上传文件</h2>
|
||
|
|
<button
|
||
|
|
onClick={onClose}
|
||
|
|
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||
|
|
>
|
||
|
|
<X size={20} />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div
|
||
|
|
onDragEnter={handleDrag}
|
||
|
|
onDragLeave={handleDrag}
|
||
|
|
onDragOver={handleDrag}
|
||
|
|
onDrop={handleDrop}
|
||
|
|
onClick={() => fileInputRef.current?.click()}
|
||
|
|
className={`border-2 border-dashed rounded-xl p-12 text-center transition-colors cursor-pointer group ${dragActive
|
||
|
|
? 'border-blue-500 bg-blue-50'
|
||
|
|
: 'border-gray-300 hover:border-blue-400 hover:bg-gray-50'
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
<div className="w-16 h-16 bg-gray-100 text-gray-600 rounded-full flex items-center justify-center mx-auto mb-4 group-hover:scale-110 transition-transform">
|
||
|
|
<Upload size={32} />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<input
|
||
|
|
ref={fileInputRef}
|
||
|
|
type="file"
|
||
|
|
multiple
|
||
|
|
accept="image/*,application/pdf"
|
||
|
|
onChange={handleFileInput}
|
||
|
|
className="hidden"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<p className="text-xs text-gray-500 mt-4">
|
||
|
|
Support JPG, PNG format
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<div className="flex items-center justify-center gap-4 mt-6 text-xs text-gray-400">
|
||
|
|
<div className="flex items-center gap-1">
|
||
|
|
<MousePointerClick className="w-3.5 h-3.5" />
|
||
|
|
<span>Click</span>
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center gap-1">
|
||
|
|
<FileUp className="w-3.5 h-3.5" />
|
||
|
|
<span>Drop</span>
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center gap-1">
|
||
|
|
<ClipboardPaste className="w-3.5 h-3.5" />
|
||
|
|
<span>Paste</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|