大文件切片上传
封装成通用组件:
<template>
<div id="app">
<!-- 上传组件 -->
<el-upload
action=""
:auto-upload="false"
:show-file-list="false"
:on-change="handleChange">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">{{ placeholder }}</div>
</el-upload>
<el-dialog
title="提示"
:visible.sync="dialog"
width="400px"
center>
<div class="display-flex fd-column ai-center">
<p class="mt-0">关闭弹窗后可继续上传,不推荐关闭,以免文件传输失败</p>
<el-progress type="circle" :percentage="Number(percent.toFixed())"></el-progress>
<div class="mt-20">
<el-button type="primary" size="mini" @click="handleClickBtn">{{ upload | btnTextFilter}}</el-button>
<el-button type="warning" size="mini" @click="handleClose">取消上传</el-button>
</div>
</div>
</el-dialog>
<div v-for="(item, index) in fileList" :key="index" class="display-flex ai-center jc-space-between fs-14 mt-6 cursor-pointer" :style="{width: fileWidth}">
<div class="display-flex ai-center" style="width: 90%;">
<i class="el-icon-document"></i>
<span class="ml-4 text-ellipsis" style="width: 90%;">{{item.name}}</span>
</div>
<i class="el-icon-close" @click="delFile(item)"></i>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'BigFileUpload',
props: {
placeholder: {
type: String,
default () {
return '可上传多个文件,不限制格式'
}
},
folder: {
type: String,
default () {
return 'test'
}
},
fileWidth: {
type: String,
default () {
return '300px'
}
}
},
filters: {
btnTextFilter (val) {
return val ? '暂停上传' : '继续上传'
}
},
watch: {
fileList: {
handler: function (value) {
this.$emit('change', value)
},
deep: true
}
},
data () {
return {
dialog: false,
fileList: [],
index: 0, // 当前传输位置
uploadId: null,
totalChunks: 0,
percent: 0,
upload: false,
percentCount: 0,
chunkList: []
}
},
methods: {
handleClose () {
this.$confirm('此操作将取消并关闭上传此文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const data = {
folder: this.folder,
uploadId: this.uploadId
}
axios.delete('/big/file/delete/oss/part', {
data: data
}).then(() => {
this.$message.success('取消成功')
this.upload = false
this.dialog = false
})
}).catch(() => {
});
},
delFile (file) {
this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const data = {
filenames: [file.url],
folder: this.folder
}
axios.delete('/big/file/delete/oss/file', {
data: data
}).then(() => {
this.fileList = this.fileList.filter(item => item.url !== file.url)
this.$message.success('删除成功')
})
}).catch(() => {
});
},
handleChange (file) {
this.upload = true
this.percent = 0
this.percentCount = 0
this.index = 0
this.chunkList = []
this.filename = file.name
axios.post('/big/file/initUpload', {
filename: this.filename,
folder: this.folder
}).then(res => {
this.dialog = true
this.uploadId = res.data.data
// 获取文件并转成 ArrayBuffer 对象
const fileObj = file.raw
// 将文件按固定大小(5M)进行切片,注意此处同时声明了多个常量
const chunkSize = 5242880;
const chunkList = []; // 保存所有切片的数组
const chunkListLength = Math.ceil(fileObj.size / chunkSize); // 计算总共多个切片
// 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
let curChunk = 0 // 切片时的初始位置
for (let i = 0; i < chunkListLength; i++) {
const item = {
index: i,
chunk: fileObj.slice(curChunk, curChunk + chunkSize)
}
curChunk += chunkSize
chunkList.push(item)
}
this.totalChunks = chunkListLength
this.chunkList = chunkList // sendRequest 要用到
this.sendRequest()
})
},
// 发送请求
sendRequest () {
const requestList = [] // 请求集合
if (this.index) {
this.chunkList = this.chunkList.filter(item => item.index > this.index)
}
this.chunkList.forEach(item => {
const fn = () => {
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('file', item.chunk)
formData.append('index', item.index)
formData.append('uploadId', this.uploadId)
return axios({
url: '/big/file/uploadChunk',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
Authorization: this.$store.state.token,
tenant_id: this.$cookies.get('tenant_id') || undefined
},
timeout: 100000,
data: formData
}).then(res => {
if (res.data.code === 200) {
resolve(res)
this.index = item.index
if (this.percentCount === 0) { // 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
this.percentCount = 100 / this.chunkList.length
}
this.percent += this.percentCount // 改变进度
} else {
reject(res)
}
}).catch((err) => {
reject(err)
this.upload = false
})
})
}
requestList.push(fn)
})
console.log(requestList, 11111111111111)
let i = 0 // 记录发送的请求个数
const complete = () => {
axios({
url: `/big/file/mergeFile?uploadId=${this.uploadId}`,
method: 'post',
headers: {
Authorization: this.$store.state.token,
tenant_id: this.$cookies.get('tenant_id') || undefined
}
}).then(res => {
this.fileList.push({
name: this.filename,
url: res.data.data
})
this.dialog = false
})
}
const send = async () => {
if (!this.upload) return
if (i >= requestList.length) {
// 发送完毕
complete()
return
}
await requestList[i]()
i++
send()
}
send() // 发送请求
},
// 按下暂停按钮
handleClickBtn () {
this.upload = !this.upload
// 如果不暂停则继续上传
if (this.upload) this.sendRequest()
}
}
}
</script>
<style scoped lang="scss">
</style>
引入使用:
import BigFileUpload from '@/components/BigFileUpload';
<big-file-upload folder="report" @change="data => drawerAddForm.file = data"></big-file-upload>