一、功能概述
1.1 下拉刷新(Pull Down Refresh)
下拉刷新是指用户通过下拉手势触发页面数据重新加载的功能。当用户下拉页面时,会显示一个刷新指示器,数据加载完成后自动隐藏。
1.2 触底加载(Reach Bottom Load More)
触底加载是指当用户滚动到页面底部时,自动加载下一页数据并追加到现有列表中,实现无限滚动的效果。
二、实现步骤
2.1 页面配置
首先,需要在 pages.json 中配置页面,启用下拉刷新功能:
json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"enablePullDownRefresh": true // 启用下拉刷新
}
}
]
}
2.2 数据结构设计
在实现分页加载之前,我们需要定义相关的状态变量:
javascript
// 新闻列表
const newsList = ref([]);
// 分页相关
const pageNum = ref(1); // 当前页码,从第1页开始
const pageSize = ref(10); // 每页数据量,默认10条
const hasMore = ref(true); // 是否还有更多数据
const loading = ref(false); // 是否正在加载
变量说明:
pageNum:当前请求的页码,用于分页请求pageSize:每页返回的数据条数hasMore:标识是否还有更多数据可以加载loading:防止重复请求的加载状态
2.3 核心函数实现
2.3.1 获取列表数据函数
这是整个功能的核心函数,需要支持首次加载和加载更多两种模式:
javascript
// 获取列表
const getHeadNewsList = (filterCode = null, isLoadMore = false) => {
// 如果正在加载,直接返回已解决的Promise
if (loading.value) {
return Promise.resolve();
}
// 如果是加载更多但没有更多数据,直接返回
if (isLoadMore && !hasMore.value) {
return Promise.resolve();
}
// 如果不是加载更多,重置分页
if (!isLoadMore) {
pageNum.value = 1;
hasMore.value = true;
}
loading.value = true;
const params = {
pageNum: pageNum.value,
pageSize: pageSize.value,
};
if (filterCode !== null) {
params.newsType = filterCode;
}
return homeApi()
.getHeadNewsList(params)
.then((res) => {
console.log(res, "列表");
loading.value = false;
// 接口返回结构:{total: 9999, size: 10, items: [...]}
if (res && res.items && Array.isArray(res.items)) {
if (isLoadMore) {
// 加载更多,追加数据
newsList.value = [...newsList.value, ...res.items];
} else {
// 首次加载或切换筛选,替换数据
newsList.value = res.items;
}
// 判断是否还有更多数据
const total = res.total || 0;
const currentTotal = newsList.value.length;
hasMore.value = currentTotal < total;
// 如果还有更多数据,页码+1,为下次加载做准备
if (hasMore.value && res.items.length > 0) {
pageNum.value += 1;
}
} else {
hasMore.value = false;
}
})
.catch((error) => {
console.error("获取列表失败:", error);
loading.value = false;
throw error;
});
};
关键点解析:
-
防重复请求 :通过
loading.value判断,如果正在加载则直接返回,避免重复请求。 -
数据追加 vs 替换:
isLoadMore = true:追加数据,使用扩展运算符合并数组isLoadMore = false:替换数据,直接赋值新数组
-
分页逻辑:
- 首次加载或刷新时,重置
pageNum = 1 - 加载成功后,如果还有更多数据,页码自动 +1,为下次加载做准备
- 首次加载或刷新时,重置
-
判断是否还有更多数据:
javascriptconst total = res.total || 0; // 总数据量 const currentTotal = newsList.value.length; // 当前已加载的数据量 hasMore.value = currentTotal < total; // 如果已加载数量小于总数,说明还有更多
2.3.2 下拉刷新实现
使用 uni-app 的 onPullDownRefresh 生命周期函数:
javascript
import { onPullDownRefresh } from "@dcloudio/uni-app";
// 下拉刷新
onPullDownRefresh(() => {
console.log("下拉刷新");
// 重置分页
pageNum.value = 1;
hasMore.value = true;
// 重新获取数据
Promise.all([
getBannerList(), // 获取轮播图
getHeadNewsFilter(), // 获取筛选标签
getHeadNewsList(activeFilter.value, false) // 获取新闻列表
]).finally(() => {
// 停止下拉刷新动画
uni.stopPullDownRefresh();
});
});
实现要点:
-
重置状态:刷新时需要重置分页状态,确保从第一页开始加载。
-
并行请求 :使用
Promise.all同时请求多个接口,提高加载效率。 -
停止动画 :无论请求成功还是失败,都要在
finally中调用uni.stopPullDownRefresh()停止刷新动画。
2.3.3 触底加载实现
使用 uni-app 的 onReachBottom 生命周期函数:
javascript
import { onReachBottom } from "@dcloudio/uni-app";
// 触底加载
onReachBottom(() => {
console.log("触底加载");
if (hasMore.value && !loading.value) {
getHeadNewsList(activeFilter.value, true);
}
});
实现要点:
-
条件判断:只有在有更多数据且不在加载中时才触发加载。
-
加载更多模式 :传入
isLoadMore = true,确保数据是追加而不是替换。
2.4 UI 状态展示
在模板中添加加载状态和没有更多数据的提示:
vue
<template>
<!-- 列表 -->
<view class="news-list">
<view
class="news-item"
v-for="(item, index) in newsList"
:key="item.id || index"
@click="handleNewsClick(item)"
>
<!-- 内容 -->
</view>
<!-- 加载状态 -->
<view class="loading-more" v-if="loading">
<text class="loading-text">加载中...</text>
</view>
<!-- 没有更多数据 -->
<view class="no-more" v-if="!hasMore && newsList.length > 0">
<text class="no-more-text">没有更多数据了</text>
</view>
</view>
</template>
样式定义:
css
.loading-more {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0;
}
.loading-text {
font-size: 24rpx;
color: #999999;
}
.no-more {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0;
}
.no-more-text {
font-size: 24rpx;
color: #999999;
}
三、完整代码示例
3.1 Script 部分
javascript
<script setup>
import { ref } from "vue";
import { onLoad, onReachBottom, onPullDownRefresh } from "@dcloudio/uni-app";
import { homeApi } from "@/api/home/index";
// 数据定义
const newsList = ref([]);
const pageNum = ref(1);
const pageSize = ref(10);
const hasMore = ref(true);
const loading = ref(false);
const activeFilter = ref(null);
// 获取列表数据
const getHeadNewsList = (filterCode = null, isLoadMore = false) => {
if (loading.value) {
return Promise.resolve();
}
if (isLoadMore && !hasMore.value) {
return Promise.resolve();
}
if (!isLoadMore) {
pageNum.value = 1;
hasMore.value = true;
}
loading.value = true;
const params = {
pageNum: pageNum.value,
pageSize: pageSize.value,
};
if (filterCode !== null) {
params.newsType = filterCode;
}
return homeApi()
.getHeadNewsList(params)
.then((res) => {
loading.value = false;
if (res && res.items && Array.isArray(res.items)) {
if (isLoadMore) {
newsList.value = [...newsList.value, ...res.items];
} else {
newsList.value = res.items;
}
const total = res.total || 0;
const currentTotal = newsList.value.length;
hasMore.value = currentTotal < total;
if (hasMore.value && res.items.length > 0) {
pageNum.value += 1;
}
} else {
hasMore.value = false;
}
})
.catch((error) => {
console.error("获取列表失败:", error);
loading.value = false;
throw error;
});
};
// 下拉刷新
onPullDownRefresh(() => {
pageNum.value = 1;
hasMore.value = true;
Promise.all([
getBannerList(),
getHeadNewsFilter(),
getHeadNewsList(activeFilter.value, false)
]).finally(() => {
uni.stopPullDownRefresh();
});
});
// 触底加载
onReachBottom(() => {
if (hasMore.value && !loading.value) {
getHeadNewsList(activeFilter.value, true);
}
});
// 页面加载
onLoad(() => {
getBannerList();
getHeadNewsFilter();
getHeadNewsList();
});
</script>