import React, { useState, useEffect } from 'react'; import { Upload, Modal, Image, Spin, Progress, message } from 'antd'; import { PlusOutlined, FileOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons'; import type { UploadFile, UploadProps } from 'antd/es/upload/interface'; import { useLanguageStore } from '../store/languageStore'; interface FileUploadProps { value?: string[]; onChange?: (urls: string[]) => void; maxCount?: number; accept?: string; } // 支持的图片格式 const imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']; const officeFormats = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx']; const isImage = (url: string) => { const ext = url.split('.').pop()?.toLowerCase(); return imageFormats.includes(ext || ''); }; const isOfficeFile = (url: string) => { const ext = url.split('.').pop()?.toLowerCase(); return officeFormats.includes(ext || ''); }; const getOfficePreviewUrl = (url: string) => { // 使用微软的Office 365在线预览服务 return `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}`; }; const FileUpload: React.FC = ({ value = [], onChange, maxCount = 9, accept = 'image/*' }) => { const [previewOpen, setPreviewOpen] = useState(false); const [previewImage, setPreviewImage] = useState(''); const [fileList, setFileList] = useState([]); const [uploading, setUploading] = useState(false); const { t, currentLanguage } = useLanguageStore(); // 当 value 变化时,更新 fileList useEffect(() => { // 只有当 value 是数组时才更新 fileList // 这样可以避免在上传过程中被重置 if (Array.isArray(value)) { const newFileList = value.map((url, index) => ({ uid: `-${index}`, name: url.split('/').pop() || `file-${index}`, status: 'done', url, thumbUrl: isImage(url) ? url : undefined })); setFileList(newFileList); } }, [value]); const handlePreview = async (file: UploadFile) => { const url = file.url || ''; if (isImage(url)) { setPreviewImage(url); setPreviewOpen(true); } else if (isOfficeFile(url)) { // Office文件,使用微软的在线预览服务 const previewUrl = getOfficePreviewUrl(url); window.open(previewUrl, '_blank'); } else { // 其他文件,新窗口打开 window.open(url, '_blank'); } }; const handleChange: UploadProps['onChange'] = (info) => { const { fileList } = info; setFileList(fileList); // 只有当文件状态发生变化时才调用 onChange // 避免在初始化时触发无限循环 if (info.file.status === 'done' || info.file.status === 'removed') { // 提取已上传成功的URL const urls = fileList .filter(file => file.status === 'done') .map(file => { // 处理不同格式的文件对象 if (file.url) { return file.url; } else if (file.response && file.response.url) { return file.response.url; } else if (file.response && typeof file.response === 'string') { return file.response; } return ''; }) .filter(url => url); // 过滤空字符串 onChange?.(urls); } }; const customRequest = async (options: any) => { const { file, onSuccess, onError, onProgress } = options; setUploading(true); const formData = new FormData(); formData.append('file', file); try { // 从 localStorage 读取认证 token let authHeader = ''; try { const authStorage = localStorage.getItem('auth-storage'); if (authStorage) { const parsed = JSON.parse(authStorage); const token = parsed?.state?.token; if (token) { authHeader = `Bearer ${token}`; } } } catch (e) { // 忽略解析错误 } const res = await fetch('/api/upload/single', { method: 'POST', body: formData, headers: authHeader ? { Authorization: authHeader } : {}, }); const data = await res.json(); if (data.success) { onProgress({ percent: 100 }); // 传递包含url属性的对象,这是Ant Design Upload组件在customRequest中期望的格式 onSuccess({ url: data.data.url }, file); message.success(t('fileUpload.uploadSuccess')); } else { onError(new Error(data.error)); message.error(data.error || t('fileUpload.uploadFailed')); } } catch (error) { console.error('上传错误:', error); onError(error); message.error(t('fileUpload.uploadFailed')); } finally { setUploading(false); } }; const uploadButton = (
{t('fileUpload.upload')}
); return ( <> {fileList.length >= maxCount ? null : uploadButton} {/* 图片预览弹窗 */} setPreviewOpen(false)} width="80%" centered >
{uploading && (
{t('fileUpload.uploading')}
)} ); }; export default FileUpload;