在开发过程中经常遇到分页加载select下拉框数据的情况,所以封装了一个通用组件。
该组件的优化点超级多,后续有空会持续优化。
组件相关的代码:
特别需要注意的是netFun异步请求中,返回的参数格式为
const data = {
pagingData: XXX,
pageIndex: XXX,
pageSize: XXX,
totalCount: XXX
}
组件代码:
<template>
<el-select
ref="selectScroll" v-model="chooseShowValue" clearable filterable :filter-method="filterMethod"
v-scrollChange="loadMore" @visible-change="visibleChange" @change="changeData" :placeholder="props.placeholder"
:disabled="props.disabled" :loading="loading" :multiple="props.multiple" :style="props.style"
>
<div :class="classSelectRef">
<el-option
v-for="(item, index) in selecOptions" :key="'labelSelect' + index" :value="item[showValue]"
:label="getShowLabel(item)"
>
{{ getShowLabel(item) }}
</el-option>
</div>
</el-select>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, Directive, DirectiveBinding, toRaw, watch } from 'vue'
import Request from '../../utils/request'
const emit = defineEmits(['update:chooseValue', 'change'])
const dom = ref()
const selectScroll = ref(null)
const loading = ref(false)
const selecOptions = ref()
const options = ref([])
const rangeNumber = ref(10)
const chooseShowValue = ref()
const timeOut = ref()
const queryRef = ref('')
const curResOptions = ref()
const classSelectRef = ref('')
const requsetObj = reactive({
current: 1,
size: 10
})
let requestObjData: {
[key: string]: {};
} = {}
let isLoadEnd = false
let requestLoading = false
let curNetFun: Function
const props = defineProps({
isShow: {
type: Boolean,
default: false
},
chooseValue: {
type: [Array, String],
default: () => ''
},
showValue: {
type: String,
default: () => 'value'
},
showAllLabel: {
type: String,
default: () => 'label'
},
showLabel: {
type: String,
default: () => 'label'
},
otherShowLabel: {
type: String,
default: () => ''
},
changeFunction: {
type: Function,
default: () => null
},
placeholder: {
type: String,
default: '请选择'
},
disabled: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: false
},
style: {
type: String,
default: ''
},
netFun: {
type: Function,
default: () => null
},
selectNetFun: {
type: Function,
default: () => null
},
showLabelFun: {
type: Function,
default: () => null
},
isShowLabelFun: {
type: Boolean,
default: false
},
pageSize: {
type: String,
default: 'pageSize'
},
pageIndex: {
type: String,
default: 'pageIndex'
},
classSelect: {
type: String,
default: 'labelSelectCpmBox'
}
})
onMounted(() => {
classSelectRef.value = props.classSelect + (new Date().getTime()).toString()
chooseShowValue.value = props.chooseValue
initData()
})
const initData = () => {
curNetFun = props.netFun
curResOptions.value = []
selecOptions.value = []
requsetObj.current = 1
requsetObj.size = 10
isLoadEnd = false
requestLoading = false
requestObjData = {}
if (props.isShow) {
loadMore()
}
}
const loadMore = async () => {
if (curNetFun) {
let param = {} as any
param = { ...requsetObj }
try {
requestLoading = true
const request = await curNetFun(param)
requestLoading = false
const pagingData = request.pagingData
curResOptions.value.push(...pagingData)
selecOptions.value.push(...pagingData)
if (pagingData.length < request.pageSize) {
isLoadEnd = true
}
if (toRaw(curResOptions.value).length >= request.totalCount) {
isLoadEnd = true
}
pagingData.forEach((item: any) => {
const tmpValue = item[props.showValue]
requestObjData[tmpValue] = item
})
} catch (error) {
requestLoading = false
}
}
}
// 防抖
const debounce = (fn: any, delay: number) => {
if (timeOut.value) {
clearTimeout(timeOut.value)
}
timeOut.value = setTimeout(() => {
fn()
}, delay)
}
const findValueInData = (data: any, value: any) => {
return data && data.toString().indexOf(value) >= 0
}
const filterMethodByQuery = async () => {
if (queryRef.value) {
if (props.selectNetFun) {
const request = await props.selectNetFun(queryRef.value)
const pagingData = request.pagingData
// curResOptions.value = []
// curResOptions.value.push(...pagingData)
selecOptions.value = []
selecOptions.value.push(...pagingData)
pagingData.forEach((item: any) => {
const tmpValue = item[props.showValue]
requestObjData[tmpValue] = item
})
} else {
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
if (props.otherShowLabel) {
const otherTmpValue = tmpData[props.otherShowLabel]
return findValueInData(tmpLabel, query) || findValueInData(tmpValue, query) || findValueInData(otherTmpValue, query)
} else {
return findValueInData(tmpLabel, query) || findValueInData(tmpValue, query)
}
})
options.value = filterArr
selecOptions.value = options.value.slice(0, rangeNumber.value)
}
} else {
options.value = curResOptions.value
selecOptions.value = options.value.slice(0, rangeNumber.value)
}
if (!props.multiple) {
chooseShowValue.value = queryRef.value
changeData()
}
}
const filterMethod = (query = '') => {
if (query) {
queryRef.value = query
debounce(filterMethodByQuery, 500)
} else {
if (queryRef.value !== query) {
queryRef.value = query
debounce(filterMethodByQuery, 500)
}
}
}
// 下拉框出现时,调用过滤方法
const visibleChange = (flag: any) => {
if (flag) {
filterMethod()
}
}
const getShowLabel = (item: any) => {
if (props.showLabelFun) {
return props.showLabelFun(item)
} else {
return item[props.showLabel]
}
}
const changeData = () => {
const tmpValue = chooseShowValue.value
queryRef.value = ''
emit('update:chooseValue', tmpValue)
emit('change', requestObjData[tmpValue])
if (!chooseShowValue.value) {
options.value = curResOptions.value
selecOptions.value = options.value.slice(0, rangeNumber.value)
}
}
/* 滚动监听函数 */
const scrollAddEventFn = (e: any) => {
const self = e.target as any
if (self.scrollHeight - self.scrollTop <= self.clientHeight) {
console.log('分页查询', isLoadEnd, requestLoading)
if (!isLoadEnd && !requestLoading) {
requsetObj.current++
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.isShow,
() => {
if (props.isShow) {
initData()
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.chooseValue,
(newValue) => {
chooseShowValue.value = JSON.parse(JSON.stringify(newValue))
},
{
deep: true,
immediate: true
}
)
</script>