组件Element-UI 2实现树形下拉选择框--详解(含代码&示例)
笔者由于项目原因需要用element-ui 2实现此效果(如下所示)。本文根据Element-UI 2的el-select和el-tree实现树形下拉选择框的效果,适用于想实现效果但项目组件版本未升级的情形,小白也能看懂!(源码在最后-->)
本文主要参考了下面这位大佬的代码,并在其基础上提出了自己的一些见解:
下面笔者将从组件开始介绍。对组件了解的朋友可以点击标题跳转至代码部分。
1.组件介绍
顾名思义,树形下拉选择框需要用到Tree控件和Select选择器。
1.1 Select选择器
官方文档:组件 | Select选择器
Select选择器实现的是下拉的效果,由el-select组件包裹着el-option。el-select的主要熟悉为v-model,el-option的主要属性为v-for和:'value'。
<el-select v-model="value">
<el-option
v-for="item in cities"
:value="item.value">
</el-option>
</el-select>
select的v-model属性实现选择器所选节点的显示以及动态绑定的效果。
option的:value属性绑定的是选项的值,v-for则表示该选项的值从数组中循环(通常数据是以包裹对象的数组的形式传入前端)。
1.2 Tree树形控件
官方文档:组件 | Tree树形控件
el-tree实现的是树形菜单的效果,其最主要的三个属性分别是data,props属性和node-click事件
<el-tree
:data="data"
:props="defaultProps"
@node-click="handleNodeClick">
</el-tree>
其中,data负责数据的绑定(数据通常是树形数组的形式)。
props负责数据的传递(引用网上的一句解释:“父组件通过 props 向下传递数据给子组件;子组件通过 events 给父组件发送消息”)。
而node-click则负责点击节点时的操作(就是click事件套了层皮而已)。
好了,现在你已经了解组件的基本用法了,试着实现树形下拉框的效果吧!
2.代码详解
实现目标效果的方法是select和tree的嵌套,细分下来可有两种方式,分别是option与tree平级(option 1)和option嵌套tree(option 2)。但这两种方式的基本思想是相通的。
<!-- option 1 -->
<el-select>
<el-option></el-option>
<el-tree/>
</el-select>
<!-- option 2 -->
<el-select>
<el-option>
<el-tree/>
</el-option>
</el-select>
2.1 option 1
此方法也正是参考文章的博主所采用的方法。
(1)数据处理
示例数据为树形数组。(一般而来说需要自己将后端返回的数据转换为树形数组,示例为了方便省略了这一步,后面会介绍平面数组与树形数组相互转换的方法)
cityData: [{
id: 1,
label: '重庆',
children: [{
id: 2,
label: '渝北区'
}]
}, {...},{...}]
在script模块data()中定义数据。
(2)模板构造
a. el-selelct
绑定v-model="selectValue",selectValue将显示所选节点的值。还需要绑定响应式属性selectTree来实现用户点击不同节点的响应式效果。
b. el-option
el-option是下拉框的选项,每项的属性由v-for循环数组(即方法optionData()的返回值,其中返回值是平面数组)绑定。选项的名称为数组元素的label,值为数组元素的value。
c. el-tree
作为目标效果的主体,el-tree需要绑定cityData数据,与之配套的treeProps属性、selectTree响应式属性和handleNodeClick事件。至于:expand-on-click-node="expandOnClickNode",则表示是否只有点击箭头列表才收缩;default-expand-all表示默认展开所有节点。
效果如下图所示。因为采用的是option与tree平级的方法,所以上半部分是下拉框,下半部分是树形目录。
这个时候只需要给el-option添加display:none样式即可实现目标效果。
(3)操作绑定
a. 树形数组转平面数组
在2.1>(2)>b的el-option中,需要用v-for给该组件绑定数据,而我们的数据是树形的,因此需要将树形数组转换为平面数组。参考文章采用的方法仅仅适用于4级数组,也就是最多只能有4级菜单,而且代码冗杂、有很多重复的地方。于是笔者采用迭代的方法实现此目的。
optionData方法的内容很简单,就是迭代加循环。方法有两个参数,一是源数组,二是存放结果的空数组。对源数组进行forEach遍历循环:将当前元素push进结果数组。其中结果数组的label是源数组的label,value是源数组的id。然后再对当前元素进行判断:如果其有children节点,则再次执行optionData方法,而参数则是该元素的children节点和第一次迭代的result。这样直到最后一个元素判断后,将返回result。
至于为何要用JSON.parse(JSON.stringify())的方式,是为了防止出现__ob__:observe的而造成无限循环的情况。通过数据深拷贝后,可以去掉数组的__ob__:observe属性。具体可以参考下面这篇文章,里面提到了更多解决方法。
“__ob__: Observer
是 Vue 对数据监控添加的属性。当数据中包含这个属性时,数据是不可枚举、不可遍历的。这里深拷贝的数据应该是我们最终用于赋值的数据,而不是接收到接口的返回值。”
最终的结果如下图所示:
b. 绑定节点事件
在2.1>(2)>c中绑定了事件handleNodeClick。点击节点时,el-select的selectValue属性为当前节点的label值,同时el-tree也会响应式失去焦点。
我们可以通过console.log检验是否正确。从下面的输出来看是无误的。
小结一下:
1.由于el-select必须与el-option配套使用,所以可以通过设置display:none的样式隐藏多余的选项;
2.为el-option绑定数据时,因为要用到v-for,因此需要将树形数组转换为平面数组。而为了降低代码的冗余度,故采用迭代的思想来实现效果;
3.vue会为数据增添observe的属性来监听变化,使用深拷贝的方式可以有效去掉,但要注意需要对使用的最终数据进行拷贝而不是返回值。
2.2 option 2
在option 1中,我们对el-option添加了display:none样式来隐藏不必要的选项、还将数组进行了转换,是否有些多此一举了?能否果断一点直接把el-tree包裹在el-option里?因此,笔者尝试用option 2实现此构思。
(1)数据处理
因为不给el-option绑定数据,因此不必对数据做过多的处理。
(2)模板构造
el-select和el-tree的构造与option 1并无大异,主要将el-option的属性全去掉了。
直接Ctrl+s,效果如下所示,然后报错了。不急,我们一个一个解决。
a. 解决样式的问题
首先是下拉框的样式错误。在树形菜单左右两侧都有灰色的背景,并且菜单也没撑满下拉框。我们只需要添加样式让树形菜单撑满即能解决此问题。
效果如下所示,我们成功解决了!
b. 解决控制台的报错
由报错信息可知,我们好像缺少一个value的属性。这是因为在使用element-ui时,el-select未绑定v-model或el-option未进行value赋值。显而易见是后者造成的原因。因此只需要添加一个小小的value:' ',即可解决报错。
大功告成!吧唧吧唧吧唧。
小结两下:
1. 也可以el-tree嵌套在el-option,这样有两个好处:一是更易于理解,毕竟是将下拉的树形菜单;二是减少没有必要的代码,包括数组的转换,数据的绑定;
2. 造成Missing required prop: “value”的原因通常由是el-select或el-tree造成。
3. Element-UI 3新增组件实现
是的,element-ui 3中新增了el-tree-select组件来实现树形下拉框。
官方文档:组件 | TreeSelect 树形选择
因为此组件是由el-tree和el-select结合而来,并未修改原有属性,故组件的属性和事件也是两者结合。
4. 源码分享
<template>
<div class="app-container">
<el-select
class="main-select-tree"
ref="selectTree"
v-model="selectValue"
style="width: 300px;"
name='option 1'>
<!-- otion 1 -->
<el-option
v-for="item in optionData(cityData)"
:label="item.label"
:value="item.value"
style="display: none;"/>
<el-tree
class="main-select-el-tree"
ref="selectelTree"
:data="cityData"
:props='treeProps'
highlight-current
@node-click="handleNodeClick"
:expand-on-click-node="expandOnClickNode"
default-expand-all />
<!-- option 2 -->
<!-- <el-option style="height: 100%; padding: 0;" value="">
<el-tree
class="main-select-el-tree"
ref="selectelTree"
:data="cityData"
:props='treeProps'
@node-click="handleNodeClick"
:expand-on-click-node="expandOnClickNode"
highlight-current
default-expand-all
style="font-weight: normal;"/>
</el-option> -->
</el-select>
</div>
</template>
<script>
export default {
data() {
return {
selectValue: '',
expandOnClickNode: true,
options: [],
treeProps: {
children: 'children',
label: 'label'
},
cityData: [{
id: 1,
label: '重庆',
children: [{
id: 2,
label: '渝北区'
}]
}, {
id: 3,
label: '北京',
children: [
{ id: 4, label: '海淀区' },
{ id: 5, label: '朝阳区' }
]
}, {
id: 6,
label: '四川',
children: [
{
id: 7,
label: '成都',
children: [
{ id: '8', label: '成华区' }
]
}
]
}]
}
},
methods: {
/**
* 树形转平面的迭代方法
* option 1的el-option需要此方法绑定数据
*/
optionData(array, result=[]) {
array.forEach(item => {
result.push({label:item.label,value:item.id})
if (item.children && item.children.length !== 0) {
this.optionData(item.children, result)
}
})
return JSON.parse(JSON.stringify(result))
},
// 点击节点的响应
handleNodeClick(node) {
this.selectValue = node.label;
this.$refs.selectTree.blur();
console.log(node.label);
}
}
}
</script>
<style>
.main-select-el-tree .el-tree-node .is-current>.el-tree-node__content {
font-weight: bold;
color: #409eff;
}
.main-select-el-tree .el-tree-node.is-current>.el-tree-node__content {
font-weight: bold;
color: #409eff;
}
</style>
因为笔者刚开始接触前端,如果文章中有错误恳请指正。如果这篇文章帮到你,不妨点个小心心支持一下笔者,阿里嘎多~
2023.6.1 更新
关于扁平数组与数据数组转换的方法已更新,感兴趣的朋友可以看我另一篇文章💖
更多推荐
所有评论(0)