使用vue-draggable-plus實(shí)現(xiàn)拖拽排序
最近實(shí)習(xí)期間遇到了一個(gè)需求:在 Vue3 的一個(gè) H5 頁(yè)面當(dāng)中點(diǎn)擊拖拽圖標(biāo)上下拖動(dòng) tab 子項(xiàng),然后點(diǎn)擊保存可以保存最新的 tab 項(xiàng)順序,當(dāng)時(shí)王哥和我說(shuō)可以用 vue-draggable-plus
這個(gè)庫(kù)來(lái)實(shí)現(xiàn)拖拽,因?yàn)楣镜捻?xiàng)目組件用的都是 TSX 寫法,是我之前沒(méi)接觸過(guò)的,所以寫起來(lái)比較慢,整個(gè)需求整整花了兩天才完成。下面的代碼展示還是使用傳統(tǒng)的 SFC 寫法。
vue-draggable-plus
首先在項(xiàng)目安裝好 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
兩個(gè)自定義事件分別在拖拽開(kāi)始和拖拽完成后調(diào)用,可以在這個(gè)期間進(jìn)行一些邏輯操作,像監(jiān)聽(tīng)數(shù)組的前后變換等。
禁止部分元素拖動(dòng)
因?yàn)楫?dāng)時(shí)項(xiàng)目還有一些其他的特殊需求:有些默認(rèn)的子項(xiàng)保持不變,不允許拖拽,這邊也可以用 vue-draggable-plus
實(shí)現(xiàn),如上述代碼所示:使用 filter=".undraggable"
過(guò)濾類名為 undraggable
的元素,只要是帶有這個(gè)類名的元素都無(wú)法被拖拽。
指定元素觸發(fā)拖拽
當(dāng)時(shí) UI 還有一個(gè)需求,只能點(diǎn)擊側(cè)邊的拖拽按鈕才能實(shí)現(xiàn)拖動(dòng),可以通過(guò) handle
屬性傳遞一個(gè)選擇器,來(lái)指定拖拽的句柄,這里我指定的是 handle
類名,只有點(diǎn)擊攜帶這個(gè)類名的拖拽圖標(biāo)才能實(shí)現(xiàn)拖拽。
實(shí)現(xiàn)需求基本邏輯:
首先定義兩個(gè)數(shù)組 sortList
和 constList
,其中 sortList
用來(lái)接收后端傳過(guò)來(lái)的數(shù)據(jù),后面實(shí)現(xiàn)排序完的數(shù)據(jù)也是存在這個(gè)數(shù)組當(dāng)中,而 constList
則是作為一個(gè)參照數(shù)組與 sortList
進(jìn)行比較實(shí)現(xiàn)元素的動(dòng)態(tài)增添。最后點(diǎn)擊保存的時(shí)候可將 sortList
作為參數(shù)請(qǐng)求保存當(dāng)前順序的接口,因?yàn)楫?dāng)時(shí)后端需要通過(guò) sortOrder
字段來(lái)判斷排序,所以最后在保存的時(shí)候給 sortList
的每個(gè)子項(xiàng)都按順序添加一個(gè) sortOrder
字段再請(qǐng)求接口,完整代碼如下:
完整代碼
<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"> <!-- 當(dāng)前排序 --> <div class="current-sort"> <span class="label">當(dāng)前排序</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> <!-- 默認(rèn)子項(xiàng) --> <div class="change-sort"> <span class="label" style="margin-top: 24px">默認(rèn)子項(xiàng)</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; } // 當(dāng)前排列項(xiàng) let sortList = ref<SortItemType[]>([ { id: 1, name: "Vue" }, { id: 2, name: "React" }, { id: 3, name: "TypeScript" }, { id: 4, name: "Uniapp" }, { id: 5, name: "Vite" }, ]); // 對(duì)比項(xiàng)(內(nèi)容不變,作為參照與sortList進(jìn)行對(duì)比) 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; }; /** * 移除當(dāng)前排列項(xiàng) */ const removeItem = (id: number) => { if (id === 1) return; // 默認(rèn)不能刪除 let idx = sortList.value.findIndex((item: SortItemType) => item.id === id); if (idx !== -1) { sortList.value.splice(idx, 1); } }; /** * 新增排列項(xiàng) */ 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), ); } }; /** * 保存當(dāng)前排序 */ 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> ?
最終實(shí)現(xiàn)效果圖
以上就是使用vue-draggable-plus實(shí)現(xiàn)拖拽排序的詳細(xì)內(nèi)容,更多關(guān)于vue-draggable-plus拖拽排序的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue結(jié)合vue-electron創(chuàng)建應(yīng)用程序小結(jié)
這篇文章主要介紹了vue結(jié)合vue-electron創(chuàng)建應(yīng)用程序,本文給大家介紹了安裝electron有兩種方式,兩種方式創(chuàng)建的項(xiàng)目結(jié)構(gòu)大不相同,需要的朋友可以參考下2024-03-03vue的無(wú)縫滾動(dòng)組件vue-seamless-scroll實(shí)例
本篇文章主要給大家講解了vue的無(wú)縫滾動(dòng)組件vue-seamless-scroll的用法,需要的朋友參考學(xué)習(xí)下吧。2017-12-12vue 如何實(shí)現(xiàn)配置@絕對(duì)路徑
這篇文章主要介紹了vue 如何實(shí)現(xiàn)配置@絕對(duì)路徑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09vue3實(shí)現(xiàn)alert自定義的plugins方式
這篇文章主要介紹了vue3實(shí)現(xiàn)alert自定義的plugins方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08解決vue 引入子組件報(bào)錯(cuò)的問(wèn)題
今天小編就為大家分享一篇解決vue 引入子組件報(bào)錯(cuò)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09Vue實(shí)現(xiàn)預(yù)覽文件(Word/Excel/PDF)功能的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)Vue實(shí)現(xiàn)預(yù)覽文件(Word/Excel/PDF)的功能,文中的實(shí)現(xiàn)步驟講解詳細(xì),需要的小伙伴可以參考一下2023-03-03詳解Element-ui NavMenu子菜單使用遞歸生成時(shí)使用報(bào)錯(cuò)
這篇文章主要介紹了詳解Element-ui NavMenu子菜單使用遞歸生成時(shí)使用報(bào)錯(cuò),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04vue.js學(xué)習(xí)筆記之綁定style樣式和class列表
數(shù)據(jù)綁定一個(gè)常見(jiàn)需求是操作元素的 class 列表和它的內(nèi)聯(lián)樣式。這篇文章主要介紹了vue.js綁定style和class的相關(guān)資料,需要的朋友可以參考下2016-10-10