vue分片上传文件功能

vue分片上传文件功能

参考https://blog.csdn.net/qq_41579104/article/details/124456211?spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaiduRate-6-124456211-blog-127953473.235%5Ev33%5Epc_relevant_increate_t0_download_v2_base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaiduRate-6-124456211-blog-127953473.235%5Ev33%5Epc_relevant_increate_t0_download_v2_base&utm_relevant_index=11 然后自己做了大量修改
支持大文件上传,文件批量上传、因为是对文件分片请求上传,请求数量巨大,对请求并发数进行了控制。并进行了错误处理(分片上传请求失败,会监听然后重新进行失败分片的上传请求) 文件上传增加了解析md5的进度展示以及上传文件的进度条和上传速度的展示 ,(解析文件MD5用了‘spark-md5’,可以先npm i spark-md5 --save)直接上代码:
效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<template>
  <div class="test-info" style="line-height: 25px; margin-top: 10px">
    <el-form
      :inline="true"
      :model="dataForm"
      @keyup.enter.native="getFileUploadList(true)"
    >
      <el-form-item>
        <el-input
          size="mini"
          class="selects"
          v-model="dataForm.fileName"
          placeholder="文件名称"
          clearable
        ></el-input>
      </el-form-item>
      <el-form-item>
        <el-select
          size="mini"
          class="selects"
          v-model="dataForm.creator"
          placeholder="上传者"
          filterable
          clearable
        >
          <el-option
            v-for="item in dataForm.testPrincipalList"
            :label="item.userName"
            :value="item.userAccount"
            :key="item.id"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button
          size="mini"
          class="btns"
          type="primary"
          plain
          style="height: 35px"
          @click="getFileUploadList(true)"
          >查询</el-button
        >
      </el-form-item>
      <el-form-item>
        <el-button
          size="mini"
          class="btns"
          style="height: 35px"
          @click="reset()"
          >重置</el-button
        >
      </el-form-item>
    </el-form>
    <div style="margin: 0 0 10px 0">
      <el-button size="mini" type="primary" @click="fileDialogVisible = true"
        >上传文件</el-button
      >
    </div>
    <el-table
      :data="fileDataList"
      border
      v-loading="dataListLoading"
      style="width: 99.6%"
      :header-cell-style="{ background: '#87CEEB', color: '#606266' }"
      ref="table"
    >
      <!-- :height="tableMaxHeight" -->
      <af-table-column
        type="index"
        :index="1"
        prop="sequence"
        header-align="center"
        align="center"
        width="60"
        label="序号"
      >
      </af-table-column>
      <!-- <af-table-column
        type="index"
        :index="1"
        prop="uploadId"
        header-align="center"
        align="center"
        width="100"
        label="uploadId"
      >
      </af-table-column> -->
      <af-table-column
        prop="submitTitle"
        header-align="center"
        align="center"
        width="250"
        label="文件名称"
      >
        <template slot-scope="scope">
          <el-popover
            width="280"
            placement="top-start"
            trigger="hover"
            :content="scope.row.fileName"
          >
            <span slot="reference" class="pop-hover">
              {{ scope.row.fileName }}
            </span>
          </el-popover>
        </template>
      </af-table-column>
      <af-table-column
        prop="fileIdentifier"
        header-align="center"
        align="center"
        width="280"
        label="文件md5"
      >
        <template slot-scope="scope">
          <el-popover
            width="290"
            placement="top-start"
            trigger="hover"
            :content="scope.row.fileIdentifier"
          >
            <span slot="reference" class="pop-hover">
              {{ scope.row.fileIdentifier }}
            </span>
          </el-popover>
        </template>
      </af-table-column>
      <af-table-column
        type="index"
        prop="uploadId"
        header-align="center"
        align="center"
        width="120"
        label="文件大小"
      >
        <template slot-scope="scope">
          <span v-if="scope.row.totalSize > 1024 * 1024 * 1024"
            >{{
              (scope.row.totalSize / 1024 / 1024 / 1024).toFixed(1)
            }}
            Gb</span
          >
          <span v-else-if="scope.row.totalSize > 1024 * 1024"
            >{{ (scope.row.totalSize / 1024 / 1024).toFixed(1) }} Mb</span
          >
          <span v-else-if="scope.row.totalSize > 1024"
            >{{ (scope.row.totalSize / 1024).toFixed(1) }} Kb</span
          >
          <span v-else>{{ scope.row.totalSize.toFixed(1) }} B</span>
        </template>
      </af-table-column>
      <!-- <af-table-column
        prop="qps"
        header-align="center"
        align="center"
        label="文件是否上传完成"
      >
        <template slot-scope="scope">
          <span v-if="scope.row.isFinished === 0">未完成</span>
          <span v-if="scope.row.isFinished === 1">已完成</span>
        </template>
      </af-table-column> -->
      <af-table-column
        prop="creatorName"
        header-align="center"
        align="center"
        label="上传者"
        width="120"
      >
      </af-table-column>
      <af-table-column
        prop="createTime"
        header-align="center"
        align="center"
        label="上传时间"
        :formatter="formatTimeStampCreate"
      >
      </af-table-column>
    </el-table>
    <el-pagination
      @size-change="sizeChangeHandle"
      @current-change="currentChangeHandle"
      :current-page="currentPage"
      :page-sizes="[10, 20, 50, 100]"
      :page-size="pageSize"
      :total="totalCount"
      layout="total, sizes, prev, pager, next, jumper"
    >
    </el-pagination>
    <el-dialog
      title="文件上传"
      :visible.sync="fileDialogVisible"
      width="40%"
      :close-on-click-modal="false"
      @close="dialogClose"
    >
      <div v-for="(item, index) in errorList" :key="index">
        <span style="color: red"
          >{{ item }}部分分片上传失败,请重新打开窗口上传</span
        >
      </div>
      <div
        class="myDiv"
        style="display: flex; justify-content: center; margin-bottom: 5px"
      >
        <!-- :file-list="fileList" -->
        <el-upload
          class="upload-demo"
          action="#"
          drag
          :on-change="uploadFile"
          :on-remove="removeFile"
          :show-file-list="true"
          :auto-upload="false"
          ref="uploadfiles"
          :multiple="true"
          :limit="10"
        >
          <i class="el-icon-upload"></i>
          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
          <!-- <div class="el-upload__tip" slot="tip">
        只能上传jpg/png文件,且不超过500kb
      </div> -->
        </el-upload>
      </div>
      <div
        style="width: 72%; margin: 4px auto"
        v-for="(item, index) in successList"
        :key="index"
      >
        <div v-if="!item.md5Name" class="cont">
          <div class="ball">
            <p>正在为 {{ item.fileName }} 计算md5值... {{ item.md5Speed }}%</p>
            <div class="ball1"></div>
            <div class="ball2"></div>
            <div class="ball3"></div>
          </div>
          <!-- <span>正在为传输 {{ item.fileName }} 做准备中...</span> -->
        </div>
        <div v-if="item.md5Name" style="position: relative">
          <el-progress
            :text-inside="true"
            :stroke-width="18"
            :percentage="item.progress"
          ></el-progress>
          <span
            v-if="item.progress != 100"
            style="font-size: 14px; float: right"
            >上传速度: {{ item.speed }} M / s
          </span>
          <span
            v-if="item.progress == 100"
            style="font-size: 14px; float: right"
            >上传速度: 0 M / s
          </span>
          <span style="font-size: 14px">{{ item.fileName }}</span>
          <!-- style="position: absolute; right: -120px; top: -2px" -->
        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import SparkMD5 from 'spark-md5'
import moment from 'moment'
const chunkSize = 10 * 1024 * 1024 //定义分片的大小 暂定为10M,方便测试
// const controller = new AbortController()
// const signal = controller.signal
import {
  storageUploadExist,
  storageUploadinitTask,
  storageUploadfier,
  storageUploadmerge,
  storageUploadGet,
  chunkNumUpload
} from '@/api/fileUpload.js'
import { deepCopy, RequestQueue } from '@/utils/utils'
// let token = localStorage.getItem('tokenStr')
// let account = localStorage.getItem('userAccount')
export default {
  name: 'fileUpload',
  components: {},
  props: {},
  data() {
    return {
      dataForm: {
        fileName: '',
        creator: '',
        testPrincipalList: [],
        testPrincipal: ''
      },
      fileDataList: [],

      dataList: [],
      testReportView: '',
      currentPage: 1,
      pageSize: 10,
      totalCount: 0,
      visible: false,
      dataListLoading: false,
      fileDialogVisible: false,

      fileList: [],
      loadingFile: false,
      progress: 0,
      showProgress: false,
      successList: [],
      totalList: [],
      fileMD5: '',
      progressList: [],
      resetList: [],
      listres: [],
      resultList: [],
      errorList: [],
      cancelList: [],
      repeatList: [],
      requestList: [],
      time: '',
      lastTime: ''
    }
  },
  computed: {
    tableMaxHeight() {
      return window.innerHeight - 200 + 'px'
    }
  },
  watch: {
    successList: {
      handler(newVal) {
        if (newVal.length) {
          this.showProgress = true
          newVal.forEach((item) => {
            this.totalList.forEach((ele) => {
              if (item.md5Name === ele.fileName) {
                if (!ele.total.length) {
                  item.progress = 100
                  // item.speed = 0
                } else {
                  item.progress = Math.floor(
                    (item.sucList.length / ele.total.length) * 100
                  )
                }

                if (item.sucList.length === ele.total.length) {
                  // console.log('监听合并上传')
                  this.resetList.push(item.md5Name)
                  this.listres = [...new Set(this.resetList)]
                  this.mergeFile(
                    this.listres[this.listres.length - 1],
                    item.fileName
                  )
                }
              }
            })
          })
        }
      },
      deep: true
    },
    // 监听分片失败上传,进行对失败分片的重新请求
    repeatList: {
      async handler(newVal) {
        let count = 0
        newVal.forEach((item, index) => {
          if (
            item.fileMD5 === newVal[newVal.length - 1].fileMD5 &&
            item.num === newVal[newVal.length - 1].num
          ) {
            count += 1
          }
        })
        if (count < 10 && newVal.length) {
          let fileMd5 = newVal[newVal.length - 1].fileMd5
          let num = newVal[newVal.length - 1].num
          let _chunkFile = newVal[newVal.length - 1]._chunkFile
          // let tokenStr = newVal[newVal.length - 1].tokenStr
          // let userAccount = newVal[newVal.length - 1].userAccount
          let fileName = newVal[newVal.length - 1].fileName
          let totalLength = newVal[newVal.length - 1].totalLength
          // const startTime = performance.now() // 记录开始时间
          this.uploadChunkFile(
            fileMd5,
            num,
            _chunkFile,
            // tokenStr,
            // userAccount,
            fileName,
            totalLength
            // startTime
          )
        } else {
          let flag = false
          let fileName = newVal[newVal.length - 1].fileName
          flag = this.errorList.some((item) => item === fileName)
          if (!flag) {
            this.errorList.push(fileName)
          }
        }
      },
      deep: true
    }
  },
  created() {
    // this.getFileUploadList(true)
    this.getAllUser()
    // console.log(this.$route.query.fileName, 111)
  },
  activated() {
    if (this.$route.query.fileName) {
      this.dataForm.fileName = this.$route.query.fileName
      this.getFileUploadList(true)
    } else {
      this.reset()
    }
  },
  methods: {
    reset() {
      this.dataForm.fileName = ''
      this.dataForm.creator = ''
      this.getFileUploadList(true)
    },
    // 获取文件列表
    getFileUploadList(flag) {
      this.dataListLoading = true
      if (flag) {
        this.currentPage = 1
        this.pageSize = 10
      }
      const form = {
        fileName: this.dataForm.fileName,
        creator: this.dataForm.creator,
        currentPage: this.currentPage,
        pageSize: this.pageSize
      }
      storageUploadGet(form)
        .then(({ data }) => {
          if (data && data.message.code === 200) {
            this.fileDataList = data.result.uploadTaskVos
            this.totalCount = data.result.totalCount
          } else if (data && data.message.code === 401) {
            this.$router.push({ name: 'login' })
          } else {
            this.dataList = []
            this.totalCount = 0
          }
          this.dataListLoading = false
        })
        .catch((err) => {
          console.log(err)
          this.$message.error('获取列表失败!')
        })
    },
    getAllUser() {
      this.$http({
        url: this.$http.adornUrl('/user/get'),
        method: 'get'
      }).then(({ data }) => {
        if (data && data.message.code === 200) {
          this.dataForm.testPrincipalList = data.result
        } else if (data && data.message.code === 401) {
          this.$router.push({ name: 'login' })
        } else {
          this.dataForm.testPrincipalList = []
        }
      })
    },
    formatTimeStampCreate(row) {
      return moment(row.createTime).format('YYYY-MM-DD HH:mm:ss')
    },
    sizeChangeHandle(val) {
      this.pageSize = val
      this.currentPage = 1
      this.getFileUploadList(false)
    },
    currentChangeHandle(val) {
      this.currentPage = val
      this.getFileUploadList(false)
    },
    /**
     * 上传文件
     */
    async uploadFile(File) {
      // console.log(File, 909)
      this.loadingFile = true
      let self = this
      //获取用户选择的文件
      const file = File.raw
      this.currentFile = file
      //文件大小(大于100m再分片哦,否则直接走普通文件上传的逻辑就可以了,这里只实现分片上传逻辑)
      const fileSize = File.size
      // 放入文件列表
      if (this.fileList.length) {
        const flag = this.fileList.some((item) => item.name === File.name)
        if (!flag) {
          this.fileList.push({ name: File.name })
        }
      } else {
        this.fileList.push({ name: File.name })
      }
      // return
      // 可以设置大于多少兆可以分片上传,否则走普通上传
      // if (fileSize <= chunkSize) {
      //   console.log('上传的文件大于20m才能分片上传')
      // }
      //计算当前选择文件需要的分片数量
      const chunkCount = Math.ceil(fileSize / chunkSize)
      console.log(
        '文件大小:',
        File.size / 1024 / 1024 + 'Mb',
        '分片数:',
        chunkCount
      )
      self.successList.push({
        fileName: File.name,
        md5Name: '',
        sucList: [],
        progress: 0,
        speed: 0,
        md5Speed: 0
        // time: '',
        // totalNum: chunkCount
      })
      const startTime = performance.now() // 记录开始时间
      //获取文件md5
      const fileMd5 = await this.getFileMd5(file, chunkCount, self.successList)
      const endTime = performance.now() // 记录结束时间
      const duration = (endTime - startTime) / 1000 // 计算时间差,单位为秒
      // console.log(duration, '获取md5的时间')
      self.fileMD5 = fileMd5
      // console.log('文件md5:', fileMd5)

      // console.log('向后端请求本次分片上传初始化')

      const initUploadParams = {
        identifier: fileMd5, //文件的md5
        fileName: File.name, //文件名
        totalSize: fileSize, //文件大小
        // totalChunks: chunkCount //分片的总数量
        chunkSize: chunkSize, //分片大小
        creator: localStorage.userAccount
      }
      // 调用后端检查文件上传情况接口
      this.time = performance.now()
      // return
      // localStorage.setItem('tokenStr', token)
      // localStorage.setItem('userAccount', account)
      storageUploadExist(fileMd5)
        .then(async ({ data }) => {
          self.showProgress = true
          if (data && data.message.code === 200) {
            //分片上传成功直接秒传
            if (data.result && data.result.finished === true) {
              // 所有分片上传成功且合并成功,直接秒传
              // console.log('当前文件上传情况:秒传')
              self.loadingFile = false
              this.$message.success(`${File.name}已经上传成功,无需再上传`)
              self.totalList.push({ fileName: fileMd5, total: [] })
              self.successList.forEach((item) => {
                if (item.fileName === File.name) {
                  item.md5Name = fileMd5
                  item.sucList = [1]
                }
              })
              // self.fileList = []
              // self.$refs.uploadFile.clearFiles()
              return false
            }
            if (!data.result) {
              const { data } = await storageUploadinitTask(initUploadParams)
              if (data && data.message.code === 200) {
                // self.$message.success('初始化上传任务成功!')
              } else if (data && data.message.code === 401) {
                self.$router.push({ name: 'login' })
              }
            }
            let list = []
            let totalList = []
            let sucList = []
            for (let i = 1; i <= chunkCount; i++) {
              list.push(i)
            }
            for (let i = 1; i <= chunkCount; i++) {
              totalList.push(i)
            }
            if (data.result && !data.result.finished) {
              // 获取后端返回的已上传分片数字的数组
              let uploaded = data.result.taskRecord.exitPartList.map((item) => {
                return item.partNumber
              })
              uploaded.forEach((item) => {
                sucList.push(1)
                list.forEach((ele, index) => {
                  if (item == ele) {
                    list.splice(index, 1)
                  }
                })
              })
            }
            // self.noUpload.push({ fileName: fileMd5, noUpload: list })
            self.totalList.push({ fileName: fileMd5, total: totalList })
            // let tokenStr = localStorage.getItem('tokenStr')
            // let userAccount = localStorage.getItem('userAccount')
            self.successList.forEach((item) => {
              if (item.fileName === File.name) {
                item.md5Name = fileMd5
                item.sucList = sucList
              }
            })
            // self.successList.push({
            //   fileName: File.name,
            //   md5Name: fileMd5,
            //   sucList: [],
            //   progress: 0
            // })
            const requestQueue = new RequestQueue(4) // 最多同时进行 4 个请求
            for (let i = 0; i < list.length; i++) {
              //分片开始位置
              let start = i * chunkSize
              //分片结束位置
              let end = Math.min(fileSize, start + chunkSize)
              //取文件指定范围内的byte,从而得到分片数据
              // console.log(File, '0000')
              let _chunkFile = File.raw.slice(start, end)
              // console.log(_chunkFile)
              // console.log('开始上传第' + i + '个分片')

              // 通过await实现顺序上传
              // await this.getMethods(formdata)
              // 并行上传
              // const startTime = performance.now() // 记录开始时间
              requestQueue.push(() =>
                self.uploadChunkFile(
                  fileMd5,
                  list[i],
                  _chunkFile,
                  // tokenStr,
                  // userAccount,
                  File.name,
                  // startTime,
                  chunkCount
                )
              )
            }
            // await self.uploadLoading(File, noUpload, fileSize, fileMd5)
            // 文件上传完毕,请求后端合并文件并传入参数

            // 定义分片开始上传的序号
            // 由于是顺序上传,可以判断后端返回的分片数组的长度,为0则说明文件是第一次上传,分片开始序号从0开始
            // 如果分片数组的长度不为0,我们取最后一个序号作为开始序号
            // let num = uploaded.length == 0 ? 0 : uploaded[uploaded.length - 1]
            // console.log(num, '分片开始序号')
            // 当前为顺序上传方式,若要测试并发上传,请将103 行 await 修饰符删除即可
            // 循环调用上传
          } else if (data && data.message.code === 401) {
            this.$router.push({ name: 'login' })
          }
        })
        .catch((err) => {
          this.$message.error('上传失败!')
        })
    },
    uploadChunkFile(
      fileMd5,
      num,
      _chunkFile,
      // tokenStr,
      // userAccount,
      fileName,
      // startTime,
      totalLength
    ) {
      const _that = this
      const startTime = performance.now() // 记录开始时间
      return new Promise((resolve, reject) => {
        _that
          .getMethods(fileMd5, num)
          .then(async (url) => {
            // localStorage.removeItem('tokenStr')
            // localStorage.removeItem('userAccount')
            chunkNumUpload(url, _chunkFile)
              .then((data) => {
                // console.log(data, '分片成功')
                if (data.status == 200) {
                  const endTime = performance.now() // 记录结束时间
                  const duration = (endTime - startTime) / 1000 // 计算时间差,单位为秒
                  // let leftTime = ''
                  // if ((totalLength - num) * duration > 60) {
                  //   leftTime =
                  //     (((totalLength - num) * duration) / 60).toFixed(1) + '分'
                  // } else {
                  //   leftTime =
                  //     ((totalLength - num) * duration).toFixed(1) + '秒'
                  // }
                  const fileSpeed = (10 / duration).toFixed(1) * 4
                  _that.successList.forEach((ele) => {
                    if (ele.md5Name === fileMd5) {
                      ele.sucList.push(1)
                      ele.speed = fileSpeed
                    }
                  })
                }
                resolve()
              })
              .catch((err) => {
                _that.repeatList.push({
                  fileMd5,
                  num,
                  _chunkFile,
                  // tokenStr,
                  // userAccount,
                  fileName,
                  totalLength
                })
                reject()
              })
          })
          .catch((err) => {
            console.log(err, '预签名错误')
          })
      })
    },
    /**
     * 上传文件方法
     * @param formdata 上传文件的参数
     */
    getMethods(identifier, partNumber) {
      return new Promise((resolve, reject) => {
        storageUploadfier(identifier, partNumber).then(({ data }) => {
          if (data && data.message.code === 200) {
            resolve(data.result)
          }
        })
      })
    },
    /**
     * 获取文件MD5
     * @param file
     * @returns {Promise<unknown>}
     */
    getFileMd5(file, chunkCount, list) {
      return new Promise((resolve, reject) => {
        let blobSlice =
          File.prototype.slice ||
          File.prototype.mozSlice ||
          File.prototype.webkitSlice
        let chunks = chunkCount
        let currentChunk = 0
        let spark = new SparkMD5.ArrayBuffer()
        let fileReader = new FileReader()

        let time = new Date().getTime()
        fileReader.onload = function (e) {
          spark.append(e.target.result)
          currentChunk++
          if (currentChunk < chunks) {
            // console.log(
            //   `第${currentChunk}分片解析完成, 开始第${
            //     currentChunk + 1
            //   } / ${chunks}分片解析`
            // )
            list.forEach((item) => {
              if (item.fileName === file.name) {
                item.md5Speed = Math.ceil((currentChunk / chunks) * 100)
              }
            })
            loadNext()
          } else {
            let md5 = spark.end()
            console.log(
              `MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
                file.size
              } 用时:${new Date().getTime() - time} ms`
            )
            spark.destroy() //释放缓存
            resolve(md5)
          }
        }
        fileReader.onerror = function (e) {
          reject(e)
        }
        function loadNext() {
          let start = currentChunk * chunkSize
          let end = start + chunkSize
          if (end > file.size) {
            end = file.size
          }
          fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
        }
        loadNext()
      })
    },
    /**
     * 请求后端合并文件
     * @param fileMd5 文件md5
     * @param fileName 文件名称
     * @param count 文件分片总数
     */
    async mergeFile(fileMd5, fileName) {
      // console.log('开始请求后端合并文件')
      const flag = this.resultList.some((item) => item === fileMd5)
      if (flag) {
        return
      }
      this.resultList.push(fileMd5)
      try {
        const { data } = await storageUploadmerge(fileMd5)
        if (data && data.message.code === 200) {
          this.$message.success(`${fileName}文件上传成功`)
          this.loadingFile = false
          this.getFileUploadList(true)
          this.lastTime = performance.now()
          console.log((this.lastTime - this.time) / 1000, 'timeLast')
          // this.$refs.uploadfile.clearFiles()
        } else {
          this.loadingFile = false
          this.$message.error(data.message)
        }
      } catch (err) {
        this.$message.error(`${fileName}文件上传失败`)
        this.$message.error(err)
      }
    },
    removeFile(file) {
      this.cancelList.push(file.name)
      const index = this.fileList.findIndex((item) => item.name === file.name)
      this.fileList.splice(index, 1)
      this.successList.forEach((item, index) => {
        if (item.fileName === file.name) {
          this.successList.splice(index, 1)
        }
      })
    },
    // 上传失败的文件自动重新请求上传
    dialogClose() {
      this.fileList = []
      this.$refs.uploadfiles.clearFiles()
      this.totalList = []
      this.successList = []
      this.cancelList = []
      this.errorList = []
    }
  }
}
</script>
<style lang="scss" scoped>
@import '@/views/tap.scss';
.ball {
  /*设置动画盒子的整体样式*/
  width: 90%; /*设置整体大小*/
  height: 90px;
  text-align: center; /*设置对齐方式*/
  // color: #fff; /*设置文字颜色*/
  // background: rgba(0, 0, 0, 0.5); /*设置背景颜色*/
  margin: 20px auto;
}

.ball > p {
  /*设置加载的提示文字的样式*/
  padding: 20px 0;
}

.ball > div {
  /*设置动画中三个小球的样式*/
  width: 18px; /*设置大小*/
  height: 18px;
  background: #1abc9c; /*设置背景颜色*/
  border-radius: 100%; /*设置圆角边框*/
  display: inline-block; /*设置其显示方式*/
  animation: move 1.4s infinite ease-in-out both; /*添加动画*/
}

.ball .ball1 {
  /*设置第一个小球的动画延迟*/
  animation-delay: 0.16s;
}

.ball .ball2 {
  /*设置第二个小球的动画延迟*/
  animation-delay: 0.32s;
}

.ball .ball3 {
  /*设置第二个小球的动画延迟*/
  animation-delay: 0.48s;
}

@keyframes move {
  /*创建动画*/
  0% {
    transform: scale(0);
  }
  40% {
    transform: scale(1);
  }
  100% {
    transform: scale(0);
  }
}
</style>


这个是对请求并发数进行控制的js代码(导入到vue文件中使用即可)
在这个的js代码中,我们创建了一个 RequestQueue 类,它接受一个参数 maxConcurrentRequests,
表示最多同时进行的请求数量。我们使用一个数组 pendingRequests 来存储等待处理的请求,
使用一个计数器 activeRequests 来记录当前正在进行的请求数量。
当我们调用 push 方法添加一个请求时,它会将请求添加到 pendingRequests 数组中,
并立即调用 processQueue 方法来处理请求队列。processQueue 方法会不断地从
pendingRequests 数组中取出请求,直到达到最大并发数或者队列为空为止。
对于每个请求,我们使用 Promise 来处理异步操作,并在请求完成后递减 activeRequests
计数器,然后再次调用 processQueue 方法来处理下一个请求。

// 并发请求数量控制
export class RequestQueue {
  constructor(maxConcurrentRequests) {
    this.maxConcurrentRequests = maxConcurrentRequests
    this.pendingRequests = []
    this.activeRequests = 0
  }

  push(request) {
    this.pendingRequests.push(request)
    this.processQueue()
  }

  processQueue() {
    while (
      this.pendingRequests.length > 0 &&
      this.activeRequests < this.maxConcurrentRequests
    ) {
      const request = this.pendingRequests.shift()
      this.activeRequests++
      // setTimeout(() => {
      request().then(() => {
         this.activeRequests--
         this.processQueue()
       })
     // }, 1000)
    }
  }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
文件切片上传是一种将大文件分割成多个小片段进行上传的方法,这种方式可以有效地提高文件上传的效率和稳定性。在Vue中实现文件切片上传可以按照以下步骤进行: 1. 首先,需要在Vue项目中引入文件切片上传的插件或者封装的组件。可以选择一些已经成熟的插件,例如`vue-slice-upload`或`vue-slice-upload-component`。 2. 在Vue组件中,使用`<input type="file">`标签创建一个文件选择框,用于选择要上传的文件。 3. 监听文件选择框的`change`事件,获取选中的文件。 4. 利用文件对象的`slice`方法将文件切分成小片段。可以根据切片的大小,将文件分成固定大小的块。可以选择每个块的大小,常见的大小为1MB或者2MB。 5. 使用`FormData`对象创建一个空的表单对象。 6. 遍历切片后的文件块,将每个块添加到表单对象中,并需要设置一个标识符来表示这个块在整个文件中的位置。 7. 在表单对象中添加其他的参数,例如文件名、文件类型等。 8. 使用Vue的HTTP库(例如axios)发送表单数据到后端服务器。 9. 后端服务器接收到文件切片后,将每个切片存储到相应位置。 10. 后端服务器接收到所有切片后,将切片合并为完整的文件。 11. 后端完成文件合并后,返回给前端上传成功的响应。 12. 前端接收到上传成功的响应后,进行相应的提示或者跳转。 需要注意的是,在文件切片上传过程中,还需要处理上传失败、上传中断等异常情况,并进行相应的处理和提示。 以上就是在Vue中实现文件切片上传的基本流程,通过这种方式可以有效地提高大文件的上传速度和稳定性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值