大家好!今天给大家分享的是vue-draggable-plus, vue-draggable-plus 是基于Sortablejs 和Vue 封装的一个具备丰富的拖拽功能的库,它支持Vue3,Vue2(>=2.7),它具备以下优点:
下面来看看vue-draggable-plus 的使用案例。
一、可拖拽照片墙
上图可以看到,vue-draggable-plus 支持组件、函数、指令三种使用方式,下面我们就用三种使用方式来实现可拖拽照片墙
1. 组件使用方式
不管是组件形式还是函数和指令方式实现,第一步肯定是先安装vue-draggable-plus
pnpm i vue-draggable-plus
<template>
<div class="flex">
<VueDraggable
ref="el"
v-model="list"
:animation="150"
ghostClass="ghost"
class="list"
@start="onStart"
@update="onUpdate"
@end="onEnd"
>
<div v-for="item in list" :key="item.id" class="item">
<img :src="getImgUrl(item.src)" />
</div>
</VueDraggable>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import {
type DraggableEvent,
type UseDraggableReturn,
VueDraggable,
} from "vue-draggable-plus";
const list = ref([
{
src: "1.webp",
id: 1,
},
{
src: "2.webp",
id: 2,
},
{
src: "3.webp",
id: 3,
},
{
src: "4.webp",
id: 4,
},
{
src: "5.webp",
id: 5,
},
{
src: "6.webp",
id: 6,
},
{
src: "7.webp",
id: 7,
},
{
src: "8.webp",
id: 8,
},
]);
const getImgUrl = (url: string) => {
return new URL(`../assets/images/${url}`, import.meta.url).href;
};
const el = ref<UseDraggableReturn>();
const onStart = (e: DraggableEvent) => {
console.log("start", e.data);
console.log("start", list.value);
};
const onEnd = (e: DraggableEvent) => {
console.log("onEnd", e);
console.log("onEnd", list.value);
};
const onUpdate = (e: any) => {
console.log("update", e);
};
</script>
<style lang="scss" scoped>
.list {
display: flex;
flex-wrap: wrap;
width: 900px;
margin: 100px auto;
.item {
width: 200px;
height: 200px;
margin: 0 0 20px 20px;
cursor: move;
img {
width: 100%;
height: 100%;
}
}
}
</style>
效果演示:
代码解析:
- 首先导入vue-draggable-plus
- 使用 VueDraggable 组件将需要拖拽的列表项进行包裹
- v-model 指向列表数据
完成上面三步,拖拽功能其实已经能实现。
方法属性解析:
- @start 开始拖动时触发的事件
- @update 更新是触发的事件
- @end 拖拽结束时触发的事件
- animation 动画,number 类型,默认值0(没有动画)
在三个事件中都可以通过事件参数的data 属性获取到被拖动的数据
在 @update和@end 中可看到列表数据顺序的变化,这样就可以将变化后的数据保存到后台
拖拽前:
拖拽后:
2. 函数的使用形式
<template>
<div class="list" ref="el">
<div v-for="item in list" :key="item.id" class="item">
<img :src="getImgUrl(item.src)" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useDraggable, type DraggableEvent } from "vue-draggable-plus";
const list = ref([
{
src: "1.webp",
id: 1,
},
{
src: "2.webp",
id: 2,
},
{
src: "3.webp",
id: 3,
},
{
src: "4.webp",
id: 4,
},
{
src: "5.webp",
id: 5,
},
{
src: "6.webp",
id: 6,
},
{
src: "7.webp",
id: 7,
},
{
src: "8.webp",
id: 8,
},
]);
const getImgUrl = (url: string) => {
return new URL(`../assets/images/${url}`, import.meta.url).href;
};
const el = ref();
const draggable = useDraggable(el, list, {
animation: 150,
onStart(e: DraggableEvent) {
console.log("start", e);
console.log("start", list.value);
},
onUpdate(e: any) {
console.log("update", e);
console.log("update", list.value);
},
onEnd(e: DraggableEvent) {
console.log("onEnd", e);
console.log("onEnd", list.value);
},
});
console.log(draggable);
</script>
<style lang="scss" scoped>
.list {
display: flex;
flex-wrap: wrap;
width: 900px;
margin: 100px auto;
.item {
width: 200px;
height: 200px;
margin: 0 0 20px 20px;
cursor: move;
img {
width: 100%;
height: 100%;
}
}
}
</style>
代码解析:
- 从vue-draggable-plus 中导入useDraggable 方法
- useDraggable 接收三个参数,第一参数拖拽列表的容器元素,第二参数个是列表的数据,第三个参数是配置项,相当于之前在组件上配置的方法和属性, 所以这里的animation、onStart、onUpdate、onEnd就不解析了,相信看了之前组件形式的这里都能看明白
3、指令的使用形式
<template>
<div
class="list"
v-draggable="[
list,
{
animation: 150,
onUpdate,
onStart,
onEnd,
},
]"
>
<div v-for="item in list" :key="item.id" class="item">
<img :src="getImgUrl(item.src)" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { vDraggable, type DraggableEvent } from "vue-draggable-plus";
const list = ref([
{
src: "1.webp",
id: 1,
},
{
src: "2.webp",
id: 2,
},
{
src: "3.webp",
id: 3,
},
{
src: "4.webp",
id: 4,
},
{
src: "5.webp",
id: 5,
},
{
src: "6.webp",
id: 6,
},
{
src: "7.webp",
id: 7,
},
{
src: "8.webp",
id: 8,
},
]);
const onStart = (e: DraggableEvent) => {
console.log("start", e);
console.log("start", list.value);
};
const onEnd = (e: DraggableEvent) => {
console.log("onEnd", e);
console.log("onEnd", list.value);
};
const onUpdate = (e: any) => {
console.log("update", e);
console.log("update", list.value);
};
const getImgUrl = (url: string) => {
return new URL(`../assets/images/${url}`, import.meta.url).href;
};
</script>
<style lang="scss" scoped>
.list {
display: flex;
flex-wrap: wrap;
width: 900px;
margin: 100px auto;
.item {
width: 200px;
height: 200px;
margin: 0 0 20px 20px;
cursor: move;
img {
width: 100%;
height: 100%;
}
}
}
</style>
代码解析:
- 从vue-draggable-plus 中导入vDraggable
- 在列表容器标签上使用指令v-draggable
- v-draggable 接收一个数组,数组的第一项是列表的数据,数组的第二项是一个配置项,相当于之前函数形式的第三个参数。
二、拖拽进行分类
拖拽在进行分类时也是非常常用的一个功能,下面就以一个水果分类和蔬菜分类来例子来看vue-draggable-plus 在这种场景中该如何使用
<template>
<div class="flex">
<div>
<p class="title">蔬菜</p>
<VueDraggable
class="list list1"
v-model="list1"
animation="150"
group="category"
@update="onUpdate"
@add="onAdd"
@remove="remove"
>
<div v-for="item in list1" :key="item.id" class="item">
{{ item.name }}
</div>
</VueDraggable>
</div>
<div>
<p class="title">水果</p>
<VueDraggable
class="list list2"
v-model="list2"
animation="150"
group="category"
@update="onUpdate"
@add="onAdd"
@remove="remove"
>
<div v-for="item in list2" :key="item.id" class="item">
{{ item.name }}
</div>
</VueDraggable>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { VueDraggable } from "vue-draggable-plus";
const list1 = ref([
{
name: "苹果",
id: "1",
},
{
name: "香蕉",
id: "2",
},
{
name: "荔枝",
id: "3",
},
{
name: "西红柿",
id: "4",
},
]);
const list2 = ref([
{
name: "黄瓜",
id: "5",
},
{
name: "茄子",
id: "6",
},
{
name: "地萝卜",
id: "7",
},
{
name: "芒果",
id: "8",
},
]);
function onUpdate() {
console.log("update");
}
function onAdd() {
console.log("add");
}
function remove() {
console.log("remove");
}
</script>
<style lang="scss" scoped>
.flex {
display: flex;
}
.title {
font-size: 24px;
text-align: center;
}
.list {
&.list2 {
margin-left: 30px;
}
.item {
width: 100px;
height: 60px;
border-radius: 5px;
background: #57bd6c;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10px;
cursor: move;
}
}
</style>
效果演示:
代码解析:
- 分类需要用到多个列表,那么多个列表之间是怎么关联的呢,主要通过group 属性配置,group 相同的列表之间可以互相拖拽。
- @update 数据更新时触发,在同一个列表时才触发
- @add 元素从一个列表拖拽到另一个列表时触发
- @remove 元素从列表中移除进入另一个列表
在上面三个方法中同样可以在触发时获取到当时的数据
三、表格拖拽
1. 行拖拽
在项目开发中经常会有表格拖拽排序的需求,下面就来实现一个商品表格拖拽排序的例子:
<template>
<VueDraggable v-model="list" target=".sort-target" :animation="150">
<table class="table-style" cellpadding="10" border="1">
<thead>
<tr>
<th>商品Id</th>
<th>商品名名称</th>
<th>分类</th>
<th>品牌</th>
<th>原价</th>
<th>折扣价</th>
<th>库存</th>
</tr>
</thead>
<tbody class="sort-target">
<tr v-for="item in list" :key="item.name" class="cursor-move">
<td>{{ item.productId }}</td>
<td>{{ item.name }}</td>
<td>{{ item.category }}</td>
<td>{{ item.brand }}</td>
<td>{{ item.price }}</td>
<td>{{ item.discountPrice }}</td>
<td>{{ item.stock }}</td>
</tr>
</tbody>
</table>
</VueDraggable>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { VueDraggable } from "vue-draggable-plus";
const list = ref([
{
productId: "10001",
name: "无线蓝牙耳机",
category: "电子产品",
brand: "SoundMaster",
price: 199.99,
discountPrice: 159.99,
stock: 150,
},
{
productId: "10002",
name: "不锈钢保温杯",
category: "家居用品",
brand: "Aqua",
price: 29.99,
stock: 500,
},
{
productId: "10003",
name: "男士运动鞋",
category: "服装鞋帽",
brand: "RunFast",
price: 89.99,
discountPrice: 69.99,
stock: 75,
},
{
productId: "10004",
name: "智能手环",
category: "电子产品",
brand: "FitTech",
price: 129.99,
stock: 200,
},
{
productId: "10005",
name: "有机绿茶250g",
category: "食品饮料",
brand: "GreenNature",
price: 15.99,
stock: 300,
},
{
productId: "10006",
name: "便携折叠桌",
category: "家居用品",
brand: "HomePlus",
price: 45.5,
stock: 120,
},
{
productId: "10007",
name: "儿童绘画套装",
category: "文具玩具",
brand: "ArtFun",
price: 32.99,
discountPrice: 25.99,
stock: 180,
},
{
productId: "10008",
name: "女士真丝围巾",
category: "服饰配件",
brand: "SilkElegance",
price: 59.99,
stock: 90,
},
{
productId: "10009",
name: "无线充电器",
category: "电子产品",
brand: "PowerUp",
price: 39.99,
stock: 250,
},
{
productId: "10010",
name: "瑜伽垫",
category: "运动户外",
brand: "YogaLife",
price: 34.99,
discountPrice: 28.99,
stock: 160,
},
]);
</script>
<style lang="scss" scoped>
.table-style {
border-collapse: collapse;
width: 100%;
tbody tr {
cursor: move;
}
td {
text-align: center;
}
}
</style>
效果演示:
代码解析,其实最重要的就两点:
- v-model 绑定数据
- target 指定拖拽列表的容器
2.列拖拽
有时候我们想看一些列的对比,如果两列的数据太远对比起来就比较麻烦,就比如上述的列表,如果我们当前最想关注的是商品对应还有多少库存,而其中还隔着3列,观察起来就非常麻烦,这时候列拖拽就非常有用了,下面来看看列拖拽的实现:
<template>
<VueDraggable v-model="headers" target=".sort-target" :animation="150">
<table class="table-style" cellpadding="10" border="1">
<thead>
<tr class="sort-target">
<th v-for="item in headers" :key="item.value">{{ item.text }}</th>
</tr>
</thead>
<tbody>
<tr v-for="item in list" :key="item.productId" class="cursor-move">
<td v-for="child in headers" :key="child.value">
{{ item[child.value] }}
</td>
</tr>
</tbody>
</table>
</VueDraggable>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { VueDraggable } from "vue-draggable-plus";
type TypeItem = {
productId: string;
name: string;
category: string;
brand: string;
price: number;
discountPrice?: number;
stock: number;
};
type TypeHeader = {
text: string;
value: keyof TypeItem;
};
const headers = ref<TypeHeader[]>([
{
text: "商品ID",
value: "productId",
},
{
text: "商品名称",
value: "name",
},
{
text: "分类",
value: "category",
},
{
text: "品牌",
value: "brand",
},
{
text: "价格",
value: "price",
},
{
text: "折扣价",
value: "discountPrice",
},
{
text: "库存",
value: "stock",
},
]);
const list = ref<TypeItem[]>([
{
productId: "10001",
name: "无线蓝牙耳机",
category: "电子产品",
brand: "SoundMaster",
price: 199.99,
discountPrice: 159.99,
stock: 150,
},
{
productId: "10002",
name: "不锈钢保温杯",
category: "家居用品",
brand: "Aqua",
price: 29.99,
stock: 500,
},
{
productId: "10003",
name: "男士运动鞋",
category: "服装鞋帽",
brand: "RunFast",
price: 89.99,
discountPrice: 69.99,
stock: 75,
},
{
productId: "10004",
name: "智能手环",
category: "电子产品",
brand: "FitTech",
price: 129.99,
stock: 200,
},
{
productId: "10005",
name: "有机绿茶250g",
category: "食品饮料",
brand: "GreenNature",
price: 15.99,
stock: 300,
},
{
productId: "10006",
name: "便携折叠桌",
category: "家居用品",
brand: "HomePlus",
price: 45.5,
stock: 120,
},
{
productId: "10007",
name: "儿童绘画套装",
category: "文具玩具",
brand: "ArtFun",
price: 32.99,
discountPrice: 25.99,
stock: 180,
},
{
productId: "10008",
name: "女士真丝围巾",
category: "服饰配件",
brand: "SilkElegance",
price: 59.99,
stock: 90,
},
{
productId: "10009",
name: "无线充电器",
category: "电子产品",
brand: "PowerUp",
price: 39.99,
stock: 250,
},
{
productId: "10010",
name: "瑜伽垫",
category: "运动户外",
brand: "YogaLife",
price: 34.99,
discountPrice: 28.99,
stock: 160,
},
]);
</script>
<style lang="scss" scoped>
.table-style {
border-collapse: collapse;
width: 100%;
thead td {
cursor: move;
}
td {
text-align: center;
}
}
</style>
运行效果:
代码解析:
- 相对于之前的行拖拽,target从tbody变成了thead下面的tr
- 新增了headers数据变量,模板渲染时使用headers 进行v-for渲染
- v-model 绑定数据绑定成了新增的变量headers
- tbody里面的列表渲染改成了双层循环,item[child.value] 读取数据, 这样做的目的是使得tbody里面的数据和头部产生关联,这样拖动头部才能使tbody里面的数据一起变化。
四、总结
本篇通过三个案例: 可拖拽的照片墙、拖拽分类、拖拽表格,学习了vue-draggable-plus的用法,体验了vue-draggable-plus 遍历性, vue-draggable-plus 还支持三种方式的使用:组件、函数、指令。可以根据自己编码风格选择合适的方式。更多的使用场景和细节可以查看官方文档:vue-draggable-plus.pages.dev/guide/
本篇已收录到Vue 知识储备 专栏,欢迎关注后续更新