之前用Vue做项目用的视图框架有element-ui,团队内部作为补充的zp-ui,以及iview。框架是好用,但是针对自己的项目往往不能全部拿来用,尤其是我们的设计妹子出的界面与现有框架差异很大,改源码效率低又容易导致未知的bug,于是自己就抽时间封装了这个上传组件。
上传文件,html部分就这么一对儿标签,不喜欢复杂啰嗦
export default {
name: 'my-upload',props: {
name: String,action: {
type: String,
required: true
},fileList: {
type: Array,default: []
},data: Object,multiple: Boolean,limit: Number,onChange: Function,onBefore: Function,onProgress: Function,onSuccess: Function,on
Failed: Function,onFinished: Function
},methods: {}//下文主要是methods的介绍,此处先省略
}
这里定义了父组件向子组件需要传递的属性值,注意,这里把方法也当做了属性传递,都是可以的。
自己写的组件,没有像流行框架发布的那样完备和全面,另外针对开头提到的绑定file-list就不能上传了的问题(更可能是我的姿势不对),本人也想极力解决掉自身遇到的这个问题,所以希望能对文件列表有绝对的控制权,除了action,把file-list也作为父组件必须要传递的属性。(属性名父组件使用“-”连接,对应子组件prop中的驼峰命名)
methods内一共4个方法,添加文件、移除文件、提交、检测(上传之前的检验),下面一一讲述:
1.添加文件
标签触发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即可。
3.提交上传
这里使用了两种方式,fetch和原生方式,由于fetch不支持获取上传的进度,如果不需要进度条或者自己模拟进度或者XMLHttpRequest对象不存在的时候,使用fetch请求上传逻辑会更简单一些
4.基于上传的两套逻辑,这里封装了两个方法xhrSubmit和fetchSubmit
fetchSubmit
{
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.on
Failed(index,res);
}
});
return { success,
Failed }; //
上传结束,将结果传递到下文
}).then(this.onFinished); //把
上传总结果返回
},
xhrSubmit
({
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.on
Failed(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的上传源码
403_85@ 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,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;
}
}
最后把请求前的校验加上
如果父组件定义了onBefore方法且返回了false,或者文件列表为空,请求就不会发送。
代码部分完了,使用时只要有了on-progress属性并且XMLHttpRequest对象可访问,就会使用原生方式发送请求,否则就用fetch发送请求(不展示进度)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。
原文链接:https://www.f2er.com/vue/33064.html