Vue封装一个简单轻量的上传文件组件的示例

网友投稿 447 2023-02-12


Vue封装一个简单轻量的上传文件组件的示例

一、之前遇到的一些问题

项目中多出有上传文件的需求,使用现有的UI框架实现的过程中,不知道什么原因,总会有一些莫名其妙的bug。比如用某上传组件,明明注明(:multiple="false"),可实际上还是能多选,上传的时候依然发送了多个文件;又比如只要加上了(:file-list="fileList")属性,希望能手动控制上传列表的时候,上传事件this.refs.[upload(组件refhttp://)].submit()就不起作用了,传不了。总之,懒得再看它怎么实现了,我用的是功能,界面本身还是要重写的,如果坚持用也会使项目多很多不必要的逻辑、样式代码……

之前用vue做项目用的视图框架有element-ui,团队内部作为补充的zp-ui,以及iview。框架是好用,但是针对自己的项目往往不能全部拿来用,尤其是我们的设计妹子出的界面与现有框架差异很大,改源码效率低又容易导致未知的bug,于是自己就抽时间封装了这个上传组件。

二、代码与介绍

父组件

上传

ref="myUpload"

:file-list="fileList"

action="/uploadPicture"

:data="param"

:on-change="onChange"

:on-progress="uploadProgress"

:on-success="uploadSuccess"

:on-failed="uploadFailed"

multiple

:limit="5"

:on-finished="onFinished">

ref="myUpload"

:file-list="fileList"

action="/uploadPicture"

:data="param"

:on-change="onChange"

:on-progress="uploadProgress"

:on-success="uploadSuccess"

:on-failed="uploadFailed"

multiple

:limit="5"

:on-finished="onFinished">

父组件处理与业务有关的逻辑,我特意加入索引参数,便于界面展示上传结果的时候能够直接操作第几个值,并不是所有方法都必须的,视需求使用。

子组件

上传文件,html部分就这么一对儿标签,不喜欢复杂啰嗦

这里定义了父组件向子组件需要传递的属性值,注意,这里把方法也当做了属性传递,都是可以的。

自己写的组件,没有像流行框架发布的那样完备和全面,另外针对开头提到的绑定file-list就不能上传了的问题(更可能是我的姿势不对),本人也想极力解决掉自身遇到的这个问题,所以希望能对文件列表有绝对的控制权,除了action,把file-list也作为父组件必须http://要传递的属性。(属性名父组件使用“-”连接,对应子组件prop中的驼峰命名)

三、主要的上传功能

methods: {

addFile, remove, submit, checkIfCanUpload

}

methods内一共4个方法,添加文件、移除文件、提交、检测(上传之前的检验),下面一一讲述:

1.添加文件

addFile({target: {files}}){//input标签触发onchange事件时,将文件加入待上传列表

for(let i = 0, l = files.length; i < l; i++){

files[i].url = URL.createObjectURL(files[i]);//创建blob地址,不然图片怎么展示?

files[i].status = 'ready';//开始想给文件一个字段表示上传进行的步骤的,后面好像也没去用......

}

let fileList = [...this.fileList];

if(this.multiple){//多选时,文件全部压如列表末尾

fileList = [...fileList, ...files];

let l = fileList.length;

let limit = this.limit;

if(limit && typeof limit === "number" && Math.ceil(limit) > 0 && l > limit){//有数目限制时,取后面limit个文件

limit = Math.ceil(limit);

// limit = limit > 10 ? 10 : limit;

fileList = fileList.slice(l - limit);

}

}else{//单选时,只取最后一个文件。注意这里没写成fileList = files;是因为files本身就有多个元素(比如选择文件时一下子框了一堆)时,也只要一个

fileList = [files[0]];

}

this.onChange(fileList);//调用父组件方法,将列表缓存到上一级data中的fileList属性

},

2.移除文件

这个简单,有时候在父组件叉掉某文件的时候,传一个index即可。

remove(index){

let fileList = [...this.fileList];

if(fileList.length){

fileList.splice(index, 1);

this.onChange(fileList);

}

},

3.提交上传

这里使用了两种方式,fetch和原生方式,由于fetch不支持获取上传的进度,如果不需要进度条或者自己模拟进度或者XMLHttpRequest对象不存在的时候,使用fetch请求上传逻辑会更简单一些

submit(){

if(this.checkIfCanUpload()){

if(this.onProgress && typeof XMLHttpRequest !== 'undefined')

this.xhrSubmit();

else

this.fetchSubmit();

}

},

4.基于上传的两套逻辑,这里封装了两个方法xhrSubmit和fetchSubmit

fetchSubmit

fetchSubmit(){

let keys = Object.keys(this.data), values = Object.values(this.data), action = this.action;

const promises = this.fileList.map(each => {

each.status = "uploading";

let data = new FormData();

data.append(this.name || 'file', each);

keys.forEach((one, index) => data.append(one, values[index]));

return fetch(action, {

method: 'POST',

headers: {

"Content-Type" : "application/x-www-form-urlencoded"

},

body: data

}).then(res => res.text()).then(res => jsON.parse(res));//这里res.text()是根据返回值类型使用的,应该视情况而定

});

Promise.all(promises).then(resArray => {//多线程同时开始,如果并发数有限制,可以使用同步的方式一个一个传,这里不再赘述。

let success = 0, failed = 0;

resArray.forEach((res, index) => {

if(res.code == 1){

success++; //统计上传成功的个数,由索引可以知道哪些成功了

this.onSuccess(index, res);

}else if(res.code == 520){ //约定失败的返回值是520

failed++; //统计上传失败的个数,由索引可以知道哪些失败了

this.onFailed(index, res);

}

});

return { success, failed }; //上传结束,将结果传递到下文

}).then(this.onFinished); //把上传总结果返回

},

xhrSubmit

xhrSubmit(){

const _this = this;

let options = this.fileList.map((rawFile, index) => ({

file: rawFile,

data: _this.data,

filename: _this.name || "file",

action: _this.action,

onProgress(e){

_this.onProgress(index, e);//闭包,将index存住

},

onSuccess(res){

_this.onSuccess(index, res);

},

onError(err){

_this.onFailed(index, err);

}

}));

let l = this.fileList.length;

let send = async options => {

for(let i = 0; i < l; i++){

await _this.sendRequest(options[i]);//这里用了个异步方法,按次序执行this.sendRequest方法,参数为文件列表包装的每个对象,this.sendRequest下面紧接着介绍

}

};

send(options);

},

这里借鉴了element-ui的上传源码

sendRequest(option){

const _this = this;

upload(option);

function getError(action, option, xhr) {

var msg = void 0;

if (xhr.response) {

msg = xhr.status + ' ' + (xhr.response.error || xhr.response);

} else if (xhr.responseText) {

msg = xhr.status + ' ' + xhr.responseText;

} else {

msg = 'fail to post ' + action + ' ' + xhr.status;

}

var err = new Error(msg);

err.status = xhr.status;

err.method = 'post';

err.url = action;

return err;

}

function getBody(xhr) {

var text = xhr.responseText || xhr.response;

if (!text) {

return text;

}

try {

return JSON.parse(text);

} catch (e) {

return text;

}

}

function upload(option) {

if (typeof XMLHttpRequest === 'undefined') {

return;

}

var xhr = new XMLHttpRequest();

var action = option.action;

if (xhr.upload) {

xhr.upload.onprogress = function progress(e) {

if (e.total > 0) {

e.percent = e.loaded / e.total * 100;

}

option.onProgress(e);

};

}

var formData = new FormData();

if (option.data) {

Object.keys(option.data).map(function (key) {

formData.append(key, option.data[key]);

});

}

formData.append(option.filename, option.file);

xhr.onerror = function error(e) {

option.onError(e);

};

xhr.onload = function onload() {

if (xhr.status < 200 || xhr.status >= 300) {

return option.onError(getError(action, option, xhr));

}

option.onSuccess(getBody(xhr));

};

xhr.open('post', action, true);

if (option.withCredentials && 'withCredentials' in xhr) {

xhr.withCredentials = true;

}

var headers = option.headers || {};

for (var item in headers) {

if (headers.hasOwnProperty(item) && headers[item] !== null) {

xhr.setRequestHeader(item, headers[item]);

}

}

xhr.send(formData);

return xhr;

}

}

最后把请求前的校验加上

checkIfCanUpload(){

return this.fileList.length ? (this.onBefore && this.onBefore() || !this.onBefore) : false;

},

如果父组件定义了onBefore方法且返回了false,或者文件列表为空,请求就不会发送。

代码部分完了,使用时只要有了on-progress属性并且XMLHttpRequest对象可访问,就会使用原生方式发送请求,否则就用fetch发送请求(不展示进度)。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信测试接口(接口测试微信code怎么获取)
下一篇:vue2 mint
相关文章

 发表评论

暂时没有评论,来抢沙发吧~