vue-draggable-plus 学习文档
简介
vue-draggable-plus 是一个基于 Sortablejs 的 Vue 拖拽排序组件,专为 Vue 3 (>=v3) 或 Vue >=2.7 设计。该组件解决了官方 Sortablejs Vue 组件与 Vue 3 严重脱节的问题。
核心特性
- 🎯 多种使用方式:支持组件、函数、指令三种使用方式
- 🔧 灵活的容器选择:可以指定任意元素作为拖拽容器
- 📦 完美兼容:基于成熟的 Sortablejs 库
- 🎨 组件库友好:解决组件库中列表根元素插槽缺失的问题
解决的痛点
在传统的 Sortablejs Vue 组件中,必须使用组件作为列表的直接子元素。当使用第三方组件库时,如果组件库没有提供列表根元素的插槽,就很难实现拖拽功能。vue-draggable-plus 通过支持选择器指定目标容器完美解决了这个问题。
安装
bash
npm install vue-draggable-plus
使用方式
1. 组件方式
最直观的使用方式,适合简单的拖拽列表场景。
vue
<template>
<VueDraggable ref="el" v-model="list">
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</VueDraggable>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { VueDraggable } from 'vue-draggable-plus'
const list = ref([
{ name: 'Joao', id: 1 },
{ name: 'Jean', id: 2 },
{ name: 'Johanna', id: 3 },
{ name: 'Juan', id: 4 }
])
</script>
特点:
- 使用
v-model
双向绑定数据 - 组件自动处理拖拽逻辑
- 适合简单场景
2. 函数方式
提供更多控制权和灵活性,适合复杂的拖拽需求。
vue
<template>
<div ref="el">
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDraggable } from 'vue-draggable-plus'
const el = ref()
const list = ref([
{ name: 'Joao', id: 1 },
{ name: 'Jean', id: 2 },
{ name: 'Johanna', id: 3 },
{ name: 'Juan', id: 4 }
])
// 返回拖拽实例,包含 start、destroy、pause 等方法
const draggable = useDraggable(el, list, {
animation: 150,
onStart() {
console.log('拖拽开始')
},
onUpdate() {
console.log('列表更新')
},
onEnd() {
console.log('拖拽结束')
}
})
</script>
特点:
- 返回拖拽实例对象
- 可以手动控制拖拽行为
- 支持丰富的事件回调
- 适合复杂交互场景
3. 指令方式
最简洁的使用方式,适合快速集成。
vue
<template>
<div v-draggable="[list, options]">
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { vDraggable } from 'vue-draggable-plus'
const list = ref([
{ name: 'Joao', id: 1 },
{ name: 'Jean', id: 2 },
{ name: 'Johanna', id: 3 },
{ name: 'Juan', id: 4 }
])
const options = {
animation: 150,
onStart() {
console.log('拖拽开始')
},
onUpdate() {
console.log('列表更新')
}
}
</script>
特点:
- 代码最简洁
- 直接在模板中配置
- 适合快速原型开发
常用配置选项
基础配置
javascript
const options = {
// 动画时长(毫秒)
animation: 150,
// 拖拽时的 CSS 类名
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag',
// 是否禁用拖拽
disabled: false,
// 拖拽手柄选择器
handle: '.drag-handle',
// 过滤不可拖拽的元素
filter: '.no-drag',
// 拖拽方向限制
direction: 'vertical', // 'vertical' | 'horizontal'
}
事件回调
javascript
const options = {
// 拖拽开始
onStart(evt) {
console.log('开始拖拽', evt)
},
// 拖拽结束
onEnd(evt) {
console.log('拖拽结束', evt)
},
// 元素位置改变
onUpdate(evt) {
console.log('位置更新', evt)
},
// 元素添加到列表
onAdd(evt) {
console.log('添加元素', evt)
},
// 元素从列表移除
onRemove(evt) {
console.log('移除元素', evt)
},
// 拖拽过程中
onMove(evt) {
console.log('移动中', evt)
// 返回 false 可以阻止移动
return true
}
}
高级用法
1. 双列表拖拽
vue
<template>
<div class="container">
<div class="list-container">
<h3>列表 A</h3>
<VueDraggable
v-model="listA"
group="shared"
class="drag-area"
>
<div v-for="item in listA" :key="item.id" class="drag-item">
{{ item.name }}
</div>
</VueDraggable>
</div>
<div class="list-container">
<h3>列表 B</h3>
<VueDraggable
v-model="listB"
group="shared"
class="drag-area"
>
<div v-for="item in listB" :key="item.id" class="drag-item">
{{ item.name }}
</div>
</VueDraggable>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { VueDraggable } from 'vue-draggable-plus'
const listA = ref([
{ name: '项目 A1', id: 1 },
{ name: '项目 A2', id: 2 }
])
const listB = ref([
{ name: '项目 B1', id: 3 },
{ name: '项目 B2', id: 4 }
])
</script>
<style>
.container {
display: flex;
gap: 20px;
}
.list-container {
flex: 1;
}
.drag-area {
min-height: 200px;
border: 2px dashed #ccc;
padding: 10px;
}
.drag-item {
padding: 8px;
margin: 4px 0;
background: #f0f0f0;
border-radius: 4px;
cursor: move;
}
</style>
2. 指定拖拽手柄
vue
<template>
<VueDraggable v-model="list" handle=".drag-handle">
<div v-for="item in list" :key="item.id" class="item">
<span class="drag-handle">⋮⋮</span>
<span>{{ item.name }}</span>
<button @click="deleteItem(item.id)">删除</button>
</div>
</VueDraggable>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { VueDraggable } from 'vue-draggable-plus'
const list = ref([
{ name: '项目 1', id: 1 },
{ name: '项目 2', id: 2 },
{ name: '项目 3', id: 3 }
])
const deleteItem = (id: number) => {
const index = list.value.findIndex(item => item.id === id)
if (index > -1) {
list.value.splice(index, 1)
}
}
</script>
<style>
.item {
display: flex;
align-items: center;
padding: 8px;
border: 1px solid #ddd;
margin: 4px 0;
}
.drag-handle {
cursor: move;
margin-right: 8px;
color: #999;
}
</style>
3. 克隆模式
vue
<template>
<div class="clone-demo">
<div class="source">
<h3>源列表(可克隆)</h3>
<VueDraggable
v-model="sourceList"
:group="{ name: 'clone', pull: 'clone', put: false }"
:sort="false"
>
<div v-for="item in sourceList" :key="item.id" class="item">
{{ item.name }}
</div>
</VueDraggable>
</div>
<div class="target">
<h3>目标列表</h3>
<VueDraggable
v-model="targetList"
group="clone"
>
<div v-for="item in targetList" :key="item.id" class="item">
{{ item.name }}
</div>
</VueDraggable>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { VueDraggable } from 'vue-draggable-plus'
const sourceList = ref([
{ name: '模板 1', id: 1 },
{ name: '模板 2', id: 2 },
{ name: '模板 3', id: 3 }
])
const targetList = ref([])
</script>
最佳实践
1. 性能优化
vue
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
import { VueDraggable } from 'vue-draggable-plus'
// 对于大列表,使用 shallowRef 提高性能
const largeList = shallowRef([...Array(1000)].map((_, i) => ({
id: i,
name: `项目 ${i}`
})))
// 使用防抖处理频繁的拖拽事件
import { debounce } from 'lodash-es'
const handleUpdate = debounce(() => {
console.log('列表更新')
// 执行保存等操作
}, 300)
</script>
2. 数据持久化
vue
<script setup lang="ts">
import { ref, watch } from 'vue'
import { VueDraggable } from 'vue-draggable-plus'
const list = ref([])
// 监听列表变化,自动保存
watch(list, (newList) => {
localStorage.setItem('dragList', JSON.stringify(newList))
}, { deep: true })
// 页面加载时恢复数据
const loadData = () => {
const saved = localStorage.getItem('dragList')
if (saved) {
list.value = JSON.parse(saved)
}
}
loadData()
</script>
3. 错误处理
vue
<script setup lang="ts">
import { ref } from 'vue'
import { useDraggable } from 'vue-draggable-plus'
const el = ref()
const list = ref([])
const draggable = useDraggable(el, list, {
onMove(evt) {
// 验证移动是否合法
const { dragged, related } = evt
// 例如:某些项目不能移动到特定位置
if (dragged.dataset.type === 'locked') {
return false // 阻止移动
}
return true
},
onEnd(evt) {
// 拖拽结束后验证数据完整性
try {
validateListIntegrity(list.value)
} catch (error) {
console.error('数据验证失败:', error)
// 恢复到之前的状态
restorePreviousState()
}
}
})
const validateListIntegrity = (list) => {
// 实现数据验证逻辑
}
const restorePreviousState = () => {
// 实现状态恢复逻辑
}
</script>
常见问题
1. 拖拽不生效
- 检查是否正确导入组件
- 确认数据格式是否正确(需要数组)
- 检查 CSS 样式是否影响拖拽
2. 数据不同步
- 确保使用
v-model
或正确的数据绑定 - 检查是否有其他代码修改了数据
3. 性能问题
- 对于大列表使用
shallowRef
- 避免在拖拽事件中执行重计算
- 使用防抖处理频繁事件
总结
vue-draggable-plus 是一个功能强大且灵活的拖拽组件,提供了三种使用方式来适应不同的开发需求:
- 组件方式:适合简单场景,代码清晰
- 函数方式:适合复杂场景,控制力强
- 指令方式:适合快速集成,代码简洁
选择合适的使用方式,结合正确的配置和最佳实践,可以轻松实现各种拖拽排序需求。