手把手教学~基于element封装tree树状下拉框

在日常项目开发中,树状下拉框的需求还是比较常见的,但是element并没有这种组件以供使用。在这里,小编就基于element如何封装一个树状下拉框做个详细的介绍。

通过这篇文章,你可以了解学习到一个树状下拉框组件是如何一步一步封装成功的。

话不多说,先看效果图:

封装组件

该组件主要基于element的select组件、tree组件及input组件进行二次封装的。

组件布局

首先我们需要基于这几个组件对我们的组件进行布局,话不多说直接上代码:

<template>
  <el-select ref="select">

    <el-option class="options">
      <el-tree  id="tree-option"
        ref="selectTree"
      >
      </el-tree>
    </el-option>
  </el-select>
</template>

<style scoped>
  .el-scrollbar .el-scrollbar__view .el-select-dropdown__item{
    height: auto;
    max-height: 274px;
    padding: 0;
    overflow: hidden;
    overflow-y: auto;
  }
  .el-select-dropdown__item.selected{
    font-weight: normal;
  }
  ul li >>>.el-tree .el-tree-node__content{
    height:auto;
    padding: 0 20px;
  }
  .el-tree-node__label{
    font-weight: normal;
  }
  .el-tree >>>.is-current .el-tree-node__label{
    color: #409EFF;
    font-weight: 700;
  }
  .el-tree >>>.is-current .el-tree-node__children .el-tree-node__label{
    color:#606266;
    font-weight: normal;
  }
</style>

注:css添加scoped属性,是为了让css只在该组件生效,避免样式污染

这个时候直接使用肯定是会报错的,因为我们组件该传的参数还未传递。

组件数据完善

上面我们已经完成了布局,接下来就是为其丰富数据了,因为我们这个组件肯定是复用的,因此我们设计数据的时候,需要把常用的数据属性提取出来通过props传递接收。我提取的主要有几下几个数据:

props:{
    /* 配置项 */
    props:{
      type: Object,
      default:()=>{
        return {
          value:'id',             // ID字段名
          label: 'title',         // 显示名称
          children: 'children'    // 子级字段名
        }
      }
    },
    /* 选项列表数据(树形结构的对象数组) */
    options:{
      type: Array,       
      default: ()=>{ return [] }
    },
    /* 初始值 */
    value:{
      default: ()=>{ return null }
    },
    /* 可清空选项 */
    clearable:{
      type:Boolean,
      default:()=>{ return true }
    },
    /* 自动收起 */
    accordion:{
      type:Boolean,
      default:()=>{ return false }
    },
    placeholder:{
      type:String,
      default:()=>{return "请选择"}
    }
  },

大家可能注意到,我所有prop字段都给了type属性,唯独value没有,这是因为在实际使用中下拉框的数据value值可能是字符串(String)也可能是数字(Number),为了项目开发中控制台不报太多无意义的错,此处就没有规定其type。

接收到prop之后,我们就开始对组件进行数据的处理,直接上代码:

<template>
  <el-select  :placeholder="placeholder" ref="select">

    <el-option class="options">
      <el-tree  id="tree-option"
        ref="selectTree"
        :accordion="accordion"
        :data="options"
        :props="props"
        :node-key="props.value"
        :default-expanded-keys="[]"
      >
      </el-tree>
    </el-option>
  </el-select>
</template>

当数据过多的时候,滚动条会出现两条,如下所示:

处理方法如下:

// 初始化滚动条
initScroll(){
  this.$nextTick(()=>{
    let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap')[0]
    let scrollBar = document.querySelectorAll('.el-scrollbar .el-scrollbar__bar')
    scrollWrap.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;'
    scrollBar.forEach(ele => ele.style.width = 0)
  })
},

mounted中调用该方法就可以了,效果如下:

点击选中

数据也渲染显示出来了,这个时候我们需要实现点击数据选中功能。

思路很简单:

  • select组件绑定value值
  • tree组件绑定节点点击事件
  • 点击事件中获取value和label
  • 将获取的值赋给select组件以及返回给父组件

代码如下:

<template>
  <el-select :value="valueTitle" :placeholder="placeholder" ref="select">

    <el-option :value="valueTitle" :label="valueTitle" class="options">
      <el-tree  id="tree-option"
        ref="selectTree"
        :accordion="accordion"
        :data="options"
        :props="props"
        :node-key="props.value"
        :default-expanded-keys="defaultExpandedKey"
        @node-click="handleNodeClick"
        >
      </el-tree>
    </el-option>
  </el-select>
</template>
data() {
    return {
      valueId:this.value,// 初始值
      valueTitle:'',
      defaultExpandedKey:[]
    }
},
// 切换选项
handleNodeClick(node){
  this.valueTitle = node[this.props.label]//获取label
  this.valueId = node[this.props.value]//获取value
  this.$emit('getValue',this.valueId)//传值给父组件
},

这样点击选中功能就实现了,但是有个问题,点击之后,下拉框选项没有隐藏,我们只需要再调用一下select组件的blur方法即可实现隐藏

数据初始化

细心的小伙伴肯定已经发现了,上面有一个初始值,并且在选择器中,初始数据也是必不可少的。实现思路如下:

  • watch监听prop中value数据变化
  • 将初始值做对应赋值
  • 获取初始值对应的label并做对应赋值
  • 设置tree组件的默认选中状态
  • 设置tree组件的默认展开节点

代码如下:

watch: {
    value(){
      this.valueId = this.value
      this.initHandle()
    }
},
// 初始化值
initHandle(){
  if(this.valueId){
    // 初始化显示label
    this.valueTitle = this.$refs.selectTree.getNode(this.valueId).data[this.props.label]     
    this.$refs.selectTree.setCurrentKey(this.valueId)// 设置默认选中
    this.defaultExpandedKey = [this.valueId]// 设置默认展开
  } 
},

mounted中调用执行既可

清除选中

一般输入框或者选择器都有清除功能,我们的组件自然也少不了清除功能,实现思路如下:

  • 给select组件设置clearable属性
  • 给select组件添加清除监听事件
  • 在监听事件中清除tree组件选中,并清除父组件中的值

代码如下:

<el-select :value="valueTitle" :clearable="clearable" @clear="clearHandle" :placeholder="placeholder" ref="select">
</el-select>
// 清除选中
clearHandle(){
  this.valueTitle = ''
  this.valueId = null
  this.defaultExpandedKey = []
  this.clearSelected()
  this.$emit('getValue',null)
},
/* 清空选中样式 */
clearSelected(){
  let allNode = document.querySelectorAll('#tree-option .el-tree-node')
  allNode.forEach((element)=>element.classList.remove('is-current'))
},

筛选数据

当tree中数据量过大时,我们需要筛选数据,实现思路如下:

  • 给tree组件添加filter-node-method方法
  • 添加一个输入框,输入筛选的内容
  • 监听输入内容变化,并调用tree组件的筛选方法

代码如下:

<template>
  <el-select :value="valueTitle" :clearable="clearable" @clear="clearHandle" :placeholder="placeholder" ref="select">
    <el-input
      class="selectInput"
      placeholder="检索关键字"
      v-model="filterText">
    </el-input>

    <el-option :value="valueTitle" :label="valueTitle" class="options">
      <el-tree  id="tree-option"
        ref="selectTree"
        :accordion="accordion"
        :data="options"
        :props="props"
        :node-key="props.value"    
        :default-expanded-keys="defaultExpandedKey"
        :filter-node-method="filterNode"
        @node-click="handleNodeClick">
      </el-tree>
    </el-option>
  </el-select>
</template>

.selectInput{
    padding: 0 5px;
    box-sizing: border-box;
}
filterNode(value, data) {
  if (!value) return true;
  return data.name.indexOf(value) !== -1;
}
watch: {
    filterText(val) {
      this.$refs.selectTree.filter(val);
    }
},

这样一个简单的树状下拉框组件就封装好了。

使用组件

下面给个简单的使用示例:

<template>
  <basic-container>
    <treeSelect
      :props="defaultProps"
      :options="treeData"
      :value="value"
      :accordion="true"
      @getValue="getValue($event)"
      placeholder="请选择所属区域"
    />
    <span>选中的id:{{value}}</span>
  </basic-container>
</template>
<script>
import treeSelect from "@/components/treeSelect/treeSelect";
export default {
  components: {
    treeSelect,
  },
  data() {
    return {
      defaultProps: {
        label: "name",
        value: "id",
        children: "children",
      },
      value:'',//选中的数据
      treeData:[
          {id:1,name:'monkey',children:[{id:2,name:'monkey2'},{id:3,name:'monkey3'},{id:4,name:'monkey4'}]},
          {id:5,name:'小猴子的web成长之路'},
          {id:6,name:'小猴子的web成长之路'},
          {id:7,name:'小猴子的web成长之路'},
          {id:8,name:'小猴子的web成长之路'},
          {id:9,name:'小猴子的web成长之路'},
          {id:10,name:'小猴子的web成长之路'},
          {id:11,name:'小猴子的web成长之路'},
          {id:12,name:'小猴子的web成长之路'},
          {id:13,name:'小猴子的web成长之路'},
          {id:14,name:'小猴子的web成长之路'},
          {id:15,name:'小猴子的web成长之路'},
          {id:16,name:'小猴子的web成长之路'},
          {id:17,name:'小猴子的web成长之路'},
      ]
    };
  },
  methods:{
      // 取值
    getValue(value) {
      this.value = value
    },
  }
};
</script>

源码收录在公众号【小猴子的web成长之路】,关注公众号回复关键字【treeSelect】即可获取

posted @ 2020-08-10 15:33  monkeySoft  阅读(649)  评论(0编辑  收藏  举报