import { useEffect, useState, useRef } from 'react'; import { Upload, message, Progress, List, Button, Space } from 'antd'; import { InboxOutlined, CloseOutlined, FileOutlined } from '@ant-design/icons'; import type { UploadProps } from 'antd'; import { getTenant, getToken } from '../../../../utils'; import { useTranslation } from 'react-i18next'; import config from '../../../../js/config'; const { Dragger } = Upload; interface IProps { typeId?: number; // 1:视频, 2:图片, 3:文档, 4:音频, 5:其他 currentCount?: number; maxCount?: number; onUploadSuccess?: (fileInfo: { type: string; fileExtension: string; fileSize: number; filePath: string; fileName: string; }) => void; onUploadError?: (error: any) => void; } interface UploadFileItem { id: string; name: string; size: number; progress: number; status: 'uploading' | 'success' | 'error' | 'cancelled'; file: File; xhr?: XMLHttpRequest; } // 根据类型ID获取对应的文件类型配置 // const getFileTypeConfig = (typeId: number) => { // const configs = { // 1: { // // 视频 // accept: '.mp4,.mov,.avi,.wmv,.flv,.rmvb', // }, // 2: { // // 图片 // accept: '.png,.jpeg,.jpg,.gif', // }, // 3: { // // 文档 // accept: '.pdf,.doc,.docx,.ppt,.pptx', // }, // 4: { // // 音频 // accept: '.mp3,.aac,.wma,.wav', // }, // 5: { // // 其他 // accept: '*', // }, // }; // // return configs[typeId as keyof typeof configs] || configs[5]; // }; // 根据类型ID获取模块类型 // const getModuleType = (typeId: number): string => { // const types = { 1: 'VIDEO', 2: 'IMAGE', 3: 'DOCUMENT', 4: 'AUDIO', 5: 'OTHER' }; // return types[typeId as keyof typeof types] || 'OTHER'; // }; export const DraggerUpload = (props: IProps) => { const { t } = useTranslation(); const [uploadFiles, setUploadFiles] = useState([]); const xhrRef = useRef>(new Map()); const { typeId, currentCount = 0, maxCount = 1, onUploadSuccess, onUploadError } = props; // const fileTypeConfig = getFileTypeConfig(typeId); const isLimitReached = maxCount > 0 && currentCount >= maxCount; // 获取文件扩展名 const getFileExtension = (fileName: string): string => { return fileName.split('.').pop()?.toLowerCase() || ''; }; useEffect(() => { setUploadFiles([]); // 清理所有正在进行的上传 xhrRef.current.forEach((xhr) => { xhr.abort(); }); xhrRef.current.clear(); }, [typeId]); // 检查文件类型(仅对其他类型做限制) const checkFileType = (file: File): boolean => { if (typeId && typeId !== 5) return true; const extension = getFileExtension(file.name); const restrictedExtensions = [ 'mp4', 'mov', 'avi', 'wmv', 'flv', 'rmvb', 'mp3', 'aac', 'wma', 'wav', 'png', 'jpeg', 'jpg', 'gif', 'docx', 'pptx', 'pdf', 'doc', ]; if (extension && restrictedExtensions.includes(extension)) { message.error(t('textbook.resource.restrictedFileType')); return false; } return true; }; // 更新文件上传进度 const updateFileProgress = ( fileId: string, progress: number, status: UploadFileItem['status'] = 'uploading' ) => { setUploadFiles((prev) => prev.map((item) => (item.id === fileId ? { ...item, progress, status } : item)) ); }; // 移除上传文件 const removeUploadFile = (fileId: string) => { const fileItem = uploadFiles.find((item) => item.id === fileId); // 如果文件正在上传,先取消上传 if (fileItem?.status === 'uploading') { cancelUpload(fileId); } setUploadFiles((prev) => prev.filter((item) => item.id !== fileId)); xhrRef.current.delete(fileId); }; // 取消上传 const cancelUpload = (fileId: string) => { const xhr = xhrRef.current.get(fileId); if (xhr) { xhr.abort(); xhrRef.current.delete(fileId); updateFileProgress(fileId, 0, 'cancelled'); message.info(t('commen.uploadCancelled')); } }; // 处理文件上传 const handleCustomRequest: UploadProps['customRequest'] = async (options) => { const { file, onSuccess, onError, onProgress } = options; const fileId = Math.random().toString(36).substr(2, 9); const uploadFile: UploadFileItem = { id: fileId, name: (file as File).name, size: (file as File).size, progress: 0, status: 'uploading', file: file as File, }; setUploadFiles((prev) => [...prev, uploadFile]); try { const formData = new FormData(); formData.append('file', file as File); // formData.append('module', getModuleType(typeId)); formData.append('duration', '0'); let appUrl = config.app_url.replace(/\/+$/, ''); if (!appUrl.startsWith('http')) { appUrl = `${window.location.protocol}//${window.location.host}${appUrl.startsWith('/') ? appUrl : '/' + appUrl}`; } const xhr = new XMLHttpRequest(); xhrRef.current.set(fileId, xhr); // 监听上传进度 xhr.upload.addEventListener('progress', (event) => { if (event.lengthComputable) { const percent = (event.loaded / event.total) * 100; updateFileProgress(fileId, percent); onProgress?.({ percent }); } }); // 监听完成 xhr.addEventListener('load', () => { xhrRef.current.delete(fileId); if (xhr.status === 200) { let response; try { response = JSON.parse(xhr.responseText); } catch { response = xhr.responseText; } updateFileProgress(fileId, 100, 'success'); onSuccess?.(response); message.success(t('commen.uploadSuccess')); // 构造返回的文件信息 const fileInfo = { type: (file as File).type, fileExtension: getFileExtension((file as File).name), fileSize: (file as File).size, filePath: response?.data?.path || response?.path || '', // 根据实际接口响应调整 fileName: (file as File).name, // 如果需要,还可以返回原始响应 rawResponse: response, }; onUploadSuccess?.(fileInfo); } else { throw new Error(`Upload failed with status ${xhr.status}`); } }); // 监听错误 xhr.addEventListener('error', () => { xhrRef.current.delete(fileId); // 如果是取消操作,不显示错误消息 if (xhr.status === 0 && xhr.readyState === 0) { return; // 取消操作,不处理 } updateFileProgress(fileId, 0, 'error'); const error = new Error('Upload failed'); onError?.(error); onUploadError?.(error); message.error(t('commen.uploadFailed')); }); // 监听取消 xhr.addEventListener('abort', () => { xhrRef.current.delete(fileId); updateFileProgress(fileId, 0, 'cancelled'); // message.info(t('commen.uploadCancelled')); }); xhr.open('POST', `${appUrl}/backend/v1/localUpload/upload`); xhr.setRequestHeader('Authorization', 'Bearer ' + getToken()); xhr.setRequestHeader('tenant-id', getTenant()); xhr.send(formData); } catch (error) { xhrRef.current.delete(fileId); updateFileProgress(fileId, 0, 'error'); onError?.(error as Error); onUploadError?.(error); message.error(t('commen.uploadFailed')); } }; const uploadProps: UploadProps = { name: 'file', multiple: false, // accept: fileTypeConfig.accept, customRequest: handleCustomRequest, showUploadList: false, disabled: isLimitReached || uploadFiles.some((file) => file.status === 'uploading'), beforeUpload: (file) => { if (isLimitReached) { message.error(t('textbook.resource.uploadLimitReached')); return false; } // 如果已经有文件在上传,不允许上传新文件 if (uploadFiles.some((f) => f.status === 'uploading')) { message.error(t('commen.uploadInProgress')); return false; } return checkFileType(file); }, }; // 格式化文件大小 const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; // 获取状态对应的文本和颜色 const getStatusInfo = (status: UploadFileItem['status']) => { switch (status) { case 'uploading': return { text: t('commen.uploading'), color: '#1890ff' }; case 'success': return { text: t('commen.uploadSuccess'), color: '#52c41a' }; case 'error': return { text: t('commen.uploadFailed'), color: '#ff4d4f' }; case 'cancelled': return { text: t('commen.uploadCancelled'), color: '#faad14' }; default: return { text: '', color: '#000' }; } }; return (
0}>

{isLimitReached ? t('textbook.resource.uploadLimitReached') : uploadFiles.some((f) => f.status === 'uploading') ? t('commen.uploadInProgress') : t('textbook.resource.uploadTips')}

{isLimitReached ? t('textbook.resource.uploadLimitReachedDesc') : uploadFiles.some((f) => f.status === 'uploading') ? t('commen.waitForUploadComplete') : t('textbook.resource.uploadTips2')}

{/* 上传文件列表 */} {uploadFiles.length > 0 && (
{ const statusInfo = getStatusInfo(item.status); return (
{item.name}
({formatFileSize(item.size)})
{statusInfo.text}
{item.status === 'uploading' && ( )}
); }} />
)}
); };