大文件上传

使用vue+elementui实现

<template>
  <el-upload
    class="upload-demo"
    ref="upload"
    :action="uploadUrl" // 文件上传接口地址
    :headers="uploadHeaders" // 上传请求头部信息
    :with-credentials="true"
    :on-change="handleChange"
    :file-list="fileList"
    :data="uploadData"
    multiple
    :on-progress="handleUploadProgress"
    :on-success="handleUploadSuccess"
    :before-remove="handleFileRemove"
    @dragover.native.prevent // 防止浏览器默认行为
    @drop.native="handleDrop" // 文件拖拽事件
  >
    <slot>
      <div v-if="!showProgress">
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      </div>
      <el-progress :percentage="uploadPercentage" v-if="showProgress"></el-progress>
    </slot>
  </el-upload>
</template>

<script>
  export default {
    name: 'BigFileUpload',
    props: {
      uploadUrl: String, // 文件上传接口地址
      uploadHeaders: Object, // 上传请求头部信息
      uploadData: Object, // 上传附带数据
      chunkSize: Number, // 分块大小(字节)
      maxRetryTimes: Number, // 最大重试次数
      retryInterval: Number, // 重试间隔时间(毫秒)
      showSlot: Boolean // 是否显示插槽
    },
    data() {
      return {
        fileList: [], // 待上传的文件列表
        showProgress: false, // 是否显示上传进度条
        uploadPercentage: 0, // 上传进度百分比
        uploadInterrupted: false, // 上传是否被中断
        uploadedChunks: [] // 已经成功上传的块列表
      }
    },
    methods: {
      handleChange(file, fileList) {
        if (file.raw) { // 判断是否通过拖拽上传
          const totalSize = file.size
          const chunks = Math.ceil(totalSize / this.chunkSize) // 计算文件需要分成多少个块上传
          let currentChunk = 0 // 当前正在上传的块编号
          let start = 0
          let end = 0

          const reader = new FileReader()
          reader.readAsArrayBuffer(file)

          reader.onload = () => { // 当文件读取完成后开始逐个上传文件块
            const buffer = reader.result
            this.showProgress = true
            this.uploadInterrupted = false // 初始化上传状态

            const uploadNextChunk = () => {
              if (currentChunk === chunks) { // 如果所有块都已上传完成,清除进度条并重置已上传块列表
                this.showProgress = false
                this.uploadedChunks = []
                return
              }

              if (this.uploadInterrupted) { // 如果上传被中断,停止上传
                return
              }

              if (this.uploadedChunks.includes(currentChunk)) { // 如果当前块已上传过,跳过该块并继续上传下一个块
                currentChunk++
                this.uploadPercentage = Math.round(currentChunk * 100 / chunks)
                uploadNextChunk()
                return
              }

              start = currentChunk * this.chunkSize // 计算当前块在文件中的起始和结束位置
              end = Math.min(start + this.chunkSize, totalSize)

              const formData = new FormData() // 创建表单对象,添加文件块和相关信息
              formData.append('chunkNumber', currentChunk)
              formData.append('totalChunks', chunks)
              formData.append('file', new Blob([buffer.slice(start, end)], { type: file.type }))

              this.$axios.post(this.uploadUrl, formData, { headers: this.uploadHeaders }) // 发送文件块到服务器
                .then(() => { // 如果上传成功,更新进度条并记录已上传块号
                  currentChunk++
                  this.uploadPercentage = Math.round(currentChunk * 100 / chunks)
                  this.uploadedChunks.push(currentChunk - 1)
                  uploadNextChunk()
                })
                .catch(error => { // 如果上传失败,记录错误并尝试重新上传该块
                  console.error(error)
                  setTimeout(() => {                this.retryUpload(formData, currentChunk, chunks)
              }, this.retryInterval)
            })
        }

        uploadNextChunk()
      }
    } else { // 通过点击上传按钮上传,直接将文件添加到待上传列表并等待用户触发上传操作
      this.fileList = fileList
    }
  },
  handleDrop(event) {
    const fileList = event.dataTransfer.files // 获取拖拽文件列表
    for (let i = 0; i < fileList.length; i++) { // 将文件添加到待上传列表中
      const file = fileList[i]
      if (!this.fileList.find(item => item.name === file.name)) {
        this.fileList.push(file)
      }
    }
  },
  handleUploadProgress(event, file, fileList) { // 更新上传进度条
    this.uploadPercentage = Math.round(event.percent)
  },
  handleUploadSuccess(response, file, fileList) { // 处理上传成功事件
    // 可以在此处进行一些操作,例如显示上传成功提示等
  },
  handleFileRemove(file, fileList) { // 处理文件移除事件
    if (this.showProgress && !this.uploadInterrupted) { // 如果上传正在进行中且未完成,将 uploadInterrupted 状态设为 true 阻止文件移除
      this.uploadInterrupted = true
      return false
    }
    return true
  },
  retryUpload(formData, currentChunk, chunks) { // 重试上传方法
    if (this.maxRetryTimes > 0) {
      this.maxRetryTimes--
      this.$axios.post(this.uploadUrl, formData, { headers: this.uploadHeaders }) // 发送文件块到服务器
        .then(() => { // 如果上传成功,更新进度条并记录已上传块号,并重置最大重试次数
          currentChunk++
          this.uploadPercentage = Math.round(currentChunk * 100 / chunks)
          this.uploadedChunks.push(currentChunk - 1)
          this.maxRetryTimes = this.props.maxRetryTimes
          this.retryUpload(formData, currentChunk, chunks)
        })
        .catch(error => { // 如果上传失败,继续尝试重新上传该块
          console.error(error)
          setTimeout(() => {
            this.retryUpload(formData, currentChunk, chunks)
          }, this.retryInterval)
        })
    }
  }
},
render() {
  return (
    <el-upload
      class="upload-demo"
      ref="upload"
      action={this.uploadUrl}
      headers={this.uploadHeaders}
      with-credentials
      onChange={this.handleChange}
      fileList={this.fileList}
      data={this.uploadData}
      multiple
      on-progress={this.handleUploadProgress}
      on-success={this.handleUploadSuccess}
      before-remove={this.handleFileRemove}
      onDragover={e => e.preventDefault()}
      onDrop={this.handleDrop}
    >
      { this.showSlot ? this.$slots.default : (
        <div v-if={!this.showProgress}>
          <i class="el-icon-upload"></i>
          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        </div>
        <el-progress :percentage="uploadPercentage" v-if="showProgress"></el-progress>
      )}
    </el-upload>
  )
}
}
</script>

在父组件中使用

<template>
  <div>
    <big-file-upload
      upload-url="/api/upload"
      :chunk-size="1024 * 1024 * 2"
      :max-retry-times="5"
      :retry-interval="3000"
      :upload-headers="{ Authorization: 'Bearer ' + token }"
      :upload-data="{ userId: userId }"
      @upload-success="handleUploadSuccess"
    >
      <div class="upload-slot">
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      </div>
    </big-file-upload>
  </div>
</template>

<script>
import BigFileUpload from '@/components/BigFileUpload'

export default {
  components: { BigFileUpload },
  data() {
    return {
      token: 'xxxxx',
      userId: '123',
      uploadedFiles: []
    }
  },
  methods: {
    handleUploadSuccess(response, file, fileList) {
      // 处理上传成功事件,将已上传文件添加到 uploadedFiles 数组中
      this.uploadedFiles.push(file)
    }
  }
}
</script>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值