事情是这样的,最近项目新增一个需求是:对行政区域进行关键词检索。但是由于行政区比较多,一次性渲染数据会导致组件崩掉,因此上一个前端是通过展开树节点逐级查询。Fine。
但是el-tree-select组件提供的filter-method或filter-node-method检索方法对懒加载方式无用,它是针对已有节点的检索,即使重新赋值data也不起作用。因此需要自己造轮子实现需求。
技术栈:Vue(3.5.12)、TypeScript(5.1.6)、Element Plus(2.8.8)、UnoCSS(0.55.2)
实现步骤
-
template模板
主要是渲染输入框+树控件,并且添加一些交互细节。
<template>
<div class="select-wrap" @click="togglePanel" v-click-outside="handleClose">
<el-input class="w-[100%]" v-model="queryKeywold" placeholder="请选择行政区域" @input="updateTreeData">
<template #suffix>
<SvgIcon
:class="{ 'rotate-180': visible }"
name="arrow-bottom"
class="text-14 transition-transform transition-duration-0.3s transition-ease-in-out color-[#a8abb2]"
></SvgIcon>
</template>
</el-input>
<div :class="{ 'is-focus': visible }" class="select-wrap-pop pop-with-border" @click.stop>
<div class="pop-group-wrap" v-loading="loading">
<el-tree
ref="treeSelectRef"
accordion
check-strictly
lazy
:load="load"
:props="treeProps"
:node-key="nodeKey"
:data="treeData"
:model-value="modelValue"
placeholder="请选择行政区域"
class="area-tree-select w-100%"
:class="[subList.length && 'first-pointer-events-none']"
:default-expanded-keys="expandedKeys"
:render-after-expand="false"
:expand-on-click-node="false"
:teleported="false"
@node-click="nodeClick"
@change="handler"
>
<template #default="{ data }">
<div class="tree-node position-relative">
<span :class="{ 'color-#172a88': data.value === modelValue, 'cursor-not-allowed': data.disabled }">{{
data.label
}}</span>
</div>
</template>
</el-tree>
</div>
</div>
</div>
</template>
-
TypeScript脚本
没啥好说的,就是普通写。
/**是否展示下拉面板 */
const visible = ref<boolean>(false)
/** 是否加载中 */
const loading = ref<boolean>(false)
/** 树数据 */
const treeData = ref<dataType[]>([])
/** 当前选中节点 */
const currentNode = ref<dataType>({})
/** 关键词检索 */
const queryKeywold = ref('')
/** 临时检索关键词 -- 加载下一层级节点用 */
const tempKeyword = ref('')
/** 关键词过滤数据 */
const updateTreeData = debounce(value => {
visible.value = true
loading.value = true
tempKeyword.value = value
// 接口请求,组装数据...
}, 300)
/** 切换面板显隐 */
const togglePanel = () => {
visible.value = !visible.value
visible.value && updateTreeData('')
}
/** 关闭面板 */
const handleClose = () => {
visible.value = false
queryKeywold.value = currentNode.value.label
}
-
CSS样式
CSS最主要的就是还原输入框右侧icon的动画、以及下拉框面板的样式。(中间小三角也还原了(得意))
.select-wrap {
width: 360px;
height: 36px;
max-height: 200px;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 4px;
color: var(--el-text-color-regular);
position: relative;
.select-wrap-pop {
display: none;
top: calc(100% + 4px);
left: 0;
right: 0;
height: 200px;
position: absolute;
margin-top: 10px;
z-index: 1000;
transform: translateZ(0);
transition: 0.3s;
.pop-group-wrap {
width: 100%;
height: 100%;
max-height: 200px;
overflow-x: hidden;
overflow-y: auto;
padding: 10px 0;
position: relative;
.active {
color: var(--el-color-primary) !important;
}
}
/* 滚动条整体 */
.pop-group-wrap::-webkit-scrollbar {
width: 8px;
}
/* 滚动条滑块 */
.pop-group-wrap::-webkit-scrollbar-thumb {
border-radius: 4px;
}
/* 滚动条轨道 */
.pop-group-wrap::-webkit-scrollbar-track {
border-radius: 5px;
}
&.is-focus {
display: block;
}
}
}
小三角形部分:
.pop-with-border {
background: #fff;
position: relative;
border-radius: 4px;
box-shadow: 0px 0px 10px rgb(0 0 0 / 12%);
&::before,
&::after {
top: -6px;
border: 6px solid transparent;
border-top: 0;
border-bottom-color: #fff;
content: '';
display: block;
width: 0;
height: 0;
left: calc(50% - 6px);
overflow: hidden;
position: absolute;
z-index: 101;
}
&::before {
top: -7px;
border-bottom-color: #ddd;
z-index: 99;
}
}
实现效果
编辑
细节注意
- 点击整个元素外部时,需要收起面板,并且将输入框的值重置成已选节点的label。
- 如果已选择节点,更改输入框内容,再进行下一节点加载时,关键词需要用临时值。
以上就是树形控件懒加载+检索的实现啦^-^