之前一直在使用antd上传文件,图片,从antd3到antd4再到antdPro,总结一下经验吧
一、antd3上传图片
这里用的antd的版本是"antd": "^3.25.0",
import React, { FC, forwardRef, useState, memo } from 'react';
import { Button, Icon, Upload as AntdUpload, Spin } from 'antd';
import { UploadChangeParam } from 'antd/lib/upload';
import { UploadFile } from 'antd/lib/upload/interface';
import { getBase64Rotatatin } from 'utils';
import { detectImageAutomaticRotation } from 'utils/rotatain';
import styles from './uploadImg.module.scss';
import classNames from 'classnames';
interface Props {
onChange?(base64: string): void;
value?: string;
size?: 'small' | 'default';
}
//获取base64
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function getBase64(img: any, callback: any) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}
const UploadImage: FC<Props> = ({ onChange, value, size = 'default' }, ref): JSX.Element => {
const [loading, setLoading] = useState(false);
const uploadProps = {
accept: 'image/*', //设置接收的文件时图片
beforeUpload(): boolean {
setLoading(true); //设置loading状态
return false;
},
async onChange(info: UploadChangeParam<UploadFile<any>>): Promise<void> {
if (!info.file) return Promise.reject(new Error('请选择图片'));
try {
//这个是我写的函数,判断系统是否自带回正
const system = await detectImageAutomaticRotation();
if (system) {
getBase64(info.file, async (imgUrl: any) => {
if (onChange) {
onChange(imgUrl as string);
}
});
} else {
//回正函数
const result = await getBase64Rotatatin(info.file);
if (onChange) {
onChange(result as string);
}
}
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
},
onRemove(): boolean {
if (onChange) {
onChange('');
}
return true;
},
};
const uploadButton = (
<div className={styles['upload-img__btn']}>
<Icon type={'camera'} className={styles['upload-img__icon']} />
<Button>上传图片</Button>
</div>
);
const className = classNames(styles['upload-img'], {
[styles['upload-img--small']]: size === 'small',
});
return (
<AntdUpload {...uploadProps} ref={ref} className={className} showUploadList={false}>
<Spin spinning={loading} tip={'上传中...'}>
{value ? (
<div className={styles['upload-img__preview']}>
<img src={value} style={{ width: '100%' }} />
</div>
) : (
uploadButton
)}
</Spin>
</AntdUpload>
);
};
export default memo(forwardRef(UploadImage));
//是否自带回正
const testAutoOrientationImageURL =
'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' +
'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' +
'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' +
'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' +
'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' +
'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==';
let isImageAutomaticRotation;
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function detectImageAutomaticRotation() {
return new Promise(resolve => {
if (isImageAutomaticRotation === undefined) {
const img = new Image();
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
img.onload = () => {
// 如果图片变成 1x2,说明浏览器对图片进行了回正
isImageAutomaticRotation = img.width === 1 && img.height === 2;
resolve(isImageAutomaticRotation);
};
img.src = testAutoOrientationImageURL;
} else {
resolve(isImageAutomaticRotation);
}
});
}
//回正且转化函数
/* eslint-disable @typescript-eslint/no-explicit-any */
import EXIF from 'exif-js';
export const getOrientation = (file: any): Promise<number> => {
return new Promise((resolve, reject) => {
EXIF.getData(file, function() {
try {
EXIF.getAllTags(file);
const orientation = EXIF.getTag(file, 'Orientation');
resolve(orientation);
} catch (e) {
reject(e);
}
});
});
};
export const setImgVertical = (imgSrc: string, orientation: number): Promise<string> => {
return new Promise((resolve, reject) => {
const image = new Image();
if (!imgSrc) return;
const type = imgSrc.split(';')[0].split(':')[0];
const encoderOptions = 1;
image.src = imgSrc;
image.onload = function(): void {
const imgWidth = (this as any).width;
const imgHeight = (this as any).height;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return;
canvas.width = imgWidth;
canvas.height = imgHeight;
if (orientation && orientation !== 1) {
switch (orientation) {
case 6:
canvas.width = imgHeight;
canvas.height = imgWidth;
ctx.rotate(Math.PI / 2);
ctx.drawImage(this as any, 0, -imgHeight, imgWidth, imgHeight);
break;
case 3:
ctx.rotate(Math.PI);
ctx.drawImage(this as any, -imgWidth, -imgHeight, imgWidth, imgHeight);
break;
case 8:
canvas.width = imgHeight;
canvas.height = imgWidth;
ctx.rotate((3 * Math.PI) / 2);
ctx.drawImage(this as any, -imgWidth, 0, imgWidth, imgHeight);
break;
}
} else {
ctx.drawImage(this as any, 0, 0, imgWidth, imgHeight);
}
resolve(canvas.toDataURL(type, encoderOptions));
};
image.onerror = (e): void => reject(e);
});
};
export const getBase64Rotatatin = (img: any): Promise<string | ArrayBuffer | null> => {
return new Promise((resolve, reject) => {
getOrientation(img).then(orientation => {
const reader = new FileReader();
reader.addEventListener(
'load',
async (): Promise<void> => {
try {
const base64 = await setImgVertical(reader.result as string, orientation);
resolve(base64);
} catch (e) {
reject(e);
}
},
);
reader.addEventListener('error', () => reject(new Error('获取图片Base64失败')));
reader.readAsDataURL(img);
});
});
};
后面就直接可以调用这个组件
const [image1, setImage1] = useState('');
const handleChange = useCallback((name: string) => {
return (base64: string): void => {
if (name === 'image1') {
setImage1(base64);
} else if (name === 'image2') {
setImage2(base64);
}
};
}, []);
//handleChange 里的参数是用来辨别是哪个图片
<UploadImg value={image1} onChange={handleChange('image1')} />
二、antd4上传文件
这里用的antd4的版本是:"antd": "^4.13.1",
const [fileName, setFileName] = useState('');
//showUploadList={false} directory={false} multiple={false} 表示不能上传文件夹,不能上传多文件,不用显示list
<Upload {...uploadProps} showUploadList={false} directory={false} multiple={false}>
<Button type="link" disabled={disable} className={styles['form-upload__link']}>
//这里是想待文件上传成功后显示文件名
{fileName ? fileName : '点击选择文件上传'}
</Button>
</Upload>
import { UploadChangeParam, UploadFile } from 'antd/lib/upload/interface';
const file_list = useRef<Blob>();
//定义的文件名检测数组
const FILE_NAME = [
'xxx1.json',
'xxx2.json',
];
const uploadProps = {
name: 'file', //设置上传的文件名
beforeUpload(): boolean {
return false;
},
async onChange(info: UploadChangeParam<UploadFile<any>>): Promise<void> {
if (!info.file) return Promise.reject(new Error('请选择文件!'));
try {
//将上传的文件名赋值给name
const name = info.file.name;
//判断name是否符合自己的约束
const flag = FILE_NAME.includes(name);
if (!flag) {
message.error(
`文件命名请按照以下规范:['xxx1.json',
'xxx2.json']`,
);
} else {
//file_list是自己定义的blob Ref ,将二进制文件赋给这个ref,需要注意的地方是二进制文件都需要formData的形式进行提交
file_list.current = info.file.originFileObj;
setFileName(name); //将文件名保存下来用于上面的组件回显
}
} catch (e) {
console.error(e);
}
},
};
比如说,我这里有个button进行提交。需要在form表单中使用我们上面写的组件,如下:
<Form onFinish={handleSubmit}>
<Form.Item name="file_id">
<Upload {...uploadProps} showUploadList={false} directory={false} multiple={false}>
<Button type="link" disabled={disable} >
{fileName ? fileName : '点击选择文件上传'}
</Button>
</Upload>
</Form.Item>
</div>
<div>
<Form.Item >
<div style={{ paddingTop: 22 }}>
<Button type={'primary'} disabled={disable} htmlType="submit">
submit
</Button>
</div>
</Form.Item>
</Form>
提交如下:
const handleSubmit = useCallback(async () => {
try {
const formData = new FormData();
//将刚刚赋值进去的blob 二进制文件流append到formData中
formData.append('fileContent', file_list.current);
//下面就是文件上传接口了,怎么写都行
const { status } = await xxx( formData);
if (status === 0) {
message.success('文件下发成功!');
} else {
message.error('文件下发失败!');
}
} catch (e) {
message.error(e || '出错了!');
}
}, [agent_info.uuid, fileName]);
三、使用antdPro
我这里的antd版本是:"antd": "^4.18.6", "@ant-design/pro-form": "^1.53.6",
import { get } from 'lodash-es';
<ProForm
onFinish={async(values)=>{
const { upload } = values;
const formData: FormData = new FormData();
fileList.current = get(upload[0], 'originFileObj');
formData.append('FileName', fileList.current);
try{
//以下根据你自己的接口函数写
await requestUpload({ data: formData });
message.success("成功!")
}catch(e){
message,error("失败!")
}
}}
layout="horizontal"
>
<ProFormUploadButton
name="upload"
label="上传文件"
max={1}
fieldProps={{
name: 'file',
beforeUpload(file): boolean {
return get(file, 'size', 0) < 2 * (1024*1024); //设置大小不能超过两mb
},
multiple: false,//不能多选
}}
accept=".xls,.xlsx" //设置文件类型,简直太方便了
rules={[{ required: true, message: '请上传文件' },
{ //这里还可以使用自定义规则来对文件大小进行约束,超过2Mb就提交不了,更适合产品逻辑
validator: (rule, [value]) =>
new Promise<void>((resolve, reject) => {
if (get(value, 'size', 0) < 2 * (1024 * 1024)) {
resolve();
} else {
reject(new Error('单个文件不能超过2MB!'));
}
}),
},]} //设置该标单项是必选
extra={ //设置提示
<span>
支持格式:.xls .xlsx ,单个文件不能超过2MB。
</span>
}
/>
</ProForm>