book-detail.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. <template>
  2. <view class="container">
  3. <!-- 顶部导航栏 -->
  4. <view class="header">
  5. <view class="back-btn" @click="goBack">
  6. <text class="back-icon">←</text>
  7. </view>
  8. <text class="header-title">{{ bookInfo.title }}</text>
  9. <view class="share-btn" @click="handleShare">
  10. <text class="share-icon">↗</text>
  11. </view>
  12. </view>
  13. <scroll-view class="scroll-content" scroll-y>
  14. <!-- 书籍头部信息 -->
  15. <view class="book-header" v-if="!isLoading">
  16. <view class="book-cover-wrapper">
  17. <image class="book-cover" :src="bookInfo.image || bookInfo.cover" mode="aspectFill"></image>
  18. </view>
  19. <view class="book-info">
  20. <text class="book-title">{{ bookInfo.title }}</text>
  21. <text class="book-brief" v-if="bookInfo.brief || bookInfo.desc">{{ bookInfo.brief || bookInfo.desc }}</text>
  22. <text class="book-author" v-if="bookInfo.author">{{ bookInfo.author }}</text>
  23. <text class="book-price" v-if="bookInfo.isFree">免费</text>
  24. <text class="book-price-paid" v-else-if="bookInfo.price">¥{{ bookInfo.price }}</text>
  25. </view>
  26. </view>
  27. <!-- 加载中 -->
  28. <view class="loading-section" v-if="isLoading">
  29. <text class="loading-text">加载中...</text>
  30. </view>
  31. <!-- 简介 -->
  32. <view class="section" v-if="bookInfo.introduction || bookInfo.desc || bookInfo.brief">
  33. <text class="section-title">简介</text>
  34. <text class="section-content">{{ bookInfo.introduction || bookInfo.desc || bookInfo.brief }}</text>
  35. </view>
  36. <!-- 书评 -->
  37. <view class="section">
  38. <view class="section-header">
  39. <text class="section-title">书评</text>
  40. <text class="write-review-btn" @click="handleWriteReview">写书评</text>
  41. </view>
  42. <view class="review-list">
  43. <view class="review-item" v-for="(review, index) in reviews" :key="index">
  44. <view class="review-header">
  45. <image class="review-avatar" :src="review.avatar" mode="aspectFill"></image>
  46. <view class="review-user-info">
  47. <text class="review-name">{{ review.name }}</text>
  48. <text class="review-tag" v-if="review.isRecommended">推荐</text>
  49. </view>
  50. <text class="review-date">{{ review.date }}</text>
  51. <view class="review-likes" @click="toggleLike(review, index)">
  52. <text class="like-icon">{{ review.isLiked ? '❤️' : '🤍' }}</text>
  53. <text class="like-count">{{ review.likes }}</text>
  54. </view>
  55. </view>
  56. <text class="review-content">{{ review.content }}</text>
  57. </view>
  58. </view>
  59. <view class="view-all-reviews" @click="viewAllReviews">
  60. <text class="view-all-text">查看全部书评</text>
  61. </view>
  62. </view>
  63. <!-- 更多推荐 -->
  64. <view class="section">
  65. <view class="section-header">
  66. <text class="section-title">更多推荐</text>
  67. <text class="section-more" @click="viewMoreRecommend">更多 ></text>
  68. </view>
  69. <view class="recommend-grid">
  70. <view class="recommend-item" v-for="(book, index) in recommendBooks" :key="index" @click="goToBookDetail(book)">
  71. <image class="recommend-cover" :src="book.image" mode="aspectFill" :lazy-load="true"></image>
  72. <text class="recommend-title">{{ book.title }}</text>
  73. </view>
  74. </view>
  75. </view>
  76. </scroll-view>
  77. <!-- 底部操作栏 -->
  78. <view class="bottom-bar">
  79. <button
  80. class="add-to-shelf-btn"
  81. :class="{ 'in-shelf': isInShelf }"
  82. @click="handleAddToShelf"
  83. :disabled="isLoading"
  84. >
  85. {{ isInShelf ? '已在书架' : '加入书架' }}
  86. </button>
  87. <button class="read-btn" @click="handleRead">阅读</button>
  88. </view>
  89. </view>
  90. </template>
  91. <script>
  92. import { addToBookshelf, checkInBookshelf, getBookById, getMoreRecommend, recordBrowsingHistory, getComments, toggleCommentLike } from '../../utils/api.js'
  93. export default {
  94. data() {
  95. return {
  96. bookInfo: {
  97. id: null,
  98. title: '',
  99. image: '',
  100. cover: '',
  101. brief: '',
  102. desc: '',
  103. author: '',
  104. isFree: false,
  105. price: 0,
  106. introduction: ''
  107. },
  108. isLoading: false,
  109. userInfo: null,
  110. isInShelf: false,
  111. reviews: [],
  112. recommendBooks: []
  113. }
  114. },
  115. onLoad(options) {
  116. // 获取用户信息
  117. try {
  118. const userInfo = uni.getStorageSync('userInfo')
  119. if (userInfo && userInfo.id) {
  120. this.userInfo = userInfo
  121. }
  122. } catch (e) {
  123. console.error('获取用户信息失败', e)
  124. }
  125. // 从路由参数获取bookId
  126. if (options.bookId) {
  127. this.bookInfo.id = parseInt(options.bookId)
  128. // 加载书籍详情
  129. this.loadBookDetail(this.bookInfo.id)
  130. } else if (options.title) {
  131. // 兼容旧的参数方式,但优先从数据库加载
  132. this.bookInfo.title = decodeURIComponent(options.title)
  133. if (options.image) {
  134. this.bookInfo.image = decodeURIComponent(options.image)
  135. }
  136. if (options.author) {
  137. this.bookInfo.author = decodeURIComponent(options.author)
  138. }
  139. // 如果有bookId,从数据库加载
  140. if (options.bookId) {
  141. this.bookInfo.id = parseInt(options.bookId)
  142. this.loadBookDetail(this.bookInfo.id)
  143. }
  144. }
  145. },
  146. onShow() {
  147. // 页面显示时重新获取用户信息
  148. try {
  149. const userInfo = uni.getStorageSync('userInfo')
  150. if (userInfo && userInfo.id) {
  151. this.userInfo = userInfo
  152. // 重新检查书架状态
  153. if (this.bookInfo.id) {
  154. this.checkBookshelfStatus(userInfo.id, this.bookInfo.id)
  155. // 重新加载评论(更新点赞状态)
  156. this.loadComments(this.bookInfo.id)
  157. }
  158. }
  159. } catch (e) {
  160. console.error('获取用户信息失败', e)
  161. }
  162. },
  163. methods: {
  164. goBack() {
  165. uni.navigateBack()
  166. },
  167. handleShare() {
  168. uni.showToast({
  169. title: '分享功能',
  170. icon: 'none'
  171. })
  172. },
  173. async loadBookDetail(bookId) {
  174. if (!bookId) {
  175. uni.showToast({
  176. title: '书籍ID不能为空',
  177. icon: 'none'
  178. })
  179. return
  180. }
  181. try {
  182. this.isLoading = true
  183. uni.showLoading({
  184. title: '加载中...',
  185. mask: false
  186. })
  187. // 传递userId参数用于VIP权限检查
  188. const userId = this.userInfo && this.userInfo.id ? this.userInfo.id : null
  189. const res = await getBookById(bookId, userId)
  190. if (res && res.code === 200 && res.data) {
  191. const book = res.data
  192. // 更新书籍信息
  193. this.bookInfo = {
  194. id: book.id,
  195. title: book.title || '',
  196. image: book.image || book.cover || '',
  197. cover: book.cover || book.image || '',
  198. brief: book.brief || '',
  199. desc: book.desc || book.brief || '',
  200. author: book.author || '',
  201. isFree: book.isFree || false,
  202. price: book.price || 0,
  203. introduction: book.introduction || book.desc || book.brief || ''
  204. }
  205. // 如果简介为空,使用描述作为简介
  206. if (!this.bookInfo.brief && this.bookInfo.desc) {
  207. this.bookInfo.brief = this.bookInfo.desc
  208. }
  209. // 如果详细介绍为空,使用描述或简介作为详细介绍
  210. if (!this.bookInfo.introduction) {
  211. this.bookInfo.introduction = this.bookInfo.desc || this.bookInfo.brief || ''
  212. }
  213. // 检查书架状态
  214. if (this.userInfo && this.userInfo.id) {
  215. this.checkBookshelfStatus(this.userInfo.id, bookId)
  216. }
  217. // 加载推荐书籍
  218. this.loadRecommendBooks()
  219. // 记录浏览历史
  220. if (this.userInfo && this.userInfo.id) {
  221. this.recordBrowsingHistory(this.userInfo.id, bookId)
  222. }
  223. // 加载评论列表
  224. this.loadComments(bookId)
  225. } else {
  226. uni.showToast({
  227. title: res.message || '加载失败',
  228. icon: 'none'
  229. })
  230. }
  231. } catch (e) {
  232. console.error('加载书籍详情失败:', e)
  233. uni.showToast({
  234. title: e.message || '加载失败,请重试',
  235. icon: 'none'
  236. })
  237. } finally {
  238. this.isLoading = false
  239. uni.hideLoading()
  240. }
  241. },
  242. async loadRecommendBooks() {
  243. try {
  244. const res = await getMoreRecommend(6)
  245. if (res && res.code === 200 && res.data && Array.isArray(res.data)) {
  246. // 过滤掉当前书籍
  247. this.recommendBooks = res.data
  248. .filter(book => book.id !== this.bookInfo.id)
  249. .slice(0, 6)
  250. .map(book => ({
  251. id: book.id,
  252. title: book.title || '',
  253. image: book.image || book.cover || ''
  254. }))
  255. }
  256. } catch (e) {
  257. console.error('加载推荐书籍失败:', e)
  258. // 不显示错误提示,因为这是非关键功能
  259. }
  260. },
  261. handleWriteReview() {
  262. uni.navigateTo({
  263. url: `/pages/write-review/write-review?bookId=${this.bookInfo.id}&title=${encodeURIComponent(this.bookInfo.title)}`
  264. })
  265. },
  266. async toggleLike(review, index) {
  267. if (!this.userInfo || !this.userInfo.id) {
  268. uni.showToast({
  269. title: '请先登录',
  270. icon: 'none'
  271. })
  272. return
  273. }
  274. try {
  275. const res = await toggleCommentLike(this.userInfo.id, review.id)
  276. if (res && res.code === 200) {
  277. // 更新本地状态
  278. this.reviews[index].isLiked = res.data
  279. if (res.data) {
  280. this.reviews[index].likes++
  281. } else {
  282. this.reviews[index].likes--
  283. }
  284. } else {
  285. uni.showToast({
  286. title: res.message || '操作失败',
  287. icon: 'none'
  288. })
  289. }
  290. } catch (e) {
  291. console.error('点赞失败', e)
  292. uni.showToast({
  293. title: '操作失败,请重试',
  294. icon: 'none'
  295. })
  296. }
  297. },
  298. async loadComments(bookId) {
  299. if (!bookId) {
  300. return
  301. }
  302. try {
  303. const userId = this.userInfo && this.userInfo.id ? this.userInfo.id : null
  304. const res = await getComments(bookId, userId)
  305. if (res && res.code === 200 && res.data) {
  306. this.reviews = res.data.map(item => ({
  307. id: item.id,
  308. name: item.name || '匿名用户',
  309. avatar: item.avatar || 'https://via.placeholder.com/100x100?text=User',
  310. isRecommended: item.isRecommended || false,
  311. date: item.date || '',
  312. likes: item.likes || 0,
  313. isLiked: item.isLiked || false,
  314. content: item.content || ''
  315. }))
  316. }
  317. } catch (e) {
  318. console.error('加载评论失败', e)
  319. // 不显示错误提示,因为评论是非关键功能
  320. }
  321. },
  322. async recordBrowsingHistory(userId, bookId) {
  323. if (!userId || !bookId) {
  324. return
  325. }
  326. try {
  327. await recordBrowsingHistory(userId, bookId)
  328. } catch (e) {
  329. console.error('记录浏览历史失败', e)
  330. // 不显示错误提示,因为这是后台操作
  331. }
  332. },
  333. viewAllReviews() {
  334. uni.showToast({
  335. title: '查看全部书评',
  336. icon: 'none'
  337. })
  338. },
  339. viewMoreRecommend() {
  340. uni.showToast({
  341. title: '查看更多推荐',
  342. icon: 'none'
  343. })
  344. },
  345. goToBookDetail(book) {
  346. if (!book || !book.id) {
  347. uni.showToast({
  348. title: '书籍信息不完整',
  349. icon: 'none'
  350. })
  351. return
  352. }
  353. uni.navigateTo({
  354. url: `/pages/book-detail/book-detail?bookId=${book.id}`
  355. })
  356. },
  357. // 检查书架状态
  358. checkBookshelfStatus(userId, bookId) {
  359. if (!userId || !bookId) {
  360. return
  361. }
  362. checkInBookshelf(userId, bookId)
  363. .then((res) => {
  364. if (res.code === 200 && res.data !== undefined) {
  365. this.isInShelf = res.data
  366. }
  367. })
  368. .catch((err) => {
  369. console.error('检查书架状态失败', err)
  370. })
  371. },
  372. handleAddToShelf() {
  373. // 检查用户是否登录
  374. if (!this.userInfo || !this.userInfo.id) {
  375. uni.showModal({
  376. title: '提示',
  377. content: '请先登录',
  378. showCancel: true,
  379. cancelText: '取消',
  380. confirmText: '去登录',
  381. success: (res) => {
  382. if (res.confirm) {
  383. uni.navigateTo({
  384. url: '/pages/login/login'
  385. })
  386. }
  387. }
  388. })
  389. return
  390. }
  391. // 检查是否已在书架中
  392. if (this.isInShelf) {
  393. uni.showToast({
  394. title: '已在书架中',
  395. icon: 'none'
  396. })
  397. return
  398. }
  399. // 检查书籍ID
  400. if (!this.bookInfo.id) {
  401. uni.showToast({
  402. title: '书籍信息不完整',
  403. icon: 'none'
  404. })
  405. return
  406. }
  407. // 防止重复提交
  408. if (this.isLoading) {
  409. return
  410. }
  411. this.isLoading = true
  412. uni.showLoading({
  413. title: '加入中...',
  414. mask: true
  415. })
  416. // 调用后端接口
  417. addToBookshelf(this.userInfo.id, this.bookInfo.id)
  418. .then((res) => {
  419. uni.hideLoading()
  420. this.isLoading = false
  421. if (res.code === 200) {
  422. this.isInShelf = true
  423. uni.showToast({
  424. title: '已加入书架',
  425. icon: 'success'
  426. })
  427. } else {
  428. uni.showToast({
  429. title: res.message || '加入失败',
  430. icon: 'none'
  431. })
  432. }
  433. })
  434. .catch((err) => {
  435. uni.hideLoading()
  436. this.isLoading = false
  437. console.error('加入书架失败:', err)
  438. uni.showToast({
  439. title: err.message || '加入失败,请检查网络连接',
  440. icon: 'none',
  441. duration: 2000
  442. })
  443. })
  444. },
  445. handleRead() {
  446. // 跳转到书籍封面页
  447. uni.navigateTo({
  448. url: `/pages/book-cover/book-cover?bookId=${this.bookInfo.id}&title=${encodeURIComponent(this.bookInfo.title)}&image=${encodeURIComponent(this.bookInfo.image)}&author=${encodeURIComponent(this.bookInfo.author)}`
  449. })
  450. }
  451. }
  452. }
  453. </script>
  454. <style scoped>
  455. .container {
  456. width: 100%;
  457. height: 100vh;
  458. background-color: #FFFFFF;
  459. display: flex;
  460. flex-direction: column;
  461. padding-top: 30px;
  462. box-sizing: border-box;
  463. }
  464. .header {
  465. display: flex;
  466. align-items: center;
  467. justify-content: space-between;
  468. padding: 20rpx 30rpx;
  469. background-color: #FFFFFF;
  470. border-bottom: 1rpx solid #E0E0E0;
  471. position: relative;
  472. }
  473. .back-btn {
  474. width: 60rpx;
  475. height: 60rpx;
  476. display: flex;
  477. align-items: center;
  478. justify-content: center;
  479. }
  480. .back-icon {
  481. font-size: 40rpx;
  482. color: #333333;
  483. font-weight: bold;
  484. }
  485. .header-title {
  486. position: absolute;
  487. left: 50%;
  488. transform: translateX(-50%);
  489. font-size: 36rpx;
  490. font-weight: bold;
  491. color: #333333;
  492. }
  493. .share-btn {
  494. width: 60rpx;
  495. height: 60rpx;
  496. display: flex;
  497. align-items: center;
  498. justify-content: center;
  499. }
  500. .share-icon {
  501. font-size: 36rpx;
  502. color: #333333;
  503. }
  504. .scroll-content {
  505. flex: 1;
  506. width: 100%;
  507. padding-bottom: 120rpx;
  508. }
  509. .loading-section {
  510. padding: 100rpx 30rpx;
  511. text-align: center;
  512. }
  513. .loading-text {
  514. font-size: 28rpx;
  515. color: #999999;
  516. }
  517. .book-header {
  518. display: flex;
  519. padding: 40rpx 30rpx;
  520. background-color: #FFFFFF;
  521. border-bottom: 1rpx solid #F0F0F0;
  522. }
  523. .book-cover-wrapper {
  524. margin-right: 30rpx;
  525. flex-shrink: 0;
  526. }
  527. .book-cover {
  528. width: 180rpx;
  529. height: 250rpx;
  530. border-radius: 8rpx;
  531. box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.15);
  532. background-color: #F5F5F5;
  533. }
  534. .book-info {
  535. flex: 1;
  536. display: flex;
  537. flex-direction: column;
  538. justify-content: flex-start;
  539. }
  540. .book-title {
  541. font-size: 40rpx;
  542. font-weight: bold;
  543. color: #333333;
  544. margin-bottom: 20rpx;
  545. }
  546. .book-brief {
  547. font-size: 28rpx;
  548. color: #666666;
  549. line-height: 1.6;
  550. margin-bottom: 20rpx;
  551. }
  552. .book-author {
  553. font-size: 26rpx;
  554. color: #999999;
  555. margin-bottom: 20rpx;
  556. }
  557. .book-price {
  558. font-size: 32rpx;
  559. color: #FF5722;
  560. font-weight: bold;
  561. margin-top: 10rpx;
  562. }
  563. .book-price-paid {
  564. font-size: 32rpx;
  565. color: #FF5722;
  566. font-weight: bold;
  567. }
  568. .section {
  569. padding: 40rpx 30rpx;
  570. background-color: #FFFFFF;
  571. border-bottom: 1rpx solid #F0F0F0;
  572. }
  573. .section-header {
  574. display: flex;
  575. justify-content: space-between;
  576. align-items: center;
  577. margin-bottom: 30rpx;
  578. }
  579. .section-title {
  580. font-size: 36rpx;
  581. font-weight: bold;
  582. color: #333333;
  583. margin-bottom: 20rpx;
  584. display: block;
  585. }
  586. .section-content {
  587. font-size: 28rpx;
  588. color: #666666;
  589. line-height: 1.8;
  590. display: block;
  591. }
  592. .write-review-btn {
  593. font-size: 26rpx;
  594. color: #999999;
  595. padding: 8rpx 20rpx;
  596. background-color: #F5F5F5;
  597. border-radius: 30rpx;
  598. }
  599. .review-list {
  600. display: flex;
  601. flex-direction: column;
  602. }
  603. .review-item {
  604. margin-bottom: 40rpx;
  605. padding-bottom: 40rpx;
  606. border-bottom: 1rpx solid #F0F0F0;
  607. }
  608. .review-item:last-child {
  609. border-bottom: none;
  610. padding-bottom: 0;
  611. margin-bottom: 0;
  612. }
  613. .review-header {
  614. display: flex;
  615. align-items: center;
  616. margin-bottom: 20rpx;
  617. position: relative;
  618. }
  619. .review-avatar {
  620. width: 60rpx;
  621. height: 60rpx;
  622. border-radius: 50%;
  623. margin-right: 20rpx;
  624. background-color: #F5F5F5;
  625. }
  626. .review-user-info {
  627. flex: 1;
  628. display: flex;
  629. align-items: center;
  630. }
  631. .review-name {
  632. font-size: 28rpx;
  633. color: #333333;
  634. font-weight: bold;
  635. margin-right: 15rpx;
  636. }
  637. .review-tag {
  638. font-size: 20rpx;
  639. color: #4CAF50;
  640. background-color: #E8F5E9;
  641. padding: 4rpx 12rpx;
  642. border-radius: 4rpx;
  643. }
  644. .review-date {
  645. font-size: 24rpx;
  646. color: #999999;
  647. margin-right: 20rpx;
  648. }
  649. .review-likes {
  650. display: flex;
  651. align-items: center;
  652. }
  653. .like-icon {
  654. font-size: 28rpx;
  655. margin-right: 8rpx;
  656. }
  657. .like-count {
  658. font-size: 24rpx;
  659. color: #999999;
  660. }
  661. .review-content {
  662. font-size: 28rpx;
  663. color: #666666;
  664. line-height: 1.8;
  665. display: block;
  666. }
  667. .view-all-reviews {
  668. margin-top: 30rpx;
  669. padding: 20rpx;
  670. background-color: #F5F5F5;
  671. border-radius: 8rpx;
  672. text-align: center;
  673. }
  674. .view-all-text {
  675. font-size: 28rpx;
  676. color: #333333;
  677. }
  678. .section-more {
  679. font-size: 26rpx;
  680. color: #999999;
  681. }
  682. .recommend-grid {
  683. display: flex;
  684. flex-wrap: wrap;
  685. justify-content: space-between;
  686. }
  687. .recommend-item {
  688. width: 160rpx;
  689. margin-bottom: 30rpx;
  690. }
  691. .recommend-cover {
  692. width: 160rpx;
  693. height: 220rpx;
  694. border-radius: 8rpx;
  695. margin-bottom: 15rpx;
  696. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
  697. background-color: #F5F5F5;
  698. }
  699. .recommend-title {
  700. font-size: 24rpx;
  701. color: #333333;
  702. display: block;
  703. overflow: hidden;
  704. text-overflow: ellipsis;
  705. white-space: nowrap;
  706. }
  707. .bottom-bar {
  708. position: fixed;
  709. bottom: 0;
  710. left: 0;
  711. right: 0;
  712. display: flex;
  713. padding: 20rpx 30rpx;
  714. background-color: #FFFFFF;
  715. border-top: 1rpx solid #E0E0E0;
  716. box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
  717. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  718. }
  719. .add-to-shelf-btn {
  720. flex: 1;
  721. height: 88rpx;
  722. background-color: #FFFFFF;
  723. color: #333333;
  724. font-size: 32rpx;
  725. border: 1rpx solid #E0E0E0;
  726. border-radius: 44rpx;
  727. margin-right: 20rpx;
  728. display: flex;
  729. align-items: center;
  730. justify-content: center;
  731. }
  732. .add-to-shelf-btn.in-shelf {
  733. background-color: #F5F5F5;
  734. color: #999999;
  735. border-color: #E0E0E0;
  736. }
  737. .add-to-shelf-btn[disabled] {
  738. opacity: 0.6;
  739. }
  740. .read-btn {
  741. flex: 1;
  742. height: 88rpx;
  743. background-color: #4FC3F7;
  744. color: #FFFFFF;
  745. font-size: 32rpx;
  746. border: none;
  747. border-radius: 44rpx;
  748. display: flex;
  749. align-items: center;
  750. justify-content: center;
  751. }
  752. </style>