├── php-api/ # 改造后的PHP接口层 ├── java-ad-service/ # 若依框架微服务(广告+VIP+分账) ├── uniapp-reader/ # UniApp前端项目 │ ├── pages/ # 各端页面 │ └──
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

read.vue 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <template>
  2. <view class="reading-container">
  3. <!-- 顶部章节信息 -->
  4. <view class="chapter-header">
  5. <text class="chapter-title">{{ chapter.title }}</text>
  6. </view>
  7. <!-- 小说内容 -->
  8. <scroll-view scroll-y class="content-container">
  9. <rich-text :nodes="chapter.content" class="content-text"></rich-text>
  10. </scroll-view>
  11. <!-- 底部操作栏 -->
  12. <view class="action-bar">
  13. <button @click="prevChapter">上一章</button>
  14. <button @click="showCatalog">目录</button>
  15. <button @click="nextChapter">下一章</button>
  16. </view>
  17. <!-- 章节间广告 -->
  18. <ad-banner v-if="showAd" :ads="midAds" />
  19. </view>
  20. </template>
  21. <script>
  22. import AdBanner from '@/components/AdBanner';
  23. export default {
  24. components: { AdBanner },
  25. data() {
  26. return {
  27. novelId: null,
  28. chapterId: null,
  29. chapter: {},
  30. chapters: [],
  31. showAd: false,
  32. midAds: []
  33. };
  34. },
  35. onLoad(options) {
  36. this.novelId = options.novelId;
  37. this.chapterId = options.chapterId;
  38. this.loadChapterData();
  39. this.scheduleAd();
  40. },
  41. methods: {
  42. async loadChapterData() {
  43. try {
  44. // 加载章节列表
  45. const chaptersRes = await this.$http.get(`/novel/${this.novelId}/chapters`);
  46. this.chapters = chaptersRes.data;
  47. // 加载当前章节内容
  48. await this.loadChapterContent(this.chapterId || this.chapters[0].id);
  49. } catch (e) {
  50. uni.showToast({ title: '加载失败', icon: 'none' });
  51. }
  52. },
  53. async loadChapterContent(chapterId) {
  54. const res = await this.$http.get(`/novel/chapter/${chapterId}`);
  55. this.chapter = res.data;
  56. this.chapterId = chapterId;
  57. // 保存阅读进度
  58. this.saveReadingProgress();
  59. },
  60. saveReadingProgress() {
  61. const progress = {
  62. novelId: this.novelId,
  63. chapterId: this.chapterId,
  64. timestamp: Date.now()
  65. };
  66. uni.setStorageSync('readingProgress', progress);
  67. // 同步到后台
  68. if (this.$store.getters.token) {
  69. this.$http.post('/user/reading-progress', progress);
  70. }
  71. },
  72. prevChapter() {
  73. const currentIndex = this.chapters.findIndex(c => c.id === this.chapterId);
  74. if (currentIndex > 0) {
  75. this.loadChapterContent(this.chapters[currentIndex - 1].id);
  76. }
  77. },
  78. nextChapter() {
  79. const currentIndex = this.chapters.findIndex(c => c.id === this.chapterId);
  80. if (currentIndex < this.chapters.length - 1) {
  81. this.loadChapterContent(this.chapters[currentIndex + 1].id);
  82. }
  83. },
  84. showCatalog() {
  85. uni.navigateTo({
  86. url: `/pages/novel/catalog?novelId=${this.novelId}`
  87. });
  88. },
  89. async scheduleAd() {
  90. // 30秒后显示广告
  91. setTimeout(async () => {
  92. const res = await this.$http.get('/ad/position?code=CHAPTER_MID');
  93. this.midAds = res.data;
  94. this.showAd = true;
  95. // 10秒后隐藏
  96. setTimeout(() => this.showAd = false, 10000);
  97. }, 30000);
  98. }
  99. }
  100. }
  101. </script>
  102. <style scoped>
  103. .reading-container {
  104. padding: 20rpx;
  105. padding-bottom: 120rpx; /* 为操作栏留空间 */
  106. }
  107. .chapter-header {
  108. padding: 20rpx 0;
  109. border-bottom: 1rpx solid #eee;
  110. }
  111. .chapter-title {
  112. font-size: 36rpx;
  113. font-weight: bold;
  114. }
  115. .content-container {
  116. height: calc(100vh - 300rpx);
  117. padding: 30rpx 0;
  118. }
  119. .content-text {
  120. font-size: 32rpx;
  121. line-height: 1.8;
  122. }
  123. .action-bar {
  124. position: fixed;
  125. bottom: 0;
  126. left: 0;
  127. right: 0;
  128. display: flex;
  129. background: white;
  130. padding: 20rpx;
  131. box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1);
  132. z-index: 999;
  133. }
  134. .action-bar button {
  135. flex: 1;
  136. margin: 0 10rpx;
  137. font-size: 28rpx;
  138. }
  139. </style>