doctor-mail/apps/web/src/components/common/uploader/AvatarUploader.tsx

136 lines
3.3 KiB
TypeScript
Raw Normal View History

2025-01-26 10:34:44 +08:00
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";
2025-01-26 18:24:16 +08:00
import { Avatar } from "antd/lib";
2025-01-26 10:34:44 +08:00
export interface AvatarUploaderProps {
value?: string;
placeholder?: string;
className?: string;
onChange?: (value: string) => void;
style?: React.CSSProperties; // 添加style属性
}
interface UploadingFile {
name: string;
progress: number;
status: "uploading" | "done" | "error";
fileId?: string;
2025-01-26 16:10:31 +08:00
url?: string;
2025-01-26 10:34:44 +08:00
fileKey?: string;
}
const AvatarUploader: React.FC<AvatarUploaderProps> = ({
value,
onChange,
className,
placeholder = "点击上传",
style, // 解构style属性
}) => {
const { handleFileUpload, uploadProgress } = useTusUpload();
const [file, setFile] = useState<UploadingFile | null>(null);
2025-01-26 16:10:31 +08:00
2025-01-26 10:34:44 +08:00
const [previewUrl, setPreviewUrl] = useState<string>(value || "");
const [uploading, setUploading] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const { token } = theme.useToken();
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = event.target.files?.[0];
if (!selectedFile) return;
setFile({
name: selectedFile.name,
progress: 0,
status: "uploading",
fileKey: `${selectedFile.name}-${Date.now()}`,
});
setUploading(true);
try {
const fileId = await new Promise<string>((resolve, reject) => {
handleFileUpload(
selectedFile,
(result) => {
setFile((prev) => ({
...prev!,
progress: 100,
status: "done",
fileId: result.fileId,
2025-01-26 16:10:31 +08:00
url: result?.url,
2025-01-26 10:34:44 +08:00
}));
2025-01-26 16:10:31 +08:00
setPreviewUrl(result?.url);
2025-01-26 10:34:44 +08:00
resolve(result.fileId);
},
(error) => {
reject(error);
},
file?.fileKey
);
});
onChange?.(fileId);
message.success("头像上传成功");
} catch (error) {
console.error("上传错误:", error);
message.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 ? (
2025-01-26 18:24:16 +08:00
<Avatar
2025-01-26 10:34:44 +08:00
src={previewUrl}
2025-01-26 18:24:16 +08:00
shape="square"
2025-01-26 10:34:44 +08:00
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;