Skip to content

Commit bc328e4

Browse files
committed
feat: 优化拖拽
1 parent 87e8228 commit bc328e4

File tree

7 files changed

+231
-68
lines changed

7 files changed

+231
-68
lines changed

docs/demos/tree/Drag.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ const expandedKeys = ref<number[]>([1, 100, 102]);
100100
:fieldNames="customFieldNames"
101101
:indent="20"
102102
:iconSize="14"
103-
:filter-method="filterMethod"
103+
:filterMethod="filterMethod"
104104
:itemGap="4"
105105
:draggable="draggable"
106106
@dragstart="onDragstart"

docs/demos/tree/DragList.vue

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<script lang="ts" setup>
2+
import { onMounted, ref, shallowRef } from 'vue';
3+
import { VirtTree, type TreeNode } from 'vue-virt-list';
4+
5+
type ItemData = {
6+
id: string | number;
7+
title: string;
8+
children?: ItemData[];
9+
// 禁止拖入
10+
disableDragIn?: boolean;
11+
// 禁止托出
12+
disableDragOut?: boolean;
13+
};
14+
15+
const customFieldNames = {
16+
key: 'id',
17+
};
18+
const list = ref<ItemData[]>([]);
19+
list.value = Array.from({ length: 40 }).map((_, i) => ({
20+
id: i + 1,
21+
title: `Node-${i}`,
22+
disableDragIn: true,
23+
}));
24+
25+
const virtTreeRef = ref<typeof VirtTree>();
26+
const filterMethod = (query: string, node: any) => {
27+
return node.name.includes(query);
28+
};
29+
30+
function onDragstart() {
31+
console.log('onDragstart');
32+
}
33+
34+
function onDragEnd(data: any) {
35+
if (data) {
36+
console.log('drag success', data);
37+
} else {
38+
console.warn('drag fail: Invalid');
39+
}
40+
}
41+
42+
const draggable = ref(true);
43+
const expandedKeys = ref<number[]>([1, 100, 102]);
44+
const toggleExpand = (key: number) => {
45+
virtTreeRef.value?.expandNode(key, !expandedKeys.value.includes(key));
46+
};
47+
function onDragStart(e: MouseEvent | Event) {
48+
if (virtTreeRef.value?.onDragstart) virtTreeRef.value?.onDragstart(e);
49+
}
50+
</script>
51+
52+
<template>
53+
<div class="demo-tree">
54+
<div class="virt-tree-wrapper">
55+
<VirtTree
56+
ref="virtTreeRef"
57+
v-model:expandedKeys="expandedKeys"
58+
:list="list"
59+
:fieldNames="customFieldNames"
60+
:indent="0"
61+
:iconSize="14"
62+
:filterMethod="filterMethod"
63+
:itemGap="4"
64+
@dragstart="onDragstart"
65+
@dragend="onDragEnd"
66+
dragOnly
67+
dragGhostClass="drag-ghost-class"
68+
dragClass="drag-class"
69+
expandOnClickNode
70+
default-expand-all
71+
>
72+
<template #default="{ node }">
73+
<div
74+
:style="{
75+
display: 'flex',
76+
alignItems: 'center',
77+
height: '32px',
78+
}"
79+
@click="toggleExpand(node.data.id)"
80+
>
81+
<!-- drag handler -->
82+
<div
83+
style="cursor: move; margin-right: 8px"
84+
draggable="true"
85+
@dragstart="onDragStart"
86+
>
87+
<svg
88+
width="16"
89+
height="16"
90+
viewBox="0 0 16 16"
91+
fill="none"
92+
xmlns="http://www.w3.org/2000/svg"
93+
>
94+
<path
95+
fill-rule="evenodd"
96+
clip-rule="evenodd"
97+
d="M5 3C5 2.44772 5.44772 2 6 2C6.55228 2 7 2.44772 7 3C7 3.55228 6.55228 4 6 4C5.44772 4 5 3.55228 5 3ZM9 3C9 2.44772 9.44772 2 10 2C10.5523 2 11 2.44772 11 3C11 3.55228 10.5523 4 10 4C9.44772 4 9 3.55228 9 3ZM5 8C5 7.44772 5.44772 7 6 7C6.55228 7 7 7.44772 7 8C7 8.55228 6.55228 9 6 9C5.44772 9 5 8.55228 5 8ZM9 8C9 7.44772 9.44772 7 10 7C10.5523 7 11 7.44772 11 8C11 8.55228 10.5523 9 10 9C9.44772 9 9 8.55228 9 8ZM5 13C5 12.4477 5.44772 12 6 12C6.55228 12 7 12.4477 7 13C7 13.5523 6.55228 14 6 14C5.44772 14 5 13.5523 5 13ZM9 13C9 12.4477 9.44772 12 10 12C10.5523 12 11 12.4477 11 13C11 13.5523 10.5523 14 10 14C9.44772 14 9 13.5523 9 13Z"
98+
fill="var(--virt-tree-color-icon)"
99+
/>
100+
</svg>
101+
</div>
102+
<!-- content -->
103+
<div>
104+
{{ node.title }}
105+
</div>
106+
</div>
107+
</template>
108+
109+
<template #empty>
110+
<div style="padding: 16px">暂无数据</div>
111+
</template>
112+
</VirtTree>
113+
</div>
114+
</div>
115+
</template>
116+
117+
<style scoped lang="scss">
118+
.demo-tree {
119+
width: 100%;
120+
display: flex;
121+
flex-direction: column;
122+
overflow: hidden;
123+
.tree-btn-container {
124+
display: flex;
125+
flex: 1;
126+
flex-direction: row-reverse;
127+
justify-content: space-between;
128+
padding: 12px 8px;
129+
gap: 8px;
130+
.input-label {
131+
font-size: 14px;
132+
}
133+
134+
.btn-item {
135+
padding: 4px 12px;
136+
cursor: pointer;
137+
border: 1px solid #ececec;
138+
border-radius: 4px;
139+
font-size: 14px;
140+
}
141+
142+
.input-container {
143+
display: flex;
144+
gap: 8px;
145+
align-items: center;
146+
input {
147+
height: 100%;
148+
border: 1px solid #ececec;
149+
border-radius: 4px;
150+
padding: 0 8px;
151+
}
152+
}
153+
}
154+
}
155+
</style>

docs/en/examples/virt-tree.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,22 @@ Focus 状态切换完全交由外部处理,内部仅给Node节点加上`.is-fo
6969

7070
## draggable
7171

72-
<!<< @/demos/tree/Drag.vue
73-
74-
## dragover placement
72+
拖拽后不直接修改数据,而是提供 dragend 事件,由业务自行判定并修改数据,数据更改后,通过响应式更新树。
7573

76-
拖拽悬浮生效区域判定
74+
位置判定说明:每个元素会被切割为3份,上面一份判定为拖入该元素上方,下面一份判定为拖入该元素下方。如果该元素被禁止拖入,则会把该元素一分为二,去掉中间的区域。
7775

78-
<!<< @/demos/tree/DragoverPlacement.vue
76+
<!<< @/demos/tree/Drag.vue
7977

8078
## drag handler
8179

80+
自定义拖拽生效图标,而不是整个节点
81+
8282
<!<< @/demos/tree/DragHandler.vue
8383

84+
通常这种形式用在替代 VueDraggable 组件
85+
86+
<!<< @/demos/tree/DragList.vue
87+
8488
## css variable
8589

8690
```css

docs/zh/examples/virt-tree.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,22 @@ Focus 状态切换完全交由外部处理,内部仅给Node节点加上`.is-fo
6969

7070
## draggable
7171

72-
<!<< @/demos/tree/Drag.vue
73-
74-
## dragover placement
72+
拖拽后不直接修改数据,而是提供 dragend 事件,由业务自行判定并修改数据,数据更改后,通过响应式更新树。
7573

76-
拖拽悬浮生效区域判定
74+
位置判定说明:每个元素会被切割为3份,上面一份判定为拖入该元素上方,下面一份判定为拖入该元素下方。如果该元素被禁止拖入,则会把该元素一分为二,去掉中间的区域。
7775

78-
<!<< @/demos/tree/DragoverPlacement.vue
76+
<!<< @/demos/tree/Drag.vue
7977

8078
## drag handler
8179

80+
自定义拖拽生效图标,而不是整个节点
81+
8282
<!<< @/demos/tree/DragHandler.vue
8383

84+
通常这种形式用在替代 VueDraggable 组件
85+
86+
<!<< @/demos/tree/DragList.vue
87+
8488
## css variable
8589

8690
```css

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vue-virt-list",
3-
"version": "1.5.7",
3+
"version": "1.5.8",
44
"description": "Tiny & Virtual scroll list & Huge amount data & High performance (support vue2.x&vue3.x)",
55
"author": "kolarorz",
66
"license": "MIT",

src/components/virt-tree/useDrag.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
findAncestorWithClass,
1515
getPrevSibling,
1616
getNextSibling,
17-
getDragoverPlacement,
1817
} from './utils';
1918

2019
export const useDrag = ({
@@ -92,9 +91,8 @@ export const useDrag = ({
9291
let scrollElementRect: DOMRect | undefined = undefined;
9392
let clientElementRect: DOMRect | undefined = undefined;
9493

95-
const [topPlacement, bottomPlacement] = getDragoverPlacement(
96-
props.dragoverPlacement,
97-
);
94+
let topPlacement = 0.33;
95+
let bottomPlacement = 0.66;
9896

9997
const dragBox = document.createElement('div');
10098
dragBox.classList.add('virt-tree-drag-box');
@@ -253,28 +251,11 @@ export const useDrag = ({
253251
const hoverElement = document.elementFromPoint(mouseX, mouseY);
254252
if (!hoverElement) return;
255253
hoverTreeItem = findAncestorWithClass(hoverElement, 'virt-tree-item');
256-
if (!hoverTreeItem) {
257-
return;
258-
}
259-
254+
if (!hoverTreeItem) return;
260255
const hoverTreeId = hoverTreeItem?.dataset?.id;
261256
if (!hoverTreeId) return;
262257
const hoverTreeNode = getTreeNode(hoverTreeId);
263258
if (!hoverTreeNode) return;
264-
265-
// 操作元素和hover元素不能一致
266-
if (hoverTreeItem === sourceTreeItem) {
267-
// 移除 line
268-
if (hasStyleTreeItem?.contains(dragLine)) {
269-
hasStyleTreeItem?.removeChild(dragLine);
270-
}
271-
// 移除 box
272-
if (hasStyleTreeItem?.contains(dragBox)) {
273-
hasStyleTreeItem?.removeChild(dragBox);
274-
}
275-
dragEffect = false;
276-
return;
277-
}
278259
const hoverTreeItemRect = hoverTreeItem?.getBoundingClientRect();
279260
if (!hoverTreeItemRect) return;
280261

@@ -285,6 +266,15 @@ export const useDrag = ({
285266
// 计算鼠标相对于元素高度的比例
286267
const positionRatio = relativeY / elementHeight;
287268

269+
if (hoverTreeNode.data.disableDragIn) {
270+
// 如果禁止拖入,就不需要中间区域判断了
271+
topPlacement = 0.5;
272+
bottomPlacement = 0.5;
273+
} else {
274+
topPlacement = 0.33;
275+
bottomPlacement = 0.66;
276+
}
277+
288278
if (positionRatio < topPlacement) {
289279
placement = 'top';
290280
} else if (positionRatio > bottomPlacement) {
@@ -312,6 +302,20 @@ export const useDrag = ({
312302
lastPlacement = placement;
313303
lastHoverTreeItem = hoverTreeItem;
314304

305+
// 操作元素和hover元素不能一致
306+
if (hoverTreeItem === sourceTreeItem) {
307+
// 移除 line
308+
if (hasStyleTreeItem?.contains(dragLine)) {
309+
hasStyleTreeItem?.removeChild(dragLine);
310+
}
311+
// 移除 box
312+
if (hasStyleTreeItem?.contains(dragBox)) {
313+
hasStyleTreeItem?.removeChild(dragBox);
314+
}
315+
dragEffect = false;
316+
return;
317+
}
318+
315319
// 一旦发生变化立马清除定时器
316320
if (hoverExpandTimer) {
317321
clearTimeout(hoverExpandTimer);
@@ -322,11 +326,7 @@ export const useDrag = ({
322326
dragEffect = false;
323327
// console.log('鼠标在中部', dragEffect);
324328
// 判断是否能够进入disableDragIn
325-
const id = hoverTreeItem?.dataset?.id;
326-
if (!id) return;
327-
const node = getTreeNode(id);
328-
if (!node) return;
329-
parentNode = node;
329+
parentNode = hoverTreeNode;
330330
prevNode = undefined;
331331
if (hasStyleTreeItem?.contains(dragLine)) {
332332
hasStyleTreeItem?.removeChild(dragLine);
@@ -345,7 +345,7 @@ export const useDrag = ({
345345
// }
346346

347347
// 被禁用
348-
if (node.data?.disableDragIn) return;
348+
if (hoverTreeNode.data?.disableDragIn) return;
349349

350350
// 添加 box
351351
hoverTreeItem?.appendChild(dragBox);
@@ -358,11 +358,11 @@ export const useDrag = ({
358358
clearTimeout(hoverExpandTimer);
359359
hoverExpandTimer = null;
360360
}
361-
const isExpanded = hasExpanded(node);
361+
const isExpanded = hasExpanded(hoverTreeNode);
362362
if (!isExpanded) {
363363
if (!hoverExpandTimer) {
364364
hoverExpandTimer = setTimeout(() => {
365-
expandNode(id, true);
365+
expandNode(hoverTreeId, true);
366366
if (hoverExpandTimer) {
367367
clearTimeout(hoverExpandTimer);
368368
hoverExpandTimer = null;

0 commit comments

Comments
 (0)