• 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)
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    原因分析

    beforeUpload 事件中,添加用户上传文件的判断操作,该事件在返回 false 时,会自动终止上传事件。
    但是!!!当上传失败后,element-ui el-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)
          }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    完整版代码:

    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
  • 相关阅读:
    Redis下载和安装(Windows系统)
    Maven入门:Java项目构建和管理的利器
    Python学习笔记 - 数据结构:元组、列表、集合和字典
    pytorch入门,deep-learning-for-image-processing-master的test3-vggnet
    rust类型
    Python ML实战-工业蒸汽量预测01-赛题理解
    IntelliJ IDEA使用——常规设置
    第一章 - 第11节- 因特网概述 - 课件
    浅谈JS——理解回调函数
    Linux中gitlab-runner部署使用备忘
  • 原文地址:https://blog.csdn.net/HH18700418030/article/details/126588690