vue el-upload 上传图片列表校验不通过后多删除了一张图片

问题

最近在使用 element-uiel-upload 组件上传图片列表时,发现当上传的图片校验不通过时,会将上一张已经上传成功的图片删除了。

场景

已经上传了一张图片1,再上传另一张图片2,如果当前这张图片2校验不通过,会提示失败并且删除当前图片2,同时,也会将上一张已经上传成功的图片1也删除。

组件主要代码:

<el-upload multiple ref="upload"
 :action="url" :file-list="fileList"
 :before-upload="beforeAvatarUpload"
 :on-success="handleSuccess"
 :on-remove="handleRemove"
 accept=".jpg,.jpeg,.png,.gif,.JPG,.JPEG,.GIF">
     <el-button>
         <i class="el-icon-document" />上传
     </el-button>
     <span slot="tip" class="el-upload__tip">
         支持上传图片,单文件上传大小限制10MB,最多上传10张附件
      </span>
</el-upload>

-----------------------------------------------------------------------
// 上传前校验,只写了简单几个条件
beforeFileUpload (file) {
   const isLenLimit = this.fileList.length < 20;
   const isLtSize = file.size / 1024 / 1024 < this.limitSize;
   if (!isLenLimit) {
      this.$message.error('最多只能上传20个附件');
   }
   if (!isLtSize) {
      this.$message.error(`上传图片大小不能超过${this.limitSize}MB!`)
   }
   return isLenLimit && isLt10M;
},
// 附件删除
handleRemove (file, fileList) {
    this.findItem(file.uid)
    this.fileList.splice(this.picIndex, 1)
    fileList = JSON.parse(JSON.stringify(this.fileList))
    this.exportImg(fileList)
},

原因分析

beforeUpload 事件中,添加用户上传文件的判断操作,该事件在返回 false 时,会自动终止上传事件。
但是!!!当上传失败后,element-uiel-upload 组件会自动执行 on-remove 事件。
根据 handleRemove 方法,file 是上传失败的文件的信息,此时 this.fileList(保存上传成功的文件)中并没有保存这个文件,findItem 会返回 -1,splice(-1,1) 会删除 this.fileList 数组中的最后一个数据项。

解决方案

on-remove 事件中添加判断,执行删除操作时,区分已上传的图片和出错的图片,确认文件是否需要被删除。如下:

handleRemove (file, fileList) { // 删除图片
      if (file && file.status === 'success') {
        this.findItem(file.uid)
        this.fileList.splice(this.picIndex, 1)
        fileList = JSON.parse(JSON.stringify(this.fileList))
        this.exportImg(fileList)
      }
}

页面效果:
请添加图片描述

完整版代码:

<template>
  <div class="img-upload-container">
    <div class="img-upload" :class="{'limit-num': fileList.length>=limit, 'mini': size === 'small'}">
      <el-upload ref="upload" :action="url" :file-list="fileList" list-type="picture-card" :on-success="handleSuccess" :on-remove="handleRemove" :on-preview="handlePictureCardPreview" :before-upload="beforeAvatarUpload">
        <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 class="el-upload-list__item-thumbnail" :src="file.url" alt="" crossorigin>
          <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)">
              <i class="el-icon-delete"></i>
            </span>
            <span v-if="size === 'small'" style="display:block;marginLeft:0" class="el-upload-list__item-delete" @click="onChangeHandle(file)">
              <i class="el-icon-edit"></i>
            </span>
            <span v-else class="el-upload-list__item-delete" @click="onChangeHandle(file)">
              <i class="el-icon-edit"></i>
            </span>
          </span>
        </div>
      </el-upload>
      <el-dialog :visible.sync="dialogVisible">
        <img width="100%" append-to-body :src="dialogImageUrl" alt crossorigin />
      </el-dialog>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ImgUpload',
  componentName: 'ImgUpload',
  data () {
    return {
      imgWidth: 0,
      imgHeight: 0,
      url: `xxx/upload/file/upload`,
      fileList: [],
      dialogImageUrl: '',
      dialogVisible: false,
      picIndex: -1,
      vmodelType: ''
    }
  },
  props: {
    value: {
      // 接收 String, Array类型,默认为 String 类型
      type: [String, Array],
      default: ''
    },
    tips: {
      type: String,
      default: ''
    },
    size: {
      type: String,
      default: 'medium' // small
    },
    limit: {
      type: Number,
      default: 2
    },
    limitSize: {
      type: Number,
      default: 10
    },
    valueType: {
      type: String,
      default: 'String' // Object
    },
    // 是否校验图片尺寸,默认不校验
    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 // 图片限制高度上限(有时需要校验上传图片高度在一个范围内)
    },
    busiType: {
      type: Number,
      default: 2
    },
    index: {
      type: Number,
      default: -1 // 当前图片index,限制可以上传多张时,针对某一张进行操作,需要知道当前的index
    },
    limitType: {
      type: String,
      default: '' // (限制上传图片格式)传入格式:png,jpg,gif  png,jpg,webp  png,jpg,gif,webp
    }
  },
  watch: {
    value: {
      deep: true,
      handler: function (val, oldVal) {
        if (val) {
          if (this.valueType === 'Object') {
            this.fileList = this.value.map(item => ({ id: item.id, url: item.url, name: item.name }))
          } else {
            if (this.vmodelType === 'array') {
              this.fileList = this.value.map(item => ({ url: item }))
            } else {
              this.fileList = [{ url: val }]
            }
          }
        } else {
          this.fileList = []
        }
      }
    }
  },
  created () {
    if (this.valueType === 'Object') {
      this.vmodelType = 'array'
    } else {
      const res = this.isString(this.value)
      if (res === true) {
        this.vmodelType = 'string'
      } else {
        this.vmodelType = 'array'
      }
    }
    // console.log('created vmodelType', this.vmodelType)
    if (this.value) {
      if (this.valueType === 'Object') {
        this.fileList = this.value.map(item => ({
          id: item.id ? item.id : '',
          url: item.url,
          name: item.name
        }))
      } else {
        if (this.vmodelType === 'array') {
          this.fileList = this.value.map(item => ({ url: item }))
        } else {
          this.fileList = [{ url: this.value }]
        }
      }
    }
  },
  mounted () {
  },
  methods: {
    findItem (uid) {
      this.fileList.forEach((ele, i) => {
        if (uid === ele.uid) {
          this.picIndex = i
        }
      })
    },
    onChangeHandle (file, fileList) {
      // console.log('onChangeHandle', file, this.fileList)
      this.findItem(file.uid)
      this.$refs.upload.$refs['upload-inner'].handleClick()
    },
    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) => {
          // window对象,将blob或file读取成一个url
          const _URL = window.URL || window.webkitURL
          const img = new Image()
          // image对象的onload事件,当图片加载完成后执行的函数
          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(() => {
          return file
        }, () => {
          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
          // console.log('getWidthAndHeight', this.imgWidth, this.imgHeight)
        }
        img.src = _URL.createObjectURL(file)
        return isType && isLtSize
      }
    },
    // 判断是否是String
    isString (str) {
      return ((str instanceof String) || (typeof str).toLowerCase() === 'string')
    },
    handleRemove (file, fileList) { // 删除图片
      console.log('this.picIndex', this.picIndex)
      console.log('handleRemove file, fileList', file, fileList, this.fileList)
      console.log('this.fileList', this.fileList)
      if (file && file.status === 'success') {
        this.findItem(file.uid)
        this.fileList.splice(this.picIndex, 1)
        fileList = JSON.parse(JSON.stringify(this.fileList))
        this.exportImg(fileList)
      }
    },
    handleSuccess (res, file, fileList) {
      if (this.picIndex !== -1) {
        fileList.splice(this.picIndex, 1)
      }
      this.exportImg(fileList)
    },
    handleError (err) {
      this.$message.error(err)
    },
    handlePictureCardPreview (file) {
      this.dialogImageUrl = file.url
      this.dialogVisible = true
    },
    exportImg (fileList = []) {
      console.log('exportImg fileList', fileList)
      this.fileList = fileList
      if (fileList.length !== 0) {
        console.log(this.imgWidth)
        console.log(this.imgHeight)
        if (this.imgWidth && this.imgHeight) {
          if (this.valueType === 'Object') {
            const imgs = fileList.map(item => {
              if (item.response && item.response.result) {
                item.id = item.response.result[0].id
                item.url = item.response.result[0].url + '&width=' + this.imgWidth + '&height=' + this.imgHeight
                item.name = item.response.result[0].fileName
              }
              return {
                id: item.id,
                url: item.url,
                name: item.name
              }
            })
            this.$emit('input', imgs)
            this.$emit('imgChange', this.index)
            // console.log('exportImg imgs', imgs)
          } else {
            if (this.vmodelType === 'array') {
              const imgs = fileList.map(item => {
                if (item.response && item.response.result) {
                  item.url = item.response.result[0].url + '&width=' + this.imgWidth + '&height=' + this.imgHeight
                }
                return item.url
              })
              this.$emit('input', imgs)
              this.$emit('imgChange', this.index)
              // console.log('exportImg imgs', imgs)
            } else {
              const resUrl = fileList[0].response.result[0].url + '&width=' + this.imgWidth + '&height=' + this.imgHeight
              this.$emit('input', resUrl)
              this.$emit('imgChange', this.index)
              // console.log('exportImg resUrl', resUrl)
            }
          }
        } else {
          this.$message.error('当前未获取到图片宽高数据,请重新上传图片!')
        }
      } else {
        this.$emit('input', '')
        this.$emit('imgChange', this.index)
      }
      this.picIndex = -1
    }
  }
}
</script>

<style lang='less'>
@small-size: 80px;
.img-upload&&.limit-num {
  .el-upload--picture-card {
    display: none !important;
  }
}
.img-upload&&.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>

版权声明:本文为HH18700418030原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。