为了解决 element-ui 中 el-select 组件在大数据量的情况下出现的性能问题(数据量太大,导致渲染过慢,或造成页面卡顿甚至于卡死) 。
本组件时基于vue-virtual-scroll-list(一个基于vue2的虚拟滚动组件,通过不渲染可视区域以外的内容,显示虚拟的滚动条来提升页面性能)实现的下拉选中框,该下拉选择组件支持单选,多选,筛选等操作,若是选中了数据,再次打开会自动定位到数据的位置
npm install vue-virtual-scroll-list --save
参数名 | 类型 | 描述 |
---|---|---|
data-key |
String|Function | 从每个数据对象中的“数据源”获取唯一键。或者用每个“数据源”调用一个函数,并返回它们的唯一键。它的值在“数据源”中必须是唯一的,用于标识条目的大小。 |
data-sources |
Array[Object] | 为列表构建的源数组,每个数组数据必须是一个对象,并具有唯一的键ID(data-key)属性。 |
data-component |
Component | vue创建/声明的渲染项组件,它将使用’ data-sources ‘中的数据对象作为渲染道具,并命名为’source’。 |
keeps |
Number | 你希望虚拟列表在真正的dom中保持呈现多少项,默认30个。 |
extra-props |
Object | 分配给不在数据源中的项目组件的额外道具。注意:索引和源都被占用在内部。 |
可以通过ref方式调用以下这些方法:
方法名 | 描述 |
---|---|
reset |
将所有状态重置为初始状态。 |
scrollToIndex(index) |
手动设置滚动位置为指定的索引。 |
scrollToOffset(offset) |
手动设置滚动位置到指定的偏移量。 |
上述是组件常用的参数和方法,其他参数及方法可 官方插件
在src/components中创建文件夹SelectV2
<template>
<div>
<el-select
:value="value"
popper-class="virtualselect"
filterable
:filter-method="filterMethod"
@visible-change="visibleChange"
v-bind="$attrs"
v-on="$listeners"
>
<vue-virtual-list
ref="virtualList"
class="virtualselect-list"
:data-key="fields.value"
:data-sources="dataSources"
:data-component="itemComponent"
:keeps="20"
:extra-props="{
label: fields.label,
value: fields.value,
rightLabel: fields.rightLabel
}"
></vue-virtual-list>
</el-select>
</div>
</template>
<script>
import VueVirtualList from 'vue-virtual-scroll-list';
import itemComponent from './itemComponent';
export default {
name: 'select-v2',
components: {
'vue-virtual-list': VueVirtualList
},
model: {
prop: 'value',
event: 'change'
},
props: {
// 下例列表数据
data: {
type: Array,
default: () => []
},
// 字段参数配置,默认为label,value
// 可尝试传入rightLabel字段,使其在右边也展示内容
fields: {
type: Object,
default() {
return {
label: 'label',
value: 'value',
};
}
},
// 绑定的默认值
value: {
type: [String, Array],
default: () => []
}
},
mounted() {
this.init();
},
watch: {
'data'() {
this.init();
}
},
data() {
return {
// 内容组件
itemComponent,
// 下拉列表中的数据
dataSources: [],
// 用来定位滚动到某个位置
start: 0
};
},
methods: {
init() {
if (!this.value || this.value.length === 0) {
// 初始化数据
this.dataSources = this.data;
} else {
// 回显问题
// 单选时,存储当前的index
// 多选时,取选中的所有值中下标的最小值
this.dataSources = JSON.parse(JSON.stringify(this.data));
if (typeof this.value === 'string') {
const value = this.value
for (let i = 0; i < this.dataSources.length; i++) {
const element = this.dataSources[i];
if (element[this.fields.value] === value) {
this.start = i
break;
}
}
} else if (Array.isArray(this.value)) {
let start = []
const valueSet = new Set(this.value)
for (let i = 0; i < this.dataSources.length; i++) {
const element = this.dataSources[i];
if (valueSet.has(element[this.fields.value])) {
start.push(i)
}
}
this.start = Math.min(...start)
}
}
},
// 搜索
filterMethod(query) {
if (query !== '') {
setTimeout(() => {
this.$refs.virtualList.scrollToIndex(0);
this.dataSources = [...this.data.filter((item) => {
return this.fields.rightLabel
? item[this.fields.label]
.indexOf(query) > -1 ||
item[this.fields.rightLabel]
.indexOf(query) > -1
: item[this.fields.label]
.indexOf(query) > -1;
})];
}, 100);
} else {
this.init();
}
},
visibleChange(bool) {
if (!bool) {
this.$refs.virtualList.reset();
this.init();
} else {
// 定位到选中的位置
this.$nextTick(() => {
this.$refs.virtualList.scrollToIndex(this.start);
})
}
}
}
};
</script>
<style lang="scss">
.virtualselect {
// 设置最大高度
&-list {
max-height: 245px;
overflow-y: auto;
}
.el-scrollbar .el-scrollbar__bar.is-vertical {
width: 0 !important;
}
}
</style>
<template>
<div>
<el-option
:key="label + value"
:label="source[label]"
:value="source[value]"
>
<span>{{ source[label] }}</span>
<span v-if="rightLabel" style="float:right;color:#939393">{{
source[rightLabel]
}}</span>
</el-option>
</div>
</template>
<script>
export default {
name: 'item-component',
props: {
// index of current item
// 每一行的索引
index: {
type: Number
},
// 每一行的内容
source: {
type: Object,
default() {
return {};
}
},
// 需要显示的名称
label: {
type: String
},
// 绑定的值
value: {
type: String
},
// 右侧显示绑定的值,为空则不显示
rightLabel: {
type: String,
default: ''
}
},
mounted() {}
};
</script>
<template>
<div class="cw-select">
<select-v2
:data="list"
v-model="value"
placeholder="请选择下拉数据"
clearable
multiple
@change="selectChange"
></select-v2>
</div>
</template>
<script>
import SelectV2 from '@/components/SelectV2'
export default {
name: 'demo',
components: {
'select-v2': SelectV2
},
data() {
return {
list: [],
// 下拉框选择的默认值,可以时字符串和数组
value: []
};
},
mounted() {
this.list = [];
// 创建20000条测试数据
for (let i = 0; i < 20000; i++) {
this.list.push({ value: i, label: '测试' + i + '' });
}
},
methods: {
selectChange(val) {
console.log('下拉框选择的值', val);
}
}
};
</script>