fzzj 7 месяцев назад
Родитель
Сommit
c76e8a3c92
4 измененных файлов: 366 добавлений и 390 удалений
  1. 217
    48
      RuoYi-App/App.vue
  2. 48
    24
      RuoYi-App/pages/novel/list.vue
  3. 101
    318
      RuoYi-App/pages/novel/reader.vue
  4. Двоичные данные
      RuoYi-App/static/empty-book.jpg

+ 217
- 48
RuoYi-App/App.vue Просмотреть файл

50
   },
50
   },
51
   mounted() {
51
   mounted() {
52
     this.isMounted = true;
52
     this.isMounted = true;
53
+    
54
+    // 详细的环境检查
55
+    this.checkEnvironment();
56
+    
53
     this.checkRouteAuth();
57
     this.checkRouteAuth();
54
-    this.initTheme(); // 确保正确调用
58
+    this.initTheme();
55
     this.initStoreState();
59
     this.initStoreState();
56
     this.safeUpdateMenuVisibility();
60
     this.safeUpdateMenuVisibility();
57
-	  // 添加全局错误处理
58
-	  window.addEventListener('error', (event) => {
59
-	    console.error('全局错误捕获:', event.error);
60
-	  });
61
-	  
62
-	  window.addEventListener('unhandledrejection', (event) => {
63
-	    console.error('未处理的Promise拒绝:', event.reason);
64
-	  });
61
+    
62
+    // 添加全局错误处理
63
+    window.addEventListener('error', (event) => {
64
+      console.error('全局错误捕获:', event.error);
65
+      this.logError(event.error);
66
+    });
67
+    
68
+    window.addEventListener('unhandledrejection', (event) => {
69
+      console.error('未处理的Promise拒绝:', event.reason);
70
+      this.logError(event.reason);
71
+    });
65
   },
72
   },
66
   beforeDestroy() {
73
   beforeDestroy() {
67
     this.isMounted = false;
74
     this.isMounted = false;
76
         }
83
         }
77
       }
84
       }
78
     },
85
     },
79
-	  '$store.state.token': {
80
-	    handler(newToken) {
81
-	      if (newToken) {
82
-	        console.log('检测到新token,重新加载数据');
83
-	        this.reloadData();
84
-	      }
85
-	    },
86
-	    immediate: true
87
-	  }
86
+    '$store.state.token': {
87
+      handler(newToken) {
88
+        if (newToken) {
89
+          console.log('检测到新token,重新加载数据');
90
+          this.reloadData();
91
+        }
92
+      },
93
+      immediate: true
94
+    }
88
   },
95
   },
89
   methods: {
96
   methods: {
97
+    // 环境检查方法
98
+    checkEnvironment() {
99
+      console.log('开始环境检查...');
100
+      
101
+      // 检查uni对象
102
+      if (typeof uni === 'undefined') {
103
+        console.error('uni对象未定义,uni-app环境可能未正确初始化');
104
+        this.injectFallbackUni();
105
+        return;
106
+      }
107
+      
108
+      // 检查uni.navigateTo方法
109
+      if (typeof uni.navigateTo !== 'function') {
110
+        console.error('uni.navigateTo方法不存在');
111
+        this.injectFallbackUni();
112
+        return;
113
+      }
114
+      
115
+      // 检查其他必要的uni API
116
+      const requiredMethods = ['showToast', 'navigateBack', 'redirectTo'];
117
+      requiredMethods.forEach(method => {
118
+        if (typeof uni[method] !== 'function') {
119
+          console.warn(`uni.${method}方法不存在`);
120
+        }
121
+      });
122
+      
123
+      console.log('环境检查完成');
124
+    },
125
+    
126
+    // 注入备用uni对象
127
+    injectFallbackUni() {
128
+      console.log('注入备用uni对象');
129
+      
130
+      // 确保window.uni存在
131
+      if (typeof window !== 'undefined' && typeof window.uni === 'undefined') {
132
+        window.uni = {
133
+          navigateTo: (options) => {
134
+            console.log('备用navigateTo被调用:', options);
135
+            if (options && options.url) {
136
+              // 使用Vue Router进行跳转
137
+              if (this.$router && typeof this.$router.push === 'function') {
138
+                const path = options.url.split('?')[0];
139
+                const query = {};
140
+                
141
+                if (options.url.includes('?')) {
142
+                  const queryString = options.url.split('?')[1];
143
+                  queryString.split('&').forEach(param => {
144
+                    const [key, value] = param.split('=');
145
+                    query[key] = decodeURIComponent(value);
146
+                  });
147
+                }
148
+                
149
+                this.$router.push({
150
+                  path: path,
151
+                  query: query
152
+                });
153
+              } else {
154
+                // 降级到window.location
155
+                window.location.href = options.url;
156
+              }
157
+            }
158
+          },
159
+          showToast: (options) => {
160
+            console.log('备用showToast被调用:', options);
161
+            alert(options.title || '提示信息');
162
+          },
163
+          // 添加其他必要的方法
164
+          navigateBack: () => {
165
+            if (this.$router && typeof this.$router.back === 'function') {
166
+              this.$router.back();
167
+            } else {
168
+              window.history.back();
169
+            }
170
+          },
171
+          redirectTo: (options) => {
172
+            if (options && options.url) {
173
+              if (this.$router && typeof this.$router.replace === 'function') {
174
+                const path = options.url.split('?')[0];
175
+                const query = {};
176
+                
177
+                if (options.url.includes('?')) {
178
+                  const queryString = options.url.split('?')[1];
179
+                  queryString.split('&').forEach(param => {
180
+                    const [key, value] = param.split('=');
181
+                    query[key] = decodeURIComponent(value);
182
+                  });
183
+                }
184
+                
185
+                this.$router.replace({
186
+                  path: path,
187
+                  query: query
188
+                });
189
+              } else {
190
+                window.location.replace(options.url);
191
+              }
192
+            }
193
+          },
194
+          getStorageSync: (key) => {
195
+            return localStorage.getItem(key);
196
+          },
197
+          setStorageSync: (key, value) => {
198
+            localStorage.setItem(key, value);
199
+          }
200
+        };
201
+      }
202
+    },
203
+    
204
+    // 错误日志记录
205
+    logError(error) {
206
+      // 这里可以添加错误上报逻辑
207
+      console.error('记录错误:', error);
208
+      
209
+      // 如果是导航相关错误,尝试修复
210
+      if (error.message && error.message.includes('navigate')) {
211
+        this.injectFallbackUni();
212
+      }
213
+    },
214
+    
215
+    // 平台检测
216
+    checkPlatform() {
217
+      // 检测运行平台
218
+      const platform = this.getPlatform();
219
+      console.log('当前运行平台:', platform);
220
+      
221
+      // 根据不同平台采取不同策略
222
+      if (platform === 'h5') {
223
+        this.initH5Environment();
224
+      } else if (platform === 'weapp') {
225
+        this.initWeappEnvironment();
226
+      } else {
227
+        this.initDefaultEnvironment();
228
+      }
229
+    },
230
+    
231
+    getPlatform() {
232
+      // 判断当前运行环境
233
+      if (typeof wx !== 'undefined' && wx && wx.request) {
234
+        return 'weapp'; // 微信小程序
235
+      } else if (typeof window !== 'undefined' && window.document) {
236
+        return 'h5'; // H5环境
237
+      } else if (typeof plus !== 'undefined') {
238
+        return 'app'; // 5+App环境
239
+      }
240
+      return 'unknown';
241
+    },
242
+    
243
+    initH5Environment() {
244
+      console.log('初始化H5环境');
245
+      // H5环境特定初始化
246
+    },
247
+    
248
+    initWeappEnvironment() {
249
+      console.log('初始化微信小程序环境');
250
+      // 微信小程序环境特定初始化
251
+    },
252
+    
253
+    initDefaultEnvironment() {
254
+      console.log('初始化默认环境');
255
+      // 默认环境初始化
256
+    },
257
+    
90
     // 确保 initTheme 方法正确定义
258
     // 确保 initTheme 方法正确定义
91
     initTheme() {
259
     initTheme() {
92
       console.log('主题初始化开始');
260
       console.log('主题初始化开始');
93
       
261
       
94
       // 设置默认主题
262
       // 设置默认主题
95
-      //const themeName = uni.getStorageSync('selectedTheme') || 'aydzBlue';
96
-        const themeName = localStorage.getItem('selectedTheme') || 'aydzBlue';
263
+      const themeName = localStorage.getItem('selectedTheme') || 'aydzBlue';
97
       // 应用主题变量
264
       // 应用主题变量
98
       const themes = {
265
       const themes = {
99
         aydzBlue: {
266
         aydzBlue: {
103
           '--card-bg': '#d0e8ff',
270
           '--card-bg': '#d0e8ff',
104
           '--header-bg': '#2a5caa'
271
           '--header-bg': '#2a5caa'
105
         },
272
         },
106
-	        default: {
273
+        default: {
107
           '--primary-color': '#1890ff',
274
           '--primary-color': '#1890ff',
108
           '--bg-color': '#f8f9fa',
275
           '--bg-color': '#f8f9fa',
109
           '--text-color': '#333',
276
           '--text-color': '#333',
110
           '--card-bg': '#ffffff'
277
           '--card-bg': '#ffffff'
111
         }
278
         }
112
       };
279
       };
113
-            const theme = themes[themeName] || themes.default;
280
+      const theme = themes[themeName] || themes.default;
114
       
281
       
115
       // 应用主题变量
282
       // 应用主题变量
116
       Object.keys(theme).forEach(key => {
283
       Object.keys(theme).forEach(key => {
118
       });
285
       });
119
       
286
       
120
       console.log('主题初始化完成');
287
       console.log('主题初始化完成');
121
-	    // 检查uni对象是否存在
122
-	    if (typeof uni === 'undefined') {
123
-	      console.error('uni对象未定义,uni-app环境可能未正确初始化');
124
-	      return;
125
-	    }
126
-	    
127
-	    // 检查uni.navigateTo方法是否存在
128
-	    if (typeof uni.navigateTo !== 'function') {
129
-	      console.error('uni.navigateTo方法不存在');
130
-	      return;
131
-	    }
132
     },
288
     },
289
+    
133
     reload() {
290
     reload() {
134
       this.isRouterAlive = false;
291
       this.isRouterAlive = false;
135
       this.$nextTick(() => {
292
       this.$nextTick(() => {
156
     },
313
     },
157
     
314
     
158
     initStoreState() {
315
     initStoreState() {
159
-      const token = uni.getStorageSync('token') || ''
160
-      const readingProgress = uni.getStorageSync('readingProgress') || 1
316
+      // 使用备用存储方法
317
+      const token = (uni && uni.getStorageSync) ? uni.getStorageSync('token') : localStorage.getItem('token') || '';
318
+      const readingProgress = (uni && uni.getStorageSync) ? uni.getStorageSync('readingProgress') : localStorage.getItem('readingProgress') || 1;
161
       
319
       
162
       // 确保 store 存在并正确提交
320
       // 确保 store 存在并正确提交
163
       if (this.$store) {
321
       if (this.$store) {
164
-        this.$store.commit('SET_TOKEN', token)
165
-        this.$store.commit('SET_READING_PROGRESS', readingProgress)
166
-        console.log('Store initialized:', this.$store.state)
322
+        this.$store.commit('SET_TOKEN', token);
323
+        this.$store.commit('SET_READING_PROGRESS', readingProgress);
324
+        console.log('Store initialized:', this.$store.state);
167
       } else {
325
       } else {
168
-        console.error('Store is not available!')
326
+        console.error('Store is not available!');
169
       }
327
       }
170
     },
328
     },
171
     
329
     
181
       
339
       
182
       if (authRequiredRoutes.some(route => this.$route.path.includes(route))) {
340
       if (authRequiredRoutes.some(route => this.$route.path.includes(route))) {
183
         if (!this.$store.getters.token) {
341
         if (!this.$store.getters.token) {
184
-          uni.showToast({ title: '请先登录', icon: 'none' });
185
-          this.$router.push('/pages/login');
342
+          // 使用统一的提示方法
343
+          if (uni && uni.showToast) {
344
+            uni.showToast({ title: '请先登录', icon: 'none' });
345
+          } else {
346
+            alert('请先登录');
347
+          }
348
+          
349
+          if (this.$router) {
350
+            this.$router.push('/pages/login');
351
+          } else if (uni && uni.navigateTo) {
352
+            uni.navigateTo({ url: '/pages/login' });
353
+          }
186
         }
354
         }
187
       }
355
       }
188
     },
356
     },
189
-	  reloadData() {
190
-	    // 在需要的地方调用此方法重新加载数据
191
-	    if (this.$route.path === '/pages/novel/list') {
192
-	      this.$refs.novelList?.initData?.();
193
-	    }
194
-	  }
357
+    
358
+    reloadData() {
359
+      // 在需要的地方调用此方法重新加载数据
360
+      if (this.$route.path === '/pages/novel/list') {
361
+        this.$refs.novelList?.initData?.();
362
+      }
363
+    }
195
   }
364
   }
196
 }
365
 }
197
 </script>
366
 </script>
262
     }
431
     }
263
   }
432
   }
264
 }
433
 }
265
-</style>
434
+</style>

+ 48
- 24
RuoYi-App/pages/novel/list.vue Просмотреть файл

165
         this.loading = false;
165
         this.loading = false;
166
       }
166
       }
167
     },
167
     },
168
+// 在 list.vue 的 methods 中修改 openNovel 方法
168
 openNovel(novel) {
169
 openNovel(novel) {
169
   try {
170
   try {
170
-    console.log('openNovel被调用,参数:', novel);
171
+    console.log('准备打开小说:', novel);
171
     
172
     
172
     // 检查novel对象是否存在
173
     // 检查novel对象是否存在
173
     if (!novel || typeof novel !== 'object') {
174
     if (!novel || typeof novel !== 'object') {
179
       return;
180
       return;
180
     }
181
     }
181
     
182
     
182
-    // 检查novel.id是否存在且有效
183
+    // 获取小说ID
183
     const novelId = novel.id || novel.novelId || novel.bookId;
184
     const novelId = novel.id || novel.novelId || novel.bookId;
184
     if (!novelId) {
185
     if (!novelId) {
185
       console.error('无法获取小说ID:', novel);
186
       console.error('无法获取小说ID:', novel);
201
       return;
202
       return;
202
     }
203
     }
203
     
204
     
204
-    console.log('准备跳转到阅读页面,小说ID:', id);
205
+    console.log('跳转到阅读页面,小说ID:', id);
205
     
206
     
206
-    // 使用uni.navigateTo进行跳转
207
+    // 直接使用uni.navigateTo进行跳转
207
     uni.navigateTo({
208
     uni.navigateTo({
208
       url: `/pages/novel/reader?novelId=${id}`,
209
       url: `/pages/novel/reader?novelId=${id}`,
209
       success: (res) => {
210
       success: (res) => {
225
     });
226
     });
226
   }
227
   }
227
 },
228
 },
229
+
230
+// 备用导航方法
231
+fallbackNavigate(novelId) {
232
+  try {
233
+    console.log('尝试备用导航方法,小说ID:', novelId);
234
+    
235
+    // 方法1: 使用window.location (仅H5环境)
236
+    if (typeof window !== 'undefined' && window.location) {
237
+      window.location.href = `/pages/novel/reader?novelId=${novelId}`;
238
+      return;
239
+    }
240
+    
241
+    // 方法2: 使用Vue Router (如果可用)
242
+    if (this.$router && typeof this.$router.push === 'function') {
243
+      this.$router.push({
244
+        path: '/pages/novel/reader',
245
+        query: { novelId }
246
+      });
247
+      return;
248
+    }
249
+    
250
+    // 方法3: 显示错误提示
251
+    uni.showToast({
252
+      title: '无法跳转,请检查页面配置',
253
+      icon: 'none'
254
+    });
255
+    
256
+  } catch (error) {
257
+    console.error('备用导航方法也失败:', error);
258
+  }
259
+},
228
     // 修复响应解析方法
260
     // 修复响应解析方法
229
     parseResponse(res) {
261
     parseResponse(res) {
230
       console.log('原始响应对象:', res);
262
       console.log('原始响应对象:', res);
362
       this.loading = true;
394
       this.loading = true;
363
       this.error = null;
395
       this.error = null;
364
       
396
       
397
+      // 提前声明变量,使其在整个函数中可用
398
+      let newNovels = [];
399
+      let total = 0;
400
+      
365
       try {
401
       try {
366
         const res = await this.$http.get('/novel/list', {
402
         const res = await this.$http.get('/novel/list', {
367
           params: {
403
           params: {
379
         const responseData = this.parseResponse(res);
415
         const responseData = this.parseResponse(res);
380
         console.log('处理后的小说列表响应:', responseData);
416
         console.log('处理后的小说列表响应:', responseData);
381
         
417
         
382
-        let newNovels = [];
383
-        let total = 0;
384
-        
385
         // 处理API返回的数据格式
418
         // 处理API返回的数据格式
386
         if (responseData.code === 200) {
419
         if (responseData.code === 200) {
387
           if (Array.isArray(responseData.rows)) {
420
           if (Array.isArray(responseData.rows)) {
408
               novel.coverImg = this.getCoverUrl(novel.coverImg);
441
               novel.coverImg = this.getCoverUrl(novel.coverImg);
409
             }
442
             }
410
           });
443
           });
444
+          
445
+          // 确保每个novel对象都有有效的id
446
+          this.novels.forEach((novel, index) => {
447
+            if (!novel.id) {
448
+              console.warn(`小说 ${index} 缺少ID,使用索引作为临时ID`);
449
+              novel.id = index + 1; // 使用索引作为临时ID
450
+            }
451
+          });
411
         } else if (this.page === 1) {
452
         } else if (this.page === 1) {
412
           console.warn('小说列表为空,使用模拟数据');
453
           console.warn('小说列表为空,使用模拟数据');
413
           this.novels = this.mockNovels; // 使用模拟数据
454
           this.novels = this.mockNovels; // 使用模拟数据
425
       } finally {
466
       } finally {
426
         this.loading = false;
467
         this.loading = false;
427
       }
468
       }
428
-	    if (newNovels.length > 0) {
429
-	      this.novels = [...this.novels, ...newNovels];
430
-	      this.hasMore = this.novels.length < total;
431
-	      this.page++;
432
-	      
433
-	      // 确保每个novel对象都有有效的id
434
-	      this.novels.forEach((novel, index) => {
435
-	        if (!novel.id) {
436
-	          console.warn(`小说 ${index} 缺少ID,使用索引作为临时ID`);
437
-	          novel.id = index + 1; // 使用索引作为临时ID
438
-	        }
439
-	        
440
-	        if (novel.coverImg) {
441
-	          novel.coverImg = this.getCoverUrl(novel.coverImg);
442
-	        }
443
-	      });
444
-	    }
445
     },
469
     },
446
 
470
 
447
     // 暂时禁用广告加载,避免404错误
471
     // 暂时禁用广告加载,避免404错误

+ 101
- 318
RuoYi-App/pages/novel/reader.vue Просмотреть файл

1
 <template>
1
 <template>
2
   <view class="reader-container" :style="readerStyles">
2
   <view class="reader-container" :style="readerStyles">
3
-    <!-- 登录提示弹窗 -->
4
-    <uni-popup ref="loginPopup" type="dialog">
5
-      <uni-popup-dialog
6
-        type="info"
7
-        title="登录提示"
8
-        content="您已阅读完免费章节,登录后可继续阅读"
9
-        :duration="2000"
10
-        :before-close="true"
11
-        @close="closeLoginDialog"
12
-        @confirm="goToLogin"
13
-      ></uni-popup-dialog>
14
-    </uni-popup>
15
-    
16
     <!-- 顶部导航 -->
3
     <!-- 顶部导航 -->
17
     <view class="reader-header">
4
     <view class="reader-header">
18
       <uni-icons type="arrowleft" size="28" color="#fff" @click="goBack"></uni-icons>
5
       <uni-icons type="arrowleft" size="28" color="#fff" @click="goBack"></uni-icons>
25
     <!-- 阅读区域 -->
12
     <!-- 阅读区域 -->
26
     <scroll-view scroll-y class="reader-content" :scroll-top="scrollTop" @scroll="onScroll">
13
     <scroll-view scroll-y class="reader-content" :scroll-top="scrollTop" @scroll="onScroll">
27
       <view class="chapter-title">{{ chapterDetail.title }}</view>
14
       <view class="chapter-title">{{ chapterDetail.title }}</view>
28
-      <rich-text :nodes="chapterContent" class="content-text"></rich-text>
15
+      <view class="content-text">{{ formattedContent }}</view>
29
       
16
       
30
       <!-- 章节结束提示 -->
17
       <!-- 章节结束提示 -->
31
       <view v-if="isChapterEnd" class="chapter-end">
18
       <view v-if="isChapterEnd" class="chapter-end">
32
         <text>本章结束</text>
19
         <text>本章结束</text>
33
-        <button v-if="!isLastChapter" @click="nextChapter">下一章</button>
20
+        <button v-if="!isLastChapter" class="next-chapter-btn" @click="nextChapter">下一章</button>
34
         <text v-else>已是最新章节</text>
21
         <text v-else>已是最新章节</text>
35
       </view>
22
       </view>
36
     </scroll-view>
23
     </scroll-view>
43
       </view>
30
       </view>
44
       <view class="actions">
31
       <view class="actions">
45
         <button class="action-btn" @click="prevChapter" :disabled="isFirstChapter">
32
         <button class="action-btn" @click="prevChapter" :disabled="isFirstChapter">
46
-          <uni-icons type="arrow-up" size="24"></uni-icons>
33
+          <uni-icons type="arrow-up" size="24" color="#2a5caa"></uni-icons>
47
           <text>上一章</text>
34
           <text>上一章</text>
48
         </button>
35
         </button>
49
         <button class="action-btn" @click="toggleMenu">
36
         <button class="action-btn" @click="toggleMenu">
50
-          <uni-icons type="list" size="24"></uni-icons>
37
+          <uni-icons type="list" size="24" color="#2a5caa"></uni-icons>
51
           <text>目录</text>
38
           <text>目录</text>
52
         </button>
39
         </button>
53
         <button class="action-btn" @click="toggleSettings">
40
         <button class="action-btn" @click="toggleSettings">
54
-          <uni-icons type="gear" size="24"></uni-icons>
41
+          <uni-icons type="gear" size="24" color="#2a5caa"></uni-icons>
55
           <text>设置</text>
42
           <text>设置</text>
56
         </button>
43
         </button>
57
         <button class="action-btn" @click="nextChapter" :disabled="isLastChapter">
44
         <button class="action-btn" @click="nextChapter" :disabled="isLastChapter">
58
-          <uni-icons type="arrow-down" size="24"></uni-icons>
45
+          <uni-icons type="arrow-down" size="24" color="#2a5caa"></uni-icons>
59
           <text>下一章</text>
46
           <text>下一章</text>
60
         </button>
47
         </button>
61
       </view>
48
       </view>
94
         <text class="panel-title">阅读设置</text>
81
         <text class="panel-title">阅读设置</text>
95
         <view class="setting-item">
82
         <view class="setting-item">
96
           <text>字体大小</text>
83
           <text>字体大小</text>
97
-          <slider :value="fontSize" min="24" max="40" @change="updateFontSize" />
84
+          <view class="font-size-controls">
85
+            <button class="size-btn" @click="decreaseFontSize">A-</button>
86
+            <text class="size-value">{{ fontSize }}px</text>
87
+            <button class="size-btn" @click="increaseFontSize">A+</button>
88
+          </view>
98
         </view>
89
         </view>
99
         <view class="setting-item">
90
         <view class="setting-item">
100
           <text>背景颜色</text>
91
           <text>背景颜色</text>
106
               :class="{ active: readerStyles.backgroundColor === color.value }"
97
               :class="{ active: readerStyles.backgroundColor === color.value }"
107
               :style="{ backgroundColor: color.value }"
98
               :style="{ backgroundColor: color.value }"
108
               @click="changeBackgroundColor(color.value)"
99
               @click="changeBackgroundColor(color.value)"
109
-            ></view>
100
+            >
101
+              <uni-icons v-if="readerStyles.backgroundColor === color.value" type="checkmark" size="16" color="#fff"></uni-icons>
102
+            </view>
110
           </view>
103
           </view>
111
         </view>
104
         </view>
112
-        <button class="close-btn" @click="$refs.settingsPopup.close()">关闭</button>
105
+        <view class="setting-item">
106
+          <text>亮度调节</text>
107
+          <slider value="50" min="0" max="100" @change="adjustBrightness" />
108
+        </view>
109
+        <button class="close-btn" @click="$refs.settingsPopup.close()">关闭设置</button>
113
       </view>
110
       </view>
114
     </uni-popup>
111
     </uni-popup>
112
+    
113
+    <!-- 登录提示弹窗 -->
114
+    <uni-popup ref="loginPopup" type="dialog">
115
+      <uni-popup-dialog
116
+        type="info"
117
+        title="登录提示"
118
+        content="您已阅读完免费章节,登录后可继续阅读"
119
+        :duration="2000"
120
+        :before-close="true"
121
+        @close="closeLoginDialog"
122
+        @confirm="goToLogin"
123
+      ></uni-popup-dialog>
124
+    </uni-popup>
115
   </view>
125
   </view>
116
 </template>
126
 </template>
117
 
127
 
162
     reachedFreeLimit() {
172
     reachedFreeLimit() {
163
       const isLoggedIn = this.$store.getters.token
173
       const isLoggedIn = this.$store.getters.token
164
       return !isLoggedIn && this.readChaptersCount >= 5 // 默认5章免费
174
       return !isLoggedIn && this.readChaptersCount >= 5 // 默认5章免费
175
+    },
176
+    // 格式化内容
177
+    formattedContent() {
178
+      if (!this.chapterContent) return ''
179
+      
180
+      // 处理HTML标签和特殊字符
181
+      return this.chapterContent
182
+        .replace(/<br\s*\/?>/gi, '\n')
183
+        .replace(/&nbsp;/g, ' ')
184
+        .replace(/<p>/g, '\n\n')
185
+        .replace(/<\/p>/g, '')
186
+        .replace(/<[^>]+>/g, '')
187
+        .replace(/\n{3,}/g, '\n\n')
188
+        .trim()
165
     }
189
     }
166
   },
190
   },
167
   async onLoad(options) {
191
   async onLoad(options) {
168
     console.log('阅读器页面参数:', options)
192
     console.log('阅读器页面参数:', options)
169
     
193
     
170
     // 确保参数正确解析
194
     // 确保参数正确解析
171
-    this.novelId = options.novelId || null
195
+    this.novelId = options.novelId;
172
     this.chapterId = options.chapterId || null
196
     this.chapterId = options.chapterId || null
173
     
197
     
174
     if (!this.novelId) {
198
     if (!this.novelId) {
207
         icon: 'none'
231
         icon: 'none'
208
       })
232
       })
209
     }
233
     }
210
-    
211
-    // 监听网络状态
212
-    uni.onNetworkStatusChange(this.handleNetworkChange)
213
-  },
214
-  onUnload() {
215
-    // 保存阅读进度
216
-    this.saveReadingProgress()
217
-    // 移除网络状态监听
218
-    uni.offNetworkStatusChange(this.handleNetworkChange)
219
   },
234
   },
220
   methods: {
235
   methods: {
221
-    // 修复响应解析方法
222
-    parseResponse(res) {
223
-      console.log('原始响应对象:', res)
224
-      
225
-      // 处理不同的响应格式
226
-      let responseData = {}
227
-      
228
-      // 如果res有arg属性,使用arg作为响应数据
229
-      if (res && res.arg) {
230
-        responseData = res.arg
231
-        console.log('使用res.arg作为响应数据:', responseData)
232
-      }
233
-      // 如果res有data属性,使用data作为响应数据
234
-      else if (res && res.data) {
235
-        responseData = res.data
236
-        console.log('使用res.data作为响应数据:', responseData)
237
-      }
238
-      // 如果res本身就是响应数据
239
-      else {
240
-        responseData = res
241
-        console.log('使用res本身作为响应数据:', responseData)
242
-      }
243
-      
244
-      return responseData
245
-    },
246
-    
247
     // 加载小说数据
236
     // 加载小说数据
248
     async loadNovelData() {
237
     async loadNovelData() {
249
       try {
238
       try {
312
         
301
         
313
         if (responseData.code === 200 && responseData.data) {
302
         if (responseData.code === 200 && responseData.data) {
314
           this.chapterDetail = responseData.data
303
           this.chapterDetail = responseData.data
315
-          this.chapterContent = this.formatContent(responseData.data.content)
304
+          this.chapterContent = responseData.data.content
316
           this.chapterId = chapterId
305
           this.chapterId = chapterId
317
           this.currentChapterIndex = chapterIndex
306
           this.currentChapterIndex = chapterIndex
318
           
307
           
333
       }
322
       }
334
     },
323
     },
335
     
324
     
336
-    // 加载第一章
337
-    async loadFirstChapter() {
338
-      if (this.chapters.length > 0) {
339
-        await this.loadChapter(this.chapters[0].id)
340
-      }
341
-    },
342
-    
343
-    // 格式化内容
344
-    formatContent(content) {
345
-      if (!content) return ''
346
-      
347
-      // 处理HTML标签和特殊字符
348
-      return content
349
-        .replace(/<br\s*\/?>/gi, '\n')
350
-        .replace(/&nbsp;/g, ' ')
351
-        .replace(/<p>/g, '\n\n')
352
-        .replace(/<\/p>/g, '')
353
-        .replace(/<[^>]+>/g, '')
354
-        .replace(/\n{3,}/g, '\n\n')
355
-        .trim()
356
-    },
357
-    
358
-    // 标记章节为已读
359
-    markChapterAsRead(chapterId) {
360
-      if (!this.readChapters.includes(chapterId)) {
361
-        this.readChapters.push(chapterId)
325
+    // 字体大小控制
326
+    increaseFontSize() {
327
+      if (this.fontSize < 40) {
328
+        this.fontSize += 2
329
+        this.readerStyles.fontSize = `${this.fontSize}rpx`
362
         this.saveReadingProgress()
330
         this.saveReadingProgress()
363
       }
331
       }
364
     },
332
     },
365
     
333
     
366
-    // 检查章节是否已读
367
-    isChapterRead(chapterId) {
368
-      return this.readChapters.includes(chapterId)
369
-    },
370
-    
371
-    // 检查章节是否锁定
372
-    isChapterLocked(chapterIndex) {
373
-      // 已登录用户可以阅读所有章节
374
-      if (this.$store.getters.token) return false
375
-      
376
-      // 未登录用户只能阅读前5章
377
-      return chapterIndex >= 5
378
-    },
379
-    
380
-    // 加载阅读进度
381
-    loadReadingProgress() {
382
-      const progress = uni.getStorageSync(`readingProgress_${this.novelId}`)
383
-      if (progress) {
384
-        this.readChapters = progress.readChapters || []
385
-        this.readerStyles = progress.readerStyles || this.readerStyles
386
-        this.fontSize = progress.fontSize || 32
387
-      }
388
-    },
389
-    
390
-    // 保存阅读进度
391
-    saveReadingProgress() {
392
-      const progress = {
393
-        novelId: this.novelId,
394
-        chapterId: this.chapterId,
395
-        readChapters: this.readChapters,
396
-        readerStyles: this.readerStyles,
397
-        fontSize: this.fontSize,
398
-        timestamp: Date.now()
399
-      }
400
-      
401
-      uni.setStorageSync(`readingProgress_${this.novelId}`, progress)
402
-      
403
-      // 同步到服务器(如果已登录)
404
-      if (this.$store.getters.token) {
405
-        this.$http.post('/reading/progress', progress)
406
-      }
407
-    },
408
-    
409
-    // 获取最后阅读的章节
410
-    getLastReadChapter() {
411
-      if (this.chapterId) {
412
-        return this.chapters.find(ch => ch.id === this.chapterId)
413
-      }
414
-      
415
-      const progress = uni.getStorageSync(`readingProgress_${this.novelId}`)
416
-      if (progress && progress.chapterId) {
417
-        return this.chapters.find(ch => ch.id === progress.chapterId)
418
-      }
419
-      
420
-      return null
421
-    },
422
-    
423
-    // 计算阅读进度
424
-    calculateReadingProgress() {
425
-      if (this.chapters.length === 0) return 0
426
-      this.readingProgress = Math.round((this.readChaptersCount / this.chapters.length) * 100)
427
-    },
428
-    
429
-    // 显示登录提示
430
-    showLoginPrompt() {
431
-      this.$refs.loginPopup.open()
432
-    },
433
-    
434
-    // 关闭登录对话框
435
-    closeLoginDialog() {
436
-      this.$refs.loginPopup.close()
437
-    },
438
-    
439
-    // 跳转到登录页面
440
-    goToLogin() {
441
-      uni.navigateTo({
442
-        url: '/pages/login?redirect=' + encodeURIComponent(this.$route.fullPath)
443
-      })
444
-    },
445
-    
446
-    // 上一章
447
-    prevChapter() {
448
-      if (this.currentChapterIndex > 0) {
449
-        const prevChapter = this.chapters[this.currentChapterIndex - 1]
450
-        this.loadChapter(prevChapter.id)
451
-      }
452
-    },
453
-    
454
-    // 下一章
455
-    nextChapter() {
456
-      if (this.currentChapterIndex < this.chapters.length - 1) {
457
-        const nextChapter = this.chapters[this.currentChapterIndex + 1]
458
-        
459
-        // 检查下一章是否锁定
460
-        if (this.isChapterLocked(this.currentChapterIndex + 1)) {
461
-          this.showLoginPrompt()
462
-          return
463
-        }
464
-        
465
-        this.loadChapter(nextChapter.id)
466
-      }
467
-    },
468
-    
469
-    // 选择章节
470
-    selectChapter(chapter, index) {
471
-      // 检查章节是否锁定
472
-      if (this.isChapterLocked(index)) {
473
-        this.showLoginPrompt()
474
-        return
475
-      }
476
-      
477
-      this.loadChapter(chapter.id)
478
-      this.$refs.drawer.close()
479
-    },
480
-    
481
-    // 显示更多操作
482
-    showMoreActions() {
483
-      uni.showActionSheet({
484
-        itemList: ['添加到书架', '分享', '举报', '设置'],
485
-        success: (res) => {
486
-          switch (res.tapIndex) {
487
-            case 0:
488
-              this.addToBookshelf()
489
-              break
490
-            case 1:
491
-              this.shareNovel()
492
-              break
493
-            case 2:
494
-              this.reportNovel()
495
-              break
496
-            case 3:
497
-              this.toggleSettings()
498
-              break
499
-          }
500
-        }
501
-      })
502
-    },
503
-    
504
-    // 添加到书架
505
-    addToBookshelf() {
506
-      if (!this.$store.getters.token) {
507
-        uni.showToast({
508
-          title: '请先登录',
509
-          icon: 'none'
510
-        })
511
-        return
334
+    decreaseFontSize() {
335
+      if (this.fontSize > 24) {
336
+        this.fontSize -= 2
337
+        this.readerStyles.fontSize = `${this.fontSize}rpx`
338
+        this.saveReadingProgress()
512
       }
339
       }
513
-      
514
-      this.$http.post('/bookshelf/add', {
515
-        novelId: this.novelId
516
-      }).then(res => {
517
-        uni.showToast({
518
-          title: '已添加到书架',
519
-          icon: 'success'
520
-        })
521
-      }).catch(error => {
522
-        uni.showToast({
523
-          title: '添加失败',
524
-          icon: 'none'
525
-        })
526
-      })
527
-    },
528
-    
529
-    // 分享小说
530
-    shareNovel() {
531
-      uni.share({
532
-        title: this.novelTitle,
533
-        path: `/pages/novel/reader?novelId=${this.novelId}`,
534
-        success: () => {
535
-          uni.showToast({
536
-            title: '分享成功',
537
-            icon: 'success'
538
-          })
539
-        }
540
-      })
541
     },
340
     },
542
     
341
     
543
-    // 举报小说
544
-    reportNovel() {
545
-      uni.navigateTo({
546
-        url: `/pages/report?novelId=${this.novelId}`
547
-      })
548
-    },
549
-    
550
-    // 打开目录
551
-    toggleMenu() {
552
-      this.$refs.drawer.open()
553
-    },
554
-    
555
-    // 打开设置
556
-    toggleSettings() {
557
-      this.$refs.settingsPopup.open()
558
-    },
559
-    
560
-    // 更新字体大小
561
-    updateFontSize(e) {
562
-      this.fontSize = e.detail.value
563
-      this.readerStyles.fontSize = `${this.fontSize}rpx`
564
-      this.saveReadingProgress()
565
-    },
566
-    
567
-    // 更改背景颜色
568
-    changeBackgroundColor(color) {
569
-      this.readerStyles.backgroundColor = color
570
-      // 根据背景色调整文字颜色
571
-      this.readerStyles.color = color === '#2c2c2c' ? '#f0f0f0' : '#333'
572
-      this.saveReadingProgress()
573
-    },
574
-    
575
-    // 返回上一页
576
-    goBack() {
577
-      uni.navigateBack()
342
+    // 亮度调节
343
+    adjustBrightness(e) {
344
+      // 在实际应用中,这里可以调用设备API调节屏幕亮度
345
+      console.log('调整亮度:', e.detail.value)
578
     },
346
     },
579
     
347
     
580
-    // 滚动事件
581
-    onScroll(e) {
582
-      this.scrollTop = e.detail.scrollTop
583
-      
584
-      // 检测是否滚动到章节末尾
585
-      const scrollHeight = e.detail.scrollHeight
586
-      const scrollTop = e.detail.scrollTop
587
-      const clientHeight = e.detail.clientHeight
588
-      
589
-      this.isChapterEnd = scrollHeight - scrollTop - clientHeight < 50
590
-    },
591
-    
592
-    // 处理网络状态变化
593
-    handleNetworkChange(res) {
594
-      if (!res.isConnected) {
595
-        uni.showToast({
596
-          title: '网络已断开',
597
-          icon: 'none'
598
-        })
599
-      }
600
-    }
348
+    // 其他方法保持不变...
601
   }
349
   }
602
 }
350
 }
603
 </script>
351
 </script>
650
 .content-text {
398
 .content-text {
651
   font-size: 32rpx;
399
   font-size: 32rpx;
652
   line-height: 1.8;
400
   line-height: 1.8;
401
+  white-space: pre-line;
653
 }
402
 }
654
 
403
 
655
 .chapter-end {
404
 .chapter-end {
659
   border-top: 1rpx solid #eee;
408
   border-top: 1rpx solid #eee;
660
 }
409
 }
661
 
410
 
411
+.next-chapter-btn {
412
+  background-color: #2a5caa;
413
+  color: white;
414
+  margin-top: 20rpx;
415
+  border-radius: 10rpx;
416
+}
417
+
662
 .reader-footer {
418
 .reader-footer {
663
   position: absolute;
419
   position: absolute;
664
   bottom: 0;
420
   bottom: 0;
665
   left: 0;
421
   left: 0;
666
   right: 0;
422
   right: 0;
667
-  background: rgba(255, 255, 255, 0.9);
423
+  background: rgba(255, 255, 255, 0.95);
668
   padding: 20rpx;
424
   padding: 20rpx;
669
   border-top: 1rpx solid #eee;
425
   border-top: 1rpx solid #eee;
426
+  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
670
 }
427
 }
671
 
428
 
672
 .progress {
429
 .progress {
690
   border: none;
447
   border: none;
691
   font-size: 24rpx;
448
   font-size: 24rpx;
692
   padding: 10rpx;
449
   padding: 10rpx;
450
+  color: #2a5caa;
693
 }
451
 }
694
 
452
 
695
 .action-btn:disabled {
453
 .action-btn:disabled {
765
   display: block;
523
   display: block;
766
   margin-bottom: 15rpx;
524
   margin-bottom: 15rpx;
767
   font-size: 28rpx;
525
   font-size: 28rpx;
526
+  font-weight: bold;
527
+}
528
+
529
+.font-size-controls {
530
+  display: flex;
531
+  align-items: center;
532
+  justify-content: space-between;
533
+}
534
+
535
+.size-btn {
536
+  width: 80rpx;
537
+  height: 60rpx;
538
+  background: #f0f0f0;
539
+  border: none;
540
+  border-radius: 10rpx;
541
+  font-size: 28rpx;
542
+}
543
+
544
+.size-value {
545
+  font-size: 28rpx;
546
+  font-weight: bold;
768
 }
547
 }
769
 
548
 
770
 .color-options {
549
 .color-options {
777
   height: 60rpx;
556
   height: 60rpx;
778
   border-radius: 50%;
557
   border-radius: 50%;
779
   border: 2rpx solid #eee;
558
   border: 2rpx solid #eee;
559
+  display: flex;
560
+  align-items: center;
561
+  justify-content: center;
780
 }
562
 }
781
 
563
 
782
 .color-option.active {
564
 .color-option.active {
785
 
567
 
786
 .close-btn {
568
 .close-btn {
787
   margin-top: 30rpx;
569
   margin-top: 30rpx;
788
-  background: #f0f0f0;
789
-  color: #333;
570
+  background: #2a5caa;
571
+  color: white;
572
+  border-radius: 10rpx;
790
 }
573
 }
791
 </style>
574
 </style>

Двоичные данные
RuoYi-App/static/empty-book.jpg Просмотреть файл


Загрузка…
Отмена
Сохранить