import { env } from "@web/src/env"; import { message, Progress, Spin, theme } from "antd"; import React, { useState, useEffect, useRef } from "react"; import { useTusUpload } from "@web/src/hooks/useTusUpload"; import { Avatar } from "antd/lib"; import toast from "react-hot-toast"; export interface AvatarUploaderProps { value?: string; placeholder?: string; className?: string; onChange?: (value: string) => void; compressed?: boolean; style?: React.CSSProperties; // 添加style属性 } interface UploadingFile { name: string; progress: number; status: "uploading" | "done" | "error"; fileId?: string; url?: string; compressedUrl?: string; fileKey?: string; } const AvatarUploader: React.FC = ({ value, onChange, compressed = false, className, placeholder = "点击上传", style, // 解构style属性 }) => { const { handleFileUpload, uploadProgress } = useTusUpload(); const [file, setFile] = useState(null); const avatarRef = useRef(null); const [previewUrl, setPreviewUrl] = useState(value || ""); const [imageSrc, setImageSrc] = useState(value); const [compressedUrl, setCompressedUrl] = useState(value || ""); const [url, setUrl] = useState(value || ""); const [uploading, setUploading] = useState(false); const inputRef = useRef(null); // 在组件中定义 key 状态 const [avatarKey, setAvatarKey] = useState(0); const { token } = theme.useToken(); useEffect(() => { if (!previewUrl || previewUrl?.length < 1) { setPreviewUrl(value || ""); } }, [value]); const handleChange = async (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0]; if (!selectedFile) return; // Create an object URL for the selected file const objectUrl = URL.createObjectURL(selectedFile); setPreviewUrl(objectUrl); setFile({ name: selectedFile.name, progress: 0, status: "uploading", fileKey: `${selectedFile.name}-${Date.now()}`, }); setUploading(true); try { const uploadedUrl = await new Promise((resolve, reject) => { handleFileUpload( selectedFile, (result) => { setFile((prev) => ({ ...prev!, progress: 100, status: "done", fileId: result.fileId, url: result.url, compressedUrl: result.compressedUrl, })); setUrl(result.url); setCompressedUrl(result.compressedUrl); // 直接使用 result 中的最新值 resolve(compressed ? result.compressedUrl : result.url); }, (error) => { reject(error); }, file?.fileKey ); }); // await new Promise((resolve) => setTimeout(resolve,4999)); // 方法1:使用 await 暂停执行 // 使用 resolved 的最新值调用 onChange // 强制刷新 Avatar 组件 setAvatarKey((prev) => prev + 1); // 修改 key 强制重新挂载 onChange?.(uploadedUrl); console.log(uploadedUrl); toast.success("头像上传成功"); } catch (error) { console.error("上传错误:", error); toast.error("头像上传失败"); setFile((prev) => ({ ...prev!, status: "error" })); } finally { setUploading(false); } }; const triggerUpload = () => { inputRef.current?.click(); }; return (
{previewUrl ? ( { if (value && previewUrl && imageSrc === value) { // 当原始图片(value)加载失败时,切换到 previewUrl setImageSrc(previewUrl); return true; // 阻止默认的 fallback 行为,让它尝试新设置的 src } return false; // 如果 previewUrl 也失败了,显示默认头像 }} className="w-full h-full object-cover" /> ) : (
{placeholder}
)} {uploading && (
)} {file && file.status === "uploading" && (
)}
); }; export default AvatarUploader;