vue3 Element-UI封装实现TreeSelect 支持搜索功能

前些天实现了一个树结构的下拉组件,可最近又让我支持下拉搜索功能,查了一圈没有找到合适的,只好今天自己写了一下,凑合能用吧,支持搜索功能了最起码,可有些参数还是要配置的

<template>
  <div>
    <el-popover
      v-model:visible="popVisible"
      placement="bottom"
      :width="popoverWidth"
      trigger="click"
    >
      <template #reference>
        <el-input
          v-model="filterText"
          v-bind="$attrs"
          :placeholder="placeholder"
          @blur="handleBlur"
          @focus="handleFocus"
        >
          <template #suffix>
            <div class="suffix" @click="handleIcon">
              <i :class="`el-icon-arrow-${popVisible ? 'up' : 'down'}`" />
            </div>
          </template>
        </el-input>
      </template>
      <el-tree
        ref="tree"
        class="filter-tree"
        :data="options"
        :props="defaultProps"
        default-expand-all
        :filter-node-method="filterNode"
        @node-click="handleNodeClick"
      />
    </el-popover>
  </div>
</template>

<script>
import { defineComponent, watch, onMounted, ref, computed } from "vue";

export default defineComponent({
  props: {
    popoverWidth: {
      type: Number,
      default: 400,
    },
    modelValue: {
      type: String,
      default: "",
    },
    options: {
      type: Array,
      default: () => [],
    },
  },
  emits: ["update:modelValue", "selected"],
  setup(props, context) {
    const defaultProps = ref({
      children: "children",
      label: "label",
    });

    const popVisible = ref(false);
    function handleIcon() {
      popVisible.value = !popVisible.value;
    }

    const preText = ref("");
    const placeholder = computed(() =>
      preText.value ? preText.value : "select"
    );

    function filterNode(value, data) {
      if (!value) return true;
      return data.label.indexOf(value) !== -1;
    }

    function findLabel(arr, target) {
      let res = target;
      function find(arr) {
        for (let i = 0; i < arr.length; i += 1) {
          if (arr[i].value === target) {
            res = arr[i].label;
            return;
          }
          if (arr[i].children && arr[i].children.length) {
            find(arr[i].children, target);
          }
        }
      }
      find(arr);
      return res;
    }

    const filterText = ref("");
    function handleNodeClick(node) {
      popVisible.value = false;
      filterText.value = node.label;
      preText.value = node.label;
      context.emit("selected", node);
      context.emit("update:modelValue", node.value);
    }
    function handleBlur() {
      setTimeout(() => {
        filterText.value = preText.value;
      }, 100);
    }
    function handleFocus() {
      preText.value = filterText.value;
      filterText.value = "";
    }
    watch(
      () => props.modelValue,
      () => {
        filterText.value = findLabel(props.options, props.modelValue);
      }
    );
    const tree = ref();
    watch(
      () => filterText.value,
      (val) => {
        tree.value.filter(val);
      }
    );

    onMounted(() => {
      filterText.value = findLabel(props.options, props.modelValue);
    });
    return {
      tree,
      placeholder,
      defaultProps,
      filterText,
      popVisible,
      preText,
      handleNodeClick,
      handleBlur,
      handleFocus,
      filterNode,
      handleIcon,
    };
  },
});
</script>
<style scoped>
.suffix {
  cursor: pointer;
  display: flex;
  width: 100%;
  height: 100%;
  align-items: center;
}
</style>
复制代码

使用起来比之前那一篇麻烦了一丢丢,相对还是好用的 且看,

<treeSelect
      style="width: 226px"
      :popoverWidth="200"
      v-model="selectData"
      :options="options"
      @selected="handleSelect"
    ></treeSelect>
   
复制代码

在父组件里我们组要把对应要修改的value传进去,options对应树结构的array模样,不过一定要有value的,或者读者可以自己再稍微改改,emit出来的参数。 关于el-popover的宽度和高度都没有做设置,不适合过多的数据,需要的话就自己加个div限制一下,或者需要的人多的话,我再把它优化下。。

const selectData = ref("");
    const options = ref([
      {
        label: "选项1",
        value: "1",
        children: [{ label: "选项1-1", value: "1-1" }],
      },
      { label: "选项2", value: "2" },
    ]);
    function handleSelect(node) {
      console.log(node);
    }
复制代码

有哪里需要优化的提一下,持续改进哈。

分类:
前端
标签: