一、之前遇到的一些问题

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

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

二、代码与介绍

父组件


<template><div class="content"><label for="my-upload"><span>上传</span></label><my-uploadref="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></div>
</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>
<div><input style="display:none" @change="addFile" :multiple="multiple" type="file" :name="name" id="my-upload"/>
</div>
</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();elsethis.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){      //约定失败的返回值是520failed++;                  //统计上传失败的个数,由索引可以知道哪些失败了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发送请求(不展示进度)。

转载于:https://juejin.im/post/5ab0a40af265da239f073331

Vue封装一个简单轻量的上传文件组件相关推荐

  1. 七牛云 vue 图片上传简单解说,js 上传文件图片

    七牛云 vue 图片上传简单解说,js 上传文件图片 一.七牛云简介 首次使用七牛云存储进行项目的图片存储,整了一上午才整明白,这些官方的教程把明白人也给说糊涂了,文档很不规范. 七牛云有免费的使用额 ...

  2. java0到9的字符怎么表示_java,_java 怎么生成一个0-9,a-z的一个44位字符串作为上传文件的名字,java - phpStudy...

    java 怎么生成一个0-9,a-z的一个44位字符串作为上传文件的名字 找到一个时间MD5加密的 package org.blog.controller; import java.io.File; ...

  3. SWFUpload上传文件组件,跨域上传文件

    转自: http://zhaowenbinmail.blog.163.com/blog/static/3908086201042743942935/ 解决SWFUpload上传文件组件使用时报告204 ...

  4. 微信小程序上传文件组件

    微信小程序上传文件 一.说明 该拍照组件带有微信授权相机功能,会结合后端接口,将上传的图片以数组集合的形式传值给父级页面. 注意:组件适用于,单独上传图片,不携带参数,结合后端接口返回路径之后,再调用 ...

  5. vue 移动端头像裁剪_vue头像上传裁剪组件_一个漂亮的Vue组件,用于图像裁剪和上传...

    vue头像上传裁剪组件 vue-image-crop-upload (vue-image-crop-upload) A beautiful vue component for image crop a ...

  6. Vue之AntDesignVue之「a-upload」上传文件列表删除列表中的某个待上传文件时,出现预期想删除的文件与实际删除的文件不一致的问题

    这是官网的参考方法 于是参考此方法,采用beforeUpload 返回 false 后,手动上传文件的上传方法. 这是我的写法 <template>: js: @change remove ...

  7. vue封装一个 从顶部滑出的浮层小组件, 原生和react都可以仿照做出来

    先看下效果哈,相信这种效果业务中还是比较常见的, 因为吧pdf最大只能上传5M的大小,所以稍微动下就要超过了,所以录制的时候我点的很快.将就着看吧.毕竟代码才是关键 哈哈.效果就凑合着看 大部分情况下 ...

  8. accept 返回0_使用Vue3.0新特性造轮子 WidgetUI3.0 (Upload上传文件组件)

    我们先看看组件效果: 基本使用 可选参数( icon='icon-service') props属性: title(类型 String) 组件显示的文本,默认"上传'. icon(类型 St ...

  9. vue前端上传文件夹的插件_基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件...

    1. 前言 之前公司要在管理系统中做一个全局上传插件,即切换各个页面的时候,上传界面还在并且上传不会受到影响,这在vue这种spa框架面前并不是什么难题.然而后端大佬说我们要实现分片上传.秒传以及断点 ...

最新文章

  1. 补充cpusim图片
  2. Linux下批量kill掉进程
  3. putty保存用户名和密码_使用PuTTY远程登录软件登录 Linux 实例
  4. python段错误原因_python – 捕获崩溃的子进程的“分段错误”...
  5. 2020年第十一届蓝桥杯 - 省赛 - Java研究生组+Java大学B组+Python大学组 - E.排序
  6. barrel-distortion
  7. 微型计算机生产制约因素,精品解析:广东省东莞市2019-2020学年高三下学期第一次统考(5月)模拟考试文科综合地理试题...
  8. Hadoop HIVE 基本数据类型
  9. UVA1091 WF4786 Barcodes【编码检查】
  10. python 交集_Python设置交集
  11. Python命令行参数选择
  12. 取(2堆)石子游戏 (hdu2177)
  13. mysql 外键 150_mysql之创建外键报150错误的处理方法
  14. InSAR数据处理软件简介
  15. java物流实时跟踪
  16. WPS做好一个PPT后,用microsoft系列的放映软件打开,出现空白页
  17. 计算机在英语中有哪些运用,计算机在英语教学中的运用
  18. 嵌入式系统开发笔记2:Linux的主流发行版本
  19. gstreamer(三) 常用命令集锦
  20. mysql5.7 优化 三

热门文章

  1. 【Mrak】C# 文本文件 ANSI编码格式 转 UTF8
  2. java科学计数字符串显示
  3. Improved Alpha-Tested Magnification for Vector Textures and Special Effects
  4. [转]double free or corruption (!prev): 0x080644c8 ***
  5. thinkphp日志泄漏漏洞_ThinkPHP框架通杀所有版本的一个SQL注入漏洞详细分析及测试方法...
  6. Python爬虫:Xpath语法笔记
  7. 51nod 1090 1267 【二分简单题】
  8. Cocos2d—声音API
  9. QTP中使用ExecuteFile加载vbs脚本
  10. 几个视频中行为识别的底层特征及代码