基于el-upload前端图片上传组件二次封装设计思路
需求如下:
- 图片上传需要限制格式
- 图片上传限制图片的张数
- 单张图片的大小
- 图片上传后可以进行拖拽排序
- 可以多张图片同时上传(发送多个请求即可)
- 如果是单张图片上传那么上传成功后就只显示图片即可 不必再显示上传按钮组件
设计如下:
定义变量:
// 限制上传的图片数量
limit: {
type: Number,
default: 99,
// 限制上传图片的文件大小(kb)
size: {
type: Number,
default: 5000,
},
还需要考虑到单个图片的情况 已经上传的url路径 由于业务的需要可能需要单独的文件夹命名等等
bizPath: {
type: String,
required: false,
default: 'temp',
postAction: {
type: String,
default: window._CONFIG['domianURL'] + '/sys/common/upload', // 默认的action url数据
// 是否是单图上传(单图上传就是已传图片和上传按钮重叠)
isSingle: {
type: Boolean,
default: false,
// 图片显示的宽度(px)
width: {
type: Number,
default: 100,
// 图片显示的高度(px)
height: {
type: Number,
default: 100,
},
最后设置一个绑定的值
// 图片数据(图片url组成的数组) 通过v-model传递
value: {
type: String,
default() {
return ''
},
计算属性
这里我们在外面传递了这个值后 需要利用计算的属性来绑定这值 由于是个字符串利用','号来拼接的 所以需要分割成数组 这样就可以用v-for 来循环展示即可
computed: {
// 图片数组数据
imgList: {
get() {
// 以逗号开始显示为逗号即可
if(this.value == '') {
return []
}else {
const urls = (this.value + '').split(',')
return urls
set(val) {
if (val.length < this.imgList.length) {
// 判断是删除图片时同步el-upload数据
this.syncElUpload(val)
let urls = ''
if (Array.isArray(val)) {
urls = val.join(',')
// 同步v-model
this.$emit('input', urls)
// 控制达到最大限制时隐藏上传按钮
isMaxHidden() {
return this.imgList.length >= this.limit
},
html的模板分析:
我们需要用到一个拖拽组件 选用vuedraggable 同时需要一个上传组件 选用el-upload即可
上传后需要点击可以显示这个图片的具体详情(原图片的大小和我们上传后的显示不一定是一个大小)选用el-image即可 对于el-image 这个组件 由于点击图片会出现一个弹出框来显示这个图片 所以为了限制z-index 的层级 最好是大于2999 由于el-dialog的显示层级问题
<div class="uploadWrapper">
<!-- 图片可以拖拽进行排序 -->
<vuedraggable
class="vue-draggable"
:class="{ single: isSingle, maxHidden: isMaxHidden }"
v-model="imgList"
tag="ul"
:data="{ biz: bizPath }"
draggable=".draggable-item"
@start="onDragStart"
@end="onDragEnd"
<!-- 拖拽元素 -->
v-for="(item, index) in imgList"
:key="item + index"
class="draggable-item"
:style="{ width: width + 'px', height: height + 'px' }"
<el-image :z-index="3000" :src="item" :preview-src-list="[item]"></el-image>
<div class="shadow" @click="onRemoveHandler(index)">
<i class="el-icon-delete"></i>
<!-- 上传按钮 -->
<el-upload
slot="footer"
ref="uploadRef"
class="uploadBox"
:style="{ width: width + 'px', height: height + 'px' }"
:action="postAction"
:headers="headers"
accept=".jpg,.jpeg,.png,.gif"
:show-file-list="false"
:multiple="!isSingle"
:limit="limit"
:before-upload="beforeUpload"
:on-success="onSuccessUpload"
:on-exceed="onExceed"
<i class="el-icon-plus uploadIcon">
<span class="uploading" v-show="isUploading">正在上传...</span>
<span v-if="!isUploading && limit && limit !== 99 && !isSingle" class="limitTxt">最多{{ limit }}张</span>
</el-upload>
</vuedraggable>
</template>
初始化
在一开始的组件加载的过程中 一般的上传需要在data里面传递token的值 所以不妨在初始化阶段获取即可
created() {
// 上传的token
const token = Vue.ls.get(ACCESS_TOKEN)
this.headers = { 'X-Access-Token': token }
},
watch属性
在图片的上传过程或者是上传后的图片删除了 由于图片是一个数组 我们需要对这个imageList 要做增加或者删除的操作 所以需要对这个数组进行深度检测
watch: {
value: {
handler(val) {
if (this.isFirstMount && this.value.length > 0) {
this.syncElUpload()
deep: true,
},
方法体之删除
的图片操作需要确定操作即可
// 移除单张图片
onRemoveHandler(index) {
this.$confirm('确定删除该图片?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
.then(() => {
this.imgList = this.imgList.filter((v, i) => {
return i !== index
.catch(() => {})
上传的图片之前的操作
// 上传图片之前
beforeUpload(file) {
this.isFirstMount = false
if (validImgUpload(file, this.size)) {
this.isUploading = true
return true
} else {
return false
},
上传图片成功后
const imgList = [].concat(this.imgList)
this.imgList = imgList
以上这个代码 不能直接写this.imgList = imgList 考虑到一个情况就是多个图片上传后 那么会导致视图不会更新这个图片 这个是因为第一张图片上传后就直接更新了数组 但是没有更新到视图里面 所以这个时候 我们不妨先手动的用一个空数组来生成一个新的数组 然后在赋值 这样vue就能检测到这个数组变化 然后更新到视图了
// 上传完单张图片
onSuccessUpload(res, file, fileList) {
console.log('onSuccessUpload', res)
// 这里需要根据你自己的接口返回数据格式和层级来自行修改
if (res.success) {
// 判断接口上传成功
if (this.imgList.length < this.limit) {
// 未超限时,把接口返回的图片url地址添加到imgList
this.imgList.push(res.result.filePath)
const imgList = [].concat(this.imgList)
this.imgList = imgList
// Vue.$set(this.imgList, this.imgList)
} else {
// 判断接口上传失败
this.syncElUpload()
this.$message({ type: 'error', message: res.msg })
this.isUploading = false
},
如果超出限制
// 超限
onExceed() {
this.$refs.uploadRef.abort() // 取消剩余接口请求
this.syncElUpload()
this.$message({
type: 'warning',
message: `图片超限,最多可上传${this.limit}张图片`,
},
拖拽的过程
在移动开始的时候对item图片增加一个外阴影 这样交互更友好一点 同时如果是结束了 那么就需要消除这个阴影
onDragStart(e) {
e.target.classList.add('hideShadow')
onDragEnd(e) {
e.target.classList.remove('hideShadow')
},
整个代码如下
<template>
<div class="uploadWrapper">
<!-- 图片可以拖拽进行排序 -->
<vuedraggable
class="vue-draggable"
:class="{ single: isSingle, maxHidden: isMaxHidden }"
v-model="imgList"
tag="ul"
:data="{ biz: bizPath }"
draggable=".draggable-item"
@start="onDragStart"
@end="onDragEnd"
<!-- 拖拽元素 -->
v-for="(item, index) in imgList"
:key="item + index"
class="draggable-item"
:style="{ width: width + 'px', height: height + 'px' }"
<el-image :z-index="3000" :src="item" :preview-src-list="[item]"></el-image>
<div class="shadow" @click="onRemoveHandler(index)">
<i class="el-icon-delete"></i>
<!-- 上传按钮 -->
<el-upload
slot="footer"
ref="uploadRef"
class="uploadBox"
:style="{ width: width + 'px', height: height + 'px' }"
:action="postAction"
:headers="headers"
accept=".jpg,.jpeg,.png,.gif"
:show-file-list="false"
:multiple="!isSingle"
:limit="limit"
:before-upload="beforeUpload"
:on-success="onSuccessUpload"
:on-exceed="onExceed"
<i class="el-icon-plus uploadIcon">
<span class="uploading" v-show="isUploading">正在上传...</span>
<span v-if="!isUploading && limit && limit !== 99 && !isSingle" class="limitTxt">最多{{ limit }}张</span>
</el-upload>
</vuedraggable>
</template>
<script>
import vuedraggable from 'vuedraggable'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { validImgUpload } from './utils'
import Vue from 'vue'
export default {
name: 'ImgUpload',
props: {
bizPath: {
type: String,
required: false,
default: 'temp',
postAction: {
type: String,
default: window._CONFIG['domianURL'] + '/sys/common/upload', // 默认的action url数据
// 图片数据(图片url组成的数组) 通过v-model传递
value: {
type: String,
default() {
return ''
// 限制上传的图片数量
limit: {
type: Number,
default: 99,
// 限制上传图片的文件大小(kb)
size: {
type: Number,
default: 5000,
// 是否是单图上传(单图上传就是已传图片和上传按钮重叠)
isSingle: {
type: Boolean,
default: false,
// 图片显示的宽度(px)
width: {
type: Number,
default: 100,
// 图片显示的高度(px)
height: {
type: Number,
default: 100,
data() {
return {
headers: {},
isUploading: false, // 正在上传状态
isFirstMount: true, // 控制防止重复回显
created() {
// 上传的token
const token = Vue.ls.get(ACCESS_TOKEN)
this.headers = { 'X-Access-Token': token }
computed: {
// 图片数组数据
imgList: {
get() {
// 以逗号开始显示为逗号即可
if(this.value == '') {
return []
}else {
const urls = (this.value + '').split(',')
return urls
set(val) {
if (val.length < this.imgList.length) {
// 判断是删除图片时同步el-upload数据
this.syncElUpload(val)
let urls = ''
if (Array.isArray(val)) {
urls = val.join(',')
// 同步v-model
this.$emit('input', urls)
// 控制达到最大限制时隐藏上传按钮
isMaxHidden() {
return this.imgList.length >= this.limit
watch: {
value: {
handler(val) {
if (this.isFirstMount && this.value.length > 0) {
this.syncElUpload()
deep: true,
mounted() {
if (this.value.length > 0) {
this.syncElUpload()
methods: {
createUniqueString() {
const timestamp = +new Date() + ''
const randomNum = parseInt((1 + Math.random()) * 65536) + ''
return (+(randomNum + timestamp)).toString(32)
// 同步el-upload数据
syncElUpload(val) {
const imgList = val || this.imgList
this.$refs.uploadRef.uploadFiles = imgList.map((v, i) => {
return {
name: 'pic' + i,
url: v,
status: 'success',
uid: this.createUniqueString(),
this.isFirstMount = false
// 上传图片之前
beforeUpload(file) {
this.isFirstMount = false
if (validImgUpload(file, this.size)) {
this.isUploading = true
return true
} else {
return false
// 上传完单张图片
onSuccessUpload(res, file, fileList) {
console.log('onSuccessUpload', res)
// 这里需要根据你自己的接口返回数据格式和层级来自行修改
if (res.success) {
// 判断接口上传成功
if (this.imgList.length < this.limit) {
// 未超限时,把接口返回的图片url地址添加到imgList
this.imgList.push(res.result.filePath)
const imgList = [].concat(this.imgList)
this.imgList = imgList
// Vue.$set(this.imgList, this.imgList)
} else {
// 判断接口上传失败
this.syncElUpload()
this.$message({ type: 'error', message: res.msg })
this.isUploading = false
// 移除单张图片
onRemoveHandler(index) {
this.$confirm('确定删除该图片?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
.then(() => {
this.imgList = this.imgList.filter((v, i) => {
return i !== index
.catch(() => {})
// 超限
onExceed() {
this.$refs.uploadRef.abort() // 取消剩余接口请求
this.syncElUpload()
this.$message({
type: 'warning',
message: `图片超限,最多可上传${this.limit}张图片`,
onDragStart(e) {
e.target.classList.add('hideShadow')
onDragEnd(e) {
e.target.classList.remove('hideShadow')
components: { vuedraggable },
</script>
<style lang="less" scoped>
/deep/ .el-upload {
width: 100%;
height: 100%;
// 上传按钮
.uploadIcon {
width: 100%;
height: 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #c0ccda;
background-color: #fbfdff;
border-radius: 6px;
font-size: 20px;
color: #999;
.limitTxt,
.uploading {
position: absolute;
bottom: 10%;
left: 0;
width: 100%;
font-size: 14px;
text-align: center;
// 拖拽
.vue-draggable {
display: flex;
flex-wrap: wrap;
padding-left: 0px;
padding-right: 15px;
.draggable-item {
margin-right: 5px;
margin-bottom: 5px;
border: 1px solid #ddd;
border-radius: 6px;
position: relative;
overflow: hidden;
.el-image {
width: 100%;
height: 100%;
.shadow {
position: absolute;
top: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 0.3s;
color: #fff;
font-size: 20px;
line-height: 20px;
padding: 2px;
cursor: pointer;
&:hover {
.shadow {
opacity: 1;
&.hideShadow {
.shadow {
display: none;
&.single {
overflow: hidden;
position: relative;
.draggable-item {
position: absolute;
left: 0;
top: 0;
z-index: 1;
&.maxHidden {
.uploadBox {
display: none;
// el-image
.el-image-viewer__wrapper {
.el-image-viewer__mask {
opacity: 0.8;