主要实现el-select下使用树结构,支持回显和筛选功能
el-select+el-tree 实现回显+搜索
数据量大时推荐使用 el-select+vl-tree 封装的组件避免页面卡顿
vl-tree 简介 | Virtual Tree
封装的组件 el-select+el-tree 实现回显+搜索
composeTree.vue
<template>
<el-select :popper-class="popperClass"
v-model="selectedList"
placeholder="请选择"
filterable
:filter-method="handleFilter"
multiple
:collapse-tags="collapseTags"
size="mini"
@visible-change="handleSelectVisibleChange"
@remove-tag="removeTag"
>
<el-tree :filter-node-method="filterNode" show-checkbox ref="tree"
@check-change="handleCheckChange"
:data="treeList"
:node-key="nodeKey"
:props="props">
<template slot-scope="{ node, data }">
<slot :node="node" :data="data">
<span class="custom-tree-node">
{{data[props.label]}}
</span>
</slot>
</template>
</el-tree>
<el-option value="" style="display: none;"></el-option>
</el-select>
</template>
<script>
export default {
props: {
props: {
type: Object,
default: () => {
return {
children: 'children',
label: 'label',
value: 'value',
}
}
},
nodeKey: {
type: String,
default: 'value'
},
collapseTags: {
type: Boolean,
default: false
},
popperClass: {
type: String,
default: ''
},
selectedIdList: {
type: Array,
default: () => []
},
treeList: {
type: Array,
default: () => []
},
feekback: {
type: Boolean,
default: false
}
},
model: {
prop: 'selectedIdList',//选中的数组
event: 'updateSelectedIdList'
},
watch: {
feekback: {
handler(val) {
if (val) {
this.$nextTick(() => {
this.$refs['tree'].setCheckedKeys(this.selectedIdList, true);
})
}
},
immediate: true
},
},
data() {
return {
list: [],
searchVal: '',
noFilterTreeNode: false,//是否过滤树节点
selectedList: [],
// checkedNodeList:[],
}
},
created() {
},
methods: {
//筛选
handleFilter(val) {
if (this.noFilterTreeNode) return;
this.searchVal = val;
this.$refs['tree']?.filter(val);
},
filterNode(value, data, node) {
if (!value) return true
return data[this.props.label].toLowerCase().indexOf(value.toLowerCase()) !== -1
},
removeTag(val) {
this.watchCheckChangeRun = false;
let obj = this.checkedNodeList.find(item => item[this.props.label] === val);
this.$refs.tree.setChecked(obj[this.props.value], false);
setTimeout(() => {//由于首次回显不对tree有任何操作的时候,直接移除标签,不能触发tree的change事件,所以添加个手动调用
if (!this.watchCheckChangeRun) {
console.log('手动调用handleCheckChange======');
this.handleCheckChange();
}
},1)
},
handleSelectVisibleChange(val) {
this.noFilterTreeNode = false;
if (!val) {//select框隐藏时,重置树结构
if (this.searchVal) {
this.handleFilter('')
}
}
},
//树选择变化
handleCheckChange() {
this.watchCheckChangeRun = true;
let checkList=this.$refs['tree'].getCheckedNodes(true);
this.selectedList = checkList.map(item => item[this.props.label]);
this.$emit('updateSelectedIdList', checkList.map(item => item[this.props.value]));
this.checkedNodeList = checkList;
this.noFilterTreeNode = true;//避免vl-tree筛选问题
},
}
}
</script>
largeAmountTree.vue
使用el-select+vl-tree 实现大数据量时的 回显+搜索
vl-tree地址 简介 | Virtual Tree
<template>
<!-- 插件demo地址 https://sangtian152.github.io/virtual-tree/zh/demo/ -->
<el-select
:popper-class="'treeScrollSep '+popperClass"
v-model="selectedList"
placeholder="请选择"
filterable
:filter-method="handleFilter"
multiple
:collapse-tags="collapseTags"
size="mini"
@visible-change="handleSelectVisibleChange"
@remove-tag="removeTag"
>
<vl-tree :filter-method="filterNode" show-checkbox ref="tree"
@check-change="handleCheckChange"
:data="treeList"
:node-key="nodeKey"
:props="props">
</vl-tree>
<el-option value="" style="display:none;"></el-option>
</el-select>
</template>
<script>
export default {
data() {
return {
list: [],
searchVal: '',
noFilterTreeNode: false,//是否过滤树节点
selectedList: [],
firstFeedback:true,
}
},
props: {
props: {
type: Object,
default: () => {
return {
children: 'children',
label: 'label',
value: 'value',
}
}
},
nodeKey: {
type: String,
default: 'value'
},
collapseTags: {
type: Boolean,
default: false
},
popperClass: {
type: String,
default: ''
},
selectedIdList: {
type: Array,
default: () => []
},
treeList: {
type: Array,
default: () => []
},
feekback: {
type: Boolean,
default: false
}
},
model: {
prop: 'selectedIdList',//选中的数组
event: 'updateSelectedIdList'
},
watch: {
feekback: {
handler(val) {
if (val) {
this.$nextTick(() => {
this.$refs['tree'].setCheckedKeys(this.selectedIdList, true);
this.handleCheckChange();
})
}
},
immediate: true
},
},
methods: {
handleFilter(val) {
if (this.noFilterTreeNode) return;
this.searchVal = val;
this.$refs['tree'].filter(val)
},
filterNode(value, data, node) {
if (!value) return true
return data[this.props.label].toLowerCase().indexOf(value.toLowerCase()) !== -1
},
removeTag(val) {
this.watchCheckChangeRun = false;
let obj = this.checkedNodeList.find(item => item[this.props.label] === val);
this.$refs.tree.setChecked(obj[this.props.value], false);
setTimeout(() => {//直接移除标签,不能触发tree的change事件,所以添加个手动调用
if (!this.watchCheckChangeRun) {
this.handleCheckChange();
}
},1)
},
handleSelectVisibleChange(val) {
this.$nextTick(() => {
this.$refs['tree'].$el.children[0].children[0].scrollTop = 0;
})
this.noFilterTreeNode = false;
if (!val) {//select框隐藏时,重置树结构
if (this.searchVal) {
this.handleFilter('')
}
}
},
//树选择变化
handleCheckChange() {
this.watchCheckChangeRun = true;
let checkList=this.$refs['tree'].getCheckedNodes(true);
this.selectedList = checkList.map(item => item[this.props.label]);
this.$emit('updateSelectedIdList', checkList.map(item => item[this.props.value]));
this.checkedNodeList = checkList;
this.noFilterTreeNode = true;//避免vl-tree筛选问题
},
getSelectedInfo(data, list , arr) {
data.forEach((item) => {
let d = false;
d = list.some(itemId => {
return item[this.props.value] === itemId[this.props.value]
})
if (d) {
arr.push(item);
}
if (item[this.props.children]) {
this.getSelectedInfo(item[this.props.children], list);
// 检查子节点是否有 disabled 为 true 的情况
const childHasDisabled = item[this.props.children].some(child => child.disabled);
if (childHasDisabled) {
arr.push(item);
}
}
});
},
}
}
</script>
<style lang="less">
.treeScrollSep{
min-width:260px !important;
.vl-scrollbar.vl-virtual-list__wrapper{
padding-left:8px;
}
.el-scrollbar__bar.is-vertical{
display:none;
}
.vl-tree__content,.vl-tree-node__label{
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
</style>
页面中引用组件
页面内使用promise模拟接口返回列表数据和回显数据
<template>
<div>
<h2>下拉框中树结构及搜索功能</h2>
<div v-for="(v,i) in list" :key="i" class="box">
<composeTree :props="props" :nodeKey="'id'"
v-model="v.selectedIdList" :treeList="treeList" :collapseTags="false" :feekback="feekback">
<!-- <template #default="{node,data}">
<div>
{{data.name}}-{{ data.id }}
</div>
</template> -->
</composeTree>
<div>
selectedIdList:{{ v.selectedIdList }}
</div>
</div>
<largeAmountTree :treeList="treeList2" :collapseTags="true" nodeKey="nodeId" :props="largeProps" v-model="largeSelectedIdList" :feekback="feekback2">
</largeAmountTree>
<div>largeSelectedIdList:{{ largeSelectedIdList }}</div>
</div>
</template>
<script>
import composeTree from './composeTree.vue';
import { resTest } from './test';
import largeAmountTree from './largeAmountTree.vue';
export default {
data() {
return {
props:{
children: 'children',
label: 'name',
value: 'id',
},
list: [],
treeList: [],
treeList2: [],
largeProps: {
children: 'children',
label: 'itemCategoryName',
value: 'nodeId',
disabled:'disabled'
},
largeSelectedIdList: [],
feekback: false,
feekback2: false
}
},
components: {
composeTree,
largeAmountTree
},
created() {
// 异步获取列表数据
const promise1 = new Promise(function(resolve, reject) {
if (true) {
setTimeout(() => {
const initials = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
const options = Array.from({ length: 50 }).map((_, idx) => ({
id: `Option${idx + 1}`,
name: `${initials[idx % 10]}${idx}`,
children: [
{
id: `Option${'0' + idx + 1}`,
name: `${initials[idx % 10]}${'0'+idx}`,
},
{
id: `Option${'1' + idx + 1}`,
name: `${initials[idx % 10]}${'1'+idx}`,
}
]
}));
// this.treeList = options;
resolve(options);
},1000)
} else {
reject(error);
}
});
// 异步获取列表数据
const promise2 = new Promise(function(resolve, reject) {
if (true) {
setTimeout(() => {
// this.treeList = options;
resolve(resTest.data);
},1000)
} else {
reject(error);
}
});
// 异步获取回显数据
const promise3 = new Promise(function(resolve, reject) {
if (true) {
setTimeout(() => {
// this.treeList = options;
resolve([
{
id: 1,
selectedIdList:['Option001']
},
{
id: 2,
selectedIdList:['Option111']
}
]);
},2000)
} else {
reject(error);
}
});
// 异步获取回显数据
const promise4 = new Promise(function(resolve, reject) {
if (true) {
setTimeout(() => {
resolve(['4100000001107797_4100000001107901']);
},2500)
} else {
reject(error);
}
});
this.getData1 = promise1.then(res => {
this.treeList = res;
})
this.getData2 = promise2.then(res => {
this.treeList2 = res;
})
this.getData3 = promise3.then(res => {
this.list = res;
})
this.getData4 = promise4.then(res => {
this.largeSelectedIdList = res;
})
//列表数据和回显数据都返回时
Promise.all([this.getData1, this.getData3]).then(() => {
console.log('all done');
this.feekback = true;
})
//列表数据和回显数据都返回时
Promise.all([this.getData2, this.getData4]).then(() => {
console.log('all done---2');
this.feekback2 = true;
})
},
mounted() {
},
}
</script>
<style lang="less" scoped>
.box {
margin-bottom: 20px;
}
</style>