Vue3.0 大文件上传

Vue3.0 大文件上传

组件:

components>fastloader.vue

<template>
  <div class="uploadBox">
    <template v-if="state.file != null">
      <div class="fileName">{{ state.file?.name }}</div>
      <!-- {uploadBtn} -->
      <div v-if="state.initMd5Status" class="initMd5loading">
        正在校验md5中<img src="../../assets/images/loading_black.gif" />
      </div>
      <img
        v-if="!state.initMd5Status && state.uploadDone"
        src="../../assets/images/upload_done.svg"
        class="done"
      />
      <div
        v-if="!state.initMd5Status && !state.uploadDone"
        class="uploadStartOrStop"
      >
        <!-- {upStatusIcon} -->
        <img
          v-if="state.uploadStatus"
          src="../../assets/images/upload_stop.svg"
          :class="`iconfont icon-kaishi1 play`"
          @click="StopUploadChunks"
        />
        <img
          v-else
          src="../../assets/images/upload_play.svg"
          :class="`iconfont icon-kaishi1 play`"
          @click="handleUpload"
        />

        <svg
          class="circleBg"
          width="25"
          height="25"
          viewBox="0 0 25 25"
          xmlns="http://www.w3.org/2000/svg"
        >
          <circle
            cx="12"
            cy="12"
            r="8"
            fill="none"
            stroke="#eee"
            stroke-width="3.5"
          ></circle>

          <circle
            cx="12"
            cy="12"
            r="8"
            class="mycircle"
            transform="translate(0,24) rotate(-90)"
            :style="`stroke-dasharray:${state.currentUploadProgress} 51`"
          ></circle>
        </svg>
      </div>
      <img
        src="../../assets/images/upload_sel_delete_icon.svg"
        class="uploaddeleteIcon"
        @click="deleteSelFile"
      />
    </template>

    <div class="uploadBtn" v-else>
      <!-- {this.props.children} -->
      <span style="color: #999999; font-size: 12px">上传:</span>
      <img
        src="../../assets/images/upload_sel_icon.png"
        class="uploadBtnIcon"
      />
      <input type="file" @change="handleFileChange($event)" />
    </div>

    <!-- <div>{{ state.currentUploadProgress }}</div> -->
  </div>
</template>
<script>
import { post, postCancel } from "../../api/http";
import { API } from "../../api/index";
import { message, Table, Progress } from "ant-design-vue";
import SparkMD5 from "spark-md5";
import { reactive, ref } from "@vue/reactivity";
export default {
  name: "upload",
  props: {
    directionType: String,
  },
  setup(context, props) {
    console.log(context, props, "!!!!!!!!!!!!!!!!!!!");
    const state = reactive({
      file: null,
      chunkData: [],
      uploadStatus: false,
      uploadDone: false,
      uploadInfos: null,
      currentUploadProgressIndex: 0,
      currentUploadProgress: 0,
      initMd5Status: false,

      requestData: [],
      location: "",
    });
    const filemd5init = (files) => {
      return new Promise((resolve, reject) => {
        var blobSlice = File.prototype.slice,
          file = files,
          chunkSize = 10 * 1024 * 1000, // Read in chunks of 2MB
          chunks = Math.ceil(file.size / chunkSize),
          currentChunk = 0,
          spark = new SparkMD5.ArrayBuffer(),
          fileReader = new FileReader();

        fileReader.onload = function (e) {
          //console.log('read chunk nr', currentChunk + 1, 'of', chunks);
          spark.append(e.target.result); // Append array buffer
          currentChunk++;

          if (currentChunk < chunks) {
            loadNext();
          } else {
            //console.log('finished loading');

            resolve({
              md5: spark.end(),
            });
          }
        };

        fileReader.onerror = function (e) {
          reject(e);
          //console.warn('oops, something went wrong.');
        };

        function loadNext() {
          let start = currentChunk * chunkSize,
            end =
              start + chunkSize >= file.size ? file.size : start + chunkSize;

          fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
        }

        loadNext();
      });
    };
    const timerNum = ref(0);
    let timer;
    const mergeRequest = async () => {
      let params = new URLSearchParams();
      params.append("fileName", state.file.name);
      params.append("identifier", state.file._hashmd5);
      params.append("locationType", "LOCAL");
      params.append("refProjectId", state.file.name);
      params.append("size", state.file.size);
      params.append("directionType", context.directionType);

      console.log(params, "!!!!!!333333");
      post(API.uploader.mergeFile, {
        data: params,
      }).then((res) => {
        timer = setInterval(async () => {
          post(API.uploader.selectFileStatus, {
            data: params,
          }).then((res) => {
            if (res.data.fileStatus == "PUSH_FINISH") {
              //清除暂停信息
              state.uploadDone = true;
              state.uploadInfos = null;
              state.uploadStatus = false;
              state.currentUploadProgressIndex = 0;
              state.currentUploadProgress = 0;
              state.location = res.data.relativePath;

              props.emit("uploadSuccess", res.data, state.file, state.location);
              clearInterval(timer);
            } else if (res.data.fileStatus == "PUSH_FAILURE") {
              state.uploadDone = false;
              state.uploadInfos = null;
              state.uploadStatus = false;
              state.currentUploadProgressIndex = 0;
              state.currentUploadProgress = 0;
            }
          });
        }, timerNum.value * 1000);
      });
    };

    const StopUploadChunks = () => {
      //暂停所有请求

      state.uploadStatus = false;

      const index = state.currentUploadProgressIndex;

      const alllength = state.requestData.length;

      for (let i = Number(index) + 1; i < alllength; i++) {
        state.requestData[i].cancel();
      }
      // for (let i = 0; i < state.requestData.length; i++) {
      //   state.requestData[i].cancel();
      // }
      //将暂停的上传信息存到localstorage 暂时先做临时的,关闭浏览器刷新页面就全部重新上传
      //保存的是暂停时候的循环的当前上传文件
      // let currentFileInfos = new Map();
      // currentFileInfos.set(arrdata[i].filename, arrdata[i].chunkNumber);
      // this.setState({
      //     uploadInfos: currentFileInfos
      // })
    };

    const deleteSelFile = () => {
      state.file = null;
      state.chunkData = [];
      state.uploadStatus = false;
      state.uploadDone = false;
      state.uploadInfos = null;
      state.currentUploadProgressIndex = 0;
      state.currentUploadProgress = 0;
      timer && clearInterval(timer);
      console.log(state, "state");
      StopUploadChunks();
    };

    const uploadChunks = async (arrdata) => {
      // if (arrdata.length === 0) {
      //   state.uploadInfos = null;
      //   state.uploadStatus = false;
      //   state.uploadDone = true;
      //   console.log("nmmmmmmmmmmb");
      // }
      let promiseReq = [];
      for (let i = 0; i < arrdata.length; i++) {
        let formDataorigin = new FormData();
        formDataorigin.append("identifier", arrdata[i].identifier);
        formDataorigin.append("chunkNumber", arrdata[i].chunkNumber);
        formDataorigin.append("chunkSize", arrdata[i].chunkSize);
        formDataorigin.append("currentChunkSize", arrdata[i].currentChunkSize);
        formDataorigin.append("totalSize", arrdata[i].totalSize);
        formDataorigin.append("filename", arrdata[i].filename);
        formDataorigin.append("relativePath", arrdata[i].relativePath);
        formDataorigin.append("totalChunks", arrdata[i].totalChunks);
        formDataorigin.append("file", arrdata[i].file);
        formDataorigin.append("directionType", context.directionType);

        // let fetchReq = this.request.uploadChunkFile(formDataorigin);
        console.log(formDataorigin, "!!!!!!");

        //请求数据
        let fetchReq = postCancel(API.uploader.uploadChunkFile, {
          data: formDataorigin,
        });

        //收集axios对象
        promiseReq.push(
          fetchReq.send().then((res) => {
            if (res.status == 200) {
              state.currentUploadProgressIndex =
                state.currentUploadProgressIndex + 1;
              state.currentUploadProgress =
                (state.currentUploadProgressIndex / state.chunkData.length) *
                51;
              return Promise.resolve(res);
            } else {
              return Promise.reject(res);
            }
          })
        );

        let tmpReqArr = state.requestData;
        tmpReqArr.push(fetchReq.abort);
        state.requestData = tmpReqArr;
      }
      //进行并发请求
      Promise.all(promiseReq)
        .then((values) => {
          console.log(values);
          mergeRequest();
        })
        .catch((reason) => {
          console.log(reason);
        });
    };

    const createFileChunk = (file, size) => {
      const fileChunkList = [];
      let cur = 0;
      while (cur < file.size) {
        fileChunkList.push({ file: file.slice(cur, cur + size) });
        cur += size;
      }

      return fileChunkList;
    };

    const handleUpload = async () => {
      if (!state.file) return;

      //console.log("文件:", this.state.file);

      //如果存在暂停信息那么就从暂停项开始截取上传数组并且继续传 否则全部重新赋予上传数组
      // if (this.state.uploadInfos) {

      //     let currentFileData = this.state.chunkData.slice(parseInt(this.state.uploadInfos.get(this.state.file?.name)))
      //     this.setState({
      //         // chunkStopData: currentFileData,
      //         uploadStatus: true
      //     }, async () => {
      //         //console.log("续传数组", currentFileData)
      //         await this.uploadChunks(currentFileData);
      //     })

      // } else {

      const fileChunkList = createFileChunk(state.file, 2048000);
      let data = fileChunkList.map(({ file }, index, array) => ({
        chunk: file,
        hash: state.file.name + "-" + index, // 文件名 + 数组下标
        identifier: state.file._hashmd5,
        chunkNumber: index + 1,
        chunkSize: file.size,
        currentChunkSize: file.size,
        totalSize: state.file.size,
        filename: state.file.name,
        relativePath: state.file.name,
        totalChunks: fileChunkList.length,
        file: file,
      }));
      state.chunkData = data;
      state.uploadStatus = true;

      //     //console.log("开始数组", data)
      //     //判断穿过的文件跳过(截取未上传的startindex->last)

      let formDataorigin = new FormData();
      formDataorigin.append("identifier", data[0].identifier);
      formDataorigin.append("chunkNumber", data[0].chunkNumber);
      formDataorigin.append("chunkSize", data[0].chunkSize);
      formDataorigin.append("currentChunkSize", data[0].currentChunkSize);
      formDataorigin.append("totalSize", data[0].totalSize);
      formDataorigin.append("filename", data[0].filename);
      formDataorigin.append("relativePath", data[0].relativePath);
      formDataorigin.append("totalChunks", data[0].totalChunks);
      formDataorigin.append("file", data[0].file);
      formDataorigin.append("directionType", context.directionType);
      //可能存在过此文件.如果存在则需要进行妙传功能

      console.log(formDataorigin, "!!!!!!22222", data);
      post(API.uploader.uploadChunkFile, {
        data: formDataorigin,
      })
        .then((res) => {
          if (res.data.ifAllExist) {
            state.uploadInfos = null;
            state.uploadStatus = false;
            state.uploadDone = true;
            // props.uploadSuccess(null, state.file, res.location);
            console.log(props, "props");
            props.emit("uploadSuccess", null, state.file, res.data.location);
            return;
          }

          //如果存在那么就进行跳过,并且截取未上传的文件,然后开始上传
          if (res.data.chunkInfoDto?.chunkNumber) {
            let currentFileData = state.chunkData.slice(
              parseInt(res.data.chunkInfoDto.chunkNumber)
            );
            state.currentUploadProgressIndex =
              res.data.chunkInfoDto.chunkNumber + 1;
            state.currentUploadProgress =
              (state.currentUploadProgressIndex / state.chunkData.length) * 51;

            uploadChunks(currentFileData);
          } else {
            console.log("不存在走全部", data);
            state.currentUploadProgressIndex =
              state.currentUploadProgressIndex + 1;
            state.currentUploadProgress =
              (state.currentUploadProgressIndex / state.chunkData.length) * 51;
            uploadChunks(data.slice(1));
          }
        })
        .catch((res) => {
          message.error(res);
        });
    };

    const handleFileChange = async (e) => {
      console.log(e);
      const [file] = e.target.files;

      if (!file) return;
      state.initMd5Status = true;
      state.file = file;

      const timerReqNum = file.size / 1024 / 1024 / 1024;
      if (timerReqNum <= 0.5) {
        timerNum.value = 3;
      } else if (timerReqNum >= 0.5 && timerReqNum <= 1) {
        timerNum.value = 15;
      } else if (timerReqNum >= 1 && timerReqNum <= 2) {
        timerNum.value = 30;
      } else if (timerReqNum >= 2) {
        timerNum.value = 40;
      }

      console.log(file);
      //校验md5
      // let md5data: any = await this.filemd5init(file);

      file._hashmd5 = file.name + file.lastModified;
      state.initMd5Status = false;
      state.file = file;
    };
    return {
      state,
      filemd5init,
      mergeRequest,
      StopUploadChunks,
      deleteSelFile,
      uploadChunks,
      createFileChunk,
      handleUpload,
      handleFileChange,
    };
  },
};
</script>
<style lang="less" scope>
.mycircle {
  stroke: #1890ff;
  fill: rgba(0, 0, 0, 0);
  stroke-width: 2.8px;
  stroke-dasharray: 0 51;
}

.uploadBox {
  display: flex;
  justify-content: flex-start;
  align-items: center;

  .fileName {
    margin-left: 10px;
    font-size: 14px;
    font-weight: 200;
    color: #8c8c8c;
  }

  .initMd5loading {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    font-size: 12px;
    margin-left: 10px;
    color: #8c8c8c;
    img {
      margin-left: 5px;
      width: 20px;
      height: 20px;
      background-size: cover;
    }
  }

  .done {
    width: 20px;
    height: 20px;
    margin-left: 10px;
  }

  .uploadStartOrStop {
    width: 25px;
    height: 25px;
    margin-left: 10px;
    position: relative;

    .play {
      width: 9px;
      height: 9px;
      color: #1890ff;
      position: absolute;
      left: 50%;
      top: 50%;
      z-index: 2;
      transform: translate(-50%, -50%);
      display: flex;
      justify-content: center;
      align-items: center;
    }

    .circleBg {
      position: absolute;
      left: 50%;
      top: 50%;
      z-index: 1;
      transform: translate(-50%, -50%);
    }
  }
}

.tips {
  text-anchor: middle;
  dominant-baseline: middle;
  font-size: 30px;
  font-family: Arial;
}

.uploadBtn {
  position: relative;
  width: 100px;
  height: 30px;
  border-radius: 18px;
  border: 2px solid #eee;
  font-size: 15px;
  font-weight: bold;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;

  .uploadBtnIcon {
    width: 17px;
    height: 14px;
    background-size: cover;
  }

  input {
    position: absolute;
    right: 0;
    top: 0;
    opacity: 0;
    width: 100%;
    height: 100%;
    cursor: pointer;
    z-index: 1;
  }
}

.uploaddeleteIcon {
  width: 20px;
  height: 16px;
  margin-left: 10px;
}
</style>

使用:

Fastloader.vue:
通过传不同的directionType来选择不同的类型

<template>
  <div class="Fastloader">
    <UploadBigFile
      class="uploadSlot"
      directionType="METEORO_GRIB2"
      @uploadSuccess="uploadSuccess"
    ></UploadBigFile>
  </div>
</template>

<script>
// @ is an alias to /srca
import Fastloader from "./Fastloader";
export default Fastloader;
</script>
<style lang="less" scoped>
.Fastloader {
  margin-top: 20px;
}
</style>

Fastloader.js:

import UploadBigFile from '../../../../components/fastloader/fastloader';
export default {
    name: "Fastloader",
    components: {
        UploadBigFile
    },
    setup(props) {
        const uploadSuccess = (e, file, location) => {
            console.log("啊啊啊啊啊哦哦哦哦哦哦", e, file, location)
        }
        return {
            uploadSuccess
        }
    }
};

效果:
上传前:
在这里插入图片描述
选择文件:
在这里插入图片描述
上传中:
在这里插入图片描述
上传完成:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值