training_data/apps/web/src/components/common/uploader/AvatarUploader.tsx

159 lines
4.3 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<AvatarUploaderProps> = ({
value,
onChange,
compressed = false,
className,
placeholder = "点击上传",
style, // 解构style属性
}) => {
const { handleFileUpload, uploadProgress } = useTusUpload();
const [file, setFile] = useState<UploadingFile | null>(null);
const avatarRef = useRef<HTMLImageElement>(null);
const [previewUrl, setPreviewUrl] = useState<string>(value || "");
const [compressedUrl, setCompressedUrl] = useState<string>(value || "");
const [url, setUrl] = useState<string>(value || "");
const [uploading, setUploading] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
// 在组件中定义 key 状态
const [avatarKey, setAvatarKey] = useState(0);
const { token } = theme.useToken();
useEffect(() => {
setPreviewUrl(value || "");
}, [value]);
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
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<string>((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 (
<div
className={`relative w-24 h-24 overflow-hidden cursor-pointer ${className}`}
onClick={triggerUpload}
style={{
border: `1px solid ${token.colorBorder}`,
background: token.colorBgContainer,
...style, // 应用外部传入的样式
}}>
<input
type="file"
ref={inputRef}
onChange={handleChange}
accept="image/*"
style={{ display: "none" }}
/>
{previewUrl ? (
<Avatar
key={avatarKey}
ref={avatarRef}
src={previewUrl}
shape="square"
className="w-full h-full object-cover"
/>
) : (
<div className="flex items-center justify-center w-full h-full text-sm text-gray-500">
{placeholder}
</div>
)}
{uploading && (
<div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-50">
<Spin />
</div>
)}
{file && file.status === "uploading" && (
<div className="absolute bottom-0 left-0 right-0 bg-white bg-opacity-75">
<Progress
percent={Math.round(
uploadProgress?.[file.fileKey!] || 0
)}
showInfo={false}
strokeColor={token.colorPrimary}
/>
</div>
)}
</div>
);
};
export default AvatarUploader;