基于vue2的切片上传,封装element-ui中的el-upload组件

5 篇文章 0 订阅
本文介绍了在文件上传中遇到大文件时如何采用切片上传策略,以避免接口响应时间过长导致的上传失败。通过将大文件切割成多个小文件流,异步上传至服务器,然后后台根据MD5标识进行合并,确保文件完整。文中提供了使用Axios实现的Vue组件代码示例,包括直接上传、切片上传和文件合并的接口调用,以及进度条和上传状态的管理。
摘要由CSDN通过智能技术生成

切片上传

我们在工作中,经常要实现图片,视频等上传功能,如果上传的文件很小,那么我们可以直接上传;但是,如果文件很大,比如500MB呢,我们还能直接上传吗?
那肯定是不行的,加入我们直接上传,就会发现这个上传的接口,响应时间很长,最终无法生效以至于报错。所以,在这里,我们就需要借助切片上传。
所谓的切片上传,就是把一个视频剪成几个小视频一个一个上传。比如:一个视频500MB,我们可以按照5MB一个文件流的大小,切成100份,异步上传到服务器,然后,后台再根据MD5一一识别,最后,我们再调用合并接口,让他们在后台进行合并。
提示:
1、切片上传需要后端写3个接口:
①、直接上传文件的接口;
②、切片后,每个文件上传的地址;
③、切片合并接口
2、需要引入MD5
3、为了方便,我这里使用Axios的写法

上面附上代码,这里的我的上传,每次只能上传一个文件,如果想要修改的话,自己去看element-uid的upload组件里面的属性。
最后,代码比较乱,很多地方未做优化,请多多见谅,毕竟,本人刚入行不久,写这个博客,也只是为了日后方便自己再次使用。

<template>
    <div class="component-upload-image">
        <el-upload
            ref="upload"
            :limit="1"
            :accept="accept"
            :headers="upload.headers"
            :action="upload.url"
            :disabled="upload.isUploading"
            :auto-upload="false"
            :http-request="uploadFile"
            :on-change="changeStatus"
            drag
        >
            <i class="el-icon-upload"></i>
            <div class="el-upload__text">
                将文件拖到此处,或
                <em>点击上传</em>
            </div>
            <div class="el-upload__tip" style="color: red" slot="tip">提示:仅允许导入图片、视频、文本!</div>
        </el-upload>
        <div v-show="progressFlag" class="head-img">
            <el-progress
                color="#f56c6c"
                :text-inside="true"
                :stroke-width="24"
                :percentage="progressPercent"
                status="success"
                :format="format"
            ></el-progress>
        </div>
        <div slot="footer" class="dialog-footer" style="margin-top:10px">
            <el-button
                type="primary"
                @click="submitFileForm"
                :loading="bLoading"
                :disabled="blDisabled"
            >确定上传</el-button>
        </div>
    </div>
</template>

<script>
import { getToken } from "@/utils/auth";
import SparkMD5 from 'spark-md5'
import Axios from "axios";

export default {
    props: {},
    data() {
        return {
        	// 可以上传的文件类型
            accept: ".pdf, .txt, .text, .pptx, .ppt, .doc, .docx, .mp4, .jpg, .jpeg, .png",
            // 文件直接上传的地址
            uploadUrl:  url1,// 文件直接上传的地址
            chunkUpload:  url2,// 文件切片后,每个切片上传的地址
            composeFiles:  url3,// 切片合并的地址
            //分片大小 这里设置为20MB
            chunkSize: 20 * 1024 * 1024,
            // 加载按钮
            bLoading: false,
            blDisabled: true,
            // 上传进度条
            progressFlag: false,
            progressPercent: 0,
            // 文件弹窗
            filePop: false,
            // 上传 文件列表
            fileList: [],
            fileform: {},
            fileformRules: {
                value: [
                    { required: true, message: "文件名称不能为空", trigger: "change" },
                ],
            },
            // 文件流
            fileRow: new FormData(),
            upload: {
                // 是否显示弹出层(用户导入)
                open: false,
                // 弹出层标题(用户导入)
                title: "上传",
                // 是否禁用上传
                isUploading: false,
                // 设置上传的请求头部,一般要加上token
                headers: { Authorization: getToken() },
                // 上传的地址
                url: "",
            },
        };
    },
    methods: {
        changeStatus() {
            this.blDisabled = false
        },
        /**
         * @desc: 上传进度条到 100时显示 合成
         */
        format(percentage) {
            return percentage === 100 ? '正在合并文件,请稍等...' : `${percentage} % `;
        },
        /**
         * @desc: 上传文件
         */
        async uploadFile(e) {
            const that = this
            const file = e.file;
            const fileSize = file.size
            const mineType = file.type
            // chunkSize是20MB
            // console.log('切片视频', that.chunkSize);
            that.fileRow.append("file", file);
            let query = {1,2};// 这里是你上传接口应该传的参数
            that.upload.isUploading = true;
            that.bLoading = true
            that.progressFlag = true
            // 如果视频小于20MB,直传
            if (fileSize <= that.chunkSize) {
                // console.log("上传的文件大于20m才能分片上传")
                Axios({
                    timeout: 1000,
                    method: "post",
                    url: that.uploadUrl,
                    headers: that.upload.headers,
                    params: query,
                    data: that.fileRow,
                    onUploadProgress: progressEvent => {
                        that.progressPercent = ((progressEvent.loaded / progressEvent.total) * 100).toFixed(0) | 0
                    },
                }).then((res) => {
                    console.log(res.data);
                    that.upload.isUploading = false;
                    if (res.data.code == 200) {
                        that.$message.success("上传成功");
                        e.file.path = res.data.data.fileKey
                        this.blDisabled = true
                        console.log(154, e.file);
                        this.$emit('getFile', e.file)
                        that.progressFlag = false
                        that.progressPercent = 0
                        that.bLoading = false
                        that.upload.open = false;
                        that.$refs.upload.clearFiles();
                        that.fileReset();
                        that.fileRow.delete("file")
                    } else {
                        that.progressFlag = false
                        that.progressPercent = 0
                        that.$message.error(res.msg);
                    }
                });
            } else {
                const chunkCount = Math.ceil(fileSize / that.chunkSize)
                console.log("文件大小:", (file.size / 1024 / 1024) + "Mb", "分片数:", chunkCount)

                //获取文件md5
                const fileMd5 = await that.getFileMd5(file);
                console.log("文件md5:", fileMd5)

                console.log("向后端请求本次分片上传初始化")
                //向后端请求本次分片上传初始化
                const initUploadParams = JSON.stringify({ chunkCount: chunkCount, fileMd5: fileMd5 })
                Axios({
                    timeout: 1000 * 60 * 60 * 1,
                    method: "post",
                    url: that.chunkUpload,
                    data: initUploadParams,
                    headers: that.upload.headers,
                    processData: true,
                }).then(res => {
                    console.log(262, res.data);
                    //code = 0 文件在之前已经上传完成,直接走秒传逻辑;code = 1 文件上传过,但未完成,走续传逻辑;code = 200 则仅需要合并文件
                    if (res.data.code == 200) {
                        console.log("当前文件上传情况:所有分片已在之前上传完成,仅需合并")
                        that.composeFile(fileMd5, file.name, mineType)
                        return;
                    } else if (res.data.code == 0) {
                        console.log("当前文件上传情况:秒传")
                        return
                    } else if (res.data.code == 500) {
                        that.$alert("该文件上传出错,请重试!", '提示', {
                            confirmButtonText: '确定',
                        }).then(() => {
                            that.clearFile()
                            return
                        })
                        return
                    } else if (res.data.code == 401) {
                        console.log("请求401,表示没有权限");
                        that.$alert(res.data.msg, '提示', {
                            confirmButtonText: '确定',
                        }).then(() => {
                            that.clearFile()
                            return
                        })
                    } else if (res.data.code == 1) {
                        console.log("当前文件上传情况:初次上传 或 断点续传")
                        let UpData = res.data.data
                        let chunkNum = 0
                        let chunkLength = UpData.length
                        //若使用并发上传方式,当前分片上传完成后打印出来的完成提示是不准确的,但这并不影响最终运行结果;原因是由ajax请求本身是异步导致的
                        UpData.forEach((item) => {
                            //分片开始位置
                            let start = (item.partNumber - 1) * that.chunkSize
                            //分片结束位置
                            let end = Math.min(fileSize, start + that.chunkSize)
                            //取文件指定范围内的byte,从而得到分片数据
                            let _chunkFile = file.slice(start, end)
                            console.log("开始上传第" + item.partNumber + "个分片")
                            Axios({
                                timeout: 1000 * 60 * 60 * 1,
                                method: "put",
                                url: item.uploadUrl,
                                data: _chunkFile,
                                headers: { contentType: false },
                                processData: false,
                            }).then(res => {
                                console.log("第" + item.partNumber + "个分片上传完成", res)
                                chunkNum++
                                that.progressPercent = ((chunkNum / chunkLength) * 100).toFixed(0) | 0
                                console.log(that.progressPercent);
                                if (chunkNum == chunkLength) {
                                    //请求后端合并文件
                                    that.composeFile(fileMd5, file.name, mineType)
                                }
                            })
                        })
                    }
                })
            }
        },
        /**
         * @desc:请求后端合并文件
         */
        composeFile(fileMd5, fileName, mineType) {
            const that = this
            console.log("开始请求后端合并文件")
            //注意:bucketName请填写你自己的存储桶名称,如果没有,就先创建一个写在这
            const composeParams = JSON.stringify({
                fileMd5: fileMd5,
                fileName: fileName,
                bucketName: "ruoyi",
                mineType: mineType,
                businessInstanceId: that.fileId,
                businessType: "student_course"
            })
            Axios({
                timeout: 1000 * 60 * 60 * 1,
                method: "post",
                url: that.composeFiles,
                data: composeParams,
                headers: that.upload.headers,
                processData: false,
            }).then(res => {
                console.log(347, res);
                that.upload.isUploading = false;
                if (res.data.code == 200) {
                    console.log('视频合并完成!!!');
                    that.$message.success("上传成功");
                    e.file.path = res.data.data.fileKey
                    this.$emit('getFile', e.file)
                    this.blDisabled = true
                    that.progressFlag = false
                    that.progressPercent = 0
                    that.bLoading = false
                    that.upload.open = false;
                    that.$refs.upload.clearFiles();
                    that.fileReset();
                } else {
                    that.progressFlag = false
                    that.progressPercent = 0
                    that.$message.error(res.data.msg);
                }
            })
        },
        /**
        * 获取文件MD5
        */
        getFileMd5(file) {
            console.log('file', file);
            let fileReader = new FileReader()
            fileReader.readAsBinaryString(file)
            let spark = new SparkMD5()
            return new Promise((resolve) => {
                fileReader.onload = (e) => {
                    spark.appendBinary(e.target.result)
                    resolve(spark.end())
                }
            })
        },
        /**
         * @desc: 确定按钮,上传视频
         */
        submitFileForm() {
            // 获取文件尾缀
            let fileName = this.$refs.upload.uploadFiles[0].name
            let testmsg = (fileName.substring(fileName.lastIndexOf('.') + 1)).toLowerCase()
            let size = this.$refs.upload.uploadFiles[0].size
            // 判断尾缀的判断条件
            const formatList = ["jpg", "jpeg", "png", "pdf", "txt", "text", "pptx", "ppt", "doc", "docx", "mp4"]
            // const extension = ()
            if (formatList.indexOf(testmsg) !== -1 && size < 512000000) {
                this.$refs.upload.submit();
            } else {
                this.$alert("对不起!您导入的文件格式不对,或者导入的文件大小超过500MB!")
            }
        },
        // 表单重置
        fileReset() {
            this.fileform = {
                value: undefined,
            };
            this.resetForm("fileform");
        },
        /**
         * @desc: 上传失败,清空上传表单
         */
        clearFile() {
            this.upload.isUploading = false;
            this.upload.open = false;
            this.$refs.upload.clearFiles();
            this.progressFlag = false
            this.progressPercent = 0
            this.bLoading = false
        },
    }
};
</script>
<style scoped lang="scss">
// .el-upload--picture-card 控制加号部分
::v-deep.hide .el-upload--picture-card {
    display: none;
}
// 去掉动画效果
::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active {
    transition: all 0s;
}

::v-deep .el-list-enter,
.el-list-leave-active {
    opacity: 0;
    transform: translateY(0);
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值