文件分片上传
<template>
<div id="global-uploader">
<!-- 上传 -->
<uploader
ref="uploader"
:options="options"
:autoStart="false"
@file-added="onFileAdded"
@file-success="onFileSuccess"
@file-progress="onFileProgress"
@file-error="onFileError"
class="uploader-app">
<uploader-unsupport></uploader-unsupport>
<div :class="{'video-upload': isResources}">
<uploader-btn id="global-uploader-btn" ref="uploadBtn" :class="{'video-upload-btn': isResources}">
<img src="@/assets/portfolio.png" class="global-btn-img" v-if="!isResources">
<img src="@/assets/videoUpload.png" class="global-btn-img" v-if="isResources">
<span v-if="!isResources">点击上传附件</span>
<span v-if="isResources">点击上传视频</span>
</uploader-btn>
<div class="video-upload-desc" v-if="isResources">
视频格式为mp4,大小不大于2GB
</div>
</div>
<!-- <div v-for="(item, index) in myFileListWatch" :key="item.id" class="simple-uploader-cont" v-if="!isResources"> -->
<div v-for="(item, index) in myFileList" :key="item.id" class="simple-uploader-cont" v-if="!isResources">
<div class="simple-uploader-btn">
<div class="simple-uploader-item">
<div class="simple-uploader-fileName">
{{ item.name }}
</div>
<div @click="deleteUpload(item, index)">
<i class="el-icon-close"></i>
</div>
</div>
<div class="simple-uploader-progress">
<el-progress :percentage="parseFloat((item._prevUploadedSize / item.size * 100).toFixed(2))" :format="((percentage) => {format(percentage, index)})" color="#5EAF8A"></el-progress>
</div>
</div>
<div class="simple-uploader-form">
<el-form label-position="top" :inline="true" class="demo-form-inline">
<el-form-item label="积分:" v-if="isAppendixPrice">
<el-input v-model="item.integral" placeholder="积分" size="mini" class="uploader-form-input"></el-input>
</el-form-item>
<el-form-item label="文件格式:" v-if="isWireframe">
<el-select v-model="item.fileFormat" placeholder="文件格式" size="mini" class="uploader-form-input">
<el-option :label="item.name" :value="item.id" v-for="item in fileFormatList" :key="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="版本:" v-if="item.needVersion == 1">
<el-input v-model="item.fileFormatVersion" placeholder="版本" size="mini" class="uploader-form-input"></el-input>
</el-form-item>
<el-form-item label="渲染器:" v-if="item.renderList && (item.renderList.length != 0)">
<el-select v-model="item.renderId" placeholder="渲染器" size="mini" class="uploader-form-input">
<el-option :label="item.name" :value="item.id" v-for="item in item.renderList" :key="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="渲染器版本:" v-if="item.needRenderVersion== 1">
<el-input v-model="item.renderVersion" placeholder="渲染器版本" size="mini" class="uploader-form-input"></el-input>
</el-form-item>
</el-form>
</div>
</div>
<div v-if="isResources" class="base-input">
<div class="resources-title">
上传合集:
</div>
<div class="resources-cont">
<el-select v-model="videoId" size="mini" placeholder="请选择上传合集" style="width:193px;margin-right:10px">
<el-option :label="item.title" :value="item.id" v-for="(item, index) in videoCollectionList" :key="item.id"></el-option>
</el-select>
<video-collection :subNavOneList="subNavOneList"/>
</div>
<div class="resources-title" v-if="videoId">
视频所属分类:
</div>
<div class="resources-cont" v-if="videoId">
<div v-for="(item, index) in videoClassifyList" :key="item">
<el-input v-model="videoClassifyList[index]" size="mini" disabled style="width:138px;margin-right:20px"></el-input>
</div>
</div>
</div>
<div class="resources-title" v-if="isResources && (myFileList.length != 0)">
上传列表:
</div>
<div class="simple-uploader-videoCont" v-for="(item, index) in myFileList" :key="item.id" v-if="isResources">
<div class="simple-uploader-videoBtn">
<div class="simple-uploader-videoDesc">
<div class="simple-uploader-videoTitle">
<div>
{{ item.name }}
</div>
<div>
{{ item.size | computerByte }}
</div>
<div>
取消上传
</div>
</div>
<div class="simple-uploader-videoProgress">
<el-progress :percentage="parseFloat((item._prevUploadedSize / item.size * 100).toFixed(2))" :format="((percentage) => {format(percentage, index)})" color="#5EAF8A"></el-progress>
</div>
<div class="resources-cont">
<div class="video-title">
标题:
</div>
<div class="video-input">
<el-input v-model="item.videoTitle" size="mini" style="width:906px;" placeholder="请输入标题"></el-input>
</div>
</div>
<div class="resources-cont">
<div class="video-title">
简介:
</div>
<div class="video-form-desc">
<el-input v-model="item.remark" type="textarea" size="mini" style="width:906px;" placeholder="请输入简介" maxlength="5000" show-word-limit class="video-form-desc-textarea"></el-input>
</div>
</div>
</div>
<div>
<i class="el-icon-close delete-icon-video" @click="deleteUpload(item, index)"></i>
</div>
</div>
</div>
</uploader>
</div>
</template>
<script>
import SparkMD5 from "spark-md5"
import { getToken } from "@/utils/auth"
import { fileCusFilePartCancel, fileCusFilePartMerge, modelCusModelFileDelKey, modelCusFileFormatMenuCollect, modelCusModelListMine, modelCusModelTypeCollectModelId } from "@/api/upload"
import { mapGetters } from "vuex"
import VideoCollection from '@/components/VideoCollection/index'
export default {
props: {
fileType: {
type: String
},
isWireframe: {
type: Boolean
},
isAppendixPrice: {
type: Boolean
},
isResources: {
type: Boolean
},
isClean: {
type: Boolean
},
subNavOneList: {
type: Array
}
},
components: {
VideoCollection
},
filters: {
computerByte(value) {
var byte = (value / 1024).toFixed(2)
if (byte >= 1024) {
var M = (byte / 1024).toFixed(2)
if (M >= 1024) {
var T = (M / 1024).toFixed(2)
if (T >= 1024) {
var P = (T / 1024).toFixed(2)
return P + 'P'
}
return T + 'T'
}
return M + 'M'
}
return byte + 'kb'
}
},
data() {
return {
hasFileObjs: [],
options: {
// collapseFlag: true,
target: "/app/file/cus/file/part/upload",
chunkSize: "5242880",
fileParameterName: "file",
maxChunkRetries: 3,
testChunks: true, //是否开启服务器分片校验
headers: { Authorization: `Bearer ${getToken()}` },
// 服务器分片校验函数,秒传及断点续传基础
checkChunkUploadedByResponse: (chunk, message) => {
var res = JSON.parse(message).data
if (res.hasFile) {
this.hasFileObjs.push(res.identifier)
return true
}
return (res.uploaded || []).indexOf(chunk.offset + 1) >= 0
},
query: {
bucketName: "xrcymodel"
},
simultaneousUploads: 5,
},
collapse: false,
files: [],
handleContextOptions: {},
myFileList: [],
mergeList: [],
fileFormatList: [],
checkForm: false,
videoCollectionList: [],
videoId: null,
videoClassifyList: []
}
},
computed: {
...mapGetters([
"videoCollectionId"
]),
//Uploader实例
uploader() {
return this.$refs.uploader.uploader
},
myFileListWatch(){
return JSON.parse(JSON.stringify(this.myFileList))
}
},
watch: {
videoCollectionId() {
modelCusModelListMine({mainTabId: 6}).then(response => {
this.videoCollectionList = response.data
this.videoId = this.videoCollectionId
})
},
videoId(newId, oldId) {
if ((newId != oldId) && newId) {
modelCusModelTypeCollectModelId({modelId: this.videoId}).then(response => {
this.videoClassifyList = response.data
})
this.$store.commit('upload/SET_VIDEOCOLLECTIONID', this.videoId)
}
if (!newId) {
this.videoClassifyList = []
}
},
myFileListWatch: {
handler(newFileList, oldFileList) {
if (!this.isResources) {
for (var i = 0, len = this.myFileList.length; i < len; i++) {
if (oldFileList.length != 0 && newFileList.length != 0 && oldFileList[i] && (newFileList[i].fileFormat != oldFileList[i].fileFormat)) {
this.$set(this.myFileList[i], 'fileFormatVersion', null)
this.$set(this.myFileList[i], 'renderId', null)
this.$set(this.myFileList[i], 'renderVersion', null)
}
if (this.myFileList[i].fileFormat) {
this.myFileList[i].needRenderVersion = 0
for (var j = 0, jlen = this.fileFormatList.length; j < jlen; j++) {
if (this.myFileList[i].fileFormat == this.fileFormatList[j].id) {
this.myFileList[i].renderList = this.fileFormatList[j].vo.render
this.myFileList[i].needVersion = this.fileFormatList[j].needVersion
if (this.fileFormatList[j].vo.render.length != 0 && this.myFileList[i].renderId) {
for (var n = 0, nlen = this.fileFormatList[j].vo.render.length; n < nlen; n++) {
if (this.myFileList[i].renderId == this.fileFormatList[j].vo.render[n].id) {
this.myFileList[i].needRenderVersion = this.fileFormatList[j].vo.render[n].needVersion
}
}
}
}
}
}
}
}
},
deep: true
},
isClean() {
this.checkForm = true
for (var w = 0; w < this.$refs.uploader.fileList.length; w++) {
this.$refs.uploader.fileList[w].cancel()
w--
}
this.checkForm = false
},
isResources() {
document.getElementById('global-uploader-btn').getElementsByTagName('input')[0].accept = this.fileType
},
},
mounted() {
this.$nextTick(() => {
document.getElementById('global-uploader-btn').getElementsByTagName('input')[0].accept = this.fileType
})
modelCusFileFormatMenuCollect().then(response => {
this.fileFormatList = response.data
})
modelCusModelListMine({mainTabId: 6}).then(response => {
this.videoCollectionList = response.data
})
},
methods: {
deleteUpload(file, index) {
var fileKey = this.getFileKeyByProcessFile(file)
file.cancel()
if (file.size == file._prevUploadedSize) {
modelCusModelFileDelKey({fileKey: fileKey}).then(response => {
this.$message({ message: '成功删除', type: 'success' })
})
} else {
fileCusFilePartCancel({ fileKey, offline: false }).then(() => {
this.$message({ message: '成功删除', type: 'success' })
})
}
this.myFileList.splice(index, 1)
for (var j = 0, jlen = this.mergeList.length; j < jlen; j++) {
if (this.mergeList[j].fileKey == fileKey) {
this.mergeList.splice(j, 1)
break
}
}
},
format(percentage, index) {
var percentages = document.getElementsByClassName('el-progress__text')
if (percentage == 100) {
this.$nextTick(() => {
percentages[index].innerHTML = '合并中'
})
var myFileFileKey = this.getFileKeyByProcessFile(this.myFileList[index])
for (var n = 0, nlen = this.mergeList.length; n < nlen; n++) {
if (this.mergeList[n].fileKey == myFileFileKey) {
this.$nextTick(() => {
percentages[index].innerHTML = '完成'
})
break
}
}
} else {
this.$nextTick(() => {
percentages[index].innerHTML = `${percentage}%`
})
}
},
onFileAdded(file) {
if (this.checkForm) {
return
}
this.computeMD5(file)
},
onFileProgress(rootFile, file, chunk) {
if (this.checkForm) {
return
}
for (var i = 0, len = this.$refs.uploader.fileList.length; i < len; i++) {
this.myFileList.splice(i, 1, this.$refs.uploader.fileList[i])
}
console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
},
getFileKeyByProcessFile(file) {
var md5 = file.uniqueIdentifier
var extName = file.name.substring(file.name.lastIndexOf("."))
return md5 + extName
},
onFileSuccess(rootFile, file, response, chunk) {
var res = JSON.parse(response)
if (res.code == 200) {
if (this.checkForm) {
return
}
var fileKey = this.getFileKeyByProcessFile(file)
for (var j = 0, jlen = this.mergeList.length; j < jlen; j++) {
if (this.mergeList[j].fileKey == fileKey) {
return
}
}
fileCusFilePartMerge({ fileKey, offline: false }).then(response => {
if (response.code == 200) {
this.mergeList.push({fileKey: response.data})
this.$message({ message: '上传成功', type: 'success' })
if (this.isResources) {
var url = URL.createObjectURL(file.file)
var audioElement = new Audio(url)
audioElement.addEventListener("loadedmetadata", (_event) => {
var duration = audioElement.duration
this.mergeList[this.mergeList.length - 1].duration = audioElement.duration
for (var i = 0, len = this.myFileList.length; i < len; i++) {
if (this.getFileKeyByProcessFile(this.myFileList[i]) == response.data) {
this.myFileList[i].duration = duration
break
}
}
})
}
file.hasUploaded = true
for (var m = 0, mlen = this.myFileList.length; m < mlen; m++) {
this.myFileList[m].uploadSuccess = false
for (var n = 0, nlen = this.mergeList.length; n < nlen; n++) {
var myFileFileKey = this.getFileKeyByProcessFile(this.myFileList[m])
if (this.mergeList[n].fileKey == myFileFileKey) {
this.myFileList[m].uploadSuccess = true
this.myFileList[m].key = response.data
break
}
}
}
} else {
this.$message({ message: response.message, type: "error" })
}
})
} else {
this.$message({ message: response.message, type: "error" })
return
}
},
onFileError(rootFile, file, response, chunk) {
this.$message({
message: response,
type: 'error'
})
},
computeMD5(file) {
for (var j = 0, jlen = this.$refs.uploader.fileList.length - 1; j < jlen; j++) {
if (this.$refs.uploader.fileList[j].name == this.$refs.uploader.fileList[this.$refs.uploader.fileList.length - 1].name) {
this.$message({ message: '同名文件已存在', type: "error" })
this.$refs.uploader.fileList[this.$refs.uploader.fileList.length - 1].cancel()
break
}
}
for (var i = 0, len = this.$refs.uploader.fileList.length; i < len; i++) {
this.myFileList.splice(i, 1, this.$refs.uploader.fileList[i])
}
var fileReader = new FileReader()
var time = new Date().getTime()
var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
var currentChunk = 0
const chunkSize = 10 * 1024 * 1000
var chunks = Math.ceil(file.size / chunkSize)
var spark = new SparkMD5.ArrayBuffer()
file.pause()
loadNext()
fileReader.onload = (e => {
spark.append(e.target.result)
if (currentChunk < chunks) {
currentChunk++
loadNext()
} else {
var md5 = spark.end()
this.computeMD5Success(md5, file)
console.log(`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`)
}
})
fileReader.onerror = () => {
this.error(`文件${file.name}读取出错,请检查该文件`)
file.cancel()
}
function loadNext() {
var start = currentChunk * chunkSize
var end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
}
},
computeMD5Success(md5, file) {
file.uniqueIdentifier = md5
file.resume()
},
error(msg) {
this.$notify({
title: "错误",
message: msg,
type: "error",
duration: 2000
});
}
}
}
</script>
<style scoped>
#global-uploader {
width: 152px;
}
#global-uploader-btn {
width: 152px;
height: 44px;
text-align: center;
line-height: 44px;
color: #fff;
background-color: #55A1E8;
padding: 0;
border: none;
font-size: 14px;
letter-spacing: 1px;
}
.global-btn-img {
vertical-align: middle;
}
.simple-uploader-cont {
display: flex;
width: 1000px;
}
.simple-uploader-btn {
width: 152px;
margin-right: 30px;
}
.simple-uploader-item {
display: flex;
justify-content: space-between;
width: 152px;
margin-top: 40px;
height: 44px;
color: #fff;
background-color: #55A1E8;
font-size: 12px;
line-height: 44px;
text-align: center;
padding: 0 9px;
}
.simple-uploader-form {
margin-top: 40px;
}
.simple-uploader-fileName {
width: 125px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.el-icon-close {
font-size: 18px;
cursor: pointer;
vertical-align: middle;
}
.simple-uploader-progress {
width: 152px;
}
.uploader-form-input {
width: 140px;
}
.simple-uploader-videoCont {
width: 1030px;
margin-top: 18px;
}
.simple-uploader-videoBtn {
display: flex;
justify-content: space-between;
width: 1030px;
height: 240px;
background-color: #3F4954;
margin-bottom: 13px;
padding: 15px 13px;
}
.delete-icon-video {
font-size: 28px;
color: #fff;
padding-top: 6px;
}
.simple-uploader-videoDesc {
width: 960px;
height: 41px;
}
.simple-uploader-videoTitle {
display: flex;
justify-content: space-between;
width: 910px;
font-size: 14px;
height: 20px;
line-height: 20px;
color: #fff;
margin-bottom: 4px;
}
.simple-uploader-videoProgress {
width: 960px;
}
.video-collection-cont {
display: flex;
width: 100%;
}
.uploader-video-collection {
width: 193px;
margin-right: 13px;
}
.video-collection-input {
display: flex;
}
.collection-svg {
font-size: 20px;
margin-left: 13px;
cursor: pointer;
margin-top: 4px;
}
.video-upload {
width: 1028px;
height: 147px;
border: 1px dashed #1890FF;
}
.video-upload-btn {
margin: 40px 0 15px 438px;
}
.video-upload-desc {
width: 1028px;
text-align: center;
font-size: 12px;
color: rgba(255, 255, 255, .63);
}
.resources-title {
font-size: 14px;
color: #dcdcdc;
margin-top: 30px;
margin-bottom: 13px;
}
.resources-cont {
display: flex;
}
.base-input {
width: 1080px;
}
.video-title {
width: 50px;
font-size: 14px;
color: #dcdcdc;
line-height: 67px;
}
.video-input {
margin-top: 20px;
}
.video-form-desc {
margin-top: 20px;
}
.video-form-desc-textarea {
width: 906px;
}
</style>
<style>
.simple-uploader-progress .el-progress__text {
color: #fff;
padding-left: 13px;
}
.simple-uploader-progress .el-progress {
white-space: nowrap;
}
.simple-uploader-videoProgress .el-progress__text {
color: #fff;
padding-left: 10px;
}
.simple-uploader-videoProgress .el-progress {
white-space: nowrap;
}
.simple-uploader-form .el-form-item__label {
line-height: 0;
color: #fff;
}
.simple-uploader-form .el-input--mini .el-input__inner {
background: #3F4953;
border: none;
color: #fff;
}
.uploader-video-collection .el-input--mini .el-input__inner {
background: #3F4953;
border: none;
color: #fff;
}
.base-input .el-form-item__label {
color: #fff !important;
}
.base-input .el-input--mini .el-input__inner {
background: #3F4953;
border: none;
color: #fff;
}
.video-input .el-form-item__label {
color: #fff !important;
}
.video-input .el-input--mini .el-input__inner {
background: #223040;
border: none;
color: #fff;
}
.video-form-desc .el-form-item__label {
padding: 0;
color: #fff;
}
.video-form-desc .el-textarea__inner {
background-color: #223040;
border: none;
color: #fff;
height: 74px;
}
.video-form-desc .el-textarea .el-input__count {
color: #fff;
background-color: #223040;
letter-spacing: 2px;
}
</style>