|
|
<template> <view class="collection-container"> <!-- 吸顶 Tab --> <view class="tab-sticky"> <my-tabs :tabData="tabData" :defaultIndex="currentIndex" :config="{ textColor: '#333' }" @tabClick="tabClick" /> </view>
<!-- 滑动内容区 --> <swiper class="swiper" :current="currentIndex" :style="{ height: currentSwiperHeight + 'px' }" @animationfinish="onSwiperEnd" @change="onSwiperChange" > <swiper-item v-for="(tabItem, idx) in tabData" :key="idx"> <view class="list-wrapper"> <!-- 加载中 --> <uni-load-more status="loading" v-if="loadingMap[tabItem.status]" />
<!-- 空数据 --> <view class="empty" v-else-if="!listData[tabItem.status] || listData[tabItem.status].length === 0"> 暂无{{ tabItem.label }}收藏 </view>
<!-- 列表 --> <block v-else> <!-- 图书收藏 --> <view class="recommendation-list"> <view class="book-item" v-for="(item, index) in listData.book" :key="index" @click="goToBookDetail(item)" v-if="tabItem.status === 'book'" > <image class="book-cover" :src="item.cover"></image> <view class="book-title">{{ item.title }}</view> </view> </view>
<!-- 活动收藏 --> <view class="activity-item" v-for="(item, index) in listData.activity" :key="index" @click="toActivityDetail(item)" v-if="tabItem.status === 'activity'" > <image class="activity-img" :src="item.imgUrl"></image> <view class="activity-info"> <view class="activity-info-left"> <text class="title">{{ item.title }}</text> <view class="time"> <uni-icons class="time-icon" custom-prefix="iconfont" type="time" size="15"></uni-icons> <text>{{ item.time }}</text> </view> </view> <button class="activity-btn" :class="item.status === 0 ? 'disabled-btn' : ''" type="primary" :disabled="item.status === 0" > {{ item.status === 1 ? '立即参加' : '活动结束' }} </button> </view> </view> </block>
<!-- 加载更多 --> <uni-load-more v-if="listData[tabItem.status] && listData[tabItem.status].length > 0 && !loadingMap[tabItem.status]" :status="loadMoreStatusMap[tabItem.status]" /> </view> </swiper-item> </swiper> </view></template>
<script>import myTabs from "@/components/my-tabs/my-tabs.vue";
export default { components: { myTabs }, data() { return { tabData: [ { label: "图书收藏", status: "book" }, { label: "活动收藏", status: "activity" }, ], currentIndex: 0,
// 数据结构完全和借阅页一致
listData: {}, loadingMap: {}, loadMoreStatusMap: {}, pageMap: {}, sizeMap: {}, hasMoreMap: {},
swiperHeightData: {}, currentSwiperHeight: 400, currentPageScrollTop: 0, isRefreshing: false, }; },
onLoad() { this.initDataStructure(); const firstTab = this.getCurrentTab(); this.getListData(firstTab.status); },
onShow() { const tabIndex = uni.getStorageSync("switch_tab_index"); if (tabIndex !== "") { this.currentIndex = Number(tabIndex); const currentTab = this.getCurrentTab(); this.getListData(currentTab.status); uni.removeStorageSync("switch_tab_index"); } },
onPullDownRefresh() { this.isRefreshing = true; const currentTab = this.getCurrentTab(); this.refreshList(currentTab.status); },
onReachBottom() { const currentTab = this.getCurrentTab(); this.loadMoreList(currentTab.status); },
onPageScroll(res) { this.currentPageScrollTop = res.scrollTop; },
methods: { initDataStructure() { this.tabData.forEach((tab) => { const key = tab.status; this.$set(this.listData, key, []); this.$set(this.loadingMap, key, true); this.$set(this.loadMoreStatusMap, key, ""); this.$set(this.pageMap, key, 1); this.$set(this.sizeMap, key, 10); this.$set(this.hasMoreMap, key, true); }); },
getCurrentTab() { return this.tabData[this.currentIndex]; },
// 获取列表
async getListData(statusKey, isRefresh = false) { if (!isRefresh && this.listData[statusKey]?.length > 0) { this.loadingMap[statusKey] = false; return; }
this.loadingMap[statusKey] = true; this.pageMap[statusKey] = 1;
try { const res = await this.getCollectData(statusKey); this.listData[statusKey] = res; this.hasMoreMap[statusKey] = res.length === this.sizeMap[statusKey]; this.loadMoreStatusMap[statusKey] = this.hasMoreMap[statusKey] ? "" : "no-more"; } catch (err) { this.listData[statusKey] = []; } finally { this.loadingMap[statusKey] = false; this.isRefreshing = false; uni.stopPullDownRefresh(); setTimeout(() => this.calcSwiperHeight(statusKey), 0); } },
// 加载更多
async loadMoreList(statusKey) { if (this.loadingMap[statusKey] || !this.hasMoreMap[statusKey] || this.isRefreshing) return;
this.loadMoreStatusMap[statusKey] = "loading"; this.pageMap[statusKey] += 1;
try { const newData = await this.getCollectData(statusKey); if (newData.length > 0) { this.listData[statusKey] = [...this.listData[statusKey], ...newData]; this.hasMoreMap[statusKey] = newData.length === this.sizeMap[statusKey]; this.loadMoreStatusMap[statusKey] = this.hasMoreMap[statusKey] ? "" : "no-more"; } else { this.hasMoreMap[statusKey] = false; this.loadMoreStatusMap[statusKey] = "no-more"; } } catch (err) { this.loadMoreStatusMap[statusKey] = ""; } setTimeout(() => this.calcSwiperHeight(statusKey), 0); },
// 模拟接口(图书/活动收藏)
getCollectData(type) { return new Promise((resolve) => { setTimeout(() => { if (type === "book") { resolve([ { isbn: "1", title: "JavaScript高级程序设计", cover: "https://qiniu.aiyxlib.com/1606124577077" }, { isbn: "2", title: "Vue3实战", cover: "https://qiniu.aiyxlib.com/1606124577077" }, { isbn: "3", title: "深入理解计算机系统", cover: "https://qiniu.aiyxlib.com/1606124577077" }, { isbn: "1", title: "JavaScript高级程序设计", cover: "https://qiniu.aiyxlib.com/1606124577077" } ]); } else { resolve([ { imgUrl: "https://qiniu.aiyxlib.com/1605060269830", title: "读书分享会", time: "2025-11-03", status: 1 }, { imgUrl: "https://qiniu.aiyxlib.com/1605060269830", title: "作者见面会", time: "2025-10-01", status: 0 }, ]); } }, 300); }); },
refreshList(statusKey) { this.getListData(statusKey, true); },
// 计算swiper高度
calcSwiperHeight(statusKey) { const selector = statusKey === "book" ? ".book-item" : ".activity-item"; const query = uni.createSelectorQuery().in(this); query.selectAll(selector).boundingClientRect((res) => { let total = 200; if (res?.length) total = res.reduce((t, h) => t + h.height + 10, 0); this.swiperHeightData[statusKey] = total; this.currentSwiperHeight = total; }).exec(); },
tabClick(index) { this.currentIndex = index; const tab = this.getCurrentTab(); if (this.currentPageScrollTop > 100) uni.pageScrollTo({ scrollTop: 0, duration: 100 }); if (!this.listData[tab.status]?.length) this.getListData(tab.status); else this.currentSwiperHeight = this.swiperHeightData[tab.status] || 400; },
onSwiperChange(e) { if (e.detail.source === "touch") this.currentIndex = e.detail.current; },
onSwiperEnd() { const tab = this.getCurrentTab(); if (!this.listData[tab.status]?.length) this.getListData(tab.status); else this.currentSwiperHeight = this.swiperHeightData[tab.status] || 400; },
goToBookDetail(item) { uni.navigateTo({ url: `/subpkg/pages/book-detail/book-detail?isbn=${item.isbn}` }); },
toActivityDetail(item) { uni.navigateTo({ url: `/subpkg/pages/activity-detail/activity-detail?title=${item.title}` }); }, },};</script>
<style lang="scss" scoped>.collection-container { background-color: #f5f5f5; min-height: 100vh;
.tab-sticky { position: sticky; top: 0; z-index: 99; background: #fff; }
.swiper { width: 100%; min-height: 300px; }
.list-wrapper { padding: 10px; }
.empty { text-align: center; padding: 100px 0; color: #999; font-size: 14px; } .recommendation-list{ flex-wrap: wrap; justify-content: flex-start; .book-item { margin-bottom: 16px; } }
}
::v-deep .uni-load-more { height: auto !important;}</style>
|