fzzj 8 månader sedan
förälder
incheckning
7829cb733e
1 ändrade filer med 299 tillägg och 108 borttagningar
  1. 299
    108
      RuoYi-App/pages/novel/list.vue

+ 299
- 108
RuoYi-App/pages/novel/list.vue Visa fil

@@ -25,59 +25,61 @@
25 25
       </scroll-view>
26 26
       
27 27
       <!-- 空状态 -->
28
-      <view v-if="novels.length === 0 && !error" class="empty-container">
28
+      <view v-if="showEmptyState" class="empty-container">
29 29
         <image src="/static/empty-book.png" class="empty-img" />
30 30
         <text class="empty-text">暂无小说数据</text>
31
-		<button class="btn-refresh" @click="reloadData">重新加载</button>
31
+        <button class="btn-refresh" @click="reloadData">重新加载</button>
32 32
       </view>
33 33
       
34 34
       <!-- 错误状态 -->
35
-      <view v-if="error" class="error-container">
35
+      <view v-if="error && !showNovels" class="error-container">
36 36
         <uni-icons type="info" size="48" color="var(--error-color)" />
37 37
         <text class="error-text">{{ error }}</text>
38 38
         <button class="btn-refresh" @click="loadNovels">重新加载</button>
39 39
       </view>
40 40
       
41 41
       <!-- 热门推荐 -->
42
-      <view v-if="hotNovels.length > 0" class="section">
43
-    <!-- ... -->
44
-  </view>
45
-  <view v-else class="section">
42
+      <view v-if="showHotNovels" class="section">
46 43
         <view class="section-header">
47
-      <text class="section-title">热门推荐(演示数据)</text>
44
+          <text class="section-title">热门推荐</text>
48 45
           <text class="section-more" @click="goMore">更多 ></text>
49 46
         </view>
50 47
         <scroll-view scroll-x class="hot-list">
51 48
           <view 
52
-        v-for="novel in demoHotNovels" 
49
+            v-for="novel in displayedHotNovels" 
53 50
             :key="novel.id" 
54 51
             class="hot-item"
55 52
             @click="openNovel(novel)"
56 53
           >
57
-        <image :src="novel.cover" class="hot-cover" />
54
+            <image 
55
+              :src="getCoverUrl(novel.coverImg)" 
56
+              class="hot-cover"
57
+              @error="handleImageError"
58
+            />
58 59
             <text class="hot-title">{{ novel.title }}</text>
59 60
           </view>
60 61
         </scroll-view>
61 62
       </view>
62 63
       
63
-      <!-- 分类小说列表 -->
64
-      <view v-if="novels.length > 0" class="section">
65
-    <!-- ... -->
66
-  </view>
67
-  <view v-else class="section">
64
+      <!-- 小说列表 -->
65
+      <view v-if="showNovels" class="section">
68 66
         <view class="section-header">
69
-      <text class="section-title">小说列表(演示数据)</text>
67
+          <text class="section-title">小说列表</text>
70 68
         </view>
71 69
         <view class="novel-grid">
72 70
           <view 
73
-        v-for="novel in demoNovels" 
74
-        :key="novel.id" 
71
+            v-for="novel in displayedNovels" 
72
+            :key="novel.id" 
75 73
             class="novel-item"
76 74
             @click="openNovel(novel)"
77 75
           >
78
-        <image :src="novel.cover" class="novel-cover" />
79
-        <text class="novel-title">{{ novel.title }}</text>
80
-        <text class="novel-author">{{ novel.author }}</text>
76
+            <image 
77
+              :src="getCoverUrl(novel.coverImg)" 
78
+              class="novel-cover"
79
+              @error="handleImageError"
80
+            />
81
+            <text class="novel-title">{{ novel.title }}</text>
82
+            <text class="novel-author">{{ novel.author || '未知作者' }}</text>
81 83
           </view>
82 84
         </view>
83 85
       </view>
@@ -94,9 +96,7 @@
94 96
 </template>
95 97
 
96 98
 <script>
97
-// 确保路径正确
98 99
 import AdBanner from '@/components/AdBanner';
99
-import urls from '@/api/urls';
100 100
 
101 101
 export default {
102 102
   components: { AdBanner },
@@ -114,49 +114,66 @@ export default {
114 114
       
115 115
       // 临时解决方案 - 模拟数据
116 116
       mockHotNovels: [
117
-        { id: 1, title: '总裁的替身前妻', cover: '/static/demo-cover1.jpg' },
118
-        { id: 2, title: '神医毒妃', cover: '/static/demo-cover2.jpg' },
119
-        { id: 3, title: '穿越之王妃逆袭', cover: '/static/demo-cover3.jpg' }
117
+        { id: 1, title: '总裁的替身前妻', coverImg: '/static/demo-cover1.jpg', author: '言情作家' },
118
+        { id: 2, title: '神医毒妃', coverImg: '/static/demo-cover2.jpg', author: '古风大神' },
119
+        { id: 3, title: '穿越之王妃逆袭', coverImg: '/static/demo-cover3.jpg', author: '穿越达人' }
120 120
       ],
121 121
       mockNovels: [
122
-        { id: 4, title: '都市最强赘婿', author: '网络作家', cover: '/static/demo-cover4.jpg' },
123
-        { id: 5, title: '修仙归来', author: '仙侠迷', cover: '/static/demo-cover5.jpg' },
124
-        { id: 6, title: '校园纯爱物语', author: '青春校园', cover: '/static/demo-cover6.jpg' }
125
-      ]
122
+        { id: 4, title: '都市最强赘婿', author: '网络作家', coverImg: '/static/demo-cover4.jpg' },
123
+        { id: 5, title: '修仙归来', author: '仙侠迷', coverImg: '/static/demo-cover5.jpg' },
124
+        { id: 6, title: '校园纯爱物语', author: '青春校园', coverImg: '/static/demo-cover6.jpg' }
125
+      ],
126
+      // 添加分页参数
127
+      page: 1,
128
+      pageSize: 10,
129
+      total: 0,
130
+      hasMore: true
131
+    }
132
+  },
133
+  computed: {
134
+    // 添加计算属性控制显示逻辑
135
+    showEmptyState() {
136
+      return this.novels.length === 0 && !this.error && !this.loading;
137
+    },
138
+    showHotNovels() {
139
+      return this.hotNovels.length > 0 && !this.error;
140
+    },
141
+    showNovels() {
142
+      return this.novels.length > 0 && !this.error;
143
+    },
144
+    displayedHotNovels() {
145
+      return this.hotNovels.length > 0 ? this.hotNovels : this.mockHotNovels;
146
+    },
147
+    displayedNovels() {
148
+      return this.novels.length > 0 ? this.novels : this.mockNovels;
126 149
     }
127 150
   },
128 151
   mounted() {
129
-    console.log('$http available in component:', typeof this.$http.get === 'function');
130
-    
131
-    // 确保所有方法都已绑定
132
-    console.log('loadCategories:', typeof this.loadCategories);
133
-    console.log('loadHotNovels:', typeof this.loadHotNovels);
134
-    console.log('loadNovels:', typeof this.loadNovels);
135
-    console.log('loadAds:', typeof this.loadAds);
136
-    
137 152
     this.initData();
138 153
   },
139 154
   methods: {
140 155
     async initData() {
141
-      // 确保所有方法都已绑定
142
-      if (
143
-        typeof this.loadCategories !== 'function' ||
144
-        typeof this.loadHotNovels !== 'function' ||
145
-        typeof this.loadNovels !== 'function' ||
146
-        typeof this.loadAds !== 'function'
147
-      ) {
148
-        console.error('一个或多个方法未绑定!');
149
-        this.error = '系统初始化失败,请刷新页面';
150
-        this.loading = false;
151
-        return;
152
-      }
153
-      
154 156
       try {
155
-        // 顺序加载数据
156
-        await this.loadCategories();
157
-        await this.loadHotNovels();
158
-        await this.loadNovels();
159
-        await this.loadAds();
157
+        // 确保所有方法都已绑定
158
+        if (
159
+          typeof this.loadCategories !== 'function' ||
160
+          typeof this.loadHotNovels !== 'function' ||
161
+          typeof this.loadNovels !== 'function' ||
162
+          typeof this.loadAds !== 'function'
163
+        ) {
164
+          console.error('一个或多个方法未绑定!');
165
+          this.error = '系统初始化失败,请刷新页面';
166
+          this.loading = false;
167
+          return;
168
+        }
169
+        
170
+        // 并行加载数据以提高性能
171
+        await Promise.all([
172
+          this.loadCategories(),
173
+          this.loadHotNovels(),
174
+          this.loadNovels(),
175
+          this.loadAds()
176
+        ]);
160 177
       } catch (e) {
161 178
         console.error('初始化失败', e);
162 179
         this.error = '初始化失败,请检查网络连接';
@@ -164,33 +181,63 @@ export default {
164 181
         this.loading = false;
165 182
       }
166 183
     },
167
-        // 加载分类
184
+
185
+    // 修复API请求逻辑
168 186
     async loadCategories() {
169 187
       try {
170
-    // 使用正确的参数格式
171
-    const res = await this.$http.get('/category/tree', {
172
-      header: { isToken: false }
173
-              });
174
-        this.categories = res && res.data ? [{ id: 0, name: '全部' }, ...res.data] : [];
188
+        const res = await this.$http.get('/category/tree', {
189
+          header: { isToken: false }
190
+        });
191
+        
192
+        console.log('分类API响应:', res);
193
+        
194
+        // 确保正确解析API响应
195
+        if (res && res.data && res.data.code === 200 && Array.isArray(res.data.data)) {
196
+          // 提取所有子分类(第二级分类)
197
+          let subCategories = [];
198
+          res.data.data.forEach(parent => {
199
+            if (parent.children && Array.isArray(parent.children)) {
200
+              subCategories = subCategories.concat(parent.children);
201
+            }
202
+          });
203
+          
204
+          // 添加"全部"分类
205
+          this.categories = [
206
+            { id: 0, name: '全部' }, 
207
+            ...subCategories
208
+          ];
209
+        } else {
210
+          console.warn('分类数据结构异常,使用默认分类');
211
+          this.categories = [{ id: 0, name: '全部' }];
212
+        }
175 213
       } catch (e) {
176 214
         console.error('加载分类失败', e);
177 215
         this.categories = [{ id: 0, name: '全部' }];
178 216
       }
179 217
     },
180
-    // 加载热门小说
218
+
181 219
     async loadHotNovels() {
182 220
       try {
183
-      // 公开API,不需要认证
184
-    const res = await this.$http.get('/novel/hot', {
185
-      header: { isToken: false }
186
-      });
187
-        // 处理响应
188
-        if (res && res.data && res.data.rows) {
189
-          this.hotNovels = res.data.rows;
190
-        } else if (res && res.data && Array.isArray(res.data)) {
191
-          this.hotNovels = res.data;
192
-        } else {
193
-          console.warn('热门小说API返回格式未知', res);
221
+        const res = await this.$http.get('/novel/hot', {
222
+          header: { isToken: false }
223
+        });
224
+        
225
+        console.log('热门小说API响应:', res);
226
+        
227
+        // 处理不同API响应格式
228
+        if (res && res.data) {
229
+          if (res.data.code === 200 && Array.isArray(res.data.rows)) {
230
+            this.hotNovels = res.data.rows;
231
+          } else if (Array.isArray(res.data)) {
232
+            this.hotNovels = res.data;
233
+          } else {
234
+            console.error('热门小说数据结构异常');
235
+          }
236
+        }
237
+        
238
+        // 只在真实数据不可用时使用模拟数据
239
+        if (!this.hotNovels || this.hotNovels.length === 0) {
240
+          console.warn('使用模拟热门小说数据');
194 241
           this.hotNovels = this.mockHotNovels;
195 242
         }
196 243
       } catch (e) {
@@ -198,66 +245,119 @@ export default {
198 245
         this.hotNovels = this.mockHotNovels;
199 246
       }
200 247
     },
201
-    // 加载小说列表
202
-    async loadNovels() {
248
+
249
+    async loadNovels(reset = false) {
250
+      if (reset) {
251
+        this.page = 1;
252
+        this.hasMore = true;
253
+        this.novels = [];
254
+      }
255
+      
256
+      if (!this.hasMore) return;
257
+      
203 258
       this.loading = true;
204 259
       this.error = null;
260
+      
205 261
       try {
206
-        // 使用正确的请求方式
207
-        //const res = await this.$http.get('/novel/list');
208
-    const res = await this.$http.get('/novel/list', {
209
-      header: { isToken: false }
262
+        const res = await this.$http.get('/novel/list', {
263
+          params: {
264
+            page: this.page,
265
+            pageSize: this.pageSize,
266
+            categoryId: this.activeCategory
267
+          },
268
+          header: { isToken: false }
210 269
         });
211 270
         
212
-        // 修复可选链操作符问题
213
-        if (res && res.data && res.data.rows) {
214
-          this.novels = res.data.rows;
215
-        } else if (res && res.data && Array.isArray(res.data)) {
216
-          this.novels = res.data;
217
-        } else {
218
-          console.warn('小说列表API返回格式未知', res);
219
-          this.novels = this.mockNovels;
271
+        console.log('小说列表API响应:', res);
272
+        
273
+        let newNovels = [];
274
+        let total = 0;
275
+        
276
+        // 处理不同API响应格式
277
+        if (res && res.data) {
278
+          if (res.data.code === 200 && Array.isArray(res.data.rows)) {
279
+            newNovels = res.data.rows;
280
+            total = res.data.total;
281
+          } 
282
+          else if (Array.isArray(res.data)) {
283
+            newNovels = res.data;
284
+            total = res.data.length;
285
+          }
286
+        }
287
+        
288
+        if (newNovels.length > 0) {
289
+          this.novels = [...this.novels, ...newNovels];
290
+          this.hasMore = this.novels.length < total;
291
+          this.page++;
292
+        } else if (this.page === 1) {
293
+          console.warn('小说列表为空');
294
+          this.novels = [];
220 295
         }
221 296
       } catch (e) {
222 297
         console.error('加载小说失败', e);
223 298
         this.error = '加载失败,请重试';
224
-        this.novels = this.mockNovels;
299
+        if (this.novels.length === 0) {
300
+          this.novels = this.mockNovels;
301
+        }
225 302
       } finally {
226 303
         this.loading = false;
227 304
       }
228 305
     },
229
-    
230
-    // 加载广告
306
+
231 307
     async loadAds() {
232 308
       try {
309
+        // 使用Promise.all并行请求
233 310
         const [topRes, bottomRes] = await Promise.all([
234
-      this.$http.get('/ad/position?code=TOP_BANNER', {
235
-        header: { isToken: false }
236
-      }),
237
-      this.$http.get('/ad/position?code=BOTTOM_BANNER', {
238
-        header: { isToken: false }
239
-      })
311
+          this.$http.get('/ad/position?code=TOP_BANNER', {
312
+            header: { isToken: false }
313
+          }),
314
+          this.$http.get('/ad/position?code=BOTTOM_BANNER', {
315
+            header: { isToken: false }
316
+          })
240 317
         ]);
241 318
         
242
-        this.topAds = topRes && topRes.data ? topRes.data : [];
243
-        this.bottomAds = bottomRes && bottomRes.data ? bottomRes.data : [];
319
+        // 处理广告响应
320
+        this.topAds = this.parseAdResponse(topRes);
321
+        this.bottomAds = this.parseAdResponse(bottomRes);
244 322
       } catch (e) {
245 323
         console.error('加载广告失败', e);
246 324
       }
247 325
     },
248
-    // 封面URL处理
326
+    
327
+    // 新增:广告响应解析方法
328
+    parseAdResponse(response) {
329
+      if (!response || !response.data) return [];
330
+      
331
+      if (response.data.code === 200 && Array.isArray(response.data.data)) {
332
+        return response.data.data;
333
+      } else if (Array.isArray(response.data)) {
334
+        return response.data;
335
+      } else if (Array.isArray(response.data.rows)) {
336
+        return response.data.rows;
337
+      }
338
+      
339
+      return [];
340
+    },
341
+
342
+    // 修复封面URL处理
249 343
     getCoverUrl(cover) {
250 344
       if (!cover) return '/static/default-cover.jpg';
251 345
       if (cover.startsWith('http')) return cover;
252 346
       if (cover.startsWith('/')) return cover;
253
-      return `${process.env.VUE_APP_BASE_URL || ''}/uploads/${cover}`;
347
+      return `${process.env.VUE_APP_BASE_URL || ''}${cover}`;
348
+    },
349
+    
350
+    // 添加图片错误处理
351
+    handleImageError(event) {
352
+      event.target.src = '/static/default-cover.jpg';
254 353
     },
255 354
     
256 355
     // 其他方法...
257 356
     changeCategory(categoryId) {
258 357
       this.activeCategory = categoryId;
259
-      this.currentCategoryName = this.categories.find(c => c.id === categoryId)?.name || '全部';
260
-      this.loadNovels();
358
+      const category = this.categories.find(c => c.id === categoryId);
359
+      this.currentCategoryName = category ? category.name : '全部';
360
+      this.loadNovels(true); // 重置并加载新分类
261 361
     },
262 362
     
263 363
     openNovel(novel) {
@@ -287,9 +387,15 @@ export default {
287 387
         url: '/pages/novel/more'
288 388
       });
289 389
     },
290
-        // 确保重新加载按钮绑定到正确的方法
390
+    
391
+    // 确保重新加载按钮绑定到正确的方法
291 392
     reloadData() {
292 393
       this.initData();
394
+    },
395
+    
396
+    // 添加上拉加载事件
397
+    onReachBottom() {
398
+      this.loadNovels();
293 399
     }
294 400
   }
295 401
 }
@@ -358,16 +464,18 @@ export default {
358 464
 }
359 465
 
360 466
 .novel-item {
361
-  background-color: var(--card-bg);
467
+  background-color: white;
362 468
   border-radius: 12rpx;
363 469
   overflow: hidden;
364
-  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
470
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
365 471
 }
366 472
 
367
-.novel-cover {
473
+/* 确保封面有默认背景和固定比例 */
474
+.novel-cover, .hot-cover {
368 475
   width: 100%;
369 476
   height: 300rpx;
370
-  background-color: #f5f5f5; /* 添加默认背景 */
477
+  background-color: #f5f5f5;
478
+  object-fit: cover;
371 479
 }
372 480
 
373 481
 .novel-title {
@@ -385,4 +493,87 @@ export default {
385 493
   color: #888;
386 494
   padding: 0 15rpx 15rpx;
387 495
 }
496
+
497
+/* 热门列表样式 */
498
+.hot-list {
499
+  display: flex;
500
+  padding: 20rpx;
501
+  overflow-x: auto;
502
+  white-space: nowrap;
503
+}
504
+
505
+.hot-item {
506
+  display: inline-block;
507
+  width: 200rpx;
508
+  margin-right: 30rpx;
509
+}
510
+
511
+.hot-cover {
512
+  width: 200rpx;
513
+  height: 280rpx;
514
+  border-radius: 8rpx;
515
+}
516
+
517
+.hot-title {
518
+  display: block;
519
+  margin-top: 10rpx;
520
+  font-size: 26rpx;
521
+  white-space: nowrap;
522
+  overflow: hidden;
523
+  text-overflow: ellipsis;
524
+}
525
+
526
+/* 分类导航样式 */
527
+.category-nav {
528
+  white-space: nowrap;
529
+  padding: 20rpx 0;
530
+  background-color: #fff;
531
+  border-bottom: 1rpx solid #eee;
532
+}
533
+
534
+.category-item {
535
+  display: inline-block;
536
+  padding: 10rpx 30rpx;
537
+  margin: 0 10rpx;
538
+  border-radius: 30rpx;
539
+  font-size: 28rpx;
540
+}
541
+
542
+.category-item.active {
543
+  background-color: var(--primary-color);
544
+  color: white;
545
+}
546
+
547
+/* 章节标题样式 */
548
+.section {
549
+  margin-top: 30rpx;
550
+}
551
+
552
+.section-header {
553
+  display: flex;
554
+  justify-content: space-between;
555
+  align-items: center;
556
+  padding: 0 20rpx 20rpx;
557
+}
558
+
559
+.section-title {
560
+  font-size: 32rpx;
561
+  font-weight: bold;
562
+}
563
+
564
+.section-more {
565
+  font-size: 26rpx;
566
+  color: var(--text-secondary-color);
567
+}
568
+
569
+/* 成为作家按钮样式 */
570
+.become-author {
571
+  margin: 40rpx 20rpx;
572
+  padding: 20rpx;
573
+  text-align: center;
574
+  background-color: var(--primary-color);
575
+  color: white;
576
+  border-radius: 10rpx;
577
+  font-size: 30rpx;
578
+}
388 579
 </style>

Laddar…
Avbryt
Spara