图书馆小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

317 lines
9.4 KiB

<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>