首页 前端知识 Vue Elementui: el-select组件下拉数据量大,产生页面卡顿或崩溃,详细解决方案!

Vue Elementui: el-select组件下拉数据量大,产生页面卡顿或崩溃,详细解决方案!

2025-03-20 12:03:50 前端知识 前端哥 559 639 我要收藏

1.安装插件

npm install vue-virtual-scroll-list

2.封装组件

1> 新建文件夹 VirtualSelect

创建文件:index.vue,OptionNode.vue

/VirtualSelect/index.vue

<template>
<div>
<el-select
size="mini"
popper-class="virtualselect"
class="virtual-select-custom-style"
:value="defaultValue"
filterable
:filter-method="filterMethod"
default-first-option
:placeholder="placeholder"
:disabled="disabled"
:multiple="isMultiple"
:collapse-tags="collapseTags"
:allow-create="allowCreate"
@visible-change="visibleChange"
v-on="$listeners"
v-bind="$attrs"
@clear="clearChange"
@focus="focusChange"
>
<!--clearable 清除按钮 -->
<!-- 使用插件 -->
<virtual-list
ref="virtualList"
class="virtualselect-list"
:data-key="value"
:data-sources="selectArr"
:data-component="itemComponent"
:keeps="keepsParams"
:extra-props="{
label: label,
value: value,
isRight: isRight,
isConcat: isConcat,
concatSymbol: concatSymbol,
concatPropArr: concatPropArr,
isDifferent: isDifferent,
selectOptionWidth: selectOptionWidth
}"
></virtual-list>
</el-select>
</div>
</template>
<script>
const validatenull = (val) => {
if (typeof val === 'boolean') {
return false
}
if (typeof val === 'number') {
return false
}
if (val instanceof Array) {
if (val.length === 0) return true
} else if (val instanceof Object) {
if (JSON.stringify(val) === '{}') return true
} else {
if (
val === 'null' ||
val === null ||
val === 'undefined' ||
val === undefined ||
val === ''
)
return true
return false
}
return false
}
// 引入插件
import virtualList from 'vue-virtual-scroll-list'
import OptionNode from './OptionNode.vue'
export default {
name: 'VirtualSelect',
components: {
// 注册插件
'virtual-list': virtualList
},
model: {
prop: 'defaultValue',
event: 'change'
},
props: {
// 数组
list: {
type: Array,
default() {
return []
}
},
// 显示名称
label: {
type: String,
default: ''
},
// 标识
value: {
type: String,
default: ''
},
// 是否拼接字段
isConcat: {
type: Boolean,
default: false
},
// 拼接字段的符号
concatSymbol: {
type: String,
default: ' | '
},
// 要拼接的字段
concatPropArr: {
type: Array,
default() {
return []
}
},
// 反显是否与下拉选项不同
isDifferent: {
type: Boolean,
default: false
},
// 显示右边
isRight: {
type: Boolean,
default: false
},
// 加载条数
keepsParams: {
type: Number,
default: 10
},
// 绑定值 v-model
defaultValue: {
type: [Number, String, Array]
},
// 是否多选
isMultiple: {
type: Boolean,
default: false
},
// 多选折叠标签
collapseTags: {
type: Boolean,
default: false
},
// 输入框占位文本
placeholder: {
type: String,
default: '请选择'
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 是否允许创建条目
allowCreate: {
type: Boolean,
default: false
}
},
data() {
return {
itemComponent: OptionNode,
selectArr: [],
selectOptionWidth: null, // 下拉框宽度
dataList: []
}
},
watch: {
list() {
this.init()
},
defaultValue: {
handler(val) {
if (validatenull(val)) this.clearChange()
this.init()
},
immediate: false,
deep: true
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.list.forEach((item) => {
if (validatenull(item[this.label])) {
item[this.label] = item[this.value]
}
})
this.dataList = this.list
if (!this.defaultValue || this.defaultValue?.length === 0) {
this.selectArr = this.dataList
} else {
// 回显问题
// 由于只渲染固定keepsParams(10)条数据,当默认数据处于10条之外,在回显的时候会显示异常
// 解决方法:遍历所有数据,将对应回显的那一条数据放在第一条即可
this.selectArr = JSON.parse(JSON.stringify(this.dataList))
let obj = {}
if (!this.isMultiple) {
if (this.allowCreate) {
const arr = this.selectArr.filter((val) => {
return val[this.value] === this.defaultValue
})
if (arr.length === 0) {
const item = {}
item[this.value] = this.defaultValue
item[this.label] = this.defaultValue
item.allowCreate = true
this.selectArr.push(item)
this.$emit('selChange', item)
} else {
this.$emit('selChange', arr[0])
}
}
// 单选
for (let i = 0; i < this.selectArr.length; i++) {
const element = this.selectArr[i]
if (
element[this.value]?.toString()?.toLowerCase() ===
this.defaultValue?.toString()?.toLowerCase()
) {
obj = element
this.selectArr?.splice(i, 1)
break
}
}
this.selectArr?.unshift(obj)
} else if (this.isMultiple) {
if (this.allowCreate) {
this.defaultValue?.map((v) => {
const arr = this.selectArr.filter((val) => {
return val[this.value] === v
})
if (arr?.length === 0) {
const item = {}
item[this.value] = v
item[this.label] = v
item.allowCreate = true
this.selectArr.push(item)
this.$emit('selChange', item)
} else {
this.$emit('selChange', arr[0])
}
})
}
// 多选
for (let i = 0; i < this.selectArr.length; i++) {
const element = this.selectArr[i]
this.defaultValue?.map((val) => {
if (
element[this.value]?.toString()?.toLowerCase() ===
val?.toString()?.toLowerCase()
) {
obj = element
this.selectArr?.splice(i, 1)
this.selectArr?.unshift(obj)
}
})
}
}
}
},
// 搜索
filterMethod(query) {
if (!validatenull(query?.trim())) {
this.$refs.virtualList.scrollToIndex(0) // 滚动到顶部
setTimeout(() => {
const orgArr = this.dataList.filter((item) => {
let result =
item[this.label]
?.toLowerCase()
?.indexOf(query?.trim()?.toLowerCase()) > -1
if (this.isRight || this.isConcat) {
const concatPropArr = validatenull(this.concatPropArr)
? [this.label, this.value]
: this.concatPropArr
const checkResArr = concatPropArr.map((prop) => {
return (
item[prop]
?.trim()
?.toLowerCase()
?.indexOf(query?.trim()?.toLowerCase()) > -1
)
})
result = checkResArr.some((val) => val)
}
return result
})
this.selectArr = this.handleSort(orgArr) || []
}, 100)
} else {
setTimeout(() => {
this.init()
}, 100)
}
},
// 按拼音字母数字顺序排序
handleSort(arr) {
const sortArr = arr.sort((a, b) => {
const reg = /[a-zA-Z0-9]/
const x = a[this.label],
y = b[this.label]
if (reg.test(x) || reg.test(y)) {
if (x > y) {
return 1
} else if (x < y) {
return -1
} else {
return 0
}
} else {
return x.localeCompare(y)
}
})
return sortArr
},
visibleChange(bool) {
if (!bool) {
this.$refs.virtualList.reset()
this.init()
}
},
clearChange() {
this.visibleChange(false)
},
focusChange(event) {
this.setOptionWidth(event)
},
setOptionWidth(event) {
// 下拉框弹出时,设置弹框的宽度
this.$nextTick(() => {
this.selectOptionWidth = event.srcElement.offsetWidth + 'px'
if (this.isMultiple) {
this.selectOptionWidth =
event.srcElement.offsetParent.clientWidth + 25 + 'px'
}
})
}
}
}
</script>
<style lang="scss" scoped>
// .virtualselect ::v-deep .el-select-dropdown__item {
// // .virtual-select-custom-style ::v-deep .el-select-dropdown__item {
// // 设置最大宽度,超出省略号,鼠标悬浮显示
// // options 需写 :title="source[label]"
// // width: 250px;
// // display: inline-block;
// // overflow: hidden;
// // text-overflow: ellipsis;
// // white-space: nowrap;
// // z-index: 9;
// }
.virtualselect {
// 设置最大高度
max-height: 245px;
overflow-y: auto;
// &-list {
// max-height:245px;
// overflow-y:auto;
// }
}
.virtualselect-list {
max-height: 245px;
overflow-y: auto;
}
.el-select {
width: 100%;
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: transparent;
cursor: pointer;
margin-right: 5px;
}
::-webkit-scrollbar-thumb {
background-color: rgba(144, 147, 153, 0.3) !important;
border-radius: 3px !important;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(144, 147, 153, 0.5) !important;
}
::-webkit-scrollbar-track {
background-color: transparent !important;
border-radius: 3px !important;
-webkit-box-shadow: none !important;
}
::v-deep .el-select__tags {
flex-wrap: unset;
overflow: auto;
}
</style>
<style>
.virtualselect .el-select-dropdown__item {
display: block;
/* max-width: 350px; */
overflow: visible;
}
.virtualselect .el-scrollbar__bar.is-vertical {
width: 0px;
}
</style>

/VirtualSelect/OptionNode.vue

<template>
<el-option
:key="label + value"
:label="isDifferent ? source[label] : handleConcat()"
:value="source[value]"
:disabled="source.disabled"
:title="handleConcat()"
:style="{ width: selectOptionWidth, 'min-width': '200px' }"
>
<span>{{ handleConcat() }}</span>
<span v-if="isRight" style="float: right; color: #939393">{{
source[value]
}}</span>
</el-option>
</template>
<script>
const validatenull = (val) => {
if (typeof val === 'boolean') {
return false
}
if (typeof val === 'number') {
return false
}
if (val instanceof Array) {
if (val.length === 0) return true
} else if (val instanceof Object) {
if (JSON.stringify(val) === '{}') return true
} else {
if (
val === 'null' ||
val === null ||
val === 'undefined' ||
val === undefined ||
val === ''
)
return true
return false
}
return false
}
export default {
name: 'OptionNode',
props: {
// 每一行的索引
index: {
type: Number,
default: 0
},
// 每一行的内容
source: {
type: Object,
default() {
return {}
}
},
// 需要显示的名称
label: {
type: String,
default: ''
},
// 绑定的值
value: {
type: String,
default: ''
},
// 是否拼接字段
isConcat: {
type: Boolean,
default: false
},
// 拼接字段的符号
concatSymbol: {
type: String,
default: ' | '
},
// 要拼接的字段
concatPropArr: {
type: Array,
default() {
return []
}
},
// 反显是否与下拉选项不同
isDifferent: {
type: Boolean,
default: false
},
// 右侧是否显示绑定的值
isRight: {
type: Boolean,
default() {
return false
}
},
selectOptionWidth: {
type: String,
default: '350px'
}
},
methods: {
handleConcat() {
let result = this.source[this.label]
if (this.isConcat) {
const concatPropArr = validatenull(this.concatPropArr)
? [this.label, this.value]
: this.concatPropArr
result = concatPropArr
.map((item) => {
return this.source[item]
})
.filter((val) => !validatenull(val))
.join(this.concatSymbol)
}
return result
},
concatString(a, b) {
a = a || ''
b = b || ''
if (this.isConcat) {
return a + (a && b ? this.concatSymbol : '') + b
}
return a
}
}
}
</script>

2. 组件内使用 ,当前使用的是单选,多选绑定为空数组[]

index.vue

<template>
<div>
<!--
虚拟滚动下拉框:label:标题,value:值
label和value:可设置为后端返回得字段名称
keeps-params:单次加载得条数
is-multiple:是否多选
list:绑定接口请求的数据list
-->
<virtual-select
v-model="values"
:list="optionsList"
label="label"
value="value"
:placeholder-params="'请选择'"
:keeps-params="10"
:is-multiple="false"
@change="handleChange"
/>
</div>
</template>
<script>
import VirtualSelect from './VirtualSelect.vue'
export default {
name: 'index',
components: { VirtualSelect },
data() {
return {
values:'',//绑定的值
optionsList:[
// {
// label: '选项1',
// value: 1
// },
// {
// label: '选项2',
// value: 2
// },
// {
// label: '选项3',
// value: 3
// },
// ......
]
}
},
mounted() {
// 模拟数据
this.getData()
},
methods: {
getData(){
// 模拟三万条数据
let testData=[]
for (let i = 0; i < 30000; i++) {
testData.push({
label: '选项'+i,
value: i
})
}
// 设置数据
this.optionsList = testData
},
// 监听
handleChange(value) {
console.log(value)// 这里拿到的是选中的值
},
}
}
</script>

转载请注明出处或者链接地址:https://www.qianduange.cn//article/24171.html
标签
评论
发布的文章

【Linux笔记】基础IO(上)

2025-03-27 13:03:40

大家推荐的文章
会员中心 联系我 留言建议 回顶部
浏览器升级提示:您的浏览器版本较低,建议您立即升级为知了极速浏览器,极速、安全、简约,上网速度更快!立即下载
复制成功!