1 场景及需求
考试选择考生,使用el-tree树组件展示学校的组织结构,点击班级时请求班级学生进行选择,要求树结构有过滤功能,且只有最后一级可选中,其它结构鼠标移入鼠标样式变为不可操作,最终实现结果如下图:
el-tree的基本使用非常简单,Tree 树形控件 | Element Plus参照基础用发粘贴代码即可。
2 实现树的过滤并高亮显示
针对过滤,官方也提供了属性filter-node-method,它接收一个函数,底层会遍历树结构的每个节点并注入两个参数,value和data分别表述调用过滤方法时传入的值和当前遍历到的节点,它允许我们定义树的过滤规则。所以我们只需要定义一个filterText并绑定给输入框,监听这个值变化,并调用过滤的方法,这样就能实现最基础的过滤功能了。
<template>
<el-input v-model="filterText" placeholder="请输入" />
<el-tree :data="treeData" node-key="id" ref="treeRef" :filter-node-method="filterNode" />
</template>
<script setup>
// 树数据
const treeData = ref()
// 树结构过滤
const filterText = ref('')
const treeRef = ref()
// 定义过滤规则
const filterNode = (value, data) => {
if (!value) return true
return data.label.indexOf(value) !== -1
}
watch(filterText, (val) => treeRef.value.filter(val))
</script>
官方允许我们通过插槽的形式定义树节点内容的显示,所以我们只需要匹配搜索的关键字为其添加样式,再使用v-html进行渲染即可实现搜索高亮效果。
<el-tree :data="treeData" node-key="id" ref="treeRef" :filter-node-method="filterNode">
<template #default="{ node, data }">
<div
v-html="data.label.replace(new RegExp(filterText, 'g'), `<span style='color:orange'>${filterText}</span>`)">
</div>
</template>
</el-tree>
3 实现只有最后一节点可点击
这段过程可谓十分曲折,刚开始因为用惯了ant design的树组件,做到这块的时候想也没想就按照ant design的方法去做了(递归遍历树的数据为其非底层节点添加disabled)。
const setTreeDataAttr = (data) => {
let dataCopy = JSON.parse(JSON.stringify(data))
// 递归添加层级及状态
const traverse = (data, level) => {
data.forEach(item => {
// 添加层级,方便后续使用
item.level = level
if (item?.children) {
item.disabled = true
traverse(item.children, level + 1)
}else{
// 标记为最后一层节点
item.isEndNode = true
}
})
}
traverse(dataCopy, 1)
return dataCopy
}
做完之后去看结果,发现每层都能点击和选中,并没有达到预期的结果,这是内心还是比较平静的,想到两个组件库的实现方法不一样也正常,于是又去浅浅的翻了下文档,就注意到了props这个属性。
看到有disabled这个值,于是我又一次进行尝试...
<template>
<el-tree :data="treeData" node-key="id" ref="treeRef" :filter-node-method="filterNode" :props="treeProps">
<template #default="{ node, data }">
<div
v-html="data.label.replace(new RegExp(filterText, 'g'), `<span style='color:orange'>${filterText}</span>`)">
</div>
</template>
</el-tree>
</template>
<script setup>
const treeProps = {
disabled: (data) => {
return data.isEndNode ? false : true
}
}
</script>
做完这些后这次很有底气的去页面查看效果,结果,还是不行!这时感觉有点汗流浃背的感觉了,于是在百度的过程中发现,这个是如果开启show-checkbox后控制这个节点前面的checkbox是否可选中的😵。到这个时候开始怀疑人生了,于是干脆袖子一撸,你没有我自己来做!(这里并不是没有,而是博主没有认证文档没有发现正确的打开方式😭)。思路是这样的,判断当前节点的disabled是否为true,如果为true的话单独使用一套样式.disabled
设置鼠标的样式cursor: not-allowed;
于是代码就开始向*山的方向发展了....
<el-tree :data="treeData" node-key="id" ref="treeRef" :filter-node-method="filterNode">
<template #default="{ node, data }">
<!-- 禁止点击 -->
<div v-if="data.disabled" @click.stop class="disabled">
<div style="cursor: not-allowed;"
v-html="data.label.replace(new RegExp(filterText, 'g'), `<span style='color:orange'>${filterText}</span>`)"
v-if="filterText"></div>
<div v-else style="cursor: not-allowed;">{{ data.label }}</div>
</div>
<div v-else>
<div
v-html="data.label.replace(new RegExp(filterText, 'g'), `<span style='color:orange'>${filterText}</span>`)"
v-if="filterText"></div>
<div v-else>{{ data.label }}</div>
</div>
</template>
</el-tree>
虽然代码难看点,但最终效果还是完成了(我以为的完成了),但是我们所设置的display:none和@click.stop只是针对文本,当我们的鼠标放到最左侧的绿色区域仍然可以选中,这是因为el-tree组件从第二层级开始为每层追加18px的padding...
一不做,二不休,我的解决方案是为每个节点设置一个绝对定位的dom元素,宽度根据层级动态设置,并设置left为0把最左侧的区域挡住,于是代码就成了这样:
<el-tree :data="treeData" node-key="id" ref="treeRef" :filter-node-method="filterNode">
<template #default="{ node, data }">
<!-- 禁止点击 -->
<!-- 根据层级在右侧生成不可点击的元素占位 -->
<div style="position: absolute;top: 0;height: 100%;cursor: not-allowed" :style="{ left: `-${(data.level - 1) * 18 + 24}px`, width: `${(data.level - 1) * 18}px` }">
</div>
<div v-if="data.disabled" @click.stop class="disabled">
<div style="cursor: not-allowed;"
v-html="data.label.replace(new RegExp(filterText, 'g'), `<span style='color:orange'>${filterText}</span>`)"
v-if="filterText"></div>
<div v-else style="cursor: not-allowed;">{{ data.label }}</div>
</div>
<div v-else>
<div
v-html="data.label.replace(new RegExp(filterText, 'g'), `<span style='color:orange'>${filterText}</span>`)"
v-if="filterText"></div>
<div v-else>{{ data.label }}</div>
</div>
</template>
</el-tree>
4 横向滚动条
到这里,按照我的预期,选中和不可选中的功能就全部完成了,但是没想到还有一个坑...那就是当我们的label过长的时候,不会出现横向滚动条。
解决的方法也很简单,通过控制台调试,可以很容易的找到.el-tree-node这个节点,这里我们直接样式穿透,设置display即可解决:
:deep(.el-tree-node) {
display: table;
min-width: 100%;
}
5 实现只有最后一节点可点击的最终方案
至此,这就是我实现需求的全部步骤了,当我第二天温度降下来的时候我又去认真的读了文档,于是就发现的最终实现方案只需要一个方法!再结合监听点击实现我们的需求。
<el-input v-model="filterText" style="width: 100%;height: 40px;margin-bottom: 10px;" placeholder="请输入" />
<el-tree :data="treeData" class="tree" node-key="id" ref="treeRef" :filter-node-method="filterNode"
highlight-current @node-click="handleNodeClick" default-expand-all :props="treeProps">
</el-tree>
// 树节点点击
const handleNodeClick = (data) => {
// 如果点击的不是最顶层的叶子节点,则清空当前选中节点
if (data.chilidren) {
return treeRef.value.setCurrentKey(null)
}
// 下面是选择最后一节点的处理逻辑
const key = treeRef.value.getCurrentKey() // 获取选择的最顶层叶子节点
}
除了鼠标移入没有cursor不可选中效果外,实现的效果和上面是一样的(想要这个效果也可以直接加css),所以,通过这次经历可见,不能先入为主,不同组件库的使用方法还是不一样的,写之前一定要认真读文档!