前言
在后台管理系统中,数据几千上万条还是比较常见的,但当 select 数据过多导致页面卡顿至卡死 也是有可能的。
思路
大家想,导致页面卡顿或者卡死的原因是什么呢,归根到底就是数据量太多,渲染 select 的DOM数 太多,导致渲染引擎被撑炸了,总之就是 需要展示的数据太多。
解决办法
一、减少数据展示——使用scroll事件 + 数组slice切割
那还不好解决么,直接给 select 绑定一个 滚动事件 上代码
<template>
<el-select
ref="defineSelectRef"
v-model="modelValue"
>
<el-option
v-for="item in options.slice(0,rangeNumber)"
:key="item[optionsValue]"
:label="item[optionsLabel]"
:value="item[optionsValue]"
@click="selectChangeTap(item)"
/>
</el-select>
</template>
<script setup lang="ts">
const options = ref<any[]>([]) // select 数据
const defineSelectRef = ref() // select RefDom
const rangeNumber = ref(10) // 给用户展示的数据个数
onMounted(() => {
// 获取下拉框的Dom 添加懒加载
const SELECT_DOM = defineSelectRef.value.popperPaneRef.getElementsByClassName("el-select-dropdown__wrap")
// 添加滚轮事件
SELECT_DOM[0].addEventListener('scroll', (el:any) => {
// 当滑动到底部 展示的数值加10
if (el.target.scrollHeight - el.target.scrollTop === el.target.clientHeight) {
rangeNumber.value += 10
}
})
})
</script>
这种简单粗暴,只适合用于 不需要搜索 和 级联 的,不过一般来说,这种大数据量的都会要求有搜索功能,那只能再封装了
二 、添加搜索功能
使用搜索功能,那就需要 用到俩个数组了,一个数组就不够用了,老样子,上代码说话
<template>
<el-select
ref="defineSelectRef"
v-model="modelValue"
popper-class="defineSelect"
style="width: 100%"
clearable
filterable
:filter-method="filterSelect"
:placeholder="props.placeholder"
@clear="clearItem"
@visible-change="visibleChange"
:loading="loading"
>
<template v-if="type === 'default' && options.length > 0">
<el-option
v-for="item in optionsDefault"
:key="item[optionsValue]"
:label="item[optionsLabel]"
:value="item[optionsValue]"
@click="selectChangeTap(item)"
/>
</template>
<template v-else>
<el-option
v-for="item in optionsFilter.slice(0,rangeNumber)"
:key="item[optionsValue]"
:label="item[optionsLabel]"
:value="item[optionsValue]"
@click="selectChangeTap(item)"
/>
</template>
<template #loading>
<svg class="circular" viewBox="0 0 50 50">
<circle class="path" cx="25" cy="25" r="20" fill="none" />
</svg>
</template>
</el-select>
</template>
<script setup lang="ts">
import type { formItemInput } from 'types/form'
import type { PropType } from 'vue'
import { ref, toRefs, onMounted, watch } from 'vue'
interface optionsSettingType{
optionsData:Array<any>
optionsLabel:{label:string, value:string}
getOptionsFunc:any
}
const props = defineProps({
modelValue: {
},
placeholder: {
type: String,
default: ''
},
})
const emits = defineEmits(['update:modelValue', 'selectItem'])
const defineSelectRef = ref()
const { modelValue } = toRefs(props) as any
const loading = ref(true)
const type = ref<'filter'|'default'>('default')
const options = ref<any[]>([]) // 初始化得到的
const optionsDefault = ref<any[]>([]) // 默认数组
const optionsFilter = ref<any[]>([]) // 过滤处理后的
const rangeNumber = ref(10)
const optionsValue = ref(props.optionsLabel.value || 'value')
const optionsLabel = ref(props.optionsLabel.label || 'label')
onMounted(() => {
initialize()
// 获取下拉框的Dom 添加懒加载
const SELECT_DOM = defineSelectRef.value.popperPaneRef.getElementsByClassName("el-select-dropdown__wrap")
// 添加滚轮事件
SELECT_DOM[0].addEventListener('scroll', (el:any) => {
// 当滑动到底部 展示的数值加10
if (el.target.scrollHeight - el.target.scrollTop === el.target.clientHeight) {
rangeNumber.value += 10
}
})
})
// -----------------------------监听数据 开始---------------------------------
watch(() => rangeNumber.value, (newValue) => {
optionsDefault.value = options.value.slice(0, newValue)
}, { immediate: true })
// -----------------------------监听数据 结束---------------------------------
/**
* 这里使用了 防抖 ... 为何不是用的库呐 因为 每个项目用的库都不一样 就随便手写了个
* 有没有大佬帮忙补充一 在此感谢
*/
let tiemFilter = 500 // 毫秒
let isFilter = false
let ifFilterTimeout:any
// 搜索功能
function filterSelect(value:string) {
loading.value = true
value = value.trim()
// 当没有值的时候展示默认数据
if (value) {
type.value = 'filter'
} else {
type.value = 'default'
}
if (isFilter) {
clearTimeout(ifFilterTimeout)
ifFilterTimeout = setTimeout(() => {
isFilter = false
filterSelectFun(value.toLowerCase())
}, tiemFilter)
} else {
isFilter = true
clearTimeout(ifFilterTimeout)
ifFilterTimeout = setTimeout(() => {
isFilter = false
filterSelectFun(value.toLowerCase())
}, tiemFilter)
}
}
function filterSelectFun(value:string) {
// 重置搜索的集合
optionsFilter.value = []
// 精确匹配的结果
const exactMatches:object[] = []
// 其他结果 这里使用二维数组 通过找到匹配项的 index 顺序排列
const otherMatches:Array<Array<object>> = []
options.value.forEach(item => {
// 获取到 label 的值
const label = item[optionsLabel.value].toLowerCase()
if (label === value) { // 当完全相等时 插入到精准匹配中
exactMatches.push(item)
} else {
let index = label.indexOf(value) // 模糊匹配
if (index !== -1) { // 如果匹配上了 则插入其他结果的数组中
otherMatches[index] = otherMatches[index] ? otherMatches[index] : []
otherMatches[index].push(item)
}
}
})
optionsFilter.value = exactMatches.concat(...otherMatches.filter(subArr => subArr.length !== 0))
loading.value = false
}
// 打开 || 关闭 下拉框时
function visibleChange(visible: boolean) {
visible ? '' : type.value = 'default'
}
//