vue自定义封装大文件分片上传组件,带上传进度条显示,断点续传,视频播放器组件,原生JS的AJAX封装,Promise异步变同步
说明:请求用了自定义封装的原生js的ajax请求和axios视频播放用了vue-video-player插件文件的md5编码用了sparkmd5插件读取二进制文件用的客户端自带的FileReader接口封装的分片上传和断点续传组件:<template><div class="biguploadfile&qu
·
说明:
请求用了自定义封装的原生js的ajax请求和axios
视频播放用了vue-video-player插件
文件的md5编码用了sparkmd5.js
读取二进制文件用的客户端自带的FileReader接口
封装的分片上传和断点续传组件:
<template>
<div class="biguploadfile">
<div class="videoBox">
<div class="progressBox" v-if="showProgress">
<span class="progressItem" ref="progressItem"></span>
</div>
<videoPlay :src="fileBaseUrl+src" poster='' v-if="showVideo && !pauseStatus && !showProgress"></videoPlay>
</div>
<input type="file" class="bigFile" @change="computedSliceMd5" ref="file" name="file">
<div class="btns">
<Button type="primary" v-if="!pauseStatus" @click="toUpload">上传</Button>
<Button type="default" v-if="pauseStatus" @click="pauseUpload">取消</Button>
</div>
</div>
</template>
<script>
import SparkMD5 from "spark-md5"; //获取二进制文件md5编码
import videoPlay from "@/components/videoPlayer"; //利用vue-video-player插件封装的播放器
import Ajax from "@/plugins/ajax.js"; //封装的原生JS ajax请求
import api from "@/api/commonApi.js"; //本地用的接口
import code from "@/config/base.js"; //这是项目中自定义的参数
export default {
data() {
return {
showVideo: false,
blobSlice: null,
file: null,
identifier: null,
chunkSize: 1024000,
chunks: 0,
currentChunk: 0,
spark: null,
fileReader: null,
tmpDataList: [],
formDataList: [],
uploadedList: [],
start: 0,
end: 0,
headers: {},
urlCode: code.urlCode.lectureDemand,
xhr: null,
pauseStatus: false,
showProgress: false,
percent: 0
};
},
props: {
src: { //利用props将上传的视频地址传回来,用于预览
type: String,
default: ""
}
},
components: { videoPlay }, //播放器组件
methods: {
pauseUpload() { //取消上传
this.xhr.abort();
this.xhr = null;
},
toUpload() { //调用上传接口
this.$refs.file.click();
},
checkMd5() { //断点续传需要用的方法,断点续传获取到的是已经上传过去的分片编号,已经上传过的分片直接跳过去
this.showProgress = true;
this.$axios
.post(api.lecturedemanduploadfilemd5, { md5: this.identifier })
.then(resp => {
if (resp.data.object.code == 2) {
let tmpIdsList = resp.data.object.ids;
tmpIdsList.map((el,index) => {
tmpIdsList[index] = parseInt(el);
});
this.uploadedList = tmpIdsList;
}
this.showVideo = false;
this.pauseStatus = true;
this.uploadFile(
1,
this.formDataList,
this.uploadedList,
this.chunks
);
})
.catch(err => {
this.initParam(0, "断点续传检查失败,请重试");
});
},
computedSliceMd5() { //获取文件的md5编码
this.file = this.$refs.file.files[0];
this.chunks = Math.ceil(this.file.size / this.chunkSize);
this.fileReader.onload = e => {
this.spark.append(e.target.result); // Append array buffer
this.currentChunk++;
if (this.currentChunk < this.chunks) {
this.loadNext();
} else {
this.identifier = this.spark.end(); //文件的MD5身份标识
this.tmpDataList.map((el, i) => {
let formData = new FormData();
formData.append("filename", this.file.name);
formData.append("relativePath", this.file.name);
formData.append("totalSize", this.file.size);
formData.append("chunkSize", this.chunkSize);
formData.append("identifier", this.identifier);
formData.append("totalChunks", this.chunks);
formData.append("file", el.file);
formData.append("currentChunkSize", el.currentSize);
formData.append("chunkNumber", el.currentNum + 1);
formData.append("urlCode", this.urlCode);
this.formDataList.push(formData);
});
this.checkMd5();
}
};
this.fileReader.onerror = function() {
this.$Message.error("读取文件出错,请重试");
};
this.loadNext();
},
loadNext() { //文件切片
(this.start = this.currentChunk * this.chunkSize),
(this.end =
this.start + this.chunkSize >= this.file.size
? this.file.size
: this.start + this.chunkSize);
let pieceFile = this.blobSlice.call(
this.file,
this.start,
this.end
);
pieceFile.name = this.file.name;
let tmpObj = {
file: pieceFile,
currentSize: this.end - this.start,
currentNum: this.currentChunk
};
this.tmpDataList.push(tmpObj);
this.fileReader.readAsArrayBuffer(pieceFile);
},
uploadFile(n, dataList, uploadedList, chunks) { //利用promise异步变同步上传
console.log("uploadedList",uploadedList);
let status = uploadedList.indexOf(n) == -1 ? true : false;
console.log("status",status);
let key = n - 1;
if (status) {
new Promise((resolve, reject) => {
Ajax(
resolve,
reject,
"post",
api.lecturedemanduploadfile,
this.headers,
dataList[key],
this.xhrReturn,
this.getProgress
);
})
.then(resp => {
if(resp.code == "401"){
this.$router.push({path:"/login"});
return ;
}
if (resp.object.uploadStatus == 1) {
let tmpUrl = JSON.parse(resp.object.fileUrl)[0]
.fileName;
this.$emit("getUrl", tmpUrl);
this.success();
} else {
this.uploadFile(
n + 1,
this.formDataList,
this.uploadedList,
this.chunks
);
}
})
.catch(err => {
this.initParam(0, "上传失败");
});
}else{
this.percent += Math.floor(100/this.chunks);
this.changeProgress(1);
this.uploadFile(
n + 1,
this.formDataList,
this.uploadedList,
this.chunks
);
}
},
getProgress(evt) { //ajax进度条回调方法
if (evt.lengthComputable) {
this.percent += Math.floor(
(evt.loaded * 100) / this.chunks / evt.total
);
if(this.percent>=99){
this.percent = 99;
}
this.changeProgress(1);
} else {
this.changeProgress(0);
}
},
changeProgress(type) { //进度条展示
if (type) {
this.$refs.progressItem.innerHTML =
this.percent.toFixed(2) + "%";
this.$refs.progressItem.style.width =
this.percent.toFixed(2) + "%";
} else {
this.$refs.progressItem.innerHTML = "上传失败";
}
},
success() { //成功回调
this.percent = 100;
this.changeProgress(1);
setTimeout(() => {
this.initParam(1, "上传成功");
}, 500);
},
failed(err) { //失败回调
this.initParam(0, err);
},
initParam(type, msg) { //成功或失败之后的初始化
this.percent = 0;
this.pauseStatus = false;
this.showProgress = false;
this.file = null;
this.identifier = null;
this.chunks = 0;
this.currentChunk = 0;
this.tmpDataList = [];
this.formDataList = [];
this.uploadedList = [];
this.start = 0;
this.end = 0;
this.$refs.file.value = "";
if (type == 1) {
this.showVideo = true;
this.$Message.success(msg);
} else {
this.showVideo = false;
this.$Message.error(msg);
}
},
xhrReturn(xhr) {
this.xhr = xhr;
}
},
created() { //一些变量的初始化
if (Boolean(this.src)) {
this.showVideo = true;
}
this.blobSlice =
window.File.prototype.slice ||
window.File.prototype.mozSlice ||
window.File.prototype.webkitSlice;
this.spark = new SparkMD5.ArrayBuffer();
this.fileReader = new FileReader();
this.percent = 0;
}
};
</script>
<style lang="less" scoped>
.biguploadfile {
display: inline-block;
.bigFile {
display: none;
}
.progressBox {
display: inline-block;
width: 200px;
height: 20px;
background-color: gray;
vertical-align: middle;
}
.progressItem {
display: inline-block;
height: 20px;
background-color: #2d8cf0;
font-weight: bold;
color:#ffffff;
}
.btns {
display: inline-block;
vertical-align: middle;
}
.videoBox {
display: inline-block;
vertical-align: middle;
}
}
</style>
可参考的自定义封装的Ajax
function getXhr(){
let xhr=null;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
} else {
//为了兼容IE6
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
return xhr;
}
export default function Ajax(resolve,reject,type, url, headers, data, xhrReturn=()=>{}, progress=()=>{}){ //参数可封装成对象形式的,用着方便,这里就不改了
/*
ajax暂停之后不能重新发送请求,需要创建新的xhr对象重新发送请求
*/
let xhr = getXhr();
xhrReturn(xhr);//将xhr对象返回,用于取消上传
type = type.toUpperCase();
let reg = new RegExp('\\?',"g");
if(reg.test(url)){
reject("url地址传递错误");
return ;
}
let random = Math.random();
url += "?"+random;
if(type == 'GET'){
reject("不支持GET提交方式");
} else if(type == 'POST'){
xhr.open('POST', url, true);
if(typeof headers == 'object'){
for(let key in headers){
// 如果需要像 html 表单那样 POST 数据,请使用 setRequestHeader() 来添加 http 头。
xhr.setRequestHeader(key, headers[key]);
}
}else{
reject("请求头设置有误");
return ;
}
xhr.withCredentials = false;
xhr.upload.onprogress = progress;
xhr.onabort = ()=>{
xhr = null;
};
xhr.send(data);
}
// 处理返回数据
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
let resp = JSON.parse(xhr.responseText);
resolve(resp);
} else {
reject("提交失败,请重试");
}
}
}
}
可参考的videoPlayer组件
<template>
<div class="container">
<div class="video-box" :style="videoStyle">
<video-player
class="video-player vjs-custom-skin video-viewport"
ref="videoPlayer"
:playsinline="true"
:options="playerOptions"
@play="onPlayerPlay($event)"
@pause="onPlayerPause($event)"
></video-player>
</div>
</div>
</template>
<script>
import cookie from "./../utils/cookie.js";
import api from "@/api/commonApi";
export default {
data() {
return {
playerOptions: {
playbackRates: [0.5, 1.0, 1.5, 2.0, 3.0, 6.0, 12.0], //播放速度
autoplay: false, //如果true,浏览器准备好时开始回放。
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 导致视频一结束就重新开始。
preload: "auto", // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: "zh-CN",
aspectRatio: "4:3", // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [
{
type: "video/mp4",
src: "" //你的视频地址(必填)
}
],
poster: "", //你的封面地址
width: document.documentElement.clientWidth,
notSupportedMessage: "此视频暂无法播放,请稍后再试", //允许覆盖Video.js无法播放媒体源时显示的默认信息。
controlBar: {
timeDivider: true,
durationDisplay: true,
remainingTimeDisplay: false,
fullscreenToggle: true //全屏按钮
}
}
};
},
props: {
src: {
type:String,
default: ""
},
poster: {
type:String,
default: "./static/img/heicon.jpg"
},
videoStyle:{
type:Object,
default:()=>{
return { width: "400px", height: "300px" };
}
}
},
methods: {
onPlayerPlay(player) {
console.log("触发播放");
},
onPlayerPause(player) {
console.log("触发暂停");
}
},
computed: {
player() {//暂时没用到
return this.$refs.videoPlayer.player;
}
},
mounted() {
this.playerOptions.sources[0].src = this.src;
this.playerOptions.poster = this.poster;
}
};
</script>
<style lang="less" scoped>
.container {
display:inline-block;
.video-box {
.video-viewport {
width: 100%;
height: 100%;
}
}
}
</style>
调用:
<template>
<div>
<!-- 用props将上传的视频地址传递过去,还有一个方法 -->
<bigUploadFile :src="src" @getUrl="getUploadUrl"></bigUploadFile>
</div>
</template>
<script>
import bigUploadFile from "@/components/bigUploadFile";
export default {
data() {
return {
src: ""
};
},
created() {},
components: {
bigUploadFile
},
methods: {
//获取上传的url
getUploadUrl(url) {
console.log("传递过来的url", url);
this.src = url;
}
}
};
</script>
<style lang="less">
</style>
更多推荐
已为社区贡献19条内容
所有评论(0)