|
<template>
|
<div class="greetings" v-loading="loading">
|
<el-upload accept=".mp4, .mp3, .xls, .xlsx, .doc, .docx, .ppt, .pptx, .pdf" :on-change="handleFileChange" :on-preview="view" :auto-upload="false" ref="uploadfileComponent" :limit="1" :on-exceed="handleExceed" v-model:file-list="fileList">
|
<template #trigger>
|
<el-button type="primary">选择文件</el-button>
|
</template>
|
<el-button :disabled="uploadDisabled" style="margin-left: 10px" type="success" @click="handlerUpload">上传</el-button>
|
<el-button class="ml-3" type="success" @click="resetData" >重置</el-button>
|
<template #tip>
|
<br /><br />
|
<span>上传进度:{{ fakeUploadPercentage }}%</span>
|
<el-progress :text-inside="true" :stroke-width="26" :percentage="fakeUploadPercentage" />
|
<div class="el-upload__tip text-red">限制一个文件, 新文件将会覆盖原文件</div>
|
</template>
|
</el-upload>
|
<br>
|
<div v-if="container.showVideo" style="width: 300px;height: 200px">
|
<video ref="videoPlayer" class="video-js" style="margin: auto auto"></video>
|
</div>
|
|
</div>
|
</template>
|
|
<script setup>
|
import { ElMessage } from "element-plus";
|
import videojs from "video.js"
|
import { computed, nextTick, onMounted, onUnmounted,ref,reactive,watch } from "vue";
|
import SparkMD5 from "spark-md5";
|
import {uploadFileRequest,mergeFileRequest} from "@/api/onlineEducation/upload"
|
import pLimit from 'p-limit'
|
|
const videoPlayer = ref(null)
|
const myPlayer = ref(null)
|
const uploadDisabled=ref(false)
|
const chunkSize = ref(10 * 1024 * 1024) // 切片大小
|
const uploadedCount=ref(0) //已上传的分配个数
|
const fileChunkList=ref([])
|
const fileList=ref([])
|
const uploadfileComponent=ref(null)
|
const emit = defineEmits(["getFile"]);
|
const loading = ref(false)
|
const props = defineProps({
|
responseType: {
|
type: Number,
|
default: 0
|
}
|
})
|
const container=reactive({
|
file:{
|
name:'',
|
percentage:0,
|
status:1,
|
size:0,
|
url:'',
|
raw:null,
|
uid:0
|
},
|
fileMd5:'',
|
worker:null,
|
showVideo:false
|
})
|
// 生成文件hash的进度
|
const hashPercentage = ref(0)
|
// 显示在页面上的文件上传进度
|
const fakeUploadPercentage = ref(0)
|
const type = ref();
|
onMounted(() => {
|
type.value = props.responseType
|
// getVideo(props.responseType)
|
|
})
|
const resourcePath = ref();
|
const getVideo = (value) => {
|
type.value = value;
|
if(value == 1){
|
// container.showVideo = true
|
nextTick(() => {
|
console.log("111111",videoPlayer.value)
|
myPlayer.value = videojs(videoPlayer.value, {
|
poster: "",//视频封面
|
controls: true,//视频控件
|
autoplay:true,//自动播放
|
sources: [
|
{
|
src: resourcePath.value ? resourcePath.value : '',
|
// src:'',
|
type: 'application/x-mpegURL',
|
}
|
],
|
controlBar: {
|
remainingTimeDisplay: {
|
displayNegative: false
|
}
|
},
|
playbackRates: [0.5, 1, 1.5, 2]//设置播放速度
|
}, onPlayerReady)
|
});
|
}
|
}
|
// watch(() => props.responseType, value => getVideo(value))
|
onUnmounted(() => {
|
if (myPlayer.value) {
|
myPlayer.value.dispose()
|
}
|
})
|
|
const dispose = () => {
|
// if (myPlayer.value) {
|
// myPlayer.value.dispose()
|
// resourcePath.value = ''
|
// }
|
container.showVideo = false;
|
resourcePath.value = ''
|
hashPercentage.value=0
|
uploadPercentage.value=0
|
fakeUploadPercentage.value=0
|
uploadedCount.value=0
|
fileChunkList.value=[]
|
fileList.value=[]
|
}
|
|
const changeType = (val) => {
|
type.value = val
|
dispose()
|
if(val == 1){
|
container.showVideo = true
|
nextTick(() => {
|
getVideo(val)
|
})
|
}
|
}
|
const openValue = ref();
|
const open = (val) => {
|
console.log("val",val)
|
openValue.value = val
|
fakeUploadPercentage.value = 100
|
if(val.resourceType == 1){
|
container.showVideo = true
|
resourcePath.value = val.resourcePath;
|
getVideo(val.resourceType)
|
}else {
|
container.showVideo = false
|
// if (myPlayer.value) {
|
// myPlayer.value.dispose()
|
// }
|
fileList.value.push({
|
path: val.resourcePath,
|
name: val.originName
|
})
|
}
|
}
|
const view = (file) => {
|
console.log('vlco',file)
|
// console.log("点击文件=>", file);
|
const url = file.path;
|
const link = document.createElement("a");
|
link.href = url;
|
link.download = file.name;
|
// link.target = "_blank";
|
document.body.appendChild(link);
|
link.click();
|
document.body.removeChild(link);
|
}
|
// video初始化完成的回调函数
|
const onPlayerReady = () => {
|
myPlayer.value.log("play.....")
|
bindVideoEvents()
|
}
|
// 绑定事件
|
const bindVideoEvents = () => {
|
if (!myPlayer.value) return
|
myPlayer.value.on('play', onPlay)
|
myPlayer.value.on('pause', onPause)
|
myPlayer.value.on('ended', onEnded)
|
myPlayer.value.on('timeupdate', onTimeupdate)
|
myPlayer.value.on('loadedmetadata', onLoadedmetadata)
|
myPlayer.value.on('fullscreenchange', onFullscreenchange)
|
myPlayer.value.on('error', err => {
|
console.log('视频加载发生错误', err)
|
})
|
}
|
|
const onPlay = () => {
|
console.log('播放视频')
|
}
|
const onPause = () => {
|
console.log('暂停播放')
|
}
|
const onEnded = () => {}
|
const onTimeupdate = () => {
|
console.log('播放位置已更改时,播放时间更新')
|
}
|
// 全屏切换
|
const onFullscreenchange = () => {
|
console.log('全屏状态改变')
|
}
|
// 元数据加载完成
|
const onLoadedmetadata = () => {
|
console.log('元数据加载完成')
|
|
}
|
|
|
//计算文件上传的进度
|
const uploadPercentage= computed({
|
get(){
|
if(!container.file||!fileChunkList.value.length){
|
return 0
|
}
|
const loaded=fileChunkList.value.map(item => item.size * item.percentage).reduce((acc,cur) => {
|
return acc+cur
|
})
|
console.log('loaded',uploadedCount.value,loaded)
|
return parseInt((loaded/container.file.size).toFixed(2))
|
},
|
set(value){
|
return value
|
}
|
})
|
|
// watch uploadPercentage,得到fakeUploadPercentage
|
watch(uploadPercentage, (newValue) => {
|
if (newValue >= fakeUploadPercentage.value) {
|
fakeUploadPercentage.value = newValue
|
}
|
})
|
|
const resetData = () => {
|
container.showVideo = false
|
resourcePath.value = ''
|
hashPercentage.value=0
|
uploadPercentage.value=0
|
fakeUploadPercentage.value=0
|
uploadedCount.value=0
|
fileChunkList.value=[]
|
fileList.value=[]
|
if(container.worker){
|
container.worker.onmessage=null
|
}
|
// if (myPlayer.value) {
|
// myPlayer.value.dispose()
|
// }
|
const file = {
|
resourceSize: null,
|
md5: '',
|
resourcePath: '',
|
mediaType: '',
|
docPage: 0,
|
resourceLength: '',
|
originName:''
|
}
|
emit("getFile",file)
|
}
|
|
//选择了文件
|
const handleFileChange=(uploadFile,uploadFiles) =>{
|
// resetData()
|
if(!uploadFile){
|
return
|
}
|
container.file=uploadFile
|
fileList.value=uploadFiles
|
}
|
|
const handleExceed= (files) => {
|
uploadfileComponent.value.clearFiles()
|
nextTick(() => {
|
uploadfileComponent.value.handleStart(files[0])
|
})
|
}
|
|
//上传
|
const handlerUpload= async() => {
|
|
if(type.value == null){
|
ElMessage({
|
type: 'warning',
|
message: '请先选择资源类型'
|
});
|
return false
|
}
|
|
if(!container.file.raw){
|
return
|
}
|
if(container.file.raw.size > 1024 * 1024 * 1000){
|
ElMessage({
|
type: 'warning',
|
message: '文件大小不能超过1G'
|
});
|
return false
|
}
|
|
const filetype = container.file.raw.name.split(".").pop();
|
const extension = (filetype === "mp4" || filetype ==="mp3" || filetype ==="xls" || filetype === "xlsx" || filetype ==="doc" || filetype ==="docx" || filetype === "ppt" || filetype ==="pptx" || filetype ==="pdf");
|
if (!extension ) {
|
ElMessage({
|
type: 'warning',
|
message: '暂不支持该格式上传'
|
});
|
return false;
|
}
|
if((type.value == 1 && filetype != 'mp4') || (type.value == 2 && filetype != 'mp3')){
|
ElMessage({
|
type: 'warning',
|
message: '请上传所选资源类型的文件'
|
});
|
return false;
|
}
|
if(type.value == 3){
|
if( filetype == 'xls' || filetype == 'xlsx' || filetype == 'doc'|| filetype == 'docx'|| filetype == 'ppt'|| filetype == 'pptx'|| filetype == 'pdf' ){
|
|
}else {
|
ElMessage({
|
type: 'warning',
|
message: '请上传所选资源类型的文件'
|
});
|
return false;
|
}
|
}
|
loading.value = true
|
//文件分片
|
const chunkList=createFileChunk(container.file.raw)
|
console.log('文件分了多少片:',chunkList.length)
|
//通过webworker计算出文件hash
|
container.fileMd5=await calculateMd5(chunkList)
|
console.log('文件hash1:',container.fileMd5)
|
// container.fileMd5=await getFileMD5(container.file.raw)
|
// console.log('文件hash2:',container.hash)
|
|
fileChunkList.value=[]
|
fileChunkList.value=chunkList.map(({file},index) => ({
|
fileMd5:container.fileMd5,
|
index,
|
chunkName: `${container.fileMd5}-${index}`,
|
chunk:file,
|
size:file.size,
|
// 如果已上传切片数组uploadedList中包含这个切片,则证明这个切片之前已经上传成功了,进度设为100。
|
percentage:0
|
}))
|
|
uploadChunks(fileChunkList)
|
|
}
|
|
//文件分片
|
const createFileChunk = (file,size=chunkSize.value) => {
|
const chunkList=[]
|
let cur=0
|
while(cur<file.size){
|
chunkList.push({
|
file:file.slice(cur,cur+size),
|
})
|
cur+=size
|
}
|
return chunkList
|
}
|
|
//计算文件md5 方法1
|
const calculateMd5 = (chunkList) => {
|
return new Promise((resolve) => {
|
container.worker=new Worker('/hash.js')
|
container.worker.postMessage({fileChunkList:chunkList})
|
container.worker.onmessage= (e) => {
|
const {percentage,hash} = e.data
|
hashPercentage.value=percentage.toFixed(2)
|
if(hash){
|
resolve(hash)
|
}
|
}
|
})
|
}
|
|
//计算文件md5 方法2
|
const getFileMD5 = (file) => {
|
return new Promise((resolve, reject) => {
|
const spark = new SparkMD5.ArrayBuffer()
|
const fileReader = new FileReader()
|
fileReader.onload = (e) => {
|
spark.append(e.target?.result)
|
resolve(spark.end())
|
}
|
fileReader.onerror = () => {
|
reject('')
|
}
|
fileReader.readAsArrayBuffer(file)
|
})
|
}
|
|
//计算上传进度
|
const createProgressHandler = (item) => {
|
console.log('createProgresshandler -> item', item);
|
return (p) => {
|
if(item.percentage>=100){
|
item.percentage = 100
|
}else{
|
item.percentage=parseInt(String((p.loaded/p.total)*100))
|
}
|
// 确保进度百分比不会超过100%
|
if (item.percentage > 100) item.percentage = 100
|
}
|
}
|
|
|
|
//上传切片
|
const uploadChunks= async(uploadedList) => {
|
const limit = pLimit(10); // 控制并发数为10
|
const requestList=uploadedList.value.map(({chunk,chunkName,index,fileMd5}) => {
|
const formdata=new FormData()
|
formdata.append('file',chunk)
|
formdata.append('chunkName',chunkName)
|
formdata.append('fileName',container.file.name)
|
formdata.append('fileMd5',fileMd5)
|
formdata.append('index',index)
|
return {formdata,index}
|
}).map(async ({formdata,index}) => {
|
return limit(() => doUploadChunk({data:formdata,onUploadProgress:createProgressHandler(fileChunkList.value[index])}))
|
})
|
await Promise.all(requestList)
|
console.log("数组:",fileChunkList)
|
if(uploadedCount.value>=fileChunkList.value.length){
|
mergeRequest()
|
}
|
|
}
|
|
|
const doUploadChunk = ({data,onUploadProgress}) => {
|
return new Promise((resolve) => {
|
uploadFileRequest(data,onUploadProgress).then((result) => {
|
let resData=result.data
|
if(result&&result.code==200){
|
uploadedCount.value=uploadedCount.value+1
|
fileChunkList.value[data.get('index')].percentage=100 //手动更新进度
|
console.log(uploadedCount.value,'result--------------')
|
}else {
|
loading.value = false
|
}
|
resolve('done')
|
})
|
})
|
}
|
|
const mergeRequest = async() => {
|
if(container.file.name.lastIndexOf(".") === -1){
|
ElMessage.warning("请输入文件后缀名")
|
return
|
}
|
let data=await mergeFileRequest({fileMd5:container.fileMd5,fileName:container.file.name})
|
console.log(data,"mege------------222")
|
if(data && data.code==200){
|
const filetype = data.data.originName.split(".").pop();
|
if(filetype == 'mp4' || filetype == 'MP4'){
|
container.showVideo = true
|
await nextTick(() => {
|
|
console.log("myPlayer.value",myPlayer.value)
|
myPlayer.value.src(
|
{
|
src: data.data.url,
|
type: 'application/x-mpegURL',
|
})
|
// myPlayer.value.load()
|
myPlayer.value.play().catch((error) => {
|
console.error('Error playing video:', error);
|
});
|
})
|
// myPlayer.value.pause()
|
//myPlayer.value.reset()
|
}
|
const file = {
|
resourceSize: data.data.size,
|
md5: data.data.md5,
|
resourcePath: data.data.path,
|
mediaType: filetype,
|
docPage: data.data.docPage,
|
resourceLength: data.data.resourceLength,
|
originName: data.data.originName
|
}
|
emit("getFile",file)
|
|
|
loading.value = false
|
ElMessage.success("上传成功")
|
}else{
|
loading.value = false
|
ElMessage.success("合并数据失败")
|
}
|
}
|
|
defineExpose({
|
dispose,
|
changeType,
|
open
|
});
|
</script>
|
|
|
<style scoped>
|
.greetings{
|
:deep(.video-js) {
|
width: 300px;
|
height: 200px;
|
}
|
:deep(.el-icon--close) {
|
display: none;
|
}
|
}
|
|
|
|
h1 {
|
font-weight: 500;
|
font-size: 2.6rem;
|
position: relative;
|
top: -10px;
|
}
|
|
h3 {
|
font-size: 1.2rem;
|
}
|
|
.greetings h1,
|
.greetings h3 {
|
text-align: center;
|
}
|
|
@media (min-width: 1024px) {
|
.greetings h1,
|
.greetings h3 {
|
text-align: left;
|
}
|
}
|
</style>
|