Vue项目中实现拖拽排序效果-demo

news/2025/2/11 1:57:15/

在Vue3中实现拖拽排序,可以借助一些浏览器自带的API,以及一些Vue3的特性:

使用<template>标签中的v-for指令渲染出一个列表,每个列表项绑定一个draggable属性,使其能够被拖拽。

<template><ul><li v-for="(item, index) in list" :key="item.id" :draggable="true" @dragstart="dragStart(index)">{{ item.title }}</li></ul>
</template>

<script>标签中,定义一个list数组,用于存储待排序的数据。同时,定义一个dragIndex变量,记录当前正在拖拽的元素的索引位置。

<script>
import { reactive } from 'vue';export default {setup() {const state = reactive({list: [{ id: 1, title: 'Item 1' },{ id: 2, title: 'Item 2' },{ id: 3, title: 'Item 3' },{ id: 4, title: 'Item 4' },{ id: 5, title: 'Item 5' }],dragIndex: null});const dragStart = (index) => {state.dragIndex = index;}return { state, dragStart };}
}
</script>

使用@dragenter@dragover事件处理函数,阻止默认行为,避免无法放置拖拽元素。

<template><ul><li v-for="(item, index) in state.list" :key="item.id" :draggable="true" @dragstart="dragStart(index)" @dragenter.prevent @dragover.prevent>{{ item.title }}</li></ul>
</template>

@dragenter事件处理函数中,获取当前拖拽元素的索引位置,以及目标元素的索引位置。根据两个索引位置的大小关系,判断拖拽元素是否需要往前移动或往后移动,同时更新list数组的顺序。

const dragEnter = (index) => {if (state.dragIndex !== null && state.dragIndex !== index) {// 计算拖拽元素和目标元素的位置关系const dragItem = state.list[state.dragIndex];const targetItem = state.list[index];const isAfter = state.dragIndex < index;// 更新列表顺序state.list.splice(state.dragIndex, 1);state.list.splice(isAfter ? index - 1 : index, 0, dragItem);state.dragIndex = isAfter ? index : index - 1;}
};

<template>标签中,绑定@dragenter事件,同时定义一个方法,将当前目标元素的索引位置作为参数传递给该方法。

<template><ul><li v-for="(item, index) in state.list" :key="item.id" :draggable="true" @dragstart="dragStart(index)" @dragenter.prevent @dragover.prevent @dragenter="dragEnter(index)">{{ item.title }}</li></ul>
</template>

这样,就可以实现Vue3的拖拽排序功能了。完整代码如下:

<template><ul><li v-for="(item, index) in state.list" :key="item.id" :draggable="true" @dragstart="dragStart(index)" @dragenter.prevent @dragover.prevent @dragenter="dragEnter(index)">{{ item.title }}</li></ul>
</template><script>
import { reactive } from 'vue';export default {setup() {const state = reactive({list: [{ id: 1, title: 'Item 1' },{ id: 2, title: 'Item 2' },{ id: 3, title: 'Item 3' },{ id: 4, title: 'Item 4' },{ id: 5, title: 'Item 5' }],dragIndex: null});const dragStart = (index) => {state.dragIndex = index;}const dragEnter = (index) => {if (state.dragIndex !== null && state.dragIndex !== index) {const dragItem = state.list[state.dragIndex];const targetItem = state.list[index];const isAfter = state.dragIndex < index;state.list.splice(state.dragIndex, 1);state.list.splice(isAfter ? index - 1 : index, 0, dragItem);state.dragIndex = isAfter ? index : index - 1;}};return { state, dragStart, dragEnter };}
}
</script>

HTML5 新增的可拖拽属性 

draggable 属性是 HTML5 新增的可拖拽属性

HTML 中,除了图像、链接和选择的文本默认可拖拽外,其他元素默认是不可拖拽的。如果想让其他元素变成可拖拽的,首先需要把 draggable 属性设置为 true

<p draggable="true"> 可拖拽draggable</p>

拖拽元素的事件

事件触发时机
dragstart开始拖拽时执行 1 次
drag拖拽开始后多次触发
dragend拖动结束后触发 1 次

可释放目标的事件

事件触发时机
dragenter拖拽元素进入可释放目标时执行 1 次
dragover拖拽元素进入可释放目标时触发多次(100毫秒触发一次)
drop拖拽元素进入可释放目标内释放时(设置了dragover此事件才会生效)

可放置目标

dragenter 或 dragover事件可用于表示有效的放置目标,也就是被拖拽元素可能放置的地方。设置允许被被放置还需要阻止 dragenter 和 dragover 事件的默认处理。<div ondragenter="event.preventDefault()">
  1. 创建一个列表,遍历渲染到页面
  2. 列表项添加 draggable="true"
  3. 列表项添加事件 dragstart dragenter dragend dragover
  4. dragenter 事件中,需要传入列表项的下标,实时进行元素的排序。排序的核心逻辑也是在 dragenter
  5. 代码执行的逻辑是:列表项拖拽到可放置目标时,将该拖拽的元素从原位置删除,再将拖拽的元素插入到当前可放置目标的位置

 

<template><div><TransitionGroup name="list" tag="div" class="container"><divclass="item"v-for="(item, i) in drag.list":key="item.id":draggable="true"@dragstart="dragstart($event, i)"@dragenter="dragenter($event, i)"@dragend="dragend"@dragover="dragover">{{ item.name }}</div></TransitionGroup></div>
</template>
<script setup>
import { reactive } from 'vue';
const drag = reactive({list: [{ name: 'a', id: 1 },{ name: 'b', id: 2 },{ name: 'c', id: 3 },{ name: 'd', id: 4 },{ name: 'e', id: 5 }]
});let dragIndex = 0;function dragstart(e, index) {e.stopPropagation();dragIndex = index;setTimeout(() => {e.target.classList.add('moveing');}, 0);
}
function dragenter(e, index) {e.preventDefault();// 拖拽到原位置时不触发if (dragIndex !== index) {const source = drag.list[dragIndex];drag.list.splice(dragIndex, 1);drag.list.splice(index, 0, source);// 更新节点位置dragIndex = index;}
}
function dragover(e) {e.preventDefault();e.dataTransfer.dropEffect = 'move';
}
function dragend(e) {e.target.classList.remove('moveing');
}
</script><style lang="scss" scoped>
.item {width: 200px;height: 40px;line-height: 40px;// background-color: #f5f6f8;background-color: skyblue;text-align: center;margin: 10px;color: #fff;font-size: 18px;
}.container {position: relative;padding: 0;
}.moveing {opacity: 0;
}.list-move, /* 对移动中的元素应用的过渡 */.list-enter-active,.list-leave-active {transition: all 0.2s ease;
}
</style>

sortable.js-配置文档

Element Plus 组件库中使用 sortable.js 进行表格排序

npm i sortablejs -S
<template>
<div><el-table :data="tableData" id="dragTable" border style="width: 800px;"><el-table-column prop="date" label="Date" width="180" /><el-table-column prop="name" label="Name" width="180" /><el-table-column prop="address" label="Address" /></el-table>
</div>
</template><script setup>
const tableData = [{date: '2016-05-03',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-02',name: 'Cilly',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Linda',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'John',address: 'No. 189, Grove St, Los Angeles',},
]
</script>

导入 sortable.js 

<script setup>
import Sortable from 'sortablejs'
import { onMounted } from 'vue'function setSort() {const el = document.querySelector('#dragTable table tbody')new Sortable(el, {sort: true,ghostClass: 'sortable-ghost',onEnd: (e) => {const targetRow = tableData.splice(e.oldIndex, 1)[0]tableData.splice(e.newIndex, 0, targetRow)console.log(tableData)},})
}
onMounted(() => {setSort()
})const tableData = [// ...
]
</script>

在 onMounted 中,也就是组件挂载完成之后,实例化 Sortable(),传入要进行拖拽排序的节点 el 和其它一些配置参数。现在可以进行拖拽了。

可能需要按住拖动图标才可以进行拖动,需要添加 handle 配置,并指定对应的样式名

<el-table :data="tableData" id="dragTable" border style="width: 600px; margin: 20px"><!-- ...... 省略代码 --><el-table-column label="操作" width="100"><template #default><div class="handle-drag"><el-icon><Sort /></el-icon></div></template></el-table-column></el-table>

上面代码将表格添加了一个操作列,并将操作列的图标设置一个样式类。下面的配置表示只有包含.handle-drag 样式的元素才可以被拖动。其他位置不能被拖动

new Sortable(el, {// ...handle: '.handle-drag',// ...})


vuedraggable-配置文档-推荐

vue.draggable.next 是 Vue3 的拖拽组件,是基于 Sortable.js 实现的。可以用于拖拽列表、菜单、工作台、选项卡等常见的场景。

npm i -S vuedraggable@next

属性 

参数说明类型默认值
value用于实现拖拽的list,通常和内部v-for循环的数组为同一数组Arraynull
list效果同value的。和v-model不能共用Arraynull
tagdraggable 标签在渲染后展现出来的标签类型Stringdiv
optionsdraggable 列表配置项Objectnull
emptyInsertThreshold拖动时,鼠标必须与空的可排序对象之间的距离Number5
clone返回值为true时克隆,可以理解为正常的拖拽变成了复制。当pull:'clone时的拖拽的回调函数’Function无处理
move如果不为空,这个函数将以类似于Sortable onMove回调的方式调用。返回false将取消拖动操作。Functionnull
componentData用来结合UI组件的,可以理解为代理了UI组件的定制信息Objectnull

注意:vuedraggable新版本废弃了options属性,建议使用v-bind属性作为配置项

options配置项

参数说明类型
group用于分组,同一组的不同list可以相互拖动String/Array
sort定义是否可以拖拽Boolean
delay定义鼠标选中列表单元可以开始拖动的延迟时间Number
disabled定义是否此sortable对象是否可用Boolean
animation动画时间 单位:msNumber
handle使列表单元中符合选择器的元素成为拖动的手柄,只有按住拖动手柄才能使列表单元进行拖动Selector
filter定义哪些列表单元不能进行拖放,可设置为多个选择器,中间用“,”分隔Selector
preventOnFilter当拖动filter时是否触发event.preventDefault() 默认触发Boolean
draggable定义哪些列表单元可以进行拖放Selector
ghostClass当拖动列表单元时会生成一个副本作为影子单元来模拟被拖动单元排序的情况,此配置项就是来给这个影子单元添加一个classSelector
chosenClass目标被选中时添加Selector
dragClass目标拖动过程中添加Selector
forceFallback如果设置为true时,将不使用原生的html5的拖放,可以修改一些拖放中元素的样式等Boolean
fallbackClass:当forceFallback设置为true时,拖放过程中鼠标附着单元的样式String
dataIdAttrdata-idSelector
scroll当排序的容器是个可滚动的区域,拖放可以引起区域滚动Boolean
scrollFn用于自定义滚动条的适配Function(offsetX, offsetY, originalEvent, touchEvt, hoverTargetEl)
ScrollSensitivity就是鼠标靠近边缘多远开始滚动默认30Number
scrollSpeed滚动速度Number

事件

参数说明回调参数
start开始拖动时的回调函数function({to,from,item,clone,oldIndex,newIndex})
add添加单元时的回调函数function({to,from,item,clone,oldIndex,newIndex})
remove单元被移动到另一个列表时的回调函数function({to,from,item,clone,oldIndex,newIndex})
update排序发生变化时的回调函数function({to,from,item,clone,oldIndex,newIndex})
end拖动结束时的回调函数function({to,from,item,clone,oldIndex,newIndex})
choose选择单元时的回调函数function({to,from,item,clone,oldIndex,newIndex})
sort排序发生变化时的回调函数function({to,from,item,clone,oldIndex,newIndex})
filter尝试选择一个被filter过滤的单元的回调函数function({to,from,item,clone,oldIndex,newIndex})
cloneclone时的回调函数function({to,from,item,clone,oldIndex,newIndex})

插槽

页眉或页脚插槽都不能与 tarnstion-group 一起使用。

Header
使用标题插槽在vuedraggable组件中添加不可拖动的元素。它应该与draggable选项一起使用来标记draggable元素。请注意,无论标题槽在模板中的位置如何,它总是被添加到默认槽之前。<draggable v-model="myArray" draggable=".item"><div v-for="element in myArray" :key="element.id" class="item">{{element.name}}</div><button slot="header" @click="addPeople">Add</button>
</draggable>Footer
使用页脚槽在vuedraggable组件中添加不可拖动的元素。它应该与draggable选项一起使用,以标记draggable元素。请注意,无论页脚槽在模板中的位置如何,它都将始终添加到默认槽之后。<draggable v-model="myArray" draggable=".item"><div v-for="element in myArray" :key="element.id" class="item">{{element.name}}</div><button slot="footer" @click="addPeople">Add</button>
</draggable>

使用代码

<script setup>
import draggable from 'vuedraggable'
import { reactive } from 'vue'const state = reactive({list1: [1, 2, 3, 4],list2: ['a', 'b', 'c', 'd'],
})function onStart() {}function onEnd() {console.log(state)
}</script>

导入 draggable并定义一些基础数据

<template><div style="margin-left: 30px;"><draggable:list="state.list1":force-fallback="true"chosen-class="chosen"animation="300"@start="onStart"@end="onEnd"><template #item="{ element }"><div class="item">{{ element }}</div></template></draggable></div>
</template>

其中 @start 和 @end 为拖拽开始和结束时的事件。chosen-class 为拖拽时的样式

为组件设置相同的 group 属性,可以实现在不同的块之间拖拽

<draggable group="group" :list="state.list1" ><template #item="{ element }"><div class="item bck1">{{ element }}</div></template></draggable><draggable group="group" :list="state.list2" ><template #item="{ element }"><div class="item bck2">{{ element }}</div></template></draggable>


http://www.ppmy.cn/news/1109593.html

相关文章

【mars3d学习】淹没分析,计算最高最低值出错

问题一&#xff1a;淹没分析&#xff08;地形分析&#xff09; Mars3d淹没分析的示例 - 功能示例(Vue版) | Mars3D三维可视化平台 | 火星科技 初始化一个polygon面的时候&#xff0c;使用 mars3d.PolyUtil.interPolygonByDepth 直接计算淹没的最大最小高度值&#xff1b; 但…

json转换

json转html {"DS": [{"PROVINCE": "陕西省","ADMIN_CODE_CHN": "610600","STATION_ID_C": "53845","LON": "109.4497","V31001_S": 10,"V31001_X": 0},{&quo…

MAC MINI 2012安装Montery折腾笔记

MAC MINI 2012安装Montery折腾笔记&#xff08;作为电视盒子/远程开发机&#xff09; 起因&#xff1a; 手头有个mac mini&#xff0c;2018年买的2手。一直都是10.12系统&#xff0c;处理python和苹果开发都受制于旧系统&#xff0c;很多软件也装不上&#xff0c;于是有了升级…

浅谈旁通阀式余压智能控制系统

安科瑞 华楠 摘要&#xff1a;详细阐述了旁通阀式余压控制系统的组成、工作原理&#xff0c;并结合实际项目实例&#xff0c;分析了平面优化设计方法。提出旁通阀式余压控制系统是通过设在住宅前室的压力传感器来调节加压送风系统在区域的进风量&#xff0c;从而改变区域的风压…

Linux提权

shell分本地shell 和 webshell 有些提权方式只能本地shell使用 常见内核漏洞查找脚本以及利用 环境变量提权 suid https://www.cnblogs.com/banglook/archive/2022/03/17/16019354.html linux特殊命令https://www.secrss.com/articles/28493 什么是suid SUID (Set UID)是Li…

Marin说PCB之封装设计系列---(02)--异形焊盘的封装设计总结

每天下班回家看电视本来是一件很美好的事情&#xff0c;可是正当我磕着瓜子看着异人之下的时候&#xff0c;手机突然响起来了&#xff0c;我以为是我们组哪个同事找我呢。一接电话居然是我的老朋友陈世美陈总&#xff0c;江湖人称少妇杀手。给我打电话主要是说他最近遇到一个异…

Nginx__高级进阶篇之LNMP动态网站环境部署

动态网站和LNMP&#xff08;LinuxNginxMySQLPHP&#xff09;都是用于建立和运行 web 应用程序的技术。 动态网站是通过服务器端脚本语言&#xff08;如 PHP、Python、Ruby等&#xff09;动态生成网页内容的网站。通过这种方式&#xff0c;动态网站可以根据用户的不同请求生成不…

TCP特性的滑动窗口,流量控制

目录 一、TCP特性滑动窗口 二、TCP特性流量控制&#xff08;作为滑动窗口的补充&#xff09; 一、TCP特性滑动窗口 提高传输效率&#xff08;更准确的说&#xff0c;让TCP在可靠传输的前提下&#xff0c;效率不太拉跨&#xff09;&#x1f49b; 当然你要是想让TCP媲美UDP&…