vue3+element 滚动触底加载选择器
·

组件封装:
<template>
<el-select v-model="selectedValue" filterable remote clearable :placeholder="placeholder" remote-show-suffix
:remote-method="remoteMethod" @visible-change="emits('visible-change')" popper-class="cit-scroll-select">
<el-option v-for="item in props.options" :key="item[dataConfig.value]" :label="item[dataConfig.name]"
:value="item[dataConfig.value]" />
<template #footer v-if="showFooter">
<div class="text-center">
<span v-if="props.loading && props.options?.length < props.total">
{{ props.loading ? '加载中...' : '' }}
</span>
<span v-else-if="props.options?.length === props.total">
没有更多数据了
</span>
</div>
</template>
</el-select>
</template>
<script setup>
/**
* @author 全易
* @time 2025-12-08 17:38:17 星期一
* @description 触底加载 选择器
* @example <scroll-select v-model="value" :options="options" :loading="loading" :total="total" :remoteMethod="remoteMethod" @reachBottom="reachBottom" />
**/
const selectedValue = defineModel()
const emits = defineEmits(['reachBottom'])
const props = defineProps({
loading: {
type: Boolean,
default: false
},
options: {
type: Array,
default: () => []
},
total: {
type: Number,
required: true
},
placeholder: {
type: String,
default: ''
},
// 取值配置
dataConfig: {
type: Object,
default: () => ({
name: 'name',
value: 'id'
})
},
remoteMethod: {
type: Function,
required: true
}
})
// 显示底部加载更多的文本
const showFooter = computed(() => {
return (props.loading && props.options?.length < props.total) || (props.options?.length === props.total)
})
// 监听下拉框滚动事件
const setupScrollListener = () => {
const popperEl = document.querySelector('.cit-scroll-select .el-select-dropdown__wrap')
if (popperEl) {
popperEl.addEventListener('scroll', handleScroll)
}
}
// 移除滚动监听
const removeScrollListener = () => {
const popperEl = document.querySelector('.cit-scroll-select .el-select-dropdown__wrap')
if (popperEl) {
popperEl.removeEventListener('scroll', handleScroll)
}
}
// 滚动处理函数
const handleScroll = (e) => {
const { scrollTop, scrollHeight, clientHeight } = e.target
// 距离底部 60px 时开始加载
if (scrollHeight - scrollTop - clientHeight < 60 && !props.loading && (props.options.length < props.total)) {
emits('reachBottom')
}
}
// 使用 MutationObserver 监听下拉框的出现
let observer = null
const initObserver = () => {
observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.addedNodes.length) {
const selectDropdown = document.querySelector('.cit-scroll-select .el-select-dropdown__wrap')
if (selectDropdown) {
setupScrollListener()
}
}
})
})
// 监听 body 的变化
observer.observe(document.body, {
childList: true,
subtree: true
})
}
// 组件挂载时初始化
onMounted(() => {
initObserver()
})
// 组件卸载前清理
onBeforeUnmount(() => {
if (observer) {
observer.disconnect()
}
removeScrollListener()
})
</script>
<style scoped lang="scss"></style>
使用案例:
<template>
<scroll-select v-model="selectedValue" :options="options" :loading="loading" :total="total" :disabled="disabled"
:placeholder="placeholder" @reachBottom="reachBottom" :remoteMethod="remoteMethod"
:dataConfig="{ name: 'customerFullName', value: 'id' }" />
</template>
<script setup>
/**
* @author 全易
* @time 2025-12-09 09:27:35 星期二
* @description 客户选择器 (触底加载)
* @example <customer-selector v-model="value" />
**/
import { customerListByDataScope } from '@/views/customer/api/customer.js';
const props = defineProps({
placeholder: {
type: String
},
disabled: {
type: Boolean,
default: false
},
// 接口参数
params: {
type: Object,
}
})
const selectedValue = defineModel({
type: [String, Number],
default: null
})
const loading = ref(false)
const options = ref([])
const total = ref(0)
// 初始化参数
const { queryForm } = toRefs(reactive({
queryForm: {
pageNum: 1,
pageSize: 20,
isNormal: false
}
}));
watch(() => selectedValue.value, (now) => {
if (!now) { return }
getData()
}, { immediate: true })
// 获取数据
function getData(loadMore = false) {
if (loading.value) return
loading.value = true
customerListByDataScope({ ...queryForm.value, ...props.params }).then((res) => {
if (res.code !== 200) { return }
if (loadMore) {
options.value = [...options.value, ...res.data.list]
} else {
options.value = res?.data?.list
}
total.value = res.data.total
}).catch(() => {
if (loadMore && queryForm.value.pageNum !== 1) {
queryForm.value.pageNum--
}
}).finally(() => {
loading.value = false
})
};
// 远程获取数据
function remoteMethod(query) {
// if (!query) { return }
options.value = []
queryForm.value.customerName = query
queryForm.value.pageNum = 1
getData(false)
}
// 触底追加
function reachBottom() {
if (options.value.length != 0 && options.value.length < total.value) {
queryForm.value.pageNum++
getData(true)
}
}
</script>
<style scoped lang="scss"></style>
更多推荐



所有评论(0)