功能需求:下拉框中分页加载后端接口返回的人员数据,实现滑动加载更多数据效果,并且可以手动搜索定位数据,此项目使用Vue3 + ts+ elementUi 实现
把此分页滑动加载数据功能封装成vue中的hooks,文件命名为useMoreUser.ts
import {ref,reactive,nextTick} from 'vue'
export const useMoreUser = () => {
const selectMoreData = reactive({
page: 0, //当前页
loading: false, //loading
hasMore: true, //判断是否还有更多数据
selectValue: '', //下拉框选中数据
selectOptions: [] //下拉框选项
})
// 人员列表加载数据列表【newPage: 页数,name: 搜索条件】
const loadDataList = async (newPage: number, name?: string = '' ) => {
try {
selectMoreData.loading = true;
//后端接口,入参为搜索条件人员姓名,页数
let res = await getUserList(name, newPage);
if (newPage === 1) { //初始化
selectMoreData.selectOptions = [];
}
//存储后端接口返回数据
selectMoreData.selectOptions.push(...res.rows);
//判断是否还有更多数据
selectMoreData.hasMore = selectMoreData.selectOptions.length < res.total;
selectMoreData.page = newPage;
} catch (err) {
console.error(err);
} finally {
selectMoreData.loading = false;
}
};
//加载更多数据
const handleLoadMore = async (newPage: number, name?: string = '' ) => {
await loadDataList(newPage,name);
};
//返回下拉框选项
const getList = ()=>{
return selectMoreData.selectOptions;
}
//导出数据方法等
return {selectMoreData,getList, loadDataList, handleLoadMore}
}
再继续封装下拉框选项组件 option.vue
<!-- 监听 el-select 的滚动,并提供触底加载数据的回调 -->
<template>
<el-option ref="el" class="el-select-loading" value="">
<template v-if="hasMore">
<el-icon class="el-select-loading__icon"><Loading /></el-icon>
<span class="el-select-loading__tips">{{ loadingText || "正在加载" }}</span>
</template>
<template v-else>{{ noMoreText || "到底了~" }}</template>
</el-option>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import { ElOption } from "element-plus";
interface Props {
// 当前页码
page: number;
// 是否加载中,用来过滤重复的加载
loading: boolean;
// 加载中的提示文案
loadingText?: string;
// 是否有更多数据可加载
hasMore: boolean;
// 没有更多数据的提示文案
noMoreText?: string;
}
const props = defineProps<Props>();
interface Emits {
(event: "loadMore", data: number): any;
}
const emit = defineEmits<Emits>();
const el = ref<typeof ElOption>();
const observer = ref<IntersectionObserver>();
// 组件加载成功,监听滚动
onMounted(() => {
if (!el.value) {
return;
}
const callback: IntersectionObserverCallback = (entries) => {
if (props.loading || !props.hasMore || !entries[0].isIntersecting) {
return;
}
emit("loadMore", props.page + 1);
};
const options: IntersectionObserverInit = {
root: el.value.$el.parentElement?.parentElement,
rootMargin: "0px 0px 0px 0px",
};
observer.value = new IntersectionObserver(callback, options);
observer.value.observe(el.value.$el);
});
// 组件卸载成功,取消滚动监听
onUnmounted(() => {
if (!el.value) {
return;
}
observer.value?.unobserve(el.value.$el);
});
</script>
<style lang="scss" scoped>
.el-select-loading {
display: flex;
align-items: center;
justify-content: center;
cursor: initial;
pointer-events: none;
color: var(--el-color-info);
font-size: 12px;
&__icon {
font-size: 16px;
animation: rotate 1.5s linear infinite;
}
&__tips {
margin-left: 6px;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
}
</style>
最后到咱们真正使用页面index.vue,封装时候有些费事,但是使用起来就简单了
template部分
<el-select class="customSelect" filterable remote :remote-method="remoteMethod"
v-model="user" placeholder="请选择人员"
style="width: 100%">
<el-option
v-for="item in selectMoreData.selectOptions"
:key="item.userId"
:label="`${item.userName}`"
:value="item.userId"
>
<span style="float: left">{{ `${item.userName}` }</span>
</el-option>
<ElSelectLoading
:page="selectMoreData.page"
:loading="selectMoreData.loading"
:hasMore="selectMoreData.hasMore"
@loadMore="handleLoadMore"
/>
</el-select>
script部分
<script lang="ts" setup>
import {ref, reactive, onUnmounted, onMounted, nextTick, computed, watch} from 'vue'
import ElSelectLoading from "@/components/Option/option.vue";
import {useMoreUser} from '@/hooks/useMoreUser.ts'
const {selectMoreData, loadDataList, handleLoadMore} = useMoreUser();
const user = ref('');
const remoteMethod = (query: string) => {
loadDataList(1, query);
}
</script>