import React, { useState, useRef } from 'react'; import { Upload, Link as LinkIcon, X, Film, Music, Image as ImageIcon, ExternalLink } from 'lucide-react'; import { uploadAsset } from '../api'; import { filenameFromUrl, thumbUrl, localAsset } from '../utils'; export default function MediaUploader({ label, accept = 'image/*', type = 'image', // 'image' | 'video' | 'audio' multiple = false, maxCount = 99, value, // string | string[] onChange, hint, }) { const [urlInput, setUrlInput] = useState(''); const [isUploading, setIsUploading] = useState(false); const [isDragOver, setIsDragOver] = useState(false); const fileInputRef = useRef(null); const items = multiple ? (Array.isArray(value) ? value : []) : (value ? [value] : []); const handleFiles = async (files) => { if (!files || files.length === 0) return; setIsUploading(true); try { const newUrls = []; for (const file of Array.from(files)) { if (items.length + newUrls.length >= maxCount) break; const res = await uploadAsset(file); if (res.url) newUrls.push(res.url); } if (multiple) { onChange([...items, ...newUrls].slice(0, maxCount)); } else if (newUrls.length > 0) { onChange(newUrls[0]); } } catch (e) { alert('上传失败: ' + e.message); } finally { setIsUploading(false); if (fileInputRef.current) fileInputRef.current.value = ''; } }; const handleAddUrl = (e) => { if (e.key === 'Enter') { e.preventDefault(); const u = urlInput.trim(); if (!u) return; if (multiple) { if (items.length >= maxCount) { alert(`最多只允许添加 ${maxCount} 个`); return; } onChange([...items, u]); } else { onChange(u); } setUrlInput(''); } }; const handleRemove = (index) => { if (multiple) { const next = items.filter((_, i) => i !== index); onChange(next); } else { onChange(null); } }; // 支持键盘粘贴截图 const handlePaste = (e) => { const clipboardFiles = e.clipboardData?.files; if (clipboardFiles && clipboardFiles.length > 0) { e.preventDefault(); handleFiles(clipboardFiles); } }; return (
{hint && {hint}}
{/* 拖拽上传与输入区 */} {(items.length < maxCount || !multiple) && (
{ e.preventDefault(); setIsDragOver(true); }} onDragLeave={() => setIsDragOver(false)} onDrop={(e) => { e.preventDefault(); setIsDragOver(false); handleFiles(e.dataTransfer.files); }} className={`relative border-2 border-dashed rounded-xl p-3.5 transition-all flex flex-col sm:flex-row items-center gap-3 bg-zinc-900/60 ${ isDragOver ? 'border-emerald-500 bg-emerald-950/20 shadow-inner' : 'border-zinc-800 hover:border-zinc-700' }`} > handleFiles(e.target.files)} />
setUrlInput(e.target.value)} onKeyDown={handleAddUrl} className="w-full pl-9 pr-3 py-1.5 bg-zinc-950/60 border border-zinc-800 focus:border-emerald-500/60 focus:ring-1 focus:ring-emerald-500/40 rounded-lg text-xs text-zinc-200 placeholder-zinc-500 outline-none transition-all" />
)} {/* 媒体缩略预览网格 */} {items.length > 0 && (
{items.map((u, idx) => { const rawName = filenameFromUrl(u); return (
{/* 媒体呈现 */}
{type === 'image' && ( {rawName} )} {type === 'video' && (
{/* 底部文件名 */}
{rawName || '媒体文件'}
); })}
)}
); }