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.
438 lines
13 KiB
438 lines
13 KiB
<template>
|
|
<view class="lending-container">
|
|
<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>
|
|
<!-- @click="goToDetail(item)" -->
|
|
<lending-list-item
|
|
:class="'list-item-' + tabItem.status"
|
|
v-for="(item, index) in listData[tabItem.status]"
|
|
:key="index"
|
|
:data="item"
|
|
:ranking="index + 1"
|
|
/>
|
|
</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";
|
|
import lendingListItem from "@/components/lending-list-item/lending-list-item.vue";
|
|
|
|
export default {
|
|
components: { myTabs, lendingListItem },
|
|
data() {
|
|
return {
|
|
tabData: [
|
|
{ label: "全部", status: "all", apiStatus: -1 },
|
|
{ label: "在借中", status: "lending", apiStatus: 0 },
|
|
{ label: "将过期", status: "expiring", apiStatus: 1 },
|
|
{ label: "已过期", status: "expired", apiStatus: 2 },
|
|
],
|
|
currentIndex: 0,
|
|
listData: {},
|
|
loadingMap: {}, // 首次加载/刷新 loading
|
|
loadMoreStatusMap: {}, // 加载更多状态:loading / no-more / ""
|
|
pageMap: {}, // 分页页码
|
|
sizeMap: {}, // 每页条数
|
|
hasMoreMap: {}, // 是否还有更多
|
|
|
|
swiperHeightData: {},
|
|
currentSwiperHeight: 400,
|
|
currentPageScrollTop: 0,
|
|
isRefreshing: false // 是否正在下拉刷新
|
|
};
|
|
},
|
|
|
|
onLoad() {
|
|
this.initDataStructure();
|
|
// 加载第一个tab的数据
|
|
const firstTab = this.getCurrentTab();
|
|
if (firstTab) {
|
|
this.getListData(firstTab.status);
|
|
}
|
|
},
|
|
|
|
onShow() {
|
|
// 读取要切换的 tabIndex
|
|
const tabIndex = uni.getStorageSync('switch_tab_index');
|
|
|
|
if (tabIndex !== undefined && tabIndex !== '') {
|
|
this.currentIndex = Number(tabIndex);
|
|
|
|
// 加载对应 tab 数据
|
|
const currentTab = this.getCurrentTab();
|
|
if (currentTab) {
|
|
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) {
|
|
const tab = this.tabData.find(item => item.status === statusKey);
|
|
if (!tab) return;
|
|
|
|
if (!isRefresh && this.listData[statusKey]?.length > 0) {
|
|
this.loadingMap[statusKey] = false;
|
|
return;
|
|
}
|
|
|
|
this.loadingMap[statusKey] = true;
|
|
this.pageMap[statusKey] = 1;
|
|
|
|
try {
|
|
const data = await this.fetchBorrowList(tab.apiStatus, this.pageMap[statusKey], this.sizeMap[statusKey]);
|
|
|
|
let list = data.records || [];
|
|
list = list.map(item => {
|
|
// 已归还
|
|
if (item.realityTime) {
|
|
if (new Date(item.realityTime) <= new Date(item.returnTime)) {
|
|
item.returnBook = 2; // 准时归还
|
|
} else {
|
|
item.returnBook = 3; // 逾期归还
|
|
}
|
|
} else {
|
|
// 未归还
|
|
const now = new Date();
|
|
const returnDate = new Date(item.returnTime);
|
|
const diffDay = Math.ceil((returnDate - now) / (1000 * 3600 * 24));
|
|
|
|
if (diffDay < 0) {
|
|
// 1. 已逾期(应还时间 < 当前时间)
|
|
item.returnBook = 3;
|
|
}else if (diffDay <= 3) {
|
|
// 2. 临期(剩余 0~3 天)
|
|
item.returnBook = 1;
|
|
} else {
|
|
// 3. 正常在借
|
|
item.returnBook = 0;
|
|
}
|
|
}
|
|
return item;
|
|
});
|
|
this.listData[statusKey] = list;
|
|
this.hasMoreMap[statusKey] = list.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(async () => {
|
|
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;
|
|
|
|
const tab = this.tabData.find(item => item.status === statusKey);
|
|
try {
|
|
const data = await this.fetchBorrowList(tab.apiStatus, this.pageMap[statusKey], this.sizeMap[statusKey]);
|
|
|
|
let newList = data.records || [];
|
|
|
|
newList = newList.map(item => {
|
|
// 已归还
|
|
if (item.realityTime) {
|
|
if (new Date(item.realityTime) <= new Date(item.returnTime)) {
|
|
item.returnBook = 2; // 准时归还
|
|
} else {
|
|
item.returnBook = 3; // 逾期归还
|
|
}
|
|
} else {
|
|
// 未归还
|
|
const now = new Date();
|
|
const returnDate = new Date(item.returnTime);
|
|
const diffDay = Math.ceil((returnDate - now) / (1000 * 3600 * 24));
|
|
|
|
if (diffDay < 0) {
|
|
// 1. 已逾期(应还时间 < 当前时间)
|
|
item.returnBook = 3;
|
|
}else if (diffDay <= 3) {
|
|
// 2. 临期(剩余 0~3 天)
|
|
item.returnBook = 1;
|
|
} else {
|
|
// 3. 正常在借
|
|
item.returnBook = 0;
|
|
}
|
|
}
|
|
return item;
|
|
});
|
|
|
|
if (newList.length > 0) {
|
|
this.listData[statusKey] = [...this.listData[statusKey], ...newList];
|
|
this.hasMoreMap[statusKey] = newList.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(async () => {
|
|
this.calcSwiperHeight(statusKey);
|
|
}, 0);
|
|
},
|
|
|
|
// 下拉刷新
|
|
refreshList(statusKey) {
|
|
this.getListData(statusKey, true);
|
|
},
|
|
|
|
// 模拟接口(带分页)
|
|
async fetchBorrowList(apiStatus, pageNum, pageSize) {
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
const mock = this.getMockData(apiStatus, pageNum, pageSize);
|
|
resolve({
|
|
records: mock,
|
|
total: mock.length * 3
|
|
});
|
|
}, 500);
|
|
});
|
|
},
|
|
|
|
// 模拟分页数据
|
|
getMockData(apiStatus, pageNum, pageSize) {
|
|
const base = [
|
|
{
|
|
imgCover: "https://qiniu.aiyxlib.com/1606124577077",
|
|
title: "名侦探柯南",
|
|
nickname: "青山刚昌",
|
|
publish: "长春出版社",
|
|
isbn: "1001",
|
|
desc: '精心提炼20种GPT提问方法及指令,从入门到进阶再到精通,100个。',
|
|
startTime: "2026-03-01",
|
|
returnTime: "2026-04-30", // 应还时间:还有10天左右 → 在借中
|
|
realityTime: ""
|
|
},
|
|
{
|
|
imgCover: "https://qiniu.aiyxlib.com/1606124577077",
|
|
title: "三体",
|
|
nickname: "刘慈慈",
|
|
publish: "重庆出版社",
|
|
isbn: "1002",
|
|
desc: '精心提炼20种GPT提问方法及指令,从入门到进阶再到精通,100个。',
|
|
startTime: "2026-03-10",
|
|
returnTime: "2026-04-22", // 应还时间:剩2天 → 临期
|
|
realityTime: ""
|
|
},
|
|
{
|
|
imgCover: "https://qiniu.aiyxlib.com/1606124577077",
|
|
title: "红楼梦",
|
|
nickname: "曹雪芹",
|
|
publish: "人民文学",
|
|
isbn: "1003",
|
|
desc: '精心提炼20种GPT提问方法及指令,从入门到进阶再到精通,100个。',
|
|
startTime: "2026-02-01",
|
|
returnTime: "2026-03-25", // 已过期 → 逾期
|
|
realityTime: ""
|
|
},
|
|
{
|
|
imgCover: "https://qiniu.aiyxlib.com/1606124577077",
|
|
title: "西游记",
|
|
nickname: "吴承恩",
|
|
publish: "中华书局",
|
|
isbn: "1004",
|
|
desc: '精心提炼20种GPT提问方法及指令,从入门到进阶再到精通,100个。',
|
|
startTime: "2026-02-10",
|
|
returnTime: "2026-04-01",
|
|
realityTime: "2026-03-31" // 提前归还 → 准时
|
|
}
|
|
];
|
|
|
|
if (pageNum >= 3) { // 第3页开始返回空
|
|
return [];
|
|
}
|
|
|
|
const list = [...base];
|
|
if (pageNum > 1) {
|
|
list.push(...base.map(item => ({ ...item, isbn: item.isbn + pageNum })));
|
|
}
|
|
|
|
switch (apiStatus) {
|
|
case -1: return list;
|
|
case 0: return list;
|
|
case 1: return [list[0], list[1]];
|
|
case 2: return [];
|
|
default: return [];
|
|
}
|
|
},
|
|
|
|
// 计算swiper高度
|
|
calcSwiperHeight(statusKey) {
|
|
const selector = `.list-item-${statusKey}`;
|
|
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 + 8, 0);
|
|
}
|
|
this.swiperHeightData[statusKey] = total;
|
|
this.currentSwiperHeight = total;
|
|
}).exec();
|
|
},
|
|
|
|
// tab切换
|
|
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;
|
|
}
|
|
},
|
|
|
|
goToDetail(item) {
|
|
uni.navigateTo({
|
|
url: `/subpkg/pages/book-detail/book-detail?isbn=${item.isbn}`
|
|
});
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.lending-container {
|
|
background-color: #f5f5f5;
|
|
min-height: 100vh;
|
|
|
|
.tab-sticky {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 99;
|
|
background: #fff;
|
|
}
|
|
|
|
.swiper {
|
|
width: 100%;
|
|
min-height: 300px;
|
|
}
|
|
|
|
.swiper-item {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.list-wrapper {
|
|
padding: 10px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.empty {
|
|
text-align: center;
|
|
padding: 100px 0;
|
|
color: #999;
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
::v-deep .uni-load-more{
|
|
height: auto !important;
|
|
}
|
|
</style>
|