Vue拖拽组件:vue-draggable-plus

news2025/6/5 4:09:09

vue-draggable-plus 学习文档

简介

vue-draggable-plus 是一个基于 Sortablejs 的 Vue 拖拽排序组件,专为 Vue 3 (>=v3) 或 Vue >=2.7 设计。该组件解决了官方 Sortablejs Vue 组件与 Vue 3 严重脱节的问题。

核心特性

  • 🎯 多种使用方式:支持组件、函数、指令三种使用方式
  • 🔧 灵活的容器选择:可以指定任意元素作为拖拽容器
  • 📦 完美兼容:基于成熟的 Sortablejs 库
  • 🎨 组件库友好:解决组件库中列表根元素插槽缺失的问题

解决的痛点

在传统的 Sortablejs Vue 组件中,必须使用组件作为列表的直接子元素。当使用第三方组件库时,如果组件库没有提供列表根元素的插槽,就很难实现拖拽功能。vue-draggable-plus 通过支持选择器指定目标容器完美解决了这个问题。

安装

npm install vue-draggable-plus

使用方式

1. 组件方式

最直观的使用方式,适合简单的拖拽列表场景。

<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. 函数方式

提供更多控制权和灵活性,适合复杂的拖拽需求。

<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. 指令方式

最简洁的使用方式,适合快速集成。

<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>

特点:

  • 代码最简洁
  • 直接在模板中配置
  • 适合快速原型开发

常用配置选项

基础配置

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'
}

事件回调

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. 双列表拖拽

<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. 指定拖拽手柄

<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. 克隆模式

<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. 性能优化

<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. 数据持久化

<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. 错误处理

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

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

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

参考资源

  • vue-draggable-plus 官方文档
  • Sortablejs 官方文档
  • Vue 3 官方文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2396861.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TDengine 基于 TDgpt 的 AI 应用实战

基于 TDgpt 时序数据智能体的风力发电预测 作者&#xff1a; derekchen Demo 数据集准备 我们使用公开的UTSD数据集里面的某风场发电数据&#xff0c;作为预测算法的数据来源&#xff0c;基于历史数据预测未来一天内的每15分钟的发电量。原始数据集的采集频次为4秒&#xff…

RocketMQ 消息发送核心源码解析:DefaultMQProducerImpl.send () 方法深度剖析

引言 在分布式系统中&#xff0c;消息队列是实现异步通信、服务解耦和流量削峰的关键组件。Apache RocketMQ 作为一款高性能、高可靠的消息中间件&#xff0c;被广泛应用于各类互联网场景。其中&#xff0c;消息发送是最基础也是最重要的功能之一。本文将深入剖析 RocketMQ 中…

BiliNote部署实践

​ 开源地址&#xff1a; https://github.com/JefferyHcool/BiliNote &#x1f680; 快速开始 1. 克隆仓库 git clone https://github.com/JefferyHcool/BiliNote.git cd BiliNote mv .env.example .env2. 启动后端&#xff08;FastAPI&#xff09; cd backend pip insta…

bismark OT CTOT OB CTOB 以及mapping后的bam文件中的XG,XR列的含义

首先&#xff0c;OT&#xff0c;OB&#xff0c;CTOT&#xff0c;CTOB都是描述测序reads的&#xff0c;而不是描述参考基因组的。 bisul-fate建库会将DNA双链文库中非甲基化的C转化成U。转化结束后&#xff0c;被转化的U和互补链的G并不配对。此时正链&#xff08;&#xff0c;…

Android Native 之 adbd进程分析

目录 1、adbd守护进程 2、adbd权限降级 3、adbd命令解析 1&#xff09;adb shell 2&#xff09;adb root 3&#xff09;adb reboot 4、案例 1&#xff09;案例之实现不需要执行adb root命令自动具有root权限 2&#xff09;案例之实现不需要RSA认证直接能够使用adb she…

CAN通讯协议中各种参数解析

1.各种参数缩写 2.多帧传输时间参数解析 - Sender&#xff08;左侧&#xff09; 指的是 多帧数据的发送者&#xff0c;也就是&#xff1a; ECU&#xff08;被测系统 / 响应方&#xff09; - Receiver&#xff08;右侧&#xff09; 指的是 多帧数据的接收者&#xff0c;也就是…

网络攻防技术三:网络脆弱性分析

文章目录 一、影响安全的因素二、计算机网络三、网络体系结构脆弱性1、因特网容易被攻击的特性 四、典型网络协议安全性分析&#xff08;重要&#xff09;1、IPv42、RIP&#xff08;UDP)3、ICMP(UDP)4、ARP5、OSPF(IP数据报&#xff09;6、BGP(TCP)7、UDP8、TCP9、DNS(UDP)10、…

(八)登录认证与学生写作画像

本次将赵昱琨同学之前完成的学生写作画像与智能学习路径规划的后端与目前已有的后端框架进行整合。同时为了实现学生写作画像与智能学习路径规划&#xff0c;需要在之前简易的登录系统上进行重构&#xff0c;所以本次大规模重写了登录模块&#xff0c;同时发现很多过去冗余的代…

Netty学习example示例

文章目录 simpleServer端NettyServerNettyServerHandler Client端NettyClientNettyClientHandler tcp&#xff08;粘包和拆包&#xff09;Server端NettyTcpServerNettyTcpServerHandler Client端NettyTcpClientNettyTcpClientHandler protocolcodecCustomMessageDecoderCustomM…

[RoarCTF 2019]Easy Calc

查看源代码 <!--Ive set up WAF to ensure security.--> <script>$(#calc).submit(function(){$.ajax({url:"calc.php?num"encodeURIComponent($("#content").val()),type:GET,success:function(data){$("#result").html(<div …

[Windows]在Win上安装bash和zsh - 一个脚本搞定

目录 前言安装步骤配置要求下载安装脚本启动程序 前言 Windows是一个很流行的系统, 但是在Windows上安装bash和zsh一直是一个让人头疼的问题. 本蛙特意打包了一个程序, 用于一站式解决这一类的问题. 安装步骤 配置要求 系统: Windows软件: Powershell 5.1或以上 下载安装…

从认识AI开始-----解密LSTM:RNN的进化之路

前言 我在上一篇文章中介绍了 RNN&#xff0c;它是一个隐变量模型&#xff0c;主要通过隐藏状态连接时间序列&#xff0c;实现了序列信息的记忆与建模。然而&#xff0c;RNN在实践中面临严重的“梯度消失”与“长期依赖建模困难”问题&#xff1a; 难以捕捉相隔很远的时间步之…

leetcode0513. 找树左下角的值-meidum

1 题目&#xff1a;找树左下角的值 官方标定难度&#xff1a;中 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1 示例 2: 输入: [1,2,3,4,null,5,6,null,null,7]…

命令行式本地与服务器互传文件

文章目录 1. 背景2. 传输方式2.1 SCP 协议传输2.2 SFTP 协议传输 3. 注意 命令行式本地与服务器互传文件 1. 背景 多设备协同工作中&#xff0c;因操作系统的不同&#xff0c;我们经常需要将另外一个系统中的文件传输到本地PC进行浏览、编译。多设备文件互传&#xff0c;在嵌入…

LabelImg: 开源图像标注工具指南

LabelImg: 开源图像标注工具指南 1. 简介 LabelImg 是一个图形化的图像标注工具&#xff0c;使用 Python 和 Qt 开发。它是目标检测任务中最常用的标注工具之一&#xff0c;支持 PASCAL VOC 和 YOLO 格式的标注输出。该工具开源、免费&#xff0c;并且跨平台支持 Windows、Lin…

计算机网络 TCP篇常见面试题总结

目录 TCP 的三次握手与四次挥手详解 1. 三次握手&#xff08;Three-Way Handshake&#xff09; 2. 四次挥手&#xff08;Four-Way Handshake&#xff09; TCP 为什么可靠&#xff1f; 1. 序列号与确认应答&#xff08;ACK&#xff09; 2. 超时重传&#xff08;Retransmis…

树欲静而风不止,子欲养而亲不待

2025年6月2日&#xff0c;13~26℃&#xff0c;一般 待办&#xff1a; 物理2 、物理 学生重修 职称材料的最后检查 教学技能大赛PPT 遇见&#xff1a;使用通义创作了一副照片&#xff0c;很好看&#xff01;都有想用来创作自己的头像了&#xff01; 提示词如下&#xff1a; A b…

Kotlin中的::操作符详解

Kotlin提供了::操作符&#xff0c;用于创建对类或对象的成员(函数、属性)的引用。这种机制叫做成员引用(Member Reference)。这是Kotlin高阶函数和函数式编程的重要组成部分。 简化函数传递 在Java中&#xff0c;我们这样传方法&#xff1a; list.forEach(item -> System.…

深入详解编译与链接:翻译环境和运行环境,翻译环境:预编译+编译+汇编+链接,运行环境

目录 一、翻译环境和运行环境 二、翻译环境&#xff1a;预编译编译汇编链接 &#xff08;一&#xff09;预处理&#xff08;预编译&#xff09; &#xff08;二&#xff09;编译 1、词法分析 2、语法分析 3、语义分析 &#xff08;三&#xff09;汇编 &#xff08;四&…

定时任务:springboot集成xxl-job-core(二)

定时任务实现方式&#xff1a; 存在的问题&#xff1a; xxl-job的原理&#xff1a; 可以根据服务器的个数进行动态分片&#xff0c;每台服务器分到的处理数据是不一样的。 1. 多台机器动态注册 多台机器同时配置了调度器xxl-job-admin之后&#xff0c;执行器那里会有多个注…