import React, { useRef, useState } from 'react'; import { motion } from 'framer-motion'; import { Camera, X, Upload, ImageIcon } from 'lucide-react'; import { http } from '../api'; export type UploadBucket = 'avatars' | 'product_images'; interface ImageUploaderProps { /** 当前图片 URL */ value?: string | null; /** 图片变更回调 */ onChange: (url: string | null) => void; /** 存储桶名称 */ bucket?: UploadBucket; /** 上传路径前缀(仅 product_images 使用) */ pathPrefix?: string; /** 是否圆形裁剪(头像模式) */ circular?: boolean; /** 容器尺寸(px) */ size?: number; /** 占位图标 */ placeholderIcon?: React.ReactNode; /** 是否禁用 */ disabled?: boolean; /** 自定义类名 */ className?: string; /** 最大文件大小(MB) */ maxFileSize?: number; } export function ImageUploader({ value, onChange, bucket = 'product_images', pathPrefix = 'products', circular = false, size = 80, placeholderIcon, disabled = false, className = '', maxFileSize = 5 }: ImageUploaderProps) { const fileInputRef = useRef(null); const [uploading, setUploading] = useState(false); const [previewUrl, setPreviewUrl] = useState(value || null); // 同步外部 value 变化 React.useEffect(() => { setPreviewUrl(value || null); }, [value]); const handleFileSelect = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // 验证文件类型 if (!file.type.startsWith('image/')) { alert('请选择图片文件'); return; } // 验证文件大小 if (file.size > maxFileSize * 1024 * 1024) { alert(`图片大小不能超过 ${maxFileSize}MB`); return; } setUploading(true); try { const formDataObj = new FormData(); formDataObj.append('file', file); formDataObj.append('bucket', bucket); formDataObj.append('path_prefix', pathPrefix); // TODO: 文件上传接口待后端实现,当前使用 fetch 直接发送 FormData const res = await fetch('/api/v1/common/upload', { method: 'POST', body: formDataObj, }); if (!res.ok) { alert('图片上传失败'); setUploading(false); return; } const json = await res.json(); const publicUrl = json.data?.url || ''; setPreviewUrl(publicUrl); onChange(publicUrl); setUploading(false); } catch (error) { alert('图片上传失败'); setUploading(false); } }; const handleRemove = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); setPreviewUrl(null); onChange(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const triggerFileSelect = () => { if (!disabled && !uploading) { fileInputRef.current?.click(); } }; const borderRadius = circular ? 'rounded-full' : 'rounded-xl'; const containerStyle = { width: size, height: size }; return (