背景
在日常工作中,我们经常遇到图片上传的需求,同时对上传的图片尺寸、大小和格式有要求,比如如下:
根据上图我们来拆分下功能:
- 只允许选择单个图片;
- 校验图片的格式、大小和尺寸;
- 实现图片上传到后端服务器;
等会儿我们也就按照这个功能步骤来逐步实现我们的功能~~
工欲善其事,必先利其器。我们先去Upload组件文档里学习一下它的API,大概看下它能实现什么功能~
其实就是主要瞄一瞄,咱们的需求是否都能实现:),看了眼,嗯~ 我们要的功能貌似都满足呢,那我们就开始吧~~
Step 1:只允许选择单个图片
在文档中看到,fileList
代表已经上传的文件列表,我们可以通过控制fileList
的长度为1来达到只能选择单个图片。不过有一点需要注意,fileList
一用,文件列表就受控了,我们还需要添加onChange
事件:
const UploadImage = () => {
let [fileList, setFileList] = useState([]);
const handleChange = ({ file }) => {
setFileList([file])
}
return (
<Upload
listType="picture-card"
fileList={fileList}
onChange={handleChange}
>
{
fileList.length === 0 ? <div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>上传图片</div>
</div> : null
}
</Upload>
)
}
复制代码
如上,我们就可以点击选择图片了~ 这里有个小技巧,当fileList
有内容的时候,我们就渲染了null,没有上传入口了,这样也一样做到了只能上传一张图片:)
鼠标hover图片,可以看到有个删除按钮,我们换个图片试试。 点击一下删除,呀,删不了!咋回事呢?那是因为文件列表已受控,我们需要自己加删除时的逻辑。
在handleChange
里面断点一看,发现点击删除的时候,file
的status
会是removed
,修改下handleChange
:
const handleChange = ({ file }) => {
if (file.status === 'removed') {
setFileList([]);
} else {
setFileList([file]);
}
}
复制代码
运行下,嗯~ 可以删除了,从无到有的选择图片已经work了,我们可以继续加砖啦~~
Step 2: 校验图片的格式、大小和尺寸
1. 校验图片格式
方法一:用accept
配置
在Upload组件文档里有个API accept
,用于接受上传的文件类型,和input accept属性一样的用法。在Upload组建上添加如下属性:
accept="image/png"
复制代码
如果允许多个格式用“,”隔开即可,这种在选择文件的时候有个优点,不符合格式要求的直接是不可选的状态,没法选中,是不是很easy?
方法二:使用beforeUpload
方法
在上面提到的Upload组件文档里,页面右边第一个demo就是一个限制上传功能,使用的是beforeUpload
,我们可以依葫芦画瓢了,哈~
添加beforeUpload
方法:
const beforeUpload = (file) => {
//校验格式
const isValidType = file.type === 'image/png';
if (!isValidType) {
message.error('只能上传格式为PNG的图片');
return false;
}
}
复制代码
限制尺寸的还没有做,先测试一下咱们的格式和大小是不是运行良好~
于是我特意选了一个不是png的文件上传,然后我看到了这个?!
错误报了,文件也上传到页面了?这是拦截了啥?
带着疑问重回文档看了看beforeUpload
API,上面这么写的:
上传文件之前的钩子,参数为上传的文件,若返回 false 则停止上传。支持返回一个 Promise 对象,Promise 对象 reject 时则停止上传,resolve 时开始上传( resolve 传入 File 或 Blob 对象则上传 resolve 传入对象);也可以返回 Upload.LIST_IGNORE,此时列表中将不展示此文件。 注意:IE9 不支持该方法
返回false则停止上传,表示没懂呀,于是再去跑到文档里,看看有没有啥补充说明,是不是漏了些啥。
突然看到文档的一个demo(左第5个demo),下面有段话这么说的:
beforeUpload 返回 false 或 Promise.reject 时,只用于拦截上传行为,不会阻止文件进入上传列表(原因)。如果需要阻止列表展现,可以通过返回 Upload.LIST_IGNORE 实现。
只用于拦截上传行为,不会阻止文件进入上传列表!! 原来停止上传这个意思。
点开demo看了下人家的代码,人家用的是Upload.LIST_IGNORE
来做的,我们的文件列表都受控了,没法用这个来处理。既然能被显示到上传列表,说明肯定被setFileList
过,整个代码就只有handleChange
里面做过setFileList
,到handleChange
里打印看看拦截时的file
有没有啥不同。
哈哈,找到不同点了,被beforeUpload返回false拦截的文件没有status属性,我们来改改handleChange
方法:
const handleChange = ({ file }) => {
let curFile;
switch (file.status) {
case 'uploading':
case 'done':
curFile = [file];
break;
case 'removed':
default:
curFile = [];
break;
}
setFileList([...curFile]);
}
复制代码
再跑一把,good,这回不满足要求的不会显示了,并且弹出错误提醒~
方法三:通过读取文件的二进制数据来判断文件类型
一般情况下,通过文件的后缀名或者文件的MIME
类型来判断文件的类型,可以满足大部分的需求。为了测试上面的格式判断起效,我特意把一个JPEG
格式的图片改成.png
后缀,发现图片就成功“越狱”了!
如果要杜绝这种“越狱”的话,我们需要通过读取文件的二进制数据,通过魔数来判断。
PNG的魔数是 0x89 50 4E 47 0D 0A 1A 0A,我们可以通过读取文件二进制的前8个字节来一一比对来判定文件的类型:
const getBuffers = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(file.slice(0, 8));
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
})
};
const beforeUpload = (file) => {
//校验格式
const isValidType = file => {
return new Promise((resolve, reject) => {
//PNG的魔数
const pngBufferValues = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
getBuffers(file).then(buffers => {
const fileBufferValues = new Uint8Array(buffers);
const isPNG = pngBufferValues.join(',') === fileBufferValues.join(',');
if (!isPNG) {
message.error('只能上传格式为PNG的图片');
reject();
} else {
resolve(file);
}
})
})
}
return isValidType(file);
复制代码
这么一来,即使把JPEG
格式的图片改成.png
后缀,这个图片也会被拦截住,完美~~
2. 校验图片大小
上面格式已经可以work了,大小瞬间变得好容易,在beforeUpload
函数里面添加图片大小限制的逻辑即可:
const beforeUpload = (file) => {
//校验尺寸.....
//校验大小
const isValidVolume = file.size / 1024 < 300;
if (!isValidVolume) {
message.error('只能上传大小小于300k的图片');
return false;
}
}
复制代码
测一把,嗯,大小的限制也okay了~~
3. 校验图片尺寸
思路:图片再加载好后(onload
),可以获得到它的宽度和高度。
直接上代码,在beforeUpload
函数里添加校验图片尺寸:
const beforeUpload = (file) => {
//校验格式......
//校验大小......
//校验尺寸......
const isSize = file => {
return new Promise((resolve, reject) => {
let width = 800;
let height = 600;
let _URL = window.URL || window.webkitURL;
let img = new Image();
img.onload = function () {
_URL.revokeObjectURL(this.src);
let valid = img.width <= width && img.height <= height;
valid ? resolve() : reject();
};
img.src = _URL.createObjectURL(file);
}).then(
() => {
return file;
},
() => {
message.error('只能上传尺寸为800*600的图片');
return Promise.reject();
}
);
};
return isSize(file);
}
复制代码
跑跑看,嗯~尺寸限制也运行良好
Step 3:实现图片上传到后端服务器
在Upload的API中,我们可以看到一个API,action
上传的地址,这里,只要把我们后端的接口url配置在这就可以了,如果你上传还需要额外的参数,那就配置在data
这个API里。
<Upload
listType="picture-card"
fileList={fileList}
onChange={handleChange}
beforeUpload={beforeUpload}
action="/api/file/upload"
data={{ platform: 'pc' }}
>
{
fileList.length === 0 ? <div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>上传图片</div>
</div> : null
}
</Upload>
复制代码
也可以参考文档右倒数第三个demo(使用阿里云 OSS 上传示例)。
不过有点需要注意,现在已经走了接口上传,那么接口就可能会成功,也会失败。这么一来,我们handleChange
里面file.status
是done的情况,需要添加接口的返回逻辑判断:
case 'uploading':
curFile = [file];
break;
case 'done':
if (file.response.status === 0) {
curFile = [file];
} else {
curFile = [];
}
break;
复制代码
到这里,图片在选择完成,通过我们的校验后就被成功上传到后端接口啦~~
最后的话
Antd Upload
单个图片的校验和上传内容到这里结束咯~
感谢你的阅读,希望对在读的你有所帮助:)