实现大文件分片上传,多文件上传,进度管控

直接附上代码具体内容代码中注释都有,这个只是上传的模板,具体请求和要传递的参数需要自己和后端去对接

使用spark-md5 根据文件的 ArrayBuffer  格式将文件的内容生成一个hash值,来表示文件的信息

<template>
  <div class="box">
    <div class="upload-box">
      <!-- 通过accept 来限制文件类型,需要用逗号将文件后缀名隔开 -->
      <!-- <input ref="inp" type="file" style="display: none" @change="inpChange" accept=".png,.jpg"/> -->
      <input
        ref="inp"
        type="file"
        style="display: none"
        @change="inpChange"
        multiple
      />
      <div class="operate">
        <div>
          <el-button type="primary" :loading="isUploading" @click="clickHandler"
            >选择文件</el-button
          >
          <el-button type="success" :loading="isUploading" @click="uploadHandler"
            >上传文件</el-button
          >
        </div>
        <el-button
          type="text"
          @click="emptyHandler"
          v-if="this.files.length != 0"
          :disabled="isUploading"
          >清空</el-button
        >
      </div>
      <div class="list">
        <div
          class="upload-show"
          v-for="(item, index) in files"
          :key="item.file.name"
        >
          <div class="icon-progress-box">
            <i
              class="el-icon-document transform icon"
              style="font-size: 20px"
            ></i>
            <div class="upload_progress">
              <!--进度条 -->
              <div
                class="upload_progress-item"
                :style="{ width: item.progress + '%' }"
              ></div>
              <span class="chart">{{ item.file.name }}</span>
            </div>
          </div>

          <el-button
            type="text"
            @click="delHandler(index)"
            style="color: red"
            class="transform"
            :disabled="isUploading||item.state"
            >移除</el-button
          >
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import SparkMD5 from "spark-md5";
export default {
  name: "APP",
  data() {
    return {
      files: [], // 存储选中的文件及其上传进度
      isUploading: false, // 上传状态
      completeList:[],//已经上传完成的数据
    };
  },
  methods: {
    clickHandler() {
      // 触发文件输入框的点击事件
      this.$refs.inp.click();
    },
    // 将文件转换为 ArrayBuffer 格式,并生成 MD5 哈希值
    changeBuffer(file) {
      return new Promise((resolve) => {
        let fileReader = new FileReader();
        fileReader.readAsArrayBuffer(file); // 读取文件为 ArrayBuffer
        fileReader.onload = (e) => {
          let buffer = e.target.result; // 获取文件的 ArrayBuffer
          const spark = new SparkMD5.ArrayBuffer();
          spark.append(buffer); // 计算 MD5 哈希值
          let hash = spark.end();
          let suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1]; // 获取文件后缀名
          resolve({
            hash,
            suffix,
            filename: `${hash}.${suffix}`, // 生成新的文件名
          });
        };
      });
    },
    // 处理文件选择事件
    async inpChange(e) {
      console.log('选择文件事件对象',e.target.files)
      // 将选中的文件转为数组,并为每个文件添加一个进度属性
      const arr = Array.from(e.target.files).map((file) => ({
        file,
        progress: 0,
      }));
      console.log(77777,arr)
      const tempArr = [...this.files, ...arr];
      this.files = Array.from(new Set(tempArr.map((f) => f.file.name))).map(
        (name) => {
          return tempArr.find((f) => f.file.name === name);
        }
      );

      console.log(this.files); // 输出选中的文件信息
    },
    // 删除选中的文件
    delHandler(type) {
      this.files = this.files.filter((item, index) => {
        return index != type;
      });
      this.$refs.inp.value = ''; // 重置文件输入框的值,input的change 事件
    },
    // 清空文件列表
    emptyHandler() {
      this.files = this.files.filter(item=>item.state);
      this.$refs.inp.value = ''; // 重置文件输入框的值
    },
    // 上传单个文件的分片
    async uploadFile(fileObj) {
      console.log(6666666,fileObj)
      if (fileObj.state) {
        alert("该文件已上传");
        return;
      }
      let { file } = fileObj;
      let { hash, suffix } = await this.changeBuffer(file); // 获取文件的哈希值和后缀名
      //
      console.log(111, hash);
      console.log(111, suffix);

      let chunks = []; // 文件分片数组
      let max = 1024 * 1024; // 分片大小,1MB
      let count = Math.ceil(file.size / max); // 分片数量

      // 创建文件分片
      for (let i = 0; i < count; i++) {
        chunks.push({
          file: file.slice(i * max, (i + 1) * max),
          filename: `${hash}_${i}.${suffix}`, // 生成分片文件名
        });
      }

      console.log(chunks);
      // 上传分片的递归函数
      const uploadChunk = async (index) => {
        if (index >= chunks.length) {
          // 此处代表分片上传成功进行merag 合并
          console.log(`${file.name} 上传完成`); // 输出上传完成信息
          return;
        }

        let item = chunks[index]; //通过索引获取到每一片分片的blob
        let fm = new FormData();
        fm.append("file", item.file); // 将分片文件添加到 FormData
        fm.append("filename", item.filename); // 添加分片文件名到 FormData
        // 模拟上传,实际应用中应替换为 HTTP 请求
        await new Promise((resolve) => setTimeout(resolve, 100)); // 模拟网络延迟
        // 更新进度,去到最小值,当全部上传后进度条,为100
        fileObj.progress = Math.min(((index + 1) / count) * 100, 100);

        // 递归上传下一个分片
        await uploadChunk(index + 1);
      };

      // 开始上传第一个分片
      await uploadChunk(0);
    },
    // 触发文件上传
    async uploadHandler() {
      if (this.files.length === 0) {
        alert("请先选择文件"); // 如果没有选择文件,提示用户
        return;
      }

      this.isUploading = true; // 开始上传时设置loading状态
      const uploadPromises = this.files.map((fileObj) =>
        this.uploadFile(fileObj)
      );

      await Promise.all(uploadPromises).then(() => {
        this.isUploading = false; // 上传完成后移除loading状态
        this.files.forEach((item) => {
          item.state = true;
        });
        this.completeList = [...this.files]
      }); // 等待所有文件上传完成
      console.log('completeList',this.completeList)
    },
  },
};
</script>

<style scoped>
.box {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 20px;
}
.upload-box {
  width: 550px;
  height: 260px;
  border: 1px dashed #000;
  padding: 10px;
}
.operate {
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.list {
  width: 100%;
  height: calc(100% - 60px);
  overflow: auto;
  margin-top: 10px;
}
.transform {
  transform: translateY(5px);
}
.upload-show {
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 10px;
  border-bottom: 1px solid #bbb;
}
.icon-progress-box {
  display: flex;
  align-items: center;
}
.icon {
  font-size: 20px;
  margin: 0 10px 0 15px ;
}
.upload_progress {
  width: 430px;
  height: 25px;
  background-color: #e2e2e2;
  position: relative;
  margin-top: 10px;
  border-radius: 10px;
}
.upload_progress-item {
  height: 100%;
  border-radius: 10px;
  background-color: #8ee700;
  position: absolute;
  bottom: 0%;
  top: 0;
  right: 0;
  left: 0;
}
.chart {
  height: 100%;
  position: absolute;
  bottom: 0%;
  top: 0;
  right: 0;
  left: 0;
  z-index: 9;
  display: flex;
  align-items: center;
  padding-left: 20px;
  font-size: 14px;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值