使用vue-draggable-plus实现拖拽排序
作者:迪士尼在逃保安
最近实习期间遇到了一个需求:在 Vue3 的一个 H5 页面当中点击拖拽图标上下拖动 tab 子项,然后点击保存可以保存最新的 tab 项顺序,当时王哥和我说可以用 vue-draggable-plus
这个库来实现拖拽,因为公司的项目组件用的都是 TSX 写法,是我之前没接触过的,所以写起来比较慢,整个需求整整花了两天才完成。下面的代码展示还是使用传统的 SFC 写法。
vue-draggable-plus
首先在项目安装好 vue-draggable-plus
:
npm install vue-draggable-plus
具体使用可以参考中文文档:vue-draggable-plus | vue-draggable-plus (gitee.io)
基本布局
vue-draggable-plus 的使用有三种方式,这里我采用的是组件方式引入:
<template> ... <VueDraggable ref="el" v-model="sortList" :disabled="disabled" :animation="150" ghostClass="ghost" class="flex flex-col gap-2 p-4 w-300px h-300px m-auto bg-gray-500/5 rounded" @start="onStart" @update="onUpdate" filter=".undraggable" handle=".handle" > <div class="sort-item" v-for="item in sortList" :key="item.id" :class="item.id === 1 ? 'undraggable' : ''" :style="item.id === 1 ? 'opacity:0.3' : ''" > <img class="delete" src="./assets/ic_delete.svg" @click="removeItem(item.id)" /> <span>{{ item.name }}</span> <img class="handle" src="./assets/ic_items.svg" /> </div> </VueDraggable> ... </template> <script setup lang="ts"> import { ref } from "vue"; import { type UseDraggableReturn, VueDraggable } from "vue-draggable-plus"; ... const sortShow = ref(false); const el = ref<UseDraggableReturn>(); const disabled = ref(false); ... </script>
其中@start
和 @update
两个自定义事件分别在拖拽开始和拖拽完成后调用,可以在这个期间进行一些逻辑操作,像监听数组的前后变换等。
禁止部分元素拖动
因为当时项目还有一些其他的特殊需求:有些默认的子项保持不变,不允许拖拽,这边也可以用 vue-draggable-plus
实现,如上述代码所示:使用 filter=".undraggable"
过滤类名为 undraggable
的元素,只要是带有这个类名的元素都无法被拖拽。
指定元素触发拖拽
当时 UI 还有一个需求,只能点击侧边的拖拽按钮才能实现拖动,可以通过 handle
属性传递一个选择器,来指定拖拽的句柄,这里我指定的是 handle
类名,只有点击携带这个类名的拖拽图标才能实现拖拽。
实现需求基本逻辑:
首先定义两个数组 sortList
和 constList
,其中 sortList
用来接收后端传过来的数据,后面实现排序完的数据也是存在这个数组当中,而 constList
则是作为一个参照数组与 sortList
进行比较实现元素的动态增添。最后点击保存的时候可将 sortList
作为参数请求保存当前顺序的接口,因为当时后端需要通过 sortOrder
字段来判断排序,所以最后在保存的时候给 sortList
的每个子项都按顺序添加一个 sortOrder
字段再请求接口,完整代码如下:
完整代码
<template> <van-button type="success" @click="sortClick">排列菜单</van-button> <div v-for="item in sortList" :key="item.id">{{ item }}</div> <van-action-sheet v-model:show="sortShow" title="排序管理"> <div class="content"> <!-- 当前排序 --> <div class="current-sort"> <span class="label">当前排序</span> <VueDraggable ref="el" v-model="sortList" :disabled="disabled" :animation="150" ghostClass="ghost" class="flex flex-col gap-2 p-4 w-300px h-300px m-auto bg-gray-500/5 rounded" @start="onStart" @update="onUpdate" filter=".undraggable" handle=".handle" > <div class="sort-item" v-for="item in sortList" :key="item.id" :class="item.id === 1 ? 'undraggable' : ''" :style="item.id === 1 ? 'opacity:0.3' : ''" > <img class="delete" src="./assets/ic_delete.svg" @click="removeItem(item.id)" /> <span>{{ item.name }}</span> <img class="handle" src="./assets/ic_items.svg" /> </div> </VueDraggable> </div> <!-- 默认子项 --> <div class="change-sort"> <span class="label" style="margin-top: 24px">默认子项</span> <div class="sort-item" v-for="item in constList" :key="item.id" :style="sortList.find((el) => el.id === item.id) ? 'opacity:0.3' : ''" > <img class="add" src="./assets/ic_add.svg" @click="addItem(item.id)" /> <span>{{ item.name }}</span> </div> </div> </div> <div class="sheet-footer"> <van-button>取消</van-button> <van-button type="primary" @click="onConfirm">保存</van-button> </div> </van-action-sheet> </template> <script setup lang="ts"> import { ref } from "vue"; import { type UseDraggableReturn, VueDraggable } from "vue-draggable-plus"; interface SortItemType { name: string; id: number; } // 当前排列项 let sortList = ref<SortItemType[]>([ { id: 1, name: "Vue" }, { id: 2, name: "React" }, { id: 3, name: "TypeScript" }, { id: 4, name: "Uniapp" }, { id: 5, name: "Vite" }, ]); // 对比项(内容不变,作为参照与sortList进行对比) let constList = ref<SortItemType[]>([ { id: 2, name: "React" }, { id: 3, name: "TypeScript" }, { id: 4, name: "Uniapp" }, { id: 5, name: "Vite" }, ]); const sortShow = ref(false); const el = ref<UseDraggableReturn>(); const disabled = ref(false); const onStart = () => { console.log("拖拽前-sortList:", sortList.value); }; const onUpdate = () => { console.log("拖拽后-sortList:", sortList.value); }; const sortClick = () => { sortShow.value = true; }; /** * 移除当前排列项 */ const removeItem = (id: number) => { if (id === 1) return; // 默认不能删除 let idx = sortList.value.findIndex((item: SortItemType) => item.id === id); if (idx !== -1) { sortList.value.splice(idx, 1); } }; /** * 新增排列项 */ const addItem = (id: number) => { let idx = sortList.value.findIndex((item: SortItemType) => item.id === id); if (idx === -1) { sortList.value.push( constList.value.find((item: SortItemType) => item.id === id), ); } }; /** * 保存当前排序 */ const onConfirm = () => { sortList.value.map((item: any, index: number) => { item.sortOrder = index + 1; }); sortShow.value = false; }; </script> <style lang="less" scoped> .content { .label { display: block; font-weight: 600; font-size: 13px; color: #969799; height: 18px; padding-left: 16px; margin-bottom: 16px; } .sort-item { margin-bottom: 16px; font-weight: 600; font-size: 15px; color: #323233; position: relative; .delete, .add { vertical-align: middle; margin: 0 12px 0 19px; } .handle { vertical-align: middle; position: absolute; right: 15px; } } } .sheet-footer { display: flex; justify-content: center; padding: 10px; .van-button { width: 165px; height: 40px; margin: 0 6px; } } </style>
最终实现效果图
以上就是使用vue-draggable-plus实现拖拽排序的详细内容,更多关于vue-draggable-plus拖拽排序的资料请关注脚本之家其它相关文章!