欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

断点续传,分片上传

程序员文章站 2022-05-17 21:03:48
...
<!--
 * @Description: 上传视频组件 只封装了允许上传一个视频
 * @Author zhangyu
 * @Date 2019-09-03 15:55:42
 * @Params {}
 -->

<template>
  <div class="f-upload">
        <el-upload
          class="f-upload__video"
          :show-file-list="false"
          :action="params.action"
          :http-request="(params) => uploadFile(params)">
          <video-player
            v-if="false"
            class="video-player vjs-custom-skin"
            ref="videoPlayer"
            :playsinline="true"
            :options="playerOptions">
              <i class="el-icon-error video-delete-icon" @click="handleDelete(index-1, $event)"></i>
          </video-player>
          <i class="el-icon-plus video-uploader-icon"></i>
        </el-upload>
    <div>
      <ul class="f-upload__hit">
        注意:
        <li>请选择本地视频上传</li>
        <li>大小不能超过{{params.size}}M</li>
        <li>视频格式为MP4</li>
      </ul>
    </div>
    </div>
</template>

<script>
import { hexMd5 } from '@/utils/md5'
import { playerOptions, ConstantField } from '@/utils/constantField'
import moment from 'moment'
const DEFAULT = {
  action: '', // 必须,文件提交路由 ****暂未使用
  readonly: false, // 是否只读
  size: 100, // 最大上传大小,单位 M
  limit: 1,
  value: [] // 图片url
}

export default {
  name: 'UploadVideo',
  props: {
    config: {
      type: Object,
      required: true
    },
    name: { // 字段名
      type: String,
      required: true
    }
  },
  data () {
    return {
      playerOptions,
      params: null,
      fileForm: {
        action: '',
        date: '',
        fileMd5: '',
        index: 0,
        md5: '',
        size: '',
        total: 0
      }
    }
  },
  computed: {
    imageSrcs: function () {
      let arr = []

      this.params.value.forEach(function (val) {
        arr.push('${ConstantField.IMAGE_API}${val}')
      })
      if (arr.length > 0) {
        return arr
      } else {
        return []
      }
    },
    mimeTypes: function () {
      return ConstantField.MIME_TYPE.VIDEO.extensions
    },
    extension: function () {
      let str = null
      const val = this.mimeTypes

      if (val && val.length > 0) {
        str = ''
        for (let i = 0; i < val.length; i++) {
          str += ` .${val[i]} `
          if (i < val.length - 1) {
            str += '|'
          }
        }
      }
      return str
    }
  },
  watch: {
    config: {
      handler: function () {
        this.setParams()
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    // 将接收的config数据与默认配置合并
    setParams () {
      this.params = Object.assign({}, DEFAULT, this.config)
    },
    async beforeUpload (file) {
      const name = file.name
      const arr = name.split('.')

      if (this.mimeTypes.indexOf(arr.pop()) === -1) {
        this.$alert(`当前仅支持上传视频扩展名:${this.extension}`, '上传视频失败')
        return false
      }
      if (this.size) {
        let isLimit = file.size / 1024 / 1024 < this.params.size

        if (!isLimit) {
          this.$message.error(`上传视频大小不能超过 ${this.params.size}MB!`)
          return false
        }
      }
      this.getFileMD5(file)
      // const formVerify = new FormData()
      // const fileRes = await this.$http.attachmentApi.uploadFile(formVerify)

      // console.log('fileRes', fileRes)
    },
    async uploadFile (params) {
      const file = params.file

      await this.beforeUpload(file)
    },
    // 获取整个文件的hash
    async getFileMD5 (file) {
      const reader = new FileReader()
      const context = this

      reader.onload = async function (e) {
        const bolb = e.target.result
        const fileHash = hexMd5(bolb)

        context.fileForm.fileMd5 = fileHash
        console.log('整个文件hash ', fileHash)
        const fileRes = await context.$http.attachmentApi.isUpload(fileHash)

        console.log('dasd', fileRes)
        if (fileRes.code === 200) {
          context.fileForm.uuid = fileRes.data.uuid
          switch (fileRes.data.status) {
          case ConstantField.NOT_UPLOAD:
          case ConstantField.PART_UPLOAD:
            context.handleSuccess(file)
            break
          case ConstantField.ALREADY_UPLOAD:
            context.$message.info('该文件已经上传,请不要上传重复文件')
            break
          default:
            context.$message.error('服务器错误无法上传')
            break
          }
        }
      }
      reader.readAsBinaryString(file)
    },
    handleError (msg) {
      this.$message.error(msg || '上传视频失败,请重试')
    },
    handleDelete (index, event) {
      event.stopPropagation()
      this.$confirm('确定删除该视频?', '提示', {
        type: 'warning'
      }).then(() => {
        this.params.value.splice(index, 1)
        this.handleChange()
        this.$message.success('删除成功!')
      }).catch(() => {
        // 取消删除
      })
    },
    handleChange () {
      this.$emit('change-event', {
        name: this.name,
        value: this.params.value
      })
    },
    /**
     * 分片获取hash 上传 ConstantField.MIME_TYPE.SLICESIZE规定按5M大小拆分
     */
    async handleSuccess (file) {
      const size = file.size // 总大小
      const shardCount = Math.ceil(size / ConstantField.MIME_TYPE.SLICESIZE) // 总片数

      this.uploadSliceFile(file, 0, 1, shardCount)
      // const res = await this.$http.attachmentApi.uploadFile(form)

      // if (res.code === ConstantField.RQ_SUCCESS) {
      //   this.params.value[index] = res.data.url
      //   this.handleChange()
      //   this.$message.success('上传成功!')
      // } else {
      //   this.handleError(res.msg)
      // }
    },
    /**
     *
     * @param file
     * @param filemd5 整个文件的md5
     * @param i 文件第i个分片
     * @param type type  1为检测;2为上传
     */
    async uploadSliceFile (file, i, type, shardCount) {
      const date = moment().format('YYYYMMDD')
      // 计算每一片的起始与结束位置
      const start = i * ConstantField.MIME_TYPE.SLICESIZE
      const end = Math.min(file.size, start + ConstantField.MIME_TYPE.SLICESIZE)
      // const form = new FormData()

      // if (type === 1) {
      //   form.append('action', 'check') // 检测分片是否上传
      // } else {
      //   form.append('action', 'upload') // 直接上传分片
      //   form.append('data', file.slice(start, end)) // slice方法用于切出文件的一部分
      // }
      // form.append('filemd5', this.fileForm.fileMd5)
      this.fileForm.action = 'upload'// 直接上传 check-校验分片
      this.fileForm.date = date
      // this.fileForm.name = file.name
      this.fileForm.size = file.size
      this.fileForm.total = shardCount
      this.fileForm.index = i + 1


      // 按大小切割文件段
      const data = file.slice(start, end)
      const reader = new FileReader()

      reader.onload = async (e) => {
        const bolb = e.target.result

        const sliceHash = hexMd5(bolb)

        this.fileForm.md5 = sliceHash
        const formData = new FormData()

        // formData.append('uuid', this.fileForm.uuid)
        // formData.append('fileMd5', this.fileForm.fileMd5)
        // formData.append('action', 'upload')
        // formData.append('date', date)
        // formData.append('name', file.name)
        // formData.append('size', file.size)
        // formData.append('total', shardCount) // 总片数
        // formData.append('index', i + 1) // 当前是第几片
        // formData.append('fileUploadDTO', this.fileForm)
        formData.append('file', file)
        console.log('0628----shardCount', shardCount)
        console.log('0628----i', i)

        const res = await this.$http.attachmentApi.uploadFile(formData)

        if (res.code === ConstantField.RQ_SUCCESS) {
          // this.params.value[index] = res.data.url
          if ((i + 1) < shardCount) {
            this.uploadSliceFile(file, i + 1, 1, shardCount)
          }
        } else {
          this.handleError(res.msg)
        }
      }
      reader.readAsBinaryString(data)
    }
  },
  created () {
  }

}
</script>

<style lang="less" scoped>
@width: 400px;
@height: 400px;
@max-width: 560px;
.f-upload{
  /*最外层设置宽高 固定400px*/
  width: @width;
  height: @width;
  max-width: @max-width;
  min-height: @width;
  &__video{
    min-width: @width ;
    min-height: @width;
    border: 1px dashed #D9D9D9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    text-align: center;
    /deep/ .el-upload {
      width: 100%;
    }

    .video-uploader-icon {
      font-size: 28px;
      color: #8C939D;
      line-height: @height;
    }

    &:hover {
      border-color: #409EFF;
      color: #409EFF;

      .image-uploader-icon {
        color: #409EFF;
      }
    }

      .video-delete-icon {
        position: absolute;
        font-size: 22px;
        right: 5px;
        top: 5px;
        color: red;
        &:hover {
          color: #ff5500;
        }
      }
  }

  /*上传视频组件提示*/
  &__hit {
    font-weight: 500;
    //color: @color-hint-text;
    line-height: 20px;
    padding-top: 20px;
    padding-bottom: 10px;
    li {
      //color: @text;
      padding-bottom: 5px;
      &::before{
        content: "●";
        color: #1790FF;
        padding-right: 10px;
      }
    }
  }
}

</style>

相关标签: vuecli3 前端