最近在使用 element-ui
的 el-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-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)
}
}
页面效果:
完整版代码:
<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>