import React, { useState, useCallback } from 'react'; import { Upload, Progress, message, Tag, Space, Typography, Button } from 'antd'; import { UploadOutlined, CloseOutlined, CheckCircleOutlined, SyncOutlined } from '@ant-design/icons'; import { App } from 'antd'; import type { UploadFile, UploadProps } from 'antd'; import type { RcFile } from 'rc-upload/lib/interface'; import type { UploadFileStatus } from 'antd/es/upload/interface'; import type { UploadRequestOption } from 'rc-upload/lib/interface'; import { uploadMinIOWithPolicy, MinioProgressEvent } from '@/client/utils/minio'; interface MinioUploaderProps { /** 上传路径 */ uploadPath: string; /** 允许的文件类型,如['image/*', '.pdf'] */ accept?: string; /** 最大文件大小(MB) */ maxSize?: number; /** 是否允许多文件上传 */ multiple?: boolean; /** 上传成功回调 */ onUploadSuccess?: (fileKey: string, fileUrl: string, file: File) => void; /** 上传失败回调 */ onUploadError?: (error: Error, file: File) => void; /** 自定义上传按钮文本 */ buttonText?: string; /** 自定义提示文本 */ tipText?: string; } const MinioUploader: React.FC = ({ uploadPath = '/', accept, maxSize = 500, // 默认最大500MB multiple = false, onUploadSuccess, onUploadError, buttonText = '点击或拖拽上传文件', tipText = '支持单文件或多文件上传,单个文件大小不超过500MB' }) => { const { message: antdMessage } = App.useApp(); const [fileList, setFileList] = useState([]); const [uploadingFiles, setUploadingFiles] = useState>(new Set()); // 处理上传进度 const handleProgress = useCallback((uid: string, event: MinioProgressEvent) => { setFileList(prev => prev.map(item => { if (item.uid === uid) { return { ...item, status: event.stage === 'error' ? ('error' as UploadFileStatus) : ('uploading' as UploadFileStatus), percent: event.progress, error: event.stage === 'error' ? event.message : undefined }; } return item; }) ); }, []); // 处理上传成功 const handleComplete = useCallback((uid: string, result: { fileKey: string; fileUrl: string }, file: File) => { setFileList(prev => prev.map(item => { if (item.uid === uid) { return { ...item, status: 'success' as UploadFileStatus, percent: 100, response: { fileKey: result.fileKey }, url: result.fileUrl, }; } return item; }) ); setUploadingFiles(prev => { const newSet = new Set(prev); newSet.delete(uid); return newSet; }); antdMessage.success(`文件 "${file.name}" 上传成功`); onUploadSuccess?.(result.fileKey, result.fileUrl, file); }, [antdMessage, onUploadSuccess]); // 处理上传失败 const handleError = useCallback((uid: string, error: Error, file: File) => { setFileList(prev => prev.map(item => { if (item.uid === uid) { return { ...item, status: 'error' as UploadFileStatus, percent: 0, error: error.message || '上传失败' }; } return item; }) ); setUploadingFiles(prev => { const newSet = new Set(prev); newSet.delete(uid); return newSet; }); antdMessage.error(`文件 "${file.name}" 上传失败: ${error.message}`); onUploadError?.(error, file); }, [antdMessage, onUploadError]); // 自定义上传逻辑 const customRequest = async (options: UploadRequestOption) => { const { file, onSuccess, onError } = options; const rcFile = file as RcFile; const uid = rcFile.uid; // 添加到文件列表 setFileList(prev => [ ...prev.filter(item => item.uid !== uid), { uid, name: rcFile.name, size: rcFile.size, type: rcFile.type, lastModified: rcFile.lastModified, lastModifiedDate: new Date(rcFile.lastModified), status: 'uploading' as UploadFileStatus, percent: 0, } ]); // 添加到上传中集合 setUploadingFiles(prev => new Set(prev).add(uid)); try { // 调用minio上传方法 const result = await uploadMinIOWithPolicy( uploadPath, options.file as unknown as File, rcFile.name, { onProgress: (event) => handleProgress(uid, event), signal: new AbortController().signal } ); handleComplete(uid, result, rcFile as unknown as File); onSuccess?.({}, rcFile); } catch (error) { handleError(uid, error instanceof Error ? error : new Error('未知错误'), rcFile as unknown as File); onError?.(error instanceof Error ? error : new Error('未知错误')); } }; // 处理文件移除 const handleRemove = (uid: string) => { setFileList(prev => prev.filter(item => item.uid !== uid)); }; // 验证文件大小 const beforeUpload = (file: File) => { const fileSizeMB = file.size / (1024 * 1024); if (fileSizeMB > maxSize!) { message.error(`文件 "${file.name}" 大小超过 ${maxSize}MB 限制`); return false; } return true; }; // 渲染上传状态 const renderUploadStatus = (item: UploadFile) => { switch (item.status) { case 'uploading': return ( {item.percent}% ); case 'done': return ( 上传成功 ); case 'error': return ( {item.error || '上传失败'} ); default: return null; } }; return (
0 && !multiple} >
{buttonText} {tipText}
{/* 上传进度列表 */} {fileList.length > 0 && (
{fileList.map(item => (
{item.name}
{renderUploadStatus(item)}
{item.status === 'uploading' && ( )}
))}
)}
); }; export default MinioUploader;