el-upload 实现列表拖拽,上传图片更换/查看/删除功能
本文介绍了基于Vue和Element UI实现图片上传、拖拽排序及交互功能的技术方案。通过vuedraggable组件实现图片拖拽排序,使用el-upload组件封装上传功能,并实现了鼠标悬停显示更换/查看/删除按钮的交互效果。方案包括:1)安装配置vuedraggable;2)封装支持图片预览、替换和删除功能的el-upload组件;3)组件参数说明及使用示例。该方案提供完整的图片上传管理功能,
·
项目需求:在表单中需要上传图片,上传成功的图片列表项需要拖拽排序,鼠标划过当前图片,显示更换/查看/删除功能,点击执行相对应的功能
实现效果:


一、安装 VueDraggable
# 使用 npm 安装
npm install vuedraggable --save
# 使用 yarn 安装
yarn add vuedraggable
二、使用
1. 引入并注册组件
import draggable from 'vuedraggable';
components: { draggable }
2. 组件使用
<draggable
v-model="fileList"
class="flexBox"
:options="{ group: { name: 'file', pull: 'clone', put: false } }"
@end="draggableEnd">
</draggable>
三、el-upload 组件封装
<template>
<div>
<!-- 上传多张图片 新增更换查看删除功能 列表可拖拽 默认不验证上传大小 -->
<el-upload
class="upload-demoDelete"
ref="uploadRef"
action="#"
:headers="headers"
:disabled="disabled"
:limit="limitNum"
:show-file-list="false"
:auto-upload="false"
list-type="picture-card"
:multiple="isMul"
:on-change="onChangeUpload"
:on-exceed="handleExceed"
:file-list="fileList"
>
</el-upload>
<!-- 上传列表 -->
<draggable v-model="fileList" class="flexBox" @end="draggableEnd" :disabled="disabled" handle=".pacbox">
<div v-for="(file, index) in fileList" :key="file.url" class="pacbox" @mouseenter="showActions = index"
:style="{ width: width + 'px', height: height + 'px' }" @mouseleave="showActions = -1">
<img :src="file.url" alt="" :style="{ width: width + 'px', height: height + 'px' }" />
<div v-if="showActions === index" class="button-container">
<div class="flexBtn">
<div class="button" @click="handleReplace(file, index)" v-if="!disabled">更换</div>
<div @click.stop="handlePreview(file)" class="button marginLeft10-common">查看</div>
<div @click.stop="handleRemove(file, index)" class="button marginLeft10-common" v-if="!disabled">删除</div>
</div>
</div>
</div>
<div class="uploads marginTop15-common" v-if="fileList.length < limit && !disabled"
:style="{ width: width + 'px', height: height + 'px', lineHeight: height + 'px' }" @click="handleUpload">
<i class="el-icon-plus pic-uploader-icon"></i>
</div>
</draggable>
<!-- 查看弹窗 -->
<el-dialog :close-on-click-modal="false" :visible.sync="dialogVisible" :append-to-body='true'>
<div style="text-align: center;">
<img :src="dialogImageUrl" alt="" style="width: 100%">
</div>
</el-dialog>
</div>
</template>
<script>
import { getToken } from "@/utils/auth";
import draggable from 'vuedraggable';
import request from "@/utils/request"
export default {
components: { draggable },
data() {
return {
dialogImageUrl: '',
dialogVisible: false,
headers: {
Authorization: "Bearer " + getToken(),
token: getToken(),
},
fileList: [],
onceWatch: false,
showActions: -1, // 当前鼠标悬停显示的操作按钮
ischange: false, // 是否更换
changeIndex: null, // 当前选择图片index
limitNum: 6, // 当前限制的数量
};
},
props: {
value: {
default: "",
type: String,
},
disabled: { // 查看页面--禁止上传不显示更换和删除
default: false,
type: Boolean,
},
limit: {
type: Number,
default: 6,
},
isMul: {
default: false,
type: Boolean,
},
islimitSize: {
default: false,
type: Boolean,
},
width: String,
height: String,
},
methods: {
//图片上传接口
upImg(data) {
data.append("dir", 'other')
data.append("type", 0)
return request({
url: "/system/pc/ct/oss/upload",
method: "post",
data: data,
})
},
// 文件超出个数限制
handleExceed() {
this.$message.warning(`最多可选择${this.limit}个`);
},
// 文件上传
onChangeUpload(file, fileList) {
const isJPEG = file.raw.type === "image/jpeg"
const isJPG = file.raw.type === "image/jpg"
const isPNG = file.raw.type === "image/png"
const isLt6M = file.raw.size / 1024 / 1024 < 6
if (!isJPEG && !isJPG && !isPNG) {
return this.$message.error("请上传文件为图片格式!")
}
if (!isLt6M && this.islimitSize) {
return this.$message.error("上传图片大小不能超过 6M!")
}
const loading = this.$loading({
lock: true,
text: "上传中",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
})
const formData = new FormData()
formData.append("file", file.raw)
this.upImg(formData).then((response) => {
if (this.changeIndex !== null && this.ischange) {
// 替换对应索引的文件
const newFile = {
name: "",
url: response.data.url
};
this.fileList.splice(this.changeIndex, 1, newFile);
this.ischange = false
this.changeIndex = null // 重置
} else {
this.fileList.push({
name: "",
url: response.data.url
})
}
// 父组件传值
let imageValue = [];
this.fileList.forEach(el => {
imageValue.push(el.url)
})
this.$emit("input", imageValue.join(","));
loading.close();
})
},
handleUploadError() {
this.$message({
type: "error",
message: "上传失败",
});
this.loading.close();
},
// 初始化图片数据
updateFileList() {
if (this.value) {
this.fileList = this.value.split(",").map((item, index) => {
return {
name: "",
url: item
}
})
}
},
// 拖拽结束
draggableEnd() {
this.$emit("input", this.fileList.map((item) => item.url).join(","))
},
// 更换图片
handleReplace(file, index) {
this.ischange = true
this.changeIndex = index
this.limitNum = this.limit+1
if (this.$refs.uploadRef && this.$refs.uploadRef.$el) {
this.$refs.uploadRef.$el.querySelector('input').click()
}
},
// 查看图片
handlePreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
this.ischange = false
},
// 删除图片
handleRemove(file, index) {
this.fileList.splice(index, 1);
this.$emit("input", this.fileList.map((item) => item.url).join(","));
this.ischange = false
},
// 上传
handleUpload() {
this.limitNum = this.limit
if (this.$refs.uploadRef && this.$refs.uploadRef.$el) {
this.$refs.uploadRef.$el.querySelector('input').click()
}
}
},
mounted() {
this.limitNum = this.limit // 初始化limit
},
watch: {
value() {
if (!this.onceWatch) {
this.onceWatch = true
this.updateFileList(); // 更新文件列表
}
}
},
};
</script>
<style scoped lang="scss">
::v-deep .upload-demoDelete .el-upload--picture-card {
display: none;
}
.flexBox {
display: flex;
flex-wrap: wrap;
}
.uploads {
background-color: #F6FBFF;
background-repeat: no-repeat;
background-position: center center;
border: 1px solid #C6EBFF;
border-radius: 16px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.uploads .pic-uploader-icon {
font-size: 20px;
color: #fff;
width: 36px;
height: 36px;
line-height: 36px;
text-align: center;
background-color: #8AD2FF;
border-radius: 50%;
}
.pacbox img {
border-radius: 17px;
}
.pacbox:hover .button-container {
display: block;
}
.pacbox {
position: relative;
margin-right: 15px;
margin-top: 10px;
margin-bottom: 10px;
}
.button-container {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
transition: opacity 0.3s;
display: none;
border-radius: 17px;
}
.flexBtn {
display: flex;
justify-content: center;
align-items: center;
margin-top: 60px;
}
.button {
text-decoration: underline;
cursor: pointer;
font-size: 14px;
color: #007bff;
white-space: nowrap;
line-height: 20px;
}
</style>
四、组件使用
1.引入并注册组件
import UploadImageDelete from "@/components/UploadImageDelete";
components: { UploadImageDelete }
2.使用
<UploadImageDelete :limit='6' :isMul="true" :width="'148'" :height="'148'" :disabled="isLook" v-model="form.imgUrlsString" />
注意:
upImg 请求的接口 替换成自己的接口地址
使用了this.$emit("input")要使用 v-model="form.imgUrlsString" 绑定变量
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)