vue2 + el-upload 组合下的多数据分片上传

前提条件,后端接口要支持

1.大体思路

在这里插入图片描述

2.功能代码(含多文件上传,上传进度,删除功能)

<template>
    <div class="upload-file">
        <el-upload
            multiple
            class="upload-demo"
            :limit="limit"
            :auto-upload="true"
            :action="action"
            :headers="headers"
            :file-list="fileList"
            :on-exceed="onExceed"
            :on-error="onError"
            :on-success="onSuccess"
            :before-upload="beforeUpload"
            :before-remove="beforeRemove"
            :show-file-list="false"
        >
            <el-button size="mini" type="primary">{{
                $t("dataInfo.selectFile")
            }}</el-button>
        </el-upload>

        <el-table
            :data="beforeUploadingDisplayData"
            stripe
            style="width: 100%"
            :max-height="350"
        >
            <el-table-column
                align="center"
                prop="fileName"
                :label="$t('dataInfo.fileName')"
            >
            </el-table-column>
            <el-table-column
                align="center"
                prop="name"
                :label="$t('dataInfo.fileSize')"
                width="120"
            >
                <template slot-scope="scope">
                    <span>
                        {{ getUnitSize(scope.row.fileSize) }}
                    </span>
                </template>
            </el-table-column>
            <el-table-column
                align="center"
                prop="address"
                width="230"
                :label="$t('upload.uploadProgress')"
            >
                <template slot-scope="scope">
                    <div class="upload-progress" v-if="fileUid[scope.row.uid]">
                        <el-progress
                            style="width: 150px"
                            :percentage="fileUid[scope.row.uid].totalProgress"
                        ></el-progress>
                        <span>{{
                            promptStatement(
                                fileUid[scope.row.uid].totalProgress
                            )
                        }}</span>
                    </div>
                    <div class="upload-progress" v-else>
                        <el-progress
                            style="width: 150px"
                            :percentage="scope.row.totalProgress"
                        ></el-progress>
                        <span>{{
                            promptStatement(scope.row.totalProgress)
                        }}</span>
                    </div>
                </template>
            </el-table-column>
            <el-table-column
                align="center"
                :label="$t('public.operation')"
                width="80"
            >
                <template slot-scope="scope">
                    <el-button
                        type="text"
                        @click="handleDelete(scope.$index)"
                        icon="el-icon-delete"
                        style="color: #f56c6c"
                    ></el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>

<script>
import { getToken } from "@/utils/auth";
import { checkMd5, uploadChunk, mergeChunks } from "@/api/dataManage/dataInfo";
import SparkMD5 from "spark-md5";
export default {
    name: "FileUpload",
    props: {
        // 数量限制
        limit: {
            type: Number,
            default: 5,
        },
        // 大小限制(MB)
        fileSize: {
            type: Number,
            default: 5,
        },
        // 文件类型, 例如['png', 'jpg', 'jpeg']
        fileType: {
            type: Array,
            default: () => ["doc", "xls", "ppt", "txt", "pdf"],
        },
    },
    data() {
        return {
            // 上传的数量
            number: 0,
            uploadList: [],
            action: process.env.VUE_APP_BASE_API + "/minio/upload", // 上传服务器地址
            headers: {
                Authorization: "Bearer " + getToken(),
            },
            getToken: getToken(),
            fileList: [],
            // 分片大小为50MB
            chunkSize: 1024 * 1024 * 50,
            // 上传的文件的uid合集,用于处理进度条与块数
            fileUid: {},
            // 上传前的展示数据
            beforeUploadingDisplayData: [],
        };
    },
    methods: {
        // 转换表格中单位
        getUnitSize(value) {
            if (value === null || value === undefined) {
                return "";
            }
            const units = ["B", "KB", "MB", "GB", "TB", "PB"];
            let index = 0;
            while (value >= 1024 && index < units.length - 1) {
                value /= 1024;
                index++;
            }

            return `${value.toFixed(2)} ${units[index]}`;
        },
        // 上传文件之前的钩子
        beforeUpload(file) {
            var beforeUploadFile = {
                uid: file.uid,
                fileName: file.name,
                fileSize: file.size,
                totalProgress: 0,
            };
            this.beforeUploadingDisplayData.push(beforeUploadFile);

            if (file.size < this.chunkSize) {
                this.number++;
                return true;
            } else {
                this.uploadFile(file);
                return false;
            }
        },
        // 小文件超出个数限制时的钩子
        onExceed() {
            this.$modal.msgError(
                this.$t("upload.fileNumber", {
                    num: this.limit,
                })
            );
        },
        // 小文件上传失败时的钩子
        onError(err) {
            this.$modal.msgError(this.$t("upload.uploadFailed"));
        },
        // 小文件上传成功时的钩子
        onSuccess(res) {
            this.fileProgress(res.data);
            this.uploadList.push(res.data);
            if (this.uploadList.length === this.number) {
                this.fileList = this.fileList.concat(this.uploadList);
                this.uploadList = [];
                this.number = 0;

                if (
                    this.beforeUploadingDisplayData.length ==
                    this.fileList.length
                ) {
                    this.$modal.msgSuccess(this.$t("upload.uploadSuccessful"));
                    this.$emit("input", this.fileList);
                }
            }
        },
        // 小文件进度
        fileProgress(res) {
            this.beforeUploadingDisplayData.forEach((i) => {
                if (i.fileName == res.fileName && i.fileSize == res.fileSize) {
                    i.totalProgress = 100;
                }
            });
        },
        // 删除文件
        handleDelete(index) {
            this.fileList.splice(index, 1);
            this.beforeUploadingDisplayData.splice(index, 1);
            this.$emit("input", this.fileList);
        },
        // 大文件上传
        async uploadFile(file) {
            if (!file) {
                return;
            }
            const uid = file.uid;

            var uploadFile = {};
            uploadFile.name = file.name;
            uploadFile.size = file.size;
            uploadFile.chunkList = null;
            uploadFile.file = file;
            uploadFile.uploadingStop = false;
            // 文件uid唯一, 防止对象变化了而视图未更改
            this.$set(this.fileUid, uid, {
                currentChunk: 0, // 当前块
                totalChunks: 0, // 总块数
                totalProgress: 0, // 进度
            });
            // 获取md5值
            var md5 = await this.computeMd5(file, uploadFile);
            var res = await checkMd5(md5);
            // 库中有了相应的md5数据
            if (res.data) {
                // 总进度100
                this.fileUid[uid].totalProgress = 100;
                let arr = [res.data];
                this.fileList = this.fileList.concat(arr);
                if (
                    this.beforeUploadingDisplayData.length ==
                    this.fileList.length
                ) {
                    this.$modal.msgSuccess(this.$t("upload.uploadSuccessful"));
                    this.$emit("input", this.fileList);
                }
                return;
            }
            // 库中没有,则分片,上传
            const chunkRequests = [];
            const list = [];
            // 计算出多少总块
            this.fileUid[uid].totalChunks = Math.ceil(
                file.size / this.chunkSize
            );
            // 遍历总块,开始上传
            for (let i = 0; i < this.fileUid[uid].totalChunks; i++) {
                const start = i * this.chunkSize;
                const end = Math.min(start + this.chunkSize, file.size);
                const chunk = file.slice(start, end);
                const formData = new FormData(); // 封装成form表单
                formData.append("index", i); // 那一块
                formData.append("file", chunk); // 那一块的大小
                formData.append("chunkTotal", this.fileUid[uid].totalChunks); // 总共多少块
                formData.append("chunkSize", this.chunkSize); // 多少兆为基准分的块
                formData.append("fileSize", file.size); // 上传文件的总大小
                formData.append("fileName", file.name); // 上传文件名称
                // 分片上传
                let data = uploadChunk(formData).then((res) => {
                    if (res.code === 200) {
                        this.fileUid[uid].currentChunk++;
                        if (
                            this.fileUid[uid].currentChunk >
                            this.fileUid[uid].totalChunks
                        ) {
                            this.fileUid[uid].currentChunk =
                                this.fileUid[uid].totalChunks;
                        }
                        const chunkProgress =
                            ((i + 1) / this.fileUid[uid].totalChunks) * 0.7; // 计算分片上传的进度
                        const totalProgress = Math.round(
                            (chunkProgress + 0.2) * 100
                        );
                        if (totalProgress > this.fileUid[uid].totalProgress) {
                            // 防止对象变化了而视图未更改
                            this.$set(
                                this.fileUid[uid],
                                "totalProgress",
                                totalProgress
                            );
                        }
                        // 存入每个上传片段的md5
                        list.push(res.md5);
                    }
                });
                chunkRequests.push(data);
            }
            Promise.all(chunkRequests)
                .then(() => {
                    // list放接口返回的结果,如果lise和分片总数一致,说明都已经上传完毕,否则中间有失败
                    if (list.length === this.fileUid[uid].totalChunks) {
                        // 把分片合片,获取路径
                        this.mergeChunks(
                            list,
                            this.fileUid[uid].totalChunks,
                            md5,
                            file,
                            uid
                        );
                    } else {
                        this.$modal.msgWarning("上传失败,重新上传");
                    }
                })
                .catch((error) => {
                    console.error(error);
                    // 处理错误情况
                });
        },
        // 计算文件获取Md5
        computeMd5(file, uploadFile) {
            return new Promise((resolve, reject) => {
                const chunkSize = this.chunkSize;
                const fileReader = new FileReader();
                const md5 = new SparkMD5();
                let index = 0;

                const loadFile = (uploadFile) => {
                    const slice = file.slice(index, index + chunkSize);
                    fileReader.readAsBinaryString(slice);
                };

                loadFile(uploadFile);

                fileReader.onload = (e) => {
                    md5.appendBinary(e.target.result);
                    if (index < file.size) {
                        index += chunkSize;
                        loadFile(uploadFile);
                    } else {
                        resolve(md5.end());
                    }
                };
            });
        },
        // 检查文件是否已经上传
        checkMd5(md5) {
            getSoftwareVersion(md5).then((response) => {
                return respose;
            });
        },
        // 合片获取文件路径
        mergeChunks(list, totalChunks, md5, file, uid) {
            const formattedList = list.join(",");
            const formData = new FormData();
            formData.append("md5s", formattedList);
            formData.append("chunkTotal", totalChunks);
            formData.append("fileSize", file.size);
            formData.append("fileName", file.name);
            formData.append("fileMd5", md5);

            mergeChunks(formData).then((res) => {
                if (res.code === 200) {
                    let arr = [res.data];
                    this.fileList = this.fileList.concat(arr);
                    this.fileUid[uid] = {
                        currentChunk: 0,
                        totalChunks: 0,
                        totalProgress: 100,
                    };
                    if (
                        this.beforeUploadingDisplayData.length ==
                        this.fileList.length
                    ) {
                        this.$modal.msgSuccess(
                            this.$t("upload.uploadSuccessful")
                        );
                        this.$emit("input", this.fileList);
                    }
                }
            });
        },
        // 提示语句
        promptStatement(totalProgress) {
            if (totalProgress == 0) {
                return "校验中"; //"校验中"
            } else if (totalProgress == 100) {
                return "已上传"; // "已上传"
            } else {
                return "上传中"; //"上传中"
            }
        },
        // 删除文件之前的钩子
        beforeRemove(file, fileList) {},
    },
};
</script>

<style scoped lang="scss">
::v-deep .el-link--inner {
    display: flex;
    align-items: center;
}
.upload-progress {
    display: flex;
    align-items: center;
}
</style>

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泰日天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值