227 lines
5.6 KiB
TypeScript
227 lines
5.6 KiB
TypeScript
![]() |
import React, { useState, useEffect } from 'react';
|
|||
|
import { Upload, Button, message, Tooltip } from 'antd';
|
|||
|
import { UploadOutlined, InboxOutlined } from '@ant-design/icons';
|
|||
|
import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
|
|||
|
import { validateFileSize } from '@/utils/utils';
|
|||
|
import { useIntl } from 'umi';
|
|||
|
import './FileUpload.less';
|
|||
|
|
|||
|
export interface FileUploadProps {
|
|||
|
value?: UploadFile[];
|
|||
|
onChange?: (fileList: UploadFile[]) => void;
|
|||
|
maxCount?: number;
|
|||
|
maxSize?: number;
|
|||
|
allowedTypes?: string[];
|
|||
|
listType?: 'text' | 'picture' | 'picture-card';
|
|||
|
buttonText?: string;
|
|||
|
disabled?: boolean;
|
|||
|
accept?: string;
|
|||
|
showUploadList?:
|
|||
|
| boolean
|
|||
|
| { showPreviewIcon?: boolean; showRemoveIcon?: boolean; showDownloadIcon?: boolean };
|
|||
|
isDragger?: boolean;
|
|||
|
tip?: string;
|
|||
|
action?: string;
|
|||
|
}
|
|||
|
|
|||
|
// 上传接口返回数据格式
|
|||
|
interface UploadResponseData {
|
|||
|
fileName: string;
|
|||
|
fileSize: string;
|
|||
|
filePath: string;
|
|||
|
fileType: string;
|
|||
|
url: string;
|
|||
|
}
|
|||
|
|
|||
|
// API响应格式
|
|||
|
interface ApiResponse<T> {
|
|||
|
code: number;
|
|||
|
success: boolean;
|
|||
|
message: string;
|
|||
|
data: T;
|
|||
|
}
|
|||
|
|
|||
|
const FileUpload: React.FC<FileUploadProps> = ({
|
|||
|
value,
|
|||
|
onChange,
|
|||
|
maxCount = 1,
|
|||
|
maxSize = 5,
|
|||
|
allowedTypes = ['*'],
|
|||
|
listType = 'text',
|
|||
|
buttonText,
|
|||
|
disabled = false,
|
|||
|
accept,
|
|||
|
showUploadList = true,
|
|||
|
isDragger = false,
|
|||
|
tip,
|
|||
|
action,
|
|||
|
}) => {
|
|||
|
const actionUrl = action ? `${UPLOAD_URL}${action}` : `${UPLOAD_URL}/api/files/upload`;
|
|||
|
const intl = useIntl();
|
|||
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
|||
|
|
|||
|
// 监听value变化
|
|||
|
useEffect(() => {
|
|||
|
|
|||
|
// 处理字符串URL值(这是关键修复)
|
|||
|
if (typeof value === 'string' && value) {
|
|||
|
const file: Partial<UploadFile> = {
|
|||
|
uid: '-1',
|
|||
|
name: 'image.jpg',
|
|||
|
status: 'done',
|
|||
|
url: value,
|
|||
|
size: 0,
|
|||
|
type: 'image/jpeg',
|
|||
|
};
|
|||
|
setFileList([file as UploadFile]);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 处理空值
|
|||
|
if (!value || (Array.isArray(value) && value.length === 0)) {
|
|||
|
setFileList([]);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 处理正常的文件数组
|
|||
|
if (Array.isArray(value)) {
|
|||
|
setFileList(value);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 处理对象格式
|
|||
|
if (typeof value === 'object') {
|
|||
|
// 处理fileList属性
|
|||
|
if ('fileList' in value && Array.isArray((value as any).fileList)) {
|
|||
|
const files = (value as { fileList: UploadFile[] }).fileList;
|
|||
|
setFileList(files);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 尝试作为单个文件对象处理
|
|||
|
if ('uid' in value && 'status' in value) {
|
|||
|
setFileList([value as unknown as UploadFile]);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
console.warn('FileUpload: Unrecognized object format', value);
|
|||
|
setFileList([]);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 无法识别的格式
|
|||
|
console.warn('FileUpload component: unrecognized value format:', value);
|
|||
|
setFileList([]);
|
|||
|
}, [value]);
|
|||
|
|
|||
|
const defaultButtonText = intl.formatMessage({
|
|||
|
id: 'component.fileUpload.buttonText',
|
|||
|
defaultMessage: '上传文件',
|
|||
|
});
|
|||
|
|
|||
|
// 处理上传接口返回的数据格式
|
|||
|
const getFileUrl = (file: UploadFile) => {
|
|||
|
// 如果response是API的标准响应格式
|
|||
|
if (file.response && typeof file.response === 'object') {
|
|||
|
// 首先检查是否是标准API响应格式
|
|||
|
if ('success' in file.response && 'data' in file.response) {
|
|||
|
const response = file.response as ApiResponse<UploadResponseData>;
|
|||
|
if (response.success && response.data) {
|
|||
|
return response.data.url;
|
|||
|
}
|
|||
|
}
|
|||
|
// 如果response直接包含url属性
|
|||
|
else if ('url' in file.response) {
|
|||
|
return (file.response as UploadResponseData).url;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 回退到文件的url或thumbUrl
|
|||
|
return file.url || file.thumbUrl;
|
|||
|
};
|
|||
|
|
|||
|
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
|
|||
|
const filteredList = newFileList.filter((file) => file.status !== 'error');
|
|||
|
|
|||
|
// 为每个文件添加正确的url
|
|||
|
const processedList = filteredList.map(file => {
|
|||
|
if (file.status === 'done' && file.response && !file.url) {
|
|||
|
return {
|
|||
|
...file,
|
|||
|
url: getFileUrl(file)
|
|||
|
};
|
|||
|
}
|
|||
|
return file;
|
|||
|
});
|
|||
|
|
|||
|
setFileList(processedList);
|
|||
|
|
|||
|
if (onChange) {
|
|||
|
onChange(processedList);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
const beforeUpload = (file: File) => {
|
|||
|
return validateFileSize(file, maxSize, allowedTypes);
|
|||
|
};
|
|||
|
|
|||
|
const UploadComponent = isDragger ? Upload.Dragger : Upload;
|
|||
|
|
|||
|
const uploadProps = {
|
|||
|
fileList,
|
|||
|
onChange: handleChange,
|
|||
|
beforeUpload,
|
|||
|
listType,
|
|||
|
maxCount,
|
|||
|
disabled,
|
|||
|
accept,
|
|||
|
showUploadList,
|
|||
|
action: actionUrl,
|
|||
|
};
|
|||
|
|
|||
|
const renderUploadButton = () => {
|
|||
|
if (fileList.length >= maxCount) {
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
if (isDragger) {
|
|||
|
return (
|
|||
|
<>
|
|||
|
<p className="ant-upload-drag-icon">
|
|||
|
<InboxOutlined />
|
|||
|
</p>
|
|||
|
<p className="ant-upload-text">
|
|||
|
{buttonText ||
|
|||
|
intl.formatMessage({
|
|||
|
id: 'component.fileUpload.dragText',
|
|||
|
defaultMessage: '点击或拖拽文件到此区域上传',
|
|||
|
})}
|
|||
|
</p>
|
|||
|
{tip && <p className="ant-upload-hint">{tip}</p>}
|
|||
|
</>
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
return (
|
|||
|
<Button icon={<UploadOutlined />} disabled={disabled}>
|
|||
|
{buttonText || defaultButtonText}
|
|||
|
</Button>
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
return (
|
|||
|
<div className="file-upload-wrapper">
|
|||
|
<UploadComponent {...uploadProps}>{renderUploadButton()}</UploadComponent>
|
|||
|
{!isDragger && tip && (
|
|||
|
<div className="file-upload-tip">
|
|||
|
<Tooltip title={tip}>
|
|||
|
<span>{tip}</span>
|
|||
|
</Tooltip>
|
|||
|
</div>
|
|||
|
)}
|
|||
|
</div>
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
export default FileUpload;
|