最近使用了elementUI提供的upload组件上传文件,总结下使用过程中的一些心得。本文不会介绍如何使用elementUI中的upload组件,因为官网本身就已经介绍的很详细了。
本文主要包括以下几个问题:
- 文件校验是如何做的,能否同时校验多种文件格式;
- 上传文件的时候能否自定义拖拽样式;
- 点击的时候如何实现手动上传。
序言
- 本文涉及到源码的部分,以v2.15.1为例;
- upload组件源代码在
/packages/upload
文件夹下面;
文件校验是如何做的,能否同时校验多种文件格式
upload组件提供了两种上传文件的方式:
- input元素方式:通过input元素触发系统的文件选择窗口来选择文件;
- 拖拽方式:通过原生drag和drop API来选择文件。
针对上述两种方式,upload组件提供了属性accept
来限制和校验文件格式。这里,我使用了两个词:
- 限制:从源头层面上限制,也就是不是特定的格式就选择不了,适用于input元素方式;
校验:从结果层面上校验,也就是在最后一步才能发现选择不了,同时适用于上述两种方式,其中校验又包括两种:
- 源码本身包含的校验逻辑;
- 自定义校验。
接下来我们结合upload组件源码来分析下实现原理。
限制
限制形式的主要原理就是原生input元素的accept属性(源码/packages/upload/src/upload.vue文件第206行):
<input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input>
当没有添加accept属性的时候,系统的文件选择框中的文件都是可选的:
<input type="file" />
当添加了accept之后,比如.png
,系统的文件选择框中只有符合格式的文件才是可选的:
<input type="file" accept=".png" />
如何支持多种文件格式上传呢?看下原生input元素accept属性的文档,会发现accept是支持逗号分割的多种文件格式的:
<input type="file" accept=".png,.jpg,.jpeg" />
开发的时候发现一个有意思的问题,就是如果accept里面.jpg和.jpeg只写一个,另外一种格式也是可以选择的:
<input type="file" accept=".png,.jpeg" />
// 或者
<input type="file" accept=".png,.jpg" />
所以,jpg和jpeg有啥关系?查找了下资料,发现这两种格式并没有任何区别,至于为什么会有两个名字,说是先有了jpeg,然后Windows的早期版本规定文件的扩展名只能是三个字符,所以就变成了jpg。感兴趣的可以参考这篇文章。
所以,
- 只通过accept的形式限制文件类型有时候可能不准确;
- Windows系统可能会有兼容性问题,也就是即使提供了accept属性,其他格式的文件也是可选的;
这个时候就只能依赖下面的校验方式了。
源码本身包含的校验逻辑
当使用拖拽方式上传文件的时候,是可以拖拽任何文件的,所以只能在拖拽完成的时候去校验文件格式。那么,应该去校验哪些格式呢?从upload组件使用者的角度来说,无论是input元素方式还是拖拽方式,都应该是保持统一的。也就是,原生input的accept属性支持的格式,在使用拖拽方式的时候,也都得支持。
所以,先看下input的accept属性支持的格式类型:
- 文件扩展名形式,以
.
开头; - MIME类型;
- "audio/*" "video/*" "image/*"。
upload组件源码针对上述三种类型做的相应处理(/packages/upload/src/upload-dragger.vue
文件onDrop方法)如下:
this.$emit('file', [].slice.call(e.dataTransfer.files).filter(file => {
const { type, name } = file;
const extension = name.indexOf('.') > -1
? `.${ name.split('.').pop() }`
: '';
const baseType = type.replace(/\/.*$/, '');
return accept.split(',')
.map(type => type.trim())
.filter(type => type)
.some(acceptedType => {
if (/\..+$/.test(acceptedType)) { // 1. 扩展名形式
return extension === acceptedType;
}
if (/\/\*$/.test(acceptedType)) { // 3. "audio/*" "video/*" "image/*"
return baseType === acceptedType.replace(/\/\*$/, '');
}
if (/^[^\/]+\/[^\/]+$/.test(acceptedType)) { // 2. MIME类型
return type === acceptedType;
}
return false;
});
}));
自定义校验
upload组件提供了自定义校验的钩子函数:before-upload。根据该函数的返回值决定是继续还是终止(/packages/upload/src/upload.vue
文件第91行):
const before = this.beforeUpload(rawFile);
上传文件的时候能否自定义拖拽样式
结论:不能
尝试
dragEvent有一个dataTransfer属性,这个属性有一个setDragImage方法,该方法看似可以用来自定义拖拽样式。但是,这个方法有一个使用限制:只能在dragstart事件中调用。
但是当从操作系统拖拽文件到浏览器中的时候,dragstart事件是不会触发的,所以setDragImage方法不能实现我们的目的。
点击的时候如何实现手动上传
upload组件dom中包含一个不显示的input元素,当点击组件的时候触发input元素的click事件(/packages/upload/src/upload.vue):
handleClick() {
if (!this.disabled) {
this.$refs.input.value = null;
this.$refs.input.click();
}
}
总结
如有错误,欢迎留言讨论。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。