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

808 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. this.getDateil()
  188. this.getAchievement()
  189. this.getBookCatalogList()
  190. },
  191. methods: {
  192. updateVote() {
  193. this.getDateil()
  194. this.getAchievement()
  195. },
  196. getDateil() {
  197. let data = {
  198. id: this.id
  199. }
  200. if(uni.getStorageSync('data')){
  201. data.token = uni.getStorageSync('token')
  202. }
  203. this.$fetch('getBookDetail', data).then(res => {
  204. this.novelData = res
  205. })
  206. },
  207. getAchievement() {
  208. this.$fetch('getAchievement', {
  209. id: this.id
  210. }).then(res => {
  211. this.bookLevel = res
  212. })
  213. },
  214. getBookCatalogList() {
  215. this.$fetch('getBookCatalogList', {
  216. bookId : this.id,
  217. pageNo : 1,
  218. pageSize : 9999999,
  219. reverse : 0,
  220. }).then(res => {
  221. this.chapterList = res.records
  222. this.catalog = res.records[res.records.length - 1]
  223. this.fastCatalog = res.records[0]
  224. })
  225. // 获取最后章节
  226. // this.$fetch('getBookCatalogList', {
  227. // bookId: this.id,
  228. // pageNo: 1,
  229. // pageSize: 1,
  230. // reverse: 1,
  231. // }).then(res => {
  232. // this.catalog = res.records[0]
  233. // })
  234. // // 获取第一章节
  235. // this.$fetch('getBookCatalogList', {
  236. // bookId: this.id,
  237. // pageNo: 1,
  238. // pageSize: 1,
  239. // reverse: 0,
  240. // }).then(res => {
  241. // this.fastCatalog = res.records[0]
  242. // })
  243. },
  244. toggleCollect() {
  245. this.isCollected = !this.isCollected
  246. },
  247. goToWriteReview() {
  248. uni.navigateTo({
  249. url: `/pages_order/comment/review?id=${this.id}`
  250. })
  251. },
  252. addToBookshelf() {
  253. this.$fetch('addReadBook', {
  254. shopId: this.id,
  255. name: this.novelData.name,
  256. image: this.novelData.image,
  257. }).then(res => {
  258. uni.showToast({
  259. title: '已加入书架',
  260. icon: 'success'
  261. })
  262. })
  263. },
  264. toggleInteractive() {
  265. uni.navigateTo({
  266. url: `/pages_order/novel/Tipping?id=${this.id}`
  267. })
  268. },
  269. goToGiftbox() {
  270. uni.navigateTo({
  271. url: `/pages_order/novel/Giftbox?id=${this.id}`
  272. })
  273. },
  274. toRead() {
  275. if (!this.fastCatalog) {
  276. uni.showToast({
  277. title: '暂无章节',
  278. icon: 'none'
  279. })
  280. return
  281. }
  282. uni.navigateTo({
  283. url: `/pages_order/novel/readnovels?cid=${this.fastCatalog.id}&id=${this.id}`
  284. })
  285. },
  286. selectChapter({item, index}){
  287. uni.navigateTo({
  288. url: `/pages_order/novel/readnovels?cid=${item.id}&id=${this.id}`
  289. })
  290. },
  291. }
  292. }
  293. </script>
  294. <style lang="scss" scoped>
  295. .novel-detail {
  296. min-height: 100vh;
  297. background-color: #f5f5f5;
  298. padding-bottom: calc(env(safe-area-inset-bottom) + 30rpx);
  299. .nav-header {
  300. display: flex;
  301. justify-content: space-between;
  302. align-items: center;
  303. padding: 20rpx 30rpx;
  304. background-color: transparent;
  305. position: fixed;
  306. top: 0;
  307. left: 0;
  308. right: 0;
  309. z-index: 100;
  310. }
  311. .novel-info {
  312. padding: 20rpx;
  313. display: flex;
  314. background: #fff;
  315. .novel-cover {
  316. width: 200rpx;
  317. height: 280rpx;
  318. margin-right: 20rpx;
  319. image {
  320. width: 100%;
  321. height: 100%;
  322. border-radius: 8rpx;
  323. }
  324. }
  325. .novel-basic {
  326. flex: 1;
  327. display: flex;
  328. flex-direction: column;
  329. justify-content: space-between;
  330. .title {
  331. font-size: 36rpx;
  332. font-weight: bold;
  333. margin-bottom: 16rpx;
  334. }
  335. .author-line,
  336. .status-line {
  337. display: flex;
  338. align-items: center;
  339. margin-bottom: 12rpx;
  340. font-size: 28rpx;
  341. color: #666;
  342. }
  343. .content-row {
  344. display: flex;
  345. align-items: center;
  346. margin-bottom: 10rpx;
  347. .book-status {
  348. flex-shrink: 0;
  349. text {
  350. font-size: 20rpx;
  351. color: #67C23A;
  352. background-color: rgba(103, 194, 58, 0.1);
  353. border-radius: 20rpx;
  354. padding: 4rpx 12rpx;
  355. }
  356. }
  357. .book-text {
  358. font-size: 20rpx;
  359. }
  360. }
  361. .label {
  362. color: #999;
  363. margin-right: 8rpx;
  364. }
  365. .score-line {
  366. margin-top: 16rpx;
  367. .score {
  368. font-size: 32rpx;
  369. color: #333;
  370. font-weight: bold;
  371. }
  372. .score-label {
  373. font-size: 24rpx;
  374. color: #999;
  375. margin-left: 8rpx;
  376. }
  377. }
  378. }
  379. }
  380. .recommendation-section {
  381. padding: 24rpx 32rpx;
  382. background: #fff;
  383. display: flex;
  384. justify-content: space-between;
  385. align-items: center;
  386. position: relative;
  387. .rec-left {
  388. display: flex;
  389. flex-direction: column;
  390. align-items: flex-start;
  391. margin-left: 70rpx;
  392. .rec-count {
  393. font-size: 44rpx;
  394. font-weight: 500;
  395. color: #333;
  396. line-height: 1.2;
  397. }
  398. .rec-label {
  399. font-size: 26rpx;
  400. color: #999;
  401. margin-top: 4rpx;
  402. }
  403. }
  404. .rec-divider {
  405. position: absolute;
  406. right: 160rpx;
  407. top: 20rpx;
  408. bottom: 20rpx;
  409. width: 2rpx;
  410. background: #eee;
  411. }
  412. .rec-right {
  413. flex-shrink: 0;
  414. .recommend-btn {
  415. background: #fff;
  416. color: #4a90e2;
  417. border: 2rpx solid #4a90e2;
  418. border-radius: 40rpx;
  419. padding: 12rpx 32rpx;
  420. font-size: 28rpx;
  421. display: flex;
  422. align-items: center;
  423. line-height: 1;
  424. height: 64rpx;
  425. .btn-icon {
  426. margin-right: 8rpx;
  427. font-size: 32rpx;
  428. }
  429. }
  430. }
  431. }
  432. .action-buttons {
  433. display: flex;
  434. padding: 30rpx;
  435. gap: 20rpx;
  436. button {
  437. flex: 1;
  438. height: 80rpx;
  439. border-radius: 40rpx;
  440. font-size: 32rpx;
  441. display: flex;
  442. align-items: center;
  443. justify-content: center;
  444. }
  445. .read-btn {
  446. background-color: #4a90e2;
  447. color: #fff;
  448. }
  449. .collect-btn {
  450. background-color: #f0f0f0;
  451. color: #666;
  452. }
  453. }
  454. .user-level {
  455. margin: 20rpx 30rpx;
  456. background-color: #fff;
  457. border-radius: 12rpx;
  458. padding: 24rpx 32rpx;
  459. display: flex;
  460. justify-content: space-between;
  461. align-items: stretch;
  462. .level-left {
  463. flex: 1;
  464. .level-title {
  465. display: flex;
  466. align-items: center;
  467. gap: 8rpx;
  468. margin-bottom: 20rpx;
  469. margin-left: 20rpx;
  470. .title-icon {
  471. font-size: 36rpx;
  472. color: #FFB800;
  473. }
  474. text {
  475. font-size: 32rpx;
  476. font-weight: 500;
  477. color: #333;
  478. }
  479. }
  480. .level-info {
  481. display: flex;
  482. align-items: flex-start;
  483. gap: 20rpx;
  484. .user-avatar {
  485. width: 80rpx;
  486. height: 80rpx;
  487. border-radius: 50%;
  488. border: 2rpx solid #f0f0f0;
  489. }
  490. .user-details {
  491. display: flex;
  492. flex-direction: column;
  493. gap: 8rpx;
  494. .username {
  495. display: flex;
  496. font-size: 28rpx;
  497. color: #333;
  498. font-weight: 500;
  499. image {
  500. width: 60rpx;
  501. height: 60rpx;
  502. }
  503. }
  504. .user-score {
  505. display: flex;
  506. align-items: center;
  507. gap: 8rpx;
  508. .score-value {
  509. font-size: 28rpx;
  510. color: #333;
  511. }
  512. .score-label {
  513. font-size: 24rpx;
  514. color: #999;
  515. }
  516. }
  517. .user-role {
  518. font-size: 24rpx;
  519. color: #666;
  520. background: #f5f5f5;
  521. padding: 4rpx 12rpx;
  522. border-radius: 4rpx;
  523. display: inline-block;
  524. }
  525. }
  526. }
  527. }
  528. .level-right {
  529. display: flex;
  530. align-items: center;
  531. .rank-btn {
  532. display: flex;
  533. flex-direction: column;
  534. align-items: center;
  535. justify-content: center;
  536. padding: 0 20rpx;
  537. .rank-icon {
  538. width: 200rpx;
  539. height: 60rpx;
  540. margin-bottom: 8rpx;
  541. }
  542. text {
  543. font-size: 26rpx;
  544. color: #333;
  545. line-height: 1.4;
  546. }
  547. .check-text {
  548. font-size: 22rpx;
  549. color: #999;
  550. }
  551. }
  552. }
  553. }
  554. .novel-intro {
  555. margin: 20rpx 30rpx;
  556. background-color: #fff;
  557. border-radius: 12rpx;
  558. padding: 24rpx;
  559. .intro-title {
  560. font-size: 32rpx;
  561. font-weight: 500;
  562. color: #333;
  563. margin-bottom: 16rpx;
  564. }
  565. .intro-content {
  566. font-size: 28rpx;
  567. color: #666;
  568. line-height: 1.6;
  569. display: flex;
  570. flex-direction: column;
  571. gap: 16rpx;
  572. text {
  573. display: block;
  574. }
  575. }
  576. }
  577. .comments-section {
  578. margin: 20rpx 30rpx;
  579. background-color: #fff;
  580. border-radius: 12rpx;
  581. padding: 24rpx;
  582. .comments-header {
  583. display: flex;
  584. align-items: center;
  585. margin-bottom: 24rpx;
  586. border-bottom: 2rpx solid #f5f5f5;
  587. padding-bottom: 24rpx;
  588. justify-content: flex-start;
  589. .header-left {
  590. display: flex;
  591. align-items: center;
  592. gap: 8rpx;
  593. .title-icon {
  594. font-size: 32rpx;
  595. }
  596. text {
  597. display: flex;
  598. align-items: center;
  599. font-size: 32rpx;
  600. font-weight: 500;
  601. color: #333;
  602. white-space: nowrap;
  603. }
  604. }
  605. .header-right {
  606. margin-left: auto;
  607. }
  608. }
  609. .comment-list {
  610. display: flex;
  611. flex-direction: column;
  612. gap: 32rpx;
  613. }
  614. .like-icon {
  615. font-size: 24rpx;
  616. color: #999;
  617. }
  618. .like-count {
  619. font-size: 24rpx;
  620. color: #999;
  621. }
  622. }
  623. .novel-catalog {
  624. margin: 20rpx 30rpx;
  625. background-color: #fff;
  626. border-radius: 12rpx;
  627. padding: 24rpx;
  628. .catalog-header {
  629. display: flex;
  630. justify-content: space-between;
  631. align-items: center;
  632. border-bottom: 2rpx solid #f5f5f5;
  633. .catalog-title {
  634. display: flex;
  635. align-items: center;
  636. gap: 8rpx;
  637. .title-icon {
  638. font-size: 32rpx;
  639. }
  640. text {
  641. font-size: 32rpx;
  642. font-weight: 500;
  643. color: #333;
  644. }
  645. }
  646. .chapter-nav {
  647. display: flex;
  648. align-items: center;
  649. gap: 8rpx;
  650. .current-chapter {
  651. font-size: 28rpx;
  652. color: #666;
  653. }
  654. .nav-arrow {
  655. font-size: 28rpx;
  656. color: #999;
  657. }
  658. }
  659. }
  660. }
  661. .novel-bottom {
  662. position: fixed;
  663. bottom: 0;
  664. left: 0;
  665. right: 0;
  666. height: 100rpx;
  667. background: #fff;
  668. display: flex;
  669. align-items: center;
  670. padding: 0 30rpx;
  671. padding-top: 15rpx;
  672. padding-bottom: env(safe-area-inset-bottom);
  673. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  674. gap: 40rpx;
  675. .bottom-left {
  676. display: flex;
  677. gap: 40rpx;
  678. .action-btn {
  679. display: flex;
  680. flex-direction: column;
  681. align-items: center;
  682. gap: 4rpx;
  683. .btn-icon {
  684. font-size: 40rpx;
  685. line-height: 1;
  686. }
  687. text {
  688. font-size: 24rpx;
  689. color: #666;
  690. }
  691. }
  692. }
  693. .bottom-right {
  694. flex: 1;
  695. display: flex;
  696. .read-now-btn {
  697. flex: 1;
  698. background: #1a237e;
  699. color: #fff;
  700. font-size: 32rpx;
  701. height: 80rpx;
  702. line-height: 80rpx;
  703. padding: 0 60rpx;
  704. border-radius: 40rpx;
  705. border: none;
  706. }
  707. }
  708. }
  709. }
  710. </style>