yinshaojie 5 mēnešus atpakaļ
vecāks
revīzija
ead4bb47bf

+ 2
- 2
RuoYi-App/App.vue Parādīt failu

@@ -382,9 +382,9 @@ export default {
382 382
           }
383 383
           
384 384
           if (this.$router) {
385
-            this.$router.push('/pages/login');
385
+            this.$router.push('/pages/login/index');
386 386
           } else if (uni && uni.navigateTo) {
387
-            uni.navigateTo({ url: '/pages/login' });
387
+            uni.navigateTo({ url: '/pages/login/index' });
388 388
           }
389 389
         }
390 390
       }

+ 25
- 25
RuoYi-App/api/login.js Parādīt failu

@@ -1,6 +1,6 @@
1 1
 import request from '@/utils/request'
2 2
 
3
-// 登录方法
3
+// 登录方法 - 修复版
4 4
 export function login(username, password, code, uuid) {
5 5
   const data = {
6 6
     username,
@@ -9,12 +9,24 @@ export function login(username, password, code, uuid) {
9 9
     uuid
10 10
   }
11 11
   return request({
12
-    'url': '/login',
12
+    url: '/login',
13
+    method: 'post',
14
+    data: data,
15
+    headers: {
16
+      isToken: false
17
+    }
18
+  })
19
+}
20
+
21
+// 获取验证码
22
+export function getCodeImg() {
23
+  return request({
24
+    url: '/captchaImage',
25
+    method: 'get',
13 26
     headers: {
14 27
       isToken: false
15 28
     },
16
-    'method': 'post',
17
-    'data': data
29
+    timeout: 20000
18 30
   })
19 31
 }
20 32
 
@@ -22,38 +34,26 @@ export function login(username, password, code, uuid) {
22 34
 export function register(data) {
23 35
   return request({
24 36
     url: '/register',
37
+    method: 'post',
38
+    data: data,
25 39
     headers: {
26 40
       isToken: false
27
-    },
28
-    method: 'post',
29
-    data: data
41
+    }
30 42
   })
31 43
 }
32 44
 
33
-// 获取用户详细信息
45
+// 获取用户信息
34 46
 export function getInfo() {
35 47
   return request({
36
-    'url': '/getInfo',
37
-    'method': 'get'
48
+    url: '/getInfo',
49
+    method: 'get'
38 50
   })
39 51
 }
40 52
 
41
-// 退出方法
53
+// 退出登录
42 54
 export function logout() {
43 55
   return request({
44
-    'url': '/logout',
45
-    'method': 'post'
46
-  })
47
-}
48
-
49
-// 获取验证码
50
-export function getCodeImg() {
51
-  return request({
52
-    'url': '/captchaImage',
53
-    headers: {
54
-      isToken: false
55
-    },
56
-    method: 'get',
57
-    timeout: 20000
56
+    url: '/logout',
57
+    method: 'post'
58 58
   })
59 59
 }

+ 12
- 1
RuoYi-App/main.js Parādīt failu

@@ -33,4 +33,15 @@ new Vue({
33 33
       router.push('/pages/novel/list')
34 34
     }
35 35
   }
36
-}).$mount('#app')
36
+}).$mount('#app')
37
+
38
+Vue.config.errorHandler = function (err, vm, info) {
39
+  console.error('Vue错误:', err);
40
+  console.error('错误信息:', info);
41
+}
42
+
43
+// 全局未处理的Promise拒绝
44
+window.addEventListener('unhandledrejection', function (event) {
45
+  console.error('未处理的Promise拒绝:', event.reason);
46
+  event.preventDefault();
47
+});

+ 49
- 43
RuoYi-App/pages.json Parādīt failu

@@ -8,76 +8,82 @@
8 8
       }
9 9
     },
10 10
     {
11
-      "path": "pages/bookshelf/index",
11
+      "path": "pages/novel/list",
12 12
       "style": {
13
-        "navigationBarTitleText": "我的书架"
13
+        "navigationBarTitleText": "小说",
14
+        "enablePullDownRefresh": false
14 15
       }
15 16
     },
16 17
     {
17
-      "path": "pages/me/index",
18
+      "path": "pages/bookshelf/index",
18 19
       "style": {
19
-        "navigationBarTitleText": "个人中心"
20
+        "navigationBarTitleText": "我的书架"
20 21
       }
21 22
     },
22 23
     {
23
-      "path": "pages/novel/list", // 移动到tabBar页面组后面
24
+      "path": "pages/me/index",
24 25
       "style": {
25
-        "navigationBarTitleText": "小说",
26
-        "enablePullDownRefresh": false
26
+        "navigationBarTitleText": "个人中心"
27 27
       }
28 28
     },
29 29
     {
30
-      "path": "pages/novel/reader",  // 添加阅读器页面配置
30
+      "path": "pages/novel/reader",
31 31
       "style": {
32
-        "navigationBarTitleText": "阅读",
32
+        "navigationBarTitleText": "阅读",
33 33
         "enablePullDownRefresh": false,
34
-        "navigationStyle": "custom",  // 自定义导航栏
34
+        "navigationStyle": "custom",
35 35
         "app-plus": {
36
-          "titleNView": false  // 禁用原生导航栏
36
+          "titleNView": false
37 37
         }
38 38
       }
39 39
     },
40 40
     {
41
-      "path": "pages/login",
41
+      "path": "pages/login/index",
42 42
       "style": {
43
-        "navigationBarTitleText": "登录"
43
+        "navigationBarTitleText": "登录",
44
+        "navigationStyle": "custom"
44 45
       }
45 46
     },
46 47
     {
47
-      "path": "pages/register",
48
+      "path": "pages/register/index",
48 49
       "style": {
49 50
         "navigationBarTitleText": "注册"
50 51
       }
51 52
     }
52 53
   ],
53
-"tabBar": {
54
-  "list": [
55
-    {
56
-      "pagePath": "pages/index/index",
57
-      "iconPath": "/static/tabbar/home.png",
58
-      "selectedIconPath": "/static/tabbar/home_selected.png",
59
-      "text": "首页"
60
-    },
61
-    {
62
-      "pagePath": "pages/novel/list",
63
-      "iconPath": "/static/tabbar/novel.png",
64
-      "selectedIconPath": "/static/tabbar/novel_selected.png",
65
-      "text": "小说"
66
-    },
67
-    {
68
-      "pagePath": "pages/bookshelf/index",
69
-      "iconPath": "/static/tabbar/bookshelf.png",
70
-      "selectedIconPath": "/static/tabbar/bookshelf_selected.png",
71
-      "text": "书架"
72
-    },
73
-    {
74
-      "pagePath": "pages/me/index",
75
-      "iconPath": "/static/tabbar/mine.png",
76
-      "selectedIconPath": "/static/tabbar/mine_selected.png",
77
-      "text": "我的"
78
-    }
79
-  ]
80
-},
54
+  "tabBar": {
55
+    "color": "#7a7e83",
56
+    "selectedColor": "#3a8ee6",
57
+    "backgroundColor": "#ffffff",
58
+    "borderStyle": "black",
59
+    "height": "120rpx",
60
+    "list": [
61
+      {
62
+        "pagePath": "pages/index/index",
63
+        "iconPath": "/static/tabbar/home.png",
64
+        "selectedIconPath": "/static/tabbar/home_selected.png",
65
+        "text": "首页"
66
+      },
67
+      {
68
+        "pagePath": "pages/novel/list",
69
+        "iconPath": "/static/tabbar/novel.png",
70
+        "selectedIconPath": "/static/tabbar/novel_selected.png",
71
+        "text": "小说"
72
+      },
73
+      {
74
+        "pagePath": "pages/bookshelf/index",
75
+        "iconPath": "/static/tabbar/bookshelf.png",
76
+        "selectedIconPath": "/static/tabbar/bookshelf_selected.png",
77
+        "text": "书架"
78
+      },
79
+      {
80
+        "pagePath": "pages/me/index",
81
+        "iconPath": "/static/tabbar/mine.png",
82
+        "selectedIconPath": "/static/tabbar/mine_selected.png",
83
+        "text": "我的"
84
+      }
85
+    ]
86
+  },
81 87
   "globalStyle": {
82 88
     "navigationBarTextStyle": "black",
83 89
     "navigationBarTitleText": "哎呀免费小说",
@@ -87,4 +93,4 @@
87 93
       "titleNView": false
88 94
     }
89 95
   }
90
-}
96
+}

+ 3
- 3
RuoYi-App/pages/index/index.vue Parādīt failu

@@ -235,7 +235,7 @@ export default {
235 235
       if (this.$store.getters.token) {
236 236
         uni.navigateTo({ url: '/pages/user/index' })
237 237
       } else {
238
-        uni.navigateTo({ url: '/pages/login' })
238
+        uni.navigateTo({ url: '/pages/login/index' })
239 239
       }
240 240
     },
241 241
     
@@ -244,7 +244,7 @@ export default {
244 244
     },
245 245
     
246 246
     goToLogin() {
247
-      uni.navigateTo({ url: '/pages/login' })
247
+      uni.navigateTo({ url: '/pages/login/index' })
248 248
     },
249 249
     // 下一章
250 250
     nextChapter() {
@@ -273,7 +273,7 @@ export default {
273 273
     // 跳转登录页
274 274
     goToLogin() {
275 275
       uni.navigateTo({
276
-        url: '/pages/login'
276
+        url: '/pages/login/index'
277 277
       })
278 278
     },
279 279
     

+ 667
- 186
RuoYi-App/pages/login.vue Parādīt failu

@@ -1,9 +1,15 @@
1 1
 <template>
2 2
   <view class="normal-login-container">
3
+
4
+
3 5
     <view class="logo-content align-center justify-center flex">
4
-      <text class="title">哎呀免费小说登录</text>
6
+      <image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
7
+      </image>
8
+      <text class="title">哎呀电子小说2</text>
5 9
     </view>
10
+
6 11
     <view class="login-form-content">
12
+      <!-- 用户名密码登录 -->
7 13
       <view class="input-item flex align-center">
8 14
         <view class="iconfont icon-user icon"></view>
9 15
         <input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
@@ -19,9 +25,19 @@
19 25
           <image :src="codeUrl" @click="getCode" class="login-code-img"></image>
20 26
         </view>
21 27
       </view>
28
+
22 29
       <view class="action-btn">
23
-        <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
30
+        <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">账号登录</button>
31
+      </view>
32
+
33
+      <!-- 微信一键登录 -->
34
+      <view class="wechat-login-section">
35
+        <button class="wechat-login-btn" @click="handleWechatLogin">
36
+          <image src="/static/icons/wechat.png" class="wechat-icon"></image>
37
+          <text>微信一键登录</text>
38
+        </button>
24 39
       </view>
40
+
25 41
       <view class="reg text-center" v-if="register">
26 42
         <text class="text-grey1">没有账号?</text>
27 43
         <text @click="handleUserRegister" class="text-blue">立即注册</text>
@@ -31,235 +47,626 @@
31 47
         <text @click="handleUserAgrement" class="text-blue">《用户协议》</text>
32 48
         <text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
33 49
       </view>
34
-    </view>
35
-    <view class="reading-tips">
36
-      <text class="tip-text">登录后即可:</text>
37
-      <view class="benefits">
38
-        <text>✓ 继续阅读精彩章节</text>
39
-        <text>✓ 加入书架保存进度</text>
40
-        <text>✓ 获得每日阅读奖励</text>
50
+      
51
+      <!-- 备案信息 - 只在H5环境中显示 -->
52
+      <!-- #ifdef H5 -->
53
+      <view class="beian-info">
54
+        <view class="beian-links">
55
+          <a href="https://beian.miit.gov.cn" target="_blank" class="beian-link">陕ICP备2024057340号-2</a>
56
+<!--          <a href="http://www.beian.gov.cn" target="_blank" class="beian-link">
57
+            <img src="/static/icons/police-beian.png" class="police-icon" />
58
+            京公网安备 11010502034567号
59
+          </a> -->
60
+        </view>
61
+        <view class="copyright">
62
+          © 2025 哎呀电子小说 * 哎呀电子 版权所有
63
+        </view>
41 64
       </view>
65
+      <!-- #endif -->
66
+    </view>
67
+    <!-- 广告组件保持不变 -->
68
+<!--    <ad-interstitial adpid="1433929280" :loadnext="true" v-slot:default="{loading, error}" @load="onadload" @close="onadclose" @error="onaderror">
69
+      <button :disabled="loading" :loading="loading">点击广告获取奖励</button>
70
+      <view v-if="error">{{error}}</view>
71
+    </ad-interstitial>
72
+    <view class="ad-view">
73
+      <ad adpid="1325240772" @load="onload" @close="onclose" @error="onerror"></ad>
42 74
     </view>
75
+    <view class="ad-view">
76
+      <ad adpid="1695714925" @load="onload1" @close="onclose1" @error="onerror1"></ad>
77
+    </view> -->
43 78
   </view>
44 79
 </template>
45 80
 
46 81
 <script>
47
-export default {
48
-  data() {
49
-    return {
50
-      codeUrl: "",
51
-      captchaEnabled: false, // 默认不开启验证码
52
-      register: false,
53
-      // 安全的全局配置访问方式
54
-      globalConfig: {
55
-        appInfo: {
56
-          logo: "",
57
-          agreements: []
82
+  import { getCodeImg } from '@/api/login'
83
+import { getToken, setToken, removeToken } from '@/utils/auth'
84
+  export default {
85
+    data() {
86
+      return {
87
+        codeUrl: "",
88
+        captchaEnabled: true,
89
+        // 用户注册开关
90
+        register: true,
91
+        wxCode: "", // 添加这个变量来保存微信code
92
+        globalConfig: {
93
+          appInfo: {
94
+            logo: '/static/logo.png',
95
+            agreements: []
96
+          }
97
+        },
98
+        loginForm: {
99
+          username: "",
100
+          password: "",
101
+          code: "",
102
+          uuid: ''
58 103
         }
59
-      },
60
-      loginForm: {
61
-        username: "",
62
-        password: "",
63
-        code: "",
64
-        uuid: ""
65 104
       }
66
-    }
67
-  },
68
-  created() {
69
-    // 安全地获取全局配置
70
-    this.safeInit();
71
-  },
72
-  onLoad() {
73
-    // 如果全局配置已设置,则使用全局配置
74
-    if (this.$config) {
75
-      this.globalConfig = this.$config;
76
-    }
77
-  },
78
-  methods: {
79
-    // 安全的初始化方法
80
-    safeInit() {
81
-      try {
82
-        // 安全地获取全局配置
83
-        if (typeof getApp === 'function') {
84
-          const app = getApp();
105
+    },
106
+    created() {
107
+      this.safeInitConfig()
108
+      this.getCode()
109
+    },
110
+    methods: {
111
+      // 安全初始化配置
112
+      safeInitConfig() {
113
+        try {
114
+          const app = getApp()
85 115
           if (app && app.globalData && app.globalData.config) {
86
-            this.globalConfig = app.globalData.config;
116
+            this.globalConfig = app.globalData.config
117
+          } else {
118
+            this.globalConfig = {
119
+              appInfo: {
120
+                logo: '/static/logo.png',
121
+                name: '恋爱圈',
122
+                agreements: [
123
+                  { title: '隐私协议', url: 'https://www.aiyadianzi.cn/privacy' },
124
+                  { title: '用户协议', url: 'https://www.aiyadianzi.cn/agreement' }
125
+                ]
126
+              }
127
+            }
128
+          }
129
+        } catch (error) {
130
+          console.warn('获取全局配置失败:', error)
131
+          this.globalConfig = {
132
+            appInfo: {
133
+              logo: '/static/logo.png',
134
+              name: '哎呀电子',
135
+              agreements: []
136
+            }
87 137
           }
88 138
         }
139
+      },
140
+      
141
+      // 广告相关方法
142
+      onadload(e) { console.log('广告数据加载成功') },
143
+      onadclose(e) { console.log("onadclose",e) },
144
+      onaderror(e) { console.log("onaderror: ", e.detail) },
145
+      onload(e) { console.log("onload") },
146
+      onclose(e) { console.log("onclose: " + e.detail) },
147
+      onerror(e) { console.log("onerror: " + e.detail.errCode + " message:: " + e.detail.errMsg) },
148
+      onload1(e) { console.log("onload") },
149
+      onclose1(e) { console.log("onclose: " + e.detail) },
150
+      onerror1(e) { console.log("onerror: " + e.detail.errCode + " message:: " + e.detail.errMsg) },
151
+
152
+      // 微信登录 - 修复版
153
+      async handleWechatLogin() {
154
+        console.log('开始微信登录流程')
89 155
         
90
-        // 尝试获取验证码
91
-        this.getCodeSafe();
92
-      } catch (error) {
93
-        console.error('初始化失败:', error);
94
-      }
95
-    },
96
-    
97
-    // 安全的获取验证码方法
98
-    async getCodeSafe() {
99
-      try {
100
-        await this.getCode();
101
-      } catch (error) {
102
-        console.error('获取验证码失败:', error);
103
-        // 可以设置默认行为,比如不显示验证码
104
-        this.captchaEnabled = false;
105
-      }
106
-    },
156
+        try {
157
+          
158
+          // 1. 先检查网络状态
159
+          const networkState = await this.checkNetworkState()
160
+          if (!networkState) {
161
+            uni.showToast({
162
+              title: '网络连接不可用,请检查网络',
163
+              icon: 'none'
164
+            })
165
+            return
166
+          }
167
+          
168
+          // 2. 先获取微信code
169
+          const loginRes = await this.getWxLoginCode()
170
+          console.log('微信login结果:', loginRes)
171
+
172
+          if (loginRes.errMsg !== 'login:ok' || !loginRes.code) {
173
+            uni.showToast({
174
+              title: '获取微信登录凭证失败',
175
+              icon: 'none'
176
+            })
177
+            return
178
+          }
179
+          
180
+          // 保存code,用于后续请求
181
+          this.wxCode = loginRes.code
182
+
183
+          // 3. 显示确认授权弹窗,然后获取用户信息
184
+          uni.showModal({
185
+            title: '授权确认',
186
+            content: '需要获取您的用户信息以完成登录',
187
+            confirmText: '允许',
188
+            cancelText: '拒绝',
189
+            success: async (res) => {
190
+              if (res.confirm) {
191
+                // 用户确认授权,获取用户信息
192
+                await this.getUserInfoAndLogin()
193
+              } else {
194
+                uni.showToast({
195
+                  title: '授权已取消',
196
+                  icon: 'none'
197
+                })
198
+              }
199
+            }
200
+          })
201
+          
202
+        } catch (error) {
203
+          console.error('微信登录流程失败:', error)
204
+          
205
+          let errorMsg = '登录失败,请重试'
206
+          if (error.errMsg) {
207
+            errorMsg = error.errMsg
208
+          } else if (error.message) {
209
+            errorMsg = error.message
210
+          }
211
+          
212
+          uni.showToast({
213
+            title: errorMsg,
214
+            icon: 'none',
215
+            duration: 3000
216
+          })
217
+        }
218
+      },
219
+
220
+      // 获取用户信息并登录
221
+      async getUserInfoAndLogin() {
222
+        try {
223
+          uni.showLoading({
224
+            title: '获取用户信息...',
225
+            mask: true
226
+          })
227
+          
228
+          // 直接调用getUserProfile,确保在用户交互中
229
+          const userProfileRes = await new Promise((resolve, reject) => {
230
+            uni.getUserProfile({
231
+              desc: '用于完善会员资料',
232
+              success: resolve,
233
+              fail: reject
234
+            })
235
+          })
236
+          
237
+          console.log('用户信息获取结果:', userProfileRes)
238
+          
239
+          if (userProfileRes.errMsg !== 'getUserProfile:ok') {
240
+            uni.hideLoading()
241
+            uni.showToast({
242
+              title: '获取用户信息失败',
243
+              icon: 'none'
244
+            })
245
+            return
246
+          }
247
+          
248
+          const userInfo = userProfileRes.userInfo
249
+          uni.hideLoading()
250
+
251
+          // 调用后端登录接口
252
+          await this.sendLoginRequest(this.wxCode, userInfo)
253
+          
254
+        } catch (error) {
255
+          uni.hideLoading()
256
+          console.error('获取用户信息失败:', error)
257
+          
258
+          let errorMsg = '获取用户信息失败'
259
+          if (error.errMsg) {
260
+            if (error.errMsg.includes('getUserProfile:fail')) {
261
+              errorMsg = '用户拒绝授权'
262
+            } else {
263
+              errorMsg = error.errMsg
264
+            }
265
+          }
266
+          
267
+          uni.showToast({
268
+            title: errorMsg,
269
+            icon: 'none',
270
+            duration: 3000
271
+          })
272
+        }
273
+      },
274
+
275
+      // 发送登录请求到后端 - 修复版
276
+      async sendLoginRequest(code, userInfo) {
277
+        uni.showLoading({
278
+          title: '登录中...',
279
+          mask: true
280
+        })
281
+        try {
282
+          const loginData = {
283
+            code: code,
284
+            userInfo: {
285
+              nickName: userInfo.nickName,
286
+              avatarUrl: userInfo.avatarUrl,
287
+              gender: userInfo.gender,
288
+              country: userInfo.country,
289
+              province: userInfo.province,
290
+              city: userInfo.city
291
+            }
292
+          }
293
+          
294
+          console.log('发送登录数据:', loginData)
295
+          
296
+          // 使用配置中的baseUrl
297
+          const baseUrl = this.getBaseUrl()
298
+          const requestUrl = `${baseUrl}/wxLogin`
299
+          
300
+          console.log('请求URL:', requestUrl)
107 301
     
108
-    // 获取验证码
109
-    async getCode() {
110
-      // 确保$http对象可用
111
-      if (!this.$http || typeof this.$http.get !== 'function') {
112
-        console.warn('$http未定义或不可用');
113
-        return;
302
+    const res = await new Promise((resolve, reject) => {
303
+      uni.request({
304
+              url: requestUrl,
305
+              method: 'POST',
306
+              data: loginData,
307
+              header: {
308
+                'content-type': 'application/json',
309
+              },
310
+              success: (res) => {
311
+                console.log('请求成功,状态码:', res.statusCode)
312
+                console.log('响应数据:', res.data)
313
+                resolve(res.data)
314
+              },
315
+              fail: (err) => {
316
+                console.error('请求失败:', err)
317
+                reject(err)
318
+              }
319
+            })
320
+          })
321
+          
322
+          uni.hideLoading()
323
+          console.log('后端登录响应:', res)
324
+          
325
+          if (res.code === 200) {
326
+      // 修复:使用正确的 token 存储方式
327
+      const token = res.data.token
328
+      const user = res.data.user
329
+      
330
+      console.log('解析出的token:', token)
331
+      console.log('解析出的user:', user)
332
+      
333
+      if (!token) {
334
+        throw new Error('后端返回的token为空')
114 335
       }
115 336
       
337
+      // 修复:使用 auth.js 中的方法存储 token
116 338
       try {
117
-        const res = await this.$http.get('/captchaImage');
118
-        if (res.code === 200) {
119
-          this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
120
-          if (this.captchaEnabled) {
121
-            this.codeUrl = 'data:image/png;base64,' + res.data.img;
122
-            this.loginForm.uuid = res.data.uuid;
339
+        // 使用 setToken 方法存储 token(key 为 'App-Token')
340
+        setToken(token)
341
+        
342
+        // 存储用户信息到本地存储
343
+        uni.setStorageSync('userInfo', user)
344
+        
345
+        // 验证存储是否成功
346
+        const storedToken = getToken()  // 使用 getToken 方法获取
347
+        const storedUserInfo = uni.getStorageSync('userInfo')
348
+        
349
+        console.log('存储验证 - token:', storedToken ? '成功' : '失败')
350
+        console.log('存储验证 - userInfo:', storedUserInfo ? '成功' : '失败')
351
+        
352
+        if (!storedToken) {
353
+          throw new Error('token存储失败')
354
+        }
355
+        
356
+        // 尝试更新 Vuex 状态(如果存在)
357
+        if (this.$store) {
358
+          try {
359
+            // 使用 user.js 中的 mutation 更新状态
360
+            if (this.$store._mutations && this.$store._mutations['user/SET_TOKEN']) {
361
+              this.$store.commit('user/SET_TOKEN', token)
362
+            }
363
+            
364
+            if (user && this.$store._mutations && this.$store._mutations['user/SET_NAME']) {
365
+              const userName = user.nickName || user.userName
366
+              this.$store.commit('user/SET_NAME', userName)
367
+            }
368
+            
369
+            if (user && user.avatar && this.$store._mutations && this.$store._mutations['user/SET_AVATAR']) {
370
+              this.$store.commit('user/SET_AVATAR', user.avatar)
371
+            }
372
+          } catch (error) {
373
+            console.warn('Vuex状态更新失败,但不影响登录:', error)
123 374
           }
124
-        } else {
125
-          console.error('验证码获取失败:', res.msg);
126 375
         }
127
-      } catch (error) {
128
-        console.error('验证码请求失败:', error);
129
-        throw error;
376
+        
377
+      } catch (storageError) {
378
+        console.error('存储数据失败:', storageError)
379
+        throw new Error('登录状态保存失败')
130 380
       }
131
-    },
132
-    
133
-    // 用户注册
134
-    handleUserRegister() {
135
-      this.$router.push('/pages/register');
136
-    },
381
+      
382
+      // 触发全局用户信息更新事件
383
+      uni.$emit('userInfoUpdate', user)
384
+      uni.$emit('loginStatusUpdate', { 
385
+        isLoggedIn: true, 
386
+        userInfo: user 
387
+      })
388
+      
389
+      uni.showToast({
390
+        title: '登录成功',
391
+        icon: 'success'
392
+      })
393
+      
394
+      // 立即跳转页面
395
+      setTimeout(() => {
396
+        this.loginSuccess()
397
+      }, 500)
398
+      
399
+    } else {
400
+      uni.showToast({
401
+        title: res.msg || '登录失败,请重试',
402
+        icon: 'none',
403
+        duration: 3000
404
+      })
405
+    }
137 406
     
138
-    // 隐私协议
139
-    handlePrivacy() {
140
-      // 简化处理,直接跳转到webview页面
141
-      this.$router.push('/pages/common/webview?title=隐私协议&url=https://example.com/privacy');
142
-    },
407
+  } catch (error) {
408
+    uni.hideLoading()
409
+    console.error('登录请求失败:', error)
143 410
     
144
-    // 用户协议
145
-    handleUserAgrement() {
146
-      // 简化处理,直接跳转到webview页面
147
-      this.$router.push('/pages/common/webview?title=用户协议&url=https://example.com/agreement');
148
-    },
411
+    let errorMsg = '登录失败,请重试'
412
+    if (error.message) {
413
+      errorMsg = error.message
414
+    }
149 415
     
150
-    // 登录方法
151
-    async handleLogin() {
152
-      if (!this.loginForm.username) {
153
-        this.$modal.msgError("请输入账号");
154
-        return;
155
-      }
416
+    uni.showToast({
417
+      title: errorMsg,
418
+      icon: 'none',
419
+      duration: 3000
420
+    })
421
+  }
422
+},
423
+
424
+      // 获取微信登录code
425
+      getWxLoginCode() {
426
+        return new Promise((resolve, reject) => {
427
+          uni.login({
428
+            provider: 'weixin',
429
+            success: resolve,
430
+            fail: reject
431
+          })
432
+        })
433
+      },
434
+
435
+      // 检查网络状态
436
+      checkNetworkState() {
437
+        return new Promise((resolve) => {
438
+          uni.getNetworkType({
439
+            success: (res) => {
440
+              if (res.networkType === 'none') {
441
+                resolve(false)
442
+              } else {
443
+                resolve(true)
444
+              }
445
+            },
446
+            fail: () => {
447
+              resolve(true) // 如果获取网络状态失败,默认继续执行
448
+            }
449
+          })
450
+        })
451
+      },
452
+
453
+      // 获取基础URL - 使用你的配置
454
+      getBaseUrl() {
455
+        // 直接从配置中获取baseUrl
456
+        try {
457
+          // 方式1: 从全局配置获取
458
+          const app = getApp()
459
+          if (app && app.globalData && app.globalData.config && app.globalData.config.baseUrl) {
460
+            return app.globalData.config.baseUrl
461
+          }
462
+          
463
+          // 方式2: 从Vue原型获取
464
+          if (this.$config && this.$config.baseUrl) {
465
+            return this.$config.baseUrl
466
+          }
467
+          
468
+          // 方式3: 使用你的配置中的默认值
469
+          return 'https://love.aiyadianzi.cn/prod-api' // 使用你配置的生产环境地址
470
+        } catch (error) {
471
+          console.warn('获取baseUrl失败,使用默认值:', error)
472
+          return 'https://love.aiyadianzi.cn/prod-api'
473
+        }
474
+      },
475
+
476
+      // 原有方法保持不变
477
+      handleUserRegister() {
478
+        this.$tab.redirectTo(`/pages/register/index`)
479
+      },
480
+      // 隐私协议
481
+      handlePrivacy() {
482
+        const site = this.globalConfig.appInfo.agreements[0] || { title: '隐私协议', url: '' }
483
+        this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
484
+      },
485
+      // 用户协议
486
+      handleUserAgrement() {
487
+        const site = this.globalConfig.appInfo.agreements[1] || { title: '用户协议', url: '' }
488
+        this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
489
+      },
490
+      // 获取图形验证码
491
+      getCode() {
492
+        getCodeImg().then(res => {
493
+          this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
494
+          if (this.captchaEnabled) {
495
+            this.codeUrl = 'data:image/gif;base64,' + res.img
496
+            this.loginForm.uuid = res.uuid
497
+          }
498
+        }).catch(error => {
499
+          console.error('获取验证码失败:', error)
500
+          // 如果API调用失败,使用默认验证码
501
+          this.captchaEnabled = false
502
+        })
503
+      },
504
+      // 登录方法 - 使用原有的若依登录逻辑
505
+async handleLogin() {
506
+  if (!this.loginForm.username) {
507
+    this.$modal.msgError("请输入您的账号")
508
+    return
509
+  }
510
+  if (!this.loginForm.password) {
511
+    this.$modal.msgError("请输入您的密码")
512
+    return
513
+  }
514
+  if (this.captchaEnabled && !this.loginForm.code) {
515
+    this.$modal.msgError("请输入验证码")
516
+    return
517
+  }
518
+
519
+  this.$modal.loading("登录中,请耐心等待...")
520
+  
521
+  try {
522
+    // 使用原有的Login action
523
+    await this.$store.dispatch('Login', this.loginForm)
524
+          
525
+    // 获取用户信息 - 添加详细的错误处理
526
+    try {
527
+      await this.$store.dispatch('GetInfo')
156 528
       
157
-      if (!this.loginForm.password) {
158
-        this.$modal.msgError("请输入密码");
159
-        return;
160
-      }
529
+      // 确保用户信息正确存储
530
+      const userInfo = this.$store.state.user
531
+      console.log('Vuex用户信息:', userInfo)
161 532
       
162
-      if (this.captchaEnabled && !this.loginForm.code) {
163
-        this.$modal.msgError("请输入验证码");
164
-        return;
533
+      // 手动存储用户信息到本地存储,确保一致性
534
+      if (userInfo && userInfo.name) {
535
+        uni.setStorageSync('userInfo', {
536
+          userName: userInfo.name,
537
+          nickName: userInfo.name,
538
+          avatar: userInfo.avatar
539
+        })
165 540
       }
166 541
       
167
-      this.$modal.loading("登录中,请耐心等待...");
542
+      this.$modal.msgSuccess("登录成功")
168 543
       
169
-      try {
170
-        await this.pwdLogin();
171
-      } catch (error) {
172
-        this.$modal.closeLoading();
173
-        this.$modal.msgError(error.message || '登录失败');
174
-        
175
-        // 刷新验证码
176
-        if (this.captchaEnabled) {
177
-          this.getCodeSafe();
178
-        }
179
-      }
180
-    },
544
+      // 立即触发全局状态更新
545
+      uni.$emit('userInfoUpdate', userInfo)
546
+      uni.$emit('loginStatusUpdate', { 
547
+        isLoggedIn: true, 
548
+        userInfo: userInfo 
549
+      })
550
+      
551
+      // 登录成功处理 - 使用延时确保状态更新完成
552
+      setTimeout(() => {
553
+        this.loginSuccess()
554
+      }, 1000)
555
+      
556
+    } catch (infoError) {
557
+      console.error('获取用户信息失败:', infoError)
558
+      // 即使获取用户信息失败,只要登录成功就继续
559
+      this.$modal.msgSuccess("登录成功")
560
+      setTimeout(() => {
561
+        this.loginSuccess()
562
+      }, 1000)
563
+    }
181 564
     
182
-    // 密码登录
183
-    async pwdLogin() {
184
-      try {
185
-        // 使用安全的API调用方式
186
-        if (this.$store && this.$store.dispatch) {
187
-          await this.$store.dispatch('Login', this.loginForm);
188
-          this.$modal.closeLoading();
189
-          this.loginSuccess();
190
-        } else {
191
-          throw new Error('存储系统不可用');
192
-        }
193
-      } catch (error) {
194
-        console.error('登录失败:', error);
195
-        throw error;
196
-      }
197
-    },
565
+  } catch (error) {
566
+    this.$modal.closeLoading()
567
+    console.error('登录失败:', error)
198 568
     
199
-    // 登录成功后,处理函数
200
-    loginSuccess() {
201
-      // 设置用户信息
202
-      if (this.$store && this.$store.dispatch) {
203
-        this.$store.dispatch('GetInfo').then(() => {
204
-          this.$router.replace('/pages/index');
205
-        }).catch(error => {
206
-          console.error('获取用户信息失败:', error);
207
-          this.$router.replace('/pages/index');
208
-        });
209
-      } else {
210
-        this.$router.replace('/pages/index');
211
-      }
569
+    let errorMsg = '登录失败'
570
+    if (error.message) {
571
+      errorMsg = error.message
572
+    } else if (error.msg) {
573
+      errorMsg = error.msg
574
+    }
575
+    
576
+    this.$modal.msgError(errorMsg)
577
+    
578
+    if (this.captchaEnabled) {
579
+      this.getCode()
212 580
     }
213 581
   }
582
+},
583
+
584
+// 登录成功处理 - 增强版
585
+loginSuccess() {
586
+  console.log('执行登录成功跳转')
587
+  
588
+  // 多重验证登录状态
589
+  const token = getToken()
590
+  const vuexUser = this.$store.state.user
591
+  const storedUserInfo = uni.getStorageSync('userInfo')
592
+  
593
+  console.log('跳转前状态检查:')
594
+  console.log('- token:', token ? '存在' : '不存在')
595
+  console.log('- vuex用户:', vuexUser)
596
+  console.log('- 存储用户:', storedUserInfo)
597
+  
598
+  if (token) {
599
+    console.log('Token存在,允许跳转')
600
+    
601
+    // 即使没有完整的用户信息,只要token存在就允许访问
602
+    uni.switchTab({
603
+      url: '/pages/user/index',
604
+      success: () => {
605
+        console.log('跳转个人中心成功')
606
+        // 强制刷新页面数据
607
+        setTimeout(() => {
608
+          uni.$emit('forceRefreshUserInfo')
609
+        }, 500)
610
+      },
611
+      fail: (err) => {
612
+        console.error('跳转失败:', err)
613
+        // 备用跳转方案
614
+        uni.switchTab({
615
+          url: '/pages/index/index'
616
+        })
617
+      }
618
+    })
619
+  } else {
620
+    console.error('跳转时Token不存在')
621
+    uni.showToast({
622
+      title: '登录状态异常,请重新登录',
623
+      icon: 'none',
624
+      duration: 3000
625
+    })
626
+  }
214 627
 }
628
+    }
629
+  }
215 630
 </script>
216 631
 
217
-<style lang="scss" scoped>
218
-  /* 保持原有样式不变 */
632
+<style lang="scss">
219 633
   page {
220 634
     background-color: #ffffff;
221 635
   }
222
-  
223
-  .reading-tips {
224
-    margin-top: 40rpx;
225
-    padding: 20rpx;
226
-    background-color: #f9f9f9;
227
-    border-radius: 10rpx;
228
-  }
229
-  
230
-  .tip-text {
231
-    font-size: 32rpx;
232
-    font-weight: bold;
233
-    display: block;
234
-    margin-bottom: 20rpx;
235
-  }
236
-  
237
-  .benefits text {
238
-    display: block;
239
-    font-size: 28rpx;
240
-    margin: 10rpx 0;
241
-    color: #2c3e50;
242
-  }
243
-  
636
+
244 637
   .normal-login-container {
245 638
     width: 100%;
639
+    min-height: 100vh;
640
+    padding: 40rpx;
246 641
 
247 642
     .logo-content {
248 643
       width: 100%;
249 644
       font-size: 21px;
250 645
       text-align: center;
251 646
       padding-top: 15%;
252
-      
647
+      flex-direction: column;
648
+
649
+      image {
650
+        border-radius: 4px;
651
+      }
652
+
253 653
       .title {
254
-        margin-left: 10px;
654
+        margin: 10px 0;
655
+        font-size: 24px;
656
+        color: #e94f87;
657
+      }
658
+      
659
+      .subtitle {
660
+        font-size: 14px;
661
+        color: #999;
255 662
       }
256 663
     }
257 664
 
258 665
     .login-form-content {
259 666
       text-align: center;
260 667
       margin: 20px auto;
261
-      margin-top: 15%;
262
-      width: 80%;
668
+      margin-top: 10%;
669
+      width: 100%;
263 670
 
264 671
       .input-item {
265 672
         margin: 20px auto;
@@ -280,35 +687,109 @@ export default {
280 687
           text-align: left;
281 688
           padding-left: 15px;
282 689
         }
283
-
284 690
       }
285 691
 
286 692
       .login-btn {
287 693
         margin-top: 40px;
288 694
         height: 45px;
695
+        background: #e94f87 !important;
696
+        color: #fff;
697
+        border-radius: 25px;
698
+        font-size: 16px;
699
+      }
700
+      
701
+      .wechat-login-section {
702
+        margin: 20px 0;
703
+      }
704
+      
705
+      .wechat-login-btn {
706
+        display: flex;
707
+        align-items: center;
708
+        justify-content: center;
709
+        background: #07c160;
710
+        color: #fff;
711
+        border: none;
712
+        border-radius: 25px;
713
+        padding: 12px;
714
+        font-size: 16px;
715
+        width: 100%;
716
+        
717
+        .wechat-icon {
718
+          width: 20px;
719
+          height: 20px;
720
+          margin-right: 10px;
721
+        }
289 722
       }
290 723
       
291 724
       .reg {
292 725
         margin-top: 15px;
726
+        font-size: 14px;
293 727
       }
294 728
       
295 729
       .xieyi {
296 730
         color: #333;
297 731
         margin-top: 20px;
732
+        font-size: 12px;
298 733
       }
299 734
       
300 735
       .login-code {
301 736
         height: 38px;
302
-        float: right;
303
-      
737
+        position: absolute;
738
+        right: 10px;
739
+        
304 740
         .login-code-img {
305 741
           height: 38px;
306
-          position: absolute;
307
-          margin-left: 10px;
308
-          width: 200rpx;
742
+          width: 100px;
309 743
         }
310 744
       }
745
+      
746
+      /* 备案信息样式 - 只在H5环境中生效 */
747
+      /* #ifdef H5 */
748
+      .beian-info {
749
+        margin-top: 40px;
750
+        padding-top: 20px;
751
+        border-top: 1px solid #f0f0f0;
752
+        text-align: center;
753
+        color: #999;
754
+        font-size: 12px;
755
+        
756
+        .beian-links {
757
+          display: flex;
758
+          justify-content: center;
759
+          align-items: center;
760
+          flex-wrap: wrap;
761
+          gap: 15px;
762
+          margin-bottom: 8px;
763
+          
764
+          .beian-link {
765
+            color: #999;
766
+            text-decoration: none;
767
+            display: flex;
768
+            align-items: center;
769
+            transition: color 0.3s;
770
+            
771
+            &:hover {
772
+              color: #e94f87;
773
+            }
774
+            
775
+            .police-icon {
776
+              width: 14px;
777
+              height: 14px;
778
+              margin-right: 4px;
779
+            }
780
+          }
781
+        }
782
+        
783
+        .copyright {
784
+          color: #999;
785
+        }
786
+      }
787
+      /* #endif */
311 788
     }
312 789
   }
313
-
790
+  
791
+  .ad-view {
792
+    background-color: #FFFFFF;
793
+    margin-bottom: 20px;
794
+  }
314 795
 </style>

+ 300
- 0
RuoYi-App/pages/login/index.vue Parādīt failu

@@ -0,0 +1,300 @@
1
+<template>
2
+  <view class="normal-login-container">
3
+    <view class="logo-content align-center justify-center flex">
4
+      <image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
5
+      </image>
6
+      <text class="title">哎呀电子小说</text>
7
+    </view>
8
+
9
+    <view class="login-form-content">
10
+      <!-- 用户名密码登录 -->
11
+      <view class="input-item flex align-center">
12
+        <view class="iconfont icon-user icon"></view>
13
+        <input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
14
+      </view>
15
+      <view class="input-item flex align-center">
16
+        <view class="iconfont icon-password icon"></view>
17
+        <input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
18
+      </view>
19
+      
20
+      <!-- 修复验证码显示 -->
21
+      <view class="input-item flex align-center" v-if="captchaEnabled">
22
+        <view class="iconfont icon-code icon"></view>
23
+        <input v-model="loginForm.code" type="text" class="input" placeholder="请输入验证码" maxlength="4" />
24
+        <view class="login-code"> 
25
+          <image :src="codeUrl" @click="getCode" class="login-code-img" mode="widthFix"></image>
26
+        </view>
27
+      </view>
28
+
29
+      <view class="action-btn">
30
+        <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">账号登录</button>
31
+      </view>
32
+
33
+      <view class="reg text-center" v-if="register">
34
+        <text class="text-grey1">没有账号?</text>
35
+        <text @click="handleUserRegister" class="text-blue">立即注册</text>
36
+      </view>
37
+      <view class="xieyi text-center">
38
+        <text class="text-grey1">登录即代表同意</text>
39
+        <text @click="handleUserAgrement" class="text-blue">《用户协议》</text>
40
+        <text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
41
+      </view>
42
+    </view>
43
+  </view>
44
+</template>
45
+
46
+<script>
47
+import { getCodeImg, login } from '@/api/login'
48
+import { setToken } from '@/utils/auth'
49
+
50
+export default {
51
+  data() {
52
+    return {
53
+      codeUrl: "",
54
+      captchaEnabled: true,
55
+      register: true,
56
+      globalConfig: {
57
+        appInfo: {
58
+          logo: '/static/logo.png',
59
+          agreements: []
60
+        }
61
+      },
62
+      loginForm: {
63
+        username: "",
64
+        password: "",
65
+        code: "",
66
+        uuid: ''
67
+      }
68
+    }
69
+  },
70
+  onLoad(options) {
71
+    console.log('登录页面参数:', options)
72
+    this.redirectUrl = options.redirect || ''
73
+    this.safeInitConfig()
74
+    this.getCode()
75
+  },
76
+  methods: {
77
+    // 安全初始化配置
78
+    safeInitConfig() {
79
+      try {
80
+        const app = getApp()
81
+        if (app && app.globalData && app.globalData.config) {
82
+          this.globalConfig = app.globalData.config
83
+        }
84
+      } catch (error) {
85
+        console.warn('获取全局配置失败:', error)
86
+      }
87
+    },
88
+
89
+    // 获取图形验证码
90
+    async getCode() {
91
+      try {
92
+        const res = await getCodeImg()
93
+        console.log('验证码响应:', res)
94
+        
95
+        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
96
+        if (this.captchaEnabled) {
97
+          this.codeUrl = 'data:image/gif;base64,' + res.img
98
+          this.loginForm.uuid = res.uuid
99
+          console.log('验证码URL设置成功')
100
+        }
101
+      } catch (error) {
102
+        console.error('获取验证码失败:', error)
103
+        this.captchaEnabled = false
104
+        this.$modal.msgError("验证码加载失败")
105
+      }
106
+    },
107
+
108
+    // 登录方法 - 简化版
109
+    async handleLogin() {
110
+      if (!this.loginForm.username) {
111
+        this.$modal.msgError("请输入您的账号")
112
+        return
113
+      }
114
+      if (!this.loginForm.password) {
115
+        this.$modal.msgError("请输入您的密码")
116
+        return
117
+      }
118
+      if (this.captchaEnabled && !this.loginForm.code) {
119
+        this.$modal.msgError("请输入验证码")
120
+        return
121
+      }
122
+
123
+      this.$modal.loading("登录中,请耐心等待...")
124
+      
125
+      try {
126
+        // 直接调用登录API
127
+        const res = await login(
128
+          this.loginForm.username,
129
+          this.loginForm.password,
130
+          this.loginForm.code,
131
+          this.loginForm.uuid
132
+        )
133
+        
134
+        console.log('登录响应:', res)
135
+        
136
+        if (res.code === 200 && res.token) {
137
+          // 存储token
138
+          setToken(res.token)
139
+          
140
+          this.$modal.msgSuccess("登录成功")
141
+          
142
+          // 触发全局登录状态更新
143
+          uni.$emit('loginStatusUpdate', { isLoggedIn: true })
144
+          
145
+          // 跳转逻辑
146
+          setTimeout(() => {
147
+            this.loginSuccess()
148
+          }, 1000)
149
+          
150
+        } else {
151
+          this.$modal.msgError(res.msg || '登录失败')
152
+          if (this.captchaEnabled) {
153
+            this.getCode()
154
+          }
155
+        }
156
+        
157
+      } catch (error) {
158
+        console.error('登录失败:', error)
159
+        this.$modal.msgError(error.msg || '登录失败,请重试')
160
+        if (this.captchaEnabled) {
161
+          this.getCode()
162
+        }
163
+      } finally {
164
+        this.$modal.closeLoading()
165
+      }
166
+    },
167
+
168
+    // 登录成功处理
169
+    loginSuccess() {
170
+      console.log('登录成功,准备跳转')
171
+      
172
+      // 如果有重定向URL,跳转到指定页面
173
+      if (this.redirectUrl) {
174
+        console.log('跳转到重定向页面:', this.redirectUrl)
175
+        uni.redirectTo({
176
+          url: this.redirectUrl
177
+        })
178
+      } else {
179
+        // 默认跳转到首页
180
+        console.log('跳转到首页')
181
+        uni.switchTab({
182
+          url: '/pages/index/index'
183
+        })
184
+      }
185
+    },
186
+
187
+    // 其他方法保持不变
188
+    handleUserRegister() {
189
+      uni.navigateTo({
190
+        url: '/pages/register/index'
191
+      })
192
+    },
193
+    
194
+    handlePrivacy() {
195
+      const site = this.globalConfig.appInfo.agreements[0] || { title: '隐私协议', url: '' }
196
+      uni.navigateTo({
197
+        url: `/pages/common/webview/index?title=${site.title}&url=${site.url}`
198
+      })
199
+    },
200
+    
201
+    handleUserAgrement() {
202
+      const site = this.globalConfig.appInfo.agreements[1] || { title: '用户协议', url: '' }
203
+      uni.navigateTo({
204
+        url: `/pages/common/webview/index?title=${site.title}&url=${site.url}`
205
+      })
206
+    }
207
+  }
208
+}
209
+</script>
210
+
211
+<style lang="scss">
212
+page {
213
+  background-color: #ffffff;
214
+}
215
+
216
+.normal-login-container {
217
+  width: 100%;
218
+  min-height: 100vh;
219
+  padding: 40rpx;
220
+
221
+  .logo-content {
222
+    width: 100%;
223
+    font-size: 21px;
224
+    text-align: center;
225
+    padding-top: 15%;
226
+    flex-direction: column;
227
+
228
+    image {
229
+      border-radius: 4px;
230
+    }
231
+
232
+    .title {
233
+      margin: 10px 0;
234
+      font-size: 24px;
235
+      color: #e94f87;
236
+    }
237
+  }
238
+
239
+  .login-form-content {
240
+    text-align: center;
241
+    margin: 20px auto;
242
+    margin-top: 10%;
243
+    width: 100%;
244
+
245
+    .input-item {
246
+      margin: 20px auto;
247
+      background-color: #f5f6f7;
248
+      height: 45px;
249
+      border-radius: 20px;
250
+      display: flex;
251
+      align-items: center;
252
+      padding: 0 15px;
253
+
254
+      .icon {
255
+        font-size: 20px;
256
+        color: #999;
257
+        margin-right: 10px;
258
+      }
259
+
260
+      .input {
261
+        flex: 1;
262
+        font-size: 14px;
263
+        line-height: 20px;
264
+        text-align: left;
265
+        background: transparent;
266
+      }
267
+    }
268
+
269
+    .login-btn {
270
+      margin-top: 40px;
271
+      height: 45px;
272
+      background: #e94f87 !important;
273
+      color: #fff;
274
+      border-radius: 25px;
275
+      font-size: 16px;
276
+    }
277
+    
278
+    .reg {
279
+      margin-top: 15px;
280
+      font-size: 14px;
281
+    }
282
+    
283
+    .xieyi {
284
+      color: #333;
285
+      margin-top: 20px;
286
+      font-size: 12px;
287
+    }
288
+    
289
+    .login-code {
290
+      margin-left: 10px;
291
+      
292
+      .login-code-img {
293
+        height: 38px;
294
+        width: 100px;
295
+        border-radius: 4px;
296
+      }
297
+    }
298
+  }
299
+}
300
+</style>

+ 1
- 1
RuoYi-App/pages/me/index.vue Parādīt failu

@@ -93,7 +93,7 @@ export default {
93 93
     
94 94
     goToLogin() {
95 95
       uni.navigateTo({
96
-        url: '/pages/login'
96
+        url: '/pages/login/index'
97 97
       })
98 98
     },
99 99
     

+ 716
- 77
RuoYi-App/pages/novel/detail.vue Parādīt failu

@@ -1,114 +1,753 @@
1 1
 <template>
2
-  <view>
3
-    <!-- 章节间广告 -->
4
-    <ad-banner v-if="showMidAd" :ads="midAds" />
5
-    
6
-    <scroll-view scroll-y>
7
-      <!-- 章节内容 -->
8
-      <view v-for="chapter in chapters" :key="chapter.id">
9
-        <text>{{ chapter.title }}</text>
10
-        <rich-text :nodes="chapter.content" />
2
+  <view class="detail-container">
3
+    <!-- 调试信息 -->
4
+    <view v-if="debugInfo" class="debug-info">
5
+      <text>调试信息: novelId={{novelId}}, loading={{loading}}, error={{error}}</text>
6
+    </view>
7
+    
8
+    <!-- 加载状态 -->
9
+    <view v-if="loading" class="loading-container">
10
+      <uni-icons type="spinner-cycle" size="36" color="var(--primary-color)" class="loading-icon" />
11
+      <text>加载中...</text>
12
+    </view>
13
+    
14
+    <!-- 错误状态 -->
15
+    <view v-else-if="error" class="error-container">
16
+      <uni-icons type="info" size="48" color="var(--error-color)" />
17
+      <text class="error-text">{{ error }}</text>
18
+      <button class="btn-retry" @click="initDetail">重新加载</button>
19
+    </view>
20
+    
21
+    <!-- 内容区域 -->
22
+    <view v-else class="content-container">
23
+      <!-- 小说封面和基本信息 -->
24
+      <view class="novel-header">
25
+        <!-- 修改封面图片绑定 -->
26
+        <image 
27
+          :src="getSafeCoverUrl(novel.coverImg)" 
28
+          class="novel-cover"
29
+          mode="aspectFill"
30
+          @error="handleCoverError"
31
+        />
32
+        <view class="novel-info">
33
+          <text class="update-time">
34
+            更新时间: {{ formatTime(novel.updateTime) }}
35
+          </text>
36
+          <text class="novel-title">{{ novel.title || '未知小说' }}</text>
37
+          <text class="novel-author">{{ novel.author || '未知作者' }}</text>
38
+          <text class="novel-meta">{{ novel.categoryName || '未知分类' }} · {{ novel.statusText || '连载中' }}</text>
39
+          <text class="novel-meta">{{ (novel.wordCount || 0) }}字 · {{ (novel.readCount || 0) }}阅读</text>
40
+        </view>
11 41
       </view>
12 42
       
13
-      <!-- 章节底部广告 -->
14
-      <ad-banner :ads="chapterAds" />
15
-    </scroll-view>
16
-    
17
-    <!-- 底部操作栏 -->
18
-    <view class="action-bar">
19
-      <button @click="prevChapter">上一章</button>
20
-      <button @click="showCatalog">目录</button>
21
-      <button @click="nextChapter">下一章</button>
43
+      <!-- 小说描述 -->
44
+      <view class="description-section">
45
+        <text class="section-title">作品简介</text>
46
+        <text class="novel-description">{{ novel.description || '暂无简介' }}</text>
47
+      </view>
48
+      
49
+      <!-- 操作按钮 -->
50
+      <view class="action-buttons">
51
+        <button 
52
+          class="btn-add-bookshelf" 
53
+          :class="{ 'in-bookshelf': isInBookshelf }"
54
+          @click="addToBookshelf"
55
+        >
56
+          {{ isInBookshelf ? '已在书架' : '加入书架' }}
57
+        </button>
58
+        <button class="btn-start-reading" @click="startReading">
59
+          开始阅读
60
+        </button>
61
+      </view>
62
+      
63
+      <!-- 章节列表 -->
64
+      <view class="chapter-preview">
65
+        <view class="section-header">
66
+          <text class="section-title">章节列表</text>
67
+          <text class="section-more" @click="showAllChapters = !showAllChapters">
68
+            {{ showAllChapters ? '收起' : '展开' }}
69
+          </text>
70
+        </view>
71
+        <view v-if="displayedChapters.length > 0" class="chapter-list">
72
+          <view 
73
+            v-for="chapter in displayedChapters" 
74
+            :key="chapter.id" 
75
+            class="chapter-item"
76
+            @click="readChapter(chapter)"
77
+          >
78
+            <text class="chapter-title">{{ chapter.title }}</text>
79
+            <text class="chapter-time">
80
+              {{ formatTime(chapter.updateTime, 'MM-DD HH:mm') }}
81
+            </text>
82
+          </view>
83
+        </view>
84
+        <view v-else class="empty-chapters">
85
+          <text>暂无章节</text>
86
+        </view>
87
+      </view>
22 88
     </view>
23 89
   </view>
24 90
 </template>
25 91
 
26 92
 <script>
27
-import AdBanner from '@/components/AdBanner';
93
+// 导入 request 模块
94
+import request from '@/utils/request'
95
+import { getToken, setToken, removeToken } from '@/utils/auth'
28 96
 
29 97
 export default {
30
-  components: { AdBanner },
31 98
   data() {
32 99
     return {
100
+      novelId: null,
33 101
       novel: {},
34 102
       chapters: [],
35
-      currentChapter: 0,
36
-      // 确保数组初始化
37
-      midAds: [],
38
-      chapterAds: [],
39
-      showMidAd: false
40
-    };
103
+      loading: true,
104
+      error: null,
105
+      isInBookshelf: false,
106
+      showAllChapters: false,
107
+      debugInfo: true
108
+    }
109
+  },
110
+  
111
+  computed: {
112
+    // 显示前5章或全部章节
113
+    displayedChapters() {
114
+      if (this.showAllChapters) {
115
+        return this.chapters;
116
+      }
117
+      return this.chapters.slice(0, 5);
118
+    }
41 119
   },
120
+  
42 121
   onLoad(options) {
43
-    this.novelId = options.id;
44
-    this.loadNovelData();
45
-    this.scheduleMidAd();
122
+    console.log('🚨 DETAIL onLoad 开始执行');
123
+    this.novelId = options.id || this.$route.params.id;
124
+    console.log('🔍 最终 novelId:', this.novelId);
125
+    
126
+    if (!this.novelId) {
127
+      this.error = '无法获取小说信息';
128
+      this.loading = false;
129
+      return;
130
+    }
131
+    
132
+    this.initDetail();
46 133
   },
47
-  methods: {
48
-    async loadNovelData() {
49
-      // 加载小说详情
50
-      const novelRes = await this.$http.get(`/novel/detail/${this.novelId}`);
51
-      if (novelRes.data) {
52
-        this.novel = novelRes.data;
134
+  
135
+  mounted() {
136
+    // 检查 uni 对象是否可用
137
+    if (typeof uni === 'undefined') {
138
+      console.warn('⚠️ uni 对象未定义,使用 Vue Router 进行导航');
139
+    } else {
140
+      console.log('✅ uni 对象可用:', typeof uni.navigateTo);
141
+    }
142
+    console.log('🚨 DETAIL mounted 执行');
143
+    // 备选方案:如果onLoad没有获取到参数,在这里再次尝试
144
+    if (!this.novelId) {
145
+      console.log('🔄 mounted中重新获取参数');
146
+      this.novelId = this.$route.params.id;
147
+      if (this.novelId) {
148
+        this.initDetail();
53 149
       }
150
+    }
151
+  },
152
+  
153
+  methods: {
154
+    // 格式化时间
155
+    formatTime(time, format = 'YYYY-MM-DD HH:mm') {
156
+      if (!time) return ''
54 157
       
55
-      // 加载章节列表
56
-      const chapterRes = await this.$http.get(`/chapter/list/${this.novelId}`);
57
-      if (chapterRes.rows && Array.isArray(chapterRes.rows)) {
58
-        this.chapters = chapterRes.rows;
59
-      }
158
+      const date = new Date(time)
159
+      const year = date.getFullYear()
160
+      const month = String(date.getMonth() + 1).padStart(2, '0')
161
+      const day = String(date.getDate()).padStart(2, '0')
162
+      const hour = String(date.getHours()).padStart(2, '0')
163
+      const minute = String(date.getMinutes()).padStart(2, '0')
164
+      const second = String(date.getSeconds()).padStart(2, '0')
60 165
       
61
-      // 加载当前章节内容
62
-      if (this.chapters.length > 0) {
63
-        await this.loadChapterContent(0);
166
+      return format
167
+        .replace('YYYY', year)
168
+        .replace('MM', month)
169
+        .replace('DD', day)
170
+        .replace('HH', hour)
171
+        .replace('mm', minute)
172
+        .replace('ss', second)
173
+    },
174
+    
175
+    // 或者使用更简单的时间格式化
176
+    formatTimeSimple(time) {
177
+      if (!time) return ''
178
+      const date = new Date(time)
179
+      return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
180
+    },
181
+    
182
+    async initDetail() {
183
+      try {
184
+        console.log('🎬 initDetail 开始执行,novelId:', this.novelId)
185
+        this.loading = true
186
+        this.error = null
187
+        
188
+        // 并行请求小说详情和章节列表
189
+        const [detailRes, chaptersRes] = await Promise.allSettled([
190
+          this.loadNovelDetail(),
191
+          this.loadChapters()
192
+        ])
193
+        
194
+        // 处理结果
195
+        if (detailRes.status === 'fulfilled') {
196
+          console.log('✅ 小说详情加载完成')
197
+        } else {
198
+          console.error('❌ 小说详情加载失败:', detailRes.reason)
199
+        }
200
+        
201
+        if (chaptersRes.status === 'fulfilled') {
202
+          console.log('✅ 章节列表加载完成')
203
+        } else {
204
+          console.error('❌ 章节列表加载失败:', chaptersRes.reason)
205
+        }
206
+        
207
+        // 检查书架状态
208
+        this.checkBookshelfStatus()
209
+        
210
+      } catch (error) {
211
+        console.error('🎬 initDetail 执行失败:', error)
212
+        this.error = '加载失败,请重试'
213
+      } finally {
214
+        this.loading = false
215
+      }
216
+    },
217
+    
218
+    async loadNovelDetail() {
219
+      try {
220
+        console.log('📚 开始加载小说详情...')
221
+        // 明确设置不携带token
222
+        const res = await request.get(`/novel/detail/${this.novelId}`, {}, {
223
+          header: {
224
+            isToken: false
225
+          }
226
+        })
227
+        
228
+        console.log('✅ 小说详情API响应:', res)
229
+        
230
+        if (res && res.code === 200 && res.data) {
231
+          this.novel = res.data
232
+          console.log('📖 小说详情数据:', this.novel)
233
+        } else {
234
+          console.warn('⚠️ 小说详情接口返回数据异常,使用模拟数据')
235
+          // 使用模拟数据的逻辑
236
+          this.useMockData()
237
+        }
238
+      } catch (error) {
239
+        console.error('❌ 加载小说详情失败:', error)
240
+        console.warn('⚠️ 网络请求失败,使用模拟数据')
241
+        this.useMockData()
64 242
       }
65
-      
66
-      // 加载章节广告
67
-      const adRes = await this.$http.get('/ad/position?code=CHAPTER_FOOTER');
68
-      this.chapterAds = adRes.data;
69 243
     },
70 244
     
71
-    async loadChapterContent(index) {
72
-      if (index < 0 || index >= this.chapters.length) return;
245
+// detail.vue 中修改 loadChapters 方法
246
+async loadChapters() {
247
+  try {
248
+    console.log('📑 开始加载章节列表...');
249
+    
250
+    // 尝试获取token
251
+    const token = uni.getStorageSync('token');
252
+    console.log('🔑 当前token状态:', token ? '已存在' : '不存在');
253
+    
254
+    let requestConfig = {
255
+      header: {
256
+        isToken: false
257
+      }
258
+    };
259
+    
260
+    // 如果有token,使用token请求
261
+    if (token) {
262
+      requestConfig = {
263
+        header: {
264
+          'Authorization': 'Bearer ' + token,
265
+          isToken: true
266
+        }
267
+      };
268
+      console.log('🔐 使用token请求章节列表');
269
+    } else {
270
+      console.log('👤 使用公开API请求章节列表');
271
+    }
272
+    
273
+    // 尝试请求真实数据
274
+    const res = await request.get(`/chapter/list/${this.novelId}`, {}, requestConfig);
275
+    
276
+    console.log('✅ 章节列表API响应:', res);
277
+    
278
+    if (res && res.code === 200 && res.rows) {
279
+      // 注意:后端返回的是 res.rows,不是 res.data
280
+      this.chapters = res.rows;
281
+      console.log('📑 章节列表数据:', this.chapters);
282
+    } else if (res && res.rows) {
283
+      // 如果直接返回 rows 数组
284
+      this.chapters = res.rows;
285
+      console.log('📑 章节列表数据(rows):', this.chapters);
286
+    } else {
287
+      console.warn('⚠️ 章节列表接口返回异常,使用模拟数据');
288
+      this.useMockChapters();
289
+    }
290
+  } catch (error) {
291
+    console.error('❌ 加载章节列表失败:', error);
292
+    
293
+    // 如果是401错误,提示用户登录
294
+    if (error.message && error.message.includes('认证失败')) {
295
+      uni.showToast({
296
+        title: '请登录后查看章节',
297
+        icon: 'none'
298
+      });
299
+    }
300
+    
301
+    console.warn('⚠️ 网络请求失败,使用模拟章节数据');
302
+    this.useMockChapters();
303
+  }
304
+},
305
+
306
+// 改进模拟数据生成
307
+useMockChapters() {
308
+  console.log('🎭 生成模拟章节数据');
309
+  const mockChapters = [];
310
+  const chapterTitles = [
311
+    '初遇异能者', '白冰的身世', '小队集结', '首次任务', '危机四伏',
312
+    '突破极限', '新的伙伴', '黑暗组织', '真相大白', '最终决战'
313
+  ];
314
+  
315
+  // 生成更真实的模拟数据
316
+  for (let i = 0; i < 15; i++) {
317
+    const chapterNum = i + 1;
318
+    const titleIndex = i % chapterTitles.length;
319
+    mockChapters.push({
320
+      id: chapterNum,
321
+      title: `第${chapterNum}章 ${chapterTitles[titleIndex]}`,
322
+      updateTime: new Date(Date.now() - (15 - i) * 24 * 60 * 60 * 1000),
323
+      content: `这是第${chapterNum}章的内容...`,
324
+      wordCount: Math.floor(Math.random() * 3000) + 1500
325
+    });
326
+  }
327
+  
328
+  this.chapters = mockChapters;
329
+  console.log('📚 模拟章节数据生成完成:', this.chapters.length + '章');
330
+},
331
+
332
+    
333
+    useMockChapters() {
334
+      // 你的模拟章节数据逻辑
335
+      this.chapters = [
336
+        { id: 1, title: '第一章 初遇', updateTime: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000) },
337
+        { id: 2, title: '第二章 相识', updateTime: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000) },
338
+        { id: 3, title: '第三章 冒险开始', updateTime: new Date() }
339
+      ]
340
+    },
341
+    
342
+    // 其他方法保持不变...
343
+    getSafeCoverUrl(coverImg) {
344
+      if (!coverImg) return '/static/default-cover.jpg';
345
+      if (coverImg && !coverImg.includes('ishuquge.org') && !coverImg.includes('xshuquge.net')) {
346
+        return coverImg;
347
+      }
348
+      return '/static/default-cover.jpg';
349
+    },
350
+    
351
+    handleCoverError(event) {
352
+      console.log('封面图片加载失败,使用默认图片');
353
+      // 注意:在小程序中,可能需要使用不同的方式处理图片错误
354
+      this.novel.coverImg = '/static/default-cover.jpg';
355
+    },
356
+    
357
+    // 检查书架状态
358
+    checkBookshelfStatus() {
359
+      try {
360
+        const bookshelf = uni.getStorageSync('user_bookshelf') || [];
361
+        this.isInBookshelf = bookshelf.some(item => item.novelId == this.novelId);
362
+        console.log('📚 书架状态:', this.isInBookshelf ? '已在书架' : '未在书架');
363
+      } catch (error) {
364
+        console.error('检查书架状态失败:', error);
365
+      }
366
+    },
367
+    
368
+    // 加入书架
369
+    addToBookshelf() {
370
+      console.log('📚 加入书架 clicked');
73 371
       
74
-      const chapterId = this.chapters[index].id;
75
-      const res = await this.$http.get(`/chapter/content/${chapterId}`);
372
+      // 立即更新状态
373
+      this.isInBookshelf = true;
76 374
       
77
-      // 更新章节内容
78
-      if (res.data) {
79
-        this.$set(this.chapters, index, {
80
-          ...this.chapters[index],
81
-          content: res.data.content
375
+      // 保存到本地存储
376
+      try {
377
+        const bookshelf = uni.getStorageSync('user_bookshelf') || [];
378
+        const existingIndex = bookshelf.findIndex(item => item.novelId == this.novelId);
379
+        
380
+        if (existingIndex === -1) {
381
+          bookshelf.push({
382
+            novelId: this.novelId,
383
+            novel: this.novel,
384
+            addTime: new Date().getTime(),
385
+            lastReadChapter: 0
386
+          });
387
+          uni.setStorageSync('user_bookshelf', bookshelf);
388
+        }
389
+        
390
+        uni.showToast({
391
+          title: '加入书架成功',
392
+          icon: 'success',
393
+          duration: 2000
82 394
         });
83
-        this.currentChapter = index;
84
-      }}
85
-,
86
-    
87
-    // 定时显示中间广告
88
-    scheduleMidAd() {
89
-      setTimeout(async () => {
90
-        const res = await this.$http.get('/ad/position?code=MID_CHAPTER');
91
-        this.midAds = res.data;
92
-        this.showMidAd = true;
93 395
         
94
-        // 10秒后隐藏
95
-        setTimeout(() => this.showMidAd = false, 10000);
96
-      }, 30000); // 30秒后显示
396
+        console.log('✅ 加入书架成功');
397
+        
398
+      } catch (error) {
399
+        console.error('保存到书架失败:', error);
400
+        uni.showToast({
401
+          title: '加入书架失败',
402
+          icon: 'none'
403
+        });
404
+      }
97 405
     },
98 406
     
99
-    prevChapter() {
100
-      this.loadChapterContent(this.currentChapter - 1);
407
+    // 开始阅读
408
+    startReading() {
409
+      console.log('📖 开始阅读 clicked, novelId:', this.novelId);
410
+      
411
+      if (this.chapters.length === 0) {
412
+        uni.showToast({
413
+          title: '暂无章节',
414
+          icon: 'none'
415
+        });
416
+        return;
417
+      }
418
+      
419
+      const firstChapterId = this.chapters[0].id || 1;
420
+      
421
+      console.log('🎯 跳转到阅读器,参数:', {
422
+        novelId: this.novelId,
423
+        chapterId: firstChapterId
424
+      });
425
+      
426
+      // 安全跳转方法
427
+      this.safeNavigateToReader(this.novelId, firstChapterId);
101 428
     },
102 429
     
103
-    nextChapter() {
104
-      this.loadChapterContent(this.currentChapter + 1);
430
+    // 阅读指定章节
431
+    readChapter(chapter) {
432
+      console.log('阅读章节:', chapter);
433
+      const chapterId = chapter.id;
434
+      
435
+      this.safeNavigateToReader(this.novelId, chapterId);
105 436
     },
106 437
     
107
-    showCatalog() {
108
-      uni.navigateTo({
109
-        url: `/pages/novel/catalog?id=${this.novelId}`
438
+    // 安全跳转到阅读器
439
+// 安全跳转到阅读器
440
+safeNavigateToReader(novelId, chapterId) {
441
+  console.log('🔒 安全跳转: novelId=' + novelId + ', chapterId=' + chapterId);
442
+  
443
+  // 方法1: 使用 uni.navigateTo (小程序环境)
444
+  try {
445
+    console.log('🔄 尝试使用 uni.navigateTo 跳转...');
446
+    
447
+    // 检查目标页面是否存在
448
+    const pages = getCurrentPages();
449
+    console.log('📄 当前页面栈:', pages.length);
450
+    
451
+    // 构建跳转URL - 确保路径正确
452
+    const targetUrl = `/pages/novel/reader?novelId=${novelId}&chapterId=${chapterId}`;
453
+    console.log('🎯 跳转目标URL:', targetUrl);
454
+    
455
+    uni.navigateTo({
456
+      url: targetUrl,
457
+      success: (res) => {
458
+        console.log('✅ uni.navigateTo 跳转成功', res);
459
+      },
460
+      fail: (err) => {
461
+        console.error('❌ uni.navigateTo 跳转失败:', err);
462
+        this.fallbackNavigate(novelId, chapterId);
463
+      }
464
+    });
465
+    
466
+  } catch (uniError) {
467
+    console.error('❌ uni.navigateTo 异常:', uniError);
468
+    this.fallbackNavigate(novelId, chapterId);
469
+  }
470
+},
471
+
472
+// 备选跳转方案
473
+fallbackNavigate(novelId, chapterId) {
474
+  console.log('🔄 启动备选跳转方案');
475
+  
476
+  // 方法2: 直接使用 Vue Router (H5环境)
477
+  try {
478
+    console.log('🔄 尝试使用 Vue Router 跳转...');
479
+    if (this.$router) {
480
+      this.$router.push({
481
+        path: '/pages/novel/reader',
482
+        query: {
483
+          novelId: novelId,
484
+          chapterId: chapterId
485
+        }
110 486
       });
487
+      console.log('✅ Vue Router 跳转成功');
488
+      return;
111 489
     }
490
+  } catch (routerError) {
491
+    console.error('❌ Vue Router 跳转失败:', routerError);
112 492
   }
493
+  
494
+  // 方法3: 使用相对路径
495
+  try {
496
+    console.log('🔄 尝试使用相对路径跳转...');
497
+    const currentPath = window.location.pathname;
498
+    const basePath = currentPath.substring(0, currentPath.lastIndexOf('/'));
499
+    const targetUrl = `${basePath}/reader?novelId=${novelId}&chapterId=${chapterId}`;
500
+    
501
+    window.location.href = targetUrl;
502
+    console.log('✅ 相对路径跳转成功');
503
+    return;
504
+  } catch (locationError) {
505
+    console.error('❌ 相对路径跳转失败:', locationError);
506
+  }
507
+  
508
+  // 所有方法都失败
509
+  console.error('💥 所有跳转方法都失败了');
510
+  uni.showToast({
511
+    title: '跳转失败,请检查阅读器页面是否存在',
512
+    icon: 'none',
513
+    duration: 3000
514
+  });
515
+},
516
+      
517
+
518
+
519
+      
520
+
521
+    // 生成模拟小说数据
522
+    useMockNovelData() {
523
+      this.novel = {
524
+        title: '曦与她的冰姐姐',
525
+        author: '旺旺碎冰冰big',
526
+        categoryName: '都市言情',
527
+        statusText: '已完结',
528
+        wordCount: 118383,
529
+        readCount: 9972,
530
+        description: '这是一个异能者的世界。孤曦和白冰都可谓同年龄天才。十岁的相遇。十五岁同队。这是一个小队成长的故事。白冰又有什么样的身世呢。变异生物和人类最终的大结局会是怎样?内多人物。'
531
+      };
532
+    },
533
+    
534
+    // 生成模拟章节数据
535
+    generateMockChapters() {
536
+      const chapters = [];
537
+      const chapterTitles = [
538
+        '初遇', '相识', '冒险开始', '新的伙伴', '危机降临',
539
+        '突破', '成长', '离别', '重逢', '最终决战'
540
+      ];
541
+      
542
+      for (let i = 0; i < 10; i++) {
543
+        chapters.push({
544
+          id: i + 1,
545
+          title: `第${i + 1}章 ${chapterTitles[i] || '新的篇章'}`,
546
+          updateTime: new Date(Date.now() - (10 - i) * 24 * 60 * 60 * 1000)
547
+        });
548
+      }
549
+      return chapters;
550
+    }
551
+  }
552
+}
553
+</script>
554
+
555
+<style scoped>
556
+.detail-container {
557
+  padding: 20rpx;
558
+  min-height: 100vh;
559
+  background-color: #f5f5f5;
560
+}
561
+
562
+/* 调试信息样式 */
563
+.debug-info {
564
+  background: #ffeb3b;
565
+  color: #333;
566
+  padding: 10rpx;
567
+  font-size: 24rpx;
568
+  text-align: center;
569
+  border-bottom: 1rpx solid #ffc107;
570
+}
571
+
572
+.loading-container, .error-container {
573
+  display: flex;
574
+  flex-direction: column;
575
+  align-items: center;
576
+  justify-content: center;
577
+  padding: 200rpx 0;
578
+  text-align: center;
579
+}
580
+
581
+.loading-icon {
582
+  animation: rotate 1s linear infinite;
583
+  margin-bottom: 20rpx;
584
+}
585
+
586
+@keyframes rotate {
587
+  from { transform: rotate(0deg); }
588
+  to { transform: rotate(360deg); }
589
+}
590
+
591
+.error-text {
592
+  font-size: 32rpx;
593
+  color: var(--error-color);
594
+  margin: 20rpx 0;
595
+}
596
+
597
+.btn-retry {
598
+  background-color: var(--primary-color);
599
+  color: white;
600
+  padding: 20rpx 40rpx;
601
+  border-radius: 10rpx;
602
+  margin-top: 20rpx;
603
+}
604
+
605
+.novel-header {
606
+  display: flex;
607
+  background: white;
608
+  padding: 30rpx;
609
+  border-radius: 16rpx;
610
+  margin-bottom: 30rpx;
611
+}
612
+
613
+.novel-cover {
614
+  width: 200rpx;
615
+  height: 280rpx;
616
+  border-radius: 12rpx;
617
+  margin-right: 30rpx;
618
+  background-color: #f0f0f0;
619
+}
620
+
621
+.novel-info {
622
+  flex: 1;
623
+  display: flex;
624
+  flex-direction: column;
625
+}
626
+
627
+.novel-title {
628
+  font-size: 36rpx;
629
+  font-weight: bold;
630
+  margin-bottom: 15rpx;
631
+  line-height: 1.4;
632
+}
633
+
634
+.novel-author {
635
+  font-size: 30rpx;
636
+  color: #666;
637
+  margin-bottom: 10rpx;
638
+}
639
+
640
+.novel-meta {
641
+  font-size: 26rpx;
642
+  color: #888;
643
+  margin-bottom: 8rpx;
644
+}
645
+
646
+.description-section {
647
+  background: white;
648
+  padding: 30rpx;
649
+  border-radius: 16rpx;
650
+  margin-bottom: 30rpx;
651
+}
652
+
653
+.section-title {
654
+  font-size: 32rpx;
655
+  font-weight: bold;
656
+  margin-bottom: 20rpx;
657
+  display: block;
658
+}
659
+
660
+.novel-description {
661
+  font-size: 28rpx;
662
+  line-height: 1.6;
663
+  color: #666;
664
+}
665
+
666
+.action-buttons {
667
+  display: flex;
668
+  gap: 20rpx;
669
+  margin-bottom: 30rpx;
670
+}
671
+
672
+.btn-add-bookshelf, .btn-start-reading {
673
+  flex: 1;
674
+  padding: 25rpx;
675
+  border-radius: 12rpx;
676
+  font-size: 30rpx;
677
+  text-align: center;
678
+  transition: all 0.3s;
679
+}
680
+
681
+.btn-add-bookshelf {
682
+  background-color: #f8f9fa;
683
+  color: var(--primary-color);
684
+  border: 2rpx solid var(--primary-color);
685
+}
686
+
687
+.btn-add-bookshelf.in-bookshelf {
688
+  background-color: #e8f5e8;
689
+  color: #67c23a;
690
+  border-color: #67c23a;
691
+}
692
+
693
+.btn-start-reading {
694
+  background-color: var(--primary-color);
695
+  color: white;
696
+}
697
+
698
+.btn-start-reading:active {
699
+  opacity: 0.8;
700
+}
701
+
702
+.chapter-preview {
703
+  background: white;
704
+  padding: 30rpx;
705
+  border-radius: 16rpx;
706
+}
707
+
708
+.section-header {
709
+  display: flex;
710
+  justify-content: space-between;
711
+  align-items: center;
712
+  margin-bottom: 20rpx;
713
+}
714
+
715
+.section-more {
716
+  font-size: 26rpx;
717
+  color: var(--primary-color);
718
+}
719
+
720
+.chapter-list {
721
+  margin-bottom: 20rpx;
722
+}
723
+
724
+.chapter-item {
725
+  display: flex;
726
+  justify-content: space-between;
727
+  align-items: center;
728
+  padding: 20rpx 0;
729
+  border-bottom: 1rpx solid #f0f0f0;
730
+  cursor: pointer;
731
+}
732
+
733
+.chapter-item:last-child {
734
+  border-bottom: none;
735
+}
736
+
737
+.chapter-title {
738
+  font-size: 28rpx;
739
+  color: #333;
740
+  flex: 1;
741
+}
742
+
743
+.chapter-time {
744
+  font-size: 24rpx;
745
+  color: #999;
746
+}
747
+
748
+.empty-chapters {
749
+  text-align: center;
750
+  padding: 40rpx;
751
+  color: #999;
113 752
 }
114
-</script>
753
+</style>

+ 17
- 14
RuoYi-App/pages/novel/list.vue Parādīt failu

@@ -241,11 +241,12 @@ useVueRouterFallback(url) {
241 241
 // 在 list.vue 中修改 openNovel 方法
242 242
 openNovel(novel) {
243 243
   try {
244
-    console.log('准备打开小说:', novel);
244
+    console.log('📖 准备打开小说:', novel);
245 245
     
246
-    const novelId = novel.id || novel.novelId || novel.bookId;
246
+    // 确保获取到正确的小说ID
247
+    const novelId = novel.id || novel.novelId;
247 248
     if (!novelId) {
248
-      console.error('无法获取小说ID:', novel);
249
+      console.error('无法获取小说ID:', novel);
249 250
       uni.showToast({
250 251
         title: '小说ID不存在',
251 252
         icon: 'none'
@@ -253,21 +254,23 @@ openNovel(novel) {
253 254
       return;
254 255
     }
255 256
     
256
-    console.log('跳转到阅读页面,小说ID:', novelId);
257
+    console.log('🎯 跳转到详情页,小说ID:', novelId);
257 258
     
258
-    // 使用若依框架的标准导航方式
259
+    // 使用动态路由参数
259 260
     this.$router.push({
260
-      path: '/pages/novel/reader',
261
-      query: { 
262
-        novelId: novelId,
263
-        chapterId: 1
261
+      name: 'NovelDetail',
262
+      params: { 
263
+        id: novelId
264 264
       }
265 265
     });
266 266
   } catch (error) {
267
-    console.error('openNovel方法执行异常:', error);
268
-    uni.showToast({
269
-      title: '发生未知错误',
270
-      icon: 'none'
267
+    console.error('💥 openNovel方法执行异常:', error);
268
+    
269
+    // 备选方案:使用查询参数
270
+    const novelId = novel.id || novel.novelId;
271
+    this.$router.push({
272
+      path: '/pages/novel/detail',
273
+      query: { novelId }
271 274
     });
272 275
   }
273 276
 },
@@ -674,7 +677,7 @@ fallbackNavigate(novelId) {
674 677
     applyAuthor() {
675 678
       if (!this.$store.getters.token) {
676 679
         uni.showToast({ title: '请先登录', icon: 'none' });
677
-        uni.navigateTo({ url: '/pages/login' });
680
+        uni.navigateTo({ url: '/pages/login/index' });
678 681
         return;
679 682
       }
680 683
       uni.navigateTo({ url: '/pages/author/apply' });

+ 0
- 155
RuoYi-App/pages/novel/read.vue Parādīt failu

@@ -1,155 +0,0 @@
1
-<template>
2
-  <view class="reading-container">
3
-    <!-- 顶部章节信息 -->
4
-    <view class="chapter-header">
5
-      <text class="chapter-title">{{ chapter.title }}</text>
6
-    </view>
7
-    
8
-    <!-- 小说内容 -->
9
-    <scroll-view scroll-y class="content-container">
10
-      <rich-text :nodes="chapter.content" class="content-text"></rich-text>
11
-    </scroll-view>
12
-    
13
-    <!-- 底部操作栏 -->
14
-    <view class="action-bar">
15
-      <button @click="prevChapter">上一章</button>
16
-      <button @click="showCatalog">目录</button>
17
-      <button @click="nextChapter">下一章</button>
18
-    </view>
19
-    
20
-    <!-- 章节间广告 -->
21
-    <ad-banner v-if="showAd" :ads="midAds" />
22
-  </view>
23
-</template>
24
-
25
-<script>
26
-import AdBanner from '@/components/AdBanner';
27
-
28
-export default {
29
-  components: { AdBanner },
30
-  data() {
31
-    return {
32
-      novelId: null,
33
-      chapterId: null,
34
-      chapter: {},
35
-      chapters: [],
36
-      showAd: false,
37
-      midAds: []
38
-    };
39
-  },
40
-  onLoad(options) {
41
-    this.novelId = options.novelId;
42
-    this.chapterId = options.chapterId;
43
-    this.loadChapterData();
44
-    this.scheduleAd();
45
-  },
46
-  methods: {
47
-    async loadChapterData() {
48
-      try {
49
-        // 加载章节列表
50
-        const chaptersRes = await this.$http.get(`/novel/${this.novelId}/chapters`);
51
-        this.chapters = chaptersRes.data;
52
-        
53
-        // 加载当前章节内容
54
-        await this.loadChapterContent(this.chapterId || this.chapters[0].id);
55
-      } catch (e) {
56
-        uni.showToast({ title: '加载失败', icon: 'none' });
57
-      }
58
-    },
59
-    async loadChapterContent(chapterId) {
60
-      const res = await this.$http.get(`/novel/chapter/${chapterId}`);
61
-      this.chapter = res.data;
62
-      this.chapterId = chapterId;
63
-      
64
-      // 保存阅读进度
65
-      this.saveReadingProgress();
66
-    },
67
-    saveReadingProgress() {
68
-      const progress = {
69
-        novelId: this.novelId,
70
-        chapterId: this.chapterId,
71
-        timestamp: Date.now()
72
-      };
73
-      uni.setStorageSync('readingProgress', progress);
74
-      
75
-      // 同步到后台
76
-      if (this.$store.getters.token) {
77
-        this.$http.post('/user/reading-progress', progress);
78
-      }
79
-    },
80
-    prevChapter() {
81
-      const currentIndex = this.chapters.findIndex(c => c.id === this.chapterId);
82
-      if (currentIndex > 0) {
83
-        this.loadChapterContent(this.chapters[currentIndex - 1].id);
84
-      }
85
-    },
86
-    nextChapter() {
87
-      const currentIndex = this.chapters.findIndex(c => c.id === this.chapterId);
88
-      if (currentIndex < this.chapters.length - 1) {
89
-        this.loadChapterContent(this.chapters[currentIndex + 1].id);
90
-      }
91
-    },
92
-    showCatalog() {
93
-      uni.navigateTo({
94
-        url: `/pages/novel/catalog?novelId=${this.novelId}`
95
-      });
96
-    },
97
-    async scheduleAd() {
98
-      // 30秒后显示广告
99
-      setTimeout(async () => {
100
-        const res = await this.$http.get('/ad/position?code=CHAPTER_MID');
101
-        this.midAds = res.data;
102
-        this.showAd = true;
103
-        
104
-        // 10秒后隐藏
105
-        setTimeout(() => this.showAd = false, 10000);
106
-      }, 30000);
107
-    }
108
-  }
109
-}
110
-</script>
111
-
112
-<style scoped>
113
-.reading-container {
114
-  padding: 20rpx;
115
-  padding-bottom: 120rpx; /* 为操作栏留空间 */
116
-}
117
-
118
-.chapter-header {
119
-  padding: 20rpx 0;
120
-  border-bottom: 1rpx solid #eee;
121
-}
122
-
123
-.chapter-title {
124
-  font-size: 36rpx;
125
-  font-weight: bold;
126
-}
127
-
128
-.content-container {
129
-  height: calc(100vh - 300rpx);
130
-  padding: 30rpx 0;
131
-}
132
-
133
-.content-text {
134
-  font-size: 32rpx;
135
-  line-height: 1.8;
136
-}
137
-
138
-.action-bar {
139
-  position: fixed;
140
-  bottom: 0;
141
-  left: 0;
142
-  right: 0;
143
-  display: flex;
144
-  background: white;
145
-  padding: 20rpx;
146
-  box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1);
147
-  z-index: 999;
148
-}
149
-
150
-.action-bar button {
151
-  flex: 1;
152
-  margin: 0 10rpx;
153
-  font-size: 28rpx;
154
-}
155
-</style>

+ 463
- 321
RuoYi-App/pages/novel/reader.vue Parādīt failu

@@ -1,412 +1,554 @@
1 1
 <template>
2
-  <view class="reader-container" :class="{ 'night-mode': nightMode }">
3
-    <!-- 顶部导航栏 -->
4
-    <view class="reader-header" v-if="showHeader">
5
-      <view class="header-left">
6
-        <text class="iconfont icon-back" @click="goBack"></text>
7
-        <text class="novel-title">{{novelTitle}}</text>
8
-      </view>
9
-      <view class="header-right">
10
-        <text class="iconfont icon-menu" @click="showMenu"></text>
11
-      </view>
2
+  <view class="reader-container">
3
+    <!-- 登录提示 -->
4
+    <view v-if="requireLogin" class="login-prompt">
5
+      <text>免费章节已结束,登录后继续阅读</text>
6
+      <button class="btn-login" @click="showLoginPrompt">立即登录</button>
12 7
     </view>
13
-
14
-    <!-- 阅读内容区域 -->
15
-    <scroll-view 
16
-      class="reader-content" 
17
-      scroll-y 
18
-      :scroll-top="scrollTop"
19
-      @scroll="onScroll"
20
-      @touchstart="onTouchStart"
21
-      @touchend="onTouchEnd"
22
-    >
23
-      <view class="chapter-title">{{chapterTitle}}</view>
24
-      <view class="content-text">
25
-        <text>{{content}}</text>
8
+    
9
+    <!-- 广告提示 -->
10
+    <view v-if="showAd" class="ad-prompt">
11
+      <text>VIP用户免广告,开通VIP享受更好的阅读体验</text>
12
+    </view>
13
+    <!-- 加载状态 -->
14
+    <view v-if="loading" class="loading-container">
15
+      <uni-icons type="spinner-cycle" size="36" color="var(--primary-color)" class="loading-icon" />
16
+      <text>加载中...</text>
17
+    </view>
18
+    
19
+    <!-- 错误状态 -->
20
+    <view v-else-if="error" class="error-container">
21
+      <uni-icons type="info" size="48" color="var(--error-color)" />
22
+      <text class="error-text">{{ error }}</text>
23
+      <button class="btn-retry" @click="retryLoad">重新加载</button>
24
+    </view>
25
+    
26
+    <!-- 内容区域 -->
27
+    <view v-else class="content-container">
28
+      <!-- 小说标题 -->
29
+      <view class="novel-header">
30
+        <text class="novel-title">{{ novel.title || '加载中...' }}</text>
31
+        <text class="novel-author">{{ novel.author || '未知作者' }}</text>
26 32
       </view>
27 33
       
28
-      <!-- 加载状态 -->
29
-      <view v-if="loading" class="loading-container">
30
-        <text>加载中...</text>
34
+      <!-- 章节标题 -->
35
+      <view class="chapter-header">
36
+        <text class="chapter-title">{{ currentChapter.title || '第一章' }}</text>
31 37
       </view>
32 38
       
33
-      <!-- 错误状态 -->
34
-      <view v-if="error" class="error-container">
35
-        <text>{{error}}</text>
36
-        <button @click="loadChapterContent">重试</button>
37
-      </view>
38
-    </scroll-view>
39
-
40
-    <!-- 底部控制栏 -->
41
-    <view class="reader-footer" v-if="showFooter">
42
-      <view class="progress-text">
43
-        第{{currentChapter}}章 {{progress}}%
44
-      </view>
45
-      <view class="control-buttons">
46
-        <text class="iconfont icon-catalog" @click="showCatalog"></text>
47
-        <text class="iconfont icon-font" @click="showFontSettings"></text>
48
-        <text class="iconfont icon-night" @click="toggleNightMode"></text>
49
-        <text class="iconfont icon-download" @click="downloadChapter"></text>
50
-      </view>
51
-    </view>
52
-
53
-    <!-- 广告位 -->
54
-    <ad v-if="showAd" unit-id="您的广告单元ID" class="reader-ad"></ad>
55
-
56
-    <!-- 设置面板 -->
57
-    <view class="settings-panel" v-if="showSettings">
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>
73
-    </view>
74
-
75
-    <!-- 目录面板 -->
76
-    <view class="catalog-panel" v-if="showCatalogPanel">
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>
39
+      <!-- 章节内容 -->
40
+      <scroll-view scroll-y class="chapter-content">
41
+        <text class="content-text">
42
+          {{ chapterContent || '正在加载章节内容...' }}
43
+        </text>
91 44
       </scroll-view>
45
+      
46
+      <!-- 底部操作栏 -->
47
+      <view class="action-bar">
48
+        <button class="btn-action" @click="prevChapter" :disabled="!hasPrevChapter">
49
+          上一章
50
+        </button>
51
+        <button class="btn-action" @click="showCatalog">
52
+          目录
53
+        </button>
54
+        <button class="btn-action" @click="nextChapter" :disabled="!hasNextChapter">
55
+          下一章
56
+        </button>
57
+      </view>
92 58
     </view>
93 59
   </view>
94 60
 </template>
95 61
 
96 62
 <script>
63
+import request from '@/utils/request'
64
+import { getToken, setToken, removeToken } from '@/utils/auth'
65
+
97 66
 export default {
98 67
   data() {
99 68
     return {
100
-      novelId: '',
101
-      chapterId: '',
102
-      novelTitle: '加载中...',
103
-      chapterTitle: '加载中...',
104
-      content: '正在加载内容...',
105
-      currentChapter: 1,
106
-      totalChapters: 0,
107
-      progress: 0,
108
-      scrollTop: 0,
109
-      showHeader: false,
110
-      showFooter: false,
111
-      showSettings: false,
112
-      showCatalogPanel: false,
113
-      showAd: false,
114
-      nightMode: false,
115
-      touchStartX: 0,
116
-      touchStartY: 0,
117
-      lastScrollPosition: 0,
118
-      loading: false,
69
+      novelId: null,
70
+      novel: {},
71
+      chapters: [],
72
+      currentChapter: {},
73
+      chapterContent: '',
74
+      loading: true,
119 75
       error: null,
120
-      fontSize: 18,
121
-      backgroundColor: '#f5f0e1',
122
-      backgroundOptions: [
123
-        { value: '#f5f0e1', name: '羊皮纸' },
124
-        { value: '#e8f5e9', name: '护眼绿' },
125
-        { value: '#e3f2fd', name: '天空蓝' },
126
-        { value: '#fce4ec', name: '粉红' },
127
-        { value: '#1a1a1a', name: '夜间' }
128
-      ],
129
-      chapterList: []
76
+      currentChapterIndex: 0,
77
+          // 新增字段
78
+    freeChapters: 5, // 免费章节数
79
+    adStartChapter: 15, // 开始显示广告的章节
80
+    isVip: false,
81
+    isLoggedIn: false,
82
+    showLoginModal: false
83
+    }
84
+  },
85
+  
86
+  computed: {
87
+    // 计算当前章节是否需要登录
88
+  requireLogin() {
89
+    return this.currentChapterIndex >= this.freeChapters && !this.isLoggedIn;
90
+  },
91
+  
92
+  // 计算当前章节是否需要显示广告
93
+  showAd() {
94
+    return this.currentChapterIndex >= this.adStartChapter && !this.isVip;
95
+  },
96
+    hasPrevChapter() {
97
+      return this.currentChapterIndex > 0;
98
+    },
99
+    hasNextChapter() {
100
+      return this.currentChapterIndex < this.chapters.length - 1;
130 101
     }
131 102
   },
132
-  onLoad(options) {
133
-    console.log('阅读器页面加载,参数:', options);
103
+  
104
+  // 使用 uni-app 生命周期
105
+onLoad(options) {
106
+  console.log('📥 READER onLoad 参数:', options);
107
+  console.log('📍 READER 路由信息:', this.$route);
108
+  
109
+  // 简化参数获取
110
+  this.novelId = options.novelId || this.$route.query.novelId;
111
+  const chapterId = options.chapterId || this.$route.query.chapterId;
112
+  
113
+  console.log('🔍 READER 最终参数 - novelId:', this.novelId, 'chapterId:', chapterId);
114
+  
115
+  if (!this.novelId) {
116
+    console.error('❌ READER: 无法获取小说ID');
117
+    this.error = '无法获取小说信息';
118
+    this.loading = false;
119
+    return;
120
+  }
121
+  
122
+  this.initReader();
123
+},
124
+// reader.vue - 添加 onShow 方法
125
+onShow() {
126
+  console.log('🔍 onShow 生命周期');
127
+  // 再次检查参数,确保不会漏掉
128
+  if (!this.novelId && this.$route.query.novelId) {
129
+    console.log('🔄 onShow 中检测到参数,重新初始化');
130
+    this.initReader(this.$route.query);
131
+  }
132
+},
133
+  // 同时使用 Vue 生命周期作为备选
134
+mounted() {
135
+  console.log('🔄 mounted 生命周期');
136
+  // 如果 onLoad 没有获取到参数,尝试从路由获取
137
+  if (!this.novelId) {
138
+    const route = this.$route;
139
+    console.log('🔄 从 $route 获取参数:', route);
134 140
     
135
-    // 确保参数获取方式正确
136
-    this.novelId = options.novelId || '';
137
-    this.chapterId = options.chapterId || '1';
141
+    // 合并所有可能的参数来源
142
+    const allParams = {
143
+      ...(route.params || {}),
144
+      ...(route.query || {}),
145
+      novelId: route.query.novelId // 确保novelId被单独提取
146
+    };
138 147
     
139
-    console.log('novelId:', this.novelId, 'chapterId:', this.chapterId);
148
+    this.initReader(allParams);
149
+  }
150
+},
151
+
152
+  
153
+  methods: {
154
+  // 检查用户状态
155
+  checkUserStatus() {
156
+    const token = uni.getStorageSync('token');
157
+    this.isLoggedIn = !!token;
140 158
     
141
-    if (!this.novelId) {
142
-      console.error('小说ID参数缺失');
143
-      this.error = '小说ID参数缺失';
159
+    // 这里可以调用API检查VIP状态
160
+    // const userInfo = uni.getStorageSync('userInfo');
161
+    // this.isVip = userInfo && userInfo.isVip;
162
+  },
163
+  
164
+  // 修改下一章方法,添加限制
165
+  async nextChapter() {
166
+    if (!this.hasNextChapter) {
144 167
       uni.showToast({
145
-        title: '小说ID参数缺失',
168
+        title: '已经是最后一章了',
146 169
         icon: 'none'
147 170
       });
148 171
       return;
149 172
     }
150 173
     
151
-    // 显示加载提示
152
-    uni.showLoading({
153
-      title: '加载中...',
154
-      mask: true
155
-    });
174
+    // 检查是否需要登录
175
+    if (this.currentChapterIndex + 1 >= this.freeChapters && !this.isLoggedIn) {
176
+      this.showLoginPrompt();
177
+      return;
178
+    }
156 179
     
157
-    this.loadChapterContent();
158
-  },
159
-  onShow() {
160
-    console.log('reader onShow');
180
+    // 正常跳转到下一章
181
+    this.currentChapterIndex++;
182
+    this.currentChapter = this.chapters[this.currentChapterIndex];
183
+    await this.loadChapterContent(this.currentChapter.id);
161 184
     
162
-    // 确保页面显示时也能处理参数
163
-    const pages = getCurrentPages();
164
-    const currentPage = pages[pages.length - 1];
165
-    if (currentPage && currentPage.options) {
166
-      const options = currentPage.options;
167
-      console.log('当前页面参数:', options);
168
-      
169
-      if (options.novelId && (!this.novelId || this.novelId !== options.novelId)) {
170
-        this.novelId = options.novelId;
171
-        this.chapterId = options.chapterId || '1';
172
-        this.loadChapterContent();
173
-      }
185
+    // 检查是否需要显示广告
186
+    if (this.showAd) {
187
+      this.showChapterAd();
174 188
     }
175 189
   },
176
-  // 添加所有生命周期钩子进行调试
177
-  beforeCreate() {
178
-    console.log('reader beforeCreate');
179
-  },
180
-  created() {
181
-    console.log('reader created');
182
-  },
183
-  beforeMount() {
184
-    console.log('reader beforeMount');
185
-  },
186
-  mounted() {
187
-    console.log('reader mounted');
188
-  },
189
-  onReady() {
190
-    console.log('reader onReady');
191
-  },
192
-  onHide() {
193
-    console.log('reader onHide');
190
+  // 显示登录提示
191
+  showLoginPrompt() {
192
+    uni.showModal({
193
+      title: '登录提示',
194
+      content: `免费阅读前${this.freeChapters}章,第${this.currentChapterIndex + 2}章需要登录后阅读\n是否立即登录?`,
195
+      confirmText: '去登录',
196
+      cancelText: '取消',
197
+      success: (res) => {
198
+        if (res.confirm) {
199
+          uni.navigateTo({
200
+            url: '/pages/login/index'
201
+          });
202
+        }
203
+      }
204
+    });
194 205
   },
195
-  onUnload() {
196
-    console.log('reader onUnload');
206
+  
207
+  // 显示章节广告
208
+  showChapterAd() {
209
+    // 这里可以集成广告SDK
210
+    console.log('显示章节广告');
211
+    uni.showToast({
212
+      title: '广告展示中...',
213
+      icon: 'none'
214
+    });
197 215
   },
198
-  methods: {
199
-    async loadChapterContent() {
200
-      console.log('开始加载章节内容');
216
+  
217
+  // 在初始化时检查用户状态
218
+    async initReader(params) {
219
+      console.log('🎬 初始化阅读器,参数:', params);
220
+    console.log('📍 当前路由查询参数:', this.$route.query);
201 221
       
202
-      this.loading = true;
203
-      this.error = null;
222
+    // 修复参数获取逻辑 - 从多个位置获取novelId
223
+    this.novelId = params.novelId || 
224
+                   params.id || 
225
+                   this.$route.query.novelId || 
226
+                   this.$route.query.id ||
227
+                   (this.$route.params && this.$route.params.id);
204 228
       
229
+      console.log('🔍 最终获取到的小说ID:', this.novelId);
230
+          // 检查用户状态
231
+    this.checkUserStatus();
232
+    // 如果还是没有获取到,从URL中解析
233
+    if (!this.novelId) {
234
+      console.log('🔄 尝试从URL解析参数...');
235
+      const urlParams = new URLSearchParams(window.location.search);
236
+      this.novelId = urlParams.get('novelId') || urlParams.get('id');
237
+      console.log('🔍 从URL解析到的小说ID:', this.novelId);
238
+    }
239
+    
240
+    if (!this.novelId) {
241
+      this.error = '无法获取小说ID,请返回重新选择';
242
+      this.loading = false;
243
+      console.error('❌ 小说ID为空,所有获取方式都失败了');
244
+      console.error('📋 可用参数:', {
245
+        params: params,
246
+        routeQuery: this.$route.query,
247
+        routeParams: this.$route.params,
248
+        fullPath: this.$route.fullPath
249
+      });
250
+      return;
251
+    }
252
+    
253
+      
254
+      try {
255
+        await this.loadNovelDetail();
256
+        await this.loadChapters();
257
+        await this.loadFirstChapterContent();
258
+      } catch (error) {
259
+        console.error('💥 初始化失败:', error);
260
+        this.error = '加载失败: ' + (error.message || '未知错误');
261
+      } finally {
262
+        this.loading = false;
263
+      }
264
+    },
265
+    
266
+    // 加载小说详情
267
+    async loadNovelDetail() {
268
+      console.log('📚 加载小说详情,ID:', this.novelId);
205 269
       try {
206
-        console.log('开始请求章节内容,章节ID:', this.chapterId);
270
+      // 使用导入的 request,而不是 this.$http
271
+      const res = await request.get(`/novel/detail/${this.novelId}`, {}, {
272
+        header: { isToken: false }
273
+      });
274
+      console.log('✅ 小说详情响应:', res);
275
+      
276
+      if (res && res.code === 200 && res.data) {
277
+        this.novel = res.data;
278
+        console.log('✅ 成功加载小说详情:', this.novel.title);
279
+      } else {
280
+        console.warn('⚠️ 小说详情接口返回异常:', res);
281
+        this.useMockNovelData();
282
+      }
283
+      } catch (error) {
284
+        console.error('❌ 加载小说详情失败:', error);
285
+    // 使用模拟数据继续流程
286
+    this.useMockNovelData();
287
+  }
288
+},
289
+    useMockNovelData() {
290
+  this.novel = {
291
+    title: '加载中的小说',
292
+    author: '加载中...',
293
+    description: '正在加载小说信息...'
294
+  };
295
+},
296
+    // 加载章节列表
297
+    async loadChapters() {
298
+      console.log('📑 加载章节列表,小说ID:', this.novelId);
299
+      try {
300
+      const token = getToken();
301
+      let requestConfig = {
302
+        header: { isToken: false }
303
+      };
304
+      
305
+      if (token) {
306
+        requestConfig.header = {
307
+          'Authorization': 'Bearer ' + token,
308
+          isToken: true
309
+        };
310
+      }
311
+      const res = await request.get(`/chapter/list/${this.novelId}`, {}, requestConfig);
312
+      console.log('✅ 章节列表响应:', res);
313
+        
314
+        let chapters = [];
207 315
         
208
-    // 确保$http对象可用
209
-    if (!this.$http || typeof this.$http.get !== 'function') {
210
-      throw new Error('网络请求不可用');
316
+      if (res && res.code === 200 && res.rows) {
317
+        chapters = res.rows;
318
+      } else if (res && res.rows) {
319
+        chapters = res.rows;
320
+      }
321
+      
322
+      if (chapters.length > 0) {
323
+        this.chapters = chapters;
324
+        console.log(`✅ 成功加载 ${chapters.length} 个章节`);
325
+      } else {
326
+        this.useMockChapters();
327
+        console.log('⚠️ 使用模拟章节数据');
328
+      }
329
+      } catch (error) {
330
+      console.error('❌ 加载章节列表失败:', error);
331
+      this.useMockChapters();
211 332
     }
333
+    },
212 334
     
213
-    // 使用若依框架的API调用方式
214
-    const res = await this.$http.get(`/novel/chapter/${this.chapterId}`);
215
-    console.log('章节内容响应:', res);
335
+    // 加载第一章内容
336
+    async loadFirstChapterContent() {
337
+      if (this.chapters.length > 0) {
338
+        this.currentChapterIndex = 0;
339
+        this.currentChapter = this.chapters[0];
340
+        await this.loadChapterContent(this.chapters[0].id);
341
+      }
342
+    },
216 343
     
217
-    if (res.code === 200) {
218
-      this.novelTitle = res.data.novelTitle || '未知小说';
219
-      this.chapterTitle = res.data.chapterTitle || '未知章节';
220
-      this.content = res.data.content || '';
221
-      this.currentChapter = res.data.chapterIndex || 1;
222
-      this.totalChapters = res.data.totalChapters || 0;
344
+    // 加载章节内容
345
+    async loadChapterContent(chapterId) {
346
+      console.log('📖 加载章节内容,章节ID:', chapterId);
223 347
       
348
+      if (!chapterId) {
349
+        console.error('❌ 章节ID为空');
350
+        return;
351
+      }
352
+      
353
+      this.loading = true;
354
+      
355
+      try {
356
+      const token = getToken();
357
+      let requestConfig = {
358
+        header: { isToken: false }
359
+      };
360
+      
361
+      if (token) {
362
+        requestConfig.header = {
363
+          'Authorization': 'Bearer ' + token,
364
+          isToken: true
365
+        };
366
+      }
367
+      const res = await request.get(`/chapter/content/${chapterId}`, {}, requestConfig);
368
+      console.log('✅ 章节内容响应:', res);
369
+      
370
+      if (res && res.code === 200 && res.data) {
371
+        // 后端返回的数据在 res.data 中
372
+        this.chapterContent = res.data.content || '本章节内容为空';
373
+        console.log('✅ 成功加载章节内容');
374
+      } else if (res && res.content) {
375
+        // 或者直接返回 content
376
+        this.chapterContent = res.content;
377
+        console.log('✅ 成功加载章节内容(direct)');
378
+      } else {
379
+          // 模拟章节内容
380
+          this.chapterContent = `这是第${this.currentChapterIndex + 1}章的示例内容。\n\n这是一个美丽的春天,主人公开始了他的冒险旅程...\n\n本章内容正在努力加载中,请稍后查看完整内容。`;
381
+          console.log('⚠️ 使用模拟章节内容');
382
+        }
383
+      } catch (error) {
384
+        console.error('❌ 加载章节内容失败:', error);
385
+        // 模拟错误时的内容
386
+        this.chapterContent = `第${this.currentChapterIndex + 1}章内容加载失败,请检查网络连接后重试。\n\n错误信息: ${error.message}`;
387
+      } finally {
388
+        this.loading = false;
389
+      }
390
+    },
391
+      // 添加模拟章节方法
392
+  useMockChapters() {
393
+    this.chapters = [
394
+      { id: 1, title: '第一章 开始', content: null },
395
+      { id: 2, title: '第二章 发展', content: null },
396
+      { id: 3, title: '第三章 结束', content: null }
397
+    ];
398
+  },
399
+    // 上一章
400
+    async prevChapter() {
401
+      if (this.hasPrevChapter) {
402
+        this.currentChapterIndex--;
403
+        this.currentChapter = this.chapters[this.currentChapterIndex];
404
+        await this.loadChapterContent(this.currentChapter.id);
405
+      }
406
+    },
407
+    
408
+
409
+    
410
+    // 显示目录
411
+    showCatalog() {
224 412
       uni.showToast({
225
-        title: '加载成功',
226
-        icon: 'success',
227
-        duration: 1500
228
-      });
229
-    } else {
230
-      console.error('API返回错误状态码:', res.code);
231
-      this.error = '加载失败: ' + (res.msg || '未知错误');
232
-      uni.showToast({
233
-        title: res.msg || '加载失败',
413
+        title: '目录功能开发中',
234 414
         icon: 'none'
235 415
       });
416
+    },
417
+    
418
+    // 重新加载
419
+    retryLoad() {
420
+      this.loading = true;
421
+      this.error = null;
422
+      this.initReader({ id: this.novelId });
236 423
     }
237
-  } catch (error) {
238
-    console.error('加载章节内容失败:', error);
239
-    this.error = '加载失败,请检查网络连接';
240
-    uni.showToast({
241
-      title: '加载失败,请检查网络连接',
242
-      icon: 'none'
243
-    });
244
-  } finally {
245
-    this.loading = false;
246
-    uni.hideLoading();
247 424
   }
248 425
 }
249
-}}
250 426
 </script>
251 427
 
252
-<style>
428
+<style scoped>
253 429
 .reader-container {
254
-  position: relative;
255
-  height: 100vh;
256
-  display: flex;
257
-  flex-direction: column;
258
-  background-color: v-bind(backgroundColor);
259
-  transition: background-color 0.3s ease;
430
+  padding: 20rpx;
431
+  min-height: 100vh;
432
+  background-color: #f5f5f5;
260 433
 }
261 434
 
262
-.reader-header {
263
-  height: 44px;
264
-  padding: 0 15px;
435
+.loading-container, .error-container {
265 436
   display: flex;
266
-  justify-content: space-between;
437
+  flex-direction: column;
267 438
   align-items: center;
268
-  background: rgba(0, 0, 0, 0.8);
269
-  color: white;
270
-  position: fixed;
271
-  top: 0;
272
-  left: 0;
273
-  right: 0;
274
-  z-index: 1000;
275
-}
276
-
277
-.reader-content {
278
-  flex: 1;
279
-  padding: 20px;
280
-  box-sizing: border-box;
281
-  line-height: 1.8;
282
-  font-size: v-bind(fontSize + 'px');
283
-  color: #333;
284
-  margin-top: 44px;
285
-  margin-bottom: 50px;
286
-}
287
-
288
-.chapter-title {
439
+  justify-content: center;
440
+  padding: 200rpx 0;
289 441
   text-align: center;
290
-  font-size: 20px;
291
-  font-weight: bold;
292
-  margin-bottom: 20px;
293 442
 }
294 443
 
295
-.content-text {
296
-  text-indent: 2em;
297
-  line-height: 1.8;
298
-}
299
-
300
-.reader-footer {
301
-  height: 50px;
302
-  padding: 0 15px;
303
-  display: flex;
304
-  justify-content: space-between;
305
-  align-items: center;
306
-  background: rgba(0, 0, 0, 0.8);
307
-  color: white;
308
-  position: fixed;
309
-  bottom: 0;
310
-  left: 0;
311
-  right: 0;
312
-  z-index: 1000;
444
+.loading-icon {
445
+  animation: rotate 1s linear infinite;
446
+  margin-bottom: 20rpx;
313 447
 }
314 448
 
315
-.reader-ad {
316
-  height: 90px;
317
-  margin-bottom: 10px;
449
+@keyframes rotate {
450
+  from { transform: rotate(0deg); }
451
+  to { transform: rotate(360deg); }
318 452
 }
319 453
 
320
-.loading-container, .error-container {
321
-  display: flex;
322
-  flex-direction: column;
323
-  align-items: center;
324
-  justify-content: center;
325
-  padding: 50px 0;
454
+.error-text {
455
+  font-size: 32rpx;
456
+  color: var(--error-color);
457
+  margin: 20rpx 0;
458
+  text-align: center;
326 459
 }
327 460
 
328
-.error-container button {
329
-  margin-top: 20px;
461
+.btn-retry {
330 462
   background-color: var(--primary-color);
331 463
   color: white;
332
-  border: none;
333
-  padding: 10px 20px;
334
-  border-radius: 5px;
464
+  padding: 20rpx 40rpx;
465
+  border-radius: 10rpx;
466
+  margin-top: 20rpx;
335 467
 }
336 468
 
337
-.settings-panel {
338
-  position: fixed;
339
-  bottom: 50px;
340
-  left: 0;
341
-  right: 0;
342
-  background: rgba(255, 255, 255, 0.95);
343
-  padding: 20px;
344
-  z-index: 999;
345
-  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
469
+.novel-header {
470
+  text-align: center;
471
+  padding: 30rpx 0;
472
+  border-bottom: 1rpx solid #eee;
473
+  margin-bottom: 30rpx;
346 474
 }
347 475
 
348
-.font-size-controls {
349
-  display: flex;
350
-  justify-content: space-between;
351
-  align-items: center;
352
-  margin-bottom: 20px;
476
+.novel-title {
477
+  display: block;
478
+  font-size: 36rpx;
479
+  font-weight: bold;
480
+  margin-bottom: 10rpx;
353 481
 }
354 482
 
355
-.background-options {
356
-  display: flex;
357
-  justify-content: space-around;
483
+.novel-author {
484
+  display: block;
485
+  font-size: 28rpx;
486
+  color: #666;
358 487
 }
359 488
 
360
-.bg-option {
361
-  width: 40px;
362
-  height: 40px;
363
-  border-radius: 50%;
364
-  border: 2px solid transparent;
365
-  cursor: pointer;
489
+.chapter-header {
490
+  padding: 20rpx 0;
491
+  border-bottom: 1rpx solid #eee;
492
+  margin-bottom: 30rpx;
366 493
 }
367 494
 
368
-.bg-option.active {
369
-  border-color: var(--primary-color);
495
+.chapter-title {
496
+  font-size: 32rpx;
497
+  font-weight: bold;
498
+  color: #333;
370 499
 }
371 500
 
372
-.catalog-panel {
373
-  position: fixed;
374
-  top: 0;
375
-  left: 0;
376
-  right: 0;
377
-  bottom: 0;
501
+.chapter-content {
502
+  height: 70vh;
503
+  padding: 30rpx;
378 504
   background: white;
379
-  z-index: 1001;
505
+  border-radius: 10rpx;
506
+  margin-bottom: 30rpx;
380 507
 }
381 508
 
382
-.catalog-header {
383
-  display: flex;
384
-  justify-content: space-between;
385
-  align-items: center;
386
-  padding: 15px;
387
-  border-bottom: 1px solid #eee;
509
+.content-text {
510
+  font-size: 32rpx;
511
+  line-height: 1.8;
512
+  color: #333;
388 513
 }
389 514
 
390
-.chapter-list {
391
-  height: calc(100vh - 60px);
515
+.action-bar {
516
+  display: flex;
517
+  justify-content: space-between;
518
+  padding: 20rpx 0;
519
+  background: white;
520
+  border-radius: 10rpx;
392 521
 }
393 522
 
394
-.chapter-item {
395
-  padding: 15px;
396
-  border-bottom: 1px solid #eee;
523
+.btn-action {
524
+  flex: 1;
525
+  margin: 0 10rpx;
526
+  padding: 20rpx;
527
+  background-color: var(--primary-color);
528
+  color: white;
529
+  border-radius: 8rpx;
530
+  font-size: 28rpx;
397 531
 }
398 532
 
399
-.chapter-item.active {
400
-  color: var(--primary-color);
401
-  font-weight: bold;
533
+.btn-action:disabled {
534
+  background-color: #ccc;
535
+  color: #666;
402 536
 }
403
-
404
-/* 夜间模式样式 */
405
-.night-mode .reader-content {
406
-  color: #888;
537
+.login-prompt, .ad-prompt {
538
+  background: #fff3cd;
539
+  border: 1px solid #ffeaa7;
540
+  padding: 20rpx;
541
+  margin: 20rpx;
542
+  border-radius: 8rpx;
543
+  text-align: center;
407 544
 }
408 545
 
409
-.night-mode .content-text {
410
-  color: #888;
546
+.btn-login {
547
+  background: var(--primary-color);
548
+  color: white;
549
+  padding: 10rpx 20rpx;
550
+  border-radius: 6rpx;
551
+  margin-top: 10rpx;
552
+  font-size: 26rpx;
411 553
 }
412 554
 </style>

+ 2
- 2
RuoYi-App/pages/register.vue Parādīt failu

@@ -59,7 +59,7 @@
59 59
     methods: {
60 60
       // 用户登录
61 61
       handleUserLogin() {
62
-        this.$tab.navigateTo(`/pages/login`)
62
+        this.$tab.navigateTo(`/pages/login/index`)
63 63
       },
64 64
       // 获取图形验证码
65 65
       getCode() {
@@ -97,7 +97,7 @@
97 97
           	content: "恭喜你,您的账号 " + this.registerForm.username + " 注册成功!",
98 98
           	success: function (res) {
99 99
           		if (res.confirm) {
100
-                uni.redirectTo({ url: `/pages/login` });
100
+                uni.redirectTo({ url: `/pages/login/index` });
101 101
           		}
102 102
           	}
103 103
           })

+ 306
- 0
RuoYi-App/pages/register/index.vue Parādīt failu

@@ -0,0 +1,306 @@
1
+<template>
2
+  <view class="normal-login-container">
3
+    <view class="logo-content align-center justify-center flex">
4
+      <image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
5
+      </image>
6
+      <text class="title">哎呀电子注册</text>
7
+    </view>
8
+    
9
+    <view class="login-form-content">
10
+      <view class="input-item flex align-center">
11
+        <view class="iconfont icon-user icon"></view>
12
+        <input v-model="registerForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
13
+      </view>
14
+      <view class="input-item flex align-center">
15
+        <view class="iconfont icon-password icon"></view>
16
+        <input v-model="registerForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
17
+      </view>
18
+      <view class="input-item flex align-center">
19
+        <view class="iconfont icon-password icon"></view>
20
+        <input v-model="registerForm.confirmPassword" type="password" class="input" placeholder="请再次输入密码" maxlength="20" />
21
+      </view>
22
+      <view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
23
+        <view class="iconfont icon-code icon"></view>
24
+        <input v-model="registerForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
25
+        <view class="login-code"> 
26
+          <image :src="codeUrl" @click="getCode" class="login-code-img"></image>
27
+        </view>
28
+      </view>
29
+      
30
+      <!-- 用户协议 - 优化后的样式 -->
31
+      <view class="agreement-section">
32
+        <view class="agreement-item" @click="toggleAgreement">
33
+          <view class="checkbox-wrapper">
34
+            <view class="custom-checkbox" :class="{ checked: agreementChecked }">
35
+              <text class="checkmark" v-if="agreementChecked">✓</text>
36
+            </view>
37
+          </view>
38
+          <view class="agreement-text">
39
+            <text>我已阅读并同意</text>
40
+            <text class="agreement-link" @click.stop="handleUserAgrement">《用户协议》</text>
41
+            <text>和</text>
42
+            <text class="agreement-link" @click.stop="handlePrivacy">《隐私协议》</text>
43
+          </view>
44
+        </view>
45
+      </view>
46
+      
47
+      <view class="action-btn">
48
+        <button @click="handleRegister()" class="register-btn cu-btn block bg-blue lg round" :class="{ disabled: !agreementChecked }" :disabled="!agreementChecked">注册</button>
49
+      </view>
50
+    </view>
51
+    
52
+    <view class="login-link text-center">
53
+      <text @click="handleUserLogin" class="text-blue">使用已有账号登录</text>
54
+    </view>
55
+  </view>
56
+</template>
57
+
58
+<script>
59
+  import { getCodeImg, register } from '@/api/login'
60
+
61
+  export default {
62
+    data() {
63
+      return {
64
+        codeUrl: "",
65
+        captchaEnabled: true,
66
+        agreementChecked: false,
67
+        globalConfig: getApp().globalData.config,
68
+        registerForm: {
69
+          username: "",
70
+          password: "",
71
+          confirmPassword: "",
72
+          code: "",
73
+          uuid: ''
74
+        }
75
+      }
76
+    },
77
+    created() {
78
+      this.getCode()
79
+    },
80
+    methods: {
81
+      // 用户登录
82
+      handleUserLogin() {
83
+        this.$tab.navigateTo(`/pages/login/index`)
84
+      },
85
+      
86
+      // 协议相关
87
+      handlePrivacy() {
88
+        let site = this.globalConfig.appInfo.agreements[0]
89
+        this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
90
+      },
91
+      handleUserAgrement() {
92
+        let site = this.globalConfig.appInfo.agreements[1]
93
+        this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
94
+      },
95
+      toggleAgreement() {
96
+        this.agreementChecked = !this.agreementChecked
97
+      },
98
+      
99
+      // 获取图形验证码
100
+      getCode() {
101
+        getCodeImg().then(res => {
102
+          this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
103
+          if (this.captchaEnabled) {
104
+            this.codeUrl = 'data:image/gif;base64,' + res.img
105
+            this.registerForm.uuid = res.uuid
106
+          }
107
+        })
108
+      },
109
+      
110
+      // 注册方法
111
+      async handleRegister() {
112
+        if (!this.agreementChecked) {
113
+          this.$modal.msgError("请先同意用户协议和隐私协议")
114
+          return
115
+        }
116
+        
117
+        if (this.registerForm.username === "") {
118
+          this.$modal.msgError("请输入您的账号")
119
+        } else if (this.registerForm.password === "") {
120
+          this.$modal.msgError("请输入您的密码")
121
+        } else if (this.registerForm.confirmPassword === "") {
122
+          this.$modal.msgError("请再次输入您的密码")
123
+        } else if (this.registerForm.password !== this.registerForm.confirmPassword) {
124
+          this.$modal.msgError("两次输入的密码不一致")
125
+        } else if (this.registerForm.code === "" && this.captchaEnabled) {
126
+          this.$modal.msgError("请输入验证码")
127
+        } else {
128
+          this.$modal.loading("注册中,请耐心等待...")
129
+          this.register()
130
+        }
131
+      },
132
+      
133
+      // 用户注册
134
+      async register() {
135
+        register(this.registerForm).then(res => {
136
+          this.$modal.closeLoading()
137
+          uni.showModal({
138
+            title: "系统提示",
139
+            content: "恭喜你,您的账号 " + this.registerForm.username + " 注册成功!",
140
+            success: (res) => {
141
+              if (res.confirm) {
142
+                uni.redirectTo({ url: `/pages/login/index` });
143
+              }
144
+            }
145
+          })
146
+        }).catch(() => {
147
+          if (this.captchaEnabled) {
148
+            this.getCode()
149
+          }
150
+        })
151
+      }
152
+    }
153
+  }
154
+</script>
155
+
156
+<style lang="scss">
157
+  page {
158
+    background-color: #ffffff;
159
+  }
160
+
161
+  .normal-login-container {
162
+    width: 100%;
163
+
164
+    .logo-content {
165
+      width: 100%;
166
+      font-size: 21px;
167
+      text-align: center;
168
+      padding-top: 15%;
169
+      flex-direction: column;
170
+
171
+      image {
172
+        border-radius: 4px;
173
+      }
174
+
175
+      .title {
176
+        margin: 10px 0;
177
+        font-size: 24px;
178
+        color: #e94f87;
179
+      }
180
+    }
181
+
182
+    .login-form-content {
183
+      text-align: center;
184
+      margin: 20px auto;
185
+      margin-top: 10%;
186
+      width: 80%;
187
+
188
+      .input-item {
189
+        margin: 20px auto;
190
+        background-color: #f5f6f7;
191
+        height: 45px;
192
+        border-radius: 20px;
193
+
194
+        .icon {
195
+          font-size: 38rpx;
196
+          margin-left: 10px;
197
+          color: #999;
198
+        }
199
+
200
+        .input {
201
+          width: 100%;
202
+          font-size: 14px;
203
+          line-height: 20px;
204
+          text-align: left;
205
+          padding-left: 15px;
206
+        }
207
+      }
208
+      
209
+      /* 优化后的协议区域样式 */
210
+      .agreement-section {
211
+        margin: 25px 0;
212
+        padding: 0 10px;
213
+      }
214
+      
215
+      .agreement-item {
216
+        display: flex;
217
+        align-items: flex-start;
218
+        justify-content: flex-start;
219
+        text-align: left;
220
+        font-size: 14px;
221
+        color: #666;
222
+        line-height: 1.5;
223
+      }
224
+      
225
+      .checkbox-wrapper {
226
+        margin-right: 10px;
227
+        margin-top: 2px;
228
+        flex-shrink: 0;
229
+      }
230
+      
231
+      .custom-checkbox {
232
+        width: 20px;
233
+        height: 20px;
234
+        border: 2px solid #ddd;
235
+        border-radius: 4px;
236
+        display: flex;
237
+        align-items: center;
238
+        justify-content: center;
239
+        transition: all 0.3s ease;
240
+        background-color: #fff;
241
+      }
242
+      
243
+      .custom-checkbox.checked {
244
+        border-color: #e94f87;
245
+        background-color: #e94f87;
246
+      }
247
+      
248
+      .checkmark {
249
+        color: #fff;
250
+        font-size: 14px;
251
+        font-weight: bold;
252
+      }
253
+      
254
+      .agreement-text {
255
+        flex: 1;
256
+        display: flex;
257
+        flex-wrap: wrap;
258
+        align-items: center;
259
+      }
260
+      
261
+      .agreement-link {
262
+        color: #e94f87;
263
+        margin: 0 3px;
264
+        text-decoration: underline;
265
+      }
266
+      
267
+      .agreement-link:active {
268
+        opacity: 0.7;
269
+      }
270
+
271
+      .register-btn {
272
+        margin-top: 20px;
273
+        height: 45px;
274
+        background: #e94f87 !important;
275
+        color: #fff;
276
+        border-radius: 25px;
277
+        font-size: 16px;
278
+      }
279
+      
280
+      .register-btn.disabled {
281
+        background: #ccc !important;
282
+        color: #999;
283
+      }
284
+
285
+      .login-code {
286
+        height: 38px;
287
+        float: right;
288
+      
289
+        .login-code-img {
290
+          height: 38px;
291
+          position: absolute;
292
+          margin-left: 10px;
293
+          width: 200rpx;
294
+        }
295
+      }
296
+    }
297
+    
298
+    .login-link {
299
+      margin-top: 30px;
300
+      
301
+      .text-blue {
302
+        color: #e94f87;
303
+      }
304
+    }
305
+  }
306
+</style>

+ 205
- 0
RuoYi-App/pages/user/index.vue Parādīt failu

@@ -0,0 +1,205 @@
1
+<template>
2
+  <view class="user-center">
3
+    <!-- 用户信息 -->
4
+    <view class="user-info">
5
+      <image :src="user.avatar || '/static/default-avatar.png'" class="avatar" />
6
+      <view class="info">
7
+        <text class="name">{{ user.nickName || '未登录用户' }}</text>
8
+        <text class="vip">普通用户</text>
9
+      </view>
10
+      <button v-if="!isLogin" class="login-btn" @click="goToLogin">登录/注册</button>
11
+      <uni-icons v-else type="gear" size="28" color="#333" @click="goToSettings"></uni-icons>
12
+    </view>
13
+    
14
+    <!-- 功能列表 -->
15
+    <view class="menu-list">
16
+      <view class="menu-item" @click="goToMyBooks">
17
+        <uni-icons type="star" size="24" color="#2a5caa" />
18
+        <text>我的收藏</text>
19
+        <uni-icons type="right" size="18" color="#999" />
20
+      </view>
21
+      <view class="menu-item" @click="goToHistory">
22
+        <uni-icons type="time" size="24" color="#2a5caa" />
23
+        <text>阅读历史</text>
24
+        <uni-icons type="right" size="18" color="#999" />
25
+      </view>
26
+      <view class="menu-item" @click="goToSettings">
27
+        <uni-icons type="gear" size="24" color="#2a5caa" />
28
+        <text>设置</text>
29
+        <uni-icons type="right" size="18" color="#999" />
30
+      </view>
31
+    </view>
32
+    
33
+    <!-- 退出登录 -->
34
+    <button v-if="isLogin" class="btn-logout" @click="logout">退出登录</button>
35
+  </view>
36
+</template>
37
+
38
+<script>
39
+import { getToken } from '@/utils/auth'
40
+
41
+export default {
42
+  data() {
43
+    return {
44
+      user: {
45
+        avatar: '',
46
+        nickName: '',
47
+      },
48
+      isLogin: false
49
+    }
50
+  },
51
+  onShow() {
52
+    this.checkLoginStatus()
53
+    if (this.isLogin) {
54
+      this.loadUserInfo()
55
+    }
56
+  },
57
+  methods: {
58
+    checkLoginStatus() {
59
+      const token = getToken()
60
+      this.isLogin = !!token
61
+      if (!this.isLogin) {
62
+        this.user = {
63
+          avatar: '',
64
+          nickName: '未登录用户'
65
+        }
66
+      }
67
+    },
68
+    
69
+    async loadUserInfo() {
70
+      try {
71
+        const userInfo = uni.getStorageSync('userInfo')
72
+        if (userInfo) {
73
+          this.user = userInfo
74
+        }
75
+      } catch (error) {
76
+        console.error('获取用户信息失败', error)
77
+      }
78
+    },
79
+    
80
+    goToLogin() {
81
+      uni.navigateTo({
82
+        url: '/pages/login/index'
83
+      })
84
+    },
85
+    
86
+    logout() {
87
+      uni.removeStorageSync('token')
88
+      uni.removeStorageSync('userInfo')
89
+      this.isLogin = false
90
+      this.user = {
91
+        avatar: '',
92
+        nickName: '未登录用户'
93
+      }
94
+      uni.showToast({
95
+        title: '已退出登录',
96
+        icon: 'success'
97
+      })
98
+    },
99
+    
100
+    goToSettings() {
101
+      uni.showToast({
102
+        title: '设置功能开发中',
103
+        icon: 'none'
104
+      })
105
+    },
106
+    
107
+    goToMyBooks() {
108
+      uni.showToast({
109
+        title: '我的收藏开发中',
110
+        icon: 'none'
111
+      })
112
+    },
113
+    
114
+    goToHistory() {
115
+      uni.showToast({
116
+        title: '阅读历史开发中',
117
+        icon: 'none'
118
+      })
119
+    }
120
+  }
121
+}
122
+</script>
123
+
124
+<style scoped>
125
+.user-center {
126
+  padding: 20rpx;
127
+  background-color: #f5f5f5;
128
+  min-height: 100vh;
129
+}
130
+
131
+.user-info {
132
+  display: flex;
133
+  align-items: center;
134
+  padding: 30rpx;
135
+  background: white;
136
+  border-radius: 16rpx;
137
+  margin-bottom: 30rpx;
138
+}
139
+
140
+.avatar {
141
+  width: 120rpx;
142
+  height: 120rpx;
143
+  border-radius: 50%;
144
+  margin-right: 30rpx;
145
+}
146
+
147
+.info {
148
+  flex: 1;
149
+}
150
+
151
+.name {
152
+  font-size: 36rpx;
153
+  font-weight: bold;
154
+  display: block;
155
+  margin-bottom: 10rpx;
156
+}
157
+
158
+.vip {
159
+  font-size: 28rpx;
160
+  color: #e67e22;
161
+  background: #fef9e7;
162
+  padding: 5rpx 15rpx;
163
+  border-radius: 20rpx;
164
+}
165
+
166
+.login-btn {
167
+  background: #2a5caa;
168
+  color: white;
169
+  font-size: 28rpx;
170
+  padding: 10rpx 30rpx;
171
+  border-radius: 40rpx;
172
+}
173
+
174
+.menu-list {
175
+  background: white;
176
+  border-radius: 16rpx;
177
+  overflow: hidden;
178
+}
179
+
180
+.menu-item {
181
+  display: flex;
182
+  align-items: center;
183
+  padding: 30rpx;
184
+  border-bottom: 1rpx solid #f0f0f0;
185
+  
186
+  text {
187
+    flex: 1;
188
+    margin-left: 20rpx;
189
+    font-size: 32rpx;
190
+  }
191
+}
192
+
193
+.menu-item:last-child {
194
+  border-bottom: none;
195
+}
196
+
197
+.btn-logout {
198
+  margin-top: 50rpx;
199
+  background: white;
200
+  color: #e74c3c;
201
+  border: 1rpx solid #e74c3c;
202
+  border-radius: 40rpx;
203
+  padding: 20rpx;
204
+}
205
+</style>

+ 3
- 3
RuoYi-App/permission.js Parādīt failu

@@ -4,8 +4,8 @@ import { getToken } from '@/utils/auth'
4 4
 // 页面白名单
5 5
 const whiteList = [
6 6
   '/pages/index/index', // 首页无需登录
7
-  '/pages/login',
8
-  '/pages/register'
7
+  '/pages/login/index',
8
+  '/pages/register/index'
9 9
 ]
10 10
 
11 11
 // 检查地址白名单
@@ -29,7 +29,7 @@ list.forEach(item => {
29 29
         return true
30 30
       } else {
31 31
         // 非白名单页面重定向到登录
32
-        uni.reLaunch({ url: "/pages/login" })
32
+        uni.reLaunch({ url: "/pages/login/index" })
33 33
         return false
34 34
       }
35 35
     },

+ 26
- 11
RuoYi-App/router/index.js Parādīt failu

@@ -7,7 +7,7 @@ Vue.use(Router)
7 7
 const routes = [
8 8
   {
9 9
     path: '/',
10
-    redirect: '/pages/index/index'
10
+    redirect: '/pages/novel/list'
11 11
   },
12 12
   {
13 13
     path: '/pages/novel/list',
@@ -15,12 +15,21 @@ const routes = [
15 15
     component: () => import('@/pages/novel/list'),
16 16
     meta: { title: '小说列表' }
17 17
   },
18
-  {
19
-    path: '/pages/novel/reader',
18
+{
19
+    // 修复:添加动态路由参数
20
+    path: '/pages/novel/detail/:id?',
20 21
     name: 'NovelDetail',
22
+    component: () => import('@/pages/novel/detail.vue'),
23
+    meta: { title: '小说详情' },
24
+    props: true // 启用 props 接收参数
25
+},
26
+  {
27
+    path: '/pages/novel/reader/:id?',
28
+    name: 'NovelReader', 
21 29
     component: () => import('@/pages/novel/reader.vue'),
22
-    meta: { title: '小说详情' }
23
-  },
30
+    meta: { title: '阅读器' },
31
+    props: true
32
+},
24 33
   {
25 34
     path: '/pages/author/apply',
26 35
     name: 'AuthorApply',
@@ -28,15 +37,15 @@ const routes = [
28 37
     meta: { title: '作者申请', requiresAuth: true }
29 38
   },
30 39
   {
31
-    path: '/pages/login',
40
+    path: '/pages/login/index',
32 41
     name: 'Login',
33
-    component: () => import('@/pages/login'),
42
+    component: () => import('@/pages/login/index'),
34 43
     meta: { title: '登录' }
35 44
   },
36 45
   {
37
-    path: '/pages/register',
46
+    path: '/pages/register/index',
38 47
     name: 'Register',
39
-    component: () => import('@/pages/register'),
48
+    component: () => import('@/pages/register/index'),
40 49
     meta: { title: '注册' }
41 50
   },
42 51
   // 添加更多路由...
@@ -53,6 +62,10 @@ const router = new Router({
53 62
 
54 63
 // 添加简单的路由守卫
55 64
 router.beforeEach((to, from, next) => {
65
+  console.log('🚀 路由跳转:', from.path, '->', to.path)
66
+  console.log('📋 目标路由参数:', to.params)
67
+  console.log('🔍 目标路由查询:', to.query)
68
+  console.log('📍 完整路由对象:', to)
56 69
   // 设置页面标题
57 70
   if (to.meta.title) {
58 71
     document.title = to.meta.title
@@ -61,14 +74,16 @@ router.beforeEach((to, from, next) => {
61 74
   // 检查是否需要登录
62 75
   if (to.meta.requiresAuth) {
63 76
     if (!store.getters.token) {
64
-      next('/pages/login')
77
+      next('/pages/login/index')
65 78
       return
66 79
     }
67 80
   }
68 81
   
69 82
   next()
70 83
 })
71
-
84
+router.afterEach((to, from) => {
85
+  console.log('✅ 路由跳转完成:', to.path)
86
+})
72 87
 // 确保路由初始化
73 88
 router.onReady(() => {
74 89
   console.log('Router is ready');

+ 49
- 27
RuoYi-App/utils/request.js Parādīt failu

@@ -3,25 +3,31 @@ import config from '@/config'
3 3
 import { getToken, removeToken } from '@/utils/auth'
4 4
 import { toast, showConfirm, tansParams } from '@/utils/common'
5 5
 
6
-// 移除对 store 的依赖
7 6
 const request = (configObj = {}) => {
8 7
   // 确保配置对象有效
9
-  configObj.header = configObj.header || {}
8
+  configObj.header = configObj.header || {};
10 9
   
11
-  // 获取token - 只从本地存储获取
12
-  const token = getToken() || ''
10
+  // 判断是否为公开API
11
+  const isPublicApi = configObj.isToken === false || 
12
+                     configObj.url.includes('/novel/') || 
13
+                     configObj.url.includes('/chapter/') || 
14
+                     configObj.url.includes('/category/');
13 15
   
14
-  // 仅当token存在且未明确禁止时才添加
15
-  if (token && configObj.header.isToken !== false) {
16
-    configObj.header['Authorization'] = 'Bearer ' + token
17
-  } else if (!token) {
18
-    console.log('请求未携带Token: 未登录状态访问公开API', configObj.url)
16
+  // 获取token
17
+  const token = getToken() || '';
18
+  
19
+  // 仅当token存在且不是公开API时才添加token
20
+  if (token && !isPublicApi && configObj.header.isToken !== false) {
21
+    configObj.header['Authorization'] = 'Bearer ' + token;
22
+  } else if (isPublicApi) {
23
+    // 明确设置公开API不携带token
24
+    configObj.header.isToken = false;
19 25
   }
20 26
   
21 27
   // 完整URL处理
22
-  let fullUrl = configObj.url
28
+  let fullUrl = configObj.url;
23 29
   if (!fullUrl.startsWith('http')) {
24
-    fullUrl = (configObj.baseUrl || config.baseUrl || '') + fullUrl
30
+    fullUrl = (configObj.baseUrl || config.baseUrl || '') + fullUrl;
25 31
   }
26 32
   
27 33
   return new Promise((resolve, reject) => {
@@ -29,43 +35,59 @@ const request = (configObj = {}) => {
29 35
       method: configObj.method || 'get',
30 36
       url: fullUrl,
31 37
       data: configObj.data,
38
+      params: configObj.params,
32 39
       header: configObj.header,
33 40
       success: (res) => {
34 41
         // 处理401认证失败
35 42
         if (res.statusCode === 401) {
36
-          const errorMsg = res.data?.msg || '认证失败,请重新登录'
37
-          console.warn('认证失败:', configObj.url, errorMsg)
43
+          const errorMsg = res.data?.msg || '认证失败,请重新登录';
44
+          console.warn('认证失败:', configObj.url, errorMsg);
38 45
           
39 46
           // 清除无效token
40
-          removeToken()
47
+          removeToken();
41 48
           
42 49
           // 显示登录提示
43
-          toast(errorMsg)
50
+          toast(errorMsg);
44 51
           
45 52
           // 跳转到登录页面
46
-          uni.navigateTo({ url: '/pages/login' })
53
+          setTimeout(() => {
54
+            uni.navigateTo({ url: '/pages/login/index' });
55
+          }, 1500);
47 56
           
48
-          reject(new Error(errorMsg))
57
+          reject(new Error(errorMsg));
58
+          return;
49 59
         } 
50 60
         // 处理其他成功响应
51 61
         else if (res.statusCode >= 200 && res.statusCode < 300) {
52
-          resolve(res.data)
62
+          // 如果响应数据包含code字段,需要根据code判断业务状态
63
+          if (res.data && typeof res.data.code !== 'undefined') {
64
+            if (res.data.code === 200) {
65
+              resolve(res.data);
66
+            } else {
67
+              const errorMsg = res.data.msg || `请求失败 (${res.data.code})`;
68
+              toast(errorMsg);
69
+              reject(new Error(errorMsg));
70
+            }
71
+          } else {
72
+            // 没有code字段,直接返回数据
73
+            resolve(res.data);
74
+          }
53 75
         } 
54 76
         // 处理其他错误
55 77
         else {
56
-          const errorMsg = res.data?.msg || `请求失败 (${res.statusCode})`
57
-          toast(errorMsg)
58
-          reject(new Error(errorMsg))
78
+          const errorMsg = res.data?.msg || `请求失败 (${res.statusCode})`;
79
+          toast(errorMsg);
80
+          reject(new Error(errorMsg));
59 81
         }
60 82
       },
61 83
       fail: (err) => {
62
-        let message = '网络连接异常'
63
-        if (err.errMsg.includes('timeout')) message = '请求超时'
64
-        toast(message)
65
-        reject(err)
84
+        let message = '网络连接异常';
85
+        if (err.errMsg.includes('timeout')) message = '请求超时';
86
+        toast(message);
87
+        reject(err);
66 88
       }
67
-    })
68
-  })
89
+    });
90
+  });
69 91
 }
70 92
 
71 93
 // 添加便捷方法

+ 1
- 1
RuoYi-App/utils/upload.js Parādīt failu

@@ -38,7 +38,7 @@ const upload = config => {
38 38
             showConfirm("登录状态已过期,您可以继续留在该页面,或者重新登录?").then(res => {
39 39
               if (res.confirm) {
40 40
                 store.dispatch('LogOut').then(res => {
41
-                  uni.reLaunch({ url: '/pages/login/login' })
41
+                  uni.reLaunch({ url: '/pages/login/index' })
42 42
                 })
43 43
               }
44 44
             })

+ 5
- 2
RuoYi-App/vue.config.js Parādīt failu

@@ -4,10 +4,13 @@ const path = require('path');
4 4
 module.exports = {
5 5
   devServer: {
6 6
     proxy: {
7
-      // 统一代理配置
8
-      '/': {
7
+      '/api': {
9 8
         target: 'http://localhost:8080',
10 9
         changeOrigin: true,
10
+        pathRewrite: {
11
+          '^/api': '' // 根据后端接口路径决定是否重写
12
+        },
13
+        secure: false,
11 14
 		logLevel: 'debug' // 添加调试日志
12 15
       }
13 16
     }

+ 20
- 4
RuoYi-Vue/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java Parādīt failu

@@ -130,7 +130,18 @@ public class SecurityConfig
130 130
                 // 权限配置
131 131
                 .authorizeHttpRequests(auth -> auth
132 132
                         .antMatchers(permitAllUrl.getUrls().toArray(new String[0])).permitAll()
133
-                        .antMatchers("/login", "/register", "/captchaImage").permitAll()
133
+                        .antMatchers(
134
+                                "/register",
135
+                                "/",
136
+                                "/login",
137
+                                "/logout",
138
+                                "/captchaImage",
139
+                                "/css/**",
140
+                                "/js/**",
141
+                                "/img/**",
142
+                                "/favicon.ico",
143
+                                "/webjars/**"
144
+                        ).permitAll()
134 145
                         .antMatchers(HttpMethod.GET,
135 146
                                 "/",
136 147
                                 "/*.html",
@@ -146,6 +157,10 @@ public class SecurityConfig
146 157
                                 "/*/api-docs",
147 158
                                 "/druid/**"
148 159
                         ).permitAll()
160
+                        // 2. 👍 核心:放行小说章节查询接口 - 请根据您的实际URL路径调整
161
+                        .antMatchers("/novel/chapter/**").permitAll() // 放行所有以 /novel/chapter/ 开头的请求
162
+                        // .antMatchers("/api/novel/**").permitAll() // 如果您的接口有统一的前缀,例如 /api,可以这样配置
163
+                        // 3. 对于其他所有请求,需要认证
149 164
                         .anyRequest().authenticated()
150 165
                 )
151 166
                 // 登出配置
@@ -155,13 +170,14 @@ public class SecurityConfig
155 170
                 )
156 171
                 // 添加JWT过滤器
157 172
                 .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
158
-                // 添加CORS过滤器
159
-                //.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
160
-                ;
173
+        // 添加CORS过滤器
174
+        //.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
175
+        ;
161 176
 
162 177
         return http.build();
163 178
     }
164 179
 
180
+
165 181
     // CORS配置
166 182
     private CorsConfigurationSource corsConfigurationSource() {
167 183
         CorsConfiguration configuration = new CorsConfiguration();

+ 24
- 16
RuoYi-Vue/ruoyi-system/src/main/java/com/ruoyi/novel/controller/NovelController.java Parādīt failu

@@ -113,24 +113,32 @@ public class NovelController extends BaseController {
113 113
     @PreAuthorize("@ss.hasPermi('novel:chapter:query')")
114 114
     @GetMapping("/chapter/{chapterId}")
115 115
     public AjaxResult getChapterContent(@PathVariable("chapterId") Long chapterId) {
116
-        //return AjaxResult.success(novelService.getChapterContent(chapterId));
116
+        try {
117
+            NovelChapter chapter = novelService.getChapterContent(chapterId);
118
+            if (chapter == null) {
119
+                return AjaxResult.error("章节不存在");
120
+            }
117 121
 
118
-        NovelChapter chapter = novelService.getChapterContent(chapterId);
119
-        if (chapter == null) {
120
-            return AjaxResult.error("章节不存在");
122
+            // 获取小说信息
123
+            Novel novel = novelService.selectNovelById(chapter.getNovelId());
124
+            String novelTitle = novel != null ? novel.getTitle() : "未知小说";
125
+
126
+            // 获取总章节数
127
+            int totalChapters = novelService.getChapterCount(chapter.getNovelId());
128
+
129
+            // 构建响应数据
130
+            Map<String, Object> data = new HashMap<>();
131
+            data.put("novelTitle", novelTitle);
132
+            data.put("chapterTitle", chapter.getTitle());
133
+            data.put("content", chapter.getContent());
134
+            data.put("chapterIndex", chapter.getChapterOrder());
135
+            data.put("totalChapters", totalChapters);
136
+
137
+            return AjaxResult.success(data);
138
+        } catch (Exception e) {
139
+            logger.error("获取章节内容失败,章节ID: " + chapterId, e);
140
+            return AjaxResult.error("获取章节内容失败: " + e.getMessage());
121 141
         }
122
-
123
-        // 构建响应数据
124
-        Map<String, Object> data = new HashMap<>();
125
-        Novel novel = novelService.selectNovelById(chapter.getNovelId());
126
-
127
-        data.put("novelTitle", novel.getTitle() != null ? novel.getTitle() : "");
128
-        data.put("chapterTitle", chapter.getTitle());
129
-        data.put("content", chapter.getContent());
130
-        data.put("chapterIndex", chapter.getChapterOrder());
131
-        data.put("totalChapters", novelService.getChapterCount(chapter.getNovelId()));
132
-
133
-        return AjaxResult.success(data);
134 142
     }
135 143
 
136 144
 

+ 1
- 1
RuoYi-Vue/ruoyi-system/src/main/java/com/ruoyi/novel/mapper/NovelMapper.java Parādīt failu

@@ -22,7 +22,7 @@ public interface NovelMapper extends BaseMapper<Novel> {
22 22
     // 以下方法已由MyBatis-Plus实现,无需额外代码
23 23
     // int updateById(Novel novel);
24 24
     // int deleteBatchIds(@Param("ids") List<Long> ids);
25
-    // Novel selectById(Long id);
25
+     Novel selectById(Long id);
26 26
 
27 27
     // 自定义方法:根据分类ID查询小说
28 28
     @Select("SELECT * FROM novel WHERE category_id = #{categoryId}")

+ 6
- 0
RuoYi-Vue/ruoyi-system/src/main/resources/mapper/novel/NovelMapper.xml Parādīt failu

@@ -111,4 +111,10 @@
111 111
         </set>
112 112
         WHERE id = #{id}
113 113
     </update>
114
+
115
+    <!-- 根据ID查询章节 -->
116
+    <select id="selectById" parameterType="Long" resultMap="NovelResult">
117
+        <include refid="selectNovelVo"/>
118
+        where id = #{id}
119
+    </select>
114 120
 </mapper>

Notiek ielāde…
Atcelt
Saglabāt