148 lines
4.5 KiB
TypeScript
148 lines
4.5 KiB
TypeScript
import { PlusOutlined } from '@ant-design/icons';
|
||
import type { GetProp, UploadFile, UploadProps } from 'antd';
|
||
import { Image, message, Spin, Upload } from 'antd';
|
||
import React, { useCallback, useEffect, useState } from 'react';
|
||
import { uploadImage } from '@/services/infra/media';
|
||
|
||
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||
|
||
const getBase64 = (file: FileType): Promise<string> =>
|
||
new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.readAsDataURL(file);
|
||
reader.onload = () => resolve(reader.result as string);
|
||
reader.onerror = (error) => reject(error);
|
||
});
|
||
|
||
// accept: .doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document image/*,.pdf
|
||
const UploadImages: React.FC<{
|
||
value?: string;
|
||
onChange?: (value: string | string[]) => void;
|
||
multiple?: boolean;
|
||
accept?: string;
|
||
maxCount?: number;
|
||
}> = (props) => {
|
||
const {
|
||
value,
|
||
multiple = false,
|
||
maxCount = 1,
|
||
accept = 'image/png,image/jpeg',
|
||
onChange,
|
||
} = props;
|
||
const [previewOpen, setPreviewOpen] = useState(false);
|
||
const [previewImage, setPreviewImage] = useState('');
|
||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||
const [uploading, setUploading] = useState(false);
|
||
useEffect(() => {
|
||
if (value) {
|
||
setFileList([{ uid: '-1', url: value, status: 'done', name: value }]);
|
||
} else {
|
||
setFileList([]);
|
||
}
|
||
}, [value]);
|
||
const beforeUpload = (file: FileType) => {
|
||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
|
||
if (!isJpgOrPng) {
|
||
message.error('仅支持.jpg .png 格式!');
|
||
}
|
||
// const isLt2M = file.size / 1024 / 1024 < 2;
|
||
// if (!isLt2M) {
|
||
// message.error('Image must smaller than 2MB!');
|
||
// }
|
||
return isJpgOrPng;
|
||
};
|
||
const handlePreview = async (file: UploadFile) => {
|
||
if (!file.url && !file.preview) {
|
||
file.preview = await getBase64(file.originFileObj as FileType);
|
||
}
|
||
|
||
setPreviewImage(file.url || (file.preview as string));
|
||
setPreviewOpen(true);
|
||
};
|
||
|
||
const handleRemove = (file: UploadFile): boolean => {
|
||
const newFileList = fileList.filter((item) => item.uid !== file.uid);
|
||
const newUrl = newFileList.map((item) => item.url) as string[];
|
||
onChange?.(newUrl[0]);
|
||
return true;
|
||
};
|
||
|
||
const uploadButton = (
|
||
<button style={{ border: 0, background: 'none' }} type="button">
|
||
<PlusOutlined />
|
||
<div style={{ marginTop: 8 }}>Upload</div>
|
||
</button>
|
||
);
|
||
const uploadFile = useCallback(async (file: File): Promise<string> => {
|
||
return new Promise((resolve, reject) => {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
try {
|
||
uploadImage(formData).then((res) => {
|
||
if (res) {
|
||
resolve(res);
|
||
} else {
|
||
reject(new Error('上传失败:未返回有效的URL'));
|
||
}
|
||
});
|
||
} catch (error) {
|
||
reject(error);
|
||
}
|
||
});
|
||
}, []);
|
||
const handleLoadImage: UploadProps['customRequest'] = async (option) => {
|
||
const { file, onSuccess, onError, onProgress } = option;
|
||
|
||
try {
|
||
setUploading(true);
|
||
// 模拟进度更新
|
||
onProgress?.({ percent: 10 });
|
||
// 调用后端接口
|
||
const url = await uploadFile(file as File);
|
||
onProgress?.({ percent: 100 });
|
||
if (url) {
|
||
onChange?.(url);
|
||
onSuccess?.({ url });
|
||
message.success('上传成功');
|
||
} else {
|
||
throw new Error('上传失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('Upload error:', error);
|
||
onError?.(error as Error);
|
||
message.error(`上传失败: ${(error as Error).message}`);
|
||
} finally {
|
||
setUploading(false);
|
||
}
|
||
};
|
||
return (
|
||
<Spin spinning={uploading}>
|
||
<Upload
|
||
listType="picture-card"
|
||
fileList={fileList}
|
||
onPreview={handlePreview}
|
||
onRemove={handleRemove}
|
||
beforeUpload={beforeUpload}
|
||
customRequest={handleLoadImage}
|
||
multiple={multiple}
|
||
accept={accept}
|
||
>
|
||
{fileList.length >= maxCount ? null : uploadButton}
|
||
</Upload>
|
||
{previewImage && (
|
||
<Image
|
||
wrapperStyle={{ display: 'none' }}
|
||
preview={{
|
||
visible: previewOpen,
|
||
onVisibleChange: (visible) => setPreviewOpen(visible),
|
||
afterOpenChange: (visible) => !visible && setPreviewImage(''),
|
||
}}
|
||
src={previewImage}
|
||
/>
|
||
)}
|
||
</Spin>
|
||
);
|
||
};
|
||
|
||
export default UploadImages;
|