vue3自定义实现可过滤关键字的树形下拉选择框

背景

最近项目中有一个部门选择需求,一开始是用element-plus的级联下拉写的,但是由于层级过深,会出现级联下拉超出屏幕的情况,所以改用树形下拉,但是element没有相关组件,现记录下vue3+js自定义实现可以根据关键字筛选的树形下拉选择框。

实现效果

在这里插入图片描述

代码

<template>
  <el-popover popper-class="tree-select-popper el-select-dropdown" :width="width" trigger="click">
    <template #reference>
      <div
        ref="selectRef"
        class="tree-select el-select"
        :class="[elFormltemSize ? 'el-select--' + elFormltemSize : '']"
      >
        <overlay-scrollbars class="tree-select-input">
          <div :class="{ 'is-placeholder': !treeSelectText }" class="input-content">
            {
   
   { treeSelectText || placeholder }}
          </div>
        </overlay-scrollbars>
      </div>
    </template>
    <div class="tree-select_wrapper">
      <div class="tree-search_input">
        <el-input v-model="treeFilterText" class="" placeholder="筛选" />
      </div>
      <overlay-scrollbars class="tree-scroll">
        <div class="select-tree-box">
          <el-tree
            ref="selectTree"
            :node-key="nodeKey"
            :current-node-key="modelValue"
            :data="data"
            :props="treeProps"
            :expand-on-click-node="false"
            class="select-tree"
            :filter-node-method="filterTreeNode"
            @node-click="handleClickTreeNode"
          />
        </div>
      </overlay-scrollbars>
    </div>
  </el-popover>
</template>
<script setup>
  import {
      
       computed, inject, nextTick, ref, watch } from 'vue';

  import {
      
       useElementSize } from '@vueuse/core';
  const props = defineProps({
      
      
    modelValue: {
      
      
      type: String,
      required: true,
    },
    size: String,
    data: {
      
      
      type: Array,
      default: () => [],
    },
    props: {
      
      
      type: Object,
      default: () => ({
      
      }),
    },
    placeholder: String,
    searchPlaceholder: String,
    nodeKey: {
      
      
      type: String,
      default: 'id',
    },
  });
  const emits = defineEmits(['update:modelValue']);
  const treeProps = computed(() => ({
      
      
    label: 'label',
    id: 'id',
    children: 'children',
    ...props.props,
  }));
  const elFormltem = inject('elFormltem');
  const elFormltemSize = computed(() => elFormltem?.size); //大小

  //选中的label
  const treeSelectText = computed(() => {
      
      
    let text = '';
    const getTreeSelectData = (arr, id, textArr) => {
      
      
      for (const item of arr) {
      
      
        if (item[treeProps.value.id] === id) {
      
      
          text = [...textArr, item[treeProps.value.label]].join('/');
          return;
        }
        if (!item[treeProps.value.children] || !item[treeProps.value.children]?.length) continue;
        getTreeSelectData(item[treeProps.value.children], id, [
          ...textArr,
          item[treeProps.value.label],
        ]);
      }
    };
    getTreeSelectData(props.data, props.modelValue, []);
    return text || props.modelValue;
  });
  //选中某个节点
  const handleClickTreeNode = (node) => {
      
      
    emits('update:modelValue', node[treeProps.value.id]);
  };
  const selectTree = ref(null);
  const treeFilterText = ref(''); //过滤树文字
  //节点过滤
  const filterTreeNode = (value, data) => {
      
      
    if (!value) return true;
    return data.label.includes(value);
  };
  watch(treeFilterText, (val) => selectTree.value?.filter(val));
  const selectRef = ref(null);
  const {
      
       width } = useElementSize(selectRef);
  const expandParents = (node) => {
      
      
    if (!node) return;
    node.expanded = true;
    expandParents(node?.parent);
  };
  //选中节点初始化
  watch(
    () => props.modelValue,
    (value) => {
      
      
      if (!value) return;
      nextTick(() => {
      
      
        selectTree.value?.setCurrentKey(value);
        const node = selectTree.value?.getNode(value);
        expandParents(node?.parent);
      });
    },
    {
      
       immediate: true, flush: 'post' },
  );
</script>
<style scoped lang="scss">
  .tree-select {
      
      
    width: 100%;
    .tree-select-input {
      
      
      width: 100%;
      color: #c0c4cc;
      border-radius: 4px;
      border: 1px solid #8a99b0;
      .os-content {
      
      
        .input-content {
      
      
          line-height: 32px;
          height: 32px !important;
          overflow: visible;
          white-space: nowrap;
          padding: 0 11px !important;
          display: inline-block;
          color: #606266;
          font-size: 14px;
          &.is-placeholder {
      
      
            color: #7f8da5;
          }
        }
      }
    }
  }
  .tree-select_wrapper {
      
      
    max-height: 271px;
    display: flex;
    flex-direction: column;
    .tree-search_input {
      
      
      flex: 0 0 auto;
      padding: 10px;
    }
    .tree-scroll {
      
      
      max-height: 271px;
      flex: 1;
      margin-top: 10px;
      .select-tree-box {
      
      
        padding: 0 10px 10px;
      }
      :v-deep(.select-tree) {
      
      
        .el-tree-node_children {
      
      
          overflow: visible !important;
        }
        .el-tree-node {
      
      
          &.is-current > .el-tree-node_content {
      
      
            color: $common;
          }
        }
      }
    }
  }
</style>
<style>
  .tree-select-popper {
      
      
    padding: O;
  }
</style>

猜你喜欢

转载自blog.csdn.net/qq_45840993/article/details/130014284