目录
- 一、需求描述:
- 二、问题阐述:
- 三、解决方法:
- 四、实现思路:
- 主要逻辑详解:
- 总结
一、需求描述:
在页面上点击按钮弹出选择电脑文件的界面,可以一次性选择多个文件一起上传到服务器上,并把上传成功的文件展示在页面上。
二、问题阐述:
el-upload是支持多文件上传的,但是是同步进行的,你点击上传按钮,选择了多个文件后点击确定,会同时调用上传文件的接口,这样很容易导致服务器奔溃,导致接口报错。
三、解决方法:
为了解决这一难题,本文采用递归的方法来实现精准上传文件。
四、实现思路:
递归上传是指:你选择了n个文件点击确定后,第一个接口上传成功或者失败后,再调用第二个接口上传第二个文件,依次等待上传完所有文件,这样做法可以大大减轻服务器的压力,就是上传时间会比较长。上传效果请看下方动态示例图
主要逻辑详解:
1、首先需要先取消组件的自动上传操作,把属性auto-upload的值设置为false,就禁用了文件的自动上传功能了,把自动转化为手动,之所以选择多个文件会并行调用上传接口,就是这个属性导致的。
2、属性auto-upload设置为false之后,action的属性就失效了,只会触发change事件、上传失败on-error事件以及上传个数限制before-upload事件。
3、关键点就在于change事件,选择了多少个文件,这个事件就会执行多少次,例如你选择了100个文件,change事件就会循环执行100次,为了保证上传的准确性,这里顺手写了防抖事件,通过防抖,可以清楚的知道,上传进度到哪了,change事件什么时候结束,在这里就可以开始调用后端给我们的上传接口了。
4.因为我是想减少服务器压力,既在上一个文件上传结束后,再去上传下一个文件。故,我采用了递归一次传一个(递归逻辑在submitUpload2事件里)。同样的,如果你想只调用一次接口,将1000个文件传给后端,那么你就将submitUpload2递归事件给修改调,改成调一次接口,传所有file文件数组即可(具体看你们后端的数据结构)。
5.因为是上传多个文件,时间肯定长,如果你不想继续传递后续文件,就调用submitAbort即可。但是element文档说的abort()取消上传事件不生效。所以我是直接将上传列表给置空了,所以递归也就结束了就不传递了。
6.同样因为上传时间长,我就设置了进度条(当前已上传的个数/所有需要上传个数)。但是你如果切换到其他页面的话,再切换到当前上传vue页面,因为生命周期原因导致看不到上传进度。所以可以将这个上传vue页面使用keep-alive进行路由缓存,这样除了刷新浏览器外,切换到当前页还是会读缓存会看到上传进度。(如果你既想要缓存进度条,又想要刷新页面的list等某些数据,那么你缓存此页面,然后在activated单独调获取list数据方法即可)。
7.因为我的需要是,选择文件成功后,默认去上传,所以我是在防抖事件后就去调递归上传了。正常情况下,其实需要点击这个按钮然后去上传的,既手动点击上传(被注释掉了)<!-- <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload2">手动点击上传</el-button> -->
以下代码仅供参考,可以直接复制使用(注意将axios的接口转化为后端提供的接口即可)!
<template> | |
<div class="group_insurance_order" style="padding-top:100px;"> | |
<!-- | |
:auto-upload="false" 是否在选取文件后立即进行上传 false阻止自动上传 -- 且上传前和上传成功的事件都不会再触发 只会触发 事件了 | |
:http-request="uploadFile" 覆盖默认的上传行为,可以自定义上传的实现(this.$refs.upload.submit() 会触发调用uploadFile函数) | |
--> | |
<el-upload ref="upload" class="upload-demo" action="/chc-shop/api/v1/accident/szcp/electronicfile/upload" accept=".pdf" :disabled="disabledUpload" :auto-upload="false" :on-change="changeFile" :on-error='fileErr' :on-exceed="handleExceed" :file-list="fileList1" :before-upload="beforeAvatarUpload" :on-success="msgSuccessOne" :data="fileData" list-type="picture" drag :show-file-list="false" :multiple="true" :limit="1000"> | |
<i class="el-icon-upload"></i> | |
<div class="el-upload__text" style="margin-top: -px;line-height: 20px;"> | |
将文件拖到此处,<em v-if="!disabledUpload">或点击上传(单个文件需小于M,一次最多上传1000个pdf文件)</em><em v-else>(文件正在上传中,请等待...)</em> | |
</div> | |
</el-upload> | |
<div> | |
<!-- <el-button style="margin-left:px;" size="small" type="success" ="submitUpload2">手动点击上传</el-button> --> | |
<el-button v-if="showPercent" style="margin-left:px;" size="small" type="success" ="submitAbort">取消后续文件上传</el-button> | |
</div> | |
<div style="color:orange;" v-if="showPercent">上传过程请勿刷新浏览器和跳转其他页面...</div> | |
<!-- 进度条 --> | |
<el-progress v-if="showPercent" :percentage="Number((percentNow*/percentTotal).toFixed(0))"></el-progress> | |
</div> | |
</template> | |
<script> | |
import axios from 'axios' | |
export default { | |
data () { | |
return { | |
fileNum: '', // 单词递归上传的文件 | |
upFileList: '',//需要依次上传的待传列表 | |
percentTotal:,//总上传个数 | |
percentNow:,//当前上传个数 | |
showDesc: '',//结束文案 | |
showPercent: false,//显示上传进度条 | |
time: null,// change事件是否结束 是否可以直接调手动上传事件(目前设置.5s) | |
disabledUpload: false,//正在上传中 禁止再次选择文件上传 | |
fileData: { | |
},//上传参数 | |
fileList: [], | |
} | |
}, | |
activated: { | |
// 对于每次进入页面想要刷新的数据,放在这里调用即可 例如 this.getList() | |
}, | |
methods: { | |
// 超出限制个数提示 | |
handleExceed (files, fileList) { | |
console.log('当前限制一次性最多上传个文件', files, fileList) | |
this.$message.warning("当前限制一次性最多上传个文件") | |
}, | |
changeFile (file, fileList) { | |
this.disabledUpload = true | |
console.log('changeFile', file, fileList) | |
const isLtM = file.size / 1024 / 1024 < 100 | |
if (!isLtM) { | |
this.$message.warning('上传文件大小不能超过M') | |
// return false // 这个return无效 故去掉 | |
} | |
if (!(file.name.indexOf('.pdf') > -)) { | |
this.$message.warning("当前仅支持pdf格式的文件上传") | |
// return false // 这个return无效 故去掉 | |
} | |
// 符合条件的进入待传列表 | |
this.upFileList = [] | |
for (let x of fileList) { | |
if (x.raw && (x.name.indexOf('.pdf') > -) && (x.size / 1024 / 1024 < 100)) {// 过滤掉非pdf 和小于100M的 | |
this.upFileList.push(x.raw) | |
this.percentTotal = this.upFileList.length | |
this.percentNow = | |
this.showPercent = false | |
this.showDesc = '' | |
} | |
} | |
clearTimeout(this.time) | |
this.time = setTimeout(() => { | |
this.time = null | |
console.log('防抖 高频触发后n秒内只会执行一次 再次触发重新计时') | |
this.fnBegin()//说明此时change了所有文件了 可以上传了 | |
},) | |
}, | |
fnBegin () { | |
console.log('此时change了所有文件 开始上传', this.upFileList) | |
this.submitUpload() | |
}, | |
// 正式上传掉后端接口 | |
submitUpload () { | |
if (this.upFileList.length >) { | |
this.showPercent = true | |
this.fileNum = new FormData() // new formData对象 | |
this.fileNum.append('file', this.upFileList[]) // append增加数据 | |
this.fileNum.append('name', this.upFileList[].name) // append增加数据 | |
let _vm = this | |
axios({ | |
url: '/chc-shop/api/v/accident/szcp/electronicfile/upload', | |
headers: { | |
"Content-Type": "multipart/form-data", | |
}, | |
method: "post", | |
data: this.fileNum, | |
}) | |
.then(res => { | |
// 每次上传当前一个后 不论成功失败就删除当前这个--如果上传失败想继续传当前这个 就把这两行注释掉 | |
this.percentNow = this.percentNow + | |
this.upFileList.shift() | |
console.log('上传返回', res) | |
if (res.data.success) { | |
// this.$message({ | |
// message: "上传成功", | |
// type: 'success' | |
// }) | |
// 进行递归 上传下一个 | |
this.submitUpload() | |
} else { | |
_vm.$message({ | |
message: res.data.return_message || '上传失败', | |
type: "error", | |
}) | |
// 进行递归 上传下一个 | |
this.showDesc = '上传结束,部分文件上传失败' | |
this.submitUpload() | |
} | |
}) | |
.catch(error => { | |
console.log(error) | |
_vm.$message({ | |
message: error || '上传失败', | |
type: "error", | |
}) | |
// 每次上传当前一个后 不论成功失败就删除当前这个--如果上传失败想继续传当前这个 就把这两行注释掉 | |
this.percentNow = this.percentNow + | |
this.upFileList.shift() | |
// 进行递归 上传下一个 | |
this.showDesc = '上传结束,部分文件上传失败' | |
this.submitUpload() | |
}) | |
} else { | |
this.disabledUpload = false | |
this.showPercent = false | |
this.upFileList = [] //清空待传列表 | |
this.$refs.upload.clearFiles() | |
this.fileList = [] | |
if ((this.percentNow == this.percentTotal) && this.percentTotal) { | |
this.$message.success(this.showDesc ? this.showDesc : '已全部上传成功!') | |
this.percentTotal = | |
this.percentNow = | |
this.showDesc = '' | |
} else if ((this.percentNow == this.percentTotal) && this.percentTotal ==) { | |
this.$message.warning('请先选择文件!') | |
this.percentTotal = | |
this.percentNow = | |
} else { | |
this.$message.success('已部分上传成功,且取消后续文件上传!') | |
this.percentTotal = | |
this.percentNow = | |
} | |
return false | |
} | |
}, | |
// 终止后需上传 | |
submitAbort () { | |
this.showPercent = false | |
// .abort()不生效,故自己直接将this.upFileList置空 那么就不会走到递归了 就制止后续的上传了 | |
this.upFileList = [] | |
// this.upFileList.forEach(ele => { | |
// this.$refs.upload.abort(ele) | |
// }) | |
// this.$refs.upload.abort() | |
// this.$message.warning('已取消后续文件上传!') | |
}, | |
fileErr (err, file, fileList) { | |
this.$message({ | |
message: file.name + '上传失败', | |
type: "error", | |
}) | |
}, | |
// 这两个事件不会再触发--因为阻止了自动上传 | |
beforeAvatarUpload (file) { | |
console.log('上传文件前', file) | |
}, | |
msgSuccessOne (data, file, fileList) { | |
console.log('成功', file) | |
}, | |
}, | |
}; | |
</script> |