vue组件学习-upload

楼主最近项目结束无所事事=。=,加上之前用element的上传组件,用着有一点点不爽…就试着自己写了下上传组件,啊,写完的心理活动就是,虽然一直吐槽element的upload,但是真正轮到自己写才发现too young =。=。他们还是挺厉害的,特别是生命周期的封装上。
回归正题,楼主的组件,参照着element和ant-design的upload api,大致实现了如下功能:

  • 单文件/多文件上传
  • 文件预览,可选预览方式:列表/卡片,卡片主要是针对img(用URL.createObjectURL)
  • progress进度条
    代码中实际上有两种:

    • 一种是读到内存,用fileReader的progress事件
    • 还有一种是xhr的upload.onprogress事件

      最终进度条是按照post到服务器的进度来显示的)

  • accept 限定所能接受的文件类型
  • 可以支持拖拽上传、粘贴上传

源码请戳upload组件,upload分支哟


1. fileReader

内置了几个事件,包括onprogress,onloadstart,onabort,onerror,onload,onloadend等几个事件,其中onloadend类似于finally操作,不管onerror还是onload,结束都会执行。在这边我们需要处理进度条,那么先看onprogress事件。event对象有这么几个有用的属性:

  • lengthComputable:决定浏览器是否可以对进度进行计算
  • total: 文件大小
  • loaded:已经上传的大小
  • result: 读取的结果,以data:url格式,可以用来作为src实现预览功能,当然我们也可以对file对象直接用URL.createObjectURL())来获取实现。

2. 文件上传

新版本的XMLHttpRequest对象,传送数据的时候,有一个progress事件,用来返回进度信息。
它分成上传和下载两种情况。下载的progress事件属于XMLHttpRequest对象,上传的progress事件属于XMLHttpRequest.upload对象。

1
2
xhr.onprogress
xhr.upload.onprogress

要显示进度的话,可以在onprogress事件中获取信息。具体的跟上面一样。主要就是xhr和filereader其实都实现了progressEvent。具体的可以通过给event对象打log看下event.target就知道了。
在filereader的onprogress中,e.target就是filereader,所以当freader.onload事件中可以调用e.target.result来获取src实现预览。如下:

1
2
3
4
5
6
7
8
//filereader实现预览
var reader = new FileReader();
reader.onload = (function(aImg) {
return function(e) {
//e.target=== reader
aImg.src = e.target.result;
};
})(img);
1
2
3
4
5
6
7
8
//xhr中显示进度
xhr.upload.onprogress = function updateProgress(e){
//e.target===XMLHttpRequestUpload,即xhr.upload对象
if(e.lengthComputable){
e.percent = e.loaded/e.total*100;
if(options.onProgress) options.onProgress(e);
}
};

3. xhr

xhr的时间周期
执行顺序:

1
2
3
4
5
6
7
1. xhr open。打开连接
2. xhr send :在这个过程中,追踪请求xhr当前状态,用xhr.readyState来访问。
- case 0: xhr对象刚被创建,尚未open
- case 1: //opened
- case 2: // headers_received。收到响应头
- case 3: //loading 下载阶段
- case 4: //done

只有当 responseType 为”text”、””时,xhr对象上才有此属性,此时才能调用xhr.responseText

从上面介绍的事件中,可以知道若xhr请求成功,就会触发xhr.onreadystatechange和xhr.onload两个事件。 一般倾向于 xhr.onload事件,因为xhr.onreadystatechange是每次xhr.readyState变化时都会触发,而不是xhr.readyState=4时才触发。

1
2
3
4
5
6
7
8
9
//onload 当请求成功完成时触发,此时xhr.readystate=4
const xhr = new XMLHttpRequest();
xhr.onload = function onload(){
if(xhr.status<200 ||(xhr.status>=300 && xhr.status!== 304))
if(options.onError)
options.onError(getError(action,xhr));
else
options.onSuccess(getResponseBody(xhr));
}

补充一条,xhr.send在xmlhttprequest2中data type其实是可以有formdata的,这个就有利于我们上传文件。它会自动将文件转变为字节流对象,然后后台可以进行解析。如果是单纯的obj data,里面放 file的话,无法传输过去。也没法解析。

4. html5 drag drop

  • 一定要在dragover的处理事件中preventDefault(),否则默认行为是会跳转到另一个页面预览文件的。
  • 获取文件列表: e.dataTransfer.files
    e
  • 不会触发到input的change事件

5. file上传

1
2
3
4
<input type="file" style="display:none" id="fileInput" ref="fileInput" multiple @change="handleChange">
<div @click="clickToChooseFile" for="fileInput">
<p class="upload-hint">支持单文件/多文件上传,请选择image类型</p>
</div>
1
2
3
4
//模拟input点击
clickToChooseFile(){
this.$refs.fileInput.click();
},

一般是把input进行隐藏,用另一个自定义样式的div代替它,然后在click事件中去手动click该input。

而监听文件变化的事件 onchange事件的参数是本地新变更(增加的files数组)。

6. 粘贴上传


我们需要对items的每一项转化为file对象。

1
2
3
4
5
const imageFiles = Array.prototype.slice.call(e.clipboardData.items).filter(item=>{
return (item.kind == 'file' && item.type.indexOf('image/') >= 0);
}).map(item=> item.getAsFile());
this.handleFiles(imageFiles);
e.clipboardData.items.clear();//实际上是不会生效的

然后就可以继续用之前的方法(URL.createObjURL(file))读取,预览等等操作。
其实它内置了两个方法,一个是getAsFile,一个是getAsString。转换完后会成为blob对象(file也是一个blob对象)。

注意点: 楼主本来是想粘贴完以后清空剪贴板的。但是貌似看了下w3c的文档,只有在read/write mode下才可以进行操作,而read对应于drag event,write对应于drop事件,其他对应于protected mode。

而平时我们所说的比如知乎的剪贴版权信息,则是用e.clipboardData.setData()这些来进行操作的。既然可以set,那么也可以用它来清空。e.clipboardData.clearData(“text”)也可以实现。

references

  1. 你真的会使用XMLHttpRequest吗?
  2. HTML5 drag & drop 拖拽与拖放简介