154 lines
2.9 KiB
Vue
154 lines
2.9 KiB
Vue
<template>
|
|
<view class="page">
|
|
<view class="header">
|
|
<text class="title">scroll-view 上拉加载 Demo</text>
|
|
<text class="sub">下拉刷新 + 触底加载更多</text>
|
|
</view>
|
|
|
|
<scroll-view
|
|
class="list-scroll"
|
|
scroll-y
|
|
lower-threshold="80"
|
|
refresher-enabled
|
|
:refresher-triggered="refreshing"
|
|
@refresherrefresh="onRefresh"
|
|
@scrolltolower="onLoadMore"
|
|
>
|
|
<view v-for="item in list" :key="item.id" class="list-item">
|
|
<text class="item-title">{{ item.title }}</text>
|
|
<text class="item-time">{{ item.time }}</text>
|
|
</view>
|
|
|
|
<view v-if="loading" class="tip">加载中...</view>
|
|
<view v-else-if="noMore && list.length > 0" class="tip">没有更多数据了</view>
|
|
<view v-else-if="list.length === 0" class="tip">暂无数据</view>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue';
|
|
|
|
const page = ref(1);
|
|
const pageSize = ref(10);
|
|
const list = ref([]);
|
|
const loading = ref(false);
|
|
const refreshing = ref(false);
|
|
const noMore = ref(false);
|
|
|
|
const totalMockCount = 37;
|
|
const fullData = Array.from({ length: totalMockCount }, (_, i) => {
|
|
const index = i + 1;
|
|
return {
|
|
id: index,
|
|
title: `第 ${index} 条数据`,
|
|
time: `2026-03-${String((index % 28) + 1).padStart(2, '0')}`
|
|
};
|
|
});
|
|
|
|
const getPageData = (pageNum, size) => {
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
const start = (pageNum - 1) * size;
|
|
const end = start + size;
|
|
resolve(fullData.slice(start, end));
|
|
}, 350);
|
|
});
|
|
};
|
|
|
|
const fetchList = async (isRefresh = false) => {
|
|
if (loading.value) return;
|
|
loading.value = true;
|
|
try {
|
|
if (isRefresh) {
|
|
page.value = 1;
|
|
noMore.value = false;
|
|
}
|
|
const rows = await getPageData(page.value, pageSize.value);
|
|
if (isRefresh) {
|
|
list.value = rows;
|
|
} else {
|
|
list.value = [...list.value, ...rows];
|
|
}
|
|
noMore.value = rows.length < pageSize.value;
|
|
} finally {
|
|
loading.value = false;
|
|
refreshing.value = false;
|
|
}
|
|
};
|
|
|
|
const onRefresh = async () => {
|
|
if (refreshing.value) return;
|
|
refreshing.value = true;
|
|
await fetchList(true);
|
|
};
|
|
|
|
const onLoadMore = async () => {
|
|
if (loading.value || refreshing.value || noMore.value) return;
|
|
page.value += 1;
|
|
await fetchList(false);
|
|
};
|
|
|
|
onMounted(() => {
|
|
fetchList(true);
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.page {
|
|
height: 100vh;
|
|
background: #f7f8fa;
|
|
}
|
|
|
|
.header {
|
|
padding: 30rpx;
|
|
background: #ffffff;
|
|
border-bottom: 1rpx solid #ededed;
|
|
}
|
|
|
|
.title {
|
|
display: block;
|
|
font-size: 34rpx;
|
|
color: #222;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.sub {
|
|
display: block;
|
|
margin-top: 8rpx;
|
|
font-size: 24rpx;
|
|
color: #888;
|
|
}
|
|
|
|
.list-scroll {
|
|
height: calc(100vh - 130rpx);
|
|
}
|
|
|
|
.list-item {
|
|
margin: 20rpx 24rpx 0;
|
|
padding: 24rpx;
|
|
background: #ffffff;
|
|
border-radius: 12rpx;
|
|
}
|
|
|
|
.item-title {
|
|
display: block;
|
|
font-size: 30rpx;
|
|
color: #222;
|
|
}
|
|
|
|
.item-time {
|
|
display: block;
|
|
margin-top: 10rpx;
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
|
|
.tip {
|
|
padding: 28rpx 0 40rpx;
|
|
text-align: center;
|
|
color: #9aa0a6;
|
|
font-size: 24rpx;
|
|
}
|
|
</style>
|