手把手教学~基于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>
1
2
// 清除选中
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>
欢迎关注微信公众号:猴哥说前端