UNIAPP 封装upload组件(添加文字水印)直传OSS

1,202 阅读2分钟

前言

接上篇文章# UNIAPP封装小程序直传阿里云对象存储使用stsToken,这个组件是与上面方法配合使用的,当然也可以单独使用上篇文中的方法,既然项目中有需要,那么我们就要封装一个覆盖项目日常使用的upload组件。

支持功能

  • 三栏布局与两栏布局(both)
  • 图片上传前添加文字水印(waterMark)
  • 支持详情展示模式(detail)
  • 支持添加描述文字与自定义文字颜色(desc、descColor)
  • 支持删除图片与自定义删除按钮的颜色(deleteable,delColor)
  • 支持限制图片数量、大小与格式(maxCount、maxSize、limitType)
  • 支持传入默认图片数组(defaultList)

注意事项

此组件适用于uniapp项目,vue@2.x,组件库uview-ui@1.x。如果组件库不是uview,将代码($u、u-icon、u-line-1)改写即可。upload组件中使用了ts,以及装饰器写法(vue-property-decorator)。

组件全文

<template>
  <view class="upload">
    <view style="width: 0px; height: 0px; overflow: hidden">
      <canvas
        id="canone"
        :style="{ width: imageWidth + 'px', height: imageHeight + 'px' }"
        canvas-id="canone"
      />
    </view>
    <view class="upload__preview">
      <view
        v-for="(item, index) in lists"
        :key="index"
        class="upload__preview--item"
        :class="{ 'both-width': both, 'third-width': !both }"
      >
        <view
          class="upload__preview--imagebox"
          :class="{ both: both, third: !both }"
        >
          <image
            class="upload__preview--image"
            :src="item"
            @click="previewImage(index)"
          ></image>
          <u-icon
            v-if="!detail"
            class="upload__preview--close"
            name="close-circle-fill"
            color="#666666"
            size="40"
            @click="deleteImageList(index)"
          ></u-icon>
        </view>
        <view
          v-if="desc"
          :class="{ both: both, third: !both }"
          class="upload__up--desc u-line-1"
          :style="{ color: descColor }"
        >
          {{ desc }}
        </view>
      </view>
      <view
        v-if="!detail && lists.length < maxCount"
        class="upload__up"
        :class="{ 'both-width': both, 'third-width': !both }"
      >
        <view
          class="upload__up--btn"
          :class="{ 'both-border both': both, third: !both }"
          @click="selectFile"
        >
          <u-icon
            name="plus"
            size="50"
          ></u-icon>
        </view>
        <view
          v-if="desc"
          :class="{ both: both, third: !both }"
          class="upload__up--desc u-line-1"
          :style="{ color: descColor }"
        >
          {{ desc }}
        </view>
      </view>
    </view>
  </view>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
import { uploadWaterMarkImage, uploadFile } from '@/utils/upload'
@Component({})
export default class Upload extends Vue {
  /**
   * 是否加水印
   */
  @Prop({ type: Boolean, default: false }) waterMark!: boolean
  /**
   * 最大上传数量
   */
  @Prop({ type: Number, default: 9 }) maxCount!: number
  /**
   * 是否展示删除按钮
   */
  @Prop({ type: Boolean, default: true }) deleteable!: boolean
  /**
   * 是否是详情
   */
  @Prop({ type: Boolean, default: false }) detail!: boolean
  /**
   * 图片描述
   */
  @Prop({ type: String, default: '' }) desc!: boolean
  /**
   * 图片描述的文字颜色
   */
  @Prop({ type: String, default: '#2593F2' }) descColor!: boolean
  /**
   * 是否展示两栏, 默认三栏展示
   */
  @Prop({ type: Boolean, default: false }) both!: boolean
  /**
   * 文件大小限制,单位Mb
   */
  @Prop({ type: Number, default: 10 }) maxSize!: number
  /**
   * 右上角关闭按钮的叉号图标的颜色
   */
  @Prop({ type: String, default: '#666666' }) delColor!: string
  /**
   * 图片限制类型
   */
  @Prop({
    type: Array,
    default () {
      return ['png', 'jpg', 'jpeg', 'gif', 'webp', 'image']
    }
  })
  limitType!: []

  /**
   * 默认图片
   */
  @Prop({
    type: Array,
    default () {
      return []
    }
  })
  defaultList!: []

  private imageWidth = 300
  private imageHeight = 500
  private lists: string[] = []
  @Watch('defaultList')
  onChangeDefaultList (n: []) {
    this.lists = n
  }

  /**
   * 图片预览
   */
  previewImage (index: number) {
    uni.previewImage({
        current: index,
        urls: this.lists,
        indicator: 'default',
        loop: true
    })
  }

  /**
   * 删除图片
   */
  deleteImageList (index: number) {
    let deleteList = this.lists.splice(index, 1)
    this.$emit('on-change', this.lists)
    this.$emit('on-delete', deleteList,  this.lists)
  }

  /**
   * 选择图片,校验图片格式和图片大小
   */
  selectFile () {
    const { maxSize, maxCount, waterMark } = this
    let chooseFile = null
    chooseFile = new Promise((resolve, reject) => {
      uni.chooseImage({
        count: 1, // 为了避免麻烦,偷懒,限制了只能选择一张
        success: resolve,
        fail: reject
      })
    })
    chooseFile
      .then((res: any) => {
        // 检查文件后缀是否允许,如果不在this.limitType内,就会返回false
        let file = res.tempFiles[0]
        if (!this.checkFileType(file)) {
          return
        }
        if (file.size > maxSize * 1024 * 1024) {
          this.$u.toast('最大允许上传' + maxSize + 'MB的图片')
          return
        }
        if (this.lists.length >= maxCount) {
          this.$u.toast('最多允许上传' + maxCount + '张图片')
          return
        }
        let tempFilePath = res.tempFilePaths[0]
        if (waterMark) {
          this.uploadWaterMarkImage(tempFilePath)
        } else {
          this.uploadImage(tempFilePath)
        }
      })
      .catch((error: any) => {
        this.$emit('on-choose-fail', error)
      })
  }

  // 图片上传
  uploadImage (tempFilePath: string) {
    uni.getImageInfo({
      src: tempFilePath,
      success: (res) => {
        let data: { [key: string]: any } = {
          filePath: tempFilePath,
          extensionName: '.' + res.type
        }
        uploadFile(data)
          .then((image: any) => {
            this.lists.push(image.url)
            this.$emit('on-success', image, this.lists)
            this.$emit('on-change', this.lists)
          })
          .catch((err) => {
            this.uploadError(err)
          })
      }
    })
  }

  // 图片添加水印后上传
  uploadWaterMarkImage (tempFilePath: string) {
    uni.getImageInfo({
      src: tempFilePath,
      success: (res) => {
        this.imageWidth = res.width
        this.imageHeight = res.height
        // 组件调用时读不到canvas,所以需要将this传入进去,同时,上传水印那里也需要把当前的this传入
        let ctx = uni.createCanvasContext('canone', this) // 创建画布
        let data: { [key: string]: any } = {
          that: this,
          filePath: tempFilePath,
          extensionName: '.' + res.type,
          ctx: ctx,
          height: res.height,
          width: res.width
        }
        uploadWaterMarkImage(data)
          .then((image: any) => {
            this.lists.push(image.url)
            this.$emit('on-success', image, this.lists)
            this.$emit('on-change', this.lists)
          })
          .catch((err) => {
            this.uploadError(err)
          })
      }
    })
  }

  /**
   * 上传失败
   */
  uploadError (err: any) {
    this.$emit('on-error', err, this.lists)
    this.$u.toast('上传失败,请重试')
  }

  // 判断文件后缀是否允许
  checkFileType (file: any) {
    // 检查是否在允许的后缀中
    let flag = false
    // 获取后缀名
    let fileType = ''
    const reg = /.+\./
    fileType = file.path.replace(reg, '').toLowerCase()
    // 只要符合limitType中的一个,就返回true
    flag = this.limitType.some((ext) => {
      // 转为小写
      return ext.toLowerCase() === fileType
    })
    if (!flag) {
      this.$u.toast(`不允许选择${fileType}格式的文件`)
    }
    return flag
  }
}
</script>
<style lang="scss">
.upload {
  &__preview {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    &--item {
      margin-top: 20rpx;
    }
    &--imagebox {
      position: relative;
    }
    &--image {
      width: 100%;
      height: 100%;
    }
    &--close {
      position: absolute;
      top: -12rpx;
      right: -12rpx;
    }
  }
  &__up {
    margin-top: 20rpx;
    &--btn {
      border-radius: 6rpx;
      background: #f0f2f4;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    &--desc {
      height: 60rpx !important;
      line-height: 60rpx;
      font-size: 28rpx;
      text-align: center;
    }
  }
  .both {
    width: 310rpx;
    height: 240rpx;
  }
  .both-width {
    width: 50%;
  }
  .both-border {
    border: 1rpx dashed #2593f2;
  }
  .third {
    width: 190rpx;
    height: 190rpx;
  }
  .third-width {
    width: 33.3%;
  }
}
</style>

使用方法

  • 默认情况下,三栏布局
    // template
    <li-upload
      :maxCount="6"
      :limitType="['png', 'jpg', 'jpeg']"
      :detail="isResult"
      :defaultList="imageList"
      @on-change="changeImageList"
    ></li-upload>
    
    // ts
    import Upload from '@/components/upload/upload.vue'
    @Component({
      components: {
        'li-upload': Upload
      }
    })
    ...
    // 图片列表
    changeImageList (list: any) {
        this.imageList = list
    }
    

image.png

  • 两栏布局
    // template
    <li-upload
      :maxCount="6"
      :limitType="['png', 'jpg', 'jpeg']"
      :detail="isResult"
      :both="true"
      desc="'描述文字描述文字描述文字描述文字'"
      descColor="#2593F2"
      :defaultList="imageList"
      @on-change="changeImageList"
    ></li-upload>
    ...
    ...
    

image.png

  • 图片水印
    <li-upload
      :maxCount="6"
      :limitType="['png', 'jpg', 'jpeg']"
      :detail="isResult"
      :waterMark="true"
      :defaultList="imageList"
      @on-change="changeImageList"
    ></li-upload>
    

image.png

最后

以上就是封装图片上传方法,自定义上传组件全部内容。