Vue+element-ui实现⼤⽂件分⽚上传,可控制同时上传的并发数
2020.10.23更新:这篇⽂章中的⽅法有⼀些缺陷。更好的解决⽅案请看
⽤过element-ui中Upload上传组件的都知道,他不⽀持⽂件分⽚上传,如果有⽂件分⽚的上传的需求时,只能去安装其他的上传组件,如vue-simple-uploader,webuploader等,我们公司的需求涉及到获取视频时长,上传⽂件名等等,如果换⼀个上传组件就要写两套获取⽂件信息的代码,不是很⽅便,查阅element-ui中Upload⽂档之后发现它有⼀个属性
设置这个属性之后,可以覆盖组件⾃带的上传⾏为,可以实现⾃⼰的⾃定义上传,具体实现如下:
//template
<el-upload
:http-request="chunkedUpload"
:ref="chunkedUpload"
:action="uploadUrl"
:data="uploadData"
:on-error="onError"
:before-remove="beforeRemove"
name="file">
//js部分
import chunkedUpload from'./chunkedUpload'
export default{
data(){
return{
uploadData:{
/
/这⾥⾯放额外携带的参数
},
//⽂件上传的路径
uploadUrl: v.BASE_API+'/oss/oss/uploadChunkFile',//⽂件上传的路径
chunkedUpload: chunkedUpload // 分⽚上传⾃定义⽅法,在头部引⼊了
}
},
methods:{
onError(err, file, fileList){
this.$s.chunkedUploadXhr.forEach(item =>{
item.abort()
})
this.$alert('⽂件上传失败,请重试','错误',{
confirmButtonText:'确定'
})
},
beforeRemove(file){
// 如果正在分⽚上传,则取消分⽚上传
if(file.percentage !==100){
this.$s.chunkedUploadXhr.forEach(item =>{
item.abort()
})
}
}
}
}
//chunkedUpload.js
import SparkMD5 from'spark-md5'
import axios from'axios'
import store from'@/store'
// 如果上传错误,获取报错信息
function getError(action, option, xhr){
let msg
sponse){
msg =`${ || sponse}`
}else sponseText){
msg =`${sponseText}`
}else{
msg =`fail to post ${action}${xhr.status}`
}
const err =new Error(msg)
err.status = xhr.status
err.url = action
return err
}
// 上传成功完成合并之后,获取服务器返回的信息
function getBody(xhr){
const text = sponseText || sponse
if(!text){
return text
}
try{
return JSON.parse(text)
}catch(e){
return text
}
}
// 分⽚上传的⾃定义请求,以下请求会覆盖element的默认上传⾏为
export default function upload(option){
if(typeof XMLHttpRequest ==='undefined'){
return
}
const spark =new SparkMD5.ArrayBuffer()// md5的ArrayBuffer加密类
const fileReader =new FileReader()// ⽂件读取类
const action = option.action // ⽂件上传上传路径
const chunkSize =1024*1024*30// 单个分⽚⼤⼩
let md5 =''// ⽂件的唯⼀标识
const optionFile = option.file // 需要分⽚的⽂件
let fileChunkedList =[]// ⽂件分⽚完成之后的数组
const percentage =[]// ⽂件上传进度的数组,单项就是⼀个分⽚的进度
// ⽂件开始分⽚,push到fileChunkedList数组中,并⽤第⼀个分⽚去计算⽂件的md5 for(let i =0; i < optionFile.size; i = i + chunkSize){
const tmp = optionFile.slice(i, Math.min((i + chunkSize), optionFile.size))
if(i ===0){
}
fileChunkedList.push(tmp)
}
// 在⽂件读取完毕之后,开始计算⽂件md5,作为⽂件唯⼀标识
spark.append(sult)
md5 = d()+new Date().getTime()
console.log('⽂件md5为--------', md5)
// 将fileChunkedList转成FormData对象,并加⼊上传时需要的数据
fileChunkedList = fileChunkedList.map((item, index)=>{
const formData =new FormData()
if(option.data){
// 额外加⼊外⾯传⼊的data数据
Object.keys(option.data).forEach(key =>{
formData.append(key, option.data[key])
})
// 这些字段看后端需要哪些,就传哪些,也可以⾃⼰追加额外参数
formData.append(option.filename, item, option.file.name)// ⽂件
formData.append('chunkNumber', index +1)// 当前⽂件块
formData.append('chunkSize', chunkSize)// 单个分块⼤⼩
formData.append('currentChunkSize', item.size)// 当前分块⼤⼩        formData.append('totalSize', optionFile.size)// ⽂件总⼤⼩
大文件发送formData.append('identifier', md5)// ⽂件标识
formData.append('filename', option.file.name)// ⽂件名
formData.append('totalChunks', fileChunkedList.length)// 总块数}
return{ formData: formData, index: index }
})
// 更新上传进度条百分⽐的⽅法
const updataPercentage=(e)=>{
let loaded =0// 当前已经上传⽂件的总⼤⼩
percentage.forEach(item =>{
loaded += item
})
e.percent = loaded / optionFile.size *100
}
/
/ 创建队列上传任务,limit是上传并发数
function sendRequest(chunks, limit =3){
return new Promise((resolve, reject)=>{
const len = chunks.length
let counter =0
let isStop =false
const start =async()=>{
if(isStop){
return
}
const item = chunks.shift()
console.log()
if(item){
const xhr =new XMLHttpRequest()
const index = item.index
// 分⽚上传失败回调
isStop =true
reject(e)
}
// 分⽚上传成功回调
if(xhr.status <200|| xhr.status >=300){
isStop =true
reject(getError(action, option, xhr))
}
if(counter === len -1){
// 最后⼀个上传完成
resolve()
}else{
counter++
start()
}
}
// 分⽚上传中回调
if(xhr.upload){
progress=function progress(e){
al >0){
e.percent = e.loaded / e.total *100
}
percentage[index]= e.loaded
console.log(index)
updataPercentage(e)
}
}
xhr.open('post', action,true)
if(option.withCredentials &&'withCredentials'in xhr){
xhr.withCredentials =true
}
const headers = option.headers ||{}
for(const item in headers){
if(headers.hasOwnProperty(item)&& headers[item]!==null){
xhr.setRequestHeader(item, headers[item])
}
}
/
/ ⽂件开始上传
xhr.send(item.formData)
//这⾥是把所有分⽚上传的xhr存到全局中,如果⽤户⼿动取消上传,或者上传出现错误,则要调⽤xhr.abort()把store中所有xhr的停⽌,不然⽂件还会继续上传
storemit('SET_CHUNKEDUPLOADXHR', xhr)
}
}
while(limit >0){
setTimeout(()=>{
start()
}, Math.random()*1000)
limit -=1
}
})
}
try{
// 调⽤上传队列⽅法等待所有⽂件上传完成
await sendRequest(fileChunkedList,3)
// 这⾥的参数根据⾃⼰实际情况写
const data ={
identifier: md5,
filename: option.file.name,
totalSize: optionFile.size
}
// 给后端发送⽂件合并请求
const fileInfo =await axios({
method:'post',
url:'/api/oss/oss/mergeChunkFile',
data: data
})
// 这个8200是我们oss存储成功的code,根据⾃⼰实际情况可以变
if(de ===8200){
const success =quest)
return
}
}catch(error){
}
}
}
chunkedUpload.js写的太乱了,后续有时间优化完再分享⼀次,可以把⼀些回调⽅法和属性设置写到外⾯去,这样就做到了通⽤性