el-tree使用记录,el-tree横向滚动条,只有最后一级可选,筛选功能。

200 阅读4分钟

1 场景及需求

考试选择考生,使用el-tree树组件展示学校的组织结构,点击班级时请求班级学生进行选择,要求树结构有过滤功能,且只有最后一级可选中,其它结构鼠标移入鼠标样式变为不可操作,最终实现结果如下图:

1.jpg

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这个属性。

2.png 看到有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...

3.png

一不做,二不休,我的解决方案是为每个节点设置一个绝对定位的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过长的时候,不会出现横向滚动条。

4.jpg

解决的方法也很简单,通过控制台调试,可以很容易的找到.el-tree-node这个节点,这里我们直接样式穿透,设置display即可解决:

:deep(.el-tree-node) {
  display: table;
  min-width: 100%;
}

5.png

5 实现只有最后一节点可点击的最终方案

至此,这就是我实现需求的全部步骤了,当我第二天温度降下来的时候我又去认真的读了文档,于是就发现的最终实现方案只需要一个方法!再结合监听点击实现我们的需求。

6.jpg

<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),所以,通过这次经历可见,不能先入为主,不同组件库的使用方法还是不一样的,写之前一定要认真读文档!