vue 图片上传到腾讯云对象存储组件封装(完善版)

news2025/1/12 3:42:39

vue 上传图片到腾讯云对象存储

  • 1、 引入cos-js-sdk-v5
  • 2、封装`uploadcos.js`
  • 3、封装图片上传组件、调用上传方法
  • 4、页面使用组件

之前总结过 vue 封装图片上传组件到腾讯云对象存储,后来又加了一些功能,在图片过大时进行压缩,压缩完成之后,再上传到腾讯云对象存储;并且,对上传方法进行了优化,所以重新记录一下。

1、 引入cos-js-sdk-v5

安装 JavaScript SDK

npm install cos-js-sdk-v5

安装成功后会有如下信息:
在这里插入图片描述

2、封装uploadcos.js

新建文件uploadcos.js,封装上传文件方法。

/**
 * 本文件为腾讯云对象存储相关工具类方法
  注意:桶的访问权限需要设置指定域名(不然会出现跨域问题),现在设置允许访问的域名是:
  http://localhost:8080 https://xxx.com.cn/
  所以本地调试时,需要用http://localhost:8080,不可用其他端口。
  跨域配置:
  桶:指定域名 + 指定子账号能上传;外部不能访问,统一通过cdn访问;
  CDN:设置为无跨域限制

  ---- COD自主诊断工具:https://cloud.tencent.com/login?s_url=https%3A%2F%2Fconsole.cloud.tencent.com%2Fcos5%2Fdiagnose -----
 */

// https://cloud.tencent.com/document/product/436/11459
import COS from 'cos-js-sdk-v5'
import { Message } from 'element-ui'
import { getCOSSecretKey } from '@/api/index'

// 存储桶所在地域
const BUCKET_REGION = 'ap-beijing'
// 使用分片上传阈值10(M)
const SLICE_SIZE = 10

const BUCKET_TYPE_CONFIG = {
  video: 'video-b-123456',
  image: 'image-b-123456',
  file: 'file-b-123456'
}

const BUCKET_DOMAIN = {
  video: 'https://abcd-video.xxx.com.cn',
  image: 'https://abcd-image.xxx.com.cn'
}

const FOLDER_PATH_NAME = {
  // 内容图片
  ART: {
    prod: '/art/',
    test: '/test/art/'
  },
  // 日常活动图片
  ACT: {
    prod: '/act/',
    test: '/test/act/'
  },
  // 产品图片
  WARE: {
    prod: '/ware/',
    test: '/test/ware/'
  },
  // 广告&宣传图片
  ADV: {
    prod: '/adv/',
    test: '/test/adv/'
  }
}

/**
 * options @param {Object}
 * sliceSize:使用切片上传阈值 默认10(M)
 * bucketType:桶类型 video,image,file 三种类型
 * busiType:业务类型
 * needLaoding:是否需要loading遮罩层
 * bucketEnv:桶的环境 测试、生产
 * bucketName:桶的名称
 * bucketDomain:桶的域名 用来拼接key
 * bucketPrefix:自定义桶地址前缀片段
 * credentials:后台返回凭证信息
 * keyBackData:后台返回密钥信息 为credentials父级
 */
class Cos {
  constructor(options) {
    this.bucketEnv =
      window.msBaseUrl === 'https://xxx.com.cn/' ? 'prod' : 'test'
    this.bucketType = options?.bucketType || BUCKET_TYPE_CONFIG.file
    this.bucketName = BUCKET_TYPE_CONFIG[this.bucketType]
    this.bucketDomain = BUCKET_DOMAIN[this.bucketType]
    this.sliceSize = options?.sliceSize || SLICE_SIZE
    this.busiType = options?.busiType || 'ART'
    this.bucketPrefix = FOLDER_PATH_NAME[this.busiType][this.bucketEnv]
    this.credentials = null
    this.keyBackData = null
  }

  /**
   * 获取密钥
   * @returns Object
   */
  async getKey () {
    try {
      const res = await getCOSSecretKey({
        bucket: this.bucketName
      })
      if (
        res?.result?.credentials &&
        Object.keys(res?.result?.credentials).length
      ) {
        this.keyBackData = res.result
        this.credentials = res.result.credentials
        return this.credentials
      }
      return null
    } catch (error) {
      return null
    }
  }

  /**
   * 生成上传资源名称 6位随机数+uid+文件名
   */

  generateKey (file) {
    const timeStamp = file.uid ? file.uid : new Date().getTime() + ''
    const suffix = file.name.split('.')[file.name.split('.').length - 1]
    return `${this.randomString()}_${timeStamp}.${suffix}`
  }

  /**
   * 获取随机数
   * @param {*} len
   * @returns
   */
  randomString (len = 6) {
    const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789'
    const maxPos = chars.length
    let res = ''
    for (let i = 0; i < len; i++) {
      res += chars.charAt(Math.floor(Math.random() * maxPos))
    }
    return res
  }

  /**
   * 创建COS对象实例
   * @returns Object
   */
  async getCosInstance () {
    const getKey = await this.getKey()
    if (getKey) {
      const { tmpSecretId, tmpSecretKey, sessionToken } = this.credentials
      const { startTime, expiredTime } = this.keyBackData
      const params = {
        TmpSecretId: tmpSecretId,
        TmpSecretKey: tmpSecretKey,
        SecurityToken: sessionToken,
        StartTime: startTime,
        ExpiredTime: expiredTime
      }
      const _cos = new COS({
        getAuthorization: function (options, callback) {
          callback(params)
        }
      })
      return _cos
    }
    return null
  }

  /**
   * 单个文件上传到腾讯云cos
   * @param {*} file
   * @returns
   */
  async uploadHandle (file) {
    const cos = await this.getCosInstance()
    if (cos) {
      const KEY = `${this.bucketPrefix}${this.generateKey(
        file
      )}`
      console.log('KEY', KEY)
      return new Promise((resolve, reject) => {
        // if (this.needLoading) {
        //   var loadingInstance = Loading.service({ fullscreen: true })
        // }

        cos.uploadFile(
          {
            Bucket: this.bucketName /* 填入您自己的存储桶,必须字段 */,
            Region: BUCKET_REGION /* 存储桶所在地域,例如ap-beijing,必须字段 */,
            Key: KEY /* 存储在桶里的对象键(例如1.jpg,a/b/test.txt),必须字段 */,
            Body: file /* 必须,上传文件对象,可以是input[type="file"]标签选择本地文件后得到的file对象 */,
            SliceSize:
              1024 *
              1024 *
              this
                .sliceSize /* 触发分块上传的阈值,超过5MB使用分块上传,非必须 */,
            onTaskReady: function (taskId) {
              /* 非必须 */
              // console.log(taskId)
            },
            onProgress: function (progressData) {
              /* 非必须 */
              // console.log(JSON.stringify(progressData))
              const percent = parseInt(progressData.percent * 10000) / 100
              const speed =
                parseInt((progressData.speed / 1024 / 1024) * 100) / 100
              console.log('进度:' + percent + '%; 速度:' + speed + 'Mb/s;')
            },
            onFileFinish: function (err, data, options) {
              /* 非必须 */
              console.log(options.Key + '上传' + (err ? '失败' : '完成'))
            }
          },
          (err, data) => {
            // loadingInstance && loadingInstance.close()
            if (err) {
              Message.error(err)
              reject(err)
            }
            const url = `${this.bucketDomain}${KEY}`
            if (this.bucketType === 'video') {
              const fileName = file.name || ''
              const name = fileName.split('.').slice(0, fileName.split('.').length - 1).join('.') // 获取文件名称
              resolve({ url, name })
            } else {
              resolve(url)
            }
          }
        )
      })
    }
  }

  /**
   * 媒体信息接口
   */
  async getMediaInfoHandle (key) {
    const cos = await this.getCosInstance()
    if (cos) {
      return new Promise((resolve, reject) => {
        cos.request(
          {
            Bucket: this.bucketName /* 填入您自己的存储桶,必须字段 */,
            Region: BUCKET_REGION /* 存储桶所在地域,例如ap-beijing,必须字段 */,
            Method: 'GET',
            Key: key /* 存储桶内的媒体文件,必须字段 */,
            Query: {
              'ci-process': 'videoinfo' /** 固定值,必须 */
            }
          },
          function (err, data) {
            if (err) {
              Message.error(err)
              reject(err)
            }
            resolve(data)
          }
        )
      })
    }
  }
}

export default Cos

3、封装图片上传组件、调用上传方法

新建image-upload.vue封装图片上传组件,调用上传方法:

<template>
  <div class="common-image-upload-cos-container">
    <div class="image-upload-cos-content" :class="{'limit-num': fileList.length>=limit, 'mini': size === 'small'}">
      <el-upload ref="upload" :file-list="fileList" list-type="picture-card" action="#" :http-request="uploadImageHandle" v-loading="uploadLoading" :on-preview="handlePictureCardPreview" :on-remove="handleRemove" :on-exceed="exceedTips" :on-success="handeSuccess" :before-upload="beforeAvatarUpload" :on-change="onChangeHandle">
        <i class="el-icon-plus"></i>
        <p class="el-upload__tip" slot="tip" v-if="tips">{{tips}}</p>
        <div slot="file" slot-scope="{file}" class="img-con">
          <img crossorigin class="el-upload-list__item-thumbnail" :src="file.url" alt="">
          <span class="el-upload-list__item-actions">
            <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
              <i class="el-icon-zoom-in"></i>
            </span>
            <span class="el-upload-list__item-delete" @click="handleRemove(file)" v-if="!disabled">
              <i class="el-icon-delete"></i>
            </span>
            <span v-if="size === 'small' && !disabled" style="display:block;marginLeft:0px" @click="onChangeHandle(file)">
              <i class="el-icon-edit"></i>
            </span>
            <span v-if="size !== 'small' && !disabled" @click="onChangeHandle(file)">
              <i class="el-icon-edit"></i>
            </span>
          </span>
        </div>
      </el-upload>
    </div>
    <div class="img-preview-dialo">
      <el-dialog :visible.sync="dialogVisibleShow" :append-to-body="append_to_body" :modal-append-to-body="modal_append_to_body">
        <img style="width:100%" :src="dialogImageUrl" crossorigin alt>
      </el-dialog>
    </div>
  </div>
</template>

<script>
import Cos from '@/utils/uploadcos'
import ImageCompressor from '@/assets/js/image-compressor.min'
export default {
  // 上传图片到腾讯云对象存储
  name: 'ImageUpload',
  componentName: 'ImageUpload',
  data () {
    return {
      uploadLoading: false,
      imgWidth: 0,
      imgHeight: 0,
      picIndex: -1,
      dialogImageUrl: '',
      dialogVisibleShow: false,
      fileList: [],
      vmodelType: '',
      cos: new Cos({
        busiType: this.busiType,
        bucketType: this.bucketType
      })
    }
  },
  props: {
    // 接收 String, Array类型,默认为 String 类型
    value: {
      type: [String, Array],
      default: ''
    },
    tips: {
      type: String,
      default: ''
    },
    size: {
      type: String,
      default: 'medium' // small
    },
    limit: {
      // 限制上传图片张数
      type: Number,
      default: 1
    },
    limitSize: {
      // 限制上传图片大小
      type: Number,
      default: 10
    },
    valueType: {
      type: String,
      default: 'String' // Object
    },
    bucketType: {
      type: String,
      default: 'image'
    },
    // 是否校验图片尺寸,默认不校验
    isCheckPicSize: {
      type: Boolean,
      default: false
    },
    checkWidth: {
      type: Number,
      default: 0 // 图片限制宽度
    },
    checkHeight: {
      type: Number,
      default: 0 // 图片限制高度
    },
    topLimitWidth: {
      type: Number,
      default: 0 // 图片限制宽度上限(有时需要校验上传图片宽度在一个范围内)
    },
    topLimitHeight: {
      type: Number,
      default: 0 // 图片限制高度上限(有时需要校验上传图片高度在一个范围内)
    },
    index: {
      type: Number,
      default: -1 // 当前图片index,限制可以上传多张时,针对某一张进行操作,需要知道当前的index
    },
    limitType: {
      type: String,
      default: '' // (限制上传图片格式)传入格式:png,jpg,gif  png,jpg,webp  png,jpg,gif,webp
    },
    busiType: {
      type: String,
      default: 'ART'
    },
    // 禁用开关
    disabled: {
      type: Boolean,
      default: false
    },
    isGzip: {
      // 是否压缩图片,默认不压缩(false);传入 true 时,图片大小大于80KB且不是gif格式时进行压缩
      type: Boolean,
      default: false
    },
    append_to_body: {
      type: Boolean,
      default: false
    },
    modal_append_to_body: {
      type: Boolean,
      default: true
    }
  },
  components: {},
  created () {
    if (this.valueType === 'Object') {
      this.vmodelType = 'array'
    }
    if (this.value) {
      this.modifyValue()
    }
  },
  watch: {
    value: {
      deep: true,
      handler: function (val, oldVal) {
        if (val) {
          this.modifyValue()
        } else {
          this.fileList = []
        }
      }
    }
  },
  methods: {
    findItem (uid) {
      this.fileList.forEach((ele, i) => {
        if (uid === ele.uid) {
          this.picIndex = i
        }
      })
    },
    onChangeHandle (file, fileList) {
      // console.log('onChangeHandle file, fileList', file, fileList)
      this.findItem(file.uid)
      this.$refs.upload.$refs['upload-inner'].handleClick()
    },
    handleRemove (file) {
      // console.log('handleRemove file', file)
      this.findItem(file.uid)
      this.fileList.splice(this.picIndex, 1)
      this.exportImg()
    },
    exportImg () {
      if (this.fileList.length !== 0) {
        if (this.imgWidth && this.imgHeight) {
          if (this.valueType === 'Object') {
            const imgs = this.fileList.map(item => {
              return {
                url: item.url,
                name: item.fileName ? item.fileName : item.name
              }
            })
            this.$emit('input', imgs)
            this.$emit('imgChange', this.index)
          } else {
            if (this.vmodelType === 'array') {
              const imgs = this.fileList.map(item => {
                if (item.url) {
                  return item.url
                }
              })
              this.$emit('input', imgs)
              this.$emit('imgChange', this.index)
            } else {
              const resUrl = this.fileList[0].url
              this.$emit('input', resUrl)
              this.$emit('imgChange', this.index)
            }
          }
        } else {
          this.$message.error('当前未获取到图片宽高数据,请重新上传图片!')
        }
      } else {
        this.$emit('input', '')
        this.$emit('imgChange', this.index)
      }
      this.picIndex = -1
    },
    uploadImageHandle (file) {
      if (this.vmodelType === 'string') {
        if (this.picIndex !== -1 && this.fileList.length) {
          // 如果是单图编辑替换状态,手动删除被替换的文件
          this.fileList.splice(this.picIndex, 1)
        }
      }
      // console.log('uploadImageHandle file', file)
      // return
      // 不需要压缩、图片小于80KB或是gif图,不压缩直接上传;大于80KB,压缩图片后上传
      const uploadFile = file.file
      if ((!this.isGzip) || (uploadFile.size / 1024 < 80) || (uploadFile.type == 'image/gif')) {
        this.uploadToCos(uploadFile)
      } else {
        let file = uploadFile
        if (!file) {
          return
        }
        var options = {
          file: file,
          quality: 0.6,
          mimeType: 'image/jpeg',
          maxWidth: 6000,
          maxHeight: 6000,
          // width: 1000, // 指定压缩图片宽度
          // height: 1000, // 指定压缩图片高度
          minWidth: 10,
          minHeight: 10,
          convertSize: Infinity,
          loose: true,
          redressOrientation: true,
          // 压缩前回调
          beforeCompress: (result) => {
            console.log('压缩之前图片尺寸大小: ', result.size / 1024)
            // console.log('mime 类型: ', result.type);
          },
          // 压缩成功回调
          success: (result) => {
            console.log(result);
            console.log('压缩之后图片尺寸大小: ', result.size / 1024)
            console.log('实际压缩率: ', ((file.size - result.size) / file.size * 100).toFixed(2) + '%');
            this.uploadToCos(result)
          },
          // 发生错误
          error: (msg) => {
            console.error(msg);
            this.$message.error(msg)
          }
        };
        /* eslint-disable no-new */
        new ImageCompressor(options)
      }
    },
    // 上传文件
    uploadToCos (file) {
      // console.log('uploadToCos uploadFile', file)
      // return
      this.uploadLoading = true
      this.cos.uploadHandle(file).then((url) => {
        if (url) {
          if (this.imgWidth && this.imgHeight) {
            const resUrl = url + '?width=' + this.imgWidth + '&height=' + this.imgHeight
            const obj = { url: resUrl, name: file.name }
            if (this.picIndex < 0) {
              this.fileList.push(obj)
            } else {
              this.fileList[this.picIndex] = obj
            }
            this.exportImg()
          } else {
            this.$message.error('当前未获取到图片宽高数据,请重新上传图片!')
          }
        } else {
          this.fileList.splice(this.picIndex, 1)
        }
        this.uploadLoading = false
      })
    },
    modifyValue () {
      if (this.valueType === 'Object') {
        this.fileList = this.value.map(item => ({
          url: item.url,
          name: item.name
        }))
      } else {
        // 判断是否是String
        const str = this.value
        const res = ((str instanceof String) || (typeof str).toLowerCase() === 'string')
        if (res === true) {
          this.vmodelType = 'string'
        } else {
          this.vmodelType = 'array'
        }
        if (this.vmodelType === 'array') {
          this.fileList = this.value.map(item => ({ url: item }))
        } else {
          this.fileList = [{ url: this.value }]
        }
      }
    },
    beforeAvatarUpload (file) {
      const imgType = file.type
      const isLtSize = file.size / 1024 / 1024 < this.limitSize
      const TYPE_ALL = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp']
      let isType = true
      // console.log('this.limitType', this.limitType)
      // console.log('imgType', imgType)
      if (this.limitType) {
        const limitTypeArr = this.limitType.split(',')
        const limutTypeFlagArr = []
        const IMG_STATUS = {
          jpg: 'image/jpeg',
          jpeg: 'image/jpeg',
          png: 'image/png',
          gif: 'image/gif',
          webp: 'image/webp'
        }
        limitTypeArr.forEach(item => {
          if (IMG_STATUS[item]) limutTypeFlagArr.push(IMG_STATUS[item])
        })
        if (limutTypeFlagArr.indexOf(imgType) === -1) {
          isType = false
          this.$message.error(`仅支持上传 ${this.limitType} 格式的图片!`)
        }
      } else {
        // 默认情况,未传入校验类型格式,则默认可以接受全部格式
        if (TYPE_ALL.indexOf(imgType) === -1) {
          isType = false
          this.$message.error('仅支持上传 jpg、png、jpeg、webp、gif 格式的图片!')
        }
      }

      if (!isLtSize) {
        this.$message.error(`上传图片大小不能超过${this.limitSize}MB!`)
      }
      if (this.isCheckPicSize === true) {
        const width = this.checkWidth
        const height = this.checkHeight
        const topWidth = this.topLimitWidth
        const topHeight = this.topLimitHeight
        const that = this
        const isSize = new Promise((resolve, reject) => {
          // console.log('Promise')
          // window对象,将blob或file读取成一个url
          const _URL = window.URL || window.webkitURL
          const img = new Image()
          img.onload = () => { // image对象的onload事件,当图片加载完成后执行的函数
            // console.log('img.onload')
            that.imgWidth = img.width
            that.imgHeight = img.height
            if (width && height) { // 校验图片的宽度和高度
              let valid = false
              if (topWidth && topHeight) {
                // 校验图片宽度和高度范围
                valid = ((width <= img.width) && (img.width <= topWidth)) && ((height <= img.height) && (img.height <= topHeight))
              } else if (topHeight) {
                // 校验图片高度范围
                valid = img.width === width && ((height <= img.height) && (img.height <= topHeight))
              } else if (topWidth) {
                // 校验图片宽度范围
                valid = ((width <= img.width) && (img.width <= topWidth)) && img.height === height
              } else {
                // 校验图片宽度、高度固定值
                valid = img.width === width && height === img.height
              }
              valid ? resolve() : reject(new Error('error'))
            } else if (width) { // 只校验图片的宽度
              let valid = false
              if (topWidth) {
                // 校验图片宽度范围
                valid = (width <= img.width) && (img.width <= topWidth)
              } else {
                // 校验图片宽度固定值
                valid = img.width === width
              }
              valid ? resolve() : reject(new Error('error'))
            } if (height) { // 只校验图片的高度
              let valid = false
              if (topHeight) {
                // 校验图片高度范围
                valid = (height <= img.height) && (img.height <= topHeight)
              } else {
                // 校验图片高度固定值
                valid = img.height === height
              }
              valid ? resolve() : reject(new Error('error'))
            }
          }
          img.src = _URL.createObjectURL(file)
        }).then(() => {
          // console.log('then')
          return file
        }, () => {
          // console.log('reject')
          let text = ''
          if (width && height) {
            if (topWidth && topHeight) {
              text = `图片尺寸限制为:宽度${width}~${topWidth}px,高度${height}~${topHeight}px!`
            } else if (topHeight) {
              text = `图片尺寸限制为:宽度${width}px,高度${height}~${topHeight}px!`
            } else if (topWidth) {
              text = `图片尺寸限制为:宽度${width}~${topWidth}px,高度${height}px!`
            } else {
              text = `图片尺寸限制为:宽度${width}px,高度${height}px!`
            }
          } else if (width) {
            if (topWidth) {
              text = `图片尺寸限制为:宽度${width}~${topWidth}px!`
            } else {
              text = `图片尺寸限制为:宽度${width}px!`
            }
          } else if (height) {
            if (topHeight) {
              text = `图片尺寸限制为:高度${height}~${topHeight}px!`
            } else {
              text = `图片尺寸限制为:高度${height}px!`
            }
          }
          this.$message.error(text)
          return Promise.reject(new Error('error'))
        })
        return isType && isLtSize && isSize
      } else {
        // window对象,将blob或file读取成一个url
        const _URL = window.URL || window.webkitURL
        const img = new Image()
        img.onload = () => { // image对象的onload事件,当图片加载完成后执行的函数
          this.imgWidth = img.width
          this.imgHeight = img.height
        }
        img.src = _URL.createObjectURL(file)
        return isType && isLtSize
      }
    },
    handlePictureCardPreview (file) {
      this.dialogImageUrl = file.url
      this.dialogVisibleShow = true
    },
    exceedTips (file, fileList) {
      this.$message(`最多上传${fileList.length}个文件!`)
    },
    handeSuccess (res, file, fileList) {
      console.log('handeSuccess')
    }
  }
}
</script>

<style lang='less'>
@small-size: 80px;
.common-image-upload-cos-container {
  .el-dialog {
    background: transparent;
    -webkit-box-shadow: none;
    box-shadow: none;
    margin-top: 0 !important;
    width: auto;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    -webkit-transform: translate(-50%, -50%);
    .el-dialog__header {
      display: none;
    }
    .el-dialog__body {
      text-align: center;
      img {
        width: auto;
        max-width: 1000px;
      }
    }
  }
}
.image-upload-cos-content&&.limit-num {
  .el-upload--picture-card {
    display: none !important;
  }
}
.image-upload-cos-content&&.mini {
  .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  .el-upload-list__item {
    width: @small-size;
    height: @small-size;
    text-align: center;
    /*去除upload组件过渡效果*/
    transition: none !important;
  }
  .el-upload--picture-card {
    width: @small-size;
    height: @small-size;
    line-height: @small-size;
    text-align: center;
  }
}
.el-upload-list__item&&.is-success {
  .img-con {
    width: 100%;
    height: 100%;
  }
}
</style>

action:上传地址,必填,直接上传后端接口时填入接口链接;自定义上传方法使用 http-request,所以,填任意字符串即可,没有实际意义。

http-request:覆盖默认上传行为,这里自定义上传行为。

4、页面使用组件

<template>
  <div>    
    <img-upload v-model="bgImg" :isGzip="true" :size="'small'" :tips="'建议图片宽度为350,JPG、JPGE、PNG 小于5M,仅限上传一张'" :limit="1" :limitSize="5"></img-upload>
    <img-upload v-model="mainImg" tips="宽度750px,高度范围:170px~1334px" :isCheckPicSize="true" :checkWidth="750" :checkHeight="170" :topLimitHeight="1334" :limit="1" size="small"></img-upload> 
    <img-upload v-model="imgArr" :tips="'多图数组'" :limit="6" :limitSize="1"></img-upload>
    <img-upload v-model="imgList" :tips="'多图数组'" :limit="6" :limitSize="1" valueType="Object"></img-upload>
  </div>
</template>
<script>
import ImgUpload from '@/components/image-upload'

export default {
  name: 'demo',
  components: {
    ImgUpload
  },
  data () {
    return {
      bgImg: '',
      mainImg: '',
      imgArr: [ 'https://xxx', 'https://xxx']
      imgList: [{
        name: '1.jpg',
        url: 'https://xxx'
      }]     
     }    
  },
  props: {
  },
  watch: {
  },
  created () {    
  },
  mounted () {
  },
  methods: {    
  }
}
</script>

上传大小小于 80M 的图片,过程中打印进度信息如下:
在这里插入图片描述

上传大小大于 80M 的图片,过程中打印进度信息如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/691699.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于NXP i.MX 6ULL——MQTT通信协议的开发案例

前 言 本指导文档适用开发环境&#xff1a; Windows开发环境&#xff1a;Windows 7 64bit、Windows 10 64bit Linux开发环境&#xff1a;Ubuntu 18.04.4 64bit 拟机&#xff1a;VMware15.1.0 U-Boot&#xff1a;U-Boot-2020.04 Kernel&#xff1a;Linux-5.4.70 Linux S…

深入理解ThreadPoolExecutor线程池工作原理源码解析

文章目录 0. 前言1. 生命周期管理1.1 创建1.2 执行1.2.1 任务执行入口1.2.2 addWorker解析1.2.3 Worker类解析 1.3 关闭1.4 终止阶段 2. 总结 0. 前言 背景&#xff1a;最近技术交流群里有个新同学&#xff0c;面试的时候被问到线程池相关的问题&#xff0c;答的不是很好&#…

LinkedIn领英如何创建公司主页?附领英产品专区创建方法

领英常见问题-如何创建公司主页&#xff1f; 领英不仅可以创建个人主页&#xff0c;还可以以企业的身份创建公司主页。 公司主页就相当于自己的官网&#xff0c;可以发布动态&#xff0c;展示公司信息&#xff0c;做官网外链&#xff0c;对公司来讲也是一种品牌形象宣传&…

PDF转换软件有哪些?分享免费好用的PDF转换工具!

PDF是在办公和学习中常用的文件格式&#xff0c;它包含文字、图片、数据等各种信息&#xff0c;可以说是功能丰富。然而&#xff0c;有时我们需要将PDF转换为PPT格式以便于演示&#xff0c;或者将其转换为Word格式以节省内存空间。这时候就需要使用PDF转换软件。下面我将介绍一…

20230618_ISP-pipeline-hdrplus_contrast

原理&#xff1a; global映射曲线&#xff0c;通过这个曲线控制黑的更黑&#xff0c;白的更白 b&#xff1a;黑电平 s&#xff1a;强度&#xff0c;值越大越接近yx&#xff1b;越小对比度越强 代码&#xff1a; 简单的映射表&#xff0c;没什么好讲的 效果&#xff1a; before&…

【Spring】设计思想

一、Spring 是什么&#xff1f; Spring是一个开源的Java框架&#xff0c;有着活跃而庞大的社区&#xff08;例如&#xff1a;Apache&#xff09;&#xff0c;Spring 提供了一系列的工具和库&#xff0c;可以帮助开发者构建高效、可靠、易于维护的企业级应用程序。Spring的核心…

jupyter插件nbextensions及Anaconda安装nbextensions

安装nbextensions 以管理员身份运行Anaconda Prompt&#xff0c;依次敲以下命令 用清华源安装 pip install jupyter_contrib_nbextensions -i https://pypi.tuna.tsinghua.edu.cn/simple pip install jupyter_nbextensions_configurator -i https://pypi.tuna.tsinghua.edu.cn…

如何连接 ONLYOFFICE 文档与Confluence Cloud

在本文中&#xff0c;我们来解释如何将ONLYOFFICE文档编辑器连接至Confluence Cloud。 Confluence Cloud 是什么&#xff1f; Confluence Cloud 是一款 Web 端生产力工具&#xff0c;可帮助用户在共享工作区中创建文档与其他内容&#xff0c;同时还可对其进行共享并开展协作。…

vue多次跳转同一页面不触发created刷新数据

摘要&#xff1a; 今天遇到一个问题&#xff0c;就是vue中跳转同一个详情页面的时候&#xff0c;路由没有变化&#xff0c;但是后面的参数有变&#xff0c;需要重新触发created钩子函数来触发方法来刷新数据&#xff01; 分析&#xff1a; 其实是因为没有触发vue的created或者m…

机器视觉初步9:目标检测专题

文章目录 1.Two-Stage方法1.1 Faster R-CNN1.2 R-FCN 2.One-Stage方法2.1 YOLOv3(你只看一次)2.2 SSD&#xff08;单次多框检测器&#xff09; 3.传统滑动窗口方法 机器视觉领域中常见的目标检测方法主要分为以下两类&#xff1a; Two-Stage方法1&#xff1a;在这类方法中&…

ThreadPoolExecutor的addWorker方法

该方法内部有两个for循环。外for循环用于校验线程池的线程个数。内for循环用于添加for循环并启动工作线程。 retry:打上标记位&#xff0c;方便后期内层for循环跳出到外层for循环。int c ctl.get();获取ctl的值。int rs runStateOf©; 获取ctl高三位的值。if (rs > SH…

【2022吴恩达机器学习课程视频翻译笔记】2.2监督学习-part-1

B站上面那个翻译我有点看不懂&#xff0c;打算自己啃英文翻译了&#xff08;有自己意译的部分&#xff09;&#xff0c;然后懒得做字幕&#xff0c;就丢在博客上面了&#xff0c;2.2之前的章节结合那个机翻字幕能看懂 监督学习 part 1(Supervised learning part 1) Supervise…

MySQL数据库的优化技术三

如何选择mysql的存储引擎 在开发中&#xff0c;我们经常使用的存储引擎 myisam / innodb/ memory存储引擎针对的是表和数据库 事务&#xff1a;MySQL事务主要用于处理操作量大&#xff0c;复杂度高的数据&#xff0c;比如说&#xff0c;在人员管理系统中&#xff0c;你删除一…

ProTable查询表单必填项不生效解决方法

配置完发现不生效&#xff0c;需要在protable组件里再配置一项属性form才能生效 如此才能真正生效

Kafka消息队列核心概念以及部署

文章目录 1.消息队列核心概念1.1.为什么要引入消息队列1.2.消息队列的流派 2.Kafka消息队列基本概念2.1.Kafka消息队列基本概念2.2.Kafka与Zookeeper的关系2.3.Kafka消息队列各组件概念2.4.Kafka消息队列应用场景 3.部署Kafka消息队列3.1.搭建Zookeeper分布式协调服务3.2.部署K…

loss.backward

如何计算&#xff1a;autograd包根据tensor进行过的数学运算来自动计算梯度 注意&#xff1a;1&#xff1a;autograd实现的。2&#xff1a;对tensor进行计算梯度。 之前损失计算&#xff1a;分割损失和边界损失计算正常。 踩坑1&#xff1a;模型有两个损失&#xff0c;分别为分…

FTL没有映射管理,跟上班没有钱有什么区别

大家好&#xff0c;我是五月。 前言 FTL&#xff08;Flash Translation Layer&#xff09;&#xff0c;即闪存转换层&#xff0c;是各种存储设备的核心算法&#xff0c;作用是将Host传下来的逻辑地址转换成物理地址&#xff0c;也就是映射。 可以说&#xff0c;地址映射是FT…

【五、软件包管理】

1 rpm rpm -qa 查询命令 [rootredis100 ~]# rpm -qa[rootredis100 ~]# rpm -qa |grep firefox firefox-68.10.0-1.el7.centos.x86_64rpm -e 卸载命令 [rootredis100 ~]# rpm -e fixerpm -ivh 安装命令 2 yum [rootredis100 ~]# yum -y install firefox修改网络源 切换目录…

机器学习——概率与统计

参考资料&#xff1a; 《机器学习》周志华https://zhuanlan.zhihu.com/p/27056207 1 马尔可夫链 1.1 定义 直观含义&#xff1a;在已知现在的条件下&#xff0c;过去与未来相互独立。 1.2 马尔可夫模型 根据定义&#xff0c;A 必为方阵 其中&#xff0c; p i j ( n ) P {…

JMeter在高分辨率电脑上,页面显示字体特别小

最近使用JMeter的过程中&#xff0c;发现一个问题&#xff0c;在高分辨率的电脑上&#xff0c;JMeter启动后&#xff0c;页面显示的字体特别小&#xff0c;上图 我电脑的分辨率是2880*1800&#xff0c;缩放200% 上图里显示的字体真心看不清楚 我以为是JMeter的bug&#xff0c…