Files
yunhaifinance/frontend/src/components/FileUpload.tsx
T

186 lines
5.4 KiB
TypeScript

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';
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<FileUploadProps> = ({
value = [],
onChange,
maxCount = 9,
accept = 'image/*'
}) => {
const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState('');
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [uploading, setUploading] = useState(false);
// 当 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 {
const res = await fetch('/api/upload/single', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.success) {
onProgress({ percent: 100 });
// 传递包含url属性的对象,这是Ant Design Upload组件在customRequest中期望的格式
onSuccess({ url: data.data.url }, file);
message.success('上传成功');
} else {
onError(new Error(data.error));
message.error(data.error || '上传失败');
}
} catch (error) {
console.error('上传错误:', error);
onError(error);
message.error('上传失败');
} finally {
setUploading(false);
}
};
const uploadButton = (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>上传</div>
</div>
);
return (
<>
<Upload
listType="picture-card"
fileList={fileList}
onPreview={handlePreview}
onChange={handleChange}
customRequest={customRequest}
accept={accept}
maxCount={maxCount}
multiple
>
{fileList.length >= maxCount ? null : uploadButton}
</Upload>
{/* 图片预览弹窗 */}
<Modal
open={previewOpen}
title="图片预览"
footer={null}
onCancel={() => setPreviewOpen(false)}
width="80%"
centered
>
<div style={{ textAlign: 'center' }}>
<Image
src={previewImage}
style={{ maxWidth: '100%', maxHeight: '80vh' }}
preview={false}
/>
</div>
</Modal>
{uploading && (
<div style={{ marginTop: 8 }}>
<Spin size="small" /> 上传中...
</div>
)}
</>
);
};
export default FileUpload;