Vue拖拽组件:vue-draggable-plus

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 是一个功能强大且灵活的拖拽组件,提供了三种使用方式来适应不同的开发需求:

  • 组件方式:适合简单场景,代码清晰
  • 函数方式:适合复杂场景,控制力强
  • 指令方式:适合快速集成,代码简洁

选择合适的使用方式,结合正确的配置和最佳实践,可以轻松实现各种拖拽排序需求。

参考资源

相关推荐
钢铁男儿6 分钟前
C# 类和继承(使用基类的引用)
java·javascript·c#
czliutz13 分钟前
NiceGUI 是一个基于 Python 的现代 Web 应用框架
开发语言·前端·python
koooo~2 小时前
【无标题】
前端
Attacking-Coder2 小时前
前端面试宝典---前端水印
前端
姑苏洛言5 小时前
基于微信公众号小程序的课表管理平台设计与实现
前端·后端
烛阴5 小时前
比UUID更快更小更强大!NanoID唯一ID生成神器全解析
前端·javascript·后端
Alice_hhu5 小时前
ResizeObserver 解决 echarts渲染不出来,内容宽度为 0的问题
前端·javascript·echarts
charlee446 小时前
解决Vditor加载Markdown网页很慢的问题(Vite+JS+Vditor)
javascript·markdown·cdn·vditor
逃逸线LOF6 小时前
CSS之动画(奔跑的熊、两面反转盒子、3D导航栏、旋转木马)
前端·css
老马啸西风7 小时前
工作流引擎-18-开源审批流项目之 plumdo-work 工作流,表单,报表结合的多模块系统
vue.js·开源·activiti·workflow·flowable·oa·bpm