element-ui 组件使用之封装树形下拉选择组件
需求分析
- 一、实现类似
el-select
下拉框与收缩功能 - 二、展开结构为树形组件, 支持懒加载数据和完整树形结构数据
- 三、支持数据双向绑定,数据静态搜索,限制选择等
实现 el-select
采用 el-input
模拟 select
组件, 并添加图标
<template>
<div slot="reference" class="popover-input">
<el-input placeholder="请选择" :value="inputValue" size="mini" readonly />
<i class="el-icon-arrow-down input-icon" :class="{ 'input-focus': popoverShow }" />
</div>
</template>
<script>
export default {
data() {
return {
inputValue: '',
popoverShow: false,
}
}
}
</script>
<style lang="scss" scoped>
.popover-input{
position: relative;
display: inline-block;
.input-icon{
position: absolute;
top: 6px;
right: 6px;
color: #C0C4CC;
transition: transform .3s;
}
.input-focus{
transform: rotate(-180deg)
}
}
</style>
复制代码
通过变量 popoverShow
控制图标方向
添加 popover
组件
<!--selectTree -->
<template>
<el-popover
v-model="popoverShow"
placement="bottom"
width="240"
trigger="click"
>
<div slot="reference" class="popover-input">
<el-input placeholder="请选择" :value="inputValue" size="mini" readonly />
<i class="el-icon-arrow-down input-icon" :class="{ 'input-focus': popoverShow }" />
</div>
</el-popover>
</template>
复制代码
当 popover 展开图标向下, popover 收缩图标向上
添加 tree
组件
<template>
<div class="tree">
<div class="tree-search">
<el-input v-model="filterText" clearable size="mini" placeholder="输入关键字进行过滤" />
</div>
<el-tree
ref="tree"
/>
</div>
</template>
<script>
export default {
data() {
return {
filterText: ''
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val)
}
},
}
</script>
<style lang="scss" scoped>
.tree{
height: 100%;
background-color: #ffffff;
.tree-search{
height: 40px;
line-height: 40px;
}
.tree-node{
max-height: 300px;
overflow-y: scroll;
// 修复无法横向滚动问题
::v-deep>.el-tree-node{
min-width:100%;
display: inline-block;
}
}
}
</style>
复制代码
树形组件配置详解
参数 | 说明 | 类型 |
---|---|---|
data | 展示数据 | array |
load | 加载子树数据的方法,仅当 lazy 属性为true 时生效 | function(node, resolve) |
props | 属性名称配置选项(label, children, disabled, isLeaf) | object |
lazy | 是否懒加载子节点,需与 load 方法结合使用 | boolean |
node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的, 固定为 id | String |
show-checkbox | 节点是否可被选择 | boolean |
check-strictly | 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法 | boolean |
expand-on-click-node | 是否在点击节点的时候展开或者收缩节点 | boolean |
filter-node-method | 对树节点进行筛选时执行的方法 | Function(value, data, node) |
highlight-current | 是否高亮当前选中节点 | boolean |
树组件添加配置
<template>
<div class="tree">
<div class="tree-search">
<el-input v-model="filterText" clearable size="mini" placeholder="输入关键字进行过滤" />
</div>
<el-tree
ref="tree"
:load="loadNode"
:props="defaultProps"
lazy
node-key="id"
show-checkbox
check-strictly
expand-on-click-node
:filter-node-method="filterNode"
highlight-current
/>
</div>
</template>
<script>
export default {
props: {
loadNode: Function,
defaultProps: {
type: Object,
default() {
return {
children: 'children',
label: 'label',
disabled: 'disabled'
}
}
},
},
data() {
return {
filterText: ''
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val)
}
},
methods: {
filterNode(value, data) {
if (!value) return true
return data[this.defaultProps.label].includes(value)
},
}
}
</script>
复制代码
之后就是把封装好的树形组件放入 selectTree
组件了
模拟数据
参照官网我们模拟一个获取数据的方法, 为了便于使用, 每条数据都加上 id
let index = 0
loadNode(node, resolve) {
if (node.level === 0) {
return resolve([{ name: 'region', id: index++ }]);
}
if (node.level > 1) return resolve([]);
setTimeout(() => {
const data = [{
name: 'leaf',
leaf: true,
id: index++
}, {
name: 'zone',
id: index++
}];
resolve(data);
}, 500);
}
复制代码
父组件调用
<template>
<select-tree v-model="value" :load-node="loadNode" :default-props="defaultProps" />
</template>
<script>
import selectTree from './components/selectTree'
let index = 0
export default {
components: {
selectTree
},
data() {
return {
value: [],
defaultProps: {
label: 'name',
isLeaf: 'leaf'
}
}
},
methods: {
loadNode(node, resolve) {
if (node.level === 0) {
return resolve([{ name: 'region', id: index++ }])
}
if (node.level > 1) return resolve([])
setTimeout(() => {
const data = [{
name: 'leaf',
leaf: true,
id: index++
}, {
name: 'zone',
id: index++
}]
resolve(data)
}, 500)
}
}
}
</script>
复制代码
目前我们已经可以点击输入框展开树组件了, 但是数据绑定还没有,下面我们就开始实现的数据的双向绑定
树组件事件监听
树组件需要监听的事件有两个
node-click
树节点点击事件, 如果该节点可选, 那么点击后选中, 也可以配置expand-on-click-node
让节点点击后展开树层check
当复选框被点击的时候触发, 当调用内容方法时不会触发该事件
当这两个事件其中一个触发时, 我们选中值就应该发生改变, 故定义一个数据变动方法(暂时先做单选)
首先我们先要定义 v-model
中 value
保存的值是什么,因为考虑都后续需要回写数据,故我们需要获取选中数据的所有层级数据, 那么 value
值应该为一个数组,并且该数组应该是其所有祖先元素并包含自己
例如:['0', '0-1', '0-1-1']
表示当前选中的数据 ID 为 0-1-1
其父级元素 ID 为 0-1
, 其父级的父级为最高级, 并且 ID 为 0
由于 value
数据需要保存所有父级节点的信息, 但是其触发的两个事件的入参都只是当前节点信息, 故我们需要封装一个获取其祖先元素集合的方法
查看源码我们可以发现在 element-ui
el-tree
组件中实例中保存了一个所有节点信息的对象 nodesMap
,因此我们可以通过该对象遍历拿到选中节点所有父级元素集合
// 获取数据缓存
getNodesMap() {
return this.$refs.tree.store.nodesMap
},
/**
* 根据 id 获取所有父级
*/
getParentID(id) {
const nodesMap = this.getNodesMap()
const parentId = []
let node = nodesMap[id]
while (node.parent) {
parentId.unshift(node.data.id)
node = node.parent
}
return parentId
}
复制代码
多选我们暂时先不做, 先控制单选, 也就是选择后如果选中数据与较之前一致那么我们就反选值, 同时我们需要设置一下选中值的文本内容
而 element-ui
为我们提供了切换选择的方法
setCheckedKeys(keys) {
this.$refs.tree.setCheckedKeys(keys)
}
复制代码
function selectChange(data) {
const alTreeData = this.$refs.alTree
const id = data.id
let selectValue = alTreeData.getParentID(id)
if (selectValue === this.value) {
selectValue = []
this.inputValue = ''
} else {
const { label } = alTreeData.getNode(id)
this.inputValue = label
}
alTreeData.setCheckedKeys([id])
this.$emit('value:change', selectValue)
}
复制代码
现在选择数据双向绑定已经实现, 那么接下来实现数据回写