fzzj il y a 7 mois
Parent
révision
86c5a077f5
3 fichiers modifiés avec 513 ajouts et 82 suppressions
  1. 3
    0
      RuoYi-App/config.js
  2. 12
    12
      RuoYi-App/pages/novel/list.vue
  3. 498
    70
      RuoYi-App/pages/novel/reader.vue

+ 3
- 0
RuoYi-App/config.js Voir le fichier

@@ -1,8 +1,11 @@
1 1
 // 应用全局配置
2 2
 module.exports = {
3
+	
3 4
   //baseUrl: 'https://vue.ruoyi.vip/prod-api',
4 5
    //baseUrl: 'http://localhost:8080',
5 6
    baseUrl: process.env.VUE_APP_BASE_API || 'http://localhost:8080',
7
+     // 未登录用户可阅读章节数(可配置)
8
+     freeChapters: uni.getStorageSync('freeChaptersLimit') || 5,
6 9
   // 应用信息
7 10
   appInfo: {
8 11
     // 应用名称

+ 12
- 12
RuoYi-App/pages/novel/list.vue Voir le fichier

@@ -446,18 +446,18 @@ export default {
446 446
       this.loadNovels(true); // 重置并加载新分类
447 447
     },
448 448
     
449
-    openNovel(novel) {
450
-      if (novel.id) {
451
-        uni.navigateTo({
452
-          url: `/pages/novel/detail?id=${novel.id}`
453
-        });
454
-      } else {
455
-        uni.showToast({
456
-          title: '小说ID不存在',
457
-          icon: 'none'
458
-        });
459
-      }
460
-    },
449
+openNovel(novel) {
450
+  if (novel.id) {
451
+    uni.navigateTo({
452
+      url: `/pages/novel/reader?novelId=${novel.id}`  // 使用反引号
453
+    });
454
+  } else {
455
+    uni.showToast({
456
+      title: '小说ID不存在',
457
+      icon: 'none'
458
+    });
459
+  }
460
+},
461 461
     
462 462
     applyAuthor() {
463 463
       if (!this.$store.getters.token) {

+ 498
- 70
RuoYi-App/pages/novel/reader.vue Voir le fichier

@@ -1,11 +1,24 @@
1 1
 <template>
2 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
+    
3 16
     <!-- 顶部导航 -->
4 17
     <view class="reader-header">
5 18
       <uni-icons type="arrowleft" size="28" color="#fff" @click="goBack"></uni-icons>
6 19
       <text class="novel-title">{{ novelTitle }}</text>
7 20
       <view class="header-actions">
8
-        <uni-icons type="more" size="28" color="#fff"></uni-icons>
21
+        <uni-icons type="more" size="28" color="#fff" @click="showMoreActions"></uni-icons>
9 22
       </view>
10 23
     </view>
11 24
     
@@ -13,16 +26,23 @@
13 26
     <scroll-view scroll-y class="reader-content" :scroll-top="scrollTop" @scroll="onScroll">
14 27
       <view class="chapter-title">{{ chapterDetail.title }}</view>
15 28
       <rich-text :nodes="chapterContent" class="content-text"></rich-text>
29
+      
30
+      <!-- 章节结束提示 -->
31
+      <view v-if="isChapterEnd" class="chapter-end">
32
+        <text>本章结束</text>
33
+        <button v-if="!isLastChapter" @click="nextChapter">下一章</button>
34
+        <text v-else>已是最新章节</text>
35
+      </view>
16 36
     </scroll-view>
17 37
     
18 38
     <!-- 底部操作栏 -->
19 39
     <view class="reader-footer">
20 40
       <view class="progress">
21
-        <text>{{ chapterDetail.chapterNumber }}/{{ totalChapters }}</text>
22
-        <text>{{ progress }}%</text>
41
+        <text>{{ currentChapterIndex + 1 }}/{{ chapters.length }}</text>
42
+        <text>{{ readingProgress }}%</text>
23 43
       </view>
24 44
       <view class="actions">
25
-        <button class="action-btn" @click="prevChapter">
45
+        <button class="action-btn" @click="prevChapter" :disabled="isFirstChapter">
26 46
           <uni-icons type="arrow-up" size="24"></uni-icons>
27 47
           <text>上一章</text>
28 48
         </button>
@@ -34,7 +54,7 @@
34 54
           <uni-icons type="gear" size="24"></uni-icons>
35 55
           <text>设置</text>
36 56
         </button>
37
-        <button class="action-btn" @click="nextChapter">
57
+        <button class="action-btn" @click="nextChapter" :disabled="isLastChapter">
38 58
           <uni-icons type="arrow-down" size="24"></uni-icons>
39 59
           <text>下一章</text>
40 60
         </button>
@@ -45,23 +65,58 @@
45 65
     <uni-drawer ref="drawer" mode="right" :width="300">
46 66
       <view class="drawer-content">
47 67
         <text class="drawer-title">目录</text>
68
+        <view class="chapter-stats">
69
+          <text>共{{ chapters.length }}章</text>
70
+          <text>已读{{ readChaptersCount }}章</text>
71
+        </view>
48 72
         <scroll-view scroll-y class="chapter-list">
49 73
           <view 
50
-            v-for="chapter in chapters" 
74
+            v-for="(chapter, index) in chapters" 
51 75
             :key="chapter.id" 
52 76
             class="chapter-item"
53
-            :class="{ active: chapter.id === chapterDetail.id }"
54
-            @click="selectChapter(chapter)"
77
+            :class="{ 
78
+              active: chapter.id === chapterDetail.id,
79
+              read: isChapterRead(chapter.id),
80
+              locked: isChapterLocked(index)
81
+            }"
82
+            @click="selectChapter(chapter, index)"
55 83
           >
56
-            <text>{{ chapter.chapterNumber }}. {{ chapter.title }}</text>
84
+            <text>{{ index + 1 }}. {{ chapter.title }}</text>
85
+            <uni-icons v-if="isChapterLocked(index)" type="locked" size="16" color="#999"></uni-icons>
57 86
           </view>
58 87
         </scroll-view>
59 88
       </view>
60 89
     </uni-drawer>
90
+    
91
+    <!-- 设置面板 -->
92
+    <uni-popup ref="settingsPopup" type="bottom">
93
+      <view class="settings-panel">
94
+        <text class="panel-title">阅读设置</text>
95
+        <view class="setting-item">
96
+          <text>字体大小</text>
97
+          <slider :value="fontSize" min="24" max="40" @change="updateFontSize" />
98
+        </view>
99
+        <view class="setting-item">
100
+          <text>背景颜色</text>
101
+          <view class="color-options">
102
+            <view 
103
+              v-for="color in backgroundColors" 
104
+              :key="color.value"
105
+              class="color-option"
106
+              :class="{ active: readerStyles.backgroundColor === color.value }"
107
+              :style="{ backgroundColor: color.value }"
108
+              @click="changeBackgroundColor(color.value)"
109
+            ></view>
110
+          </view>
111
+        </view>
112
+        <button class="close-btn" @click="$refs.settingsPopup.close()">关闭</button>
113
+      </view>
114
+    </uni-popup>
61 115
   </view>
62 116
 </template>
63 117
 
64 118
 <script>
119
+import config from '@/config'
65 120
 import novelService from '@/services/novelService'
66 121
 
67 122
 export default {
@@ -73,10 +128,20 @@ export default {
73 128
       chapterDetail: {},
74 129
       chapterContent: '',
75 130
       chapters: [],
76
-      // 确保所有字段初始化
77
-      totalChapters: 0,
78
-      progress: 0,
131
+      currentChapterIndex: 0,
132
+      // 阅读统计
133
+      readChapters: [],
134
+      readingProgress: 0,
79 135
       scrollTop: 0,
136
+      isChapterEnd: false,
137
+      // 阅读设置
138
+      fontSize: 32,
139
+      backgroundColors: [
140
+        { name: '护眼模式', value: '#f8f2e0' },
141
+        { name: '白色', value: '#ffffff' },
142
+        { name: '夜间', value: '#2c2c2c' },
143
+        { name: '淡蓝', value: '#e8f4f8' }
144
+      ],
80 145
       readerStyles: {
81 146
         fontSize: '32rpx',
82 147
         lineHeight: '1.8',
@@ -85,117 +150,403 @@ export default {
85 150
       }
86 151
     }
87 152
   },
153
+  computed: {
154
+    // 计算属性
155
+    isFirstChapter() {
156
+      return this.currentChapterIndex === 0
157
+    },
158
+    isLastChapter() {
159
+      return this.currentChapterIndex === this.chapters.length - 1
160
+    },
161
+    readChaptersCount() {
162
+      return this.readChapters.length
163
+    },
164
+    // 检查是否达到免费章节限制
165
+    reachedFreeLimit() {
166
+      const isLoggedIn = this.$store.getters.token
167
+      return !isLoggedIn && this.readChaptersCount >= config.freeChapters
168
+    }
169
+  },
88 170
   async onLoad(options) {
89
-    this.novelId = options.novelId;
90
-    this.chapterId = options.chapterId || 1;
171
+    this.novelId = options.novelId
172
+    this.chapterId = options.chapterId || null
91 173
     
92
-    await this.loadNovelData();
93
-    await this.loadChapter();
174
+    // 加载阅读进度
175
+    this.loadReadingProgress()
176
+    
177
+    await this.loadNovelData()
178
+    
179
+    // 如果有指定章节ID,加载该章节,否则加载第一章
180
+    if (this.chapterId) {
181
+      await this.loadChapter(this.chapterId)
182
+    } else {
183
+      // 尝试从阅读记录中恢复
184
+      const lastReadChapter = this.getLastReadChapter()
185
+      if (lastReadChapter) {
186
+        await this.loadChapter(lastReadChapter.id)
187
+      } else {
188
+        await this.loadFirstChapter()
189
+      }
190
+    }
191
+    
192
+    // 监听网络状态
193
+    uni.onNetworkStatusChange(this.handleNetworkChange)
194
+  },
195
+  onUnload() {
196
+    // 保存阅读进度
197
+    this.saveReadingProgress()
198
+    // 移除网络状态监听
199
+    uni.offNetworkStatusChange(this.handleNetworkChange)
94 200
   },
95 201
   methods: {
202
+    // 加载小说数据
96 203
     async loadNovelData() {
97
-      const res = await this.$http.get(`/novel/detail/${this.novelId}`);
98
-      if (res.data) {
99
-        this.novelTitle = res.data.title;
204
+      try {
205
+        uni.showLoading({ title: '加载中...' })
206
+        
207
+    // 加载小说基本信息 - 修复API路径
208
+    const novelRes = await this.$http.get(`/novel/detail/${this.novelId}`)
209
+    
210
+    // 使用统一的响应解析方法
211
+    const novelData = this.parseResponse(novelRes)
212
+    console.log('小说详情响应:', novelData)
213
+    
214
+    if (novelData.code === 200 && novelData.data) {
215
+      this.novelTitle = novelData.data.title
216
+      uni.setNavigationBarTitle({ title: this.novelTitle })
217
+    }
218
+    
219
+    // 加载章节列表 - 修复API路径
220
+    const chapterRes = await this.$http.get(`/chapter/list/${this.novelId}`)
221
+    
222
+    // 使用统一的响应解析方法
223
+    const chapterData = this.parseResponse(chapterRes)
224
+    console.log('章节列表响应:', chapterData)
225
+    
226
+    if (chapterData.code === 200) {
227
+      // 处理不同的响应格式
228
+      if (Array.isArray(chapterData.rows)) {
229
+        this.chapters = chapterData.rows
230
+      } else if (Array.isArray(chapterData.data)) {
231
+        this.chapters = chapterData.data
100 232
       }
101 233
       
102
-      // 获取章节列表
103
-      const chapterRes = await this.$http.get(`/chapter/list/${this.novelId}`);
104
-      if (chapterRes.rows && Array.isArray(chapterRes.rows)) {
105
-        this.chapters = chapterRes.rows;
106
-        this.totalChapters = this.chapters.length;
234
+      // 初始化当前章节索引
235
+      if (this.chapterId) {
236
+        this.currentChapterIndex = this.chapters.findIndex(ch => ch.id === this.chapterId)
237
+      }
238
+    }
239
+  } catch (error) {
240
+    console.error('加载小说数据失败:', error)
241
+    uni.showToast({
242
+      title: '加载失败',
243
+      icon: 'none'
244
+    })
245
+  } finally {
246
+    uni.hideLoading()
107 247
       }
108 248
     },
109 249
     
110
-    async loadChapter() {
111
-      uni.showLoading({ title: '加载中...' });
250
+    // 加载章节内容
251
+    async loadChapter(chapterId) {
252
+      // 检查章节是否锁定
253
+      const chapterIndex = this.chapters.findIndex(ch => ch.id === chapterId)
254
+      if (this.isChapterLocked(chapterIndex)) {
255
+        this.showLoginPrompt()
256
+        return
257
+      }
112 258
       
113 259
       try {
114
-        const res = await this.$http.get(`/chapter/content/${this.chapterId}`);
260
+        uni.showLoading({ title: '加载中...' })
261
+        
262
+        const res = await this.$http.get(`/chapter/content/${chapterId}`)
115 263
         if (res.data) {
116
-          this.chapterDetail = res.data;
117
-          this.chapterContent = this.formatContent(res.data.content);
118
-          this.progress = Math.round((this.chapterDetail.chapterOrder / this.totalChapters) * 100);
264
+          this.chapterDetail = res.data
265
+          this.chapterContent = this.formatContent(res.data.content)
266
+          this.chapterId = chapterId
267
+          this.currentChapterIndex = chapterIndex
268
+          
269
+          // 标记为已读
270
+          this.markChapterAsRead(chapterId)
271
+          
272
+          // 计算阅读进度
273
+          this.calculateReadingProgress()
119 274
         }
275
+      } catch (error) {
276
+        console.error('加载章节内容失败:', error)
277
+        uni.showToast({
278
+          title: '加载失败',
279
+          icon: 'none'
280
+        })
120 281
       } finally {
121
-        uni.hideLoading();
282
+        uni.hideLoading()
122 283
       }
123 284
     },
124 285
     
286
+    // 加载第一章
287
+    async loadFirstChapter() {
288
+      if (this.chapters.length > 0) {
289
+        await this.loadChapter(this.chapters[0].id)
290
+      }
291
+    },
292
+    
293
+    // 格式化内容
125 294
     formatContent(content) {
126
-      // 处理特殊格式
295
+      if (!content) return ''
296
+      
297
+      // 处理HTML标签和特殊字符
127 298
       return content
128
-        .replace(/<br>/g, '\n')
299
+        .replace(/<br\s*\/?>/gi, '\n')
129 300
         .replace(/&nbsp;/g, ' ')
130 301
         .replace(/<p>/g, '\n\n')
131 302
         .replace(/<\/p>/g, '')
132
-        .replace(/<[^>]+>/g, '');
133
-    }
134
-
303
+        .replace(/<[^>]+>/g, '')
304
+        .replace(/\n{3,}/g, '\n\n')
305
+        .trim()
306
+    },
307
+    
308
+    // 标记章节为已读
309
+    markChapterAsRead(chapterId) {
310
+      if (!this.readChapters.includes(chapterId)) {
311
+        this.readChapters.push(chapterId)
312
+        this.saveReadingProgress()
313
+      }
314
+    },
315
+    
316
+    // 检查章节是否已读
317
+    isChapterRead(chapterId) {
318
+      return this.readChapters.includes(chapterId)
319
+    },
320
+    
321
+    // 检查章节是否锁定
322
+    isChapterLocked(chapterIndex) {
323
+      // 已登录用户可以阅读所有章节
324
+      if (this.$store.getters.token) return false
325
+      
326
+      // 未登录用户只能阅读前N章
327
+      return chapterIndex >= config.freeChapters
328
+    },
135 329
     
330
+    // 加载阅读进度
331
+    loadReadingProgress() {
332
+      const progress = uni.getStorageSync(`readingProgress_${this.novelId}`)
333
+      if (progress) {
334
+        this.readChapters = progress.readChapters || []
335
+        this.readerStyles = progress.readerStyles || this.readerStyles
336
+        this.fontSize = progress.fontSize || 32
337
+      }
338
+    },
339
+    
340
+    // 保存阅读进度
136 341
     saveReadingProgress() {
137
-      // 保存到本地
138
-      uni.setStorageSync('readingProgress', {
342
+      const progress = {
139 343
         novelId: this.novelId,
140
-        chapterId: this.chapterId
141
-      })
344
+        chapterId: this.chapterId,
345
+        readChapters: this.readChapters,
346
+        readerStyles: this.readerStyles,
347
+        fontSize: this.fontSize,
348
+        timestamp: Date.now()
349
+      }
142 350
       
143
-      // 如果已登录,同步到服务器
144
-      if (uni.getStorageSync('token')) {
145
-        this.$http.post('/reading/progress', {
146
-          novelId: this.novelId,
147
-          chapterId: this.chapterId
148
-        })
351
+      uni.setStorageSync(`readingProgress_${this.novelId}`, progress)
352
+      
353
+      // 同步到服务器(如果已登录)
354
+      if (this.$store.getters.token) {
355
+        this.$http.post('/reading/progress', progress)
149 356
       }
150 357
     },
151 358
     
359
+    // 获取最后阅读的章节
360
+    getLastReadChapter() {
361
+      if (this.chapterId) {
362
+        return this.chapters.find(ch => ch.id === this.chapterId)
363
+      }
364
+      
365
+      const progress = uni.getStorageSync(`readingProgress_${this.novelId}`)
366
+      if (progress && progress.chapterId) {
367
+        return this.chapters.find(ch => ch.id === progress.chapterId)
368
+      }
369
+      
370
+      return null
371
+    },
372
+    
373
+    // 计算阅读进度
374
+    calculateReadingProgress() {
375
+      if (this.chapters.length === 0) return 0
376
+      this.readingProgress = Math.round((this.readChaptersCount / this.chapters.length) * 100)
377
+    },
378
+    
379
+    // 显示登录提示
380
+    showLoginPrompt() {
381
+      this.$refs.loginPopup.open()
382
+    },
383
+    
384
+    // 关闭登录对话框
385
+    closeLoginDialog() {
386
+      this.$refs.loginPopup.close()
387
+    },
388
+    
389
+    // 跳转到登录页面
390
+    goToLogin() {
391
+      uni.navigateTo({
392
+        url: '/pages/login?redirect=' + encodeURIComponent(this.$route.fullPath)
393
+      })
394
+    },
395
+    
396
+    // 上一章
152 397
     prevChapter() {
153
-      if (this.chapterId > 1) {
154
-        this.chapterId--
155
-        this.loadChapter()
156
-      } else {
157
-        uni.showToast({
158
-          title: '已经是第一章',
159
-          icon: 'none'
160
-        })
398
+      if (this.currentChapterIndex > 0) {
399
+        const prevChapter = this.chapters[this.currentChapterIndex - 1]
400
+        this.loadChapter(prevChapter.id)
161 401
       }
162 402
     },
163 403
     
404
+    // 下一章
164 405
     nextChapter() {
165
-      if (this.chapterId < this.totalChapters) {
166
-        this.chapterId++
167
-        this.loadChapter()
168
-      } else {
406
+      if (this.currentChapterIndex < this.chapters.length - 1) {
407
+        const nextChapter = this.chapters[this.currentChapterIndex + 1]
408
+        
409
+        // 检查下一章是否锁定
410
+        if (this.isChapterLocked(this.currentChapterIndex + 1)) {
411
+          this.showLoginPrompt()
412
+          return
413
+        }
414
+        
415
+        this.loadChapter(nextChapter.id)
416
+      }
417
+    },
418
+    
419
+    // 选择章节
420
+    selectChapter(chapter, index) {
421
+      // 检查章节是否锁定
422
+      if (this.isChapterLocked(index)) {
423
+        this.showLoginPrompt()
424
+        return
425
+      }
426
+      
427
+      this.loadChapter(chapter.id)
428
+      this.$refs.drawer.close()
429
+    },
430
+    
431
+    // 显示更多操作
432
+    showMoreActions() {
433
+      uni.showActionSheet({
434
+        itemList: ['添加到书架', '分享', '举报', '设置'],
435
+        success: (res) => {
436
+          switch (res.tapIndex) {
437
+            case 0:
438
+              this.addToBookshelf()
439
+              break
440
+            case 1:
441
+              this.shareNovel()
442
+              break
443
+            case 2:
444
+              this.reportNovel()
445
+              break
446
+            case 3:
447
+              this.toggleSettings()
448
+              break
449
+          }
450
+        }
451
+      })
452
+    },
453
+    
454
+    // 添加到书架
455
+    addToBookshelf() {
456
+      if (!this.$store.getters.token) {
169 457
         uni.showToast({
170
-          title: '已是最新章节',
458
+          title: '请先登录',
171 459
           icon: 'none'
172 460
         })
461
+        return
173 462
       }
463
+      
464
+      this.$http.post('/bookshelf/add', {
465
+        novelId: this.novelId
466
+      }).then(res => {
467
+        uni.showToast({
468
+          title: '已添加到书架',
469
+          icon: 'success'
470
+        })
471
+      }).catch(error => {
472
+        uni.showToast({
473
+          title: '添加失败',
474
+          icon: 'none'
475
+        })
476
+      })
174 477
     },
175 478
     
176
-    selectChapter(chapter) {
177
-      this.chapterId = chapter.id
178
-      this.$refs.drawer.close()
179
-      this.loadChapter()
479
+    // 分享小说
480
+    shareNovel() {
481
+      uni.share({
482
+        title: this.novelTitle,
483
+        path: `/pages/novel/reader?novelId=${this.novelId}`,
484
+        success: () => {
485
+          uni.showToast({
486
+            title: '分享成功',
487
+            icon: 'success'
488
+          })
489
+        }
490
+      })
491
+    },
492
+    
493
+    // 举报小说
494
+    reportNovel() {
495
+      uni.navigateTo({
496
+        url: `/pages/report?novelId=${this.novelId}`
497
+      })
180 498
     },
181 499
     
500
+    // 打开目录
182 501
     toggleMenu() {
183 502
       this.$refs.drawer.open()
184 503
     },
185 504
     
505
+    // 打开设置
186 506
     toggleSettings() {
187
-      uni.navigateTo({
188
-        url: '/pages/reader/settings'
189
-      })
507
+      this.$refs.settingsPopup.open()
508
+    },
509
+    
510
+    // 更新字体大小
511
+    updateFontSize(e) {
512
+      this.fontSize = e.detail.value
513
+      this.readerStyles.fontSize = `${this.fontSize}rpx`
514
+      this.saveReadingProgress()
190 515
     },
191 516
     
517
+    // 更改背景颜色
518
+    changeBackgroundColor(color) {
519
+      this.readerStyles.backgroundColor = color
520
+      // 根据背景色调整文字颜色
521
+      this.readerStyles.color = color === '#2c2c2c' ? '#f0f0f0' : '#333'
522
+      this.saveReadingProgress()
523
+    },
524
+    
525
+    // 返回上一页
192 526
     goBack() {
193 527
       uni.navigateBack()
194 528
     },
195 529
     
530
+    // 滚动事件
196 531
     onScroll(e) {
197
-      // 记录滚动位置
198 532
       this.scrollTop = e.detail.scrollTop
533
+      
534
+      // 检测是否滚动到章节末尾
535
+      const scrollHeight = e.detail.scrollHeight
536
+      const scrollTop = e.detail.scrollTop
537
+      const clientHeight = e.detail.clientHeight
538
+      
539
+      this.isChapterEnd = scrollHeight - scrollTop - clientHeight < 50
540
+    },
541
+    
542
+    // 处理网络状态变化
543
+    handleNetworkChange(res) {
544
+      if (!res.isConnected) {
545
+        uni.showToast({
546
+          title: '网络已断开',
547
+          icon: 'none'
548
+        })
549
+      }
199 550
     }
200 551
   }
201 552
 }
@@ -207,6 +558,7 @@ export default {
207 558
   height: 100vh;
208 559
   padding: 20rpx;
209 560
   box-sizing: border-box;
561
+  transition: all 0.3s ease;
210 562
 }
211 563
 
212 564
 .reader-header {
@@ -250,6 +602,13 @@ export default {
250 602
   line-height: 1.8;
251 603
 }
252 604
 
605
+.chapter-end {
606
+  text-align: center;
607
+  margin-top: 40rpx;
608
+  padding: 20rpx;
609
+  border-top: 1rpx solid #eee;
610
+}
611
+
253 612
 .reader-footer {
254 613
   position: absolute;
255 614
   bottom: 0;
@@ -283,6 +642,10 @@ export default {
283 642
   padding: 10rpx;
284 643
 }
285 644
 
645
+.action-btn:disabled {
646
+  opacity: 0.5;
647
+}
648
+
286 649
 .drawer-content {
287 650
   padding: 30rpx;
288 651
 }
@@ -291,16 +654,27 @@ export default {
291 654
   font-size: 36rpx;
292 655
   font-weight: bold;
293 656
   display: block;
294
-  margin-bottom: 30rpx;
657
+  margin-bottom: 20rpx;
295 658
   border-bottom: 1rpx solid #eee;
296 659
   padding-bottom: 20rpx;
297 660
 }
298 661
 
662
+.chapter-stats {
663
+  display: flex;
664
+  justify-content: space-between;
665
+  margin-bottom: 20rpx;
666
+  font-size: 24rpx;
667
+  color: #666;
668
+}
669
+
299 670
 .chapter-list {
300
-  height: calc(100vh - 150rpx);
671
+  height: calc(100vh - 200rpx);
301 672
 }
302 673
 
303 674
 .chapter-item {
675
+  display: flex;
676
+  justify-content: space-between;
677
+  align-items: center;
304 678
   padding: 20rpx 10rpx;
305 679
   border-bottom: 1rpx solid #f0f0f0;
306 680
   font-size: 28rpx;
@@ -310,4 +684,58 @@ export default {
310 684
   color: #2a5caa;
311 685
   font-weight: bold;
312 686
 }
313
-</style>
687
+
688
+.chapter-item.read {
689
+  color: #666;
690
+}
691
+
692
+.chapter-item.locked {
693
+  color: #999;
694
+}
695
+
696
+.settings-panel {
697
+  padding: 30rpx;
698
+  background: white;
699
+  border-radius: 20rpx 20rpx 0 0;
700
+}
701
+
702
+.panel-title {
703
+  font-size: 36rpx;
704
+  font-weight: bold;
705
+  display: block;
706
+  margin-bottom: 30rpx;
707
+  text-align: center;
708
+}
709
+
710
+.setting-item {
711
+  margin-bottom: 30rpx;
712
+}
713
+
714
+.setting-item text {
715
+  display: block;
716
+  margin-bottom: 15rpx;
717
+  font-size: 28rpx;
718
+}
719
+
720
+.color-options {
721
+  display: flex;
722
+  justify-content: space-between;
723
+}
724
+
725
+.color-option {
726
+  width: 60rpx;
727
+  height: 60rpx;
728
+  border-radius: 50%;
729
+  border: 2rpx solid #eee;
730
+}
731
+
732
+.color-option.active {
733
+  border-color: #2a5caa;
734
+}
735
+
736
+.close-btn {
737
+  margin-top: 30rpx;
738
+  background: #f0f0f0;
739
+  color: #333;
740
+}
741
+</style>

Chargement…
Annuler
Enregistrer