preface

Ali Cloud object storage uses stsToken, this component is used with the above method, of course, you can also use the method in the previous article alone, since the project needs, then we need to package a package to cover the daily use of the project upload component.

Support functions

  • Three-column layout vs. two-column Layout (both)
  • Add words to waterMark before uploading images
  • Support detail display mode (detail)
  • Support to add description text and custom text color (desc, descColor)
  • Support to delete pictures with custom delete button color (deleteable, delColor)
  • Support for limiting the number, size and format of images (maxCount, maxSize, limitType)
  • Support for passing in the default image array (defaultList)

Matters needing attention

This component is available for the UniApp project, [email protected], component library [email protected]. If the component library is not uView, rewrite the code ($u, u-icon, u-line-1). Ts is used in the Upload component, along with vue-property-decorator notation.

Components in full

<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>

Copy the code

Method of use

  • By default, a three-column layout
    // template
    <li-upload
      :maxCount="6"
      :limitType="['png', 'jpg', 'jpeg']"
      :detail="isResult"
      :defaultList="imageList"
      @on-change="changeImageList"
    ></li-upload>
    Copy the code
    // ts import Upload from '@/components/upload/upload.vue' @Component({ components: { 'li-upload': Upload } }) ... ChangeImageList (list: any) {this.imagelist = list}Copy the code

  • A two-column layout
    // template <li-upload :maxCount="6" :limitType="['png', 'jpg', 'JPEG ']" :detail="isResult" :both="true" desc=" descColor="#2593F2" :defaultList="imageList" @on-change="changeImageList" ></li-upload> ... .Copy the code

  • Image watermarking
    <li-upload
      :maxCount="6"
      :limitType="['png', 'jpg', 'jpeg']"
      :detail="isResult"
      :waterMark="true"
      :defaultList="imageList"
      @on-change="changeImageList"
    ></li-upload>
    Copy the code

The last

The above is the package picture upload method, custom upload component all content.