我们在经常开发时,除了会遇到分页请求select之前,还会偶尔遇到后端一次性返回几十万行的数据,这种情况下就需要我们手动切割加载渲染了。
组件相关的代码:
<template>
<el-select
ref="selectScroll" v-model="chooseShowValue" clearable filterable :filter-method="filterMethod"
v-scrollChange="loadMore" @visible-change="visibleChange" @change="changeData" :placeholder="placeholderValue"
:disabled="curDisabled">
<div :class="classSelectRef">
<el-option v-for="(item, index) in selecOptions" :key="index" :label="item[showLabel]" :value="item[showValue]">
{{ item[showLabel] }}
</el-option>
</div>
</el-select>
</template>
<script lang="ts" setup>
import { onActivated, watch, onMounted, toRaw, ref, Directive, DirectiveBinding } from 'vue'
const emit = defineEmits(['update:chooseValue', 'change'])
const dom = ref()
const selectScroll = ref(null)
const selecOptions = ref()
const options = ref()
const rangeNumber = ref(10)
const chooseShowValue = ref('f99c1b07-bd7b-4c66-a1de-33c10bf80291')
const initSelectData = ref(null)
const initValue = ref('')
const placeholderValue = ref('')
const timeOut = ref()
const queryRef = ref('')
const curDisabled = ref(false)
const curResOptions: any = ref()
const classSelectRef = ref('')
const props = defineProps({
chooseValue: {
type: String,
default: () => ''
},
showValue: {
type: String,
default: () => 'value'
},
showLabel: {
type: String,
default: () => 'label'
},
resOptions: {
type: Array,
default: () => []
},
changeParam: {
type: Object,
default: () => { }
},
placeholder: {
type: String,
default: '请选择'
},
disabled: {
type: Boolean,
default: () => false
},
classSelect: {
type: String,
default: 'labelSelectCpmBox'
}
})
onMounted(() => {
classSelectRef.value = props.classSelect + (new Date().getTime()).toString()
chooseShowValue.value = props.chooseValue
placeholderValue.value = props.placeholder
curResOptions.value = props.resOptions
options.value = props.resOptions
})
// computed: {
// chooseComputedValue() {
// return JSON.parse(JSON.stringify(this.chooseValue));
// }
// },
const loadMore = () => {
// n是默认初始展示的条数会在渲染的时候就可以获取,具体可以打log查看
// elementui下拉超过7条才会出滚动条,如果初始不出滚动条无法触发loadMore方法
// return () => (this.rangeNumber += 5
// this.tmpOption = this.resOptions.splice(0,this.rangeNumber)
// );
// return () => {
rangeNumber.value += 5
selecOptions.value = options.value.slice(0, rangeNumber.value)
// };
}
// 防抖
const debounce = (fn: any, delay: number) => {
if (timeOut.value) {
clearTimeout(timeOut.value)
}
timeOut.value = setTimeout(() => {
fn()
}, delay)
}
const filterMethodByQuery = () => {
if (queryRef.value) {
const filterArr = curResOptions.value.filter((item: any) => {
const tmpData = toRaw(item)
const tmpLabel = tmpData[props.showLabel]
const tmpValue = tmpData[props.showValue]
const query = queryRef.value
return tmpLabel.includes(query)
// return tmpLabel.includes(query) || tmpValue.includes(query)
})
options.value = filterArr
selecOptions.value = options.value.slice(0, rangeNumber.value)
} else {
options.value = curResOptions.value
}
chooseShowValue.value = queryRef.value
changeData()
}
const filterMethod = (query = '') => {
if (query) {
queryRef.value = query
debounce(filterMethodByQuery, 500)
} if (queryRef.value !== query) {
queryRef.value = query
debounce(filterMethodByQuery, 500)
}
}
// 下拉框出现时,调用过滤方法
const visibleChange = (flag: any) => {
if (flag) {
filterMethod()
}
}
const changeData = () => {
emit('update:chooseValue', chooseShowValue.value)
emit('change', chooseShowValue.value, props.changeParam)
}
const showInitData = () => { }
/* 滚动监听函数 */
const scrollAddEventFn = (e: any) => {
const self = e.target as any
if (self.scrollHeight - self.scrollTop <= self.clientHeight) {
console.log('分页查询')
loadMore()
}
}
const vScrollChange: Directive = (el, binding: DirectiveBinding) => {
/* 在数据渲染完之后的回调 */
/* 初始化滚动监听 (由于 dom 渲染未完成,所以需要开启一个 timeout 在 1s 后实现监听) */
const parentDom = document.querySelectorAll('.el-select-dropdown__wrap.el-scrollbar__wrap.el-scrollbar__wrap--hidden-default') as any
setTimeout(() => {
parentDom.forEach((e: any, idx: number) => {
const className = '.' + toRaw(classSelectRef.value)
// if (e.querySelector('.labelSelectCpmBox') && e.querySelector('.labelSelectCpmBox').children && e.querySelector('.labelSelectCpmBox').children.length > 0) {
if (e.querySelector(className) && e.querySelector(className).children && e.querySelector(className).children.length > 0) {
dom.value = parentDom[idx]
dom.value.addEventListener('scroll', scrollAddEventFn, false)
}
})
}, 1000)
}
watch(
() => props.chooseValue,
(newValue) => {
chooseShowValue.value = JSON.parse(JSON.stringify(newValue))
},
{
deep: true,
immediate: true
}
)
watch(
() => props.placeholder,
(newValue) => {
placeholderValue.value = JSON.parse(JSON.stringify(newValue))
},
{
deep: true,
immediate: true
}
)
watch(
() => props.resOptions,
(newValue) => {
curResOptions.value = JSON.parse(JSON.stringify(newValue))
options.value = JSON.parse(JSON.stringify(newValue))
if (newValue.length > 10) {
selecOptions.value = JSON.parse(JSON.stringify(newValue)).slice(0, rangeNumber.value)
} else {
selecOptions.value = JSON.parse(JSON.stringify(newValue))
}
showInitData()
},
{
deep: true,
immediate: true
}
)
watch(
() => props.disabled,
(newValue) => {
curDisabled.value = JSON.parse(JSON.stringify(newValue))
},
{
deep: true,
immediate: true
}
)
</script>