小说小程序前端代码仓库(小程序)
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.

812 lines
16 KiB

  1. <template>
  2. <!-- 小说详情页面 -->
  3. <view class="novel-detail">
  4. <navbar title="小说详情" leftClick @leftClick="$utils.navigateBack" />
  5. <!-- 小说基本信息 -->
  6. <view class="novel-info">
  7. <view class="novel-cover">
  8. <image :src="novelData.image &&
  9. novelData.image.split(',')[0]" mode="aspectFill"></image>
  10. </view>
  11. <view class="novel-basic">
  12. <text class="title">{{ novelData.name }}</text>
  13. <view class="author-line">
  14. <text class="label">作者</text>
  15. <text class="author">{{ novelData.author }}</text>
  16. </view>
  17. <!-- <view class="status-line">
  18. <text class="label">完结</text>
  19. <text class="status">{{ novelData.status }}</text>
  20. </view> -->
  21. <view class="content-row">
  22. <view class="book-status">
  23. <text>{{novelData.status}}</text>
  24. </view>
  25. <view class="book-text">
  26. {{ novelData.service }}
  27. </view>
  28. </view>
  29. <view class="score-line">
  30. <text class="score">{{ novelData.qmNum || 0}}</text>
  31. <text class="score-label">作者累计亲密度值</text>
  32. </view>
  33. </view>
  34. </view>
  35. <!-- 推荐票数显示 -->
  36. <view class="recommendation-section">
  37. <view class="rec-left">
  38. <text class="rec-count">{{ novelData.tuiNum || 0 }}</text>
  39. <text class="rec-label">推荐票数</text>
  40. </view>
  41. <view class="rec-divider"></view>
  42. <view class="rec-right">
  43. <button class="recommend-btn" @click="$refs.novelVotePopup.open(id)">
  44. <text class="btn-icon">📑</text>
  45. 投推荐票
  46. </button>
  47. </view>
  48. </view>
  49. <!-- 阅读和收藏按钮 -->
  50. <!-- 我的等级 -->
  51. <view class="user-level">
  52. <view class="level-left">
  53. <view class="level-title">
  54. <!-- <text class="title-icon">👑</text> -->
  55. <image style="width: 30rpx;height: 30rpx;" src="/pages_order/static/book/level.png"
  56. mode="aspectFill"></image>
  57. <text>我的等级</text>
  58. </view>
  59. <view class="level-info">
  60. <image class="user-avatar" src="/pages_order/static/book/dj.png" mode="aspectFill"></image>
  61. <view class="user-details">
  62. <text class="username">
  63. <image
  64. src="https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain"
  65. mode="aspectFill"></image>
  66. <view class="name">
  67. 周海
  68. </view>
  69. </text>
  70. <view class="user-score">
  71. <text class="score-value">6785452</text>
  72. <text class="score-label">亲密值</text>
  73. </view>
  74. <text class="user-role">护书使者 五级</text>
  75. </view>
  76. </view>
  77. </view>
  78. <view class="level-right">
  79. <view class="rank-btn" @click="toggleInteractive">
  80. <image class="rank-icon" src="/pages_order/static/book/bd.png" mode="aspectFit"></image>
  81. <text class="check-text">点击查看</text>
  82. </view>
  83. </view>
  84. </view>
  85. <!-- 小说简介 -->
  86. <view class="novel-intro">
  87. <view class="intro-title">
  88. <text>简介</text>
  89. </view>
  90. <view class="intro-content">
  91. {{ novelData.details }}
  92. </view>
  93. </view>
  94. <!-- 目录 -->
  95. <view class="novel-catalog" @click="$refs.chapterPopup.open()">
  96. <view class="catalog-header">
  97. <view class="catalog-title">
  98. <text class="title-icon">📖</text>
  99. <text>目录</text>
  100. </view>
  101. <view class="chapter-nav">
  102. <text class="current-chapter">
  103. {{ catalog ? catalog.title : '暂无章节' }}
  104. </text>
  105. <text class="nav-arrow">></text>
  106. </view>
  107. </view>
  108. </view>
  109. <!-- 书评区域 -->
  110. <view class="comments-section">
  111. <view class="comments-header">
  112. <view class="header-left">
  113. <text class="title-icon">📝</text>
  114. <text>书评</text>
  115. </view>
  116. <view class="header-right">
  117. <text @click="goToWriteReview">写书评</text>
  118. </view>
  119. </view>
  120. <view class="comment-list">
  121. <commentItem v-for="(item, index) in list" :item="item" :key="index" />
  122. <uv-empty mode="list" v-if="list.length == 0"></uv-empty>
  123. </view>
  124. </view>
  125. <!-- 底部操作栏 -->
  126. <view class="novel-bottom">
  127. <view class="bottom-left">
  128. <view class="action-btn" @click="addToBookshelf">
  129. <view class="btn-icon">
  130. <uv-icon name="grid" color="#999" size="60rpx"></uv-icon>
  131. </view>
  132. <text>加入书架</text>
  133. </view>
  134. <!-- <view class="action-btn" @click="goToGiftbox">
  135. <text class="btn-icon">🎁</text>
  136. <text>礼物盒</text>
  137. </view> -->
  138. <view class="action-btn" @click="$utils.navigateTo(`/pages_order/novel/Giftbox?id=${novelData.id}`)">
  139. <view class="btn-icon">
  140. <uv-icon name="gift" color="#999" size="60rpx"></uv-icon>
  141. </view>
  142. <text>互动打赏</text>
  143. </view>
  144. </view>
  145. <view class="bottom-right">
  146. <button class="read-now-btn" @click="toRead">立即阅读</button>
  147. </view>
  148. </view>
  149. <novelVotePopup ref="novelVotePopup" @updateVote="updateVote"/>
  150. <chapterPopup ref="chapterPopup" :bookId="id" :chapterList="chapterList" @selectChapter="selectChapter"/>
  151. </view>
  152. </template>
  153. <script>
  154. import catalogpopup from '@/components/novel/CatalogPopup.vue'
  155. import chapterPopup from '../components/novel/chapterPopup.vue'
  156. import commentItem from '../components/comment/commentItem.vue'
  157. import novelVotePopup from '../components/novel/novelVotePopup.vue'
  158. import mixinsList from '@/mixins/list.js'
  159. export default {
  160. mixins: [mixinsList],
  161. components: {
  162. catalogpopup,
  163. chapterPopup,
  164. commentItem,
  165. novelVotePopup,
  166. },
  167. data() {
  168. return {
  169. novelData: {},
  170. isCollected: false,
  171. comments: [],
  172. currentIndex: 0,
  173. id: 0,
  174. bookLevel: {},
  175. mixinsListApi: 'getBookCommentList',
  176. catalog: {}, //最后一个章节
  177. fastCatalog: {}, //第一章节
  178. chapterList : [],//章节列表
  179. }
  180. },
  181. computed: {},
  182. onLoad({
  183. id
  184. }) {
  185. this.id = id
  186. this.queryParams.bookId = id
  187. },
  188. onShow() {
  189. this.getDateil()
  190. this.getBookCatalogList()
  191. if(this.isLogin){
  192. this.getAchievement()
  193. }
  194. },
  195. methods: {
  196. updateVote() {
  197. this.getDateil()
  198. this.getAchievement()
  199. },
  200. getDateil() {
  201. let data = {
  202. id: this.id
  203. }
  204. if(uni.getStorageSync('data')){
  205. data.token = uni.getStorageSync('token')
  206. }
  207. this.$fetch('getBookDetail', data).then(res => {
  208. this.novelData = res
  209. })
  210. },
  211. getAchievement() {
  212. this.$fetch('getAchievement', {
  213. id: this.id
  214. }).then(res => {
  215. this.bookLevel = res
  216. })
  217. },
  218. getBookCatalogList() {
  219. this.$fetch('getBookCatalogList', {
  220. bookId : this.id,
  221. pageNo : 1,
  222. pageSize : 9999999,
  223. reverse : 0,
  224. }).then(res => {
  225. this.chapterList = res.records
  226. this.catalog = res.records[res.records.length - 1]
  227. this.fastCatalog = res.records[0]
  228. })
  229. // 获取最后章节
  230. // this.$fetch('getBookCatalogList', {
  231. // bookId: this.id,
  232. // pageNo: 1,
  233. // pageSize: 1,
  234. // reverse: 1,
  235. // }).then(res => {
  236. // this.catalog = res.records[0]
  237. // })
  238. // // 获取第一章节
  239. // this.$fetch('getBookCatalogList', {
  240. // bookId: this.id,
  241. // pageNo: 1,
  242. // pageSize: 1,
  243. // reverse: 0,
  244. // }).then(res => {
  245. // this.fastCatalog = res.records[0]
  246. // })
  247. },
  248. toggleCollect() {
  249. this.isCollected = !this.isCollected
  250. },
  251. goToWriteReview() {
  252. uni.navigateTo({
  253. url: `/pages_order/comment/review?id=${this.id}`
  254. })
  255. },
  256. addToBookshelf() {
  257. this.$fetch('addReadBook', {
  258. shopId: this.id,
  259. name: this.novelData.name,
  260. image: this.novelData.image,
  261. }).then(res => {
  262. uni.showToast({
  263. title: '已加入书架',
  264. icon: 'success'
  265. })
  266. })
  267. },
  268. toggleInteractive() {
  269. uni.navigateTo({
  270. url: `/pages_order/novel/Tipping?id=${this.id}`
  271. })
  272. },
  273. goToGiftbox() {
  274. uni.navigateTo({
  275. url: `/pages_order/novel/Giftbox?id=${this.id}`
  276. })
  277. },
  278. toRead() {
  279. if (!this.fastCatalog) {
  280. uni.showToast({
  281. title: '暂无章节',
  282. icon: 'none'
  283. })
  284. return
  285. }
  286. uni.navigateTo({
  287. url: `/pages_order/novel/readnovels?cid=${this.fastCatalog.id}&id=${this.id}`
  288. })
  289. },
  290. selectChapter({item, index}){
  291. uni.navigateTo({
  292. url: `/pages_order/novel/readnovels?cid=${item.id}&id=${this.id}`
  293. })
  294. },
  295. }
  296. }
  297. </script>
  298. <style lang="scss" scoped>
  299. .novel-detail {
  300. min-height: 100vh;
  301. background-color: #f5f5f5;
  302. padding-bottom: calc(env(safe-area-inset-bottom) + 30rpx);
  303. .nav-header {
  304. display: flex;
  305. justify-content: space-between;
  306. align-items: center;
  307. padding: 20rpx 30rpx;
  308. background-color: transparent;
  309. position: fixed;
  310. top: 0;
  311. left: 0;
  312. right: 0;
  313. z-index: 100;
  314. }
  315. .novel-info {
  316. padding: 20rpx;
  317. display: flex;
  318. background: #fff;
  319. .novel-cover {
  320. width: 200rpx;
  321. height: 280rpx;
  322. margin-right: 20rpx;
  323. image {
  324. width: 100%;
  325. height: 100%;
  326. border-radius: 8rpx;
  327. }
  328. }
  329. .novel-basic {
  330. flex: 1;
  331. display: flex;
  332. flex-direction: column;
  333. justify-content: space-between;
  334. .title {
  335. font-size: 36rpx;
  336. font-weight: bold;
  337. margin-bottom: 16rpx;
  338. }
  339. .author-line,
  340. .status-line {
  341. display: flex;
  342. align-items: center;
  343. margin-bottom: 12rpx;
  344. font-size: 28rpx;
  345. color: #666;
  346. }
  347. .content-row {
  348. display: flex;
  349. align-items: center;
  350. margin-bottom: 10rpx;
  351. .book-status {
  352. flex-shrink: 0;
  353. text {
  354. font-size: 20rpx;
  355. color: #67C23A;
  356. background-color: rgba(103, 194, 58, 0.1);
  357. border-radius: 20rpx;
  358. padding: 4rpx 12rpx;
  359. }
  360. }
  361. .book-text {
  362. font-size: 20rpx;
  363. }
  364. }
  365. .label {
  366. color: #999;
  367. margin-right: 8rpx;
  368. }
  369. .score-line {
  370. margin-top: 16rpx;
  371. .score {
  372. font-size: 32rpx;
  373. color: #333;
  374. font-weight: bold;
  375. }
  376. .score-label {
  377. font-size: 24rpx;
  378. color: #999;
  379. margin-left: 8rpx;
  380. }
  381. }
  382. }
  383. }
  384. .recommendation-section {
  385. padding: 24rpx 32rpx;
  386. background: #fff;
  387. display: flex;
  388. justify-content: space-between;
  389. align-items: center;
  390. position: relative;
  391. .rec-left {
  392. display: flex;
  393. flex-direction: column;
  394. align-items: flex-start;
  395. margin-left: 70rpx;
  396. .rec-count {
  397. font-size: 44rpx;
  398. font-weight: 500;
  399. color: #333;
  400. line-height: 1.2;
  401. }
  402. .rec-label {
  403. font-size: 26rpx;
  404. color: #999;
  405. margin-top: 4rpx;
  406. }
  407. }
  408. .rec-divider {
  409. position: absolute;
  410. right: 160rpx;
  411. top: 20rpx;
  412. bottom: 20rpx;
  413. width: 2rpx;
  414. background: #eee;
  415. }
  416. .rec-right {
  417. flex-shrink: 0;
  418. .recommend-btn {
  419. background: #fff;
  420. color: #4a90e2;
  421. border: 2rpx solid #4a90e2;
  422. border-radius: 40rpx;
  423. padding: 12rpx 32rpx;
  424. font-size: 28rpx;
  425. display: flex;
  426. align-items: center;
  427. line-height: 1;
  428. height: 64rpx;
  429. .btn-icon {
  430. margin-right: 8rpx;
  431. font-size: 32rpx;
  432. }
  433. }
  434. }
  435. }
  436. .action-buttons {
  437. display: flex;
  438. padding: 30rpx;
  439. gap: 20rpx;
  440. button {
  441. flex: 1;
  442. height: 80rpx;
  443. border-radius: 40rpx;
  444. font-size: 32rpx;
  445. display: flex;
  446. align-items: center;
  447. justify-content: center;
  448. }
  449. .read-btn {
  450. background-color: #4a90e2;
  451. color: #fff;
  452. }
  453. .collect-btn {
  454. background-color: #f0f0f0;
  455. color: #666;
  456. }
  457. }
  458. .user-level {
  459. margin: 20rpx 30rpx;
  460. background-color: #fff;
  461. border-radius: 12rpx;
  462. padding: 24rpx 32rpx;
  463. display: flex;
  464. justify-content: space-between;
  465. align-items: stretch;
  466. .level-left {
  467. flex: 1;
  468. .level-title {
  469. display: flex;
  470. align-items: center;
  471. gap: 8rpx;
  472. margin-bottom: 20rpx;
  473. margin-left: 20rpx;
  474. .title-icon {
  475. font-size: 36rpx;
  476. color: #FFB800;
  477. }
  478. text {
  479. font-size: 32rpx;
  480. font-weight: 500;
  481. color: #333;
  482. }
  483. }
  484. .level-info {
  485. display: flex;
  486. align-items: flex-start;
  487. gap: 20rpx;
  488. .user-avatar {
  489. width: 80rpx;
  490. height: 80rpx;
  491. border-radius: 50%;
  492. border: 2rpx solid #f0f0f0;
  493. }
  494. .user-details {
  495. display: flex;
  496. flex-direction: column;
  497. gap: 8rpx;
  498. .username {
  499. display: flex;
  500. font-size: 28rpx;
  501. color: #333;
  502. font-weight: 500;
  503. image {
  504. width: 60rpx;
  505. height: 60rpx;
  506. }
  507. }
  508. .user-score {
  509. display: flex;
  510. align-items: center;
  511. gap: 8rpx;
  512. .score-value {
  513. font-size: 28rpx;
  514. color: #333;
  515. }
  516. .score-label {
  517. font-size: 24rpx;
  518. color: #999;
  519. }
  520. }
  521. .user-role {
  522. font-size: 24rpx;
  523. color: #666;
  524. background: #f5f5f5;
  525. padding: 4rpx 12rpx;
  526. border-radius: 4rpx;
  527. display: inline-block;
  528. }
  529. }
  530. }
  531. }
  532. .level-right {
  533. display: flex;
  534. align-items: center;
  535. .rank-btn {
  536. display: flex;
  537. flex-direction: column;
  538. align-items: center;
  539. justify-content: center;
  540. padding: 0 20rpx;
  541. .rank-icon {
  542. width: 200rpx;
  543. height: 60rpx;
  544. margin-bottom: 8rpx;
  545. }
  546. text {
  547. font-size: 26rpx;
  548. color: #333;
  549. line-height: 1.4;
  550. }
  551. .check-text {
  552. font-size: 22rpx;
  553. color: #999;
  554. }
  555. }
  556. }
  557. }
  558. .novel-intro {
  559. margin: 20rpx 30rpx;
  560. background-color: #fff;
  561. border-radius: 12rpx;
  562. padding: 24rpx;
  563. .intro-title {
  564. font-size: 32rpx;
  565. font-weight: 500;
  566. color: #333;
  567. margin-bottom: 16rpx;
  568. }
  569. .intro-content {
  570. font-size: 28rpx;
  571. color: #666;
  572. line-height: 1.6;
  573. display: flex;
  574. flex-direction: column;
  575. gap: 16rpx;
  576. text {
  577. display: block;
  578. }
  579. }
  580. }
  581. .comments-section {
  582. margin: 20rpx 30rpx;
  583. background-color: #fff;
  584. border-radius: 12rpx;
  585. padding: 24rpx;
  586. .comments-header {
  587. display: flex;
  588. align-items: center;
  589. margin-bottom: 24rpx;
  590. border-bottom: 2rpx solid #f5f5f5;
  591. padding-bottom: 24rpx;
  592. justify-content: flex-start;
  593. .header-left {
  594. display: flex;
  595. align-items: center;
  596. gap: 8rpx;
  597. .title-icon {
  598. font-size: 32rpx;
  599. }
  600. text {
  601. display: flex;
  602. align-items: center;
  603. font-size: 32rpx;
  604. font-weight: 500;
  605. color: #333;
  606. white-space: nowrap;
  607. }
  608. }
  609. .header-right {
  610. margin-left: auto;
  611. }
  612. }
  613. .comment-list {
  614. display: flex;
  615. flex-direction: column;
  616. gap: 32rpx;
  617. }
  618. .like-icon {
  619. font-size: 24rpx;
  620. color: #999;
  621. }
  622. .like-count {
  623. font-size: 24rpx;
  624. color: #999;
  625. }
  626. }
  627. .novel-catalog {
  628. margin: 20rpx 30rpx;
  629. background-color: #fff;
  630. border-radius: 12rpx;
  631. padding: 24rpx;
  632. .catalog-header {
  633. display: flex;
  634. justify-content: space-between;
  635. align-items: center;
  636. border-bottom: 2rpx solid #f5f5f5;
  637. .catalog-title {
  638. display: flex;
  639. align-items: center;
  640. gap: 8rpx;
  641. .title-icon {
  642. font-size: 32rpx;
  643. }
  644. text {
  645. font-size: 32rpx;
  646. font-weight: 500;
  647. color: #333;
  648. }
  649. }
  650. .chapter-nav {
  651. display: flex;
  652. align-items: center;
  653. gap: 8rpx;
  654. .current-chapter {
  655. font-size: 28rpx;
  656. color: #666;
  657. }
  658. .nav-arrow {
  659. font-size: 28rpx;
  660. color: #999;
  661. }
  662. }
  663. }
  664. }
  665. .novel-bottom {
  666. position: fixed;
  667. bottom: 0;
  668. left: 0;
  669. right: 0;
  670. height: 100rpx;
  671. background: #fff;
  672. display: flex;
  673. align-items: center;
  674. padding: 0 30rpx;
  675. padding-top: 15rpx;
  676. padding-bottom: env(safe-area-inset-bottom);
  677. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  678. gap: 40rpx;
  679. .bottom-left {
  680. display: flex;
  681. gap: 40rpx;
  682. .action-btn {
  683. display: flex;
  684. flex-direction: column;
  685. align-items: center;
  686. gap: 4rpx;
  687. .btn-icon {
  688. font-size: 40rpx;
  689. line-height: 1;
  690. }
  691. text {
  692. font-size: 24rpx;
  693. color: #666;
  694. }
  695. }
  696. }
  697. .bottom-right {
  698. flex: 1;
  699. display: flex;
  700. .read-now-btn {
  701. flex: 1;
  702. background: #1a237e;
  703. color: #fff;
  704. font-size: 32rpx;
  705. height: 80rpx;
  706. line-height: 80rpx;
  707. padding: 0 60rpx;
  708. border-radius: 40rpx;
  709. border: none;
  710. }
  711. }
  712. }
  713. }
  714. </style>