断点续传,分片上传
程序员文章站
2022-05-17 21:03:48
...
<!--
* @Description: 上传视频组件 只封装了允许上传一个视频
* @Author zhangyu
* @Date 2019-09-03 15:55:42
* @Params {}
-->
<template>
<div class="f-upload">
<el-upload
class="f-upload__video"
:show-file-list="false"
:action="params.action"
:http-request="(params) => uploadFile(params)">
<video-player
v-if="false"
class="video-player vjs-custom-skin"
ref="videoPlayer"
:playsinline="true"
:options="playerOptions">
<i class="el-icon-error video-delete-icon" @click="handleDelete(index-1, $event)"></i>
</video-player>
<i class="el-icon-plus video-uploader-icon"></i>
</el-upload>
<div>
<ul class="f-upload__hit">
注意:
<li>请选择本地视频上传</li>
<li>大小不能超过{{params.size}}M</li>
<li>视频格式为MP4</li>
</ul>
</div>
</div>
</template>
<script>
import { hexMd5 } from '@/utils/md5'
import { playerOptions, ConstantField } from '@/utils/constantField'
import moment from 'moment'
const DEFAULT = {
action: '', // 必须,文件提交路由 ****暂未使用
readonly: false, // 是否只读
size: 100, // 最大上传大小,单位 M
limit: 1,
value: [] // 图片url
}
export default {
name: 'UploadVideo',
props: {
config: {
type: Object,
required: true
},
name: { // 字段名
type: String,
required: true
}
},
data () {
return {
playerOptions,
params: null,
fileForm: {
action: '',
date: '',
fileMd5: '',
index: 0,
md5: '',
size: '',
total: 0
}
}
},
computed: {
imageSrcs: function () {
let arr = []
this.params.value.forEach(function (val) {
arr.push('${ConstantField.IMAGE_API}${val}')
})
if (arr.length > 0) {
return arr
} else {
return []
}
},
mimeTypes: function () {
return ConstantField.MIME_TYPE.VIDEO.extensions
},
extension: function () {
let str = null
const val = this.mimeTypes
if (val && val.length > 0) {
str = ''
for (let i = 0; i < val.length; i++) {
str += ` .${val[i]} `
if (i < val.length - 1) {
str += '|'
}
}
}
return str
}
},
watch: {
config: {
handler: function () {
this.setParams()
},
deep: true,
immediate: true
}
},
methods: {
// 将接收的config数据与默认配置合并
setParams () {
this.params = Object.assign({}, DEFAULT, this.config)
},
async beforeUpload (file) {
const name = file.name
const arr = name.split('.')
if (this.mimeTypes.indexOf(arr.pop()) === -1) {
this.$alert(`当前仅支持上传视频扩展名:${this.extension}`, '上传视频失败')
return false
}
if (this.size) {
let isLimit = file.size / 1024 / 1024 < this.params.size
if (!isLimit) {
this.$message.error(`上传视频大小不能超过 ${this.params.size}MB!`)
return false
}
}
this.getFileMD5(file)
// const formVerify = new FormData()
// const fileRes = await this.$http.attachmentApi.uploadFile(formVerify)
// console.log('fileRes', fileRes)
},
async uploadFile (params) {
const file = params.file
await this.beforeUpload(file)
},
// 获取整个文件的hash
async getFileMD5 (file) {
const reader = new FileReader()
const context = this
reader.onload = async function (e) {
const bolb = e.target.result
const fileHash = hexMd5(bolb)
context.fileForm.fileMd5 = fileHash
console.log('整个文件hash ', fileHash)
const fileRes = await context.$http.attachmentApi.isUpload(fileHash)
console.log('dasd', fileRes)
if (fileRes.code === 200) {
context.fileForm.uuid = fileRes.data.uuid
switch (fileRes.data.status) {
case ConstantField.NOT_UPLOAD:
case ConstantField.PART_UPLOAD:
context.handleSuccess(file)
break
case ConstantField.ALREADY_UPLOAD:
context.$message.info('该文件已经上传,请不要上传重复文件')
break
default:
context.$message.error('服务器错误无法上传')
break
}
}
}
reader.readAsBinaryString(file)
},
handleError (msg) {
this.$message.error(msg || '上传视频失败,请重试')
},
handleDelete (index, event) {
event.stopPropagation()
this.$confirm('确定删除该视频?', '提示', {
type: 'warning'
}).then(() => {
this.params.value.splice(index, 1)
this.handleChange()
this.$message.success('删除成功!')
}).catch(() => {
// 取消删除
})
},
handleChange () {
this.$emit('change-event', {
name: this.name,
value: this.params.value
})
},
/**
* 分片获取hash 上传 ConstantField.MIME_TYPE.SLICESIZE规定按5M大小拆分
*/
async handleSuccess (file) {
const size = file.size // 总大小
const shardCount = Math.ceil(size / ConstantField.MIME_TYPE.SLICESIZE) // 总片数
this.uploadSliceFile(file, 0, 1, shardCount)
// const res = await this.$http.attachmentApi.uploadFile(form)
// if (res.code === ConstantField.RQ_SUCCESS) {
// this.params.value[index] = res.data.url
// this.handleChange()
// this.$message.success('上传成功!')
// } else {
// this.handleError(res.msg)
// }
},
/**
*
* @param file
* @param filemd5 整个文件的md5
* @param i 文件第i个分片
* @param type type 1为检测;2为上传
*/
async uploadSliceFile (file, i, type, shardCount) {
const date = moment().format('YYYYMMDD')
// 计算每一片的起始与结束位置
const start = i * ConstantField.MIME_TYPE.SLICESIZE
const end = Math.min(file.size, start + ConstantField.MIME_TYPE.SLICESIZE)
// const form = new FormData()
// if (type === 1) {
// form.append('action', 'check') // 检测分片是否上传
// } else {
// form.append('action', 'upload') // 直接上传分片
// form.append('data', file.slice(start, end)) // slice方法用于切出文件的一部分
// }
// form.append('filemd5', this.fileForm.fileMd5)
this.fileForm.action = 'upload'// 直接上传 check-校验分片
this.fileForm.date = date
// this.fileForm.name = file.name
this.fileForm.size = file.size
this.fileForm.total = shardCount
this.fileForm.index = i + 1
// 按大小切割文件段
const data = file.slice(start, end)
const reader = new FileReader()
reader.onload = async (e) => {
const bolb = e.target.result
const sliceHash = hexMd5(bolb)
this.fileForm.md5 = sliceHash
const formData = new FormData()
// formData.append('uuid', this.fileForm.uuid)
// formData.append('fileMd5', this.fileForm.fileMd5)
// formData.append('action', 'upload')
// formData.append('date', date)
// formData.append('name', file.name)
// formData.append('size', file.size)
// formData.append('total', shardCount) // 总片数
// formData.append('index', i + 1) // 当前是第几片
// formData.append('fileUploadDTO', this.fileForm)
formData.append('file', file)
console.log('0628----shardCount', shardCount)
console.log('0628----i', i)
const res = await this.$http.attachmentApi.uploadFile(formData)
if (res.code === ConstantField.RQ_SUCCESS) {
// this.params.value[index] = res.data.url
if ((i + 1) < shardCount) {
this.uploadSliceFile(file, i + 1, 1, shardCount)
}
} else {
this.handleError(res.msg)
}
}
reader.readAsBinaryString(data)
}
},
created () {
}
}
</script>
<style lang="less" scoped>
@width: 400px;
@height: 400px;
@max-width: 560px;
.f-upload{
/*最外层设置宽高 固定400px*/
width: @width;
height: @width;
max-width: @max-width;
min-height: @width;
&__video{
min-width: @width ;
min-height: @width;
border: 1px dashed #D9D9D9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: center;
/deep/ .el-upload {
width: 100%;
}
.video-uploader-icon {
font-size: 28px;
color: #8C939D;
line-height: @height;
}
&:hover {
border-color: #409EFF;
color: #409EFF;
.image-uploader-icon {
color: #409EFF;
}
}
.video-delete-icon {
position: absolute;
font-size: 22px;
right: 5px;
top: 5px;
color: red;
&:hover {
color: #ff5500;
}
}
}
/*上传视频组件提示*/
&__hit {
font-weight: 500;
//color: @color-hint-text;
line-height: 20px;
padding-top: 20px;
padding-bottom: 10px;
li {
//color: @text;
padding-bottom: 5px;
&::before{
content: "●";
color: #1790FF;
padding-right: 10px;
}
}
}
}
</style>
上一篇: php中trait是什么意思?php中trait的简单介绍
下一篇: Vue3 文档学习笔记