封装upLoad组件,自定义上传方法

2,187 阅读3分钟

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

背景

环境:vue2 ,组件:<el-upload/> 太多数组件封装的上传都是基于上传组件的action进行实现。本文主要解析自定义上传方法的实现的上传组件。 因为其中还是有两步坑是要踩的

代码

<el-upload
  ref="uploadRef"
  drag
  multiple
  action=""
  :list-type="type === 'image' ? 'picture' : 'text'"
  :file-list="list"
  :accept="acceptEnum[type]"
  :limit="limit"
  :http-request="handleUpload"
  :before-upload="beforeUpload"
  :on-remove="handleRemove"
  :on-success="handleSuccess"
  :on-error="handleError"
  :on-exceed="handleExceed"
>
  <i class="el-icon-upload"></i>
  <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
  <div v-if="type === 'image'" class="el-upload__tip" slot="tip">
    只能上传jpg/png文件,且不超过2MB
  </div>
</el-upload>

本文以upload的拖拽组件为例。

props

首先定义props,我定义了type属性主要区别于文件类型的上传,区别了文件的类型我们可以用不同的规则去限制上传前的校验。

props: {
    type: {
      type: String,
      default: 'all' // all 支持任意格式  audio video image
    },
    limit: {
      type: Number,
      default: 1
    },
    list: {
      type: Array,
      default: () => []
    }
},

beforeUpload

上传前的校验,对于不同类型,于是我们可以加不同的策略,我利用了一个策略模式进行上传前不同格式的校验。

beforeUpload(file) {
  const strategy = this.strategies[this.type] || []
  for (const rule of strategy) {
    // 验证后缀的策略
    if (
      rule.suffix !== undefined &&
      !this.checkSuffix(this.getSuffix(file.name), rule.suffix)
    ) {
      this.$message.error(rule.message)
      return false
    }
    // 验证大小的策略
    if (rule.size !== undefined && !this.checkSize(file.size, rule.size)) {
      this.$message.error(rule.message)
      return false
    }
  }

  return true
}

于是我们可以在data里面进行策略的详细配置

strategies: {
    all: [],
    audio: [],
    video: [{ size: 500 * 1024 * 1024, message: '视频必须小于500MB' }],
    image: [
      {
        suffix: ['.png', '.jpg'],
        message: '请选择正确格式的图片'
      },
      { size: 2 * 1024 * 1024, message: '图片必须小于2MB' }
    ]
}

自定义上传方法

上面我们进行上传前的校验。 于是我们可以进行上传了

// 自定义上传
handleUpload(ctx) {
  return new Promise((resolve, reject) => {
    // 这是我司实现的上传方法,保密性具体代码不公开。
    upload(
      ctx.file, // 源文件
      (v) => {
        // 上传进度
        ctx.onProgress({ percent: v * 100 })
      },
      (src) => {
        resolve(src)
      },
      () => {
        reject()
      }
    )
  })
},

这里是最重要的一点。

这里我也是翻阅源码才知道,自定义上传的方法怎么用的。:http-request="handleUpload"文档上面关于http-request的介绍很模糊。

首先这里。如果你去实现一个进度条。 你必须通过ctx去调用的它的onProgress方法,必须是一个对象,进度条的数值必须是一个0-100的数,key为percent,在文档上是看不到的。

image.png

我们看到源码,当我们上传时,他将文件的状态设为uploading,上传的进度条percentage,便是我们通过传进去的ev.percent赋值。

这是onProgress其实就是上传组件往外抛出的钩子函数。详情见文档使用方法。

为什么我自定义必须是一个promise呢? 先看源码

image.png

this.httpRequest就是我们的自定义方法。 看到下面的.then我只就知道,上传肯定是异步的,我们需要同的promise的回调,将上传的值进行返回。 这里的options.onSuccess, options.onError就是我们监听的上传成功和上传失败的函数了。

自此上传的过程就完毕了。

源码

<template>
  <div>
    <el-upload
      ref="uploadRef"
      drag
      multiple
      action=""
      :list-type="type === 'image' ? 'picture' : 'text'"
      :file-list="list"
      :accept="acceptEnum[type]"
      :limit="limit"
      :http-request="handleUpload"
      :before-upload="beforeUpload"
      :on-remove="handleRemove"
      :on-success="handleSuccess"
      :on-error="handleError"
      :on-exceed="handleExceed"
    >
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <div v-if="type === 'image'" class="el-upload__tip" slot="tip">
        只能上传jpg/png文件,且不超过2MB
      </div>
    </el-upload>
  </div>
</template>

<script>
export default {
  name: 'upload',
  props: {
    type: {
      type: String,
      default: 'all' // all 支持任意格式  audio video image
    },
    limit: {
      type: Number,
      default: 1
    },
    list: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      // 上传前校验策略配置
      strategies: {
        all: [],
        audio: [],
        video: [{ size: 500 * 1024 * 1024, message: '视频必须小于500MB' }],
        image: [
          {
            suffix: ['.png', '.jpg'],
            message: '请选择正确格式的图片'
          },
          { size: 2 * 1024 * 1024, message: '图片必须小于2MB' }
        ]
      },
      typeEnum: {
        all: '文件',
        audio: '音频',
        video: '视频',
        image: '图片'
      },
      acceptEnum: {
        all: '',
        audio: 'audio/*',
        video: 'video/*',
        image: 'image/*'
      }
    }
  },
  methods: {
    handleRemove(file) {
      const fileList = this.$refs.uploadRef.fileList
      const index = fileList.indexOf(file)
      this.$refs.uploadRef.fileList.splice(index, 1)
      this.$emit('remove', index)
    },
    handleError(err) {
      this.$message.error(`上传失败`)
      console.log('error', err)
    },
    handleExceed() {
      this.$message.error(`最多上传${this.limit}${this.typeEnum[this.type]}`)
    },
    handleSuccess(url, file, filelist) {
      file.url = url
      this.$emit('success', file)
    },
    // 图片上传验证
    beforeUpload(file) {
      const strategy = this.strategies[this.type] || []
      for (const rule of strategy) {
        if (
          rule.suffix !== undefined &&
          !this.checkSuffix(this.getSuffix(file.name), rule.suffix)
        ) {
          this.$message.error(rule.message)
          return false
        }
        if (rule.size !== undefined && !this.checkSize(file.size, rule.size)) {
          this.$message.error(rule.message)
          return false
        }
      }

      return true
    },
    // 自定义上传 这里需自己书写上传逻辑
    handleUpload(ctx) {
      return new Promise((resolve, reject) => {
        upload(
          ctx.file,
          (v) => {
            // 上传进度
            ctx.onProgress({ percent: v * 100 })
          },
          (src) => {
            resolve(src)
          },
          () => {
            reject()
          }
        )
      })
    },
    getSuffix(filename) {
      let suffix = ''
      const lastIndex = filename.lastIndexOf('.')
      if (lastIndex >= 0) {
        suffix = filename.substring(lastIndex)
      }
      return suffix
    },
    checkSize(aim, totalSize) {
      return aim < totalSize
    },
    checkSuffix(aim, suffixs) {
      return suffixs.includes(aim)
    }
  }
}
</script>
<style lang="scss" module>
.imgBox {
  border-radius: 6px;
  overflow: hidden;
  height: 100%;
  width: 100%;
  background-repeat: no-repeat;
  background-position: center center;
  background-size: cover;
}
</style>

注意

仅适用于vue2环境,当然大家可以参考逻辑,写出更好的上传组件。