多选树形选择器自定义组件

492 阅读2分钟

功能

  1. 树形选项展示选择器下拉选项
  2. 勾选数据筛选被全选的最上层节点,避免全量展示最小节点
  3. 树形选项多选联动选择器多选标签的展示和清除
  4. 搜索框能筛选包含此名称的全链路树
  5. 关闭组件时,收起树形结构并重置搜索框

具体实现

  1. html结构:在el-select插槽中插入el-input(用于模糊搜索子节点)、el-option(用于映射节点名和节点id)、el-tree(用于展示树形数据)
  2. 勾选数据只保留被全选的最上层节点:通过绑定el-tree的check事件,每次勾选时,遍历被勾选的节点数组checkedNodes,只保留parentId不存在在checkedKeys数组中的节点

  1. 树形选项多选联动选择器多选标签:通过在el-option绑定label和value确定全量数据的映射关系,通过el-select的v-model双向绑定已选中节点的key数组、绑定remove-tag事件清除节点,通过el-tree的check事件勾选节点。

组件数据输入输出需要注意,使用了自定义v-model实现数据双向绑定,需要通过emit事件修改值,所以不能将selectVal直接绑定在el-select中,el-select需要绑定一个深拷贝值,仅作展示使用

  model: {
    prop: 'selectVal',
    event: 'changeSelectVal'
  },
  props: {
    selectVal: {
      type: Array
    }
  },
    this.$emit('changeSelectVal', temparr)
  1. 搜索框能筛选包含此名称的全链路树:通过watch监听输入框,调用filter方法,筛选全链路树,返回结果(关键在于需要创建字段,保留本链路路径)

watch: {
  // 搜索过滤,监听input搜索框绑定的treeFilter
  treeFilter(val) {
    this.$refs.tree.filter(val)
  },
},
// 筛选节点
filterNode(value, data) {
  if (!value) return true
  const filterRes = data.cnFullName.indexOf(value) !== -1
  return filterRes
},
  1. 收起下拉框时收起树:绑定el-select的visible-change方法,通过遍历this.$refs.tree.store.nodesMap为每个节点的expanded赋值false

所有代码如下:

<template>
  <el-select
    ref="select"
    v-model="selectValClone"
    placeholder="请选择"
    multiple
    :popper-class="isLowLayer ? 'lower-class' : ''"
    @visible-change="collapseNodes"
    @remove-tag="removeTag"
  >
    <el-input
      v-model="treeFilter"
      class="filter-input"
      placeholder="搜索"
      prefix-icon="el-icon-search"
      size="mini"
      clearable
    ></el-input>
    <el-option
      v-for="item in $store.getters.areaListFlat"
      :key="item.areaCode"
      :label="item.cnName"
      :value="item.areaCode"
      style="display: none;"
    />
    <el-tree
      ref="tree"
      node-key="areaCode"
      show-checkbox
      :default-expanded-keys="selectVal"
      :data="treeList"
      :props="defaultProps"
      :expand-on-click-node="false"
      :check-on-click-node="true"
      :filter-node-method="filterNode"
      @check="handleCheck"
    >
    </el-tree>
  </el-select>
</template>

<script>
export default {
  model: {
    prop: 'selectVal',
    event: 'changeSelectVal'
  },
  props: {
    selectVal: {
      type: Array
    },
    isLowLayer: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      treeFilter: '', // 搜索框绑定值,用作过滤
      defaultProps: {
        children: 'children',
        label: 'cnName'
      },
      treeList: [],
      selectValClone: []
    }
  },
  watch: {
    // 搜索过滤,监听input搜索框绑定的treeFilter
    treeFilter(val) {
      this.$refs.tree.filter(val)
    },
    // 重置区域值时,同步重置label
    selectVal(val) {
      this.selectValClone = val
    }
  },
  mounted() {
    this.getArea()
    this.$refs.tree.setCheckedKeys(this.selectVal)
    this.selectValClone = this.selectVal
  },
  methods: {
    removeTag(p1) {
      this.$emit(
        'changeSelectVal',
        this.selectVal.filter(item => item !== p1)
      )
      this.$nextTick(() => {
        this.$refs.tree.setCheckedKeys(this.selectVal)
      })
    },
    // 勾选事件
    handleCheck(checkData, currentList) {
      const temparr = []
      currentList.checkedNodes.forEach(item => {
        // 只保留全选的父节点
        if (!currentList.checkedKeys.includes(item.parentId)) {
          temparr.push(item.areaCode)
        }
      })
      this.$emit('changeSelectVal', temparr)
    },

    // 收起所有节点
    collapseNodes() {
      this.treeFilter = '' // 点击后搜索框清空
      const nodeDatas = this.$refs.tree.store.nodesMap
      for (const key in nodeDatas) {
        nodeDatas[key].expanded = false
      }
    },

    // 筛选节点
    filterNode(value, data) {
      if (!value) return true
      const filterRes = data.cnFullName.indexOf(value) !== -1
      return filterRes
    },

    // 获取地区列表数据
    async getArea() {
      const hasAreaList = this.$store.getters.areaList && this.$store.getters.areaList.length > 0
      if (hasAreaList) {
        this.treeList = this.$store.getters.areaList
      } else {
        await this.$store.dispatch('area/loadAreaList')
        this.treeList = this.$store.getters.areaList
      }
    }
  }
}
</script>

<style lang="scss">
.el-select-dropdown__list {
  padding-left: 10px !important;
  padding-right: 10px !important;
}
.el-select {
  width: 100%;
}
.lower-class {
  z-index: 0 !important;
}
</style>