下面给你 两种常见实现方式 的完整示例代码与注意点 —— 都基于 uni-app(Vue 风格)。
- 方法 A:点击“加载更多”按钮(用户主动点击)
- 方法 B:滚动到底部自动加载(onReachBottom / scroll-view 的 scrolltolower)
示例包含:数据结构、请求示例、去重/节流、加载状态、无更多提示、错误处理。可直接复制到 pages/list/list.vue
里运行(替换为你自己的接口)。
后端约定(示例)
假设后端分页接口 /api/items
支持 page
、pageSize
参数,返回示例:
{
"code": 0,
"data": {
"items": [{ "id":1, "title":"A" }, ...],
"total": 123
}
}
方法 A:点击“加载更多”按钮(适合用户主动加载)
<template>
<view class="page">
<view class="list">
<block v-for="item in items" :key="item.id">
<view class="item">{{ item.title }}</view>
</block>
<!-- 空数据提示 -->
<view v-if="items.length === 0 && !loading" class="empty">暂无数据</view>
<!-- 加载中指示 -->
<view v-if="loading" class="loading">加载中...</view>
<!-- 错误提示 -->
<view v-if="error" class="error">{{ error }}</view>
<!-- 加载更多按钮 / 无更多提示 -->
<view class="more-area">
<button v-if="!noMore && !loading" @click="loadMore">加载更多</button>
<view v-else-if="noMore" class="no-more">没有更多了</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
items: [],
page: 1,
pageSize: 10,
loading: false,
noMore: false,
total: 0,
error: null,
// 防止重复请求的标志
isRequesting: false
};
},
onLoad() {
// 首次加载第一页
this.fetchPage(1);
},
methods: {
async fetchPage(page) {
if (this.isRequesting) return;
this.isRequesting = true;
this.loading = true;
this.error = null;
try {
const res = await uni.request({
url: 'https://api.example.com/items',
method: 'GET',
data: {
page,
pageSize: this.pageSize
}
});
// 假设后端返回 res[1].data
const body = res[1].data;
if (body.code !== 0) {
throw new Error(body.message || '服务器返回错误');
}
const { items: newItems, total } = body.data;
this.total = total ?? this.total;
if (page === 1) {
this.items = newItems;
} else {
this.items = this.items.concat(newItems);
}
this.page = page;
// 判断是否到尾
if (this.items.length >= this.total || newItems.length < this.pageSize) {
this.noMore = true;
} else {
this.noMore = false;
}
} catch (e) {
console.error('fetchPage error', e);
this.error = e.message || '请求失败';
} finally {
this.loading = false;
this.isRequesting = false;
}
},
// 点击加载下一页
loadMore() {
if (this.noMore || this.loading) return;
this.fetchPage(this.page + 1);
},
// 下拉刷新(可选)
async onPullDownRefresh() {
await this.fetchPage(1);
uni.stopPullDownRefresh();
}
}
};
</script>
<style>
.page { padding: 16px; }
.item { padding: 12px; border-bottom: 1px solid #eee; }
.loading, .no-more, .empty, .error { text-align:center; padding:12px; color:#888; }
.more-area { display:flex; justify-content:center; padding:12px; }
button { padding:8px 20px; }
</style>
方法 B:滚动到底部自动加载(适合长列表、无限滚动)
有两种实现:
- 页面级
onReachBottom()
(H5、小程序都支持) - 使用
<scroll-view @scrolltolower>
(更灵活,可放固定高度区块)
页面级 onReachBottom
示例(推荐简洁):
<template>
<view class="page">
<view class="list">
<block v-for="item in items" :key="item.id">
<view class="item">{{ item.title }}</view>
</block>
<view v-if="loading" class="loading">加载中...</view>
<view v-if="noMore && items.length>0" class="no-more">没有更多了</view>
<view v-if="items.length===0 && !loading" class="empty">暂无数据</view>
</view>
</view>
</template>
<script>
export default {
data() {
return { items: [], page: 1, pageSize: 10, loading: false, noMore: false, total: 0, isRequesting: false };
},
onLoad() { this.fetchPage(1); },
onReachBottom() {
// 页面滚动到底触发(H5 在某些平台需要配置)
if (!this.noMore && !this.loading) this.fetchPage(this.page + 1);
},
methods: {
async fetchPage(page) {
if (this.isRequesting) return;
this.isRequesting = true;
this.loading = true;
try {
const res = await uni.request({
url: 'https://api.example.com/items',
method: 'GET',
data: { page, pageSize: this.pageSize }
});
const body = res[1].data;
if (body.code !== 0) throw new Error(body.message || '服务器返回错误');
const { items: newItems, total } = body.data;
this.total = total ?? this.total;
if (page === 1) this.items = newItems;
else this.items = this.items.concat(newItems);
this.page = page;
if (this.items.length >= this.total || newItems.length < this.pageSize) this.noMore = true;
} catch (e) {
console.error(e);
} finally {
this.loading = false;
this.isRequesting = false;
}
}
}
};
</script>
<style>
.item { padding: 10px; border-bottom: 1px solid #eee; }
.loading, .no-more, .empty { text-align:center; padding:12px; color:#888; }
</style>
使用 <scroll-view>
的 scrolltolower
(局部滚动区域)
<template>
<scroll-view class="scroll" scroll-y="true" :scroll-top="0" @scrolltolower="onScrollToLower" lower-threshold="100">
<block v-for="item in items" :key="item.id">
<view class="item">{{ item.title }}</view>
</block>
<view v-if="loading" class="loading">加载中...</view>
<view v-if="noMore" class="no-more">没有更多了</view>
</scroll-view>
</template>
<script>
export default {
data() { return { items: [], page:1, pageSize:10, loading:false, noMore:false, isRequesting:false } },
onLoad() { this.fetchPage(1); },
methods: {
onScrollToLower() {
if (!this.noMore && !this.loading) this.fetchPage(this.page+1);
},
async fetchPage(page){ /* 同上 */ }
}
}
</script>
<style>
.scroll { height: 80vh; }
.item { padding: 12px; border-bottom: 1px solid #f0f0f0; }
</style>
常见注意点(实战要点)
- 防止重复请求:用
isRequesting
/loading
标志阻止重复触发。 - 节流/去抖:快速滚动或连点时建议对触发函数做节流(lodash/throttle)。
- 总数判断:如果返回
total
,用items.length >= total
判断无更多;否则根据newItems.length < pageSize
判断。 - 空数据处理:列表为空时显示“暂无数据”。
- 错误与重试:请求失败时显示错误,允许用户重试。
- 体验优化:加载骨架屏、占位、上拉加载动画。
- 兼容性:微信小程序
onReachBottom
在页面配置与模板中正常支持;H5 下需注意页面高度与滚动容器(推荐使用scroll-view
或监听页面滚动)。 - 后端支持:后端分页接口要幂等且稳定,避免重复返回数据或顺序不一致。建议后端返回
page
、pageSize
、total
、items
。
发表回复