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 通过支持选择器指定目标容器完美解决了这个问题。
安装
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 官方文档
相关文章:
Vue拖拽组件:vue-draggable-plus
vue-draggable-plus 学习文档 简介 vue-draggable-plus 是一个基于 Sortablejs 的 Vue 拖拽排序组件,专为 Vue 3 (>v3) 或 Vue >2.7 设计。该组件解决了官方 Sortablejs Vue 组件与 Vue 3 严重脱节的问题。 核心特性 🎯 多种使用方式ÿ…...

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

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

BiliNote部署实践
开源地址: https://github.com/JefferyHcool/BiliNote 🚀 快速开始 1. 克隆仓库 git clone https://github.com/JefferyHcool/BiliNote.git cd BiliNote mv .env.example .env2. 启动后端(FastAPI) cd backend pip insta…...
deepseek问答记录:请讲解一下transformers.HfArgumentParser()
1. 核心概念: transformers.HfArgumentParser 是 Hugging Face Transformers 库提供的一个命令行参数解析器。它基于 Python 内置的 argparse 模块,但进行了专门增强,目的是为了更简单、更优雅地管理机器学习(尤其是 NLP 任务&am…...

bismark OT CTOT OB CTOB 以及mapping后的bam文件中的XG,XR列的含义
首先,OT,OB,CTOT,CTOB都是描述测序reads的,而不是描述参考基因组的。 bisul-fate建库会将DNA双链文库中非甲基化的C转化成U。转化结束后,被转化的U和互补链的G并不配对。此时正链(,…...
new语法
在C中,new 是用于动态内存分配的操作符,允许在运行时请求内存空间。以下是 new 的完整语法和用法说明: 1. 基本语法 1.1 单一对象分配 type* pointer new type(initializer);作用:分配一个 type 类型的对象,并返回…...
npm、yarn幽灵依赖问题
很好!我们来专门讲讲**幽灵依赖(Phantom Dependency)**是什么,以及为什么 pnpm 对这个问题非常严格。 👻 什么是幽灵依赖? 幽灵依赖(Phantom Dependency),指的是&#x…...

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

CAN通讯协议中各种参数解析
1.各种参数缩写 2.多帧传输时间参数解析 - Sender(左侧) 指的是 多帧数据的发送者,也就是: ECU(被测系统 / 响应方) - Receiver(右侧) 指的是 多帧数据的接收者,也就是…...

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

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

Netty学习example示例
文章目录 simpleServer端NettyServerNettyServerHandler Client端NettyClientNettyClientHandler tcp(粘包和拆包)Server端NettyTcpServerNettyTcpServerHandler Client端NettyTcpClientNettyTcpClientHandler protocolcodecCustomMessageDecoderCustomM…...
几种常用的Agent的Prompt格式
一、基础框架范式(Google推荐标准) 1. 角色与职能定义 <Role_Definition> 你是“项目专家”(Project Pro),作为家居园艺零售商的首席AI助手,专注于家装改造领域。你的核心使命: 1. 协助…...
数据库运维管理系统在AI方向的实践
引言 关系型数据库(如MySQL、PostgreSQL、SQL Server、Oracle等)作为核心数据存储平台,承载着关键业务系统的运行。数据库的运维管理(DBA)工作变得愈发复杂和重要,涉及性能监控、故障诊断、容量规划、安全审计、自动化运维等多个方面。传统的数据库运维依赖人工经验,效…...

[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或以上 下载安装…...
ubuntu系统上运行jar程序输出时间时区不对
springboot项目打包jar文件在ubuntu系统上运行,发现在系统和日志里面,显示和打印的当前时间时区都是UTC0,通过timedatectl命令设置系统时区为Asia/Shanghai,命令date -R发现系统已经修改成功,但是发现springboot仍然输…...
React 播客专栏 Vol.18|React 第二阶段复习 · 样式与 Hooks 全面整合
视频版 🎙 欢迎回到《前端达人 React播客书单》第 18 期。 今天,我们将对第二阶段的内容进行系统复盘,重点是两个关键词:样式 与 Hooks。 样式,决定组件“长什么样”Hooks,决定组件“怎么动起来” 我们不但…...
从认识AI开始-----解密LSTM:RNN的进化之路
前言 我在上一篇文章中介绍了 RNN,它是一个隐变量模型,主要通过隐藏状态连接时间序列,实现了序列信息的记忆与建模。然而,RNN在实践中面临严重的“梯度消失”与“长期依赖建模困难”问题: 难以捕捉相隔很远的时间步之…...

leetcode0513. 找树左下角的值-meidum
1 题目:找树左下角的值 官方标定难度:中 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 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. 背景 多设备协同工作中,因操作系统的不同,我们经常需要将另外一个系统中的文件传输到本地PC进行浏览、编译。多设备文件互传,在嵌入…...
MPTCP 聚合吞吐
只破不立假把式,前面连续喷 MPTCP 是个错误,今天说说如何克服。 到底谁在阻碍 MPTCP 聚合吞吐一定要搞清楚,是算法硬伤,是数据不足。前文说过,将一个窗口内的数据多路径 spray 有损吞吐,想要聚合吞吐&…...
JavaScript性能优化实战技术文章大纲
代码层面优化 避免全局变量污染,使用let和const替代var,减少作用域链查找开销。 // 反例:全局变量 var globalVar 低效;// 正例:局部变量 function optimized() {const localVar 高效; }减少DOM操作,合并多次操作或…...

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

计算机网络 TCP篇常见面试题总结
目录 TCP 的三次握手与四次挥手详解 1. 三次握手(Three-Way Handshake) 2. 四次挥手(Four-Way Handshake) TCP 为什么可靠? 1. 序列号与确认应答(ACK) 2. 超时重传(Retransmis…...

树欲静而风不止,子欲养而亲不待
2025年6月2日,13~26℃,一般 待办: 物理2 、物理 学生重修 职称材料的最后检查 教学技能大赛PPT 遇见:使用通义创作了一副照片,很好看!都有想用来创作自己的头像了! 提示词如下: A b…...

Kotlin中的::操作符详解
Kotlin提供了::操作符,用于创建对类或对象的成员(函数、属性)的引用。这种机制叫做成员引用(Member Reference)。这是Kotlin高阶函数和函数式编程的重要组成部分。 简化函数传递 在Java中,我们这样传方法: list.forEach(item -> System.…...
【Linux】(1)—进程概念-③Linux进程概念与PCB
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、Linux进程概念与PCB 前言 提示:以下是本篇文章正文内容,下面案例可供参考 什么是进程? 进程可以理解为"正在执行的…...
神经网络中的梯度消失与梯度爆炸
在深层次的神经网络中很容易出现梯度消失与梯度爆炸的问题。这篇博客就详细介绍一下为什么会产生梯度消失与梯度爆炸的问题,以及如何解决。 首先梯度是什么 类比快递员送包裹: 神经网络训练时,需要根据预测错误(损失函数&#…...