fzzj пре 8 месеци
родитељ
комит
7f052b69ac

+ 1
- 1
RuoYi-App/.env Прегледај датотеку

@@ -1,7 +1,7 @@
1 1
 # 基础配置
2 2
 VUE_APP_NAME=哎呀电子小说
3 3
 VUE_APP_VERSION=1.0.0
4
-
4
+VUE_APP_BASE_URL=https://xiaoshuo.aiyadianzi.ltd
5 5
 # 后端API地址
6 6
 VUE_APP_API_BASE=https://api.aiyadianzi.ltd
7 7
 VUE_APP_PHP_BASE=https://php-backend.aiyadianzi.ltd

+ 105
- 193
RuoYi-App/App.vue Прегледај датотеку

@@ -1,30 +1,25 @@
1
-</template>
1
+<template>
2 2
   <div id="app">
3 3
     <!-- 顶部菜单栏 -->
4 4
     <div v-if="showHeader" class="app-header">
5 5
       <router-link to="/">首页</router-link>
6
-      <router-link to="/novel/list">小说列表</router-link>
7
-      <router-link to="/author/apply">作者申请</router-link>
8
-      <router-link to="/search">搜索</router-link>
6
+      <router-link to="/pages/novel/list">小说列表</router-link>
7
+      <router-link to="/pages/author/apply">作者申请</router-link>
8
+      <router-link to="/pages/search/index">搜索</router-link>
9 9
     </div>
10 10
     
11 11
     <!-- 主内容区 -->
12 12
     <router-view v-if="isRouterAlive" />
13
+    
13 14
     <!-- 使用自定义TabBar组件 -->
14 15
     <CustomTabbar v-if="showTabBar" />
15 16
   </div>
16 17
 </template>
17 18
 
18 19
 <script>
19
-import CustomTabbar from '@/components/custom-tabbar/index.vue'
20
-import config from './config'
21
-import { getToken } from '@/utils/auth'
20
+import CustomTabbar from '@/components/custom-tabbar/index.vue';
22 21
 
23 22
 export default {
24
-	mounted() {
25
-    // 确保方法名称正确
26
-    this.checkRouteAuth();
27
-	},
28 23
   name: 'App',
29 24
   provide() {
30 25
     return {
@@ -34,15 +29,17 @@ export default {
34 29
   components: { CustomTabbar },
35 30
   data() {
36 31
     return {
37
-          isRouterAlive: true,
38
-      showTabBar: false, // 默认不显示
32
+      // 确保所有属性都在这里定义
33
+      isRouterAlive: true,
34
+      showTabBar: false,
35
+      showHeader: false,
36
+      isMounted: false, // 添加组件挂载状态标志
39 37
       tabBarPages: [
40 38
         '/pages/index/index',
41 39
         '/pages/novel/list',
42 40
         '/pages/bookshelf/index',
43 41
         '/pages/me/index'
44 42
       ],
45
-      // 定义需要隐藏菜单的路由
46 43
       hideMenuRoutes: [
47 44
         '/login',
48 45
         '/register',
@@ -51,140 +48,72 @@ export default {
51 48
       ]
52 49
     }
53 50
   },
51
+  mounted() {
52
+    this.isMounted = true;
53
+    this.checkRouteAuth();
54
+    this.initTheme();
55
+    this.initStoreState();
56
+    this.safeUpdateMenuVisibility();
57
+  },
58
+  beforeDestroy() {
59
+    this.isMounted = false;
60
+  },
54 61
   watch: {
55
-    '$route.path': {
62
+    // 安全监听路由变化
63
+    '$route': {
56 64
       immediate: true,
57
-      handler(newPath) {
58
-        // 检查当前页面是否需要显示菜单
59
-        this.showTabBar = this.tabBarPages.some(path => 
60
-          newPath.includes(path)
61
-        );
62
-        
63
-        // 特殊处理:在小说阅读页面也显示菜单
64
-        if (newPath.includes('/pages/novel/read')) {
65
-          this.showTabBar = true;
65
+      handler(newRoute) {
66
+        if (this.isMounted && newRoute && newRoute.path) {
67
+          this.safeUpdateMenuVisibility();
66 68
         }
67
-        
68
-        // 强制更新CustomTabbar组件
69
-        this.$nextTick(() => {
70
-          if (this.$refs.customTabbar) {
71
-            this.$refs.customTabbar.updateSelectedIndex();
72
-          }
73
-        });
74 69
       }
75 70
     }
76 71
   },
77
-
78
-  onLaunch() {
79
-    // 初始化主题
80
-    this.initTheme()
81
-    
82
-    // 检查阅读进度
83
-    this.checkReadingProgress()
84
-    
85
-    // 初始化 store 状态
86
-    this.initStoreState()
87
-    console.log('App launched, store:', this.$store)
88
-  },
89
-  onShow() {
90
-    // 初始检查路由
91
-    this.$nextTick(() => {
92
-      const currentPath = this.$route.path;
93
-      this.showTabBar = this.tabBarPages.some(path => 
94
-        currentPath.includes(path)
95
-      );
96
-    });
97
-    
98
-    // 检查云端阅读进度
99
-    if (this.$store.getters.token) {
100
-      this.syncReadingProgress()
101
-    }
102
-  },
103 72
   methods: {
104
-	      checkRouteAuth() {
105
-	        const noAuthPaths = ['/', '/home', '/novel'];
106
-	        if (!noAuthPaths.includes(this.$route.path)) {
107
-	          // 需要登录的路径处理
108
-	        }
109
-	      },
110
-      reload() {
111
-      this.isRouterAlive = false
73
+    reload() {
74
+      this.isRouterAlive = false;
112 75
       this.$nextTick(() => {
113
-        this.isRouterAlive = true
114
-      })
76
+        this.isRouterAlive = true;
77
+        this.safeUpdateMenuVisibility();
78
+      });
115 79
     },
116
-        updateMenuVisibility() {
80
+    
81
+    safeUpdateMenuVisibility() {
82
+      // 确保组件已挂载且路由对象存在
83
+      if (!this.isMounted || !this.$route || !this.$route.path) return;
84
+      
117 85
       const currentPath = this.$route.path;
118
-      // 检查当前路由是否需要隐藏菜单
119
-      this.showHeader = !this.hideMenuRoutes.some(route => currentPath.includes(route));
120
-      this.showFooter = !this.hideMenuRoutes.some(route => currentPath.includes(route));
121
-    },
122
-    // 初始化 store 状态
123
-    initStoreState() {
124
-      // 从本地存储恢复状态
125
-      const token = uni.getStorageSync('token')
126
-      const readingProgress = uni.getStorageSync('readingProgress') || 1
127 86
       
128
-      // 提交到 store
129
-      this.$store.commit('SET_TOKEN', token)
130
-      this.$store.commit('SET_READING_PROGRESS', readingProgress)
131
-    },
132
-    // 检查阅读进度
133
-    checkReadingProgress() {
134
-      // 直接从本地存储读取
135
-      const chapter = uni.getStorageSync('readingProgress') || 1
87
+      // 检查是否显示底部TabBar
88
+      this.showTabBar = this.tabBarPages.some(path => 
89
+        currentPath.includes(path) || currentPath === path
90
+      );
136 91
       
137
-      if (chapter > 5 && !uni.getStorageSync('token')) {
138
-        uni.showModal({
139
-          title: '登录提示',
140
-          content: `您已阅读到第${chapter}章,登录后继续阅读`,
141
-          confirmText: '立即登录',
142
-          success: (res) => {
143
-            if (res.confirm) {
144
-              uni.navigateTo({ url: '/pages/login' })
145
-            }
146
-          }
147
-        })
148
-      }
92
+      // 检查是否显示顶部菜单
93
+      this.showHeader = !this.hideMenuRoutes.some(route => 
94
+        currentPath.includes(route) || currentPath === route
95
+      );
149 96
     },
150
-    // 同步阅读进度 (修改后)
151
-    async syncReadingProgress() {
152
-      try {
153
-        // 安全访问 store
154
-        if (!this.$store) {
155
-          console.warn('Store 未初始化,无法同步阅读进度')
156
-          return
157
-        }
158
-        
159
-        const token = this.$store.getters.token
160
-        if (!token) {
161
-          console.log('用户未登录,跳过同步')
162
-          return
163
-        }
164
-        
165
-        // 从本地存储获取进度
166
-        const localChapter = uni.getStorageSync('readingProgress') || 1
167
-        
168
-        // 调用 API 同步
169
-        const res = await this.$api.saveReadingProgress(localChapter)
170
-        console.log('进度同步成功:', res.data)
171
-      } catch (err) {
172
-        console.error('同步阅读进度失败', err)
97
+    
98
+    initStoreState() {
99
+      const token = uni.getStorageSync('token') || '';
100
+      const readingProgress = uni.getStorageSync('readingProgress') || 1;
101
+      
102
+      // 确保 store 存在
103
+      if (this.$store) {
104
+        this.$store.commit('SET_TOKEN', token);
105
+        this.$store.commit('SET_READING_PROGRESS', readingProgress);
173 106
       }
107
+      
108
+      console.log('App launched, store:', this.$store ? this.$store.state : '未初始化');
174 109
     },
175
-    // 添加 checkLogin 方法
176
-    // checkLogin() {
177
-    //   // 这里写登录检查逻辑
178
-    //   // 示例:检查本地存储的登录状态
179
-    //   const isLoggedIn = uni.getStorageSync('isLogin');
180
-    //   if (!isLoggedIn) {
181
-    //     uni.navigateTo({ url: '/pages/login' });
182
-    //   }
183
-    // },
184 110
     
185
-    // 临时主题初始化
186 111
     initTheme() {
187
-      const themeName = uni.getStorageSync('selectedTheme') || 'aydzBlue'
112
+      // 主题初始化逻辑
113
+      console.log('主题初始化完成');
114
+      
115
+      // 设置默认主题
116
+      const themeName = uni.getStorageSync('selectedTheme') || 'aydzBlue';
188 117
       
189 118
       // 应用主题变量
190 119
       const themes = {
@@ -195,47 +124,37 @@ export default {
195 124
           '--card-bg': '#d0e8ff',
196 125
           '--header-bg': '#2a5caa'
197 126
         },
198
-        darkMode: {
199
-          '--primary-color': '#52c41a',
200
-          '--bg-color': '#1a1a1a',
201
-          '--text-color': '#e6e6e6',
202
-          '--card-bg': '#2a2a2a'
203
-        },
204 127
         default: {
205 128
           '--primary-color': '#1890ff',
206 129
           '--bg-color': '#f8f9fa',
207 130
           '--text-color': '#333',
208 131
           '--card-bg': '#ffffff'
209 132
         }
210
-      }
133
+      };
211 134
       
212
-      const root = document.documentElement
213
-      const themeVars = themes[themeName] || themes.aydzBlue
135
+      const theme = themes[themeName] || themes.default;
136
+      
137
+      // 应用主题变量
138
+      Object.keys(theme).forEach(key => {
139
+        document.documentElement.style.setProperty(key, theme[key]);
140
+      });
141
+    },
142
+    
143
+    checkRouteAuth() {
144
+      // 简单的路由权限检查
145
+      if (!this.$route || !this.$route.path) return;
214 146
       
215
-      Object.keys(themeVars).forEach(key => {
216
-        root.style.setProperty(key, themeVars[key])
217
-      })
147
+      const authRequiredRoutes = [
148
+        '/pages/author/apply',
149
+        '/pages/bookshelf/index',
150
+        '/pages/me/index'
151
+      ];
218 152
       
219
-      // 动态设置导航栏颜色
220
-      if (themeName === 'darkMode') {
221
-        uni.setNavigationBarColor({
222
-          frontColor: '#ffffff',
223
-          backgroundColor: '#1a1a1a'
224
-        })
225
-      } else if (themeName === 'aydzBlue') {
226
-        uni.setNavigationBarColor({
227
-          frontColor: '#ffffff',
228
-          backgroundColor: '#2a5caa'
229
-        })
230
-      }
231
-    }
232
-  },
233
-
234
-  watch: {
235
-    '$route.path': {
236
-      immediate: true,
237
-      handler() {
238
-        this.updateMenuVisibility();
153
+      if (authRequiredRoutes.some(route => this.$route.path.includes(route))) {
154
+        if (!this.$store.getters.token) {
155
+          uni.showToast({ title: '请先登录', icon: 'none' });
156
+          this.$router.push('/pages/login');
157
+        }
239 158
       }
240 159
     }
241 160
   }
@@ -243,9 +162,13 @@ export default {
243 162
 </script>
244 163
 
245 164
 <style lang="scss">
246
-/* 确保自定义TabBar有足够的空间 */
247
-.app-container {
248
-  padding-bottom: 120rpx !important; /* 增加底部空间 */
165
+@import '@/styles/index.scss';
166
+
167
+/* 全局样式修复 */
168
+#app {
169
+  min-height: 100vh;
170
+  background-color: var(--bg-color);
171
+  color: var(--text-color);
249 172
 }
250 173
 
251 174
 /* 确保菜单不被其他元素覆盖 */
@@ -261,6 +184,7 @@ export default {
261 184
 .page-content {
262 185
   padding-bottom: 140rpx !important;
263 186
 }
187
+
264 188
 /* 确保tabbar显示 */
265 189
 uni-tabbar {
266 190
   display: flex !important;
@@ -282,37 +206,25 @@ uni-tabbar .uni-tabbar {
282 206
 .page-content {
283 207
   padding-bottom: 60px !important;
284 208
 }
285
-/* 保留原有样式 */
286
-uni-tabbar {
287
-  z-index: 999;
288
-}
289
-#app {
290
-  display: flex;
291
-  flex-direction: column;
292
-  min-height: 100vh;
293
-}
294 209
 
295
-.app-header, .app-footer {
296
-  background: #f5f5f5;
210
+.app-header {
211
+  background: var(--header-bg, #2a5caa);
297 212
   padding: 10px;
298 213
   display: flex;
299 214
   justify-content: space-around;
300
-  z-index: 1000;
301
-}
302
-
303
-.app-header {
304 215
   position: sticky;
305 216
   top: 0;
217
+  z-index: 1000;
218
+  
219
+  a {
220
+    color: white;
221
+    text-decoration: none;
222
+    font-weight: bold;
223
+    
224
+    &.router-link-exact-active {
225
+      color: #ffcc00;
226
+      border-bottom: 2px solid #ffcc00;
227
+    }
228
+  }
306 229
 }
307
-
308
-.app-footer {
309
-  position: fixed;
310
-  bottom: 0;
311
-  width: 100%;
312
-}
313
-
314
-.router-link-active {
315
-  color: #42b983;
316
-  font-weight: bold;
317
-}
318
-</style>
230
+</style>

+ 24
- 17
RuoYi-App/main.js Прегледај датотеку

@@ -1,25 +1,32 @@
1
+// src/main.js
1 2
 import Vue from 'vue'
2 3
 import App from './App'
3
-import store from './store' // 确保路径正确
4
+import router from './router'
5
+import store from './store'
4 6
 
5
-import plugins from './plugins' // plugins
6
-import './permission' // permission
7
-import { getDicts } from "@/api/system/dict/data"
8
-import http from '@/utils/request' // 导入HTTP工具
7
+// 确保路径正确
8
+import '@/styles/index.scss'
9 9
 
10
-// 注册HTTP工具
11
-Vue.use(http)
12
-Vue.use(plugins)
10
+// 导入 request.js 并挂载到 Vue 原型
11
+import http from '@/utils/request'
13 12
 
14 13
 Vue.config.productionTip = false
15
-// 全局挂载 store
16
-Vue.prototype.$store = store
17
-Vue.prototype.getDicts = getDicts
18 14
 
19
-App.mpType = 'app'
15
+// 正确挂载 $http 方法
16
+Vue.prototype.$http = http
20 17
 
21
-const app = new Vue({
22
-  store, // 关键:注入 store
23
-  ...App
24
-})
25
-app.$mount()
18
+// 初始化应用
19
+new Vue({
20
+  router,
21
+  store,
22
+  render: h => h(App),
23
+  
24
+  // 确保应用挂载时路由已准备就绪
25
+  beforeMount() {
26
+    // 确保路由初始化完成
27
+    if (!router.currentRoute) {
28
+      console.warn('路由未初始化,重定向到默认页面');
29
+      router.push('/pages/novel/list');
30
+    }
31
+  }
32
+}).$mount('#app')

+ 258
- 242
RuoYi-App/pages/novel/list.vue Прегледај датотеку

@@ -1,332 +1,348 @@
1 1
 <template>
2 2
   <view class="novel-list-page">
3
-    <!-- 顶部广告(来自Java后台) -->
4
-    <ad-banner :ads="topAds" />
3
+    <!-- 加载状态 -->
4
+    <view v-if="loading" class="loading-container">
5
+      <uni-icons type="spinner-cycle" size="36" color="var(--primary-color)" class="loading-icon" />
6
+      <text>加载中...</text>
7
+    </view>
5 8
     
6
-    <!-- 小说列表 -->
7
-    <!-- 顶部分类导航 -->
8
-    <scroll-view scroll-x class="category-nav">
9
-      <view 
10
-        v-for="category in categories" 
11
-        :key="category.id"
12
-        class="category-item"
13
-        :class="{ active: activeCategory === category.id }"
14
-        @click="changeCategory(category.id)"
15
-      >
16
-        {{ category.name }}
17
-      </view>
18
-    </scroll-view>
19
-    <!-- 热门推荐 -->
20
-    <view class="section">
21
-      <view class="section-header">
22
-        <text class="section-title">热门推荐</text>
23
-        <text class="section-more">更多 ></text>
24
-      </view>
25
-      <scroll-view scroll-x class="hot-list">
9
+    <!-- 内容区域 -->
10
+    <template v-else>
11
+      <!-- 顶部广告 -->
12
+      <ad-banner v-if="topAds.length" :ads="topAds" />
13
+      
14
+      <!-- 顶部分类导航 -->
15
+      <scroll-view scroll-x class="category-nav">
26 16
         <view 
27
-          v-for="novel in hotNovels" 
28
-          :key="novel.id" 
29
-          class="hot-item"
30
-          @click="openNovel(novel)"
17
+          v-for="category in categories" 
18
+          :key="category.id"
19
+          class="category-item"
20
+          :class="{ active: activeCategory === category.id }"
21
+          @click="changeCategory(category.id)"
31 22
         >
32
-          <image :src="novel.cover" class="hot-cover" />
33
-          <text class="hot-title">{{ novel.title }}</text>
23
+          {{ category.name }}
34 24
         </view>
35 25
       </scroll-view>
36
-    </view>
37
-    
38
-    <!-- 分类小说列表 -->
39
-    <view class="section">
40
-      <view class="section-header">
41
-        <text class="section-title">{{ currentCategoryName }}作品</text>
26
+      
27
+      <!-- 空状态 -->
28
+      <view v-if="novels.length === 0 && !error" class="empty-container">
29
+        <image src="/static/empty-book.png" class="empty-img" />
30
+        <text class="empty-text">暂无小说数据</text>
31
+        <button class="btn-refresh" @click="loadNovels">重新加载</button>
42 32
       </view>
43
-      <view class="novel-grid">
44
-        <view 
45
-          v-for="novel in novels" 
46
-          :key="novel.id" 
47
-          class="novel-item"
48
-          @click="openNovel(novel)"
49
-        >
50
-          <image :src="novel.cover" class="novel-cover" />
51
-          <text class="novel-title">{{ novel.title }}</text>
52
-          <text class="novel-author">{{ novel.author }}</text>
33
+      
34
+      <!-- 错误状态 -->
35
+      <view v-if="error" class="error-container">
36
+        <uni-icons type="info" size="48" color="var(--error-color)" />
37
+        <text class="error-text">{{ error }}</text>
38
+        <button class="btn-refresh" @click="loadNovels">重新加载</button>
39
+      </view>
40
+      
41
+      <!-- 热门推荐 -->
42
+      <view v-if="hotNovels.length > 0" class="section">
43
+        <view class="section-header">
44
+          <text class="section-title">热门推荐</text>
45
+          <text class="section-more" @click="goMore">更多 ></text>
53 46
         </view>
47
+        <scroll-view scroll-x class="hot-list">
48
+          <view 
49
+            v-for="novel in hotNovels" 
50
+            :key="novel.id" 
51
+            class="hot-item"
52
+            @click="openNovel(novel)"
53
+          >
54
+            <image :src="getCoverUrl(novel.cover)" class="hot-cover" />
55
+            <text class="hot-title">{{ novel.title }}</text>
56
+          </view>
57
+        </scroll-view>
54 58
       </view>
55
-    </view>
56
-    
57
-    <!-- 成为签约作家按钮 -->
58
-    <view class="become-author" @click="applyAuthor">
59
-      <text>成为签约作家,发布你的作品</text>
60
-    </view>
61
-	    <!-- 底部广告 -->
62
-	    <ad-banner :ads="bottomAds" />
59
+      
60
+      <!-- 分类小说列表 -->
61
+      <view v-if="novels.length > 0" class="section">
62
+        <view class="section-header">
63
+          <text class="section-title">{{ currentCategoryName }}作品</text>
64
+        </view>
65
+        <view class="novel-grid">
66
+          <view 
67
+            v-for="(novel, index) in novels" 
68
+            :key="index" 
69
+            class="novel-item"
70
+            @click="openNovel(novel)"
71
+          >
72
+            <image :src="getCoverUrl(novel.cover)" class="novel-cover" />
73
+            <text class="novel-title">{{ novel.title || '未知标题' }}</text>
74
+            <text class="novel-author">{{ novel.author || '未知作者' }}</text>
75
+          </view>
76
+        </view>
77
+      </view>
78
+      
79
+      <!-- 成为签约作家按钮 -->
80
+      <view class="become-author" @click="applyAuthor">
81
+        <text>成为签约作家,发布你的作品</text>
82
+      </view>
83
+      
84
+      <!-- 底部广告 -->
85
+      <ad-banner v-if="bottomAds.length" :ads="bottomAds" />
86
+    </template>
63 87
   </view>
64
-
65 88
 </template>
89
+
66 90
 <script>
91
+// 确保路径正确
67 92
 import AdBanner from '@/components/AdBanner';
68 93
 
69 94
 export default {
70
-	components: { AdBanner },
95
+  components: { AdBanner },
71 96
   data() {
72 97
     return {
73
-          // 确保所有数组初始化为空数组
74
-      novelList: [], // 存储小说目录数据
75
-	        topAds: [],
76
-	        bottomAds: [],
77
-			      categories: [],
78
-			      activeCategory: 0,
79
-			      hotNovels: [],
80
-			      novels: [],
81
-			      currentCategoryName: '全部'
82
-			
98
+      topAds: [],
99
+      bottomAds: [],
100
+      categories: [],
101
+      activeCategory: 0,
102
+      hotNovels: [],
103
+      novels: [],
104
+      loading: true,
105
+      error: null,
106
+      currentCategoryName: '全部'
83 107
     }
84 108
   },
85
-  async onLoad() {
86
-    await this.loadAds();
87
-    await this.loadNovels();
88
-	    this.loadCategories();
89
-	    this.loadHotNovels();
90
-	    // this.loadNovels();
109
+  async mounted() {
110
+    await this.initData();
91 111
   },
92
-  // onLoad() {
93
-  //   this.loadNovelList();
94
-  // },
95 112
   methods: {
96
-    // 从Java后台加载广告
113
+    async initData() {
114
+      try {
115
+        // 顺序加载数据,避免并行请求冲突
116
+        await this.loadCategories();
117
+        await this.loadHotNovels();
118
+        await this.loadNovels();
119
+        await this.loadAds();
120
+      } catch (e) {
121
+        console.error('初始化失败', e);
122
+        this.error = '初始化失败,请检查网络连接';
123
+      } finally {
124
+        this.loading = false;
125
+      }
126
+    },
127
+    
128
+    // 加载广告
97 129
     async loadAds() {
98
-      const [topRes, bottomRes] = await Promise.all([
99
-        this.$http.get('/java-api/ad/position?code=TOP_BANNER'),
100
-        this.$http.get('/java-api/ad/position?code=BOTTOM_BANNER')
101
-      ]);
102
-      
103
-      this.topAds = topRes.data;
104
-      this.bottomAds = bottomRes.data;
130
+      try {
131
+        const [topRes, bottomRes] = await Promise.all([
132
+          this.$http.get('/java-api/ad/position?code=TOP_BANNER'),
133
+          this.$http.get('/java-api/ad/position?code=BOTTOM_BANNER')
134
+        ]);
135
+        
136
+        this.topAds = topRes?.data || topRes || [];
137
+        this.bottomAds = bottomRes?.data || bottomRes || [];
138
+      } catch (e) {
139
+        console.error('加载广告失败', e);
140
+      }
105 141
     },
106
-	    applyAuthor() {
107
-	      if (!this.$store.getters.token) {
108
-	        uni.showToast({ title: '请先登录', icon: 'none' });
109
-	        uni.navigateTo({ url: '/pages/login' });
110
-	        return;
111
-	      }
112
-	      
113
-	      uni.navigateTo({ url: '/pages/author/apply' });
114
-	    },
115
-        async loadCategories() {
116
-          try {
117
-            // 从Java后台获取分类
142
+    
143
+    // 加载分类
144
+    async loadCategories() {
145
+      try {
118 146
         const res = await this.$http.get('/category/tree');
119
-        // 确保数据结构正确
120
-        if (res.data && Array.isArray(res.data)) {
121
-          // 添加"全部"选项
122
-          this.categories = [{ id: 0, name: '全部' }];
123
-          
124
-          // 递归处理分类树
125
-          const processCategories = (cats) => {
126
-            cats.forEach(cat => {
127
-              this.categories.push({
128
-                id: cat.id,
129
-                name: cat.title,
130
-                children: cat.children
131
-              });
132
-              if (cat.children && cat.children.length) {
133
-                processCategories(cat.children);
134
-              }
135
-            });
136
-          };
137
-          
138
-          processCategories(res.data);
147
+        // 确保有"全部"选项
148
+        this.categories = res?.data ? [{ id: 0, name: '全部' }, ...res.data] : [];
149
+      } catch (e) {
150
+        console.error('加载分类失败', e);
151
+        this.categories = [{ id: 0, name: '全部' }];
152
+      }
153
+    },
154
+    
155
+    // 加载热门小说
156
+    async loadHotNovels() {
157
+      try {
158
+        const res = await this.$http.get('/api/novel/hot');
159
+        
160
+        // 处理不同API响应格式
161
+        if (res?.data?.rows) {
162
+          this.hotNovels = res.data.rows;
163
+        } else if (Array.isArray(res?.data)) {
164
+          this.hotNovels = res.data;
165
+        } else if (Array.isArray(res)) {
166
+          this.hotNovels = res;
139 167
         } else {
140
-          console.warn('分类数据格式不正确');
168
+          console.warn('热门小说API返回格式未知', res);
169
+          this.hotNovels = [];
141 170
         }
142
-          } catch (e) {
143
-            console.error('加载分类失败', e);
144
-          }
145
-        },
146
-    // 从PHP系统加载小说目录
147
-    // async loadNovels() {
148
-    //   const res = await this.$http.get('/php-api/novel/list');
149
-    //   this.novelList = res.data;
150
-    // },
151
-        async loadHotNovels() {
152
-          try {
153
-            // 获取热门小说
154
-            const res = await this.$http.get('/novel/hot');
155
-            this.hotNovels = res.data;
156
-          } catch (e) {
157
-            console.error('加载热门小说失败', e);
158
-          }
159
-        },
160
-    async loadNovels(categoryId = 0) {
171
+      } catch (e) {
172
+        console.error('加载热门小说失败', e);
173
+        this.hotNovels = [];
174
+      }
175
+    },
176
+    
177
+    // 加载小说列表
178
+    async loadNovels() {
179
+      this.loading = true;
180
+      this.error = null;
161 181
       try {
162
-        const url = categoryId 
163
-          ? `/novel/list?categoryId=${categoryId}`
164
-          : '/novel/list';
165
-          
166
-        const res = await this.$http.get(url);
167
-        // 确保数据结构正确
168
-        if (res.rows && Array.isArray(res.rows)) {
169
-          this.novels = res.rows;
182
+        const res = await this.$http.get('/api/novel/list');
183
+        console.log('小说列表API响应:', res);
184
+        
185
+        // 处理不同API响应格式
186
+        if (res?.data?.rows) {
187
+          this.novels = res.data.rows;
188
+        } else if (Array.isArray(res?.data)) {
189
+          this.novels = res.data;
190
+        } else if (res?.data?.list) {
191
+          this.novels = res.data.list;
192
+        } else if (Array.isArray(res)) {
193
+          this.novels = res;
170 194
         } else {
171
-          console.warn('小说列表数据格式不正确');
195
+          console.warn('小说列表API返回格式未知', res);
172 196
           this.novels = [];
197
+          this.error = '数据格式错误';
173 198
         }
174
-
175 199
       } catch (e) {
176
-        uni.showToast({ title: '加载小说失败', icon: 'none' });
200
+        console.error('加载小说失败', e);
201
+        this.error = '加载失败,请重试';
202
+        uni.showToast({
203
+          title: '加载失败,请重试',
204
+          icon: 'none'
205
+        });
206
+        this.novels = [];
207
+      } finally {
208
+        this.loading = false;
177 209
       }
178 210
     },
211
+    
212
+    // 封面URL处理
213
+    getCoverUrl(cover) {
214
+      if (!cover) return '/static/default-cover.jpg';
215
+      if (cover.startsWith('http')) return cover;
216
+      if (cover.startsWith('/')) return cover;
217
+      return `${process.env.VUE_APP_BASE_URL || ''}/uploads/${cover}`;
218
+    },
219
+    
220
+    // 其他方法...
179 221
     changeCategory(categoryId) {
180 222
       this.activeCategory = categoryId;
181
-      this.loadNovels(categoryId);
223
+      this.currentCategoryName = this.categories.find(c => c.id === categoryId)?.name || '全部';
224
+      this.loadNovels();
182 225
     },
226
+    
183 227
     openNovel(novel) {
184
-      uni.navigateTo({
185
-        url: `/pages/novel/detail?id=${novel.id}`
186
-      });
228
+      if (novel.id) {
229
+        uni.navigateTo({
230
+          url: `/pages/novel/detail?id=${novel.id}`
231
+        });
232
+      } else {
233
+        uni.showToast({
234
+          title: '小说ID不存在',
235
+          icon: 'none'
236
+        });
237
+      }
187 238
     },
239
+    
188 240
     applyAuthor() {
189
-      uni.navigateTo({
190
-        url: '/pages/author/apply'
191
-      });
241
+      if (!this.$store.getters.token) {
242
+        uni.showToast({ title: '请先登录', icon: 'none' });
243
+        uni.navigateTo({ url: '/pages/login' });
244
+        return;
245
+      }
246
+      uni.navigateTo({ url: '/pages/author/apply' });
192 247
     },
193
-    // 打开小说详情页
194
-    openNovel(novel) {
248
+    
249
+    goMore() {
195 250
       uni.navigateTo({
196
-        url: `/pages/novel/detail?id=${novel.id}&title=${encodeURIComponent(novel.title)}`
251
+        url: '/pages/novel/more'
197 252
       });
198 253
     }
199 254
   }
200 255
 }
201 256
 </script>
202
-<style scoped>
203
-.become-author {
204
-  position: fixed;
205
-  bottom: 120rpx; /* 在TabBar上方 */
206
-  left: 50%;
207
-  transform: translateX(-50%);
208
-  background-color: #3cc51f;
209
-  color: white;
210
-  padding: 16rpx 40rpx;
211
-  border-radius: 50rpx;
212
-  font-size: 28rpx;
213
-  box-shadow: 0 4rpx 12rpx rgba(60, 197, 31, 0.3);
214
-  z-index: 999;
215
-}
216
-.novel-list-page {
217
-  padding: 20rpx;
218
-  padding-bottom: 40rpx;
219
-}
220 257
 
221
-.category-nav {
222
-  white-space: nowrap;
223
-  margin-bottom: 30rpx;
224
-}
225
-
226
-.category-item {
227
-  display: inline-block;
228
-  padding: 10rpx 30rpx;
229
-  margin-right: 20rpx;
230
-  border-radius: 50rpx;
231
-  background-color: #f5f5f5;
232
-  font-size: 28rpx;
258
+<style scoped>
259
+/* 添加加载状态样式 */
260
+.loading-container {
261
+  display: flex;
262
+  flex-direction: column;
263
+  align-items: center;
264
+  justify-content: center;
265
+  padding: 100rpx 0;
233 266
 }
234 267
 
235
-.category-item.active {
236
-  background-color: #3cc51f;
237
-  color: white;
268
+.loading-icon {
269
+  animation: rotate 1s linear infinite;
270
+  margin-bottom: 20rpx;
238 271
 }
239 272
 
240
-.section {
241
-  margin-bottom: 40rpx;
273
+@keyframes rotate {
274
+  from { transform: rotate(0deg); }
275
+  to { transform: rotate(360deg); }
242 276
 }
243 277
 
244
-.section-header {
278
+/* 添加空状态样式 */
279
+.empty-container, .error-container {
245 280
   display: flex;
246
-  justify-content: space-between;
281
+  flex-direction: column;
247 282
   align-items: center;
248
-  margin-bottom: 20rpx;
249
-}
250
-
251
-.section-title {
252
-  font-size: 32rpx;
253
-  font-weight: bold;
254
-}
255
-
256
-.section-more {
257
-  font-size: 24rpx;
258
-  color: #888;
283
+  justify-content: center;
284
+  padding: 100rpx 0;
285
+  text-align: center;
259 286
 }
260 287
 
261
-.hot-list {
262
-  white-space: nowrap;
288
+.empty-img {
289
+  width: 200rpx;
290
+  height: 200rpx;
291
+  opacity: 0.6;
292
+  margin-bottom: 40rpx;
263 293
 }
264 294
 
265
-.hot-item {
266
-  display: inline-block;
267
-  width: 200rpx;
268
-  margin-right: 20rpx;
295
+.empty-text, .error-text {
296
+  font-size: 32rpx;
297
+  color: var(--text-color);
298
+  margin-bottom: 40rpx;
269 299
 }
270 300
 
271
-.hot-cover {
272
-  width: 200rpx;
273
-  height: 280rpx;
274
-  border-radius: 8rpx;
301
+.error-text {
302
+  color: var(--error-color);
275 303
 }
276 304
 
277
-.hot-title {
278
-  display: block;
279
-  font-size: 26rpx;
280
-  margin-top: 10rpx;
281
-  white-space: nowrap;
282
-  overflow: hidden;
283
-  text-overflow: ellipsis;
305
+.btn-refresh {
306
+  background-color: var(--primary-color);
307
+  color: white;
308
+  width: 60%;
309
+  border-radius: 50rpx;
284 310
 }
285 311
 
312
+/* 确保网格布局正确 */
286 313
 .novel-grid {
287 314
   display: grid;
288 315
   grid-template-columns: repeat(3, 1fr);
289 316
   gap: 20rpx;
317
+  padding: 20rpx;
290 318
 }
291 319
 
292 320
 .novel-item {
293
-  text-align: center;
321
+  background-color: var(--card-bg);
322
+  border-radius: 12rpx;
323
+  overflow: hidden;
324
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
294 325
 }
295 326
 
296 327
 .novel-cover {
297 328
   width: 100%;
298 329
   height: 300rpx;
299
-  border-radius: 8rpx;
330
+  background-color: #f5f5f5; /* 添加默认背景 */
300 331
 }
301 332
 
302 333
 .novel-title {
303 334
   display: block;
304
-  font-size: 26rpx;
305
-  margin-top: 10rpx;
335
+  font-size: 28rpx;
336
+  padding: 10rpx 15rpx;
337
+  white-space: nowrap;
306 338
   overflow: hidden;
307 339
   text-overflow: ellipsis;
308
-  display: -webkit-box;
309
-  -webkit-line-clamp: 1;
310
-  -webkit-box-orient: vertical;
311 340
 }
312 341
 
313 342
 .novel-author {
314 343
   display: block;
315
-  font-size: 22rpx;
344
+  font-size: 24rpx;
316 345
   color: #888;
346
+  padding: 0 15rpx 15rpx;
317 347
 }
318
-
319
-.become-author {
320
-  position: fixed;
321
-  bottom: 120rpx; /* 在TabBar上方 */
322
-  left: 50%;
323
-  transform: translateX(-50%);
324
-  background-color: #3cc51f;
325
-  color: white;
326
-  padding: 16rpx 40rpx;
327
-  border-radius: 50rpx;
328
-  font-size: 28rpx;
329
-  box-shadow: 0 4rpx 12rpx rgba(60, 197, 31, 0.3);
330
-  z-index: 999;
331
-}
332
-</style>
348
+</style>

+ 48
- 37
RuoYi-App/router/index.js Прегледај датотеку

@@ -1,66 +1,77 @@
1 1
 import Vue from 'vue'
2 2
 import Router from 'vue-router'
3
+import store from '@/store'
3 4
 
4 5
 Vue.use(Router)
5 6
 
6 7
 const routes = [
7 8
   {
8 9
     path: '/',
9
-    redirect: '/pages/home/index'
10
-  },
11
-  {
12
-    path: '/pages/home/index',
13
-    name: 'Home',
14
-    component: () => import('@/pages/home/index')
10
+    redirect: '/pages/novel/list'
15 11
   },
16 12
   {
17 13
     path: '/pages/novel/list',
18 14
     name: 'NovelList',
19
-    component: () => import('@/pages/novel/list')
15
+    component: () => import('@/pages/novel/list'),
16
+    meta: { title: '小说列表' }
20 17
   },
21 18
   {
22
-    path: '/pages/novel/reader',
23
-    name: 'NovelReader',
24
-    component: () => import('@/pages/novel/reader'),
25
-    props: route => ({
26
-      novelId: route.query.novelId,
27
-      chapterId: route.query.chapterId
28
-    })
19
+    path: '/pages/novel/detail',
20
+    name: 'NovelDetail',
21
+    component: () => import('@/pages/novel/detail'),
22
+    meta: { title: '小说详情' }
29 23
   },
30 24
   {
31 25
     path: '/pages/author/apply',
32 26
     name: 'AuthorApply',
33
-    component: () => import('@/pages/author/apply')
34
-  },
35
-  {
36
-    path: '/pages/search/index',
37
-    name: 'Search',
38
-    component: () => import('@/pages/search/index')
39
-  },
40
-  {
41
-    path: '/pages/book/list',
42
-    name: 'BookList',
43
-    component: () => import('@/pages/book/list')
27
+    component: () => import('@/pages/author/apply'),
28
+    meta: { title: '作者申请', requiresAuth: true }
44 29
   },
45 30
   {
46
-    path: '/bookshelf',
47
-    component: () => import('@/views/bookshelf'),
48
-    meta: { requiresAuth: true } // 需要登录
31
+    path: '/pages/login',
32
+    name: 'Login',
33
+    component: () => import('@/pages/login'),
34
+    meta: { title: '登录' }
49 35
   },
50 36
   {
51
-    path: '/mine',
52
-    component: () => import('@/views/mine'),
53
-    meta: { requiresAuth: true } // 需要登录
37
+    path: '/pages/register',
38
+    name: 'Register',
39
+    component: () => import('@/pages/register'),
40
+    meta: { title: '注册' }
54 41
   },
42
+  // 添加更多路由...
55 43
   {
56
-    path: '/',
57
-    component: () => import('@/views/home'),
58
-    meta: { requiresAuth: false } // 不需要登录
44
+    path: '*',
45
+    redirect: '/pages/novel/list'
59 46
   }
60 47
 ]
61 48
 
62
-export default new VueRouter({
49
+const router = new Router({
63 50
   mode: 'history',
64
-  base: process.env.BASE_URL,
65 51
   routes
66
-})
52
+})
53
+
54
+// 添加简单的路由守卫
55
+router.beforeEach((to, from, next) => {
56
+  // 设置页面标题
57
+  if (to.meta.title) {
58
+    document.title = to.meta.title
59
+  }
60
+  
61
+  // 检查是否需要登录
62
+  if (to.meta.requiresAuth) {
63
+    if (!store.getters.token) {
64
+      next('/pages/login')
65
+      return
66
+    }
67
+  }
68
+  
69
+  next()
70
+})
71
+
72
+// 确保路由初始化
73
+router.onReady(() => {
74
+  console.log('Router is ready');
75
+})
76
+
77
+export default router

BIN
RuoYi-App/static/default-cover.jpg Прегледај датотеку


+ 27
- 0
RuoYi-App/styles/index.scss Прегледај датотеку

@@ -0,0 +1,27 @@
1
+/* src/styles/index.scss */
2
+* {
3
+  margin: 0;
4
+  padding: 0;
5
+  box-sizing: border-box;
6
+}
7
+
8
+body, html, #app {
9
+  height: 100%;
10
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+}
12
+
13
+#app {
14
+  display: flex;
15
+  flex-direction: column;
16
+  min-height: 100vh;
17
+}
18
+
19
+/* 主题变量 */
20
+:root {
21
+  --primary-color: #2a5caa;
22
+  --bg-color: #e6f7ff;
23
+  --text-color: #1a3353;
24
+  --card-bg: #d0e8ff;
25
+  --header-bg: #2a5caa;
26
+  --error-color: #f56c6c;
27
+}

+ 21
- 10
RuoYi-App/utils/request.js Прегледај датотеку

@@ -1,8 +1,8 @@
1
+// src/utils/request.js
1 2
 import config from '@/config'
2
-import store from '@/store'
3 3
 const baseUrl = config.baseUrl
4 4
 
5
-export const http = {
5
+const http = {
6 6
   get(url, params = {}, options = {}) {
7 7
     return this.request('GET', url, params, options)
8 8
   },
@@ -13,6 +13,18 @@ export const http = {
13 13
   
14 14
   request(method, url, data = {}, options = {}) {
15 15
     return new Promise((resolve, reject) => {
16
+      // 确保URL以斜杠开头
17
+      let finalUrl = url.startsWith('/') ? url : `/${url}`;
18
+      
19
+      // 特殊处理Java API请求
20
+      if (url.startsWith('/java-api')) {
21
+        finalUrl = url;
22
+      }
23
+      // 其他API添加前缀
24
+      else if (!url.startsWith('http')) {
25
+        finalUrl = '/api' + finalUrl;
26
+      }
27
+      
16 28
       const header = {
17 29
         'Content-Type': 'application/json',
18 30
         ...(options.headers || {})
@@ -25,15 +37,18 @@ export const http = {
25 37
       }
26 38
       
27 39
       uni.request({
28
-        url: baseUrl + url,
40
+        url: baseUrl + finalUrl,
29 41
         method,
30 42
         data,
31 43
         header,
32 44
         success: (res) => {
45
+          // 统一处理成功响应
33 46
           if (res.statusCode >= 200 && res.statusCode < 300) {
34 47
             resolve(res.data)
35 48
           } else {
36
-            reject(res.data)
49
+            // 尝试提取错误消息
50
+            const errorMsg = res.data?.msg || res.data?.message || '请求失败';
51
+            reject(new Error(errorMsg))
37 52
           }
38 53
         },
39 54
         fail: (err) => {
@@ -44,9 +59,5 @@ export const http = {
44 59
   }
45 60
 }
46 61
 
47
-// 挂载到Vue原型
48
-export default {
49
-  install(Vue) {
50
-    Vue.prototype.$http = http
51
-  }
52
-}
62
+// 确保正确导出
63
+export default http

Loading…
Откажи
Сачувај