基于 Element UI 的高性能树形下拉选择组件实现

2,044 阅读2分钟

大家好呀~ 我是一个普通的前端菜鸡,最近在做后台管理系统时,遇到了组织架构选择的需求。虽然 Element UI 提供了 Tree 和 Select 组件,但要把它们完美结合还是有点小挑战的。经过一番折腾,总算搞出了一个勉强能用的树形选择器,今天就跟大家分享一下这个"不太完美但还能凑合"的实现方案 😅

效果如下:

1735802586377.png

选中时,居中回显

1735802600401.png

支持下拉搜索

1735802614722.png

功能特点

  • 支持树形数据展示

  • 支持搜索过滤

  • 支持清空选择

  • 自动定位到选中节点

  • 支持懒加载

  • 优化的滚动性能

  • 1. 组件结构

组件采用 el-select + el-tree 的嵌套结构:

<el-select
    v-model="selectValue"
    placeholder="请选择组织"
    filterable
    clearable
    style="width: 100%;"
    @change="handleChange"
    :filter-method="filterOrg"
    @clear="handleClear"
    @visible-change="handleVisibleChange"
    :no-data-text="loading ? '加载中...' : '暂无数据'"
  >
    <el-option :value="selectValue" style="height: auto;padding: 0;border: 0;">
      <div v-loading="loading" class="tree-container" ref="treeContainer">
        <el-tree
          ref="orgTree"
          :data="options"
          :props="{
            children: 'children',
            label: 'fullName'
          }"
          node-key="fullName"
          highlight-current
          :filter-node-method="filterNode"
          @node-click="handleNodeClick"
          :current-node-key="selectValue"
          default-expand-all
        >
        </el-tree>
      </div>
    </el-option>
  </el-select>

2. 数据加载

async getOrgList() {
  this.loading = true
  try {
    const res = await getOrganizeSelector()
    this.options = res.data.list
  } catch (error) {
    this.$message.error('获取组织列表失败')
  } finally {
    this.loading = false
  }
}

3. 搜索过滤实现

filterNode(value, data) {
  if (!value) return true
  return data.fullName.toLowerCase().indexOf(value.toLowerCase()) !== -1
}

filterOrg(val) {
  this.$refs.orgTree.filter(val || '')
  return true
}

4. 自动定位功能

handleVisibleChange(visible) {
  if (visible && this.selectValue) {
    setTimeout(() => {
      const node = this.$refs.orgTree.getNode(this.selectValue)
      if (node) {
        // 展开所有父节点
        let parent = node.parent
        while (parent && parent.level > 0) {
          parent.expanded = true
          parent = parent.parent
        }
        // 滚动到选中节点
        this.$nextTick(() => {
          const element = this.$refs.treeContainer.querySelector(
            '.el-tree-node.is-current > .el-tree-node__content'
          )
          if (element) {
            element.scrollIntoView({ block: 'center' })
          }
        })
      }
    }, 100)
  }
}

样式优化

.tree-container {
  height: 240px;
  overflow-y: auto;
  background-color: #fff;
}

::v-deep {
  .el-tree-node__content {
    height: 32px;
    font-weight: normal;
  }

  .el-tree-node.is-current > .el-tree-node__content {
    background-color: #f5f7fa;
    color: #409EFF;
  }
}

使用方法

<template>
  <tree-select 
    v-model="selectedOrg"
    @select="handleOrgSelect"
    @clear="handleOrgClear"
  />
</template>

<script>
export default {
  data() {
    return {
      selectedOrg: ''
    }
  },
  methods: {
    handleOrgSelect(data) {
      console.log('选中组织:', data)
    },
    handleOrgClear() {
      console.log('清空选择')
    }
  }
}
</script>

注意事项

  • 组件初始化时需要处理 value 值的同步

  • 清空选择时需要同时处理 tree 和 select 的状态

  • 搜索过滤时需要考虑大小写敏感性

  • 自动定位功能需要考虑异步加载的情况