feat(用户管理): 优化部门树搜索与样式

- 将搜索输入与刷新按钮组合为紧凑布局,提升操作便捷性
- 实现部门树实时搜索过滤,高亮显示匹配的节点文本
- 调整选中节点的背景色为更柔和的悬停色
- 修复树节点标题渲染插槽名称错误
This commit is contained in:
dap
2026-01-28 15:11:33 +08:00
parent 703cdf4125
commit 1f6de0ec29

View File

@@ -3,10 +3,12 @@ import type { PropType } from 'vue';
import type { DeptTree } from '#/api/system/user/model'; import type { DeptTree } from '#/api/system/user/model';
import { onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { listToTree, treeToList } from '@vben/utils';
import { SyncOutlined } from '@antdv-next/icons'; import { SyncOutlined } from '@antdv-next/icons';
import { Empty, InputSearch, Skeleton, Tree } from 'antdv-next'; import { Empty, Input, Skeleton, SpaceCompact, Tree } from 'antdv-next';
import { getDeptTree } from '#/api/system/user'; import { getDeptTree } from '#/api/system/user';
@@ -66,6 +68,23 @@ async function loadTree() {
showTreeSkeleton.value = false; showTreeSkeleton.value = false;
} }
const deptTreeComputed = computed(() => {
if (!searchValue.value) {
return deptTreeArray.value;
}
const toTree = treeToList(deptTreeArray.value, {
id: 'id',
pid: 'parentId',
});
const filteredTree = toTree.filter((item: DeptTree) =>
item.label.toUpperCase().includes(searchValue.value.toUpperCase()),
);
return listToTree(filteredTree, {
id: 'id',
pid: 'parentId',
});
});
async function handleReload() { async function handleReload() {
await loadTree(); await loadTree();
emit('reload'); emit('reload');
@@ -90,33 +109,38 @@ onMounted(loadTree);
v-if="showSearch" v-if="showSearch"
class="sticky left-0 top-0 z-100 bg-background p-[8px]" class="sticky left-0 top-0 z-100 bg-background p-[8px]"
> >
<InputSearch <SpaceCompact class="w-full">
v-model:value="searchValue" <Input
:placeholder="$t('pages.common.search')" v-model:value="searchValue"
size="small" :placeholder="$t('pages.common.search')"
allow-clear size="small"
> allow-clear
<template #enterButton> />
<a-button @click="handleReload"> <a-button size="small" @click="handleReload">
<SyncOutlined class="text-primary" /> <SyncOutlined class="text-primary" />
</a-button> </a-button>
</template> </SpaceCompact>
</InputSearch>
</div> </div>
<div class="h-full overflow-x-hidden px-[8px]"> <div class="h-full overflow-x-hidden px-[8px]">
<Tree <Tree
v-bind="$attrs" v-bind="$attrs"
v-if="deptTreeArray.length > 0" v-if="deptTreeComputed.length > 0"
v-model:selected-keys="selectDeptId" v-model:selected-keys="selectDeptId"
:class="$attrs.class" :class="$attrs.class"
:field-names="{ title: 'label', key: 'id' }" :field-names="{ title: 'label', key: 'id' }"
:show-line="{ showLeafIcon: false }" :show-line="{ showLeafIcon: false }"
:tree-data="deptTreeArray" :tree-data="deptTreeComputed"
:virtual="false" :virtual="false"
default-expand-all default-expand-all
@select="$emit('select')" @select="$emit('select')"
:styles="{
item: {
'--ant-tree-node-selected-bg':
'var(--ant-color-primary-bg-hover)',
},
}"
> >
<template #title="{ label }"> <template #titleRender="{ label }">
<span v-if="label.includes(searchValue)"> <span v-if="label.includes(searchValue)">
{{ label.substring(0, label.indexOf(searchValue)) }} {{ label.substring(0, label.indexOf(searchValue)) }}
<span class="text-primary">{{ searchValue }}</span> <span class="text-primary">{{ searchValue }}</span>