feat:星榜新增翻页功能

This commit is contained in:
zerosaturation 2026-06-11 13:14:38 +08:00
parent c90dcf3d36
commit 37fbcb42c6
3 changed files with 147 additions and 16 deletions

View File

@ -26,7 +26,7 @@
</view>
<!-- 区域 B分类标签 -->
<view
<!-- <view
ref="categoryRef"
id="category-section"
class="category-section"
@ -43,7 +43,7 @@
<text class="category-text">{{ category.label }}</text>
</view>
</scroll-view>
</view>
</view> -->
<!-- fixed 时占位避免下方内容跳变 -->
<view
v-if="isFixed && categoryHeight > 0"

View File

@ -30,7 +30,14 @@
scroll-y
:show-scrollbar="false"
:bounce="false"
:lower-threshold="80"
:scroll-into-view="scrollIntoView"
:scroll-with-animation="false"
@scrolltolower="handleScrollToLower"
>
<!-- 顶部哨兵切换 tab 时通过 scroll-into-view 把列表强制滚回顶部 -->
<view id="grid-top" class="scroll-anchor"></view>
<!-- 骨架屏 -->
<view v-if="loading" class="grid-skeleton">
<view v-for="i in 11" :key="i" class="skeleton-card">
@ -120,13 +127,33 @@
</view>
</view>
</view>
<!-- 分页底部状态加载中 / 没有更多了 / 暂无数据 -->
<view
v-if="loadingMore"
class="load-more-tip load-more-tip-loading"
>
<text class="load-more-text">加载中...</text>
</view>
<view
v-else-if="!hasMore && items.length > 0"
class="load-more-tip"
>
<text class="load-more-text"> 没有更多了 </text>
</view>
<view
v-else-if="!loading && items.length === 0"
class="load-more-tip"
>
<text class="load-more-text">暂无数据</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { getHotRankingApi } from "@/utils/api.js";
import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js";
@ -149,8 +176,24 @@ const loading = ref(false);
const likingMap = ref({});
const activeTabKey = ref("");
// ===== =====
// currentPage : 1
// hasMore : < PAGE_SIZE >= total false
// loadingMore : scrolltolower
const currentPage = ref(1);
const hasMore = ref(true);
const loadingMore = ref(false);
// scroll-into-view id tab 'grid-top' scroll-view
// nextTick id 使 id
const scrollIntoView = ref("");
//
const PAGE_SIZE = 10;
// Tab
// tab push { key, label, icon, iconWidth, iconHeight, fetch }
// fetch page
const tabs = [
{
key: "hot",
@ -158,7 +201,7 @@ const tabs = [
icon: "/static/square/galaxy/dianzanbang.png",
iconWidth: 64,
iconHeight: 72,
fetch: () => getHotRankingApi("displaying", null, 1, 11),
fetch: (page) => getHotRankingApi("displaying", null, page, PAGE_SIZE),
},
{
key: "new",
@ -166,7 +209,7 @@ const tabs = [
icon: "/static/square/galaxy/huoyuebang.png",
iconWidth: 64,
iconHeight: 72,
fetch: () => getHotRankingApi("displaying", null, 1, 11),
fetch: (page) => getHotRankingApi("displaying", null, page, PAGE_SIZE),
},
{
key: "trending",
@ -174,7 +217,7 @@ const tabs = [
icon: "/static/square/galaxy/baoguangbang.png",
iconWidth: 64,
iconHeight: 72,
fetch: () => getHotRankingApi("displaying", null, 1, 11),
fetch: (page) => getHotRankingApi("displaying", null, page, PAGE_SIZE),
},
{
key: "tongcheng",
@ -182,7 +225,7 @@ const tabs = [
icon: "/static/square/galaxy/tongchengbang.png",
iconWidth: 64,
iconHeight: 72,
fetch: () => getHotRankingApi("displaying", null, 1, 11),
fetch: (page) => getHotRankingApi("displaying", null, page, PAGE_SIZE),
},
];
@ -226,9 +269,9 @@ const handleTabClick = (e) => {
}
};
// activeTab
// activeTab
watch(activeTab, () => {
loadData();
resetAndLoad();
});
//
@ -265,20 +308,41 @@ const onAssetLiked = ({ asset_id, data }) => {
}
};
// 1
// tab items currentPage 1hasMore true
const resetAndLoad = () => {
currentPage.value = 1;
hasMore.value = true;
loadingMore.value = false;
items.value = [];
// id 'grid-top'
scrollIntoView.value = "";
nextTick(() => {
scrollIntoView.value = "grid-top";
});
loadData({ append: false });
};
//
const loadData = async () => {
// append=false itemsappend=true items
const loadData = async ({ append = false } = {}) => {
const tab = activeTab.value;
if (!tab || typeof tab.fetch !== "function") {
console.warn("[HotCategoryBlock] 当前 tab 未配置 fetch:", tab);
items.value = [];
if (!append) items.value = [];
return;
}
loading.value = true;
// loading loadingMore
if (append) {
loadingMore.value = true;
} else {
loading.value = true;
}
try {
const res = await tab.fetch();
const res = await tab.fetch(currentPage.value);
if (res && res.code === 200 && res.data?.items) {
// cover_url 访 URLOSS
items.value = await Promise.all(
const newItems = await Promise.all(
res.data.items.map(async (item) => {
return await resolveItemUrls({
...item,
@ -286,17 +350,42 @@ const loadData = async () => {
});
}),
);
} else {
if (append) {
items.value = [...items.value, ...newItems];
} else {
items.value = newItems;
}
//
// 1) total
// 2)
const total = Number(res.data.total ?? 0);
if (total > 0) {
hasMore.value = items.value.length < total;
} else {
hasMore.value = newItems.length >= PAGE_SIZE;
}
} else if (!append) {
items.value = [];
hasMore.value = false;
}
} catch (e) {
console.error("[HotCategoryBlock] 加载数据失败", e?.message ?? e);
items.value = [];
if (!append) items.value = [];
} finally {
loading.value = false;
loadingMore.value = false;
}
};
//
const handleScrollToLower = () => {
if (loading.value || loadingMore.value || !hasMore.value) return;
currentPage.value += 1;
loadData({ append: true });
};
onMounted(() => {
uni.$on("assetLiked", onAssetLiked);
loadData();
@ -754,4 +843,45 @@ onUnmounted(() => {
transform: scale(1.5);
}
}
/* 分页底部加载提示 */
.load-more-tip {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 16rpx 0 8rpx;
position: relative;
z-index: 3;
}
/* 滚动哨兵:仅作为 scroll-into-view 的锚点,不占据视觉空间 */
.scroll-anchor {
width: 100%;
height: 0;
overflow: hidden;
}
.load-more-text {
font-size: 22rpx;
color: #fffabd;
text-shadow: -1px 1px 4px #ce0909d6;
opacity: 0.85;
letter-spacing: 1rpx;
}
.load-more-tip-loading .load-more-text {
animation: loadMorePulse 1s ease-in-out infinite;
}
@keyframes loadMorePulse {
0%,
100% {
opacity: 0.5;
}
50% {
opacity: 1;
}
}
</style>

View File

@ -360,6 +360,7 @@ onUnmounted(() => {
position: relative;
/* 100vh - header(208rpx) - banner(360rpx) - tabs(88rpx) = 可用 body 高度 */
height: calc(100vh - 208rpx - 360rpx - 88rpx);
top: -8rpx;
}
/* 蒙层 */