场景: 需求是接口一次上传一个文件,前一个文件上传成功后再调下一个接口上传下一个文件。
el-upload本身就支持多文件上传。但是它是并发进行,例如:选择一千个文件后,是一千个文件自动立马并行调用一千个后端接口去上传。这样容易把服务器搞爆。
.
网上还有,将一千个文件放在一个接口里上传。这一样也容易把服务器搞爆 (具体看下方逻辑详解第4点)。
.
我采用的是一次选择一千个文件后, 递归先上传第一个文件,第一个接口上传成功或者失败后,再调后端第二个接口上传第二个文件,依次等待上传完一千个文件(具体看下方逻辑详解第4点)。 这样优缺点就是:上传时间比较长,需要等前一个传好才会进行下一个,但是这样服务器压力小不会爆掉。
主要逻辑详解:
1.属性:auto-upload=“false” ,设置fallse否后,就阻止了文件的立即上传功能了,变成手动或者调用接口上传。之前一次选择1000个后会立即调用1000个接口就是立即上传导致的。
.
2.属性:auto-upload=“false” ,设置fallse否后,原来的上传前和上传成功事件就都不会触发了,action好像也无效了,只会触发上传change事件、上传失败事件、和上传个数超限事件。
.
3.注意change事件,一次选了1000条后,change事件也会执行1000次(第一次是1个文件、第二次是两个文件、第1000次拿到1000个文件…),所以我在这里写了个防抖事件time。通过防抖,可以知道什么时候,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_order1" style="padding-top:100px;">
<!--
:auto-upload="false" 是否在选取文件后立即进行上传 false阻止自动上传 -- 且上传前和上传成功的事件都不会再触发 只会触发@change事件了
:http-request="uploadFile" 覆盖默认的上传行为,可以自定义上传的实现(this.$refs.upload1.submit() 会触发调用uploadFile函数)
-->
<el-upload ref="upload1" 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: -10px;line-height: 20px;">
将文件拖到此处,<em v-if="!disabledUpload">或点击上传(单个文件需小于100M,一次最多上传1000个pdf文件)</em><em v-else>(文件正在上传中,请等待...)</em>
</div>
</el-upload>
<div>
<!-- <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload2">手动点击上传</el-button> -->
<el-button v-if="showPercent" style="margin-left: 10px;" size="small" type="success" @click="submitAbort">取消后续文件上传</el-button>
</div>
<div style="color:orange;" v-if="showPercent">上传过程请勿刷新浏览器和跳转其他页面...</div>
<!-- 进度条 -->
<el-progress v-if="showPercent" :percentage="Number((percentNow*100/percentTotal).toFixed(0))"></el-progress>
</div>
</template>
<script>
import axios from 'axios'
export default {
data () {
return {
fileNum: '', // 单词递归上传的文件
upFileList: '',//需要依次上传的待传列表
percentTotal: 0,//总上传个数
percentNow: 0,//当前上传个数
showDesc: '',//结束文案
showPercent: false,//显示上传进度条
time: null,// change事件是否结束 是否可以直接调手动上传事件(目前设置1.5s)
disabledUpload: false,//正在上传中 禁止再次选择文件上传
fileData: {
},//上传参数
fileList1: [],
}
},
activated: {
// 对于每次进入页面想要刷新的数据,放在这里调用即可 例如 this.getList()
},
methods: {
// 超出限制个数提示
handleExceed (files, fileList) {
console.log('当前限制一次性最多上传1000个文件', files, fileList)
this.$message.warning("当前限制一次性最多上传1000个文件")
},
changeFile (file, fileList) {
this.disabledUpload = true
console.log('changeFile', file, fileList)
const isLt2M = file.size / 1024 / 1024 < 100
if (!isLt2M) {
this.$message.warning('上传文件大小不能超过 100M')
// return false // 这个return无效 故去掉
}
if (!(file.name.indexOf('.pdf') > -1)) {
this.$message.warning("当前仅支持pdf格式的文件上传")
// return false // 这个return无效 故去掉
}
// 符合条件的进入待传列表
this.upFileList = []
for (let x of fileList) {
if (x.raw && (x.name.indexOf('.pdf') > -1) && (x.size / 1024 / 1024 < 100)) {// 过滤掉非pdf 和小于100M的
this.upFileList.push(x.raw)
this.percentTotal = this.upFileList.length
this.percentNow = 0
this.showPercent = false
this.showDesc = ''
}
}
clearTimeout(this.time)
this.time = setTimeout(() => {
this.time = null
console.log('防抖 高频触发后n秒内只会执行一次 再次触发重新计时')
this.fnBegin()//说明此时change了所有文件了 可以上传了
}, 1500)
},
fnBegin () {
console.log('此时change了所有文件 开始上传', this.upFileList)
this.submitUpload2()
},
// 正式上传掉后端接口
submitUpload2 () {
if (this.upFileList.length > 0) {
this.showPercent = true
this.fileNum = new FormData() // new formData对象
this.fileNum.append('file', this.upFileList[0]) // append增加数据
this.fileNum.append('name', this.upFileList[0].name) // append增加数据
let _vm = this
axios({
url: '/chc-shop/api/v1/accident/szcp/electronicfile/upload',
headers: {
"Content-Type": "multipart/form-data",
},
method: "post",
data: this.fileNum,
})
.then(res2 => {
// 每次上传当前一个后 不论成功失败就删除当前这个--如果上传失败想继续传当前这个 就把这两行注释掉
this.percentNow = this.percentNow + 1
this.upFileList.shift()
console.log('上传返回', res2)
if (res2.data.success) {
// this.$message({
// message: "上传成功",
// type: 'success'
// })
// 进行递归 上传下一个
this.submitUpload2()
} else {
_vm.$message({
message: res2.data.return_message || '上传失败',
type: "error",
})
// 进行递归 上传下一个
this.showDesc = '上传结束,部分文件上传失败'
this.submitUpload2()
}
})
.catch(error => {
console.log(error)
_vm.$message({
message: error || '上传失败',
type: "error",
})
// 每次上传当前一个后 不论成功失败就删除当前这个--如果上传失败想继续传当前这个 就把这两行注释掉
this.percentNow = this.percentNow + 1
this.upFileList.shift()
// 进行递归 上传下一个
this.showDesc = '上传结束,部分文件上传失败'
this.submitUpload2()
})
} else {
this.disabledUpload = false
this.showPercent = false
this.upFileList = [] //清空待传列表
this.$refs.upload1.clearFiles()
this.fileList1 = []
if ((this.percentNow == this.percentTotal) && this.percentTotal) {
this.$message.success(this.showDesc ? this.showDesc : '已全部上传成功!')
this.percentTotal = 0
this.percentNow = 0
this.showDesc = ''
} else if ((this.percentNow == this.percentTotal) && this.percentTotal == 0) {
this.$message.warning('请先选择文件!')
this.percentTotal = 0
this.percentNow = 0
} else {
this.$message.success('已部分上传成功,且取消后续文件上传!')
this.percentTotal = 0
this.percentNow = 0
}
return false
}
},
// 终止后需上传
submitAbort () {
this.showPercent = false
// .abort()不生效,故自己直接将this.upFileList置空 那么就不会走到递归了 就制止后续的上传了
this.upFileList = []
// this.upFileList.forEach(ele => {
// this.$refs.upload1.abort(ele)
// })
// this.$refs.upload1.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>