文章目录
一、函数
(1)图片绝对路径转文件流File
路径就是类似下面这种
"/pic/userpic/4594b300f6b33903d172d71c6c884dde_1614841699937034945.jpeg"
1. 主函数
export const urlToFile = (url: string): Promise<any> => {
return new Promise((resolve, reject) => {
const image = new Image();
image.crossOrigin = '';
image.src = url;
image.onload = function(): void {
//转base64
const base64ImageSrc = getBase64Image(image);
//base64转file
const file = dataURLtoFile(base64ImageSrc, 'image');
resolve(file);
};
image.onerror = (e): void => reject(e);
});
};
2. 获取图片base64
export function getBase64Image(img: any): string {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
if (!ctx) return '';
ctx.drawImage(img, 0, 0, img.width, img.height);
const ext = img.src.substring(img.src.lastIndexOf('.') + 1).toLowerCase();
const dataURL = canvas.toDataURL('image/' + ext);
return dataURL;
}
3. base64转file
export function dataURLtoFile(dataurl: string, filename: string): any {
// 获取到base64编码
const arr = dataurl.split(',');
// 将base64编码转为字符串
const bstr = window.atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n); // 创建初始化为0的,包含length个元素的无符号整型数组
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {
type: 'image/jpeg',
});
}
二、页面结构(最终图片以file格式提交给后端)即formdata
(1)html页面结构显示
1. 主页面
<Item label={'头像设置'}>
{getFieldDecorator('image', {
rules: [{ required: true, message: '请上传头像' }],
})(<UploadImg size={'default'} />)}
</Item>
2. 子页面
import React, { FC, forwardRef, useState, memo, useEffect } 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?(file: any): void;
//传递base64时,onChange?(image: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 [img, setImg] = useState(''); //使用变量实现预览图片
const uploadProps = {
accept: 'image/*',
beforeUpload(): boolean {
setLoading(true);
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) {
//获取base64
getBase64(info.file, async (imgUrl: any) => {
if (onChange) {
//onChange(imgUrl as string); //可以传递base64
setImg(imgUrl);
onChange(info.file);//传递file对象
}
});
} else {
//没有自带旋转则旋转该图片
const result = await getBase64Rotatatin(info.file);
if (onChange) {
//onChange(result as string);
setImg(result as string);
onChange(info.file);
}
}
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
},
onRemove(): boolean {
if (onChange) {
onChange('');
}
return true;
},
};
useEffect(() => {
if (typeof value === 'string') {
setImg(value);
}
}, [value]);
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={'上传中...'}>
{img ? (
<div className={styles['upload-img__preview']}>
<img src={img} style={{ width: '100%' }} />
</div>
) : (
uploadButton
)}
</Spin>
</AntdUpload>
);
};
export default memo(forwardRef(UploadImage));
(2) 判断浏览器是否自带复原函数
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);
}
});
}
(3)图片旋转函数
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);
});
});
};
三、页面使用
(1)不涉及图片回显
不涉及图片回显的情况直接调用上面的代码即可
当表单提交,通过value即可拿到数据
const handleSubmit: React.FormEventHandler<HTMLFormElement> = useCallback(
(e): void => {
e.preventDefault();
validateFields(async (err, values) => {
if (!err) {
try {
const formData = new FormData();
formData.append('image', values.image);;
shangchuantupian(formData);
history.push('/xxx');
message.success('添加成功');
} catch (e) {
message.error(e.message || '添加失败');
}
}
});
},
[validateFields],
);
(2)涉及图片回显(回显格式为路径)
const handleSubmit: React.FormEventHandler<HTMLFormElement> = useCallback(
(e): void => {
e.preventDefault();
validateFields(async (err, values) => {
if (!err) {
try {
const query = queryString.parse(location.search);
const { image = '' } = values;
let img_file = image;
if (typeof image === 'string') {
//路径,调用上面的函数
img_file = await urlToFile(image);
}
const formData = new FormData();
formData.append('image', img_file);
if (query && query.id) {
formData.append('id', query.id as string);
editToFile(formData);
history.push('/xxx');
message.success('更新成功');
}
} catch (e) {
message.error(e.message || '更新失败');
}
}
});
},
[validateFields, location.search],
);
四、后端接口
(1)后端上传接口
export async function editToFile(params: any): Promise<ResponseReturn<PliliangrukuResponse>> {
return await http.post('/users/modify', params, {
headers: {
'Content-Type': 'multipart/form-data',//重写请求头
},
});
}
(2)axios封装http
export const http = axios.create({
baseURL: '/xxx',
timeout: 100000,
responseType: 'json',
headers: { 'Content-Type': 'application/json' },//可有可无
});
参考文章:https://my.oschina.net/u/4280335/blog/3387307
https://blog.youkuaiyun.com/yin13037173186/article/details/83302628