原生手写实现vue的upload组件

73 阅读2分钟

和vue组件一样引用就行

<Upload

          :action="action"

          :headers="headers"

          @onSuccess="handleUploadSuccess"

          @onError="handleUploadError"

          @onRemove="hanldeRemoveIcon"

          @onBeforeUpload="beforeUploadImage"

          :image-url="formData.imageUrl"

          :tip-text="$t('emp_max_10m')"

        ></Upload>

image.png

mixins.js

export default {

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d2ecd1a79513434d9b490cdf2a4a7520~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=254&h=159&s=8532&e=png&b=181818)
  props: {

    icon: {

      //上传组件的占位图

      type: String,

      default: 'iconcamera'

    },

    size: {

      //图片超过指定大小不让上传

      type: Number,

      default: 30720

    },

    disabled: {

      //禁止上传

      type: Boolean

    },

    iconSize: {

      //占位icon的大小

      type: Number,

      default: 24

    },

    name: {

      //input的原生属性

      type: String,

      default: 'file'

    },

    accept: {

      //接受上传的文件类型

      type: Array,

      default() {

        return []

      }

    },

    acceptErrorMessage: {

      //文件类型错误的提示内容

      type: String,

      default: '文件类型错误'

    },

    compress: {

      //是否开启图片压缩

      type: Boolean,

      default: true

    },

    compressSize: {

      //超过大小的图片压缩

      type: Number,

      default: 512

    },

    data: {

      //上传附带的内容

      type: Object,

      default() {

        return {}

      }

    },

    action: {

      //上传地址

      type: String,

      default: ''

    },

    headers: {

      //设置上传的请求头部

      type: Object,

      default() {

        return {}

      }

    },

    imgWidth: {

      //图片压缩时指定压缩的图片宽度

      type: [Number, Boolean],

      default: 800

    },

    quality: {

      //图片压缩的质量

      type: Number,

      default: 1

    },

    beforeUpload: {

      //上传文件之前的钩子

      type: Function

    },

    onSuccess: {

      //上传成功的钩子

      type: Function

    },

    onError: {

      //上传失败的钩子

      type: Function

    },

    onLoadend: {

      //文件上传成功或者失败都会执行的钩子

      type: Function

    },

    onProgress: {

      //文件上传进度的钩子

      type: Function

    },

    onSuccessText: {

      //上传成功的提示内容

      type: String,

      default: '上传成功'

    },

    onErrorText: {

      //上传失败的提示内容

      type: String,

      default: '上传失败'

    },

    beforeRemove: {

      //删除文件的钩子

      type: Function

    },

    showRemove: {

      //是否展示删除icon

      type: Boolean,

      default: true

    },

    type: {

      //单文件上传还是多文件上传

      type: String,

      default: 'single',

      validator: function (value) {

        return ['single', 'multiple'].includes(value)

      }

    },

    maxNumber: {

      //多文件上传最多上传的个数

      type: Number

    },
    isImage: {
      //文件是否为图片
      type: Boolean,
    }
  }
}

upload.vue

<!-- components/upload.vue -->

<template>

  <div class="g7-Upload-single">

    <div class="g7-Upload-default-icon">

      <template v-if="!value">

        <slot>

          <!-- <Icon :size="iconSize" :icon="icon" /> -->

          <i class="el-icon-upload" />

        </slot>

      </template>

      <template v-else>

        <template v-if="isImage">

          <img class="g7-Upload-img" :src="value" />

        </template>

        <template v-else>

          <!-- <Icon :size="34" icon="iconicon-" /> -->

          <i class="el-icon-upload" />

        </template>

        <span

          @click.stop="onRemove"

          v-if="showRemove"

          class="g7-Upload-removeImg"

        >

          <!-- <Icon :size="14" icon="iconcuowu" color="#fff" /> -->

          <i class="el-icon-upload" />

        </span>

      </template>

    </div>

    <input

      class="g7-Upload-input"

      @change="change"

      :disabled="computedDisabled"

      :name="name"

      type="file"

      ref="input"

    />

    <!-- 图片压缩需要用到的canvas -->

    <canvas hidden="hidden" v-if="compress" ref="canvas"></canvas>

    <!-- 进度条 -->

    <div v-if="progress > 0" class="g7-Upload-progress">

      <div

        :style="{ width: `${progress}%` }"

        class="g7-Upload-progress-bar"

      ></div>

    </div>

  </div>

</template>

  


<script>

// import Icon from './Icon' //自定义组件

import mixins from './mixins'

import { getType, fetch } from './utils'

// import Toast from '../../~Toast' //自定义组件

const compressList = ['png', 'PNG', 'jpg', 'JPG', 'jpeg', 'JPEG']

export default {

  // components: { Icon },

  mixins: [mixins],

  props: {

    value: {

      type: String

    }

  },

  data() {

    return {

      file: '',

      progress: 0,

      src: ''

    }

  },

  computed: {

    computedDisabled() {

      return this.disabled || this.progress !== 0

    }

  },

  methods: {

    change(e) {

      if (this.disabled) {

        return

      }

      // debugger

      const file = e.target.files[0]

      console.log('log--> eeeee', e.target.files)

      console.log('log--> file', file)

  


      if (!file) {

        return

      }

  


      const type = getType(file.name)

      if (this.accept.length) {

        if (!this.accept.includes(type)) {

          this.$message.info(this.acceptErrorMessage)

          return

        }

      }

  


      const size = Math.round((file.size / 1024) * 100) / 100

      if (size > this.size) {

        this.$message.info(`请上传小于${this.size / 1024}M的文件`)

        return

      }

  


      // if (this.isCompress(type, size)) {

      //   console.log('log--> URL', URL)

      //   this.canvasDataURL(URL.createObjectURL(file))

      //   return

      // }

      this.$emit('on-change')

      this.file = file

      this.upload(file)

    },

    // /**

    //  * 判断是否满足压缩条件

    //  */

    // isCompress(type, size) {

    //   console.log(

    //     'log-->this.compress ',

    //     this.compress,

    //     compressList.includes(type),

    //     size,

    //     this.compressSize

    //   )

  


    //   return (

    //     this.compress && compressList.includes(type) && size > this.compressSize

    //   )

    // },

    // canvasDataURL(base) {

    //   const img = new Image()

    //   img.src = base

    //   const that = this

    //   function ImgOnload() {

    //     /**

    //      * 计算生成图片的宽高

    //      */

    //     const scale = this.width / this.height

    //     const width =

    //       that.imgWidth === false || this.width <= that.imgWidth

    //         ? this.width

    //         : that.imgWidth

    //     const height = width / scale

    //     const canvas = that.$refs.canvas

    //     canvas.width = width

    //     canvas.height = height

    //     // 利用canvas绘制压缩的图片并生成新的blob

    //     const context = canvas.getContext('2d')

    //     context.drawImage(this, 0, 0, width, height)

    //     canvas.toBlob(

    //       (blob) => {

    //         that.file = blob

    //         that.upload(blob)

    //         that.$emit('on-change', blob, that.options)

    //       },

    //       'image/png',

    //       that.quality

    //     )

    //     /**

    //      * 使用完的createObjectURL需要释放内存

    //      */

    //     window.URL.revokeObjectURL(this.src)

    //   }

    //   img.onload = ImgOnload

    // },

    /**

     * 上传成功

     */

    onload(e) {

      this.progress = 0

      this.$emit('input', e)

      if (this.onSuccess) {

        this.onSuccess(this.file, e)

        return

      }

      this.$message.info(this.onSuccessText)

    },

    /**

     * 上传进度

     */

    uploadProgress(e) {

      this.progress = e.percent

      console.log('log--> e.percent', e.percent)

  


      if (this.onProgress < 100) {

        this.onProgress(this.file, e)

      }

    },

    /**

     * 上传失败

     */

    uploadError(e) {

      this.progress = 0

      if (this.onError) {

        this.onSuccess(this.file, e)

        return

      }

      this.$message.info(this.onErrorText)

    },

    /**

     * 请求结束

     */

    uploadLoadend(e) {

      this.clearInput()

      if (this.onloadend) {

        this.onloadend(this.file, e)

      }

    },

    /**

     * 上传

     */

    upload(file) {

      // this.clearInput()

      if (!this.beforeUpload) {

        fetch(this, file)

        return

      }

  


      const before = this.beforeUpload(file)

      if (before && before.then) {

        before.then((res) => {

          if (res !== false) {

            fetch(this, file)

          }

        })

        return

      }

      if (before !== false) {

        fetch(this, file)

      }

    },

    /**

     * 删除文件

     */

    onRemove() {

      // this.clearInput()

      if (this.type === 'single') {

        if (!this.beforeRemove) {

          this.$emit('input', '')

          return

        }

  


        const before = this.beforeRemove(this.file, this.value)

        if (before && before.then) {

          before.then((res) => {

            if (res !== false) {

              this.$emit('input', '')

            }

          })

          return

        }

        if (before !== false) {

          this.$emit('input', '')

        }

        return

      }

      this.$emit('on-remove')

    },

    clearInput() {

      this.$refs.input.value = null

    }

  }

}

</script>

uploadExport.vue

<template>

  <div class="g7-Upload">

    <template v-if="type === 'single'">

      <Upload

        ref="upload"

        :icon="icon"

        :size="size"

        :accept="accept"

        :name="name"

        :acceptErrorMessage="acceptErrorMessage"

        :compress="compress"

        :iconSize="iconSize"

        :compressSize="compressSize"

        :imgWidth="imgWidth"

        :headers="headers"

        :action="action"

        :data="data"

        :quality="quality"

        :beforeRemove="beforeRemove"

        :beforeUpload="beforeUpload"

        :onSuccess="onSuccess"

        :onSuccessText="onSuccessText"

        :onError="onError"

        :onProgress="onProgressFun"

        :onLoadend="onLoadend"

        :onErrorText="onErrorText"

        :disabled="disabled"

        :showRemove="showRemove"

        v-model="currentValue"

        @input="input"

        :type="type"

        :maxNumber="maxNumber"

        :isImage="isImage"

      >

        <slot></slot>

      </Upload>

    </template>

  </div>

</template>

  


<script>

import Upload from './upload'

import mixins from './mixins'

export default {

  name: 'GUpload',

  components: { Upload },

  mixins: [mixins],

  props: {

    // eslint-disable-next-line vue/require-default-prop

    value: {

      type: [String, Array]

    }

  },

  data() {

    return {

      currentValue: this.value

    }

  },

  watch: {

    value(val) {

      this.currentValue = val

    }

  },

  methods: {

    input(val) {

      this.$emit('input', val)

    },

    // 接管文件上传时,自定义文件上传进度

    onProgressFun(percent) {

      this.$refs.upload.uploadProgress(percent)

    }

  }

}

</script>

utils.js

/**

 * 获取文件的后缀

 * @param {*} file

 */

export const getType = (file) => file.substr(file.lastIndexOf('.') + 1)

  


/**

 * 请求封装

 * @param {*} options

 * @param {*} file

 */

export function fetch(options, file) {

  // console.log('log--> ', typeof XMLHttpRequest)

  


  if (typeof XMLHttpRequest === 'undefined') {

    return

  }

  const xhr = new XMLHttpRequest()

  console.log('log--> xhr', xhr)

  


  const action = options.action

  if (xhr.upload) {

    xhr.upload.onprogress = function progress(e) {

      if (e.total > 0) {

        e.percent = (e.loaded / e.total) * 100

      }

      options.uploadProgress(e)

    }

  }

  const formData = new FormData()

  console.log('log--> formData', formData)

  


  formData.append(options.name, file, options.fileName)

  


  for (const key in options.data) {

    formData.append(key, options.data[key])

  }

  // 成功回调

  xhr.onload = (e) => {

    console.log('log-->成功回调 e', e)

    console.log('log-->成功回调e.target.response ', e.target.response)

  


    const response = e.target.response

    if (xhr.status < 200 || xhr.status >= 300) {

      options.uploadError(response)

      return

    }

    options.onload(response)

  }

  // 出错回调

  xhr.onerror = (e) => {

    const response = e.target.response

    options.uploadError(response)

  }

  // 请求结束

  xhr.onloadend = (e) => {

    const response = e.target.response

    options.uploadLoadend(response)

  }

  xhr.open('post', action, true)

  const headers = options.headers

  for (const key in headers) {

    if (headers[key] !== null) {

      xhr.setRequestHeader(key, headers[key])

    }

  }

  console.log('log--> formData2', formData)

  


  xhr.send(formData)

}