Skip to content

Commit 5cdb4c4

Browse files
perf: tree filter
1 parent 4ae5bda commit 5cdb4c4

2 files changed

Lines changed: 107 additions & 35 deletions

File tree

ui/src/components/folder-virtualized-tree/VirtualizedTree.vue

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
virtualization
55
:defaultOpen="false"
66
v-bind="$attrs"
7+
:model-value="filteredTreeData"
78
class="maxkb-virtualized-tree"
89
@click:node="handleNodeClick"
910
@after-drop="onAfterDrop"
@@ -33,14 +34,24 @@
3334
</template>
3435

3536
<script lang="ts" setup>
36-
import { ref, watch, nextTick } from 'vue'
37+
import { ref, computed, nextTick } from 'vue'
3738
import { Draggable, dragContext } from '@he-tree/vue'
3839
import '@he-tree/vue/style/default.css'
3940
const props = defineProps({
41+
modelValue: {
42+
type: Array,
43+
default: () => [],
44+
},
4045
currentNodeKey: {
4146
type: String,
4247
default: 'default',
4348
},
49+
filterNodeMethod: {
50+
type: Function,
51+
default: (node: any, filterText: string) => {
52+
return node.name?.toLowerCase().includes(filterText.toLowerCase())
53+
},
54+
},
4455
})
4556
4657
type DraggableInstance = InstanceType<typeof Draggable>
@@ -105,25 +116,83 @@ function onAfterDrop() {
105116
}
106117
const statHandler = (stat: any) => {
107118
stat.open = stat.level === 1
119+
if (filterText.value) {
120+
stat.open = true
121+
}
108122
return stat
109123
}
124+
125+
// 过滤文本
126+
const filterText = ref('')
127+
/**
128+
* 递归过滤树
129+
* @param nodes 节点数组
130+
* @param text 过滤文本
131+
* @returns 过滤后的新树(新对象,但节点内的基本属性保持原引用)
132+
*/
133+
const filterTree = (nodes: any[], text: string): any[] => {
134+
if (!text || !props.filterNodeMethod) {
135+
return nodes
136+
}
137+
138+
const result: any[] = []
139+
for (const node of nodes) {
140+
const isMatch = props.filterNodeMethod(node, text)
141+
let filteredChildren: any[] = []
142+
if (node.children && node.children.length) {
143+
filteredChildren = filterTree(node.children, text)
144+
}
145+
if (isMatch || filteredChildren.length) {
146+
// 创建新节点对象,保留原有属性,替换 children
147+
result.push({
148+
...node,
149+
children: filteredChildren,
150+
})
151+
}
152+
}
153+
return result
154+
}
155+
156+
// 计算过滤后的树数据
157+
const filteredTreeData = computed(() => {
158+
return filterTree(props.modelValue, filterText.value)
159+
})
160+
161+
// 暴露过滤方法给父组件
162+
const filter = (text: string) => {
163+
filterText.value = text
164+
}
165+
166+
defineExpose({
167+
filter,
168+
})
110169
</script>
111170

112171
<style lang="scss">
113172
.maxkb-virtualized-tree {
114-
overflow: overlay !important;
173+
overflow: auto !important;
115174
scrollbar-gutter: stable;
116-
117-
::-webkit-scrollbar-thumb {
118-
background-color: rgba(0, 0, 0, 0.2);
175+
// 滚动条
176+
::-webkit-scrollbar {
177+
width: 5px;
178+
height: 5px;
179+
-webkit-border-radius: 5px;
180+
-moz-border-radius: 5px;
119181
border-radius: 5px;
182+
background-color: transparent;
183+
}
184+
::-webkit-scrollbar-thumb {
120185
transition: all 0.2s ease-in-out;
121-
122-
&:hover {
123-
cursor: pointer;
124-
background-color: rgba(0, 0, 0, 0.3);
125-
}
186+
background-color: transparent;
187+
background-clip: padding-box;
188+
-webkit-border-radius: 5px;
189+
-moz-border-radius: 5px;
190+
border-radius: 5px;
126191
}
192+
&:hover::-webkit-scrollbar-thumb {
193+
background-color: rgba(0, 0, 0, 0.1);
194+
}
195+
127196
.tree-arrow-icon {
128197
color: var(--app-text-color-secondary);
129198
padding: 6px;
@@ -153,4 +222,10 @@ const statHandler = (stat: any) => {
153222
}
154223
}
155224
}
225+
.he-tree-drag-placeholder {
226+
background-color: var(--el-color-primary-light-9);
227+
border: 2px dashed var(--el-color-primary);
228+
border-radius: 4px;
229+
width: 98%;
230+
}
156231
</style>

ui/src/components/folder-virtualized-tree/index.vue

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
</div>
5252
</div>
5353
<VirtualizedTree
54+
ref="treeRef"
5455
class="folder-tree__main"
5556
v-model="sortedData"
5657
:class="
@@ -59,12 +60,10 @@
5960
@node-drop="handleDrop"
6061
@handleNodeClick="handleNodeClick"
6162
:current-node-key="currentNodeKey"
63+
:filter-node-method="filterNode"
6264
>
6365
<template #default="{ node, stat }">
64-
<div
65-
@mouseenter.stop="handleMouseEnter(node)"
66-
class="flex align-center w-full custom-tree-node"
67-
>
66+
<div @mouseenter.stop="handleMouseEnter(node)" class="flex align-center custom-tree-node">
6867
<AppIcon iconName="app-folder" style="font-size: 20px"></AppIcon>
6968
<span class="tree-label ml-8 lighter" :title="node.name">{{ i18n_name(node.name) }}</span>
7069
<div
@@ -232,6 +231,24 @@ const MoreFilledPermission = (node: any) => {
232231
233232
const emit = defineEmits(['handleNodeClick', 'refreshTree'])
234233
234+
const treeRef = ref()
235+
const filterText = ref('')
236+
const hoverNodeId = ref<string | undefined>('')
237+
const title = ref('')
238+
const loading = ref(false)
239+
240+
watch(filterText, (val) => {
241+
let v = val
242+
if (val) {
243+
v = val.trim()
244+
}
245+
treeRef.value!.filter(v)
246+
})
247+
const filterNode = (data: any, value: string) => {
248+
if (!value) return true
249+
return data.name.toLowerCase().includes(value.toLowerCase())
250+
}
251+
235252
const handleDrop = (draggingNode: any, dropNode: any, dropType: string) => {
236253
const dragData = draggingNode.data
237254
const dropData = dropNode.data
@@ -375,11 +392,6 @@ function getSortContext(positions: Record<string, number>, nodeId: string) {
375392
376393
return { dropPos, sortedNodes, dropIndex }
377394
}
378-
const treeRef = ref()
379-
const filterText = ref('')
380-
const hoverNodeId = ref<string | undefined>('')
381-
const title = ref('')
382-
const loading = ref(false)
383395
384396
const isDropdownOpen = ref(false)
385397
let time: any
@@ -687,26 +699,11 @@ onUnmounted(() => {})
687699
height: calc(100vh - 180px);
688700
}
689701
:deep(.folder-tree__main) {
690-
padding: 4px 0 0 8px;
702+
padding: 4px 2px 0 8px;
691703
.custom-tree-node {
692704
box-sizing: content-box;
693705
position: relative;
694706
}
695707
}
696-
697-
// :deep(.folder-tree__main) {
698-
// .el-tree-node.is-dragging {
699-
// opacity: 0.5;
700-
// }
701-
// .el-tree-node.is-drop-inner > .el-tree-node__content {
702-
// background-color: var(--el-color-primary-light-9);
703-
// border: 2px dashed var(--el-color-primary);
704-
// border-radius: 4px;
705-
// }
706-
707-
// .el-tree-node__children {
708-
// overflow: inherit !important;
709-
// }
710-
// }
711708
}
712709
</style>

0 commit comments

Comments
 (0)