vue3中使用vuedraggable實現(xiàn)拖拽el-tree數(shù)據(jù)分組功能
看效果:
可以實現(xiàn)單個拖拽、雙擊添加、按住ctrl鍵實現(xiàn)多個添加,或者按住shift鍵實現(xiàn)范圍添加,添加到框中的數(shù)據(jù),還能拖拽排序

先安裝 vuedraggable
這是他的官網(wǎng) vue.draggable中文文檔 - itxst.com
npm i vuedraggable -S
直接粘貼代碼即可:
index.vue
<template>
<div class="w-full h-full">
<!-- 頁面拖拽組件 -->
<div class="leftPart">
<ElTree
class="w100%"
:data="$.treeData"
ref="treeTableListRef"
:props="$.defaultProps"
highlight-current
:expand-on-click-node="false"
key="id"
:default-expand-all="true"
@node-click="(data, node) => $.tableFieldsNodeClick(data, node, treeTableListRef)"
>
<template #default="{ data }">
<Draggable
:list="[data]"
ghost-class="ghost"
chosen-class="chosenClass"
animation="300"
@start="onStart"
@end="onEnd"
group="group1"
v-tooltip="`Tips:按住Ctrl或Shift進行批量選擇`"
>
<template #item="{ element }">
<div @dblclick="dbAddData(element)" style="user-select: none" class="item">
{{ element.name }}
</div>
</template>
</Draggable>
</template>
</ElTree>
</div>
<!-- 右側(cè)內(nèi)容 -->
<div class="rightPart">
<div class="flex">
<div
@mouseover="divMouseOver"
@mouseleave="divMouselease"
class="w-full rightContent"
style="border: 1px solid #ccc"
>
<Draggable
:list="state.list"
ghost-class="ghost"
group="group1"
chosen-class="chosenClass"
animation="300"
@start="onStart"
@end="onEnd"
@add="addData"
class="w-full dragArea"
>
<template #item="{ element }">
<div class="item">
{{ element.name }}
</div>
</template>
</Draggable>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Draggable from "vuedraggable";
import { useData } from "./hooks/drag";
const treeTableListRef = ref();
let { $data: $ } = useData();
const state = reactive<any>({
//需要拖拽的數(shù)據(jù),拖拽后數(shù)據(jù)的順序也會變化
list: [],
});
//拖拽開始的事件
const onStart = () => {
console.log("開始拖拽");
};
const divMouseOver = (e: any) => {};
const divMouselease = (e: any) => {};
//拖拽結(jié)束的事件
const onEnd = () => {
console.log("結(jié)束拖拽");
};
// 雙擊添加
const dbAddData = (data: any) => {
let i = state.list.findIndex((a: any) => a.id == data.id);
if (data.children) return; //父級節(jié)點不添加
if (i == -1) state.list.push(data);
};
// 批量添加
const addData = () => {
// 拿到所有nodes節(jié)點數(shù)組
const nodes = treeTableListRef.value.store._getAllNodes();
nodes.map((a: any) => {
if ($.selectNodes.includes(a.id)) {
state.list.push(a.data);
}
// 排除父級,只添加子級
state.list = state.list.filter((a: any) => !a.children);
// 去重
state.list = [...new Set(state.list)];
});
};
onMounted(() => {});
onBeforeMount(() => {
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
});
// 按下為true
const handleKeyDown = (event: any) => {
// 代表按下的是ctrl鍵
if (event.key == "Control") {
$.ctrlKeyPressed = true;
}
// 代表按下的是shift鍵
if (event.key == "Shift") {
$.shiftKeyPressed = true;
}
};
// 釋放為false
const handleKeyUp = (event: any) => {
// 代表按下的是ctrl鍵
if (event.key == "Control") {
$.ctrlKeyPressed = false;
}
// 代表按下的是shift鍵
if (event.key == "Shift") {
$.shiftKeyPressed = false;
}
};
</script>
<style scoped lang="scss">
.leftPart {
width: 20%;
height: 100%;
float: left;
border-right: 1px dashed #ccc;
}
.rightPart {
padding: 20px;
width: 60%;
height: 100%;
float: left;
}
.list_drap {
min-width: 120px;
max-height: 86px;
min-height: 22px;
overflow-y: auto;
height: auto;
}
.rightContent {
border-radius: 4px;
min-height: 30px;
display: flex;
}
.dragArea {
padding: 10px 5px;
flex-grow: 1;
.item {
float: left;
min-width: 50px;
display: inline;
margin: 0 3px 2px 3px;
background-color: rgb(235, 241, 255);
color: #3370ff;
font-size: 12px;
cursor: all-scroll;
user-select: none;
height: 20px;
line-height: 20px;
padding-left: 9px;
padding-right: 9px;
background: #ececfd;
color: #333333;
font-size: 12px;
}
}
</style>drag.ts
export function useData() {
const $data: any = reactive({
ctrlKeyPressed: false,
shiftKeyPressed: false,
shiftKeyFelid: [],
defaultProps: {
children: "children",
label: "name",
},
treeData: [
{
name: "一級1",
id: 1,
children: [
{
name: "二級1",
id: 2,
children: [
{
name: "三級1",
id: 2,
},
{
name: "三級2",
id: 4,
},
{
name: "三級3",
id: 5,
},
{
name: "三級4",
id: 6,
},
{
name: "三級5",
id: 7,
},
],
},
{
name: "二級2",
id: 8,
},
{
name: "二級3",
id: 9,
},
{
name: "二級4",
id: 10,
},
{
name: "二級5",
id: 11,
},
],
},
{
name: "一級2",
id: 12,
children: [
{
name: "二級1",
id: 13,
},
{
name: "二級2",
id: 14,
},
{
name: "二級3",
id: 15,
},
{
name: "二級4",
id: 16,
},
{
name: "二級5",
id: 17,
},
],
},
],
selectNodes: [],
treeTableListRef: null,
});
// 節(jié)點選中事件
$data.tableFieldsNodeClick = (nodeData: any, node: any, treeTableListRef: any) => {
const nodes = treeTableListRef.store._getAllNodes(); //所有node節(jié)點
const ishas = $data.selectNodes.includes(node.id);
// 遞歸遍歷節(jié)點數(shù)組進行ID存放
function addSelectId(arr: any) {
for (const item of arr) {
$data.selectNodes.push(item.id);
if (Array.isArray(item.childNodes) && item.childNodes.length) {
addSelectId(item.childNodes);
}
}
}
// 遞歸遍歷刪除節(jié)點id
function delSelectId(arr: any) {
for (const item of arr) {
const index = $data.selectNodes.findIndex((x: any) => x == item.id);
$data.selectNodes.splice(index, 1);
if (Array.isArray(item.children) && item.children.length) {
delSelectId(item.children);
}
}
}
// 按住了ctrl鍵,可以進行單個多選
if ($data.ctrlKeyPressed) {
// 如果為true代表當前選中的節(jié)點已存在
if (ishas) {
// 查找當前選中的節(jié)點的索引
const index = $data.selectNodes.findIndex((x: any) => x == node.id);
// 刪除父節(jié)點
$data.selectNodes.splice(index, 1);
// 刪除子節(jié)點
if (Array.isArray(node.childNodes) && node.childNodes.length) {
delSelectId(node.childNodes);
}
} else {
// 否則當前選中的節(jié)點不存在,就加入到已選節(jié)點數(shù)組序列
$data.selectNodes.push(node.id);
// 防止選中的是父節(jié)點,就需要遞歸將子節(jié)點加入
if (Array.isArray(node.childNodes) && node.childNodes.length) {
addSelectId(node.childNodes);
}
}
node.isCurrent = !node.isCurrent;
// 按下了shift鍵,可以進行范圍多選
} else if ($data.shiftKeyPressed) {
// 先清空
$data.selectNodes = [];
// 將當前節(jié)點放入
$data.selectNodes.push(node.id);
$data.shiftKeyFelid.push(node.id);
if ($data.shiftKeyFelid.length > 1) {
// 首索引
const sIndex = nodes.findIndex((x: any) => x.id == $data.shiftKeyFelid[0]);
// 尾索引
const eIndex = nodes.findIndex((x: any) => x.id == $data.shiftKeyFelid[$data.shiftKeyFelid.length - 1]);
// 根據(jù)首尾索引,存入中間節(jié)點
const s = sIndex < eIndex ? sIndex : eIndex; //取小值當開頭索引
const e = sIndex < eIndex ? eIndex : sIndex; //取大值當結(jié)尾索引
for (let i = s; i < e; i++) {
$data.selectNodes.push(nodes[i].id);
}
}
} else {
// 否則就是單機選擇
$data.shiftKeyFelid = [];
$data.selectNodes = [];
$data.selectNodes = [node.id];
}
// 下面是對已選中的節(jié)點,進行高亮展示
// 通過控制elementui中節(jié)點上的isCurrent屬性
// isCurrent為true是高亮,否則取消高亮
for (const item of nodes) {
if ($data.selectNodes.includes(item.id)) {
item.isCurrent = true;
} else {
item.isCurrent = false;
}
}
};
return {
$data: $data,
};
}到此這篇關(guān)于vue3中使用vuedraggable實現(xiàn)拖拽el-tree數(shù)據(jù)進分組的文章就介紹到這了,更多相關(guān)vue draggable拖拽分組內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue.js利用defineProperty實現(xiàn)數(shù)據(jù)的雙向綁定
本篇文章主要介紹了用Node.js當作后臺、jQuery寫前臺AJAX代碼實現(xiàn)用戶登錄和注冊的功能的相關(guān)知識。具有很好的參考價值。下面跟著小編一起來看下吧2017-04-04
詳解vue數(shù)組遍歷方法forEach和map的原理解析和實際應(yīng)用
這篇文章主要介紹了詳解vue數(shù)組遍歷方法forEach和map的原理解析和實際應(yīng)用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11
vue中為什么在組件內(nèi)部data是一個函數(shù)而不是一個對象
這篇文章主要介紹了vue中為什么在組件內(nèi)部data是一個函數(shù)而不是一個對象,本文通過示例代碼給大家講解的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04
vue.js通過自定義指令實現(xiàn)數(shù)據(jù)拉取更新的實現(xiàn)方法
數(shù)據(jù)拉取更新這個功能相信大家基本都見過,但是如果要做起來卻不止如何做起,所以這篇文章給大家分享了vue.js通過自定義指令實現(xiàn)的方法,閱讀本文需要對vue有一定理解,有需要的朋友們下面來一起看看吧。2016-10-10

