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

499 lines
11 KiB

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