21 changed files with 1834 additions and 1097 deletions
-
72api/book.js
-
8components/book-list-item/book-list-item.vue
-
111components/lending-list-item/lending-list-item.vue
-
6pages.json
-
35pages/home/home.vue
-
273pages/lendCar/lendCar copy.vue
-
293pages/lendCar/lendCar.vue
-
279pages/search/search.vue
-
1pages/user/user.vue
-
4static/iconfont.css
-
BINstatic/iconfont.ttf
-
BINstatic/images/default-book.png
-
157subpkg/pages/activity-list/activity-list copy.vue
-
125subpkg/pages/activity-list/activity-list.vue
-
204subpkg/pages/book-detail/book-detail.vue
-
8subpkg/pages/book-list/book-list.vue
-
76subpkg/pages/feedback-list/feedback-list.vue
-
335subpkg/pages/myLending/myLending copy.vue
-
538subpkg/pages/myLending/myLending.vue
-
217subpkg/pages/ranking/ranking.vue
-
189subpkg/pages/unbind-card/unbind-card.vue
@ -0,0 +1,273 @@ |
|||||
|
<template> |
||||
|
<view class="item-container"> |
||||
|
<!-- 图书数量 + 右侧设置按钮 --> |
||||
|
<view class="count-text"> |
||||
|
<text>图书数量 ({{ bookList.length }})</text> |
||||
|
<text class="edit-btn" @click="toggleEdit">{{ editMode ? "完成" : "管理" }}</text> |
||||
|
</view> |
||||
|
|
||||
|
<checkbox-group @change="checkboxChange"> |
||||
|
<view class="car-list" v-for="item in bookList" :key="item.isbn"> |
||||
|
<checkbox :value="item.isbn" :checked="item.checked" /> |
||||
|
<view class="book-item-box"> |
||||
|
<view class="item-box-left"> |
||||
|
<image class="img-item" :src="defaultCover" mode="aspectFill" /> |
||||
|
</view> |
||||
|
<view class="item-box-right"> |
||||
|
<view class="item-title line-clamp-2">{{ item.title || '暂无标题' }}</view> |
||||
|
<view class="tag-box"> |
||||
|
<text class="item-author">{{ item.author || '佚名' }}</text> |
||||
|
</view> |
||||
|
<view class="item-desc"> |
||||
|
应还时间:{{ item.returndate || '暂无' }} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</checkbox-group> |
||||
|
|
||||
|
<view class="bottom-placeholder"></view> |
||||
|
|
||||
|
<view class="car-bottom"> |
||||
|
<!-- 全选 --> |
||||
|
<view class="all-check" @click="handleAllCheck"> |
||||
|
<checkbox :checked="isAllChecked" /> |
||||
|
<text style="margin-left:6px">全选</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 根据编辑状态切换按钮:续借 / 删除 --> |
||||
|
<button |
||||
|
class="join-btn" |
||||
|
@click="editMode ? handleDelete() : handleRenew()" |
||||
|
:disabled="!hasChecked" |
||||
|
> |
||||
|
{{ editMode ? "删除选中" : "一键续借" }} |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { FetchInitScreenSetting } from '@/api/user'; |
||||
|
import { FetchRdloanlist, FetchRenewbook } from '@/api/book'; |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
editMode: false, |
||||
|
bookList: [], |
||||
|
defaultCover: 'https://qiniu.aiyxlib.com/1606124577077', |
||||
|
screenConfig: {}, |
||||
|
// 用数组维护选中项(更稳定) |
||||
|
checkedValues: [] |
||||
|
}; |
||||
|
}, |
||||
|
onLoad() { |
||||
|
this.getConfigAndList(); |
||||
|
}, |
||||
|
computed: { |
||||
|
isAllChecked() { |
||||
|
return this.bookList.length > 0 && this.checkedValues.length === this.bookList.length; |
||||
|
}, |
||||
|
hasChecked() { |
||||
|
return this.checkedValues.length > 0; |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
async getConfigAndList() { |
||||
|
try { |
||||
|
const res = await FetchInitScreenSetting({ libcode: '1201' }); |
||||
|
this.screenConfig = { |
||||
|
thirdUrl: res.data.open_lib_http?.context || '', |
||||
|
thirdAppid: res.data.open_lib_appId?.context || '', |
||||
|
thirdSecret: res.data.open_lib_secret?.context || '', |
||||
|
sm4Key: res.data.sm4_key?.context || '' |
||||
|
}; |
||||
|
this.getLendingList(); |
||||
|
} catch (err) {} |
||||
|
}, |
||||
|
|
||||
|
async getLendingList() { |
||||
|
try { |
||||
|
const params = { |
||||
|
...this.screenConfig, |
||||
|
rdid: uni.getStorageSync('currentReaderCard'), |
||||
|
}; |
||||
|
const res = await FetchRdloanlist(params); |
||||
|
const result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data; |
||||
|
this.bookList = result.loanlist || []; |
||||
|
// 切换列表时清空选中 |
||||
|
this.checkedValues = []; |
||||
|
} catch (err) { |
||||
|
uni.showToast({ title: '加载失败', icon: 'none' }); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
toggleEdit() { |
||||
|
this.editMode = !this.editMode; |
||||
|
if (!this.editMode) { |
||||
|
this.checkedValues = []; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// ✅ 修复2:稳定的 checkbox 变更 |
||||
|
checkboxChange(e) { |
||||
|
// 直接保存数组(字符串类型) |
||||
|
this.checkedValues = e.detail.value; |
||||
|
}, |
||||
|
|
||||
|
handleAllCheck() { |
||||
|
if (this.isAllChecked) { |
||||
|
// 取消全选 |
||||
|
this.checkedValues = []; |
||||
|
} else { |
||||
|
// 全选:把所有 isbn 转字符串放进数组 |
||||
|
this.checkedValues = this.bookList.map(item => String(item.isbn)); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
async handleRenew() { |
||||
|
// 从 checkedValues 过滤出对应图书 |
||||
|
const checkedBooks = this.bookList.filter(item => |
||||
|
this.checkedValues.includes(String(item.isbn)) |
||||
|
); |
||||
|
|
||||
|
if (checkedBooks.length === 0) { |
||||
|
uni.showToast({ title: '请选择要续借的图书', icon: 'none' }); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const barcodes = checkedBooks.map(item => item.barcode).join('|'); |
||||
|
uni.showLoading({ title: '续借中...' }); |
||||
|
|
||||
|
try { |
||||
|
const params = { |
||||
|
...this.screenConfig, |
||||
|
rdid: uni.getStorageSync('currentReaderCard'), |
||||
|
barcode: barcodes, |
||||
|
logtype: '30007' |
||||
|
}; |
||||
|
console.log('续借参数', params); |
||||
|
await FetchRenewbook(params); |
||||
|
|
||||
|
uni.hideLoading(); |
||||
|
uni.showToast({ |
||||
|
title: `成功续借 ${checkedBooks.length} 本`, |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
this.getLendingList(); |
||||
|
} catch (err) { |
||||
|
uni.hideLoading(); |
||||
|
uni.showToast({ title: '续借失败', icon: 'none' }); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
handleDelete() { |
||||
|
// 只保留未选中的 |
||||
|
this.bookList = this.bookList.filter(item => |
||||
|
!this.checkedValues.includes(String(item.isbn)) |
||||
|
); |
||||
|
uni.showToast({ |
||||
|
title: `已删除 ${this.checkedValues.length} 本`, |
||||
|
icon: "success" |
||||
|
}); |
||||
|
this.checkedValues = []; |
||||
|
this.editMode = false; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scss> |
||||
|
.item-container { |
||||
|
padding: 10px; |
||||
|
background: #f5f6f7; |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
.count-text { |
||||
|
margin-bottom: 10px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.edit-btn { |
||||
|
color: #01a4fe; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
.car-list { |
||||
|
display: flex; |
||||
|
align-items: flex-start; |
||||
|
margin-bottom: 10px; |
||||
|
checkbox { |
||||
|
margin-top: 15px; |
||||
|
padding: 0 10px; |
||||
|
} |
||||
|
} |
||||
|
.book-item-box { |
||||
|
flex: 1; |
||||
|
background: #fff; |
||||
|
border-radius: 8px; |
||||
|
padding: 12px; |
||||
|
display: flex; |
||||
|
.item-box-left { |
||||
|
margin-right: 12px; |
||||
|
.img-item { |
||||
|
width: 64px; |
||||
|
height: 90px; |
||||
|
border-radius: 6px; |
||||
|
} |
||||
|
} |
||||
|
.item-box-right { |
||||
|
flex: 1; |
||||
|
} |
||||
|
.item-title { |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 4px; |
||||
|
} |
||||
|
.item-author { |
||||
|
font-size: 12px; |
||||
|
background: #f4f6fc; |
||||
|
padding: 2px 6px; |
||||
|
border-radius: 4px; |
||||
|
margin-right: 6px; |
||||
|
} |
||||
|
.item-desc { |
||||
|
font-size: 12px; |
||||
|
color: #999; |
||||
|
margin-top: 6px; |
||||
|
} |
||||
|
} |
||||
|
.bottom-placeholder { |
||||
|
height: 60px; |
||||
|
} |
||||
|
.car-bottom { |
||||
|
position: fixed; |
||||
|
left: 0; |
||||
|
bottom: 0; |
||||
|
right: 0; |
||||
|
background: #fff; |
||||
|
padding: 12px 15px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
.all-check { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.join-btn { |
||||
|
font-size: 14px; |
||||
|
color: #fff; |
||||
|
margin: 0 !important; |
||||
|
background-color: #01a4fe !important; |
||||
|
border-radius: 23px; |
||||
|
padding: 0 30px; |
||||
|
&::after{ |
||||
|
border: none !important; |
||||
|
} |
||||
|
&[disabled] { |
||||
|
background: #ccc !important; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
|
After Width: 756 | Height: 1063 | Size: 8.1 KiB |
@ -0,0 +1,157 @@ |
|||||
|
<template> |
||||
|
<view class="activity-list"> |
||||
|
<!-- 下拉刷新区域 --> |
||||
|
<scroll-view |
||||
|
scroll-y |
||||
|
refresher-enabled |
||||
|
:refresher-triggered="refreshing" |
||||
|
@refresherrefresh="onRefresh" |
||||
|
@scrolltolower="onLoadMore" |
||||
|
lower-threshold="100" |
||||
|
> |
||||
|
<view class="activity-item" v-for="(item, index) in activityList" @click="toActivityDetail(item)" :key="index"> |
||||
|
<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="icon-shijian" 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> |
||||
|
|
||||
|
<!-- 加载提示 --> |
||||
|
<view class="load-tip" v-if="loading"> |
||||
|
<text>加载中...</text> |
||||
|
</view> |
||||
|
<view class="load-tip" v-if="noMore"> |
||||
|
<text>没有更多数据了</text> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
activityList: [], // 活动列表数据 |
||||
|
refreshing: false, // 下拉刷新状态 |
||||
|
loading: false, // 加载中 |
||||
|
noMore: false, // 是否没有更多数据 |
||||
|
page: 1, // 当前页码 |
||||
|
pageSize: 5, // 每页条数 |
||||
|
total: 20, // 模拟总数据量 |
||||
|
}; |
||||
|
}, |
||||
|
onLoad() { |
||||
|
// 页面加载时获取第一页数据 |
||||
|
this.getActivityList(); |
||||
|
}, |
||||
|
methods: { |
||||
|
/** |
||||
|
* 模拟请求活动列表接口 |
||||
|
* @param {Number} page 页码 |
||||
|
* @param {Number} pageSize 每页条数 |
||||
|
*/ |
||||
|
getActivityList() { |
||||
|
this.loading = true; |
||||
|
|
||||
|
// 模拟网络请求延迟 |
||||
|
setTimeout(() => { |
||||
|
// 生成模拟数据 |
||||
|
const list = this.mockData(this.page, this.pageSize); |
||||
|
|
||||
|
// 下拉刷新:直接替换数据 |
||||
|
if (this.refreshing) { |
||||
|
this.activityList = list; |
||||
|
this.refreshing = false; |
||||
|
} else { |
||||
|
// 上拉加载:追加数据 |
||||
|
this.activityList = [...this.activityList, ...list]; |
||||
|
} |
||||
|
|
||||
|
// 判断是否还有更多数据 |
||||
|
if (this.activityList.length >= this.total) { |
||||
|
this.noMore = true; |
||||
|
} else { |
||||
|
this.noMore = false; |
||||
|
} |
||||
|
|
||||
|
this.loading = false; |
||||
|
}, 1000); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 模拟生成活动数据 |
||||
|
*/ |
||||
|
mockData(page, pageSize) { |
||||
|
let arr = []; |
||||
|
for (let i = 0; i < pageSize; i++) { |
||||
|
const num = (page - 1) * pageSize + i + 1; |
||||
|
arr.push({ |
||||
|
imgUrl: "https://qiniu.aiyxlib.com/1605060269830", |
||||
|
title: `活动标题${num}`, |
||||
|
time: "2025-11-03 00:00 ~2025-11-09 00:00", |
||||
|
status: num % 3 === 0 ? 0 : 1, // 0=结束 1=可参加 |
||||
|
}); |
||||
|
} |
||||
|
return arr; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 下拉刷新 |
||||
|
*/ |
||||
|
onRefresh() { |
||||
|
// 重置状态 |
||||
|
this.page = 1; |
||||
|
this.noMore = false; |
||||
|
this.refreshing = true; |
||||
|
this.getActivityList(); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 上拉加载更多 |
||||
|
*/ |
||||
|
onLoadMore() { |
||||
|
if (this.loading || this.noMore) return; |
||||
|
this.page++; |
||||
|
this.getActivityList(); |
||||
|
}, |
||||
|
toActivityDetail(item){ |
||||
|
uni.navigateTo({ |
||||
|
url: "/subpkg/pages/activity-detail/activity-detail?title=" + item.title |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.activity-list { |
||||
|
padding: 20px; |
||||
|
height: 100vh; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
scroll-view { |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.load-tip { |
||||
|
text-align: center; |
||||
|
padding: 20px; |
||||
|
color: #999; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,335 @@ |
|||||
|
<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> |
||||
|
<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 { FetchInitScreenSetting } from '@/api/user'; |
||||
|
import { FetchHistoryloan, FetchRdloanlist } from '@/api/book'; |
||||
|
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 { |
||||
|
screenConfig: {}, |
||||
|
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: {}, |
||||
|
loadMoreStatusMap: {}, |
||||
|
pageMap: {}, |
||||
|
sizeMap: {}, |
||||
|
hasMoreMap: {}, |
||||
|
|
||||
|
swiperHeightData: {}, |
||||
|
currentSwiperHeight: 400, |
||||
|
currentPageScrollTop: 0, |
||||
|
isRefreshing: false |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
onLoad() { |
||||
|
this.getScreenSetting(); |
||||
|
this.initDataStructure(); |
||||
|
}, |
||||
|
|
||||
|
onShow() { |
||||
|
const tabIndex = uni.getStorageSync('switch_tab_index'); |
||||
|
if (tabIndex !== undefined && tabIndex !== '') { |
||||
|
this.currentIndex = Number(tabIndex); |
||||
|
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: { |
||||
|
async getScreenSetting() { |
||||
|
try { |
||||
|
const res = await FetchInitScreenSetting({ libcode: '1201' }); |
||||
|
const data = res.data; |
||||
|
this.screenConfig = { |
||||
|
thirdUrl: data.open_lib_http?.context || '', |
||||
|
thirdAppid: data.open_lib_appId?.context || '', |
||||
|
thirdSecret: data.open_lib_secret?.context || '', |
||||
|
sm4Key: data.sm4_key?.context || '' |
||||
|
}; |
||||
|
} catch (err) { |
||||
|
console.error('获取配置失败:', err); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 获取 在借中 列表(字段已对齐) |
||||
|
async getRealBorrowList(statusKey) { |
||||
|
this.loadingMap[statusKey] = true; |
||||
|
try { |
||||
|
const rdid = uni.getStorageSync('currentReaderCard'); |
||||
|
const params = { ...this.screenConfig, rdid }; |
||||
|
const res = await FetchRdloanlist(params); |
||||
|
|
||||
|
const result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data; |
||||
|
let loanList = result.loanlist || []; |
||||
|
|
||||
|
// 对齐字段:loandate → loantime,returndate → returntime |
||||
|
loanList = loanList.map(item => { |
||||
|
const loantime = item.loandate || ''; |
||||
|
const returntime = item.returndate || ''; |
||||
|
|
||||
|
const now = new Date(); |
||||
|
const returnDate = new Date(returntime); |
||||
|
const diffDay = Math.ceil((returnDate - now) / (1000 * 3600 * 24)); |
||||
|
|
||||
|
let returnBook = 0; |
||||
|
if (diffDay < 0) { |
||||
|
returnBook = 3; |
||||
|
} else if (diffDay <= 3) { |
||||
|
returnBook = 1; |
||||
|
} else { |
||||
|
returnBook = 0; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
...item, |
||||
|
loantime, |
||||
|
returntime, |
||||
|
startTime: loantime, |
||||
|
returnTime: returntime, |
||||
|
returnBook |
||||
|
}; |
||||
|
}); |
||||
|
console.log('当前借阅', loanList); |
||||
|
this.listData['lending'] = loanList; |
||||
|
this.listData['expiring'] = loanList.filter(i => i.returnBook === 1); |
||||
|
this.listData['expired'] = loanList.filter(i => i.returnBook === 3); |
||||
|
|
||||
|
this.hasMoreMap[statusKey] = false; |
||||
|
this.loadMoreStatusMap[statusKey] = "no-more"; |
||||
|
|
||||
|
} catch (err) { |
||||
|
console.error('获取在借列表失败', err); |
||||
|
this.listData[statusKey] = []; |
||||
|
} finally { |
||||
|
this.loadingMap[statusKey] = false; |
||||
|
this.isRefreshing = false; |
||||
|
uni.stopPullDownRefresh(); |
||||
|
setTimeout(() => this.calcSwiperHeight(statusKey), 0); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 获取 全部/历史 列表 |
||||
|
async getHistoryList(statusKey) { |
||||
|
this.loadingMap[statusKey] = true; |
||||
|
try { |
||||
|
const rdid = uni.getStorageSync('currentReaderCard'); |
||||
|
const params = { ...this.screenConfig, rdid, logtype:'30002' }; |
||||
|
const res = await FetchHistoryloan(params); |
||||
|
|
||||
|
const result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data; |
||||
|
let loanList = result.hloanlist || []; |
||||
|
|
||||
|
loanList = loanList.map(item => { |
||||
|
return { |
||||
|
...item, |
||||
|
returnTime: item.returntime || '', |
||||
|
startTime: item.loantime || item.optime || '', |
||||
|
returnBook: 2 |
||||
|
}; |
||||
|
}); |
||||
|
console.log('历史借阅', loanList); |
||||
|
this.listData[statusKey] = loanList; |
||||
|
this.hasMoreMap[statusKey] = false; |
||||
|
this.loadMoreStatusMap[statusKey] = "no-more"; |
||||
|
|
||||
|
} catch (err) { |
||||
|
console.error('获取历史列表失败', err); |
||||
|
this.listData[statusKey] = []; |
||||
|
} finally { |
||||
|
this.loadingMap[statusKey] = false; |
||||
|
this.isRefreshing = false; |
||||
|
uni.stopPullDownRefresh(); |
||||
|
setTimeout(() => this.calcSwiperHeight(statusKey), 0); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
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) { |
||||
|
if (statusKey === 'all') { |
||||
|
await this.getHistoryList(statusKey); |
||||
|
} |
||||
|
if (statusKey === 'lending') { |
||||
|
await this.getRealBorrowList(statusKey); |
||||
|
} |
||||
|
if (statusKey === 'expiring' || statusKey === 'expired') { |
||||
|
await this.getRealBorrowList('lending'); |
||||
|
this.loadingMap[statusKey] = false; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
loadMoreList(statusKey) { |
||||
|
this.loadMoreStatusMap[statusKey] = "no-more"; |
||||
|
}, |
||||
|
|
||||
|
refreshList(statusKey) { |
||||
|
this.getListData(statusKey); |
||||
|
}, |
||||
|
|
||||
|
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(); |
||||
|
}, |
||||
|
|
||||
|
tabClick(index) { |
||||
|
this.currentIndex = index; |
||||
|
const tab = this.getCurrentTab(); |
||||
|
if (this.currentPageScrollTop > 100) { |
||||
|
uni.pageScrollTo({ scrollTop: 0, duration: 100 }); |
||||
|
} |
||||
|
this.getListData(tab.status); |
||||
|
}, |
||||
|
|
||||
|
onSwiperChange(e) { |
||||
|
if (e.detail.source === "touch") { |
||||
|
this.currentIndex = e.detail.current; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
onSwiperEnd() { |
||||
|
const tab = this.getCurrentTab(); |
||||
|
this.getListData(tab.status); |
||||
|
}, |
||||
|
|
||||
|
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> |
||||
@ -1,189 +0,0 @@ |
|||||
<template> |
|
||||
<view class="reader-card"> |
|
||||
<image class="card-top-bg" src="@/static/images/card-img1.png" mode="widthFix" /> |
|
||||
|
|
||||
<view class="card-list"> |
|
||||
<view |
|
||||
class="card-list-item" |
|
||||
v-for="(item, index) in cardList" |
|
||||
:key="item.id" |
|
||||
@click="handleSelectItem(item.bindValue)" |
|
||||
:class="{ active: selectedValue === item.bindValue }" |
|
||||
> |
|
||||
<image class="card-left-img" src="@/static/images/card-img2.png" mode="widthFix" /> |
|
||||
<view class="card-right-info"> |
|
||||
<text class="info-title">读者证号</text> |
|
||||
<text class="info-num">{{ item.bindValue }}</text> |
|
||||
</view> |
|
||||
<radio :value="item.bindValue" :checked="selectedValue === item.bindValue"/> |
|
||||
</view> |
|
||||
</view> |
|
||||
|
|
||||
<button |
|
||||
class="unbind-btn" |
|
||||
:disabled="!selectedValue" |
|
||||
:class="{disabled: !selectedValue}" |
|
||||
@click="handleUnbind" |
|
||||
> |
|
||||
确认解绑 |
|
||||
</button> |
|
||||
</view> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
import { |
|
||||
FetchFindAllReaderBindByOpenId, |
|
||||
FetchUnbindReadCard, |
|
||||
FetchSetDefaultReadCard |
|
||||
} from '@/api/user'; |
|
||||
|
|
||||
const READLIST = 'reader-card-list'; |
|
||||
|
|
||||
export default { |
|
||||
data() { |
|
||||
return { |
|
||||
selectedValue: '', |
|
||||
cardList: [], |
|
||||
} |
|
||||
}, |
|
||||
onShow() { |
|
||||
this.getBindReaderCardList(); |
|
||||
}, |
|
||||
methods: { |
|
||||
async getBindReaderCardList() { |
|
||||
try { |
|
||||
const openId = uni.getStorageSync('wx_login_code'); |
|
||||
if (!openId) return; |
|
||||
|
|
||||
const res = await FetchFindAllReaderBindByOpenId({ |
|
||||
libcode: '1201', |
|
||||
openId: openId |
|
||||
}); |
|
||||
|
|
||||
if (res.code === 200) { |
|
||||
this.cardList = res.data; |
|
||||
uni.setStorageSync(READLIST, res.data); |
|
||||
} else { |
|
||||
this.cardList = []; |
|
||||
uni.setStorageSync(READLIST, []); |
|
||||
} |
|
||||
} catch (err) { |
|
||||
this.cardList = uni.getStorageSync(READLIST) || []; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
handleSelectItem(value) { |
|
||||
if (this.selectedValue === value) { |
|
||||
this.selectedValue = ''; |
|
||||
} else { |
|
||||
this.selectedValue = value; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
// 确认解绑 |
|
||||
async handleUnbind() { |
|
||||
if (!this.selectedValue) { |
|
||||
uni.showToast({ title: '请选择要解绑的读者证', icon: 'none' }); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const openId = uni.getStorageSync('wx_login_code'); |
|
||||
if (!openId) return; |
|
||||
|
|
||||
// 判断:当前解绑的是不是【默认证】 |
|
||||
const unbindItem = this.cardList.find(item => item.bindValue === this.selectedValue); |
|
||||
const isUnbindDefault = unbindItem?.bindDefault === true; |
|
||||
|
|
||||
uni.showModal({ |
|
||||
title: '提示', |
|
||||
content: `确定要解绑读者证【${this.selectedValue}】吗?`, |
|
||||
success: async (res) => { |
|
||||
if (!res.confirm) return; |
|
||||
|
|
||||
try { |
|
||||
// 1. 执行解绑 |
|
||||
const result = await FetchUnbindReadCard({ |
|
||||
bindType: "rdid", |
|
||||
bindValue: this.selectedValue, |
|
||||
libcode: "1201", |
|
||||
openid: openId |
|
||||
}); |
|
||||
|
|
||||
if (result.code !== 200) { |
|
||||
uni.showToast({ title: result.msg || '解绑失败', icon: 'none' }); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
uni.showToast({ title: '解绑成功', icon: 'success' }); |
|
||||
this.selectedValue = ''; |
|
||||
|
|
||||
// 刷新列表 |
|
||||
await this.getBindReaderCardList(); |
|
||||
|
|
||||
if (isUnbindDefault && this.cardList.length > 0) { |
|
||||
const newDefaultCard = this.cardList[0]; |
|
||||
const newValue = newDefaultCard.bindValue; |
|
||||
|
|
||||
const setResult = await FetchSetDefaultReadCard({ |
|
||||
bindType: 'rdid', |
|
||||
bindValue: newValue, |
|
||||
libcode: '1201', |
|
||||
openid: openId |
|
||||
}); |
|
||||
|
|
||||
if (setResult.code === 200) { |
|
||||
uni.setStorageSync('currentReaderCard', newValue); |
|
||||
// 刷新列表,让界面显示新默认 |
|
||||
await this.getBindReaderCardList(); |
|
||||
uni.showToast({ title: '已自动设置默认证', icon: 'success' }); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (this.cardList.length === 0) { |
|
||||
setTimeout(() => { |
|
||||
uni.navigateBack(); |
|
||||
}, 1500); |
|
||||
} |
|
||||
|
|
||||
} catch (err) { |
|
||||
uni.showToast({ title: '解绑失败', icon: 'none' }); |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
}, |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
.reader-card{ |
|
||||
position: relative; |
|
||||
min-height: 100vh; |
|
||||
background-color: #f5f5f5; |
|
||||
|
|
||||
.card-top-bg{ |
|
||||
position: absolute; |
|
||||
left: 0; |
|
||||
top: 0; |
|
||||
width: 100%; |
|
||||
display: block; |
|
||||
} |
|
||||
|
|
||||
.unbind-btn{ |
|
||||
position: fixed; |
|
||||
bottom: 40px; |
|
||||
left: 0; |
|
||||
right: 0; |
|
||||
width: calc(100% - 40px); |
|
||||
margin: 0 auto; |
|
||||
padding: 4px 0; |
|
||||
color: #fff; |
|
||||
background-color: #01a4fe; |
|
||||
border-radius: 23px; |
|
||||
font-size: 16px; |
|
||||
&.disabled{ |
|
||||
background-color: #ccc; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue