fzzj před 7 měsíci
rodič
revize
74df5d551b

+ 17
- 17
RuoYi-App/pages/novel/list.vue Zobrazit soubor

242
   try {
242
   try {
243
     console.log('准备打开小说:', novel);
243
     console.log('准备打开小说:', novel);
244
     
244
     
245
-    // 确保 novel 对象存在且有有效 ID
246
-    if (!novel || typeof novel !== 'object') {
247
-      console.error('novel对象无效:', novel);
248
-      uni.showToast({
249
-        title: '小说信息错误',
250
-        icon: 'none'
251
-      });
252
-      return;
253
-    }
254
-    
255
-    // 获取小说ID - 使用若依框架的标准方式
256
     const novelId = novel.id || novel.novelId || novel.bookId;
245
     const novelId = novel.id || novel.novelId || novel.bookId;
257
     if (!novelId) {
246
     if (!novelId) {
258
       console.error('无法获取小说ID:', novel);
247
       console.error('无法获取小说ID:', novel);
263
       return;
252
       return;
264
     }
253
     }
265
     
254
     
266
-    console.log('跳转到阅读页面,小说ID:', novelId, '章节ID:', 1);
267
-    // 使用若依框架的标准导航方式
268
-    this.$router.push({
269
-      path: '/pages/novel/reader',
270
-      query: { novelId: novelId ,chapterId : 1}
271
-    });
255
+    console.log('跳转到阅读页面,小说ID:', novelId);
272
     
256
     
257
+    // 使用uni-app的标准导航方式
258
+    uni.navigateTo({
259
+      url: `/pages/novel/reader?novelId=${novelId}&chapterId=1`,
260
+      success: (res) => {
261
+        console.log('导航成功', res);
262
+      },
263
+      fail: (err) => {
264
+        console.error('导航失败', err);
265
+        // 备用导航方案
266
+        this.$router.push({
267
+          path: '/pages/novel/reader',
268
+          query: { novelId: novelId, chapterId: 1 }
269
+        });
270
+      }
271
+    });
273
   } catch (error) {
272
   } catch (error) {
274
     console.error('openNovel方法执行异常:', error);
273
     console.error('openNovel方法执行异常:', error);
275
     uni.showToast({
274
     uni.showToast({
279
   }
278
   }
280
 },
279
 },
281
 
280
 
281
+
282
 // 添加安全导航方法
282
 // 添加安全导航方法
283
 safeNavigateTo(url) {
283
 safeNavigateTo(url) {
284
   try {
284
   try {

+ 225
- 82
RuoYi-App/pages/novel/reader.vue Zobrazit soubor

1
 <template>
1
 <template>
2
-  <view class="reader-container">
2
+  <view class="reader-container" :class="{ 'night-mode': nightMode }">
3
     <!-- 顶部导航栏 -->
3
     <!-- 顶部导航栏 -->
4
     <view class="reader-header" v-if="showHeader">
4
     <view class="reader-header" v-if="showHeader">
5
       <view class="header-left">
5
       <view class="header-left">
22
     >
22
     >
23
       <view class="chapter-title">{{chapterTitle}}</view>
23
       <view class="chapter-title">{{chapterTitle}}</view>
24
       <view class="content-text">
24
       <view class="content-text">
25
-        <text>{{decodedContent}}</text>
25
+        <text>{{content}}</text>
26
       </view>
26
       </view>
27
       
27
       
28
       <!-- 加载状态 -->
28
       <!-- 加载状态 -->
55
 
55
 
56
     <!-- 设置面板 -->
56
     <!-- 设置面板 -->
57
     <view class="settings-panel" v-if="showSettings">
57
     <view class="settings-panel" v-if="showSettings">
58
-      <!-- 字体大小、背景颜色等设置 -->
58
+      <view class="font-size-controls">
59
+        <text class="iconfont icon-font-small" @click="decreaseFontSize"></text>
60
+        <text>字体大小: {{fontSize}}px</text>
61
+        <text class="iconfont icon-font-large" @click="increaseFontSize"></text>
62
+      </view>
63
+      <view class="background-options">
64
+        <view 
65
+          v-for="bg in backgroundOptions" 
66
+          :key="bg.value"
67
+          class="bg-option"
68
+          :class="{ active: backgroundColor === bg.value }"
69
+          :style="{ backgroundColor: bg.value }"
70
+          @click="changeBackground(bg.value)"
71
+        ></view>
72
+      </view>
59
     </view>
73
     </view>
60
 
74
 
61
     <!-- 目录面板 -->
75
     <!-- 目录面板 -->
62
     <view class="catalog-panel" v-if="showCatalogPanel">
76
     <view class="catalog-panel" v-if="showCatalogPanel">
63
-      <!-- 章节列表 -->
77
+      <view class="catalog-header">
78
+        <text class="catalog-title">目录</text>
79
+        <text class="iconfont icon-close" @click="showCatalogPanel = false"></text>
80
+      </view>
81
+      <scroll-view class="chapter-list" scroll-y>
82
+        <view 
83
+          v-for="chapter in chapterList" 
84
+          :key="chapter.id"
85
+          class="chapter-item"
86
+          :class="{ active: chapter.id === chapterId }"
87
+          @click="selectChapter(chapter.id)"
88
+        >
89
+          <text>{{chapter.title}}</text>
90
+        </view>
91
+      </scroll-view>
64
     </view>
92
     </view>
65
   </view>
93
   </view>
66
 </template>
94
 </template>
67
 
95
 
96
+
68
 <script>
97
 <script>
69
 export default {
98
 export default {
70
   data() {
99
   data() {
73
       chapterId: '',
102
       chapterId: '',
74
       novelTitle: '加载中...',
103
       novelTitle: '加载中...',
75
       chapterTitle: '加载中...',
104
       chapterTitle: '加载中...',
76
-      content: '', // Base64编码的内容
77
-      decodedContent: '正在加载内容...', // 解码后的内容
105
+      content: '正在加载内容...', // 直接存储解码后的内容
78
       currentChapter: 1,
106
       currentChapter: 1,
79
       totalChapters: 0,
107
       totalChapters: 0,
80
       progress: 0,
108
       progress: 0,
83
       showFooter: false,
111
       showFooter: false,
84
       showSettings: false,
112
       showSettings: false,
85
       showCatalogPanel: false,
113
       showCatalogPanel: false,
86
-      showAd: false, // 默认不显示广告
114
+      showAd: false,
87
       nightMode: false,
115
       nightMode: false,
88
       touchStartX: 0,
116
       touchStartX: 0,
89
       touchStartY: 0,
117
       touchStartY: 0,
90
       lastScrollPosition: 0,
118
       lastScrollPosition: 0,
91
       loading: false,
119
       loading: false,
92
-      error: null
120
+      error: null,
121
+      fontSize: 18,
122
+      backgroundColor: '#f5f0e1',
123
+      backgroundOptions: [
124
+        { value: '#f5f0e1', name: '羊皮纸' },
125
+        { value: '#e8f5e9', name: '护眼绿' },
126
+        { value: '#e3f2fd', name: '天空蓝' },
127
+        { value: '#fce4ec', name: '粉红' },
128
+        { value: '#1a1a1a', name: '夜间' }
129
+      ],
130
+      chapterList: []
93
     }
131
     }
94
   },
132
   },
133
+    // 添加所有生命周期钩子进行调试
134
+  beforeCreate() {
135
+    console.log('reader beforeCreate');
136
+  },
137
+    created() {
138
+    console.log('reader created');
139
+  },
140
+    beforeMount() {
141
+    console.log('reader beforeMount');
142
+  },
143
+    mounted() {
144
+    console.log('reader mounted');
145
+  },
146
+    onReady() {
147
+    console.log('reader onReady');
148
+  },
149
+    onHide() {
150
+    console.log('reader onHide');
151
+  },
152
+  onUnload() {
153
+    console.log('reader onUnload');
154
+  },
95
   onLoad(options) {
155
   onLoad(options) {
96
     console.log('阅读器页面加载,参数:', options);
156
     console.log('阅读器页面加载,参数:', options);
97
     
157
     
98
-    // 确保参数正确获取
158
+    // 确保参数获取方式正确
99
     this.novelId = options.novelId || '';
159
     this.novelId = options.novelId || '';
100
     this.chapterId = options.chapterId || '1';
160
     this.chapterId = options.chapterId || '1';
101
     
161
     
102
     if (!this.novelId) {
162
     if (!this.novelId) {
163
+      console.error('小说ID参数缺失');
103
       this.error = '小说ID参数缺失';
164
       this.error = '小说ID参数缺失';
165
+      uni.showToast({
166
+        title: '小说ID参数缺失',
167
+        icon: 'none'
168
+      });
104
       return;
169
       return;
105
     }
170
     }
106
     
171
     
172
+    // 显示加载提示
173
+    uni.showLoading({
174
+      title: '加载中...',
175
+      mask: true
176
+    });
177
+    
107
     this.loadChapterContent();
178
     this.loadChapterContent();
108
   },
179
   },
109
-  methods: {
110
-    // 添加onScroll方法
111
-    onScroll(e) {
112
-      this.lastScrollPosition = e.detail.scrollTop;
113
-      // 计算阅读进度
114
-      this.calculateProgress();
115
-    },
116
-    
117
-    onTouchStart(e) {
118
-      this.touchStartX = e.touches[0].clientX;
119
-      this.touchStartY = e.touches[0].clientY;
120
-    },
121
-    
122
-    onTouchEnd(e) {
123
-      const endX = e.changedTouches[0].clientX;
124
-      const endY = e.changedTouches[0].clientY;
125
-      const diffX = endX - this.touchStartX;
126
-      const diffY = endY - this.touchStartY;
127
-      
128
-      // 横向滑动大于纵向滑动,且滑动距离大于50px
129
-      if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
130
-        if (diffX > 0) {
131
-          this.prevChapter(); // 右滑,上一章
132
-        } else {
133
-          this.nextChapter(); // 左滑,下一章
134
-        }
135
-      }
136
-    },
137
-    
138
-    // Base64解码
139
-    decodeBase64(content) {
140
-      if (typeof content !== 'string') {
141
-        console.error('Base64解码失败:内容不是字符串');
142
-        return '内容格式错误';
143
-      }
144
-      
145
-      try {
146
-        // 处理可能的URL安全Base64编码
147
-        let base64 = content.replace(/-/g, '+').replace(/_/g, '/');
148
-        
149
-        // 添加必要的填充字符
150
-        const pad = base64.length % 4;
151
-        if (pad) {
152
-          if (pad === 1) {
153
-            throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
154
-          }
155
-          base64 += new Array(5-pad).join('=');
156
-        }
157
-        
158
-        // 解码
159
-        const decoded = uni.base64ToArrayBuffer(base64);
160
-        const textDecoder = new TextDecoder('utf-8');
161
-        return textDecoder.decode(decoded);
162
-      } catch (error) {
163
-        console.error('Base64解码失败:', error);
164
-        return '内容解码失败,请稍后再试。';
180
+  onShow() {
181
+    // 确保页面显示时也能处理参数
182
+    const pages = getCurrentPages();
183
+    const currentPage = pages[pages.length - 1];
184
+    if (currentPage && currentPage.options) {
185
+      const options = currentPage.options;
186
+      if (options.novelId && !this.novelId) {
187
+        this.novelId = options.novelId;
188
+        this.chapterId = options.chapterId || '1';
189
+        this.loadChapterContent();
165
       }
190
       }
166
-    },
167
-    
191
+    }
192
+  },
193
+  methods: {
168
     async loadChapterContent() {
194
     async loadChapterContent() {
169
       this.loading = true;
195
       this.loading = true;
170
       this.error = null;
196
       this.error = null;
171
       
197
       
172
       try {
198
       try {
173
         console.log('开始请求章节内容,章节ID:', this.chapterId);
199
         console.log('开始请求章节内容,章节ID:', this.chapterId);
200
+        
201
+        // 使用若依框架的API调用方式
174
         const res = await this.$http.get(`/novel/chapter/${this.chapterId}`);
202
         const res = await this.$http.get(`/novel/chapter/${this.chapterId}`);
175
         console.log('章节内容响应:', res);
203
         console.log('章节内容响应:', res);
176
         
204
         
177
         if (res.code === 200) {
205
         if (res.code === 200) {
178
           this.novelTitle = res.data.novelTitle || '未知小说';
206
           this.novelTitle = res.data.novelTitle || '未知小说';
179
           this.chapterTitle = res.data.chapterTitle || '未知章节';
207
           this.chapterTitle = res.data.chapterTitle || '未知章节';
208
+          
209
+          // 后台已经解码Base64,直接使用内容
180
           this.content = res.data.content || '';
210
           this.content = res.data.content || '';
181
-          this.decodedContent = this.decodeBase64(this.content);
182
           
211
           
183
           // 设置章节信息
212
           // 设置章节信息
184
           this.currentChapter = res.data.chapterIndex || 1;
213
           this.currentChapter = res.data.chapterIndex || 1;
185
           this.totalChapters = res.data.totalChapters || 0;
214
           this.totalChapters = res.data.totalChapters || 0;
215
+          
216
+          uni.showToast({
217
+            title: '加载成功',
218
+            icon: 'success',
219
+            duration: 1500
220
+          });
186
         } else {
221
         } else {
187
           console.error('API返回错误状态码:', res.code);
222
           console.error('API返回错误状态码:', res.code);
188
-          this.error = '加载失败,请重试';
223
+          this.error = '加载失败: ' + (res.msg || '未知错误');
224
+          uni.showToast({
225
+            title: res.msg || '加载失败',
226
+            icon: 'none'
227
+          });
189
         }
228
         }
190
       } catch (error) {
229
       } catch (error) {
191
         console.error('加载章节内容失败:', error);
230
         console.error('加载章节内容失败:', error);
192
         this.error = '加载失败,请检查网络连接';
231
         this.error = '加载失败,请检查网络连接';
232
+        uni.showToast({
233
+          title: '加载失败,请检查网络连接',
234
+          icon: 'none'
235
+        });
193
       } finally {
236
       } finally {
194
         this.loading = false;
237
         this.loading = false;
238
+        uni.hideLoading();
195
       }
239
       }
196
     },
240
     },
197
     
241
     
198
     calculateProgress() {
242
     calculateProgress() {
199
-      // 根据滚动位置计算阅读进度
200
-      // 这里需要获取内容区域的实际高度,由于uni-app中获取节点高度较为复杂,暂时用模拟数据
201
-      // 实际项目中应该通过uni.createSelectorQuery()获取
243
+      // 简化进度计算
202
       this.progress = Math.min(100, Math.round((this.lastScrollPosition / 1000) * 100));
244
       this.progress = Math.min(100, Math.round((this.lastScrollPosition / 1000) * 100));
203
     },
245
     },
204
     
246
     
224
       }
266
       }
225
     },
267
     },
226
     
268
     
269
+    selectChapter(chapterId) {
270
+      this.chapterId = chapterId;
271
+      this.showCatalogPanel = false;
272
+      this.loadChapterContent();
273
+    },
274
+    
227
     showMenu() {
275
     showMenu() {
228
-      // 显示/隐藏顶部和底部栏
229
       this.showHeader = !this.showHeader;
276
       this.showHeader = !this.showHeader;
230
       this.showFooter = !this.showFooter;
277
       this.showFooter = !this.showFooter;
231
     },
278
     },
235
     },
282
     },
236
     
283
     
237
     showFontSettings() {
284
     showFontSettings() {
238
-      this.showSettings = true;
285
+      this.showSettings = !this.showSettings;
239
     },
286
     },
240
     
287
     
241
     toggleNightMode() {
288
     toggleNightMode() {
242
       this.nightMode = !this.nightMode;
289
       this.nightMode = !this.nightMode;
243
-      // 切换夜间模式样式
290
+      this.backgroundColor = this.nightMode ? '#1a1a1a' : '#f5f0e1';
291
+    },
292
+    
293
+    increaseFontSize() {
294
+      this.fontSize = Math.min(24, this.fontSize + 2);
295
+    },
296
+    
297
+    decreaseFontSize() {
298
+      this.fontSize = Math.max(14, this.fontSize - 2);
299
+    },
300
+    
301
+    changeBackground(color) {
302
+      this.backgroundColor = color;
303
+      this.nightMode = color === '#1a1a1a';
244
     },
304
     },
245
     
305
     
246
     setAdStrategy() {
306
     setAdStrategy() {
247
-      // 设置广告显示策略,如每阅读三章显示一次广告
248
       const readChapters = uni.getStorageSync('readChapters') || 0;
307
       const readChapters = uni.getStorageSync('readChapters') || 0;
249
       this.showAd = readChapters > 0 && readChapters % 3 === 0;
308
       this.showAd = readChapters > 0 && readChapters % 3 === 0;
250
     },
309
     },
256
 }
315
 }
257
 </script>
316
 </script>
258
 
317
 
318
+
259
 <style>
319
 <style>
260
 .reader-container {
320
 .reader-container {
261
   position: relative;
321
   position: relative;
262
   height: 100vh;
322
   height: 100vh;
263
   display: flex;
323
   display: flex;
264
   flex-direction: column;
324
   flex-direction: column;
265
-  background-color: #f5f0e1; /* 羊皮纸色背景 */
325
+  background-color: v-bind(backgroundColor);
326
+  transition: background-color 0.3s ease;
266
 }
327
 }
267
 
328
 
268
 .reader-header {
329
 .reader-header {
273
   align-items: center;
334
   align-items: center;
274
   background: rgba(0, 0, 0, 0.8);
335
   background: rgba(0, 0, 0, 0.8);
275
   color: white;
336
   color: white;
337
+  position: fixed;
338
+  top: 0;
339
+  left: 0;
340
+  right: 0;
341
+  z-index: 1000;
276
 }
342
 }
277
 
343
 
278
 .reader-content {
344
 .reader-content {
280
   padding: 20px;
346
   padding: 20px;
281
   box-sizing: border-box;
347
   box-sizing: border-box;
282
   line-height: 1.8;
348
   line-height: 1.8;
283
-  font-size: 18px;
349
+  font-size: v-bind(fontSize + 'px');
284
   color: #333;
350
   color: #333;
351
+  margin-top: 44px;
352
+  margin-bottom: 50px;
285
 }
353
 }
286
 
354
 
287
 .chapter-title {
355
 .chapter-title {
304
   align-items: center;
372
   align-items: center;
305
   background: rgba(0, 0, 0, 0.8);
373
   background: rgba(0, 0, 0, 0.8);
306
   color: white;
374
   color: white;
375
+  position: fixed;
376
+  bottom: 0;
377
+  left: 0;
378
+  right: 0;
379
+  z-index: 1000;
307
 }
380
 }
308
 
381
 
309
 .reader-ad {
382
 .reader-ad {
321
 
394
 
322
 .error-container button {
395
 .error-container button {
323
   margin-top: 20px;
396
   margin-top: 20px;
397
+  background-color: var(--primary-color);
398
+  color: white;
399
+  border: none;
400
+  padding: 10px 20px;
401
+  border-radius: 5px;
402
+}
403
+
404
+.settings-panel {
405
+  position: fixed;
406
+  bottom: 50px;
407
+  left: 0;
408
+  right: 0;
409
+  background: rgba(255, 255, 255, 0.95);
410
+  padding: 20px;
411
+  z-index: 999;
412
+  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
413
+}
414
+
415
+.font-size-controls {
416
+  display: flex;
417
+  justify-content: space-between;
418
+  align-items: center;
419
+  margin-bottom: 20px;
420
+}
421
+
422
+.background-options {
423
+  display: flex;
424
+  justify-content: space-around;
425
+}
426
+
427
+.bg-option {
428
+  width: 40px;
429
+  height: 40px;
430
+  border-radius: 50%;
431
+  border: 2px solid transparent;
432
+  cursor: pointer;
433
+}
434
+
435
+.bg-option.active {
436
+  border-color: var(--primary-color);
437
+}
438
+
439
+.catalog-panel {
440
+  position: fixed;
441
+  top: 0;
442
+  left: 0;
443
+  right: 0;
444
+  bottom: 0;
445
+  background: white;
446
+  z-index: 1001;
447
+}
448
+
449
+.catalog-header {
450
+  display: flex;
451
+  justify-content: space-between;
452
+  align-items: center;
453
+  padding: 15px;
454
+  border-bottom: 1px solid #eee;
455
+}
456
+
457
+.chapter-list {
458
+  height: calc(100vh - 60px);
459
+}
460
+
461
+.chapter-item {
462
+  padding: 15px;
463
+  border-bottom: 1px solid #eee;
464
+}
465
+
466
+.chapter-item.active {
467
+  color: var(--primary-color);
468
+  font-weight: bold;
324
 }
469
 }
325
 
470
 
326
 /* 夜间模式样式 */
471
 /* 夜间模式样式 */
327
-.night-mode {
328
-  background-color: #1a1a1a;
472
+.night-mode .reader-content {
329
   color: #888;
473
   color: #888;
330
 }
474
 }
331
 
475
 
332
-.night-mode .reader-content {
333
-  background-color: #1a1a1a;
476
+.night-mode .content-text {
334
   color: #888;
477
   color: #888;
335
 }
478
 }
336
 </style>
479
 </style>

+ 1
- 1
RuoYi-App/router/index.js Zobrazit soubor

47
 ]
47
 ]
48
 
48
 
49
 const router = new Router({
49
 const router = new Router({
50
-  mode: 'history',
50
+  mode: 'hash',
51
   routes
51
   routes
52
 })
52
 })
53
 
53
 

+ 39
- 54
RuoYi-Vue/ruoyi-system/src/main/java/com/ruoyi/novel/controller/NovelController.java Zobrazit soubor

9
 import com.ruoyi.common.core.page.TableDataInfo;
9
 import com.ruoyi.common.core.page.TableDataInfo;
10
 import com.ruoyi.common.core.page.TableSupport;
10
 import com.ruoyi.common.core.page.TableSupport;
11
 import com.ruoyi.novel.domain.AuthorApplicationDTO;
11
 import com.ruoyi.novel.domain.AuthorApplicationDTO;
12
+import com.ruoyi.novel.domain.Chapter;
12
 import com.ruoyi.novel.domain.Novel;
13
 import com.ruoyi.novel.domain.Novel;
13
 import com.ruoyi.novel.domain.NovelCategory;
14
 import com.ruoyi.novel.domain.NovelCategory;
14
 import com.ruoyi.novel.mapper.NovelMapper;
15
 import com.ruoyi.novel.mapper.NovelMapper;
21
 import org.springframework.cache.annotation.Cacheable;
22
 import org.springframework.cache.annotation.Cacheable;
22
 import org.springframework.http.ResponseEntity;
23
 import org.springframework.http.ResponseEntity;
23
 import org.springframework.scheduling.annotation.Async;
24
 import org.springframework.scheduling.annotation.Async;
25
+import org.springframework.security.access.prepost.PreAuthorize;
24
 import org.springframework.web.bind.annotation.*;
26
 import org.springframework.web.bind.annotation.*;
25
 
27
 
28
+import java.util.HashMap;
26
 import java.util.List;
29
 import java.util.List;
27
 import java.util.Map;
30
 import java.util.Map;
28
 import java.util.concurrent.CompletableFuture;
31
 import java.util.concurrent.CompletableFuture;
52
 
55
 
53
     @GetMapping("/hot")
56
     @GetMapping("/hot")
54
     public TableDataInfo getHotNovels() {
57
     public TableDataInfo getHotNovels() {
55
-        //startPage();
58
+
56
         List<Novel> hotNovels = novelService.getHotNovels();
59
         List<Novel> hotNovels = novelService.getHotNovels();
57
-        //return novelService.getHotNovels();
58
-        // 返回统一格式 { code, msg, data }
60
+
59
         return getDataTable(hotNovels);
61
         return getDataTable(hotNovels);
60
-        //return (List<Novel>) AjaxResult.success(hotNovels);
62
+
61
     }
63
     }
62
 
64
 
63
     // 获取分类列表
65
     // 获取分类列表
65
     public AjaxResult getCategories() {
67
     public AjaxResult getCategories() {
66
         return AjaxResult.success(novelService.getAllCategories());
68
         return AjaxResult.success(novelService.getAllCategories());
67
     }
69
     }
68
-//    @GetMapping("/novel/categories")
69
-//    public ResponseEntity<?> getCategories() {
70
-//        return ResponseEntity.ok(novelService.getAllCategories());
71
-//    }
72
-    // 获取热门小说
73
-// 获取热门小说
74
-//    @GetMapping("/novel/hot")
75
-//    public ResponseEntity<?> getHotNovels() {
76
-//        return ResponseEntity.ok(novelService.getHotNovels());
77
-//    }
78
-    // 按分类获取小说
79
-// 按分类获取小说
80
-//@GetMapping("/list")
81
-//public TableDataInfo getNovelsByCategory(@RequestParam(required = false) Long categoryId) {
82
-//    return (TableDataInfo) novelService.getNovelsByCategory(categoryId);
83
-//}
70
+
71
+
84
     // 获取小说列表
72
     // 获取小说列表
85
     @GetMapping("/list")
73
     @GetMapping("/list")
86
     public TableDataInfo list(Novel novel) {
74
     public TableDataInfo list(Novel novel) {
115
     }
103
     }
116
 
104
 
117
 
105
 
118
-// 获取小说章节列表
119
-@GetMapping("/{novelId}/chapters")
120
-public AjaxResult getChapters(@PathVariable Long novelId) {
121
-    return AjaxResult.success(novelService.getChaptersByNovelId(novelId));
122
-}
106
+    // 获取小说章节列表
107
+    @GetMapping("/{novelId}/chapters")
108
+    public AjaxResult getChapters(@PathVariable Long novelId) {
109
+        return AjaxResult.success(novelService.getChaptersByNovelId(novelId));
110
+    }
111
+
123
 
112
 
124
-//    @GetMapping("/novel/{novelId}/chapters")
125
-//    public ResponseEntity<?> getChapters(@PathVariable Long novelId) {
126
-//        return ResponseEntity.ok(novelService.getChaptersByNovelId(novelId));
127
-//    }
128
     // 获取章节内容
113
     // 获取章节内容
129
-// 获取章节内容
130
-@GetMapping("/chapter/{chapterId}")
131
-public AjaxResult getChapterContent(@PathVariable Long chapterId) {
132
-    return AjaxResult.success(novelService.getChapterContent(chapterId));
133
-}
134
-//    @GetMapping("/novel/chapter/{chapterId}")
135
-//    public ResponseEntity<?> getChapterContent(@PathVariable Long chapterId) {
136
-//        return ResponseEntity.ok(novelService.getChapterContent(chapterId));
137
-//    }
138
-    // 提交作家申请
139
-//    @PostMapping("/author/apply")
140
-//    public ResponseEntity<?> applyAuthor(@RequestBody AuthorApplicationDTO dto,
141
-//                                         @RequestHeader("Authorization") String token) {
142
-//        Long userId = authService.getUserIdFromToken(token);
143
-//        novelService.submitAuthorApplication(dto, userId);
144
-//        return ResponseEntity.ok("申请已提交");
145
-//    }
114
+    @PreAuthorize("@ss.hasPermi('novel:chapter:query')")
115
+    @GetMapping("/chapter/{chapterId}")
116
+    public AjaxResult getChapterContent(@PathVariable("chapterId") Long chapterId) {
117
+        //return AjaxResult.success(novelService.getChapterContent(chapterId));
118
+
119
+        Chapter chapter = novelService.getChapterContent(chapterId);
120
+        if (chapter == null) {
121
+            return AjaxResult.error("章节不存在");
122
+        }
123
+
124
+        // 构建响应数据
125
+        Map<String, Object> data = new HashMap<>();
126
+        Novel novel = novelService.selectNovelById(chapter.getNovelId());
127
+
128
+        data.put("novelTitle", novel.getTitle() != null ? novel.getTitle() : "");
129
+        data.put("chapterTitle", chapter.getTitle());
130
+        data.put("content", chapter.getContent());
131
+        data.put("chapterIndex", chapter.getChapterOrder());
132
+        data.put("totalChapters", novelService.getChapterCount(chapter.getNovelId()));
133
+
134
+        return AjaxResult.success(data);
135
+    }
146
 
136
 
147
 
137
 
148
     // 提交作家申请
138
     // 提交作家申请
158
         novelService.submitAuthorApplication(dto, userId);
148
         novelService.submitAuthorApplication(dto, userId);
159
         return ResponseEntity.ok().build();
149
         return ResponseEntity.ok().build();
160
     }
150
     }
161
-//    @GetMapping("/list")
162
-//    public TableDataInfo list(Novel novel) {
163
-//        startPage();
164
-//        List<Novel> list = novelService.selectNovelList(novel);
165
-//        return getDataTable(list);
166
-//    }
151
+
167
     @PostMapping("/add")
152
     @PostMapping("/add")
168
     public AjaxResult add(@RequestBody Novel novel) {
153
     public AjaxResult add(@RequestBody Novel novel) {
169
         return toAjax(novelService.insertNovel(novel));
154
         return toAjax(novelService.insertNovel(novel));
201
     }
186
     }
202
     @Async
187
     @Async
203
     public CompletableFuture<List<Novel>> searchNovelsAsync(String keyword, Long categoryId) {
188
     public CompletableFuture<List<Novel>> searchNovelsAsync(String keyword, Long categoryId) {
204
-        return CompletableFuture.completedFuture((List<Novel>) searchNovels(keyword, categoryId));
189
+        return CompletableFuture.completedFuture(searchService.searchNovels(keyword, categoryId));
205
     }
190
     }
206
     @GetMapping("/suggest")
191
     @GetMapping("/suggest")
207
     public List<String> suggestKeywords(@RequestParam String prefix) {
192
     public List<String> suggestKeywords(@RequestParam String prefix) {
230
         //searchService.deleteNovelIndex(id); // 删除索引
215
         //searchService.deleteNovelIndex(id); // 删除索引
231
         return AjaxResult.success();
216
         return AjaxResult.success();
232
     }
217
     }
233
-}
218
+}

+ 4
- 2
RuoYi-Vue/ruoyi-system/src/main/java/com/ruoyi/novel/domain/Chapter.java Zobrazit soubor

1
 package com.ruoyi.novel.domain;
1
 package com.ruoyi.novel.domain;
2
 
2
 
3
 
3
 
4
+import com.ruoyi.novel.service.NovelService;
5
+import com.ruoyi.system.domain.SysOperLog;
4
 import lombok.Data;
6
 import lombok.Data;
7
+import org.springframework.beans.factory.annotation.Autowired;
5
 
8
 
6
 import javax.persistence.*;
9
 import javax.persistence.*;
7
 import java.util.Date;
10
 import java.util.Date;
10
 @Entity
13
 @Entity
11
 @Table(name = "chapter")
14
 @Table(name = "chapter")
12
 public class Chapter {
15
 public class Chapter {
16
+
13
     @Id
17
     @Id
14
     @GeneratedValue(strategy = GenerationType.IDENTITY)
18
     @GeneratedValue(strategy = GenerationType.IDENTITY)
15
     private Long id;
19
     private Long id;
25
     @Column(name = "create_time")
29
     @Column(name = "create_time")
26
     private Date createTime;
30
     private Date createTime;
27
 
31
 
28
-
29
-
30
     // Getters and setters
32
     // Getters and setters
31
 }
33
 }

+ 1
- 0
RuoYi-Vue/ruoyi-system/src/main/java/com/ruoyi/novel/mapper/ChapterMapper.java Zobrazit soubor

10
     int insertChapter(Chapter chapter);
10
     int insertChapter(Chapter chapter);
11
     List<Chapter> selectChapterList(Chapter chapter);
11
     List<Chapter> selectChapterList(Chapter chapter);
12
     Integer selectMaxChapterOrder(Long novelId);
12
     Integer selectMaxChapterOrder(Long novelId);
13
+    int selectChapterCountByNovelId(Long novelId);
13
 }
14
 }

+ 1
- 0
RuoYi-Vue/ruoyi-system/src/main/java/com/ruoyi/novel/service/NovelService.java Zobrazit soubor

74
         assert novelRepository != null : "JPA Repository未注入";
74
         assert novelRepository != null : "JPA Repository未注入";
75
         //assert novelSearchRepository != null : "ES Repository未注入";
75
         //assert novelSearchRepository != null : "ES Repository未注入";
76
     }
76
     }
77
+    int getChapterCount(Long novelId);
77
 }
78
 }

+ 13
- 2
RuoYi-Vue/ruoyi-system/src/main/java/com/ruoyi/novel/service/impl/NovelServiceImpl.java Zobrazit soubor

17
 
17
 
18
 import com.ruoyi.novel.repository.jpa.NovelRepository;
18
 import com.ruoyi.novel.repository.jpa.NovelRepository;
19
 import com.ruoyi.novel.service.*;
19
 import com.ruoyi.novel.service.*;
20
+import com.ruoyi.novel.utils.Base64Utils;
20
 import com.ruoyi.system.mapper.SysUserMapper;
21
 import com.ruoyi.system.mapper.SysUserMapper;
21
 import jdk.jfr.Category;
22
 import jdk.jfr.Category;
22
 import org.slf4j.Logger;
23
 import org.slf4j.Logger;
286
 
287
 
287
     @Override
288
     @Override
288
     public Chapter getChapterContent(Long chapterId) {
289
     public Chapter getChapterContent(Long chapterId) {
289
-        return chapterMapper.selectChapterById(chapterId);
290
+        Chapter chapter = chapterMapper.selectChapterById(chapterId);
291
+        if (chapter != null && chapter.getContent() != null) {
292
+            // 解码Base64内容
293
+            String decodedContent = Base64Utils.decode(chapter.getContent());
294
+            chapter.setContent(decodedContent);
295
+        }
296
+        return chapter;
297
+        //return chapterMapper.selectChapterById(chapterId);
290
     }
298
     }
291
 
299
 
292
     @Override
300
     @Override
379
 //
387
 //
380
 //    }
388
 //    }
381
 
389
 
382
-
390
+    @Override
391
+    public int getChapterCount(Long novelId) {
392
+        return chapterMapper.selectChapterCountByNovelId(novelId);
393
+    }
383
 
394
 
384
     // 其他CRUD方法...
395
     // 其他CRUD方法...
385
 }
396
 }

+ 56
- 0
RuoYi-Vue/ruoyi-system/src/main/java/com/ruoyi/novel/utils/Base64Utils.java Zobrazit soubor

1
+package com.ruoyi.novel.utils;
2
+
3
+
4
+import java.util.Base64;
5
+
6
+/**
7
+ * Base64工具类
8
+ */
9
+public class Base64Utils {
10
+
11
+    /**
12
+     * 解码Base64字符串(兼容URL安全格式)
13
+     * @param base64Str Base64编码的字符串
14
+     * @return 解码后的字符串
15
+     */
16
+    public static String decode(String base64Str) {
17
+        if (base64Str == null || base64Str.isEmpty()) {
18
+            return "";
19
+        }
20
+
21
+        try {
22
+            // 处理URL安全的Base64编码(将'-'替换为'+',将'_'替换为'/')
23
+            String base64 = base64Str.replace('-', '+').replace('_', '/');
24
+
25
+            // 添加必要的填充字符
26
+            int pad = base64.length() % 4;
27
+            if (pad > 0) {
28
+                base64 += "====".substring(pad);
29
+            }
30
+
31
+            // 解码
32
+            byte[] decodedBytes = Base64.getDecoder().decode(base64);
33
+            return new String(decodedBytes, "UTF-8");
34
+        } catch (Exception e) {
35
+            // 解码失败,返回原始字符串或空字符串
36
+            return "内容解码失败: " + e.getMessage();
37
+        }
38
+    }
39
+
40
+    /**
41
+     * 编码字符串为Base64
42
+     * @param str 要编码的字符串
43
+     * @return Base64编码的字符串
44
+     */
45
+    public static String encode(String str) {
46
+        if (str == null || str.isEmpty()) {
47
+            return "";
48
+        }
49
+
50
+        try {
51
+            return Base64.getEncoder().encodeToString(str.getBytes("UTF-8"));
52
+        } catch (Exception e) {
53
+            return "";
54
+        }
55
+    }
56
+}

+ 3
- 0
RuoYi-Vue/ruoyi-system/src/main/resources/mapper/novel/ChapterMapper.xml Zobrazit soubor

22
         FROM novel_category
22
         FROM novel_category
23
         WHERE id = #{categoryId}
23
         WHERE id = #{categoryId}
24
     </select>
24
     </select>
25
+    <select id="selectChapterCountByNovelId" parameterType="Long" resultType="int">
26
+        SELECT COUNT(*) FROM novel_chapter WHERE novel_id = #{novelId}
27
+    </select>
25
 </mapper>
28
 </mapper>

Načítá se…
Zrušit
Uložit