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

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