图书馆小程序
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.

488 lines
11 KiB

2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <view class="item-container">
  3. <!-- 加载状态 -->
  4. <view v-if="isLoading" class="loading-container">
  5. <uni-icons type="loading" size="40" color="#01a4fe" spin></uni-icons>
  6. <text class="loading-text">加载中...</text>
  7. </view>
  8. <!-- 内容区域 -->
  9. <view v-else>
  10. <!-- 图书数量提示 -->
  11. <view v-if="bookList.length > 0" class="count-text">
  12. <text>图书数量 ({{ bookList.length }})</text>
  13. </view>
  14. <!-- 借阅列表 -->
  15. <view class="car-list" v-for="item in bookList" :key="item.barcode">
  16. <checkbox :checked="item.checked" @click="toggleItem(item)" class="checkbox" />
  17. <view class="book-item-box" @click="toggleItem(item)">
  18. <view class="item-box-left">
  19. <image class="img-item" :src="item.cover || defaultCover" mode="aspectFill" />
  20. </view>
  21. <view class="item-box-right">
  22. <view class="item-title line-clamp-2">{{ item.title || '暂无标题' }}</view>
  23. <view class="tag-box">
  24. <text class="item-author">{{ item.author || '佚名' }}</text>
  25. <text v-if="item.callno" class="item-callno">{{ item.callno }}</text>
  26. </view>
  27. <view class="item-desc">
  28. <text class="return-label">应还时间</text>
  29. <text :class="isOverdue(item.returndate) ? 'overdue-text' : ''">{{ item.returndate || '暂无' }}</text>
  30. </view>
  31. </view>
  32. </view>
  33. </view>
  34. <!-- 空状态 -->
  35. <view class="empty" v-if="bookList.length === 0">
  36. <uni-icons custom-prefix="iconfont" type="icon-kongshuju" size="80" color="#ccc"></uni-icons>
  37. <text class="empty-text">暂无借阅图书</text>
  38. </view>
  39. </view>
  40. <!-- 底部占位 -->
  41. <view class="bottom-placeholder"></view>
  42. <!-- 底部操作栏 -->
  43. <view class="car-bottom">
  44. <view class="all-check" @click="toggleAllCheck">
  45. <checkbox :checked="isAllChecked" />
  46. <text style="margin-left:6px">全选</text>
  47. </view>
  48. <button class="join-btn" @click="handleRenew" :disabled="!hasChecked">
  49. 一键续借
  50. </button>
  51. </view>
  52. </view>
  53. </template>
  54. <script>
  55. import { FetchInitScreenSetting } from '@/api/user';
  56. import { FetchRdloanlist, FetchRenewbook } from '@/api/book';
  57. import { getCurrentReaderCard } from '@/utils/storage';
  58. export default {
  59. data() {
  60. return {
  61. bookList: [],
  62. defaultCover: '/static/images/default-book.png',
  63. screenConfig: {},
  64. isLoading: false,
  65. hasFetchedConfig: false
  66. };
  67. },
  68. onShow() {
  69. this.initPage();
  70. },
  71. computed: {
  72. isAllChecked() {
  73. return this.bookList.length > 0 && this.bookList.every(item => item.checked);
  74. },
  75. hasChecked() {
  76. return this.bookList.some(item => item.checked);
  77. }
  78. },
  79. methods: {
  80. /**
  81. * 初始化页面
  82. */
  83. async initPage() {
  84. // 如果已经获取过配置且有数据,直接刷新列表
  85. if (this.hasFetchedConfig && Object.keys(this.screenConfig).length > 0) {
  86. await this.getLendingList();
  87. return;
  88. }
  89. await this.getConfigAndList();
  90. },
  91. /**
  92. * 获取配置并加载列表
  93. */
  94. async getConfigAndList() {
  95. this.isLoading = true;
  96. uni.showLoading({ title: '加载中...' });
  97. try {
  98. // 1. 获取配置
  99. await this.fetchScreenConfig();
  100. // 2. 获取读者证
  101. const currentReaderCard = await getCurrentReaderCard();
  102. if (!currentReaderCard) {
  103. this.handleNoReaderCard();
  104. return;
  105. }
  106. // 3. 加载借阅列表
  107. await this.getLendingList();
  108. this.hasFetchedConfig = true;
  109. } catch (err) {
  110. console.error('初始化失败:', err);
  111. uni.showToast({ title: '初始化失败', icon: 'none' });
  112. } finally {
  113. this.isLoading = false;
  114. uni.hideLoading();
  115. }
  116. },
  117. /**
  118. * 获取屏幕配置
  119. */
  120. async fetchScreenConfig() {
  121. const res = await FetchInitScreenSetting({ libcode: '1201' });
  122. this.screenConfig = {
  123. thirdUrl: res.data.open_lib_http?.context || '',
  124. thirdAppid: res.data.open_lib_appId?.context || '',
  125. thirdSecret: res.data.open_lib_secret?.context || '',
  126. sm4Key: res.data.sm4_key?.context || '',
  127. opuser: res.data.op_user?.context || 'JH001',
  128. };
  129. },
  130. /**
  131. * 处理未绑定读者证情况
  132. */
  133. handleNoReaderCard() {
  134. this.bookList = [];
  135. uni.showModal({
  136. title: '提示',
  137. content: '请先绑定读者证',
  138. confirmText: '去绑定',
  139. cancelText: '取消',
  140. success: (res) => {
  141. if (res.confirm) {
  142. uni.navigateTo({ url: "/pages/login/login" });
  143. } else {
  144. uni.switchTab({ url: "/pages/home/home" });
  145. }
  146. }
  147. });
  148. },
  149. /**
  150. * 获取借阅列表
  151. */
  152. async getLendingList() {
  153. uni.showLoading({ title: '加载中...' });
  154. try {
  155. const currentReaderCard = await getCurrentReaderCard();
  156. if (!currentReaderCard) {
  157. this.bookList = [];
  158. return;
  159. }
  160. const params = {
  161. ...this.screenConfig,
  162. rdid: currentReaderCard.bindValue,
  163. };
  164. const res = await FetchRdloanlist(params);
  165. const result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
  166. // 添加选中状态和封面处理
  167. this.bookList = (result.loanlist || []).map(item => ({
  168. ...item,
  169. checked: false,
  170. cover: this.getBookCover(item)
  171. }));
  172. } catch (err) {
  173. console.error('获取借阅列表失败:', err);
  174. uni.showToast({ title: '加载失败', icon: 'none' });
  175. this.bookList = [];
  176. } finally {
  177. uni.hideLoading();
  178. }
  179. },
  180. /**
  181. * 获取图书封面如果有
  182. */
  183. getBookCover(item) {
  184. // 如果有封面信息,返回封面路径
  185. if (item.coverurl) {
  186. return item.coverurl;
  187. }
  188. // 如果有图片相关信息,可以尝试构建封面URL
  189. if (item.imgId) {
  190. return `/api/fileRelevant/getImg?imgType=2&imgId=${item.imgId}`;
  191. }
  192. return null;
  193. },
  194. /**
  195. * 判断是否逾期
  196. */
  197. isOverdue(returndate) {
  198. if (!returndate) return false;
  199. const today = new Date();
  200. const returnDate = new Date(returndate);
  201. return returnDate < today;
  202. },
  203. /**
  204. * 切换单个选中状态
  205. */
  206. toggleItem(item) {
  207. item.checked = !item.checked;
  208. },
  209. /**
  210. * 全选/取消全选
  211. */
  212. toggleAllCheck() {
  213. const isAll = this.isAllChecked;
  214. this.bookList.forEach(item => {
  215. item.checked = !isAll;
  216. });
  217. },
  218. /**
  219. * 一键续借
  220. */
  221. async handleRenew() {
  222. const checkedBooks = this.bookList.filter(item => item.checked);
  223. if (checkedBooks.length === 0) {
  224. uni.showToast({ title: '请选择图书', icon: 'none' });
  225. return;
  226. }
  227. uni.showLoading({ title: '续借中...' });
  228. try {
  229. const currentReaderCard = await getCurrentReaderCard();
  230. if (!currentReaderCard) {
  231. uni.hideLoading();
  232. uni.showToast({ title: '请先绑定读者证', icon: 'none' });
  233. return;
  234. }
  235. const barcodes = checkedBooks.map(item => item.barcode).join('|');
  236. const params = {
  237. ...this.screenConfig,
  238. rdid: currentReaderCard.bindValue,
  239. barcode: barcodes,
  240. logtype: '30007'
  241. };
  242. const res = await FetchRenewbook(params);
  243. const result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
  244. await this.handleRenewResult(result);
  245. } catch (err) {
  246. console.error('续借异常:', err);
  247. uni.hideLoading();
  248. uni.showToast({ title: '网络异常,续借失败', icon: 'none' });
  249. }
  250. },
  251. /**
  252. * 处理续借结果
  253. */
  254. async handleRenewResult(result) {
  255. uni.hideLoading();
  256. if (result.success === true) {
  257. uni.showToast({
  258. title: '续借成功!',
  259. icon: 'success'
  260. });
  261. // 刷新列表
  262. await this.getLendingList();
  263. } else {
  264. let msg = '续借失败';
  265. if (result.messagelist && result.messagelist.length > 0) {
  266. msg = result.messagelist[0].message;
  267. }
  268. uni.showToast({
  269. title: msg,
  270. icon: 'none',
  271. duration: 3000
  272. });
  273. }
  274. }
  275. }
  276. };
  277. </script>
  278. <style lang="scss" scoped>
  279. .item-container {
  280. padding: 10px;
  281. background: #f5f6f7;
  282. min-height: 100vh;
  283. }
  284. /* 加载状态 */
  285. .loading-container {
  286. display: flex;
  287. flex-direction: column;
  288. align-items: center;
  289. justify-content: center;
  290. height: 60vh;
  291. }
  292. .loading-text {
  293. margin-top: 16px;
  294. font-size: 14px;
  295. color: #999;
  296. }
  297. /* 数量提示 */
  298. .count-text {
  299. margin-bottom: 10px;
  300. font-size: 14px;
  301. color: #333;
  302. }
  303. /* 列表项 */
  304. .car-list {
  305. display: flex;
  306. align-items: flex-start;
  307. margin-bottom: 10px;
  308. }
  309. .checkbox {
  310. margin-top: 15px;
  311. padding: 0 10px;
  312. }
  313. .book-item-box {
  314. flex: 1;
  315. background: #fff;
  316. border-radius: 8px;
  317. padding: 12px;
  318. display: flex;
  319. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  320. }
  321. .item-box-left {
  322. margin-right: 12px;
  323. flex-shrink: 0;
  324. }
  325. .img-item {
  326. width: 64px;
  327. height: 90px;
  328. border-radius: 6px;
  329. background: #f5f5f5;
  330. }
  331. .item-box-right {
  332. flex: 1;
  333. display: flex;
  334. flex-direction: column;
  335. overflow: hidden;
  336. }
  337. .item-title {
  338. font-weight: bold;
  339. font-size: 15px;
  340. color: #333;
  341. margin-bottom: 6px;
  342. line-height: 1.4;
  343. }
  344. .tag-box {
  345. display: flex;
  346. flex-wrap: wrap;
  347. gap: 6px;
  348. margin-bottom: 8px;
  349. }
  350. .item-author {
  351. font-size: 12px;
  352. background: #f4f6fc;
  353. color: #666;
  354. padding: 2px 8px;
  355. border-radius: 4px;
  356. }
  357. .item-callno {
  358. font-size: 12px;
  359. background: #fff3e0;
  360. color: #ff9800;
  361. padding: 2px 8px;
  362. border-radius: 4px;
  363. }
  364. .item-desc {
  365. font-size: 13px;
  366. color: #999;
  367. margin-top: auto;
  368. }
  369. .return-label {
  370. color: #999;
  371. }
  372. .overdue-text {
  373. color: #ff4444;
  374. font-weight: bold;
  375. }
  376. /* 空状态 */
  377. .empty {
  378. display: flex;
  379. flex-direction: column;
  380. align-items: center;
  381. justify-content: center;
  382. height: calc(100vh - 140px);
  383. }
  384. .empty-text {
  385. margin-top: 20px;
  386. font-size: 14px;
  387. color: #999;
  388. }
  389. /* 底部占位 */
  390. .bottom-placeholder {
  391. height: 80px;
  392. }
  393. /* 底部操作栏 */
  394. .car-bottom {
  395. position: fixed;
  396. left: 0;
  397. bottom: 0;
  398. right: 0;
  399. background: #fff;
  400. padding: 12px 15px;
  401. display: flex;
  402. justify-content: space-between;
  403. align-items: center;
  404. box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  405. }
  406. .all-check {
  407. display: flex;
  408. align-items: center;
  409. font-size: 14px;
  410. color: #333;
  411. }
  412. .join-btn {
  413. font-size: 15px;
  414. color: #fff;
  415. background: #01a4fe !important;
  416. border-radius: 23px;
  417. padding: 0 30px;
  418. height: 40px;
  419. &::after {
  420. border: none !important;
  421. }
  422. &[disabled] {
  423. background: #ccc !important;
  424. }
  425. }
  426. </style>