174 lines
5.5 KiB
TypeScript
174 lines
5.5 KiB
TypeScript
import React, { useState } from 'react';
|
||
import { Upload, Modal, message } from 'antd';
|
||
import { PlusOutlined } from '@ant-design/icons';
|
||
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
||
import toast from "react-hot-toast";
|
||
|
||
|
||
export interface MultiImageUploadProps {
|
||
value?: string;
|
||
placeholder?: string;
|
||
className?: string;
|
||
onChange?: (value: string) => void;
|
||
compressed?: boolean;
|
||
style?: React.CSSProperties; // 添加style属性
|
||
}
|
||
|
||
interface UploadFile {
|
||
name: string;
|
||
progress: number;
|
||
status: "uploading" | "done" | "error";
|
||
url?: string;
|
||
fileKey?: string;
|
||
uid: string;
|
||
}
|
||
const MultiImageUpload: React.FC<MultiImageUploadProps> = ({
|
||
value,
|
||
onChange,
|
||
compressed = false,
|
||
className,
|
||
placeholder = "点击上传",
|
||
style, // 解构style属性
|
||
}) => {
|
||
const [fileList, setFileList] = useState<UploadFile[] | null>(null); // 存储已上传的文件列表
|
||
const [previewVisible, setPreviewVisible] = useState(false); // 控制预览模态框的显示
|
||
const [previewImage, setPreviewImage] = useState(''); // 当前预览的图片URL
|
||
const { handleFileUpload, uploadProgress } = useTusUpload();
|
||
const [compressedUrl, setCompressedUrl] = useState<string>(value || "");
|
||
const [url, setUrl] = useState<string>(value || "");
|
||
const [uploading, setUploading] = useState(false);
|
||
|
||
// 处理文件上传前的校验
|
||
const beforeUpload = (file) => {
|
||
const isImage = file.type.startsWith('image/');
|
||
if (!isImage) {
|
||
message.error('只能上传图片文件!');
|
||
}
|
||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||
if (!isLt10M) {
|
||
message.error('图片大小不能超过10MB!');
|
||
}
|
||
return isImage && isLt10M;
|
||
};
|
||
|
||
// 处理文件列表变化
|
||
const handleChange = async ({ fileList }) => {
|
||
//setFileList(fileList);
|
||
console.log(fileList);
|
||
const imageUrls = fileList.map(file => {
|
||
return URL.createObjectURL(file.originFileObj)
|
||
});
|
||
console.log("imageUrls", imageUrls);
|
||
|
||
const newFileList = fileList.map(file => {
|
||
return {
|
||
name: file.name,
|
||
progress: 0,
|
||
status: "uploading",
|
||
//uid: file.uid,
|
||
fileKey: `${file.name}-${Date.now()}`,
|
||
//url: file.url,
|
||
}
|
||
});
|
||
console.log("newFileList", newFileList);
|
||
setUploading(true);
|
||
|
||
try {
|
||
const resFileList = newFileList.map(async (file, index) => {
|
||
const uploadedUrl = await new Promise<string>((resolve, reject) => {
|
||
handleFileUpload(
|
||
fileList[index].originFileObj,
|
||
(result) => {
|
||
() => {
|
||
return {
|
||
...newFileList[index],
|
||
progress: 100,
|
||
status: "done",
|
||
fileId: result.fileId,
|
||
url: result.url,
|
||
compressedUrl: result.compressedUrl,
|
||
}
|
||
}
|
||
// 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);
|
||
},
|
||
newFileList[index]?.fileKey
|
||
);
|
||
});
|
||
})
|
||
// await new Promise((resolve) => setTimeout(resolve,4999)); // 方法1:使用 await 暂停执行
|
||
// 使用 resolved 的最新值调用 onChange
|
||
// 强制刷新 Avatar 组件
|
||
// setAvatarKey((prev) => prev + 1); // 修改 key 强制重新挂载
|
||
onChange?.(resFileList);
|
||
console.log(resFileList);
|
||
toast.success("图片上传成功");
|
||
} catch (error) {
|
||
console.error("上传错误:", error);
|
||
toast.error("图片上传失败");
|
||
// setFile((prev) => ({ ...prev!, status: "error" }));
|
||
} finally {
|
||
setUploading(false);
|
||
}
|
||
};uploadProgress
|
||
|
||
// 处理预览
|
||
const handlePreview = async (file) => {
|
||
if (!file.url && !file.preview) {
|
||
file.preview = await getBase64(file.originFileObj);
|
||
}
|
||
setPreviewImage(file.url || file.preview);
|
||
setPreviewVisible(true);
|
||
};
|
||
|
||
// 关闭预览模态框
|
||
const handleCancel = () => setPreviewVisible(false);
|
||
|
||
// 将文件转换为Base64格式(用于预览)
|
||
const getBase64 = (file) => {
|
||
return new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.readAsDataURL(file);
|
||
reader.onload = () => resolve(reader.result);
|
||
reader.onerror = (error) => reject(error);
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div>
|
||
<Upload.Dragger
|
||
listType="picture-card" // 卡片样式
|
||
fileList={fileList} // 已上传的文件列表
|
||
beforeUpload={beforeUpload} // 上传前的校验
|
||
onChange={handleChange} // 文件列表变化时的回调
|
||
onPreview={handlePreview} // 预览回调
|
||
multiple // 支持多选
|
||
// style={{ width: '200px' }}
|
||
>
|
||
<p className="ant-upload-drag-icon">
|
||
</p>
|
||
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||
<p className="ant-upload-hint">支持单个或批量上传</p>
|
||
</Upload.Dragger>
|
||
<Modal visible={previewVisible} footer={null} onCancel={handleCancel}>
|
||
<img alt="预览" style={{ width: '100%' }} src={previewImage} />
|
||
</Modal>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default MultiImageUpload;
|