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

706 lines
20 KiB

1 month ago
  1. <template>
  2. <view style="padding-bottom: 20px;">
  3. <view class="top-bar">
  4. <uni-icons class="erweima-top" custom-prefix="iconfont" type="icon-erweima" size="22" color="#fff" @click="showTopImgPopup"></uni-icons>
  5. <image class="top-bar-bg" src="@/static/images/mingqi-beij@2x.png" mode="aspectFill"></image>
  6. <view class="library-info">
  7. <image class="avatar" src="@/static/images/logo.jpg" mode="aspectFill"></image>
  8. <view class="library-name">葛店经济技术开发区图书馆</view>
  9. </view>
  10. <uni-icons class="search-icon" type="search" size="24" @click="onToSearch"></uni-icons>
  11. </view>
  12. <view class="status-cards">
  13. <view class="status-card" @click="toBorrowPage(0)">
  14. <view class="status-icon icon-color1" >
  15. <uni-icons custom-prefix="iconfont" type="icon-daiban02" size="22"></uni-icons>
  16. </view>
  17. <view class="status-label">在借中</view>
  18. </view>
  19. <view class="status-card" @click="toBorrowPage(1)">
  20. <view class="status-icon icon-color4" >
  21. <uni-icons custom-prefix="iconfont" type="icon-a-zhidu4" size="22"></uni-icons>
  22. </view>
  23. <view class="status-label">历史借阅</view>
  24. </view>
  25. </view>
  26. <swiper class="banner-swiper" interval="3000" circular :vertical="false" :indicator-dots="true" :autoplay="true">
  27. <swiper-item>
  28. <image data-post-id="3" src="https://mmbiz.qpic.cn/mmbiz_jpg/bVyA57fUOMicbQqItawJwQlR2oCoYql1icEbuH45ibSAzZsk2T3aDbUOIdMyOOc2CzkYouok0El5u5U3LONACaeNw/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=1"></image>
  29. </swiper-item>
  30. <swiper-item>
  31. <image data-post-id="4" src="https://mmbiz.qpic.cn/sz_mmbiz_gif/tuyGibvSXrsqqiaNpAFJ7lbfGL5vibHtUdV5SUk1RWnlq3QqDe9liaWhdS91L0orEsFxZMiaHOFxDOzINmkiby24fYmmWstiayUgywfVia1pwMcNpBA/640?wx_fmt=gif&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=3"></image>
  32. </swiper-item>
  33. </swiper>
  34. <swiper class="menu-swiper" interval="6000" :vertical="false" :autoplay="false">
  35. <swiper-item>
  36. <view class="menu-grid">
  37. <view class="menu-item" @click="onToSearch">
  38. <view class="menu-icon">
  39. <uni-icons custom-prefix="iconfont" type="icon-ziyuan" size="22"></uni-icons>
  40. </view>
  41. <view class="menu-label">书目检索</view>
  42. </view>
  43. <view class="menu-item" @click="onToLendCar">
  44. <view class="menu-icon">
  45. <uni-icons custom-prefix="iconfont" type="icon-z_renew_normal" size="24"></uni-icons>
  46. </view>
  47. <view class="menu-label">图书续借</view>
  48. </view>
  49. <view class="menu-item" @click="toLibraryCard">
  50. <view class="menu-icon">
  51. <uni-icons custom-prefix="iconfont" type="icon-duzhezheng" size="26"></uni-icons>
  52. </view>
  53. <view class="menu-label">电子证</view>
  54. </view>
  55. <view class="menu-item" @click="toRanking">
  56. <view class="menu-icon">
  57. <uni-icons custom-prefix="iconfont" type="icon-paihangbang" size="22"></uni-icons>
  58. </view>
  59. <view class="menu-label">借阅排行</view>
  60. </view>
  61. <view class="menu-item" @click="toActivityList">
  62. <view class="menu-icon">
  63. <uni-icons custom-prefix="iconfont" type="icon-remenhuodong" size="26"></uni-icons>
  64. </view>
  65. <view class="menu-label">活动报名</view>
  66. </view>
  67. <view class="menu-item" @click="toFeedback">
  68. <view class="menu-icon">
  69. <uni-icons custom-prefix="iconfont" type="icon-ruguanyuyue" size="26"></uni-icons>
  70. </view>
  71. <view class="menu-label">预约入馆</view>
  72. </view>
  73. <view class="menu-item" @click="onToUser">
  74. <view class="menu-icon">
  75. <uni-icons custom-prefix="iconfont" type="icon-yonghuxinxi-gerenxinxi" size="20"></uni-icons>
  76. </view>
  77. <view class="menu-label">个人中心</view>
  78. </view>
  79. <view class="menu-item" @click="goRegister">
  80. <view class="menu-icon">
  81. <uni-icons custom-prefix="iconfont" type="icon-daohang-banzhengxuzhi" size="25"></uni-icons>
  82. </view>
  83. <view class="menu-label">在线办证</view>
  84. </view>
  85. </view>
  86. </swiper-item>
  87. </swiper>
  88. <view v-if="isBindLibraryCard" class="library-card-has">
  89. <view class="section-title">我的借阅证</view>
  90. <swiper
  91. class="card-swiper"
  92. :circular="false"
  93. >
  94. <swiper-item v-for="item in readerCardList" :key="item.id">
  95. <view class="card-item" @click.stop="showQrcode(item.bindValue)">
  96. <image class="card-left-img" src="@/static/images/card-img2.png" mode="widthFix" />
  97. <view class="card-right-info">
  98. <text class="info-title">读者证号</text>
  99. <text class="info-num">{{ formatCardNo(item.bindValue) }}</text>
  100. </view>
  101. <view class="default-tag" v-if="item.bindDefault">默认证</view>
  102. <uni-icons class="erweima-style" custom-prefix="iconfont" type="icon-erweima" size="28"></uni-icons>
  103. </view>
  104. </swiper-item>
  105. </swiper>
  106. </view>
  107. <view class="library-card-section" @click="toCheckLogin" v-else>
  108. <view class="section-title">借阅证</view>
  109. <image class="card-icon" src="@/static/images/card-img2.png"></image>
  110. <view class="card-tip">您还没有绑定读者证哦</view>
  111. <button class="bind-btn">立即绑定</button>
  112. </view>
  113. <root-portal>
  114. <uni-popup
  115. ref="popup"
  116. type="center"
  117. :mask-click="true"
  118. @maskClick="closeAllPopup"
  119. >
  120. <view class="qrcode-popup">
  121. <view class="qrcode-title">读者证二维码</view>
  122. <w-qrcode
  123. v-if="qrcodeText"
  124. :value="qrcodeText"
  125. :size="160"
  126. foreground="#333"
  127. background="#ffffff"
  128. />
  129. <view class="qrcode-num" v-if="qrcodeText">{{ qrcodeText }}</view>
  130. </view>
  131. </uni-popup>
  132. <uni-popup
  133. ref="erweimapopup"
  134. type="center"
  135. :mask-click="true"
  136. @maskClick="closeErweiPopup"
  137. :custom-style="popupFixedStyle"
  138. >
  139. <view class="qrcode-popup" style="width: auto !important;">
  140. <image
  141. v-if="wecharQRCode"
  142. :src="wecharQRCode"
  143. style="width:200px;height:200px;"
  144. mode="aspectFit"
  145. :show-menu-by-longpress="true"
  146. @longpress="onQRCodeLongPress"
  147. ></image>
  148. </view>
  149. </uni-popup>
  150. </root-portal>
  151. <view class="recommendation-section">
  152. <view class="section-title">
  153. <text class="left-txt">图书推荐</text>
  154. <view class="more-link" @click="getMoreBoook" v-if="!(recommendedBooks.length === 0 && !isLoading)">
  155. <text style="margin-right: 5px;">查看更多</text>
  156. <uni-icons custom-prefix="iconfont" type="icon-gengduo" size="12"></uni-icons>
  157. </view>
  158. </view>
  159. <view class="recommendation-list">
  160. <uni-load-more status="loading" v-if="isLoading"></uni-load-more>
  161. <view v-else class="book-item" v-for="(item,index) in recommendedBooks.slice(0,3)" @click="goToBookDetail(item)" :key="index" >
  162. <image
  163. class="book-cover"
  164. :src="item.coverUrl || '/static/images/default-book.png'"
  165. @error="onImgError"
  166. ></image>
  167. <view class="book-title">{{item.name}}</view>
  168. </view>
  169. </view>
  170. <view class="empty" v-if="recommendedBooks.length === 0 && !isLoading">
  171. <uni-icons custom-prefix="iconfont" type="icon-kongshuju" size="80"></uni-icons>
  172. <text style="margin-top: 20px;">暂无推荐书籍~</text>
  173. </view>
  174. </view>
  175. </view>
  176. </template>
  177. <script>
  178. import { FetchInitScreenSetting, FetchOpenId, FetchFindAllReaderBindByOpenId} from '@/api/user';
  179. import { FetchInitScreenBookRecommend } from '@/api/book';
  180. import config from '@/utils/config';
  181. import { STORAGE_KEYS } from '@/utils/storage';
  182. export default {
  183. data() {
  184. return {
  185. isLoading: true,
  186. recommendedBooks: [],
  187. userInfo: {},
  188. isBindLibraryCard: false,
  189. readerCardList: [],
  190. baseUrl: config.baseUrl,
  191. qrcodeText: '',
  192. popupFixedStyle: 'position:fixed;top:0;left:0;right:0;bottom:0;',
  193. wecharQRCode: null
  194. };
  195. },
  196. onLoad() {
  197. this.getRecommendBooks();
  198. this.getScreenSetting()
  199. },
  200. onShow() {
  201. this.initUserAndCheckBind();
  202. },
  203. methods:{
  204. async getScreenSetting() {
  205. const res = await FetchInitScreenSetting({ libcode: config.LIB_CODE });
  206. if (res.data.wechar_qr_code && res.data.wechar_qr_code.context) {
  207. // 直接使用图片url,不再转base64
  208. this.wecharQRCode = this.baseUrl + '/api/fileRelevant/getImg?imgType=1&imgId=' + res.data.wechar_qr_code.context;
  209. } else {
  210. this.wecharQRCode = null;
  211. }
  212. },
  213. showTopImgPopup() {
  214. if (!this.wecharQRCode) {
  215. uni.showToast({ title: '暂无图片', icon: 'none' })
  216. return
  217. }
  218. this.$refs.erweimapopup.open()
  219. },
  220. formatCardNo(cardNo) {
  221. if (!cardNo) return '';
  222. const str = String(cardNo);
  223. const len = str.length;
  224. if (len > 8) {
  225. const front = str.substring(0, 2);
  226. const end = str.substring(len - 2);
  227. const star = '*'.repeat(len - 4);
  228. return front + star + end;
  229. } else {
  230. return '***' + str.substring(len - 2);
  231. }
  232. },
  233. onImgError(e) {
  234. // 图片加载失败,替换默认图
  235. e.target.src = "/static/images/default-book.png";
  236. },
  237. async initUserAndCheckBind() {
  238. try {
  239. let openId = uni.getStorageSync('wx_login_code');
  240. if (!openId) {
  241. const loginRes = await new Promise((resolve, reject) => {
  242. uni.login({
  243. success: resolve,
  244. fail: reject
  245. });
  246. });
  247. if (!loginRes.code) {
  248. uni.showToast({ title: '授权失败', icon: 'none' });
  249. return;
  250. }
  251. const openRes = await FetchOpenId({
  252. libcode: config.LIB_CODE,
  253. code: loginRes.code
  254. });
  255. if (openRes.code !== 200 || !openRes.data) {
  256. return;
  257. }
  258. openId = openRes.data;
  259. uni.setStorageSync('wx_login_code', openId);
  260. }
  261. const res = await FetchFindAllReaderBindByOpenId({
  262. libcode: config.LIB_CODE,
  263. openId: openId
  264. });
  265. if (res.code === 200 && res.data.length > 0) {
  266. this.isBindLibraryCard = true;
  267. const defaultCard = res.data.find(item => item.bindDefault === true);
  268. const otherCards = res.data.filter(item => !item.bindDefault);
  269. this.readerCardList = defaultCard ? [defaultCard, ...otherCards] : res.data;
  270. uni.setStorageSync(STORAGE_KEYS.READER_CARD_LIST, this.readerCardList);
  271. uni.setStorageSync(STORAGE_KEYS.CURRENT_READER_CARD, defaultCard);
  272. } else {
  273. this.isBindLibraryCard = false;
  274. this.readerCardList = [];
  275. uni.setStorageSync(STORAGE_KEYS.READER_CARD_LIST, []);
  276. uni.setStorageSync(STORAGE_KEYS.CURRENT_READER_CARD, null);
  277. }
  278. } catch (err) {
  279. console.error('初始化失败:', err);
  280. const list = uni.getStorageSync(STORAGE_KEYS.READER_CARD_LIST) || [];
  281. const defaultCard = list.find(item => item.bindDefault === true);
  282. const otherCards = list.filter(item => !item.bindDefault);
  283. this.readerCardList = defaultCard ? [defaultCard, ...otherCards] : list;
  284. this.isBindLibraryCard = list.length > 0;
  285. }
  286. },
  287. async getRecommendBooks() {
  288. try {
  289. const res = await FetchInitScreenBookRecommend({ libcode: config.LIB_CODE });
  290. let books = res.data || [];
  291. const limit = 3;
  292. const displayBooks = books.slice(0, limit);
  293. // 不再请求下载图片,直接拼接url
  294. for (let i = 0; i < displayBooks.length; i++) {
  295. let item = displayBooks[i];
  296. if (item.imgPath) {
  297. item.coverUrl = this.baseUrl + '/api/fileRelevant/getImg?imgType=2&imgId=' + item.imgPath;
  298. } else {
  299. item.coverUrl = '';
  300. }
  301. }
  302. this.recommendedBooks = displayBooks;
  303. this.isLoading = false;
  304. } catch (err) {
  305. console.error('获取图书推荐失败:', err);
  306. this.isLoading = false;
  307. }
  308. },
  309. goToBookDetail(item) {
  310. uni.navigateTo({
  311. url: "/subpkg/pages/book-detail/book-detail?bookData=" + encodeURIComponent(JSON.stringify(item)) + "&fromRecommend=true"
  312. })
  313. },
  314. getMoreBoook(){
  315. uni.navigateTo({
  316. url: "/subpkg/pages/book-list/book-list"
  317. })
  318. },
  319. onToSearch() {
  320. uni.switchTab({
  321. url: "/pages/search/search"
  322. });
  323. },
  324. onToLendCar() {
  325. this.checkBindAndNavigate("/pages/lendCar/lendCar", "请您绑定读者证", true);
  326. },
  327. toCheckLogin() {
  328. uni.navigateTo({
  329. url: "/pages/login/login"
  330. });
  331. },
  332. toRanking(){
  333. uni.navigateTo({
  334. url: "/subpkg/pages/ranking/ranking"
  335. })
  336. },
  337. toActivityList(){
  338. this.checkBindAndNavigate("/subpkg/pages/activity-list/activity-list", "请您绑定读者证");
  339. },
  340. toBorrowPage(index) {
  341. if (this.isBindLibraryCard) {
  342. uni.setStorageSync('switch_tab_index', index);
  343. uni.navigateTo({
  344. url: '/subpkg/pages/myLending/myLending'
  345. });
  346. } else {
  347. uni.showModal({
  348. title: '提示',
  349. content: '请您绑定读者证',
  350. confirmText: '去绑定',
  351. cancelText: '取消',
  352. success: (res) => {
  353. if (res.confirm) {
  354. uni.navigateTo({ url: "/pages/login/login" });
  355. }
  356. }
  357. });
  358. }
  359. },
  360. toBorrowCar() {
  361. uni.switchTab({
  362. url: '/pages/lendCar/lendCar'
  363. });
  364. },
  365. toFeedback() {
  366. uni.navigateTo({
  367. url: '/subpkg/pages/seat-booking/seat-booking'
  368. });
  369. },
  370. toLibraryCard() {
  371. if (this.isBindLibraryCard) {
  372. uni.navigateTo({ url: '/subpkg/pages/reader-card/reader-card' });
  373. }
  374. else {
  375. uni.navigateTo({ url: "/pages/login/login" });
  376. }
  377. },
  378. onToUser() {
  379. this.checkBindAndNavigate("/pages/user/user", "请您绑定读者证", true);
  380. },
  381. checkBindAndNavigate(url, message, isSwitchTab = false) {
  382. if (this.isBindLibraryCard) {
  383. if (isSwitchTab) {
  384. uni.switchTab({ url });
  385. } else {
  386. uni.navigateTo({ url });
  387. }
  388. } else {
  389. uni.showModal({
  390. title: '提示',
  391. content: message,
  392. confirmText: '去绑定',
  393. cancelText: '取消',
  394. success: (res) => {
  395. if (res.confirm) {
  396. uni.navigateTo({ url: "/pages/login/login" });
  397. }
  398. }
  399. });
  400. }
  401. },
  402. showQrcode(bindValue) {
  403. this.qrcodeText = bindValue;
  404. this.$refs.popup.open();
  405. },
  406. closeAllPopup() {
  407. this.qrcodeText = '';
  408. this.$refs.popup.close();
  409. },
  410. closeErweiPopup() {
  411. this.$refs.erweimapopup.close()
  412. },
  413. onQRCodeLongPress() {
  414. },
  415. // 【已废弃】不再使用urlToBase64
  416. goRegister() {
  417. uni.navigateTo({
  418. url: '/pages/register/register'
  419. });
  420. }
  421. }
  422. }
  423. </script>
  424. <style lang="scss" scoped>
  425. .top-bar{
  426. .erweima-top{
  427. position: absolute;
  428. left: 16px;
  429. top: 15px;
  430. z-index: 999;
  431. }
  432. .search-icon{
  433. position: absolute;
  434. right: 16px;
  435. top: 15px;
  436. z-index: 22;
  437. ::v-deep .uni-icons{
  438. color: #fff !important;
  439. }
  440. }
  441. }
  442. .status-cards {
  443. display: flex;
  444. background-color: #fff;
  445. margin: 10px;
  446. border-radius: 8px;
  447. padding: 20px 10px;
  448. box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
  449. .status-card {
  450. flex: 1;
  451. display: flex;
  452. flex-direction: column;
  453. align-items: center;
  454. }
  455. .status-icon {
  456. display: flex;
  457. justify-content: center;
  458. align-items: center;
  459. width: 40px;
  460. height: 40px;
  461. margin-bottom: 6px;
  462. border-radius: 50%;
  463. }
  464. .icon-color1{
  465. background-color: #e4f2ff;
  466. }
  467. .icon-color2{
  468. background-color: #ffeff0;
  469. }
  470. .icon-color3{
  471. background-color: #e4fff4;
  472. }
  473. .icon-color4{
  474. background-color: #fef8e2;
  475. }
  476. .status-label {
  477. font-size: 12px;
  478. color: #333;
  479. }
  480. }
  481. swiper.banner-swiper{
  482. width: 100%;
  483. height: 130px;
  484. }
  485. swiper.banner-swiper image{
  486. width: 100%;
  487. height: 130px;
  488. }
  489. swiper.menu-swiper{
  490. width:100%;
  491. margin-top: 10px;
  492. background-color: #fff;
  493. }
  494. .menu-grid {
  495. display: grid;
  496. grid-template-columns: repeat(4, 1fr);
  497. .menu-item {
  498. flex: 1;
  499. display: flex;
  500. flex-direction: column;
  501. align-items: center;
  502. padding: 10px 0;
  503. .menu-icon{
  504. display: flex;
  505. flex-direction: column;
  506. justify-content: center;
  507. height: 30px;
  508. ::v-deep .uni-icons{
  509. color: #01a4fe !important;
  510. }
  511. }
  512. .menu-label {
  513. font-size: 12px;
  514. color: #333;
  515. text-align: center;
  516. }
  517. }
  518. }
  519. .library-card-section {
  520. display: flex;
  521. flex-direction: column;
  522. justify-content: space-between;
  523. align-items: center;
  524. width: 100%;
  525. padding: 20px 0;
  526. .section-title {
  527. font-size: 22px;
  528. font-weight: bold;
  529. background: linear-gradient(140deg, #01a4ff, #2ec8fe);
  530. -webkit-background-clip: text;
  531. color: transparent;
  532. letter-spacing: 0.1em;
  533. margin-bottom: 5px;
  534. }
  535. .card-icon {
  536. width: 80px;
  537. height: 80px;
  538. }
  539. .card-tip {
  540. font-size: 12px;
  541. color: #999;
  542. margin: 5px 0;
  543. }
  544. .bind-btn {
  545. display: flex;
  546. flex-direction: row;
  547. align-items: center;
  548. justify-content: center;
  549. border: 1px solid #06a8ff ;
  550. width: 128px !important;
  551. height: 28px;
  552. border-radius: 28px;
  553. background: linear-gradient(140deg, #01a4ff, #2ec8fe);
  554. font-size: 14px;
  555. color: #fff;
  556. line-height: 28px;
  557. }
  558. }
  559. .recommendation-section {
  560. background-color: #fff;
  561. margin: 10px;
  562. border-radius: 8px;
  563. padding: 0 0 10px 0;
  564. box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
  565. .section-title{
  566. display: flex;
  567. flex-direction: row;
  568. justify-content: space-between;
  569. align-items: center;
  570. height: 40px;
  571. padding: 0 10px;
  572. .left-txt{
  573. color: #343434;
  574. font-size: 15px;
  575. font-weight: 700;
  576. }
  577. .more-link{
  578. display: flex;
  579. flex-direction: row;
  580. justify-content: flex-start;
  581. align-items: center;
  582. color: #999;
  583. font-size: 11px;
  584. }
  585. }
  586. }
  587. .library-card-has {
  588. width: 100%;
  589. padding: 20px 10px;
  590. box-sizing: border-box;
  591. .section-title {
  592. font-size: 22px;
  593. font-weight: bold;
  594. background: linear-gradient(140deg, #01a4ff, #2ec8fe);
  595. -webkit-background-clip: text;
  596. color: transparent;
  597. letter-spacing: 0.1em;
  598. margin-bottom: 10px;
  599. padding-left: 5px;
  600. }
  601. .card-list {
  602. width: 100%;
  603. }
  604. }
  605. .card-swiper {
  606. height: 106px;
  607. .card-item{
  608. display: flex;
  609. justify-content: space-between;
  610. align-items: center;
  611. padding: 10px;
  612. border: 2px solid #C6C6E2;
  613. border-radius: 8px;
  614. background: rgba(241,241,249,0.4);
  615. cursor: pointer;
  616. &.active{
  617. border-color: #01a4fe;
  618. background: rgba(1, 164, 254, 0.1);
  619. }
  620. .card-left-img{
  621. width: 80px;
  622. margin-right: 12px;
  623. display: block;
  624. }
  625. .card-right-info{
  626. flex: 1;
  627. display: flex;
  628. flex-direction: column;
  629. .info-title{
  630. font-size: 16px;
  631. color: #333;
  632. font-weight: bold;
  633. padding-bottom: 12px;
  634. }
  635. .info-num{
  636. font-size: 14px;
  637. color: #999;
  638. }
  639. }
  640. .default-tag {
  641. position: absolute;
  642. right: 12px;
  643. top: 12px;
  644. font-size: 11px;
  645. color: #01a4fe;
  646. border: 1px solid #01a4fe;
  647. border-radius: 4px;
  648. padding: 2px 6px;
  649. }
  650. }
  651. }
  652. .erweima-style{
  653. position: absolute;
  654. right: 12px;
  655. bottom: 22px;
  656. ::v-deep .icon-erweima{
  657. color: #999 !important;
  658. }
  659. }
  660. </style>