您好,欢迎访问一九零五行业门户网

Vue封装上传文件组件步骤详解(附代码)

这次给大家带来vue封装上传文件组件步骤详解(附代码),vue封装上传文件组件的注意事项有哪些,下面就是实战案例,一起来看一下。
一、之前遇到的一些问题
项目中多出有上传文件的需求,使用现有的ui框架实现的过程中,不知道什么原因,总会有一些莫名其妙的bug。比如用某上传组件,明明注明(:multiple=false),可实际上还是能多选,上传的时候依然发送了多个文件;又比如只要加上了(:file-list=filelist)属性,希望能手动控制上传列表的时候,上传事件this.refs.[upload(组件ref)].submit()就不起作用了,传不了。总之,懒得再看它怎么实现了,我用的是功能,界面本身还是要重写的,如果坚持用也会使项目多很多不必要的逻辑、样式代码……
之前用vue做项目用的视图框架有element-ui,团队内部作为补充的zp-ui,以及iview。框架是好用,但是针对自己的项目往往不能全部拿来用,尤其是我们的设计妹子出的界面与现有框架差异很大,改源码效率低又容易导致未知的bug,于是自己就抽时间封装了这个上传组件。
二、代码与介绍
父组件
<template>  <p class="content">  <label for="my-upload">   <span>上传</span>  </label>   <my-upload 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">   </my-upload>   <button @click="upload" class="btn btn-xs btn-primary">upload</button>  </p> </template> <script> import myupload from './components/my-upload' export default {  name: 'test',  data(){   return {   filelist: [],//上传文件列表,无论单选还是支持多选,文件都以列表格式保存   param: {param1: '', param2: '' },//携带参数列表   }  },  methods: {   onchange(filelist){//监听文件变化,增减文件时都会被子组件调用   this.filelist = [...filelist];   },   uploadsuccess(index, response){//某个文件上传成功都会执行该方法,index代表列表中第index个文件   console.log(index, response);   },   upload(){//触发子组件的上传方法   this.$refs.myupload.submit();   },   removefile(index){//移除某文件   this.$refs.myupload.remove(index);   },   uploadprogress(index, progress){//上传进度,上传时会不断被触发,需要进度指示时会很有用   const{ percent } = progress;   console.log(index, percent);   },   uploadfailed(index, err){//某文件上传失败会执行,index代表列表中第index个文件   console.log(index, err);   },   onfinished(result){//所有文件上传完毕后(无论成败)执行,result: { success: 成功数目, failed: 失败数目 }   console.log(result);   }  },  components: {   myupload  } } </script>
父组件处理与业务有关的逻辑,我特意加入索引参数,便于界面展示上传结果的时候能够直接操作第几个值,并不是所有方法都必须的,视需求使用。
子组件
<template> <p>  <input style="display:none" @change="addfile" :multiple="multiple" type="file" :name="name" id="my-upload"/> </p> </template>
上传文件,html部分就这么一对儿标签,不喜欢复杂啰嗦
<script> 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,  onfailed: function,  onfinished: function  },  methods: {}//下文主要是methods的介绍,此处先省略 } </script>
这里定义了父组件向子组件需要传递的属性值,注意,这里把方法也当做了属性传递,都是可以的。
自己写的组件,没有像流行框架发布的那样完备和全面,另外针对开头提到的绑定file-list就不能上传了的问题(更可能是我的姿势不对),本人也想极力解决掉自身遇到的这个问题,所以希望能对文件列表有绝对的控制权,除了action,把file-list也作为父组件必须要传递的属性。(属性名父组件使用“-”连接,对应子组件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发送请求(不展示进度)。
相信看了本文案例你已经掌握了方法,更多精彩请关注其它相关文章!
推荐阅读:
js事件委托使用详解
bootstrap中使用webuploader步骤详解
以上就是vue封装上传文件组件步骤详解(附代码)的详细内容。
其它类似信息

推荐信息