!275 fix 树状菜单渲染从tree调整为tree-v2,提升大数据结构下渲染速度

Merge pull request !275 from Nottyjay/d3code-feat-treepanel
This commit is contained in:
疯狂的狮子Li 2026-06-25 08:19:23 +00:00 committed by Gitee
commit 5f8d919f03
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F

View File

@ -19,33 +19,38 @@
</template> </template>
<template v-if="!modelCollapsed"> <template v-if="!modelCollapsed">
<el-input v-model="filterText" :placeholder="placeholder" prefix-icon="Search" clearable /> <el-input v-model="filterText" :placeholder="placeholder" prefix-icon="Search" clearable />
<el-tree <div ref="treeWrapRef" class="dept-tree-wrap">
ref="treeRef" <el-tree-v2
class="mt-2 dept-tree" ref="treeRef"
:node-key="nodeKey" class="dept-tree"
:data="data" :data="treeData"
:props="mergedTreeProps as any" :props="mergedTreeProps as any"
:expand-on-click-node="false" :height="treeHeight"
:filter-node-method="internalFilterNode" :item-size="28"
highlight-current :expand-on-click-node="false"
default-expand-all :default-expanded-keys="defaultExpandedKeys"
@node-click="onNodeClick" :filter-method="internalFilterNode"
> highlight-current
<template #default="{ data: nodeData }"> @node-click="onNodeClick"
<span class="tree-node-label" :class="{ 'is-disabled': isNodeDisabled(nodeData) }"> >
<span>{{ getNodeLabel(nodeData) }}</span> <template #default="{ node }">
<el-tooltip v-if="isNodeDisabled(nodeData)" content="停用" placement="top"> <span class="tree-node-label" :class="{ 'is-disabled': isNodeDisabled(node.data) }">
<el-icon class="tree-node-disabled-icon"><CircleCloseFilled /></el-icon> <span>{{ node.label }}</span>
</el-tooltip> <el-tooltip v-if="isNodeDisabled(node.data)" content="停用" placement="top">
</span> <el-icon class="tree-node-disabled-icon"><CircleCloseFilled /></el-icon>
</template> </el-tooltip>
</el-tree> </span>
</template>
</el-tree-v2>
</div>
</template> </template>
</el-card> </el-card>
</el-col> </el-col>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { TreeKey, TreeV2Instance } from 'element-plus';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
/** 面板标题 */ /** 面板标题 */
@ -91,23 +96,34 @@ const modelCollapsed = computed({
}); });
const filterText = ref(''); const filterText = ref('');
const treeRef = ref<ElTreeInstance>(); const treeRef = ref<TreeV2Instance>();
const treeWrapRef = ref<HTMLElement>();
const treeData = shallowRef<any[]>([]);
const defaultExpandedKeys = shallowRef<TreeKey[]>([]);
const { height: treeWrapHeight } = useElementSize(treeWrapRef);
const treeHeight = computed(() => Math.max(Math.floor(treeWrapHeight.value), 180));
const mergedTreeProps = computed(() => { const mergedTreeProps = computed(() => {
const label = props.treeProps?.label ?? 'label';
const children = props.treeProps?.children ?? 'children';
const value = props.treeProps?.value ?? props.nodeKey;
if (!props.disabledField) { if (!props.disabledField) {
return props.treeProps; return {
...props.treeProps,
value,
label,
children
};
} }
return { return {
...props.treeProps, ...props.treeProps,
value,
label,
children,
disabled: props.treeProps?.disabled ?? props.disabledField disabled: props.treeProps?.disabled ?? props.disabledField
}; };
}); });
const getNodeLabel = (data: any) => {
const labelField = props.treeProps?.label ?? 'label';
return data?.[labelField] ?? '';
};
const isNodeDisabled = (data: any) => { const isNodeDisabled = (data: any) => {
if (!props.disabledField) { if (!props.disabledField) {
return false; return false;
@ -119,6 +135,23 @@ const toggleCollapsed = () => {
modelCollapsed.value = !modelCollapsed.value; modelCollapsed.value = !modelCollapsed.value;
}; };
const getNodeKey = (data: any) => data?.[props.nodeKey] as TreeKey | undefined;
const collectExpandedKeys = (nodes: any[], keys: TreeKey[] = []) => {
const childrenField = props.treeProps?.children ?? 'children';
for (const node of nodes) {
const children = node?.[childrenField];
if (Array.isArray(children) && children.length > 0) {
const key = getNodeKey(node);
if (key !== undefined) {
keys.push(key);
}
collectExpandedKeys(children, keys);
}
}
return keys;
};
const internalFilterNode = (value: string, data: any) => { const internalFilterNode = (value: string, data: any) => {
if (props.filterNodeMethod) { if (props.filterNodeMethod) {
return props.filterNodeMethod(value, data); return props.filterNodeMethod(value, data);
@ -127,16 +160,32 @@ const internalFilterNode = (value: string, data: any) => {
return data[props.filterField]?.indexOf(value) !== -1; return data[props.filterField]?.indexOf(value) !== -1;
}; };
watchEffect( const filterTree = useDebounceFn(() => {
() => { treeRef.value?.filter(filterText.value);
treeRef.value?.filter(filterText.value); }, 200);
watch(filterText, () => filterTree());
watch(
() => props.data,
(data) => {
const rawData = toRaw(data);
treeData.value = rawData;
defaultExpandedKeys.value = collectExpandedKeys(rawData);
nextTick(() => {
treeRef.value?.setData(rawData);
treeRef.value?.setExpandedKeys(defaultExpandedKeys.value);
if (filterText.value) {
filterTree();
}
});
}, },
{ flush: 'post' } { immediate: true }
); );
const onNodeClick = (data: any, node: any, component: any) => { const onNodeClick = (data: any, node: any, component: any) => {
if (isNodeDisabled(data)) { if (isNodeDisabled(data)) {
treeRef.value?.setCurrentKey(undefined); treeRef.value?.setCurrentKey(undefined as any);
return; return;
} }
emit('node-click', data, node, component); emit('node-click', data, node, component);
@ -224,14 +273,17 @@ $mobile-breakpoint: 900px;
transform: rotate(-45deg); transform: rotate(-45deg);
} }
.dept-tree { .dept-tree-wrap {
padding-top: 6px; margin-top: 8px;
flex: 1 1 auto; flex: 1 1 auto;
min-height: 180px; min-height: 180px;
max-height: 100%; max-height: 100%;
min-width: 0;
}
.dept-tree {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding-right: 4px;
scrollbar-width: thin; scrollbar-width: thin;
} }