功能
- 树形选项展示选择器下拉选项
- 勾选数据筛选被全选的最上层节点,避免全量展示最小节点
- 树形选项多选联动选择器多选标签的展示和清除
- 搜索框能筛选包含此名称的全链路树
- 关闭组件时,收起树形结构并重置搜索框
具体实现
- html结构:在el-select插槽中插入el-input(用于模糊搜索子节点)、el-option(用于映射节点名和节点id)、el-tree(用于展示树形数据)
- 勾选数据只保留被全选的最上层节点:通过绑定el-tree的check事件,每次勾选时,遍历被勾选的节点数组checkedNodes,只保留parentId不存在在checkedKeys数组中的节点
- 树形选项多选联动选择器多选标签:通过在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)
- 搜索框能筛选包含此名称的全链路树:通过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
},
- 收起下拉框时收起树:绑定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>