需求
需求想要一个可拖拽排序的图片列表,但是发现el-upload虽然可以实现照片墙,但是没办法拖拽
实现思路
使用 vue-draggable-plus
拖拽插件,隐藏Upload原有的已上传文件列表,自定义上传后文件列表的样式并绑定预览和删除功能
安装VueDraggablePlus库
功能齐全:全面继承 Sortable.js 的所有功能
无缝迁移:适用于 Vue 3 和 Vue2
灵活使用:支持组件、指令、函数式调用
类型强:用 TypeScript 编写,带有完整的 TS 文档
数据绑定:支持 v-model 双向绑定,不需要单独维护排序数据
自定义容器:可以自定某个容器作为拖拽容器,比 Sortable.js 更灵活
安装指令
npm install vue-draggable-plus
yarn add vue-draggable-plus
pnpm add vue-draggable-plus
使用(组件方式)
封装成公共组件 /src/components/DraggableImageUpload/index.vue
HTML代码
<template>
<div class="draggable_image_upload">
<VueDraggable
class="box-uploader"
ref="draggableRef"
v-model="curList"
:animation="600"
easing="ease-out"
ghostClass="ghost"
draggable="ul"
@start="onStart"
@update="onUpdate"
>
<!-- 使用element-ui el-upload自带样式 -->
<ul v-for="(item, index) in curList" :key="index" class="el-upload-list el-upload-list--picture-card">
<li class="el-upload-list__item is-success animated">
<el-image class="originalImg" :src="item.url" :preview-src-list="[item.url]" />
<!-- <el-icon>
<Close />
</el-icon> -->
<label class="el-upload-list__item-status-label">
<el-icon class="el-icon--upload-success el-icon--check">
<Check />
</el-icon>
</label>
<span class="el-upload-list__item-actions">
<!-- 预览 -->
<span class="el-upload-list__item-preview" @click="handleImgPreview('originalImg', index)">
<el-icon>
<ZoomIn />
</el-icon>
</span>
<!-- 删除 -->
<span class="el-upload-list__item-delete" @click="onImageRemove(item.url)">
<el-icon>
<Delete />
</el-icon>
</span>
</span>
</li>
</ul>
<!-- 上传组件 -->
<el-upload
v-model:file-list="curList"
:action="uploadUrl"
:multiple="multiple"
list-type="picture-card"
:accept="accept"
:show-file-list="false"
:before-upload="beforeImgUpload"
:on-success="handleImgSuccess"
:headers="{
Authorization: token
}"
>
<el-icon><Plus /></el-icon>
</el-upload>
</VueDraggable>
</div>
</template>
JS代码
<script name="DraggableImageUpload" setup lang="ts">
import { uploadUrl } from "@/api/utils"; //请求url
import { formatToken, getToken } from "@/utils/auth"; //请求头
import { message } from "@/utils/message";
import { Check, Delete, Download, Plus, ZoomIn } from '@element-plus/icons-vue'
import type { UploadProps } from "element-plus";
import { ElMessage } from "element-plus";
import { ComponentInternalInstance, computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
import { type UseDraggableReturn, VueDraggable } from 'vue-draggable-plus';
const draggableRef = ref<UseDraggableReturn>()
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
//接受上传的文件类型(thumbnail-mode 模式下此参数无效)
accept: {
type: String,
default: ".jpg,.jpeg,.png"
},
//允许上传文件的最大数量
limit: {
type: Number,
default: 9999
},
//是否支持多选文件
multiple: {
type: Boolean,
default: true
},
//是否自动上传文件
autoUpload: {
type: Boolean,
default: false
},
modelValue: {
type: Array,
default: () => []
},
});
const token = ref();
onMounted(() => {
token.value = formatToken(getToken().accessToken);
})
const data = reactive<any>({
maxImgsLen: 0,
imgList: [],
});
const { maxImgsLen, imgList} = toRefs(data)
const emit = defineEmits(["update:modelValue"]);
const curList: any = computed({
get() {
return props.modelValue;
},
set(newValue) {
emit("update:modelValue", newValue);
}
});
//元素开始拖拽
const onStart = () => {
console.log('start')
}
//元素顺序更新时触发
const onUpdate = () => {
console.log(curList.value, 'update++++++++++++++')
}
// 图片预览
const handleImgPreview = (domClass: string, index: number) => {
/* showViewer.value = true
previewList.value = [url] */
const dom = document.getElementsByClassName(domClass);
(dom[index].firstElementChild as any).click(); //调用 el-image 的预览方法
}
// 删除图片
const onImageRemove = (url: string) => {
let list = utils.deepClone(curList.value)
list.forEach((item: any, index: number) => {
if (url === item.url) {
list.splice(index, 1)
}
})
curList.value = list
}
// 上传前校验
const beforeImgUpload: UploadProps["beforeUpload"] = rawFile => {
let types = ["image/jpeg", "image/jpg", "image/png"];
const size = rawFile.size
const isImage = types.includes(rawFile.type);
const isLt5M = size / 1024 / 1024 < 5
if (!isImage) {
message("请上传jpeg、jpg、png类型的图片", {
type: "error"
});
return false;
} else if(!isLt5M) {
message("上传图片大小不能超过5MB", {
type: "error"
});
return false
}
return true;
};
// 上传成功,给图片加前缀,防止切换时图片不显示
function handleImgSuccess() {
curList.value.forEach(item => {
item.url = import.meta.env.VITE_IMG_URL + item.response.data.url;
});
// console.log('curList',curList.value)
}
</script>
style
<style lang="scss" scoped>
.draggable_image_upload {
.box-uploader {
display: flex;
flex-wrap: wrap;
vertical-align: middle;
:deep(.el-upload) {
border-radius: 4px;
.circle-plus {
width: 24px;
height: 24px;
}
&.el-upload-list {
&.el-upload__item {
width: 100px;
height: 100px;
margin: 0 17px 17px 0;
border-color: #e7e7e7;
padding: 3px;
}
}
&.el-upload--picture-card {
width: 100px;
height: 100px;
line-height: 100px;
border-style: dashed;
margin-right: 17px;
}
}
.el-upload-list__item {
width: 100px;
height: 100px;
margin: 0 17px 17px 0;
border-color: #e7e7e7;
padding: 3px;
.originalImg {
:deep(.el-image__preview) {
width: 100%;
height: 100%;
-o-object-fit: contain;
object-fit: contain;
}
}
}
ul:nth-child(6n+6) {
li {
margin-right: 0;
}
}
}
}
</style>
在 xxx.vue 文件中使用
<template>
<el-dialog title="上传商品图片" v-model="infoVisible" :close-on-click-modal="false">
<!-- 图片拖拽组件 -->
<DraggableImageUpload accept=".jpg,.jpeg,.png,.gif" v-model="curList" />
</el-dialog>
</template>
<script name="" setup lang="ts">
import DraggableImageUpload from '@/components/DraggableImageUpload/index.vue'
import { ComponentInternalInstance, getCurrentInstance, toRefs } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const data = reactive<any>({
curList: [], // 需亚预览的数组
infoVisible: false, //预览组件可见性
});
const { infoVisible, curList } = toRefs(data)
</script>
图片预览第二种实现方式
将 li 中 el-image 组件更改为 img标签,引入
el-image-viewer组件,并修改图片预览方法,下面只给出要调整的部分代码