一、需求描述:在页面上点击按钮弹出选择电脑文件的界面,可以一次性选择多个文件一起上传到服务器上,并把上传成功的文件展示在页面上。
·
二、问题阐述: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_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>
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题