element文件上传断点续传
文件首次上传
断点续传内容展示
根据自己公司应用场景实现功能,断点续传组件代码如下
<template>
<div class="upload">
<el-upload class="upload-demo" action="" :on-exceed="handleExceed" :before-remove="beforeRemove"
:before-upload="handlePreview" :accept="parameterObj.accept" :file-list="fileList" :limit="parameterObj.limit"
:list-type="parameterObj.type" :show-file-list="parameterObj.showFile"
:http-request="handleFile" ref="uploadRef">
<slot name="button"></slot>
</el-upload>
<el-progress :percentage="percentage" style="width:120px;"
:style="[{ opacity: percentage != 0 ? 1 : 0 }]"></el-progress>
</div>
</template>
<script>
import Axios from 'axios'
// 这是生成md5字符串的文件
// 需要 npm install crypto
import crypto from 'crypto'
// inquireFileMd5 后端校验文件是否需要断点续传
// FileFinish 上传成功需要调用接口
import { inquireFileMd5, FileFinish } from '@/api/common/index'
export default {
name: "componentsUpload",
data() {
return {
// 获取本地Storage当前系统
systemText: sessionStorage.getItem('systemCode'),
// 上传文件内容
paths: [],
// 当前续传分段
sublevel: 0,
// 需要续传的总数
total: 0,
// 进度条
percentage: 0,
// 1 M 分段续传
chunkSize: 1024 * 1024 * 1,
// 后端返回的文件续传内容
sequelArr: [],
}
},
// vue2 model方法配置
model: {
prop: 'dialogVisible',
// 自定义的方法
event: 'change'
},
methods: {
/***
* 文件续传上传请求
* @param { FormData } formData
* @param { file } file
* ***/
UrlUpload(formData, file, isUlr = true) {
this.sublevel++
return new Promise((resolve, reject) => {
Axios.post(process.env.VUE_APP_IMG + '/public/basic/breakpointResumeUpload', formData)
.then((response) => {
if (response.data.success) {
--this.total
if (isUlr) {
this.breakpointUpload(file)
const { chunkSize } = this
var size = file.size; //总大小
//计算每一篇的起始位置和结束位置
var start = this.sequelArr.length === 0 ? this.sublevel * chunkSize : this.sequelArr[this.sublevel] * chunkSize
// var start = this.sublevel * chunkSize;
var end = Math.min(size, start + chunkSize);
var process = ((end / size) * 100);
this.percentage = Math.ceil(process) === 0 ? 0 : Math.ceil(process) - 1
}
resolve(response.data)
} else {
this.$message({ message: "分片上传失败", type: 'fail' })
reject(err)
}
})
.catch((err) => {
console.log(err)
})
})
},
// 处理切片函数
breakpointUpload(file) {
const { chunkSize } = this
var size = file.size; //总大小
var totalChunks = Math.ceil(size / chunkSize);
//计算每一篇的起始位置和结束位置
var start = this.sequelArr.length === 0 ? this.sublevel * chunkSize : this.sequelArr[this.sublevel] * chunkSize
// var start = this.sublevel * chunkSize;
var end = Math.min(size, start + chunkSize);
// console.log(end/size);
var fileData = file.slice(start, end)
//文件分块上传携带参数
let formData = new FormData();
formData.append('chunkSize', chunkSize);
formData.append('name', file.name);
formData.append('size', size) //总的大小
formData.append('chunks', totalChunks)
// 判断是断点续传还是第一次上传
this.sequelArr.length === 0 ? formData.append('chunk', this.sublevel) : formData.append('chunk', this.sequelArr[this.sublevel])
formData.append('md5', this.createMd5(file.name))
formData.append('file', fileData) //分段文件
formData.append('system', this.system) //分段文件
if (this.sequelArr.length === 0) {
if (this.sublevel === totalChunks) {
// 当请求最后一条切片上传完毕
if (this.total === 0) {
FileFinish({ name: file.name, system: this.system }).then(res => {
this.$message({ message: "上传成功", type: 'success' })
res.raw = URL.createObjectURL(file)
this.paths.push(res)
this.percentage = 0
}).catch((err) => {
console.log(err);
this.$message.error({ message: "上传失败" })
})
}
return
}
} else {
if (!this.sequelArr[this.sublevel]) {
if (this.total === 0) {
FileFinish({ name: file.name, system: this.system }).then(res => {
this.$message({ message: "上传成功", type: 'success' })
res.raw = URL.createObjectURL(file)
this.paths.push(res)
this.percentage = 0
}).catch((err) => {
console.log(err);
this.$message.error({ message: "上传失败" })
})
}
return
}
}
// 发送切片上传请求
this.UrlUpload(formData, file)
},
// 同时发送三次请求提高文件上传速度
uploadBySplit(file) {
// 三条请求提供文件上传
for (let index = 0; index < 3; index++) {
this.breakpointUpload(file)
}
},
// 根据文件名称生成md5字符串
createMd5(content) {
let md5 = crypto.createHash('md5')
md5.update(content)
return md5.digest('hex')
},
/***文件上传触发
* @param { file } file
* ***/
handleFile(file, fileList) {
this.percentage = 0
if (this.total) {
this.$message({
message: '文件上传中请勿再次上传',
type: 'warning'
});
return
}
let content = file ? file.file : ""
// 文件小于15M 不分片
if (file.file.size < 1024 * 1024 * 15) {
// 生成FormData格式
let formData = new FormData();
// 添加参数
formData.append('file', content)
formData.append('system', this.system)
// 发送请求
Axios.post(this.urlData, formData)
.then((response) => {
if (response.data.code === 200) {
this.$message({
message: "上传成功",
type: "success"
});
this.percentage = 0
// 页面回显
this.paths.push(response.data.data)
response.data.data.raw = URL.createObjectURL(content)
} else {
this.$message({
message: response.data.msg,
type: "warning"
});
}
})
.catch((err) => {
console.log(err)
})
return
} else {
// 大于15M
// 根据名称字符串生成md5字符串
const md5Name = this.createMd5(content.name)
// 查询是否需要文件续传
inquireFileMd5({ md5: md5Name, fileName: content.name,system:this.system }).then(res => {
// 文件不存在使用切片上传
this.sublevel = 0
if (!res.continue) {
this.sequelArr = []
var size = content.size; //总大小
// 计算出总条数
this.total = Math.ceil(size / this.chunkSize);
this.uploadBySplit(content)
} else {
if (res.list) {
// 需要文件续传
this.sequelArr = res.list
this.total = res.list.length;
this.uploadBySplit(content)
} else {
FileFinish({ name: content.name, system: this.system }).then(succeed => {
this.$message({ message: "上传成功", type: 'success' })
succeed.raw = URL.createObjectURL(content)
this.paths.push(succeed)
this.percentage = 0
}).catch((err) => {
console.log(err);
this.$message.error({ message: "上传失败" })
})
}
}
})
}
},
// 限制文件格式
handlePreview(file) {
let documentData = file.name.split('.')
let caudal = documentData[documentData.length - 1]
let accept = this.parameterObj.accept.split(',')
let isJPGTY = accept.filter(item => {
return item.includes(caudal);
})
const isJPG = (file.name.length < 90);
const isLt2M = (file.size < this.parameterObj.fileSize)
if (!isJPG) {
this.$message({
message: "文件名称过长",
type: "warning"
});
return false
}
if (!isLt2M) {
this.$message({
message: this.parameterObj.message,
type: "warning"
});
return false
}
if (isJPGTY.length === 0) {
this.$message({
message: '请上传正确的文件',
type: "warning"
});
return false
}
return isJPG && isLt2M && isJPGTY.length > 0;
},
// 文件超出个数限制时的钩子
handleExceed(files, fileList) {
this.$message.warning(
`当前限制选择 ${this.parameterObj.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`
);
},
// 文件删除
beforeRemove(file, fileList) {
this.paths.map((item, index) => {
if (file.response && item.name == file.response.data.name) {
this.paths.splice(index, 1)
}
if (item.name == file.name) {
this.paths.splice(index, 1)
}
})
},
// 文件返回给父组件
uploadData() {
this.$emit("uploadClick", this.paths)
},
fileShow() {
if (this.fileList.length > 0) {
this.paths = JSON.parse(JSON.stringify(this.fileList));
}
},
},
props: {
// 文件回显
fileList: {
type: Array,
default() {
return []
}
},
parameterObj: {
type: Object,
default() {
return {
type: 'picture-card',
accept: '.png,.PNG,.jpg,.JPG,.jpeg,.JPEG,.mp4,.MP4,.avi,.AVI,.wmv,.WMV,.mov,.MOV,.rmvb,.RMVB,.flv,.FLV,.f4v,.F4V,.3gp,3GP',
limit: 100,
fileSize: 1024 * 1024 * 100,
message: '上传文件不得大于100M',
showFile: false
}
}
},
// 需要携带的参数
paramsData: {
type: Object
},
// 请求接口
urlData: {
type: String,
default: process.env.VUE_APP_IMG + '/public/basic/upload'
},
},
watch: {
fileList: {
handler(newName, oldName) {
this.fileShow()
},
deep: true
},
paths: {
handler(newName, oldName) {
this.$emit('change', newName)
},
deep: true
}
},
computed: {
system() {
let text = ''
switch (this.systemText) {
case '/basic':
text = 'basic_system'
break;
case '/operation':
text = 'oam_system'
break;
case '/emergency':
text = 'eme_system'
break;
default:
text = 'system'
break;
}
return text
}
},
mounted() {
this.fileShow()
}
}
</script>
父组件应用
<template>
<div class="fileList-img">
<div v-for="(item, index) in fileData" :key="index" class="el-upload el-upload--picture-card picture">
<div class="img-icon">
<i class="el-icon-upload-success el-icon-check"></i>
</div>
<i class="el-icon-close" @click="fileClick(item, index)"></i>
// 后端给前端返回1为image 2为video
<el-image v-if="item.type === '1'" :src="item.url || item.raw" :preview-src-list="srcList" class="image" />
<template v-else>
<video :src="item.url || item.raw" style="height: 100%" controls="controls" preload="auto"
class="image"></video>
</template >
</div>
<staff-upload ref="uploadComponents" v-model="fileData" :file-list="fileList"
@uploadClick="uploadClick">
<template v-slot:button>
<i class="el-icon-plus avatar-uploader-icon"></i>
</template>
</staff-upload>
</div>
</template>
<script>
import StaffUpload from './upload'
export default {
components: {
StaffUpload,
},
data(){
return {
// 绑定文件上传组件的model
fileData: [],
// 文件需要回显的内容
fileList: [],
}
},
methods: {
// 获取文件内容
uploadClick(content) {
this.form.fileImg = content
},
// 删除文件
fileClick({ name, type }, key) {
this.fileData.splice(key, 1)
this.$refs.uploadComponents.$refs.uploadRef.handleRemove({ name, type })
},
}
}
</script>