Browse Source

修改登出功能可用

刘清 1 month ago
parent
commit
064d17929b

+ 2
- 0
lib/core/constants/api_constants.dart View File

8
   static const String login = '/api/v1/auth/login';
8
   static const String login = '/api/v1/auth/login';
9
   static const String register = '/api/v1/auth/register';
9
   static const String register = '/api/v1/auth/register';
10
   static const String logout = '/api/v1/auth/logout';
10
   static const String logout = '/api/v1/auth/logout';
11
+  static const String refresh = '/api/v1/auth/refresh';
11
   static const String getCurrentUser = '/api/v1/auth/me';
12
   static const String getCurrentUser = '/api/v1/auth/me';
12
   
13
   
13
   // 用户管理接口
14
   // 用户管理接口
18
   static String getLoginUrl() => '$baseUrl$login';
19
   static String getLoginUrl() => '$baseUrl$login';
19
   static String getRegisterUrl() => '$baseUrl$register';
20
   static String getRegisterUrl() => '$baseUrl$register';
20
   static String getLogoutUrl() => '$baseUrl$logout';
21
   static String getLogoutUrl() => '$baseUrl$logout';
22
+  static String getRefreshUrl() => '$baseUrl$refresh';
21
   static String getCurrentUserUrl() => '$baseUrl$getCurrentUser';
23
   static String getCurrentUserUrl() => '$baseUrl$getCurrentUser';
22
   static String getUserProfileUrl() => '$baseUrl$getUserProfile';
24
   static String getUserProfileUrl() => '$baseUrl$getUserProfile';
23
   static String getUpdateProfileUrl() => '$baseUrl$updateUserProfile';
25
   static String getUpdateProfileUrl() => '$baseUrl$updateUserProfile';

+ 16
- 6
lib/data/datasources/local/shared_prefs.dart View File

7
   SharedPrefs(this._prefs);
7
   SharedPrefs(this._prefs);
8
 
8
 
9
   // Token管理
9
   // Token管理
10
-  Future<bool> setAuthToken(String token) async {
11
-    return await _prefs.setString(AppConstants.authTokenKey, token);
10
+  Future<bool> setAccessToken(String token) async {
11
+    return await _prefs.setString('access_token', token);
12
   }
12
   }
13
 
13
 
14
-  String? getAuthToken() {
15
-    return _prefs.getString(AppConstants.authTokenKey);
14
+  Future<bool> setRefreshToken(String token) async {
15
+    return await _prefs.setString('refresh_token', token);
16
   }
16
   }
17
 
17
 
18
-  Future<bool> removeAuthToken() async {
19
-    return await _prefs.remove(AppConstants.authTokenKey);
18
+  String? getAccessToken() {
19
+    return _prefs.getString('access_token');
20
+  }
21
+
22
+  String? getRefreshToken() {
23
+    return _prefs.getString('refresh_token');
24
+  }
25
+
26
+  Future<bool> removeTokens() async {
27
+    await _prefs.remove('access_token');
28
+    await _prefs.remove('refresh_token');
29
+    return true;
20
   }
30
   }
21
 
31
 
22
   // 用户数据管理
32
   // 用户数据管理

+ 14
- 5
lib/data/datasources/remote/api_client.dart View File

78
     }
78
     }
79
   }
79
   }
80
   
80
   
81
-  // 保存token
82
-  Future<void> saveToken(String token) async {
83
-    await _prefs.setString('access_token', token);
81
+  // 保存tokens
82
+  Future<void> saveTokens(String accessToken, String? refreshToken) async {
83
+    await _prefs.setString('access_token', accessToken);
84
+    if (refreshToken != null && refreshToken.isNotEmpty) {
85
+      await _prefs.setString('refresh_token', refreshToken);
86
+    }
84
   }
87
   }
85
   
88
   
86
-  // 清除token
87
-  Future<void> clearToken() async {
89
+  // 清除所有token
90
+  Future<void> clearTokens() async {
88
     await _prefs.remove('access_token');
91
     await _prefs.remove('access_token');
92
+    await _prefs.remove('refresh_token');
93
+  }
94
+
95
+  // 获取refresh_token
96
+  String? getRefreshToken() {
97
+    return _prefs.getString('refresh_token');
89
   }
98
   }
90
   
99
   
91
   // 检查是否已登录
100
   // 检查是否已登录

+ 156
- 24
lib/data/repositories/auth_repository.dart View File

19
     required SharedPreferences prefs,
19
     required SharedPreferences prefs,
20
   }) : _apiClient = apiClient, _prefs = prefs;
20
   }) : _apiClient = apiClient, _prefs = prefs;
21
   
21
   
22
-  // 登录(需要使用安全登录替代)
22
+  // 登录(需要使用安全登录替代)保存双token
23
   Future<ApiResponse<User>> login(LoginRequest request) async {
23
   Future<ApiResponse<User>> login(LoginRequest request) async {
24
     try {
24
     try {
25
       final response = await _apiClient.post(
25
       final response = await _apiClient.post(
26
         ApiConstants.getLoginUrl(),
26
         ApiConstants.getLoginUrl(),
27
         request.toJson(),
27
         request.toJson(),
28
-        withAuth: false,
28
+        withAuth: false,  // 登录不需要认证头
29
       );
29
       );
30
       
30
       
31
       if (response.statusCode == 200) {
31
       if (response.statusCode == 200) {
33
           json.decode(response.body)
33
           json.decode(response.body)
34
         );
34
         );
35
         
35
         
36
-        // 保存token
37
-        await _apiClient.saveToken(tokenResponse.accessToken);
36
+        // 保存access_token和refresh_token
37
+        await _apiClient.saveTokens(
38
+          tokenResponse.accessToken,
39
+          tokenResponse.refreshToken,
40
+        );
41
+        
42
+        // 保存用户数据
43
+        await _prefs.setString(
44
+          'user_data',
45
+          json.encode(tokenResponse.user.toJson()),
46
+        );
38
         
47
         
39
         return ApiResponse<User>(
48
         return ApiResponse<User>(
40
           success: true,
49
           success: true,
56
     }
65
     }
57
   }
66
   }
58
   
67
   
59
-  // 注册(需要使用安全注册替代)
68
+  // 注册(需要使用安全注册替代)保存双token
60
   Future<ApiResponse<User>> register(RegisterRequest request) async {
69
   Future<ApiResponse<User>> register(RegisterRequest request) async {
61
     try {
70
     try {
62
       final response = await _apiClient.post(
71
       final response = await _apiClient.post(
70
           json.decode(response.body)
79
           json.decode(response.body)
71
         );
80
         );
72
         
81
         
73
-        // 保存token
74
-        await _apiClient.saveToken(tokenResponse.accessToken);
82
+        // 保存双token
83
+        await _apiClient.saveTokens(
84
+          tokenResponse.accessToken,
85
+          tokenResponse.refreshToken,
86
+        );
87
+        
88
+        // 保存用户数据
89
+        await _prefs.setString(
90
+          'user_data',
91
+          json.encode(tokenResponse.user.toJson()),
92
+        );
75
         
93
         
76
         return ApiResponse<User>(
94
         return ApiResponse<User>(
77
           success: true,
95
           success: true,
101
         username: request.username,
119
         username: request.username,
102
         password: request.password,
120
         password: request.password,
103
       );
121
       );
104
-
105
-      print(ApiConstants.getLoginUrl());
106
-      print(secureRequest.toJson());
107
       
122
       
108
       final response = await _apiClient.post(
123
       final response = await _apiClient.post(
109
         ApiConstants.getLoginUrl(),
124
         ApiConstants.getLoginUrl(),
110
         secureRequest.toJson(),
125
         secureRequest.toJson(),
111
         withAuth: false,
126
         withAuth: false,
112
       );
127
       );
113
-      print(response.statusCode);
114
       
128
       
115
       // ... 处理响应
129
       // ... 处理响应
116
       if (response.statusCode == 200) {
130
       if (response.statusCode == 200) {
118
           json.decode(response.body)
132
           json.decode(response.body)
119
         );
133
         );
120
         
134
         
121
-        // 保存token
122
-        await _apiClient.saveToken(tokenResponse.accessToken);
135
+        // 保存access_token和refresh_token
136
+        await _apiClient.saveTokens(
137
+          tokenResponse.accessToken,
138
+          tokenResponse.refreshToken,
139
+        );
140
+        
141
+        // 保存用户数据
142
+        await _prefs.setString(
143
+          'user_data',
144
+          json.encode(tokenResponse.user.toJson()),
145
+        );
123
         
146
         
124
         return ApiResponse<User>(
147
         return ApiResponse<User>(
125
           success: true,
148
           success: true,
169
           json.decode(response.body)
192
           json.decode(response.body)
170
         );
193
         );
171
         
194
         
172
-        // 保存token
173
-        await _apiClient.saveToken(tokenResponse.accessToken);
195
+        // 保存双token
196
+        await _apiClient.saveTokens(
197
+          tokenResponse.accessToken,
198
+          tokenResponse.refreshToken,
199
+        );
200
+        
201
+        // 保存用户数据
202
+        await _prefs.setString(
203
+          'user_data',
204
+          json.encode(tokenResponse.user.toJson()),
205
+        );
174
         
206
         
175
         return ApiResponse<User>(
207
         return ApiResponse<User>(
176
           success: true,
208
           success: true,
202
       
234
       
203
       if (response.statusCode == 200) {
235
       if (response.statusCode == 200) {
204
         final userData = json.decode(response.body);
236
         final userData = json.decode(response.body);
237
+        final user = User.fromJson(userData);
238
+        
239
+        // 更新本地用户数据
240
+        await _prefs.setString('user_data', json.encode(user.toJson()));
241
+        
205
         return ApiResponse<User>(
242
         return ApiResponse<User>(
206
           success: true,
243
           success: true,
207
           message: '获取成功',
244
           message: '获取成功',
208
-          data: User.fromJson(userData),
245
+          data: user,
209
         );
246
         );
247
+      } else if (response.statusCode == 401) {
248
+        // Token过期,尝试刷新
249
+        final refreshResult = await _refreshToken();
250
+        if (refreshResult) {
251
+          // 刷新成功,重新获取用户信息
252
+          return await getCurrentUser();
253
+        } else {
254
+          return ApiResponse<User>(
255
+            success: false,
256
+            message: '登录已过期,请重新登录',
257
+          );
258
+        }
210
       } else {
259
       } else {
211
         return ApiResponse<User>(
260
         return ApiResponse<User>(
212
           success: false,
261
           success: false,
220
       );
269
       );
221
     }
270
     }
222
   }
271
   }
223
-  
224
-  // 登出
225
-  Future<bool> logout() async {
272
+
273
+  // 刷新Token
274
+  Future<bool> _refreshToken() async {
226
     try {
275
     try {
276
+      final refreshToken = _apiClient.getRefreshToken();
277
+      
278
+      if (refreshToken == null || refreshToken.isEmpty) {
279
+        return false;
280
+      }
281
+      
227
       final response = await _apiClient.post(
282
       final response = await _apiClient.post(
228
-        ApiConstants.getLogoutUrl(),
229
-        {},
230
-        withAuth: true,
283
+        ApiConstants.getRefreshUrl(),  // 需要添加这个常量
284
+        {'refresh_token': refreshToken},
285
+        withAuth: false,
231
       );
286
       );
232
       
287
       
233
       if (response.statusCode == 200) {
288
       if (response.statusCode == 200) {
234
-        await _apiClient.clearToken();
289
+        final data = json.decode(response.body);
290
+        final newAccessToken = data['access_token'];
291
+        final newRefreshToken = data['refresh_token'];
292
+        
293
+        // 保存新的tokens
294
+        await _apiClient.saveTokens(newAccessToken, newRefreshToken);
235
         return true;
295
         return true;
236
       }
296
       }
297
+      
237
       return false;
298
       return false;
238
     } catch (e) {
299
     } catch (e) {
239
       return false;
300
       return false;
240
     }
301
     }
241
   }
302
   }
242
   
303
   
304
+  // 登出
305
+  Future<ApiResponse<bool>> logout() async {
306
+    try {
307
+      // 获取refresh_token
308
+      final refreshToken = _apiClient.getRefreshToken();
309
+      
310
+      if (refreshToken == null || refreshToken.isEmpty) {
311
+        // 没有refresh_token,只清除本地数据
312
+        await _clearLocalData();
313
+        return ApiResponse<bool>(
314
+          success: true,
315
+          message: '已退出登录',
316
+          data: true,
317
+        );
318
+      }
319
+      
320
+      // 发送登出请求到服务器
321
+      final response = await _apiClient.post(
322
+        ApiConstants.getLogoutUrl(),
323
+        {'refresh_token': refreshToken},
324
+        withAuth: true,  // 需要access_token认证头
325
+      );
326
+      
327
+      // 无论服务器响应如何,都清除本地数据
328
+      await _clearLocalData();
329
+      
330
+      if (response.statusCode == 200) {
331
+        return ApiResponse<bool>(
332
+          success: true,
333
+          message: '已成功退出登录',
334
+          data: true,
335
+        );
336
+      } else {
337
+        // 服务器登出失败,但本地已清除
338
+        return ApiResponse<bool>(
339
+          success: true,
340
+          message: '已退出登录(服务器通信失败)',
341
+          data: true,
342
+        );
343
+      }
344
+    } catch (e) {
345
+      // 网络异常,仍然清除本地数据
346
+      await _clearLocalData();
347
+      return ApiResponse<bool>(
348
+        success: true,
349
+        message: '已退出登录(网络异常)',
350
+        data: true,
351
+      );
352
+    }
353
+  }
354
+
355
+  // 清除本地数据
356
+  Future<void> _clearLocalData() async {
357
+    await _apiClient.clearTokens();
358
+    await _prefs.remove('user_data');
359
+  }
360
+  
243
   // 检查登录状态
361
   // 检查登录状态
244
   Future<bool> isLoggedIn() async {
362
   Future<bool> isLoggedIn() async {
245
-    return _apiClient.isLoggedIn();
363
+    // 检查是否有access_token
364
+    if (!_apiClient.isLoggedIn()) {
365
+      return false;
366
+    }
367
+    
368
+    // 可以进一步验证token是否有效
369
+    try {
370
+      final response = await _apiClient.get(
371
+        ApiConstants.getCurrentUserUrl(),
372
+        withAuth: true,
373
+      );
374
+      return response.statusCode == 200;
375
+    } catch (e) {
376
+      return false;
377
+    }
246
   }
378
   }
247
 }
379
 }

+ 3
- 2
lib/main.dart View File

1
 import 'package:flutter/material.dart';
1
 import 'package:flutter/material.dart';
2
 import 'package:provider/provider.dart';
2
 import 'package:provider/provider.dart';
3
+import 'core/constants/route_constants.dart';
3
 import 'presentation/providers/auth_provider.dart';
4
 import 'presentation/providers/auth_provider.dart';
4
 import 'presentation/providers/user_provider.dart';
5
 import 'presentation/providers/user_provider.dart';
5
 import 'presentation/navigation/app_router.dart';
6
 import 'presentation/navigation/app_router.dart';
7
 
8
 
8
 void main() async {
9
 void main() async {
9
   WidgetsFlutterBinding.ensureInitialized();
10
   WidgetsFlutterBinding.ensureInitialized();
10
-  await di.init();
11
+  await di.init();  // 初始化DI容器
11
   runApp(const MyApp());
12
   runApp(const MyApp());
12
 }
13
 }
13
 
14
 
42
           ),
43
           ),
43
         ),
44
         ),
44
         debugShowCheckedModeBanner: false,
45
         debugShowCheckedModeBanner: false,
45
-        initialRoute: '/',
46
+        initialRoute: RouteConstants.splash,
46
         onGenerateRoute: AppRouter.onGenerateRoute,
47
         onGenerateRoute: AppRouter.onGenerateRoute,
47
       ),
48
       ),
48
     );
49
     );

+ 21
- 2
lib/presentation/navigation/app_router.dart View File

8
 import '../screens/profile/profile_detail_screen.dart';
8
 import '../screens/profile/profile_detail_screen.dart';
9
 import '../screens/splash_screen.dart';
9
 import '../screens/splash_screen.dart';
10
 import '../../core/constants/route_constants.dart';
10
 import '../../core/constants/route_constants.dart';
11
+import 'route_guards.dart';
11
 
12
 
12
 class AppRouter {
13
 class AppRouter {
13
   static Route<dynamic> onGenerateRoute(RouteSettings settings) {
14
   static Route<dynamic> onGenerateRoute(RouteSettings settings) {
14
     switch (settings.name) {
15
     switch (settings.name) {
15
       case RouteConstants.splash:
16
       case RouteConstants.splash:
16
         return MaterialPageRoute(builder: (_) => const SplashScreen());
17
         return MaterialPageRoute(builder: (_) => const SplashScreen());
18
+      
17
       case RouteConstants.home:
19
       case RouteConstants.home:
18
         return MaterialPageRoute(builder: (_) => const HomeScreen());
20
         return MaterialPageRoute(builder: (_) => const HomeScreen());
21
+      
19
       case RouteConstants.login:
22
       case RouteConstants.login:
20
         return MaterialPageRoute(builder: (_) => const LoginScreen());
23
         return MaterialPageRoute(builder: (_) => const LoginScreen());
24
+      
21
       case RouteConstants.register:
25
       case RouteConstants.register:
22
         return MaterialPageRoute(builder: (_) => const RegisterScreen());
26
         return MaterialPageRoute(builder: (_) => const RegisterScreen());
27
+      
23
       case RouteConstants.news:
28
       case RouteConstants.news:
24
         return MaterialPageRoute(builder: (_) => const NewsScreen());
29
         return MaterialPageRoute(builder: (_) => const NewsScreen());
30
+      
25
       case RouteConstants.services:
31
       case RouteConstants.services:
26
         return MaterialPageRoute(builder: (_) => const ServicesScreen());
32
         return MaterialPageRoute(builder: (_) => const ServicesScreen());
33
+      
27
       case RouteConstants.profile:
34
       case RouteConstants.profile:
28
-        return MaterialPageRoute(builder: (_) => const ProfileScreen());
35
+        return MaterialPageRoute(
36
+          builder: (_) => ProtectedRoute(
37
+            routeName: RouteConstants.profile,
38
+            child: const ProfileScreen(),
39
+          ),
40
+        );
41
+      
29
       case RouteConstants.profileDetail:
42
       case RouteConstants.profileDetail:
30
-        return MaterialPageRoute(builder: (_) => const ProfileDetailScreen());
43
+        return MaterialPageRoute(
44
+          builder: (_) => ProtectedRoute(
45
+            routeName: RouteConstants.profileDetail,
46
+            child: const ProfileDetailScreen(),
47
+          ),
48
+        );
49
+      
31
       default:
50
       default:
32
         return MaterialPageRoute(
51
         return MaterialPageRoute(
33
           builder: (_) => Scaffold(
52
           builder: (_) => Scaffold(

+ 15
- 4
lib/presentation/navigation/route_guards.dart View File

5
 
5
 
6
 class ProtectedRoute extends StatelessWidget {
6
 class ProtectedRoute extends StatelessWidget {
7
   final Widget child;
7
   final Widget child;
8
+  final String? routeName;
8
   
9
   
9
-  const ProtectedRoute({Key? key, required this.child}) : super(key: key);
10
+  const ProtectedRoute({
11
+    super.key,
12
+    required this.child,
13
+    this.routeName,
14
+  });
10
   
15
   
11
   @override
16
   @override
12
   Widget build(BuildContext context) {
17
   Widget build(BuildContext context) {
17
     } else {
22
     } else {
18
       return LoginScreen(
23
       return LoginScreen(
19
         onSuccess: () {
24
         onSuccess: () {
20
-          Navigator.of(context).pushReplacement(
21
-            MaterialPageRoute(builder: (_) => child),
22
-          );
25
+          // 如果有routeName,可以使用pushReplacementNamed
26
+          if (routeName != null) {
27
+            Navigator.of(context).pushReplacementNamed(routeName!);
28
+          } else {
29
+            // 否则使用原来的方式
30
+            Navigator.of(context).pushReplacement(
31
+              MaterialPageRoute(builder: (_) => child),
32
+            );
33
+          }
23
         },
34
         },
24
       );
35
       );
25
     }
36
     }

+ 21
- 5
lib/presentation/providers/auth_provider.dart View File

21
   String? get error => _error;
21
   String? get error => _error;
22
   
22
   
23
   Future<void> _loadCurrentUser() async {
23
   Future<void> _loadCurrentUser() async {
24
-    final user = await authRepository.getCurrentUser();
25
-    if (user != null) {
26
-      _user = user.data;
24
+    final response = await authRepository.getCurrentUser();
25
+    if (response.success && response.data != null) {
26
+      _user = response.data;
27
       notifyListeners();
27
       notifyListeners();
28
     }
28
     }
29
   }
29
   }
77
   }
77
   }
78
   
78
   
79
   Future<void> logout() async {
79
   Future<void> logout() async {
80
-    await authRepository.logout();
81
-    _user = null;
80
+    _isLoading = true;
81
+    _error = null;
82
     notifyListeners();
82
     notifyListeners();
83
+    
84
+    try {
85
+      final response = await authRepository.logout();
86
+      
87
+      if (response.success) {
88
+        _user = null;
89
+        _error = null;
90
+      } else {
91
+        _error = response.message;
92
+      }
93
+    } catch (e) {
94
+      _error = '登出失败: $e';
95
+    } finally {
96
+      _isLoading = false;
97
+      notifyListeners();
98
+    }
83
   }
99
   }
84
   
100
   
85
   Future<void> checkAuthStatus() async {
101
   Future<void> checkAuthStatus() async {

+ 8
- 4
lib/presentation/screens/auth/login_screen.dart View File

9
 class LoginScreen extends StatefulWidget {
9
 class LoginScreen extends StatefulWidget {
10
   final VoidCallback? onSuccess;
10
   final VoidCallback? onSuccess;
11
   
11
   
12
-  const LoginScreen({super.key, this.onSuccess});
12
+  const LoginScreen({
13
+    super.key,
14
+    this.onSuccess,
15
+  });
13
   
16
   
14
   @override
17
   @override
15
   State<LoginScreen> createState() => _LoginScreenState();
18
   State<LoginScreen> createState() => _LoginScreenState();
17
 
20
 
18
 class _LoginScreenState extends State<LoginScreen> {
21
 class _LoginScreenState extends State<LoginScreen> {
19
   final _formKey = GlobalKey<FormState>();
22
   final _formKey = GlobalKey<FormState>();
20
-  final _emailController = TextEditingController(text: 'test@example.com');
21
-  final _passwordController = TextEditingController(text: '123456');
23
+  final _emailController = TextEditingController(text: 'aaa');
24
+  final _passwordController = TextEditingController(text: 'Heweidabangzi77!');
22
   
25
   
23
   @override
26
   @override
24
   void dispose() {
27
   void dispose() {
117
                           _passwordController.text,
120
                           _passwordController.text,
118
                         );
121
                         );
119
                         if (authProvider.isAuthenticated) {
122
                         if (authProvider.isAuthenticated) {
120
-                          widget.onSuccess?.call();
123
+                          // widget.onSuccess?.call();
121
                           Navigator.of(context).pushReplacementNamed(RouteConstants.home);
124
                           Navigator.of(context).pushReplacementNamed(RouteConstants.home);
125
+                          // Navigator.of(context).pushNamedAndRemoveUntil(RouteConstants.home);
122
                         }
126
                         }
123
                       }
127
                       }
124
                     },
128
                     },

+ 57
- 9
lib/presentation/screens/profile/profile_screen.dart View File

1
 import 'package:flutter/material.dart';
1
 import 'package:flutter/material.dart';
2
 import 'package:provider/provider.dart';
2
 import 'package:provider/provider.dart';
3
+import '../../../core/constants/route_constants.dart';
3
 import '../../providers/auth_provider.dart';
4
 import '../../providers/auth_provider.dart';
4
 import '../../providers/user_provider.dart';
5
 import '../../providers/user_provider.dart';
5
 import '../../navigation/bottom_nav_bar.dart';
6
 import '../../navigation/bottom_nav_bar.dart';
11
 
12
 
12
   @override
13
   @override
13
   Widget build(BuildContext context) {
14
   Widget build(BuildContext context) {
14
-    return ProtectedWidget(
15
-      child: _ProfileContent(),
16
-      loadingWidget: const Center(
17
-        child: CircularProgressIndicator(),
18
-      ),
19
-    );
15
+    // 直接显示内容,认证由路由层处理
16
+    return _ProfileContent();
20
   }
17
   }
21
 }
18
 }
22
 
19
 
231
                         title: '退出登录',
228
                         title: '退出登录',
232
                         subtitle: '安全退出当前账号',
229
                         subtitle: '安全退出当前账号',
233
                         onTap: () async {
230
                         onTap: () async {
234
-                          await authProvider.logout();
235
-                          if (mounted) {
236
-                            Navigator.of(context).pushReplacementNamed('/');
231
+                          // 防止重复点击
232
+                          if (authProvider.isLoading) return;
233
+
234
+                          final shouldLogout = await showDialog<bool>(
235
+                            context: context,
236
+                            builder: (context) {
237
+                              final isIOS = Theme.of(context).platform == TargetPlatform.iOS;
238
+                              
239
+                              if (isIOS) {
240
+                                // iOS风格:确定在右边,取消在左边
241
+                                return AlertDialog(
242
+                                  title: const Text('确认退出'),
243
+                                  content: const Text('确定要退出登录吗?'),
244
+                                  actionsAlignment: MainAxisAlignment.spaceBetween,
245
+                                  actions: [
246
+                                    TextButton(
247
+                                      onPressed: () => Navigator.of(context).pop(true),
248
+                                      child: const Text('确定'),
249
+                                    ),
250
+                                    TextButton(
251
+                                      onPressed: () => Navigator.of(context).pop(false),
252
+                                      child: const Text('取消'),
253
+                                    ),
254
+                                  ],
255
+                                );
256
+                              } else {
257
+                                // Android/Material风格:取消在左边,确定在右边
258
+                                return AlertDialog(
259
+                                  title: const Text('确认退出'),
260
+                                  content: const Text('确定要退出登录吗?'),
261
+                                  actionsAlignment: MainAxisAlignment.spaceBetween,
262
+                                  actions: [
263
+                                    TextButton(
264
+                                      onPressed: () => Navigator.of(context).pop(false),
265
+                                      child: const Text('取消'),
266
+                                    ),
267
+                                    TextButton(
268
+                                      onPressed: () => Navigator.of(context).pop(true),
269
+                                      child: const Text('确定'),
270
+                                    ),
271
+                                  ],
272
+                                );
273
+                              }
274
+                            },
275
+                          );
276
+                          
277
+                          if (shouldLogout == true) {
278
+                            await authProvider.logout();
279
+
280
+                            // 登出后,确保在组件仍然存在时进行导航
281
+                            if (mounted) {
282
+                              // 使用pushReplacementNamed清空导航栈
283
+                              Navigator.of(context).pushReplacementNamed(RouteConstants.home);
284
+                            }
237
                           }
285
                           }
238
                         },
286
                         },
239
                       ),
287
                       ),

+ 3
- 0
lib/presentation/widgets/custom/protected_widget.dart View File

18
   Widget build(BuildContext context) {
18
   Widget build(BuildContext context) {
19
     final authProvider = Provider.of<AuthProvider>(context);
19
     final authProvider = Provider.of<AuthProvider>(context);
20
     
20
     
21
+    // 如果正在加载,显示loading
21
     if (authProvider.isLoading) {
22
     if (authProvider.isLoading) {
22
       return loadingWidget ?? const LoadingIndicator();
23
       return loadingWidget ?? const LoadingIndicator();
23
     }
24
     }
24
     
25
     
26
+    // 如果未认证,显示登录页
25
     if (!authProvider.isAuthenticated) {
27
     if (!authProvider.isAuthenticated) {
26
       return LoginScreen(
28
       return LoginScreen(
27
         onSuccess: () {
29
         onSuccess: () {
30
       );
32
       );
31
     }
33
     }
32
     
34
     
35
+    // 已认证,显示子组件
33
     return child;
36
     return child;
34
   }
37
   }
35
 }
38
 }