备份:修复前完整项目快照 2026-04-19
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Upload, Button, message, Image, Spin } from 'antd';
|
||||
import { UploadOutlined, FileOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import type { UploadFile } from 'antd/es/upload/interface';
|
||||
|
||||
interface FileUploadProps {
|
||||
value?: string;
|
||||
onChange?: (url: string) => void;
|
||||
accept?: string;
|
||||
maxSize?: number; // MB
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const FileUpload: React.FC<FileUploadProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
accept = '.pdf,.doc,.docx,.jpg,.jpeg,.png,.xlsx,.xls',
|
||||
maxSize = 10,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
|
||||
const beforeUpload = (file: File) => {
|
||||
const isLt = file.size / 1024 / 1024 < maxSize;
|
||||
if (!isLt) {
|
||||
message.error(`文件大小不能超过 ${maxSize}MB`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleUpload = async (options: any) => {
|
||||
const { file, onSuccess, onError } = options;
|
||||
setLoading(true);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
message.success('上传成功');
|
||||
onChange?.(result.data.url);
|
||||
onSuccess(result.data, file);
|
||||
} else {
|
||||
message.error(result.error || '上传失败');
|
||||
onError?.(new Error(result.error));
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error('上传失败');
|
||||
onError?.(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
onChange?.('');
|
||||
setFileList([]);
|
||||
};
|
||||
|
||||
// 判断文件类型
|
||||
const getFileType = (url: string) => {
|
||||
const ext = url.split('.').pop()?.toLowerCase();
|
||||
if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext || '')) {
|
||||
return 'image';
|
||||
}
|
||||
return 'file';
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{value ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
{getFileType(value) === 'image' ? (
|
||||
<Image src={value} width={100} height={100} style={{ objectFit: 'cover' }} />
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: 4,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: '#fafafa',
|
||||
}}
|
||||
>
|
||||
<FileOutlined style={{ fontSize: 32, color: '#1890ff' }} />
|
||||
</div>
|
||||
)}
|
||||
<div style={{ flex: 1 }}>
|
||||
<a href={value} target="_blank" rel="noopener noreferrer">
|
||||
查看文件
|
||||
</a>
|
||||
</div>
|
||||
{!disabled && (
|
||||
<Button danger icon={<DeleteOutlined />} onClick={handleRemove}>
|
||||
删除
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Upload
|
||||
accept={accept}
|
||||
beforeUpload={beforeUpload}
|
||||
customRequest={handleUpload}
|
||||
fileList={fileList}
|
||||
showUploadList={false}
|
||||
disabled={disabled || loading}
|
||||
>
|
||||
<Button icon={<UploadOutlined />} disabled={disabled}>
|
||||
{loading ? <Spin size="small" /> : '选择文件'}
|
||||
</Button>
|
||||
</Upload>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileUpload;
|
||||
Reference in New Issue
Block a user