小说网站前端代码仓库
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.

478 lines
17 KiB

  1. <template>
  2. <div class="book-detail-container">
  3. <!-- 小说基本信息部分 -->
  4. <div class="book-info-wrapper">
  5. <div class="book-info">
  6. <div class="book-cover">
  7. <img :src="book.cover" :alt="book.title">
  8. </div>
  9. <div class="book-details">
  10. <h1 class="book-title">{{ book.title }}</h1>
  11. <div class="book-meta">
  12. <div class="meta-item">
  13. <span class="label">作者</span>
  14. <span class="value">{{ book.author }}</span>
  15. </div>
  16. <div class="meta-item book-status-row">
  17. <span class="status-badge">{{ book.status }}</span>
  18. <span class="dot">·</span>
  19. <span class="reading-tip">大家都在读</span>
  20. </div>
  21. </div>
  22. <div class="book-user-info">
  23. <div class="user-badges">
  24. <img class="level-icon" src="@/assets/images/book/level.png" alt="我的等级" />
  25. <span class="badge-label">我的等级</span>
  26. </div>
  27. <div class="user-medal">
  28. <img class="medal-icon" src="@/assets/images/image-1.png" alt="勋章" />
  29. </div>
  30. <div class="user-avatar-block">
  31. <img class="user-avatar" src="@/assets/images/center/headImage.png" alt="用户头像" />
  32. <span class="user-name">小巴</span>
  33. <span class="dot">·</span>
  34. <span class="user-title">VIP会员</span>
  35. </div>
  36. <span class="user-intimacy">1000 累计亲密值</span>
  37. </div>
  38. <div class="action-buttons">
  39. <div class="action-btn-group">
  40. <el-button class="reward-btn" plain @click="showRewardDialog">互动打赏</el-button>
  41. </div>
  42. <div class="action-btn-group">
  43. <el-button
  44. :class="['add-to-shelf-btn', isInShelf ? 'in-shelf' : '']"
  45. :type="isInShelf ? '' : 'primary'"
  46. @click="toggleShelf"
  47. >{{ isInShelf ? '已加入书架' : '加入书架' }}</el-button>
  48. </div>
  49. <div class="action-btn-group">
  50. <el-button class="read-btn"
  51. @click="goToChapter(1)"
  52. style="background:#0A2463;color:#fff;border:none;">点击阅读</el-button>
  53. </div>
  54. </div>
  55. </div>
  56. </div>
  57. </div>
  58. <!-- 主体内容区域左右分栏 -->
  59. <div class="book-main-content">
  60. <div class="main-left">
  61. <!-- 推荐票/亲密值统计 -->
  62. <BookStats :book-id="book.id" />
  63. <!-- 小说介绍 -->
  64. <BookIntro :book-data="book" />
  65. <!-- 目录 -->
  66. <BookCatalog :book-id="book.id" />
  67. <!-- 评论 -->
  68. <BookComments :book-id="book.id" />
  69. </div>
  70. <div class="main-right">
  71. <!-- 读者亲密值榜单 -->
  72. <IntimacyRanking :book-id="book.id" />
  73. </div>
  74. </div>
  75. </div>
  76. <!-- 互动打赏弹窗 -->
  77. <InteractiveReward
  78. v-model:visible="rewardDialogVisible"
  79. :book-id="book.id"
  80. @reward-success="handleRewardSuccess"
  81. />
  82. </template>
  83. <script>
  84. import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
  85. import { useRoute, useRouter } from 'vue-router';
  86. import BookCard from '@/components/common/BookCard.vue';
  87. import BookStats from '@/components/book/BookStats.vue';
  88. import BookIntro from '@/components/book/BookIntro.vue';
  89. import BookCatalog from '@/components/book/BookCatalog.vue';
  90. import BookComments from '@/components/book/BookComments.vue';
  91. import IntimacyRanking from '@/components/ranking/IntimacyRanking.vue';
  92. import InteractiveReward from '@/components/book/InteractiveReward.vue';
  93. export default {
  94. name: 'BookDetail',
  95. components: {
  96. BookCard,
  97. BookStats,
  98. BookIntro,
  99. BookCatalog,
  100. BookComments,
  101. IntimacyRanking,
  102. InteractiveReward
  103. },
  104. setup() {
  105. const route = useRoute();
  106. const router = useRouter();
  107. const bookId = route.params.id;
  108. const book = reactive({
  109. id: bookId,
  110. title: '重生之财源滚滚',
  111. author: '老鹰的沙',
  112. category: '都市小说',
  113. status: '已完结',
  114. userGroup: '638781087(网友交流群)',
  115. cover: 'https://bookcover.yuewen.com/qdbimg/349573/1041637443/150.webp',
  116. description: `<p>当那一世——</p>
  117. <p>贺季宁曾经是A市土豪公司的金牌经理工资高福利好女友漂亮车位靓最重要的是老板信任他但是贺季宁的对手不服老板娘不喜欢他不怀好意的对手们联合一起终于把他拉下台了他失去工作更可悲的是他连公司配他的贷款的房子都搭进去了欠了几百万的债女友也离他而去</p>
  118. <p>临终前他喃喃自语如果老天给我重来一次的机会我一定要好好做人赚很多钱让那帮人看着我过得好就比他们难受</p>`,
  119. userGroup: '638781087(网友交流群)',
  120. chapters: [
  121. { id: '1', title: '第一章 重回2004', isNew: false },
  122. { id: '2', title: '第二章 旧车旧房', isNew: false },
  123. { id: '3', title: '第三章 再拼一把', isNew: false },
  124. { id: '4', title: '第四章 带女朋友逛街', isNew: false },
  125. { id: '5', title: '第五章 小刺激', isNew: false },
  126. { id: '6', title: '第六章 老王的门道', isNew: false },
  127. { id: '7', title: '第七章 正中家门', isNew: false },
  128. { id: '8', title: '第八章 被逼迫的交易', isNew: false },
  129. { id: '9', title: '第九章 意外惊喜', isNew: false },
  130. { id: '10', title: '第十章 生意的桥梁', isNew: true },
  131. { id: '11', title: '第十一章 家族分崩离析', isNew: false },
  132. { id: '12', title: '第十二章 老张没来', isNew: false }
  133. ],
  134. comments: [
  135. {
  136. username: '万年捧',
  137. avatar: 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKlR5PibUEEsVjXGfH4c1eR5hXDicoH0EJUTHYwDO3EvZLXXgON8GrNTbRg8DnzaddicibYnGcfq28tYg/132',
  138. time: '2022-07-03',
  139. content: '这是本年内看的唯一一部完结的!看的人真幸运,发家文和风险防控写的都是一流'
  140. },
  141. {
  142. username: '残生往事',
  143. avatar: 'https://thirdwx.qlogo.cn/mmopen/vi_32/3F4feeHnMyoGjqKfP8vGKCHwyvovMHiaO0Q1QkQMRTGibLcyJbUcUJ4LmdkkDqC5ZcqP1rvqKMviaYAyehqYb6ciaA/132',
  144. content: '我很喜欢男主的性格,不小心眼,有格局,做事情多考虑下一步,商业和情感都处理得不错,就是那个林涵有点没必要吧?'
  145. }
  146. ]
  147. });
  148. // 推荐书籍
  149. const recommendedBooks = reactive([
  150. {
  151. id: '101',
  152. title: '三体',
  153. author: '刘慈欣',
  154. cover: 'https://picsum.photos/120/160?random=1',
  155. status: '已完结'
  156. },
  157. {
  158. id: '102',
  159. title: '活着',
  160. author: '余华',
  161. cover: 'https://picsum.photos/120/160?random=2',
  162. status: '已完结'
  163. },
  164. {
  165. id: '103',
  166. title: '平凡的世界',
  167. author: '路遥',
  168. cover: 'https://picsum.photos/120/160?random=3',
  169. status: '已完结'
  170. },
  171. {
  172. id: '104',
  173. title: '围城',
  174. author: '钱钟书',
  175. cover: 'https://picsum.photos/120/160?random=4',
  176. status: '已完结'
  177. }
  178. ]);
  179. const activeTab = ref('intro');
  180. const currentPage = ref(1);
  181. const commentText = ref('');
  182. const isInShelf = ref(false);
  183. const rewardDialogVisible = ref(false);
  184. const handlePageChange = (page) => {
  185. currentPage.value = page;
  186. // 实际应用中这里应该加载对应页的数据
  187. };
  188. // 创建一个清理函数
  189. const cleanup = () => {
  190. // 清理所有响应式数据
  191. Object.keys(book).forEach(key => {
  192. if (typeof book[key] === 'object') {
  193. book[key] = null;
  194. }
  195. });
  196. activeTab.value = 'intro';
  197. currentPage.value = 1;
  198. commentText.value = '';
  199. isInShelf.value = false;
  200. rewardDialogVisible.value = false;
  201. };
  202. // 在组件销毁前清理
  203. onBeforeUnmount(() => {
  204. cleanup();
  205. });
  206. // 修改路由导航方式
  207. const goToChapter = (chapterId) => {
  208. cleanup();
  209. router.push({
  210. name: 'ChapterDetail',
  211. params: {
  212. id: bookId,
  213. chapterId: chapterId
  214. }
  215. });
  216. };
  217. const submitComment = () => {
  218. if (!commentText.value.trim()) {
  219. ElMessage.warning('请输入评论内容');
  220. return;
  221. }
  222. // 实际应用中这里应该调用API提交评论
  223. book.comments.unshift({
  224. username: '当前用户',
  225. avatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
  226. time: new Date().toLocaleString(),
  227. content: commentText.value
  228. });
  229. commentText.value = '';
  230. };
  231. const toggleShelf = () => {
  232. isInShelf.value = !isInShelf.value;
  233. };
  234. const showRewardDialog = () => {
  235. rewardDialogVisible.value = true;
  236. };
  237. const handleRewardSuccess = (items) => {
  238. console.log('打赏成功,打赏项目:', items);
  239. // 这里可以添加刷新亲密值或其他相关数据的逻辑
  240. };
  241. onMounted(() => {
  242. // 实际应用中这里应该根据bookId从API获取书籍详情
  243. console.log('加载书籍详情,ID:', bookId);
  244. });
  245. return {
  246. book,
  247. recommendedBooks,
  248. activeTab,
  249. currentPage,
  250. commentText,
  251. handlePageChange,
  252. goToChapter,
  253. submitComment,
  254. isInShelf,
  255. toggleShelf,
  256. rewardDialogVisible,
  257. showRewardDialog,
  258. handleRewardSuccess,
  259. cleanup
  260. };
  261. }
  262. };
  263. </script>
  264. <style lang="scss" scoped>
  265. @use '@/assets/styles/variables.scss' as vars;
  266. .book-detail-container {
  267. width: 100%;
  268. background-color: #f5f5f5;
  269. }
  270. // 小说基本信息部分
  271. .book-info-wrapper {
  272. background-color: #fff;
  273. padding: 30px 0;
  274. border-bottom: 1px solid #eee;
  275. .book-info {
  276. display: flex;
  277. max-width: 1200px;
  278. margin: 0 auto;
  279. padding: 0 20px;
  280. .book-cover {
  281. width: 165px;
  282. height: 220px;
  283. margin-right: 30px;
  284. box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  285. img {
  286. width: 100%;
  287. height: 100%;
  288. object-fit: cover;
  289. border-radius: 6px;
  290. }
  291. }
  292. .book-details {
  293. flex: 1;
  294. .book-title {
  295. font-size: 24px;
  296. font-weight: bold;
  297. margin: 0 0 15px 0;
  298. color: #333;
  299. }
  300. .book-meta {
  301. margin-bottom: 8px;
  302. .book-status-row {
  303. display: flex;
  304. align-items: center;
  305. font-size: 15px;
  306. margin-top: 10px;
  307. .status-badge {
  308. background: #eaffea;
  309. color: #52c41a;
  310. border-radius: 6px;
  311. padding: 2px 12px;
  312. font-size: 15px;
  313. font-weight: 500;
  314. margin-right: 8px;
  315. }
  316. .dot {
  317. color: #d1d1d1;
  318. margin: 0 8px;
  319. font-size: 18px;
  320. }
  321. .reading-tip {
  322. color: #bdbdbd;
  323. font-size: 15px;
  324. }
  325. }
  326. }
  327. .book-user-info {
  328. display: flex;
  329. align-items: center;
  330. margin-bottom: 18px;
  331. .user-badges {
  332. display: flex;
  333. align-items: center;
  334. margin-right: 18px;
  335. .level-icon {
  336. width: 36px;
  337. height: 36px;
  338. }
  339. .badge-label {
  340. color: #b88a4a;
  341. font-size: 14px;
  342. margin-left: 4px;
  343. }
  344. }
  345. .user-medal {
  346. margin-right: 18px;
  347. .medal-icon {
  348. width: 44px;
  349. height: 44px;
  350. }
  351. }
  352. .user-avatar-block {
  353. display: flex;
  354. align-items: center;
  355. margin-right: 18px;
  356. .user-avatar {
  357. width: 36px;
  358. height: 36px;
  359. border-radius: 50%;
  360. margin-right: 8px;
  361. }
  362. .user-name {
  363. color: #222;
  364. font-size: 16px;
  365. font-weight: 500;
  366. margin-right: 4px;
  367. }
  368. .dot {
  369. color: #d1d1d1;
  370. margin: 0 8px;
  371. font-size: 18px;
  372. }
  373. .user-title {
  374. background: #ede6ff;
  375. color: #a97cff;
  376. border-radius: 8px;
  377. padding: 2px 10px;
  378. font-size: 15px;
  379. margin-left: 2px;
  380. }
  381. }
  382. .user-intimacy {
  383. color: #bdbdbd;
  384. font-size: 15px;
  385. }
  386. }
  387. .action-buttons {
  388. display: flex;
  389. gap: 24px;
  390. margin-top: 10px;
  391. .action-btn-group {
  392. display: flex;
  393. flex-direction: column;
  394. align-items: center;
  395. .el-button {
  396. width: 140px;
  397. height: 40px;
  398. font-size: 16px;
  399. border-radius: 4px;
  400. margin: 0;
  401. font-weight: 500;
  402. }
  403. .diamond-icon {
  404. width: 28px;
  405. height: 18px;
  406. margin-top: 4px;
  407. }
  408. .reward-btn {
  409. color: #0A2463;
  410. border: 1.5px solid #0A2463;
  411. background: #fff;
  412. }
  413. .add-to-shelf-btn {
  414. background: #FF7C6A;
  415. color: #fff;
  416. border: none;
  417. }
  418. .add-to-shelf-btn.in-shelf {
  419. background: #fff;
  420. color: #FF7C6A;
  421. border: 1.5px solid #FF7C6A;
  422. }
  423. .read-btn {
  424. background: #0A2463;
  425. color: #fff;
  426. border: none;
  427. }
  428. }
  429. }
  430. }
  431. }
  432. }
  433. .book-main-content {
  434. display: flex;
  435. gap: 3px;
  436. margin-top: 20px;
  437. .main-left {
  438. flex: 1.8;
  439. min-width: 0;
  440. display: flex;
  441. flex-direction: column;
  442. gap: 3px;
  443. }
  444. .main-right {
  445. flex: 1;
  446. min-width: 220px;
  447. max-width: 280px;
  448. }
  449. }
  450. </style>