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

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