'use client'; import { useState, useRef, useCallback } from 'react'; import { ImagePlus, Loader2, X } from 'lucide-react'; import { http } from '@/lib/api'; import { resolveUploadUrl, cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; export interface ImageUploadProps { value?: string; onChange: (url: string) => void; className?: string; /** 提示文字 */ hint?: string; } interface UploadResp { url: string; filename: string; } /** * 图片上传组件 * - 仅接受 jpg/png/jpeg/webp * - 单图 2M 上限 * - 自动 POST /api/admin/upload */ export function ImageUpload({ value, onChange, className, hint = '建议尺寸 1600x900,支持 jpg/png/webp,单图 ≤ 2M', }: ImageUploadProps) { const [uploading, setUploading] = useState(false); const [error, setError] = useState(null); const inputRef = useRef(null); const handleFile = useCallback( async (file: File) => { const allowed = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp']; if (!allowed.includes(file.type)) { setError('仅支持 jpg/png/jpeg/webp 格式'); return; } if (file.size > 2 * 1024 * 1024) { setError('单图大小不能超过 2M'); return; } setError(null); setUploading(true); try { const form = new FormData(); form.append('file', file); const res = (await http.post( '/admin/upload', form, { headers: { 'Content-Type': 'multipart/form-data' } }, )) as UploadResp; onChange(res.url); } catch (e) { setError((e as Error).message); } finally { setUploading(false); } }, [onChange], ); const onPick = () => inputRef.current?.click(); const preview = value ? resolveUploadUrl(value) : ''; return (
{preview ? ( // eslint-disable-next-line @next/next/no-img-element 预览 ) : uploading ? ( ) : (
点击上传
)} {preview && ( )}
{ const f = e.target.files?.[0]; if (f) void handleFile(f); e.target.value = ''; }} />

{hint}

{error &&

{error}

}
); }