vue自定义封装大文件分片上传组件,带上传进度条显示,断点续传,视频播放器组件,原生JS的AJAX封装,Promise异步变同步

说明:

请求用了自定义封装的原生js的ajax请求和axios
视频播放用了vue-video-player插件
文件的md5编码用了sparkmd5插件
读取二进制文件用的客户端自带的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>

猜你喜欢

转载自blog.csdn.net/qq_38652871/article/details/88229749