Просмотр исходного кода

Merge branch 'develop' of afan/flutter_app_android into master

刘清 2 месяцев назад
Родитель
Сommit
4e500f4417
53 измененных файлов: 3597 добавлений и 174 удалений
  1. 46
    1
      README.md
  2. 3
    0
      devtools_options.yaml
  3. 24
    0
      lib/core/constants/api_constants.dart
  4. 11
    0
      lib/core/constants/app_constants.dart
  5. 11
    0
      lib/core/constants/route_constants.dart
  6. 0
    0
      lib/core/exceptions/app_exceptions.dart
  7. 0
    0
      lib/core/themes/app_theme.dart
  8. 0
    0
      lib/core/themes/text_styles.dart
  9. 44
    0
      lib/core/utils/crypto_utils.dart
  10. 0
    0
      lib/core/utils/extensions.dart
  11. 0
    0
      lib/core/utils/helpers.dart
  12. 0
    0
      lib/core/utils/validators.dart
  13. 0
    0
      lib/data/datasources/local/local_storage.dart
  14. 51
    0
      lib/data/datasources/local/shared_prefs.dart
  15. 96
    0
      lib/data/datasources/remote/api_client.dart
  16. 0
    0
      lib/data/datasources/remote/auth_api.dart
  17. 40
    0
      lib/data/datasources/remote/secure_http_client.dart
  18. 0
    0
      lib/data/datasources/remote/user_api.dart
  19. 22
    0
      lib/data/models/api_response.dart
  20. 16
    0
      lib/data/models/auth/login_request.dart
  21. 25
    0
      lib/data/models/auth/register_request.dart
  22. 47
    0
      lib/data/models/auth/secure_login_request.dart
  23. 27
    0
      lib/data/models/auth/token_response.dart
  24. 85
    0
      lib/data/models/user.dart
  25. 247
    0
      lib/data/repositories/auth_repository.dart
  26. 0
    0
      lib/data/repositories/base_repository.dart
  27. 76
    0
      lib/data/repositories/user_repository.dart
  28. 0
    0
      lib/domain/entities/user_entity.dart
  29. 0
    0
      lib/domain/usecases/auth_usecases.dart
  30. 0
    0
      lib/domain/usecases/user_usecases.dart
  31. 37
    0
      lib/injection_container.dart
  32. 36
    108
      lib/main.dart
  33. 49
    0
      lib/presentation/navigation/app_router.dart
  34. 97
    0
      lib/presentation/navigation/bottom_nav_bar.dart
  35. 27
    0
      lib/presentation/navigation/route_guards.dart
  36. 0
    0
      lib/presentation/providers/app_provider.dart
  37. 99
    0
      lib/presentation/providers/auth_provider.dart
  38. 62
    0
      lib/presentation/providers/user_provider.dart
  39. 187
    0
      lib/presentation/screens/auth/login_screen.dart
  40. 221
    0
      lib/presentation/screens/auth/register_screen.dart
  41. 307
    0
      lib/presentation/screens/home/home_screen.dart
  42. 172
    0
      lib/presentation/screens/news/news_screen.dart
  43. 205
    0
      lib/presentation/screens/profile/profile_detail_screen.dart
  44. 342
    0
      lib/presentation/screens/profile/profile_screen.dart
  45. 279
    0
      lib/presentation/screens/services/services_screen.dart
  46. 73
    0
      lib/presentation/screens/splash_screen.dart
  47. 59
    0
      lib/presentation/widgets/common/app_button.dart
  48. 90
    0
      lib/presentation/widgets/common/app_text_field.dart
  49. 28
    0
      lib/presentation/widgets/common/loading_indicator.dart
  50. 35
    0
      lib/presentation/widgets/custom/protected_widget.dart
  51. 2
    0
      macos/Flutter/GeneratedPluginRegistrant.swift
  52. 302
    1
      pubspec.lock
  53. 17
    64
      pubspec.yaml

+ 46
- 1
README.md Просмотреть файл

@@ -34,4 +34,49 @@ samples, guidance on mobile development, and a full API reference.
34 34
 
35 35
 ## 项目简介
36 36
 
37
-这是一个安卓app开发项目,实现一个有关用户登录,彩票足球信息浏览的app。
37
+这是一个安卓app开发项目,实现一个有关用户登录,彩票足球信息浏览的app。
38
+
39
+后端使用FastApi实现,采用前后端分离。
40
+
41
+## 从Git开始开发
42
+
43
+首先本地环境需要安装 git ,确保 git 可用。
44
+
45
+由于需要基于已有的此代码进行继续开发,本仓库已创建好,你需要在本地进行 git clone 
46
+
47
+```
48
+git clone https://codehub.afanai.top/afan/flutter_app_android.git
49
+```
50
+
51
+此操作将在本地clone出一个一模一样的代码项目,随后可以创建本地分支,进行修改代码,推送到远端个人分支。
52
+
53
+- (注:git checkout -b feature origin/feature 指令创建一个 feature 分支并切换到 feature 分支上,并建立远端 feature分支的追踪关系。git branch -a 指令用于显示本地和远端的所有分支。git branch -vv 指令显示本地分支与远端分支的追踪关系。可以看到此时本地的 feature 分支已经与远端的 origin/feature 分支建立了连接),例如:
54
+
55
+```
56
+git checkout -b afan origin/afan
57
+
58
+// 此时本地分支为afan,你可以进行修改本地代码
59
+
60
+// 使用一下命令进行commit到本地
61
+
62
+git add .
63
+git commit -m "描述"
64
+
65
+// 进行推送到远端afan分支
66
+
67
+git push
68
+```
69
+
70
+此后进入gitea代码仓库界面创建合并请求PR,往develop分支上合并。
71
+
72
+后续进行开发时,本地已有创建的分支,如刚才的``` afan ```,由于develop分支是最新代码,每次开发前进行同步develop代码到本地分支
73
+
74
+```
75
+git pull origin develop
76
+```
77
+
78
+- (注:记得加上develop)
79
+
80
+同步完develop后即可开发代码,并按照之前操作进行推送回develop分支。
81
+
82
+其中所有人都应往develop分支合代码,此后每段时间develop会统一回合到master。

+ 3
- 0
devtools_options.yaml Просмотреть файл

@@ -0,0 +1,3 @@
1
+description: This file stores settings for Dart & Flutter DevTools.
2
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3
+extensions:

+ 24
- 0
lib/core/constants/api_constants.dart Просмотреть файл

@@ -0,0 +1,24 @@
1
+class ApiConstants {
2
+  // 开发环境
3
+  static const String baseUrl = 'https://afanai.top:10004';  // 实际restful接口地址
4
+  // static const String baseUrl = 'http://10.0.2.2:8000';  // Android模拟器
5
+  // static const String baseUrl = 'http://localhost:8000';  // iOS模拟器
6
+  
7
+  // 认证相关接口
8
+  static const String login = '/api/v1/auth/login';
9
+  static const String register = '/api/v1/auth/register';
10
+  static const String logout = '/api/v1/auth/logout';
11
+  static const String getCurrentUser = '/api/v1/auth/me';
12
+  
13
+  // 用户管理接口
14
+  static const String getUserProfile = '/api/v1/users/me';
15
+  static const String updateUserProfile = '/api/v1/users/me';
16
+  
17
+  // 工具方法
18
+  static String getLoginUrl() => '$baseUrl$login';
19
+  static String getRegisterUrl() => '$baseUrl$register';
20
+  static String getLogoutUrl() => '$baseUrl$logout';
21
+  static String getCurrentUserUrl() => '$baseUrl$getCurrentUser';
22
+  static String getUserProfileUrl() => '$baseUrl$getUserProfile';
23
+  static String getUpdateProfileUrl() => '$baseUrl$updateUserProfile';
24
+}

+ 11
- 0
lib/core/constants/app_constants.dart Просмотреть файл

@@ -0,0 +1,11 @@
1
+class AppConstants {
2
+  static const String appName = 'Flutter App';
3
+  static const String appVersion = '1.0.0';
4
+  static const String apiBaseUrl = 'http://localhost:8000/api/v1';
5
+  
6
+  static const String authTokenKey = 'auth_token';
7
+  static const String userDataKey = 'user_data';
8
+  
9
+  static const int connectTimeout = 30000;
10
+  static const int receiveTimeout = 30000;
11
+}

+ 11
- 0
lib/core/constants/route_constants.dart Просмотреть файл

@@ -0,0 +1,11 @@
1
+class RouteConstants {
2
+  static const String splash = '/';
3
+  static const String home = '/home';
4
+  static const String login = '/login';
5
+  static const String register = '/register';
6
+  static const String news = '/news';
7
+  static const String services = '/services';
8
+  static const String profile = '/profile';
9
+  static const String profileDetail = '/profile/detail';
10
+  static const String settings = '/settings';
11
+}

+ 0
- 0
lib/core/exceptions/app_exceptions.dart Просмотреть файл


+ 0
- 0
lib/core/themes/app_theme.dart Просмотреть файл


+ 0
- 0
lib/core/themes/text_styles.dart Просмотреть файл


+ 44
- 0
lib/core/utils/crypto_utils.dart Просмотреть файл

@@ -0,0 +1,44 @@
1
+// lib/core/utils/crypto_utils.dart
2
+import 'dart:convert';
3
+import 'package:crypto/crypto.dart';
4
+import 'package:encrypt/encrypt.dart' as encrypt;
5
+import 'dart:math';
6
+
7
+class CryptoUtils {
8
+  // 生成盐值
9
+  static String generateSalt([int length = 16]) {
10
+    final random = Random.secure();
11
+    final saltBytes = List<int>.generate(length, (i) => random.nextInt(256));
12
+    return base64.encode(saltBytes);
13
+  }
14
+  
15
+  // SHA256哈希(用于前端预处理)
16
+  static String sha256Hash(String input) {
17
+    final bytes = utf8.encode(input);
18
+    final digest = sha256.convert(bytes);
19
+    return digest.toString();
20
+  }
21
+  
22
+  // 生成客户端密钥(前端加密用)
23
+  static String generateClientKey() {
24
+    final random = Random.secure();
25
+    final keyBytes = List<int>.generate(32, (i) => random.nextInt(256));
26
+    return base64.encode(keyBytes);
27
+  }
28
+  
29
+  // AES加密(如果需要前端加密)
30
+  static Map<String, String> aesEncrypt(String plainText, String key) {
31
+    final iv = encrypt.IV.fromSecureRandom(16);
32
+    final encrypter = encrypt.Encrypter(
33
+      encrypt.AES(encrypt.Key.fromBase64(key))
34
+    );
35
+    
36
+    final encrypted = encrypter.encrypt(plainText, iv: iv);
37
+    
38
+    return {
39
+      'data': encrypted.base64,
40
+      'iv': iv.base64,
41
+      'key': key, // 实际项目中,key应该通过其他方式传输
42
+    };
43
+  }
44
+}

+ 0
- 0
lib/core/utils/extensions.dart Просмотреть файл


+ 0
- 0
lib/core/utils/helpers.dart Просмотреть файл


+ 0
- 0
lib/core/utils/validators.dart Просмотреть файл


+ 0
- 0
lib/data/datasources/local/local_storage.dart Просмотреть файл


+ 51
- 0
lib/data/datasources/local/shared_prefs.dart Просмотреть файл

@@ -0,0 +1,51 @@
1
+import 'package:shared_preferences/shared_preferences.dart';
2
+import '../../../core/constants/app_constants.dart';
3
+
4
+class SharedPrefs {
5
+  final SharedPreferences _prefs;
6
+
7
+  SharedPrefs(this._prefs);
8
+
9
+  // Token管理
10
+  Future<bool> setAuthToken(String token) async {
11
+    return await _prefs.setString(AppConstants.authTokenKey, token);
12
+  }
13
+
14
+  String? getAuthToken() {
15
+    return _prefs.getString(AppConstants.authTokenKey);
16
+  }
17
+
18
+  Future<bool> removeAuthToken() async {
19
+    return await _prefs.remove(AppConstants.authTokenKey);
20
+  }
21
+
22
+  // 用户数据管理
23
+  Future<bool> setUserData(String userJson) async {
24
+    return await _prefs.setString(AppConstants.userDataKey, userJson);
25
+  }
26
+
27
+  String? getUserData() {
28
+    return _prefs.getString(AppConstants.userDataKey);
29
+  }
30
+
31
+  Future<bool> removeUserData() async {
32
+    return await _prefs.remove(AppConstants.userDataKey);
33
+  }
34
+
35
+  // 通用方法
36
+  Future<bool> setString(String key, String value) async {
37
+    return await _prefs.setString(key, value);
38
+  }
39
+
40
+  String? getString(String key) {
41
+    return _prefs.getString(key);
42
+  }
43
+
44
+  Future<bool> removeKey(String key) async {
45
+    return await _prefs.remove(key);
46
+  }
47
+
48
+  Future<bool> clearAll() async {
49
+    return await _prefs.clear();
50
+  }
51
+}

+ 96
- 0
lib/data/datasources/remote/api_client.dart Просмотреть файл

@@ -0,0 +1,96 @@
1
+import 'dart:convert';
2
+import 'package:http/http.dart' as http;
3
+import 'package:shared_preferences/shared_preferences.dart';
4
+import './secure_http_client.dart';
5
+
6
+class ApiClient {
7
+  final http.Client _httpClient;
8
+  final SharedPreferences _prefs;
9
+  
10
+  ApiClient(this._prefs) : _httpClient = SecureHttpClient.createSecureClient();
11
+  
12
+  // 获取认证头
13
+  Map<String, String> _getHeaders({bool withAuth = true}) {
14
+    final headers = {
15
+      'Content-Type': 'application/json',
16
+      'Accept': 'application/json',
17
+    };
18
+    
19
+    if (withAuth) {
20
+      final token = _prefs.getString('access_token');
21
+      if (token != null && token.isNotEmpty) {
22
+        headers['Authorization'] = 'Bearer $token';
23
+      }
24
+    }
25
+    
26
+    return headers;
27
+  }
28
+  
29
+  // 通用POST请求
30
+  Future<http.Response> post(
31
+    String url, 
32
+    Map<String, dynamic> body, 
33
+    {bool withAuth = false}
34
+  ) async {
35
+    try {
36
+      final response = await _httpClient.post(
37
+        Uri.parse(url),
38
+        headers: _getHeaders(withAuth: withAuth),
39
+        body: json.encode(body),
40
+      );
41
+      return response;
42
+    } catch (e) {
43
+      throw Exception('网络请求失败: $e');
44
+    }
45
+  }
46
+  
47
+  // 通用GET请求
48
+  Future<http.Response> get(
49
+    String url, 
50
+    {bool withAuth = true}
51
+  ) async {
52
+    try {
53
+      final response = await _httpClient.get(
54
+        Uri.parse(url),
55
+        headers: _getHeaders(withAuth: withAuth),
56
+      );
57
+      return response;
58
+    } catch (e) {
59
+      throw Exception('网络请求失败: $e');
60
+    }
61
+  }
62
+  
63
+  // 通用PUT请求
64
+  Future<http.Response> put(
65
+    String url, 
66
+    Map<String, dynamic> body, 
67
+    {bool withAuth = true}
68
+  ) async {
69
+    try {
70
+      final response = await _httpClient.put(
71
+        Uri.parse(url),
72
+        headers: _getHeaders(withAuth: withAuth),
73
+        body: json.encode(body),
74
+      );
75
+      return response;
76
+    } catch (e) {
77
+      throw Exception('网络请求失败: $e');
78
+    }
79
+  }
80
+  
81
+  // 保存token
82
+  Future<void> saveToken(String token) async {
83
+    await _prefs.setString('access_token', token);
84
+  }
85
+  
86
+  // 清除token
87
+  Future<void> clearToken() async {
88
+    await _prefs.remove('access_token');
89
+  }
90
+  
91
+  // 检查是否已登录
92
+  bool isLoggedIn() {
93
+    final token = _prefs.getString('access_token');
94
+    return token != null && token.isNotEmpty;
95
+  }
96
+}

+ 0
- 0
lib/data/datasources/remote/auth_api.dart Просмотреть файл


+ 40
- 0
lib/data/datasources/remote/secure_http_client.dart Просмотреть файл

@@ -0,0 +1,40 @@
1
+// lib/data/datasources/remote/secure_http_client.dart
2
+import 'dart:io';
3
+import 'package:http/http.dart' as http;
4
+import 'package:http/io_client.dart' as http;
5
+
6
+class SecureHttpClient {
7
+  static http.Client createSecureClient() {
8
+    // 生产环境:严格的证书验证
9
+    if (const bool.fromEnvironment('dart.vm.product')) {
10
+      final securityContext = SecurityContext.defaultContext;
11
+      
12
+      // 你可以添加自定义根证书(如果需要)
13
+      // securityContext.setTrustedCertificates('path/to/certificate.pem');
14
+      
15
+      final httpClient = HttpClient(context: securityContext);
16
+      
17
+      // 配置安全策略
18
+      httpClient.badCertificateCallback = 
19
+        (X509Certificate cert, String host, int port) {
20
+          // 生产环境严格验证
21
+          return false; // 拒绝无效证书
22
+        };
23
+      
24
+      return http.IOClient(httpClient);
25
+    } 
26
+    // 开发环境:宽松的验证
27
+    else {
28
+      final httpClient = HttpClient();
29
+      
30
+      httpClient.badCertificateCallback = 
31
+        (X509Certificate cert, String host, int port) {
32
+          // 开发环境允许自签名证书
33
+          // print('警告:使用自签名证书 - $host:$port');
34
+          return true;
35
+        };
36
+      
37
+      return http.IOClient(httpClient);
38
+    }
39
+  }
40
+}

+ 0
- 0
lib/data/datasources/remote/user_api.dart Просмотреть файл


+ 22
- 0
lib/data/models/api_response.dart Просмотреть файл

@@ -0,0 +1,22 @@
1
+class ApiResponse<T> {
2
+  final bool success;
3
+  final String message;
4
+  final T? data;
5
+
6
+  ApiResponse({
7
+    required this.success,
8
+    required this.message,
9
+    this.data,
10
+  });
11
+
12
+  factory ApiResponse.fromJson(
13
+    Map<String, dynamic> json,
14
+    T Function(dynamic) fromJsonT,
15
+  ) {
16
+    return ApiResponse<T>(
17
+      success: json['success'] ?? false,
18
+      message: json['message'] ?? '',
19
+      data: json['data'] != null ? fromJsonT(json['data']) : null,
20
+    );
21
+  }
22
+}

+ 16
- 0
lib/data/models/auth/login_request.dart Просмотреть файл

@@ -0,0 +1,16 @@
1
+class LoginRequest {
2
+  final String username;  // 改为username
3
+  final String password;
4
+
5
+  LoginRequest({
6
+    required this.username,
7
+    required this.password,
8
+  });
9
+
10
+  Map<String, dynamic> toJson() {
11
+    return {
12
+      'username': username,
13
+      'password': password,
14
+    };
15
+  }
16
+}

+ 25
- 0
lib/data/models/auth/register_request.dart Просмотреть файл

@@ -0,0 +1,25 @@
1
+class RegisterRequest {
2
+  final String username;
3
+  final String email;
4
+  final String password;
5
+  final String passwordConfirm;  // 添加密码确认
6
+  final String? fullName;  // 改为fullName
7
+
8
+  RegisterRequest({
9
+    required this.username,
10
+    required this.email,
11
+    required this.password,
12
+    required this.passwordConfirm,
13
+    this.fullName,
14
+  });
15
+
16
+  Map<String, dynamic> toJson() {
17
+    return {
18
+      'username': username,
19
+      'email': email,
20
+      'password': password,
21
+      'password_confirm': passwordConfirm,
22
+      'full_name': fullName,
23
+    };
24
+  }
25
+}

+ 47
- 0
lib/data/models/auth/secure_login_request.dart Просмотреть файл

@@ -0,0 +1,47 @@
1
+import '../../../core/utils/crypto_utils.dart';
2
+
3
+class SecureLoginRequest {
4
+  final String username;
5
+  final String passwordHash; // 客户端哈希后的密码
6
+  final String salt; // 盐值
7
+  final String? clientKey; // 客户端密钥(用于加密传输)
8
+  
9
+  SecureLoginRequest({
10
+    required this.username,
11
+    required this.passwordHash,
12
+    required this.salt,
13
+    this.clientKey,
14
+  });
15
+  
16
+  Map<String, dynamic> toJson() {
17
+    return {
18
+      'username': username,
19
+      'password_hash': passwordHash,
20
+      'salt': salt,
21
+      'client_key': clientKey,
22
+    };
23
+  }
24
+  
25
+  // 工厂方法:创建安全的登录请求
26
+  factory SecureLoginRequest.create({
27
+    required String username,
28
+    required String password,
29
+  }) {
30
+    // 生成盐值
31
+    final salt = CryptoUtils.generateSalt();
32
+    
33
+    // 密码加盐哈希
34
+    final passwordWithSalt = password + salt;
35
+    final passwordHash = CryptoUtils.sha256Hash(passwordWithSalt);
36
+    
37
+    // 生成客户端密钥(可选,用于进一步加密)
38
+    final clientKey = CryptoUtils.generateClientKey();
39
+    
40
+    return SecureLoginRequest(
41
+      username: username,
42
+      passwordHash: passwordHash,
43
+      salt: salt,
44
+      clientKey: clientKey,
45
+    );
46
+  }
47
+}

+ 27
- 0
lib/data/models/auth/token_response.dart Просмотреть файл

@@ -0,0 +1,27 @@
1
+import '../user.dart';
2
+
3
+class TokenResponse {
4
+  final String accessToken;
5
+  final String tokenType;
6
+  final int? expiresIn;
7
+  final String? refreshToken;
8
+  final User user;
9
+
10
+  TokenResponse({
11
+    required this.accessToken,
12
+    required this.user,
13
+    this.tokenType = 'bearer',
14
+    this.expiresIn,
15
+    this.refreshToken,
16
+  });
17
+
18
+  factory TokenResponse.fromJson(Map<String, dynamic> json) {
19
+    return TokenResponse(
20
+      accessToken: json['access_token'] ?? '',
21
+      tokenType: json['token_type'] ?? 'bearer',
22
+      expiresIn: json['expires_in'],
23
+      refreshToken: json['refresh_token'],
24
+      user: User.fromJson(json['user']),
25
+    );
26
+  }
27
+}

+ 85
- 0
lib/data/models/user.dart Просмотреть файл

@@ -0,0 +1,85 @@
1
+class User {
2
+  final int id;  // 改为int,匹配API
3
+  final String username;  // 改为username
4
+  final String email;
5
+  final String? fullName;  // 改为fullName
6
+  final String? avatar;
7
+  final String? phone;
8
+  final bool isActive;
9
+  final bool isVerified;
10
+  final DateTime createdAt;
11
+  final DateTime? lastLogin;
12
+
13
+  User({
14
+    required this.id,
15
+    required this.username,
16
+    required this.email,
17
+    this.fullName,
18
+    this.avatar,
19
+    this.phone,
20
+    this.isActive = true,
21
+    this.isVerified = false,
22
+    required this.createdAt,
23
+    this.lastLogin,
24
+  });
25
+
26
+  factory User.fromJson(Map<String, dynamic> json) {
27
+    return User(
28
+      id: json['id'] ?? 0,
29
+      username: json['username'] ?? '',
30
+      email: json['email'] ?? '',
31
+      fullName: json['full_name'],
32
+      avatar: json['avatar'],
33
+      phone: json['phone'],
34
+      isActive: json['is_active'] ?? true,
35
+      isVerified: json['is_verified'] ?? false,
36
+      createdAt: json['created_at'] != null
37
+          ? DateTime.parse(json['created_at'])
38
+          : DateTime.now(),
39
+      lastLogin: json['last_login'] != null
40
+          ? DateTime.parse(json['last_login'])
41
+          : null,
42
+    );
43
+  }
44
+
45
+  Map<String, dynamic> toJson() {
46
+    return {
47
+      'id': id,
48
+      'username': username,
49
+      'email': email,
50
+      'full_name': fullName,
51
+      'avatar': avatar,
52
+      'is_active': isActive,
53
+      'is_verified': isVerified,
54
+      'created_at': createdAt.toIso8601String(),
55
+      'last_login': lastLogin?.toIso8601String(),
56
+    };
57
+  }
58
+
59
+  // 添加一个getter来兼容之前的name字段
60
+  // String get name => fullName ?? username;
61
+
62
+  User copyWith({
63
+    int? id,
64
+    String? username,
65
+    String? email,
66
+    String? fullName,
67
+    String? avatar,
68
+    bool? isActive,
69
+    bool? isVerified,
70
+    DateTime? createdAt,
71
+    DateTime? lastLogin,
72
+  }) {
73
+    return User(
74
+      id: id ?? this.id,
75
+      username: username ?? this.username,
76
+      email: email ?? this.email,
77
+      fullName: fullName ?? this.fullName,
78
+      avatar: avatar ?? this.avatar,
79
+      isActive: isActive ?? this.isActive,
80
+      isVerified: isVerified ?? this.isVerified,
81
+      createdAt: createdAt ?? this.createdAt,
82
+      lastLogin: lastLogin ?? this.lastLogin,
83
+    );
84
+  }
85
+}

+ 247
- 0
lib/data/repositories/auth_repository.dart Просмотреть файл

@@ -0,0 +1,247 @@
1
+import 'dart:convert';
2
+import 'package:shared_preferences/shared_preferences.dart';
3
+import '../../core/constants/api_constants.dart';
4
+import '../../core/utils/crypto_utils.dart';
5
+import '../datasources/remote/api_client.dart';
6
+import '../models/auth/secure_login_request.dart';
7
+import '../models/user.dart';
8
+import '../models/auth/login_request.dart';
9
+import '../models/auth/register_request.dart';
10
+import '../models/auth/token_response.dart';
11
+import '../models/api_response.dart';
12
+
13
+class AuthRepository {
14
+  final ApiClient _apiClient;
15
+  final SharedPreferences _prefs;
16
+  
17
+  AuthRepository({
18
+    required ApiClient apiClient,
19
+    required SharedPreferences prefs,
20
+  }) : _apiClient = apiClient, _prefs = prefs;
21
+  
22
+  // 登录(需要使用安全登录替代)
23
+  Future<ApiResponse<User>> login(LoginRequest request) async {
24
+    try {
25
+      final response = await _apiClient.post(
26
+        ApiConstants.getLoginUrl(),
27
+        request.toJson(),
28
+        withAuth: false,
29
+      );
30
+      
31
+      if (response.statusCode == 200) {
32
+        final tokenResponse = TokenResponse.fromJson(
33
+          json.decode(response.body)
34
+        );
35
+        
36
+        // 保存token
37
+        await _apiClient.saveToken(tokenResponse.accessToken);
38
+        
39
+        return ApiResponse<User>(
40
+          success: true,
41
+          message: '登录成功',
42
+          data: tokenResponse.user,
43
+        );
44
+      } else {
45
+        final error = json.decode(response.body);
46
+        return ApiResponse<User>(
47
+          success: false,
48
+          message: error['detail'] ?? '登录失败',
49
+        );
50
+      }
51
+    } catch (e) {
52
+      return ApiResponse<User>(
53
+        success: false,
54
+        message: '登录失败: $e',
55
+      );
56
+    }
57
+  }
58
+  
59
+  // 注册(需要使用安全注册替代)
60
+  Future<ApiResponse<User>> register(RegisterRequest request) async {
61
+    try {
62
+      final response = await _apiClient.post(
63
+        ApiConstants.getRegisterUrl(),
64
+        request.toJson(),
65
+        withAuth: false,
66
+      );
67
+      
68
+      if (response.statusCode == 201) {
69
+        final tokenResponse = TokenResponse.fromJson(
70
+          json.decode(response.body)
71
+        );
72
+        
73
+        // 保存token
74
+        await _apiClient.saveToken(tokenResponse.accessToken);
75
+        
76
+        return ApiResponse<User>(
77
+          success: true,
78
+          message: '注册成功',
79
+          data: tokenResponse.user,
80
+        );
81
+      } else {
82
+        final error = json.decode(response.body);
83
+        return ApiResponse<User>(
84
+          success: false,
85
+          message: error['detail'] ?? '注册失败',
86
+        );
87
+      }
88
+    } catch (e) {
89
+      return ApiResponse<User>(
90
+        success: false,
91
+        message: '注册失败: $e',
92
+      );
93
+    }
94
+  }
95
+  
96
+  // 安全登录方法
97
+  Future<ApiResponse<User>> secureLogin(LoginRequest request) async {
98
+    try {
99
+      // 创建安全登录请求
100
+      final secureRequest = SecureLoginRequest.create(
101
+        username: request.username,
102
+        password: request.password,
103
+      );
104
+
105
+      print(ApiConstants.getLoginUrl());
106
+      print(secureRequest.toJson());
107
+      
108
+      final response = await _apiClient.post(
109
+        ApiConstants.getLoginUrl(),
110
+        secureRequest.toJson(),
111
+        withAuth: false,
112
+      );
113
+      print(response.statusCode);
114
+      
115
+      // ... 处理响应
116
+      if (response.statusCode == 200) {
117
+        final tokenResponse = TokenResponse.fromJson(
118
+          json.decode(response.body)
119
+        );
120
+        
121
+        // 保存token
122
+        await _apiClient.saveToken(tokenResponse.accessToken);
123
+        
124
+        return ApiResponse<User>(
125
+          success: true,
126
+          message: '登录成功',
127
+          data: tokenResponse.user,
128
+        );
129
+      } else {
130
+        final error = json.decode(response.body);
131
+        return ApiResponse<User>(
132
+          success: false,
133
+          message: error['detail'] ?? '登录失败',
134
+        );
135
+      }
136
+    } catch (e) {
137
+      return ApiResponse<User>(
138
+        success: false,
139
+        message: '登录失败: $e',
140
+      );
141
+    }
142
+  }
143
+  
144
+  // 安全注册方法
145
+  Future<ApiResponse<User>> secureRegister(RegisterRequest request) async {
146
+    try {
147
+      // 创建安全注册数据
148
+      final salt = CryptoUtils.generateSalt();
149
+      final passwordHash = CryptoUtils.sha256Hash(request.password + salt);
150
+      
151
+      final registerData = {
152
+        'username': request.username,
153
+        'email': request.email,
154
+        'password_hash': passwordHash,
155
+        'salt': salt,
156
+        'full_name': request.fullName,
157
+        'password_confirm': request.passwordConfirm,
158
+      };
159
+      
160
+      final response = await _apiClient.post(
161
+        ApiConstants.getRegisterUrl(),
162
+        registerData,
163
+        withAuth: false,
164
+      );
165
+      
166
+      // ... 处理响应
167
+      if (response.statusCode == 201) {
168
+        final tokenResponse = TokenResponse.fromJson(
169
+          json.decode(response.body)
170
+        );
171
+        
172
+        // 保存token
173
+        await _apiClient.saveToken(tokenResponse.accessToken);
174
+        
175
+        return ApiResponse<User>(
176
+          success: true,
177
+          message: '注册成功',
178
+          data: tokenResponse.user,
179
+        );
180
+      } else {
181
+        final error = json.decode(response.body);
182
+        return ApiResponse<User>(
183
+          success: false,
184
+          message: error['detail'] ?? '注册失败',
185
+        );
186
+      }
187
+    } catch (e) {
188
+      return ApiResponse<User>(
189
+        success: false,
190
+        message: '注册失败: $e',
191
+      );
192
+    }
193
+  }
194
+
195
+  // 获取当前用户
196
+  Future<ApiResponse<User>> getCurrentUser() async {
197
+    try {
198
+      final response = await _apiClient.get(
199
+        ApiConstants.getCurrentUserUrl(),
200
+        withAuth: true,
201
+      );
202
+      
203
+      if (response.statusCode == 200) {
204
+        final userData = json.decode(response.body);
205
+        return ApiResponse<User>(
206
+          success: true,
207
+          message: '获取成功',
208
+          data: User.fromJson(userData),
209
+        );
210
+      } else {
211
+        return ApiResponse<User>(
212
+          success: false,
213
+          message: '获取用户信息失败',
214
+        );
215
+      }
216
+    } catch (e) {
217
+      return ApiResponse<User>(
218
+        success: false,
219
+        message: '获取失败: $e',
220
+      );
221
+    }
222
+  }
223
+  
224
+  // 登出
225
+  Future<bool> logout() async {
226
+    try {
227
+      final response = await _apiClient.post(
228
+        ApiConstants.getLogoutUrl(),
229
+        {},
230
+        withAuth: true,
231
+      );
232
+      
233
+      if (response.statusCode == 200) {
234
+        await _apiClient.clearToken();
235
+        return true;
236
+      }
237
+      return false;
238
+    } catch (e) {
239
+      return false;
240
+    }
241
+  }
242
+  
243
+  // 检查登录状态
244
+  Future<bool> isLoggedIn() async {
245
+    return _apiClient.isLoggedIn();
246
+  }
247
+}

+ 0
- 0
lib/data/repositories/base_repository.dart Просмотреть файл


+ 76
- 0
lib/data/repositories/user_repository.dart Просмотреть файл

@@ -0,0 +1,76 @@
1
+import 'dart:convert';
2
+import '../../core/constants/api_constants.dart';
3
+import '../datasources/remote/api_client.dart';
4
+import '../models/user.dart';
5
+import '../models/api_response.dart';
6
+
7
+class UserRepository {
8
+  final ApiClient _apiClient;
9
+  
10
+  UserRepository({required ApiClient apiClient}) : _apiClient = apiClient;
11
+  
12
+  // 获取用户资料
13
+  Future<ApiResponse<User>> getUserProfile() async {
14
+    try {
15
+      final response = await _apiClient.get(
16
+        ApiConstants.getUserProfileUrl(),
17
+        withAuth: true,
18
+      );
19
+      
20
+      if (response.statusCode == 200) {
21
+        final userData = json.decode(response.body);
22
+        return ApiResponse<User>(
23
+          success: true,
24
+          message: '获取成功',
25
+          data: User.fromJson(userData),
26
+        );
27
+      } else {
28
+        return ApiResponse<User>(
29
+          success: false,
30
+          message: '获取用户资料失败',
31
+        );
32
+      }
33
+    } catch (e) {
34
+      return ApiResponse<User>(
35
+        success: false,
36
+        message: '获取失败: $e',
37
+      );
38
+    }
39
+  }
40
+  
41
+  // 更新用户资料
42
+  Future<ApiResponse<User>> updateUserProfile(User user) async {
43
+    try {
44
+      final updateData = {
45
+        'full_name': user.fullName,
46
+        'email': user.email,
47
+      };
48
+      
49
+      final response = await _apiClient.put(
50
+        ApiConstants.getUpdateProfileUrl(),
51
+        updateData,
52
+        withAuth: true,
53
+      );
54
+      
55
+      if (response.statusCode == 200) {
56
+        final userData = json.decode(response.body);
57
+        return ApiResponse<User>(
58
+          success: true,
59
+          message: '更新成功',
60
+          data: User.fromJson(userData),
61
+        );
62
+      } else {
63
+        final error = json.decode(response.body);
64
+        return ApiResponse<User>(
65
+          success: false,
66
+          message: error['detail'] ?? '更新失败',
67
+        );
68
+      }
69
+    } catch (e) {
70
+      return ApiResponse<User>(
71
+        success: false,
72
+        message: '更新失败: $e',
73
+      );
74
+    }
75
+  }
76
+}

+ 0
- 0
lib/domain/entities/user_entity.dart Просмотреть файл


+ 0
- 0
lib/domain/usecases/auth_usecases.dart Просмотреть файл


+ 0
- 0
lib/domain/usecases/user_usecases.dart Просмотреть файл


+ 37
- 0
lib/injection_container.dart Просмотреть файл

@@ -0,0 +1,37 @@
1
+import 'package:get_it/get_it.dart';
2
+import 'package:shared_preferences/shared_preferences.dart';
3
+import 'package:http/http.dart' as http;
4
+import 'data/datasources/local/shared_prefs.dart';
5
+import 'data/datasources/remote/api_client.dart';
6
+import 'data/repositories/auth_repository.dart';
7
+import 'data/repositories/user_repository.dart';
8
+import 'presentation/providers/auth_provider.dart';
9
+import 'presentation/providers/user_provider.dart';
10
+
11
+final GetIt sl = GetIt.instance;
12
+
13
+Future<void> init() async {
14
+  // External dependencies
15
+  final sharedPreferences = await SharedPreferences.getInstance();
16
+  sl.registerLazySingleton(() => sharedPreferences);
17
+  
18
+  // HTTP Client
19
+  sl.registerLazySingleton(() => http.Client());
20
+  
21
+  // Data sources
22
+  sl.registerLazySingleton(() => SharedPrefs(sl()));
23
+  sl.registerLazySingleton(() => ApiClient(sl()));
24
+  
25
+  // Repositories
26
+  sl.registerLazySingleton(() => AuthRepository(
27
+    apiClient: sl(),
28
+    prefs: sl(),
29
+  ));
30
+  sl.registerLazySingleton(() => UserRepository(
31
+    apiClient: sl(),
32
+  ));
33
+  
34
+  // Providers
35
+  sl.registerFactory(() => AuthProvider(authRepository: sl()));
36
+  sl.registerFactory(() => UserProvider(userRepository: sl()));
37
+}

+ 36
- 108
lib/main.dart Просмотреть файл

@@ -1,6 +1,13 @@
1 1
 import 'package:flutter/material.dart';
2
-
3
-void main() {
2
+import 'package:provider/provider.dart';
3
+import 'presentation/providers/auth_provider.dart';
4
+import 'presentation/providers/user_provider.dart';
5
+import 'presentation/navigation/app_router.dart';
6
+import 'injection_container.dart' as di;
7
+
8
+void main() async {
9
+  WidgetsFlutterBinding.ensureInitialized();
10
+  await di.init();
4 11
   runApp(const MyApp());
5 12
 }
6 13
 
@@ -10,113 +17,34 @@ class MyApp extends StatelessWidget {
10 17
   // This widget is the root of your application.
11 18
   @override
12 19
   Widget build(BuildContext context) {
13
-    return MaterialApp(
14
-      title: 'Flutter Demo',
15
-      theme: ThemeData(
16
-        // This is the theme of your application.
17
-        //
18
-        // TRY THIS: Try running your application with "flutter run". You'll see
19
-        // the application has a purple toolbar. Then, without quitting the app,
20
-        // try changing the seedColor in the colorScheme below to Colors.green
21
-        // and then invoke "hot reload" (save your changes or press the "hot
22
-        // reload" button in a Flutter-supported IDE, or press "r" if you used
23
-        // the command line to start the app).
24
-        //
25
-        // Notice that the counter didn't reset back to zero; the application
26
-        // state is not lost during the reload. To reset the state, use hot
27
-        // restart instead.
28
-        //
29
-        // This works for code too, not just values: Most code changes can be
30
-        // tested with just a hot reload.
31
-        colorScheme: .fromSeed(seedColor: Colors.deepPurple),
32
-      ),
33
-      home: const MyHomePage(title: 'Flutter Demo Home liuqing'),
34
-    );
35
-  }
36
-}
37
-
38
-class MyHomePage extends StatefulWidget {
39
-  const MyHomePage({super.key, required this.title});
40
-
41
-  // This widget is the home page of your application. It is stateful, meaning
42
-  // that it has a State object (defined below) that contains fields that affect
43
-  // how it looks.
44
-
45
-  // This class is the configuration for the state. It holds the values (in this
46
-  // case the title) provided by the parent (in this case the App widget) and
47
-  // used by the build method of the State. Fields in a Widget subclass are
48
-  // always marked "final".
49
-
50
-  final String title;
51
-
52
-  @override
53
-  State<MyHomePage> createState() => _MyHomePageState();
54
-}
55
-
56
-class _MyHomePageState extends State<MyHomePage> {
57
-  int _counter = 0;
58
-
59
-  void _incrementCounter() {
60
-    setState(() {
61
-      // This call to setState tells the Flutter framework that something has
62
-      // changed in this State, which causes it to rerun the build method below
63
-      // so that the display can reflect the updated values. If we changed
64
-      // _counter without calling setState(), then the build method would not be
65
-      // called again, and so nothing would appear to happen.
66
-      _counter++;
67
-    });
68
-  }
69
-
70
-  @override
71
-  Widget build(BuildContext context) {
72
-    // This method is rerun every time setState is called, for instance as done
73
-    // by the _incrementCounter method above.
74
-    //
75
-    // The Flutter framework has been optimized to make rerunning build methods
76
-    // fast, so that you can just rebuild anything that needs updating rather
77
-    // than having to individually change instances of widgets.
78
-    return Scaffold(
79
-      appBar: AppBar(
80
-        // TRY THIS: Try changing the color here to a specific color (to
81
-        // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
82
-        // change color while the other colors stay the same.
83
-        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
84
-        // Here we take the value from the MyHomePage object that was created by
85
-        // the App.build method, and use it to set our appbar title.
86
-        title: Text(widget.title),
87
-      ),
88
-      body: Center(
89
-        // Center is a layout widget. It takes a single child and positions it
90
-        // in the middle of the parent.
91
-        child: Column(
92
-          // Column is also a layout widget. It takes a list of children and
93
-          // arranges them vertically. By default, it sizes itself to fit its
94
-          // children horizontally, and tries to be as tall as its parent.
95
-          //
96
-          // Column has various properties to control how it sizes itself and
97
-          // how it positions its children. Here we use mainAxisAlignment to
98
-          // center the children vertically; the main axis here is the vertical
99
-          // axis because Columns are vertical (the cross axis would be
100
-          // horizontal).
101
-          //
102
-          // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
103
-          // action in the IDE, or press "p" in the console), to see the
104
-          // wireframe for each widget.
105
-          mainAxisAlignment: .center,
106
-          children: [
107
-            const Text('You have pushed the button this many times:'),
108
-            Text(
109
-              '$_counter',
110
-              style: Theme.of(context).textTheme.headlineMedium,
111
-            ),
112
-          ],
20
+    return MultiProvider(
21
+      providers: [
22
+        ChangeNotifierProvider(create: (_) => di.sl<AuthProvider>()),
23
+        ChangeNotifierProvider(create: (_) => di.sl<UserProvider>()),
24
+      ],
25
+      child: MaterialApp(
26
+        title: 'Flutter App',
27
+        theme: ThemeData(
28
+          primarySwatch: Colors.blue,
29
+          // fontFamily: 'Roboto',
30
+          appBarTheme: const AppBarTheme(
31
+            backgroundColor: Colors.white,
32
+            foregroundColor: Colors.black,
33
+            elevation: 0,
34
+            centerTitle: true,
35
+          ),
36
+          scaffoldBackgroundColor: Colors.white,
37
+          bottomNavigationBarTheme: const BottomNavigationBarThemeData(
38
+            backgroundColor: Colors.white,
39
+            selectedItemColor: Colors.blue,
40
+            unselectedItemColor: Colors.grey,
41
+            showUnselectedLabels: true,
42
+          ),
113 43
         ),
114
-      ),
115
-      floatingActionButton: FloatingActionButton(
116
-        onPressed: _incrementCounter,
117
-        tooltip: 'Increment',
118
-        child: const Icon(Icons.add),
44
+        debugShowCheckedModeBanner: false,
45
+        initialRoute: '/',
46
+        onGenerateRoute: AppRouter.onGenerateRoute,
119 47
       ),
120 48
     );
121 49
   }
122
-}
50
+}

+ 49
- 0
lib/presentation/navigation/app_router.dart Просмотреть файл

@@ -0,0 +1,49 @@
1
+import 'package:flutter/material.dart';
2
+import '../screens/auth/login_screen.dart';
3
+import '../screens/auth/register_screen.dart';
4
+import '../screens/home/home_screen.dart';
5
+import '../screens/news/news_screen.dart';
6
+import '../screens/services/services_screen.dart';
7
+import '../screens/profile/profile_screen.dart';
8
+import '../screens/profile/profile_detail_screen.dart';
9
+import '../screens/splash_screen.dart';
10
+import '../../core/constants/route_constants.dart';
11
+
12
+class AppRouter {
13
+  static Route<dynamic> onGenerateRoute(RouteSettings settings) {
14
+    switch (settings.name) {
15
+      case RouteConstants.splash:
16
+        return MaterialPageRoute(builder: (_) => const SplashScreen());
17
+      case RouteConstants.home:
18
+        return MaterialPageRoute(builder: (_) => const HomeScreen());
19
+      case RouteConstants.login:
20
+        return MaterialPageRoute(builder: (_) => const LoginScreen());
21
+      case RouteConstants.register:
22
+        return MaterialPageRoute(builder: (_) => const RegisterScreen());
23
+      case RouteConstants.news:
24
+        return MaterialPageRoute(builder: (_) => const NewsScreen());
25
+      case RouteConstants.services:
26
+        return MaterialPageRoute(builder: (_) => const ServicesScreen());
27
+      case RouteConstants.profile:
28
+        return MaterialPageRoute(builder: (_) => const ProfileScreen());
29
+      case RouteConstants.profileDetail:
30
+        return MaterialPageRoute(builder: (_) => const ProfileDetailScreen());
31
+      default:
32
+        return MaterialPageRoute(
33
+          builder: (_) => Scaffold(
34
+            body: Center(
35
+              child: Text('找不到页面: ${settings.name}'),
36
+            ),
37
+          ),
38
+        );
39
+    }
40
+  }
41
+  
42
+  static void configureRoutes(BuildContext context) {
43
+    // 替换主应用的路由生成器
44
+    final materialApp = context.findAncestorWidgetOfExactType<MaterialApp>();
45
+    if (materialApp != null) {
46
+      // 这里通常会在main.dart中配置
47
+    }
48
+  }
49
+}

+ 97
- 0
lib/presentation/navigation/bottom_nav_bar.dart Просмотреть файл

@@ -0,0 +1,97 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../../core/constants/route_constants.dart';
4
+import '../../presentation/providers/auth_provider.dart';
5
+
6
+class BottomNavBar extends StatefulWidget {
7
+  final int initialIndex;
8
+  
9
+  const BottomNavBar({
10
+    super.key,
11
+    this.initialIndex = 0,
12
+  });
13
+  
14
+  @override
15
+  State<BottomNavBar> createState() => _BottomNavBarState();
16
+}
17
+
18
+class _BottomNavBarState extends State<BottomNavBar> {
19
+  int _selectedIndex = 0;
20
+  
21
+  @override
22
+  void initState() {
23
+    super.initState();
24
+    _selectedIndex = widget.initialIndex;
25
+  }
26
+  
27
+  void _onItemTapped(int index) {
28
+    final authProvider = Provider.of<AuthProvider>(context, listen: false);
29
+    
30
+    // 如果点击"我的"但未登录,跳转到登录页
31
+    if (index == 3 && !authProvider.isAuthenticated) {
32
+      Navigator.of(context).pushNamed(RouteConstants.login);
33
+      return;
34
+    }
35
+    
36
+    setState(() {
37
+      _selectedIndex = index;
38
+    });
39
+    
40
+    switch (index) {
41
+      case 0:
42
+        if (ModalRoute.of(context)?.settings.name != RouteConstants.home) {
43
+          Navigator.of(context).pushReplacementNamed(RouteConstants.home);
44
+        }
45
+        break;
46
+      case 1:
47
+        if (ModalRoute.of(context)?.settings.name != RouteConstants.news) {
48
+          Navigator.of(context).pushReplacementNamed(RouteConstants.news);
49
+        }
50
+        break;
51
+      case 2:
52
+        if (ModalRoute.of(context)?.settings.name != RouteConstants.services) {
53
+          Navigator.of(context).pushReplacementNamed(RouteConstants.services);
54
+        }
55
+        break;
56
+      case 3:
57
+        if (ModalRoute.of(context)?.settings.name != RouteConstants.profile) {
58
+          Navigator.of(context).pushReplacementNamed(RouteConstants.profile);
59
+        }
60
+        break;
61
+    }
62
+  }
63
+  
64
+  @override
65
+  Widget build(BuildContext context) {
66
+    return BottomNavigationBar(
67
+      items: const [
68
+        BottomNavigationBarItem(
69
+          icon: Icon(Icons.home_outlined),
70
+          activeIcon: Icon(Icons.home),
71
+          label: '首页',
72
+        ),
73
+        BottomNavigationBarItem(
74
+          icon: Icon(Icons.newspaper_outlined),
75
+          activeIcon: Icon(Icons.newspaper),
76
+          label: '资讯',
77
+        ),
78
+        BottomNavigationBarItem(
79
+          icon: Icon(Icons.work_outline),
80
+          activeIcon: Icon(Icons.work),
81
+          label: '服务',
82
+        ),
83
+        BottomNavigationBarItem(
84
+          icon: Icon(Icons.person_outline),
85
+          activeIcon: Icon(Icons.person),
86
+          label: '我的',
87
+        ),
88
+      ],
89
+      currentIndex: _selectedIndex,
90
+      selectedItemColor: Theme.of(context).primaryColor,
91
+      unselectedItemColor: Colors.grey,
92
+      showUnselectedLabels: true,
93
+      type: BottomNavigationBarType.fixed,
94
+      onTap: _onItemTapped,
95
+    );
96
+  }
97
+}

+ 27
- 0
lib/presentation/navigation/route_guards.dart Просмотреть файл

@@ -0,0 +1,27 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../screens/auth/login_screen.dart';
4
+import '../providers/auth_provider.dart';
5
+
6
+class ProtectedRoute extends StatelessWidget {
7
+  final Widget child;
8
+  
9
+  const ProtectedRoute({Key? key, required this.child}) : super(key: key);
10
+  
11
+  @override
12
+  Widget build(BuildContext context) {
13
+    final authProvider = Provider.of<AuthProvider>(context);
14
+    
15
+    if (authProvider.isAuthenticated) {
16
+      return child;
17
+    } else {
18
+      return LoginScreen(
19
+        onSuccess: () {
20
+          Navigator.of(context).pushReplacement(
21
+            MaterialPageRoute(builder: (_) => child),
22
+          );
23
+        },
24
+      );
25
+    }
26
+  }
27
+}

+ 0
- 0
lib/presentation/providers/app_provider.dart Просмотреть файл


+ 99
- 0
lib/presentation/providers/auth_provider.dart Просмотреть файл

@@ -0,0 +1,99 @@
1
+import 'package:flutter/material.dart';
2
+import '../../data/models/user.dart';
3
+import '../../data/models/auth/login_request.dart';
4
+import '../../data/models/auth/register_request.dart';
5
+import '../../data/repositories/auth_repository.dart';
6
+
7
+class AuthProvider with ChangeNotifier {
8
+  final AuthRepository authRepository;
9
+  
10
+  User? _user;
11
+  bool _isLoading = false;
12
+  String? _error;
13
+  
14
+  AuthProvider({required this.authRepository}) {
15
+    _loadCurrentUser();
16
+  }
17
+  
18
+  User? get user => _user;
19
+  bool get isAuthenticated => _user != null;
20
+  bool get isLoading => _isLoading;
21
+  String? get error => _error;
22
+  
23
+  Future<void> _loadCurrentUser() async {
24
+    final user = await authRepository.getCurrentUser();
25
+    if (user != null) {
26
+      _user = user.data;
27
+      notifyListeners();
28
+    }
29
+  }
30
+  
31
+  Future<void> login(String username, String password) async {
32
+    _isLoading = true;
33
+    _error = null;
34
+    notifyListeners();
35
+    
36
+    try {
37
+      final response = await authRepository.login(
38
+        LoginRequest(username: username, password: password),
39
+      );
40
+      
41
+      if (response.success && response.data != null) {
42
+        _user = response.data;
43
+        _error = null;
44
+      } else {
45
+        _error = response.message;
46
+      }
47
+    } catch (e) {
48
+      _error = '登录失败: $e';
49
+    } finally {
50
+      _isLoading = false;
51
+      notifyListeners();
52
+    }
53
+  }
54
+  
55
+  Future<void> register(String username, String email, String password, String passwordConfirm, String? phone) async {
56
+    _isLoading = true;
57
+    _error = null;
58
+    notifyListeners();
59
+    
60
+    try {
61
+      final response = await authRepository.register(
62
+        RegisterRequest(username: username, email: email, password: password, passwordConfirm: passwordConfirm),
63
+      );
64
+      
65
+      if (response.success && response.data != null) {
66
+        _user = response.data;
67
+        _error = null;
68
+      } else {
69
+        _error = response.message;
70
+      }
71
+    } catch (e) {
72
+      _error = '注册失败: $e';
73
+    } finally {
74
+      _isLoading = false;
75
+      notifyListeners();
76
+    }
77
+  }
78
+  
79
+  Future<void> logout() async {
80
+    await authRepository.logout();
81
+    _user = null;
82
+    notifyListeners();
83
+  }
84
+  
85
+  Future<void> checkAuthStatus() async {
86
+    final isLoggedIn = await authRepository.isLoggedIn();
87
+    if (!isLoggedIn) {
88
+      _user = null;
89
+    } else {
90
+      await _loadCurrentUser();
91
+    }
92
+    notifyListeners();
93
+  }
94
+  
95
+  void clearError() {
96
+    _error = null;
97
+    notifyListeners();
98
+  }
99
+}

+ 62
- 0
lib/presentation/providers/user_provider.dart Просмотреть файл

@@ -0,0 +1,62 @@
1
+import 'package:flutter/material.dart';
2
+import '../../data/models/user.dart';
3
+import '../../data/repositories/user_repository.dart';
4
+
5
+class UserProvider with ChangeNotifier {
6
+  final UserRepository userRepository;
7
+  
8
+  User? _user;
9
+  bool _isLoading = false;
10
+  String? _error;
11
+  
12
+  UserProvider({required this.userRepository});
13
+  
14
+  User? get user => _user;
15
+  bool get isLoading => _isLoading;
16
+  String? get error => _error;
17
+  
18
+  Future<void> loadUserProfile() async {
19
+    _isLoading = true;
20
+    _error = null;
21
+    notifyListeners();
22
+    
23
+    try {
24
+      final response = await userRepository.getUserProfile();
25
+      if (response.success && response.data != null) {
26
+        _user = response.data;
27
+      } else {
28
+        _error = response.message;
29
+      }
30
+    } catch (e) {
31
+      _error = '加载失败: $e';
32
+    } finally {
33
+      _isLoading = false;
34
+      notifyListeners();
35
+    }
36
+  }
37
+  
38
+  Future<void> updateUserProfile(User user) async {
39
+    _isLoading = true;
40
+    _error = null;
41
+    notifyListeners();
42
+    
43
+    try {
44
+      final response = await userRepository.updateUserProfile(user);
45
+      if (response.success && response.data != null) {
46
+        _user = response.data;
47
+      } else {
48
+        _error = response.message;
49
+      }
50
+    } catch (e) {
51
+      _error = '更新失败: $e';
52
+    } finally {
53
+      _isLoading = false;
54
+      notifyListeners();
55
+    }
56
+  }
57
+  
58
+  void clearError() {
59
+    _error = null;
60
+    notifyListeners();
61
+  }
62
+}

+ 187
- 0
lib/presentation/screens/auth/login_screen.dart Просмотреть файл

@@ -0,0 +1,187 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../../providers/auth_provider.dart';
4
+import '../../../core/constants/route_constants.dart';
5
+import '../../widgets/common/app_button.dart';
6
+import '../../widgets/common/app_text_field.dart';
7
+import '../../widgets/common/loading_indicator.dart';
8
+
9
+class LoginScreen extends StatefulWidget {
10
+  final VoidCallback? onSuccess;
11
+  
12
+  const LoginScreen({super.key, this.onSuccess});
13
+  
14
+  @override
15
+  State<LoginScreen> createState() => _LoginScreenState();
16
+}
17
+
18
+class _LoginScreenState extends State<LoginScreen> {
19
+  final _formKey = GlobalKey<FormState>();
20
+  final _emailController = TextEditingController(text: 'test@example.com');
21
+  final _passwordController = TextEditingController(text: '123456');
22
+  
23
+  @override
24
+  void dispose() {
25
+    _emailController.dispose();
26
+    _passwordController.dispose();
27
+    super.dispose();
28
+  }
29
+  
30
+  @override
31
+  Widget build(BuildContext context) {
32
+    final authProvider = Provider.of<AuthProvider>(context);
33
+    
34
+    return Scaffold(
35
+      appBar: AppBar(
36
+        title: const Text('登录'),
37
+      ),
38
+      body: SingleChildScrollView(
39
+        child: Padding(
40
+          padding: const EdgeInsets.all(24.0),
41
+          child: Form(
42
+            key: _formKey,
43
+            child: Column(
44
+              crossAxisAlignment: CrossAxisAlignment.start,
45
+              children: [
46
+                const SizedBox(height: 40),
47
+                const Center(
48
+                  child: Text(
49
+                    '欢迎回来',
50
+                    style: TextStyle(
51
+                      fontSize: 28,
52
+                      fontWeight: FontWeight.bold,
53
+                    ),
54
+                  ),
55
+                ),
56
+                const SizedBox(height: 8),
57
+                const Center(
58
+                  child: Text(
59
+                    '请输入您的账号信息',
60
+                    style: TextStyle(
61
+                      fontSize: 16,
62
+                      color: Colors.grey,
63
+                    ),
64
+                  ),
65
+                ),
66
+                const SizedBox(height: 40),
67
+                AppTextField(
68
+                  controller: _emailController,  // 改为username
69
+                  labelText: '用户名',
70
+                  hintText: '请输入用户名',
71
+                  prefixIcon: const Icon(Icons.person_outline),
72
+                  validator: (value) {
73
+                    if (value == null || value.isEmpty) {
74
+                      return '请输入用户名';
75
+                    }
76
+                    return null;
77
+                  },
78
+                ),
79
+                const SizedBox(height: 20),
80
+                AppTextField(
81
+                  controller: _passwordController,
82
+                  labelText: '密码',
83
+                  hintText: '请输入密码',
84
+                  obscureText: true,
85
+                  prefixIcon: const Icon(Icons.lock_outline),
86
+                  validator: (value) {
87
+                    if (value == null || value.isEmpty) {
88
+                      return '请输入密码';
89
+                    }
90
+                    if (value.length < 6) {
91
+                      return '密码至少需要6位字符';
92
+                    }
93
+                    return null;
94
+                  },
95
+                ),
96
+                const SizedBox(height: 30),
97
+                if (authProvider.error != null)
98
+                  Padding(
99
+                    padding: const EdgeInsets.only(bottom: 16),
100
+                    child: Text(
101
+                      authProvider.error!,
102
+                      style: const TextStyle(
103
+                        color: Colors.red,
104
+                        fontSize: 14,
105
+                      ),
106
+                    ),
107
+                  ),
108
+                if (authProvider.isLoading)
109
+                  const LoadingIndicator()
110
+                else
111
+                  AppButton(
112
+                    text: '登录',
113
+                    onPressed: () async {
114
+                      if (_formKey.currentState!.validate()) {
115
+                        await authProvider.login(
116
+                          _emailController.text.trim(),
117
+                          _passwordController.text,
118
+                        );
119
+                        if (authProvider.isAuthenticated) {
120
+                          widget.onSuccess?.call();
121
+                          Navigator.of(context).pushReplacementNamed(RouteConstants.home);
122
+                        }
123
+                      }
124
+                    },
125
+                  ),
126
+                const SizedBox(height: 20),
127
+                Center(
128
+                  child: TextButton(
129
+                    onPressed: () {
130
+                      Navigator.of(context).pushNamed(RouteConstants.register);
131
+                    },
132
+                    child: const Text(
133
+                      '还没有账号?立即注册',
134
+                      style: TextStyle(
135
+                        fontSize: 14,
136
+                        color: Colors.blue,
137
+                      ),
138
+                    ),
139
+                  ),
140
+                ),
141
+                const SizedBox(height: 30),
142
+                const Row(
143
+                  children: [
144
+                    Expanded(child: Divider()),
145
+                    Padding(
146
+                      padding: EdgeInsets.symmetric(horizontal: 16),
147
+                      child: Text('或'),
148
+                    ),
149
+                    Expanded(child: Divider()),
150
+                  ],
151
+                ),
152
+                const SizedBox(height: 20),
153
+                Row(
154
+                  mainAxisAlignment: MainAxisAlignment.center,
155
+                  children: [
156
+                    IconButton(
157
+                      onPressed: () {},
158
+                      icon: Container(
159
+                        padding: const EdgeInsets.all(10),
160
+                        decoration: BoxDecoration(
161
+                          color: Colors.grey[100],
162
+                          borderRadius: BorderRadius.circular(50),
163
+                        ),
164
+                        child: const Icon(Icons.wechat, color: Colors.green),
165
+                      ),
166
+                    ),
167
+                    IconButton(
168
+                      onPressed: () {},
169
+                      icon: Container(
170
+                        padding: const EdgeInsets.all(10),
171
+                        decoration: BoxDecoration(
172
+                          color: Colors.grey[100],
173
+                          borderRadius: BorderRadius.circular(50),
174
+                        ),
175
+                        child: const Icon(Icons.wechat, color: Colors.blue),
176
+                      ),
177
+                    ),
178
+                  ],
179
+                ),
180
+              ],
181
+            ),
182
+          ),
183
+        ),
184
+      ),
185
+    );
186
+  }
187
+}

+ 221
- 0
lib/presentation/screens/auth/register_screen.dart Просмотреть файл

@@ -0,0 +1,221 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../../providers/auth_provider.dart';
4
+import '../../../core/constants/route_constants.dart';
5
+import '../../widgets/common/app_button.dart';
6
+import '../../widgets/common/app_text_field.dart';
7
+import '../../widgets/common/loading_indicator.dart';
8
+
9
+class RegisterScreen extends StatefulWidget {
10
+  const RegisterScreen({super.key});
11
+
12
+  @override
13
+  State<RegisterScreen> createState() => _RegisterScreenState();
14
+}
15
+
16
+class _RegisterScreenState extends State<RegisterScreen> {
17
+  final _formKey = GlobalKey<FormState>();
18
+  final _nameController = TextEditingController();
19
+  final _emailController = TextEditingController();
20
+  final _phoneController = TextEditingController();
21
+  final _passwordController = TextEditingController();
22
+  final _confirmPasswordController = TextEditingController();
23
+  
24
+  @override
25
+  void dispose() {
26
+    _nameController.dispose();
27
+    _emailController.dispose();
28
+    _phoneController.dispose();
29
+    _passwordController.dispose();
30
+    _confirmPasswordController.dispose();
31
+    super.dispose();
32
+  }
33
+  
34
+  @override
35
+  Widget build(BuildContext context) {
36
+    final authProvider = Provider.of<AuthProvider>(context);
37
+    
38
+    return Scaffold(
39
+      appBar: AppBar(
40
+        title: const Text('注册'),
41
+      ),
42
+      body: SingleChildScrollView(
43
+        child: Padding(
44
+          padding: const EdgeInsets.all(24.0),
45
+          child: Form(
46
+            key: _formKey,
47
+            child: Column(
48
+              crossAxisAlignment: CrossAxisAlignment.start,
49
+              children: [
50
+                const SizedBox(height: 20),
51
+                const Text(
52
+                  '创建账号',
53
+                  style: TextStyle(
54
+                    fontSize: 24,
55
+                    fontWeight: FontWeight.bold,
56
+                  ),
57
+                ),
58
+                const SizedBox(height: 8),
59
+                const Text(
60
+                  '请填写以下信息完成注册',
61
+                  style: TextStyle(
62
+                    fontSize: 14,
63
+                    color: Colors.grey,
64
+                  ),
65
+                ),
66
+                const SizedBox(height: 30),
67
+                AppTextField(
68
+                  controller: _nameController,
69
+                  labelText: '姓名',
70
+                  hintText: '请输入您的姓名',
71
+                  prefixIcon: const Icon(Icons.person_outline),
72
+                  validator: (value) {
73
+                    if (value == null || value.isEmpty) {
74
+                      return '请输入姓名';
75
+                    }
76
+                    return null;
77
+                  },
78
+                ),
79
+                const SizedBox(height: 20),
80
+                AppTextField(
81
+                  controller: _emailController,
82
+                  labelText: '邮箱地址',
83
+                  hintText: '请输入邮箱',
84
+                  keyboardType: TextInputType.emailAddress,
85
+                  prefixIcon: const Icon(Icons.email_outlined),
86
+                  validator: (value) {
87
+                    if (value == null || value.isEmpty) {
88
+                      return '请输入邮箱地址';
89
+                    }
90
+                    if (!value.contains('@')) {
91
+                      return '请输入有效的邮箱地址';
92
+                    }
93
+                    return null;
94
+                  },
95
+                ),
96
+                const SizedBox(height: 20),
97
+                AppTextField(
98
+                  controller: _phoneController,
99
+                  labelText: '手机号码(可选)',
100
+                  hintText: '请输入手机号码',
101
+                  keyboardType: TextInputType.phone,
102
+                  prefixIcon: const Icon(Icons.phone_outlined),
103
+                ),
104
+                const SizedBox(height: 20),
105
+                AppTextField(
106
+                  controller: _passwordController,
107
+                  labelText: '密码',
108
+                  hintText: '请输入密码',
109
+                  obscureText: true,
110
+                  prefixIcon: const Icon(Icons.lock_outline),
111
+                  validator: (value) {
112
+                    if (value == null || value.isEmpty) {
113
+                      return '请输入密码';
114
+                    }
115
+                    if (value.length < 6) {
116
+                      return '密码至少需要6位字符';
117
+                    }
118
+                    return null;
119
+                  },
120
+                ),
121
+                const SizedBox(height: 20),
122
+                AppTextField(
123
+                  controller: _confirmPasswordController,
124
+                  labelText: '确认密码',
125
+                  hintText: '请再次输入密码',
126
+                  obscureText: true,
127
+                  prefixIcon: const Icon(Icons.lock_outline),
128
+                  validator: (value) {
129
+                    if (value == null || value.isEmpty) {
130
+                      return '请确认密码';
131
+                    }
132
+                    if (value != _passwordController.text) {
133
+                      return '两次输入的密码不一致';
134
+                    }
135
+                    return null;
136
+                  },
137
+                ),
138
+                const SizedBox(height: 30),
139
+                if (authProvider.error != null)
140
+                  Padding(
141
+                    padding: const EdgeInsets.only(bottom: 16),
142
+                    child: Text(
143
+                      authProvider.error!,
144
+                      style: const TextStyle(
145
+                        color: Colors.red,
146
+                        fontSize: 14,
147
+                      ),
148
+                    ),
149
+                  ),
150
+                if (authProvider.isLoading)
151
+                  const LoadingIndicator()
152
+                else
153
+                  AppButton(
154
+                    text: '注册',
155
+                    onPressed: () async {
156
+                      if (_formKey.currentState!.validate()) {
157
+                        await authProvider.register(
158
+                          _nameController.text,
159
+                          _emailController.text.trim(),
160
+                          _passwordController.text,
161
+                          _passwordController.text,
162
+                          _phoneController.text.isNotEmpty
163
+                              ? _phoneController.text
164
+                              : null,
165
+                        );
166
+                        if (authProvider.isAuthenticated) {
167
+                          Navigator.of(context).pushReplacementNamed(
168
+                            RouteConstants.home,
169
+                          );
170
+                        }
171
+                      }
172
+                    },
173
+                  ),
174
+                const SizedBox(height: 20),
175
+                Center(
176
+                  child: TextButton(
177
+                    onPressed: () {
178
+                      Navigator.of(context).pop();
179
+                    },
180
+                    child: const Text(
181
+                      '已有账号?立即登录',
182
+                      style: TextStyle(
183
+                        fontSize: 14,
184
+                        color: Colors.blue,
185
+                      ),
186
+                    ),
187
+                  ),
188
+                ),
189
+                const SizedBox(height: 40),
190
+                Padding(
191
+                  padding: const EdgeInsets.symmetric(horizontal: 16),
192
+                  child: RichText(
193
+                    textAlign: TextAlign.center,
194
+                    text: const TextSpan(
195
+                      style: TextStyle(
196
+                        fontSize: 12,
197
+                        color: Colors.grey,
198
+                      ),
199
+                      children: [
200
+                        TextSpan(text: '注册即表示您同意我们的'),
201
+                        TextSpan(
202
+                          text: '服务条款',
203
+                          style: TextStyle(color: Colors.blue),
204
+                        ),
205
+                        TextSpan(text: '和'),
206
+                        TextSpan(
207
+                          text: '隐私政策',
208
+                          style: TextStyle(color: Colors.blue),
209
+                        ),
210
+                      ],
211
+                    ),
212
+                  ),
213
+                ),
214
+              ],
215
+            ),
216
+          ),
217
+        ),
218
+      ),
219
+    );
220
+  }
221
+}

+ 307
- 0
lib/presentation/screens/home/home_screen.dart Просмотреть файл

@@ -0,0 +1,307 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../../navigation/bottom_nav_bar.dart';
4
+import '../../providers/auth_provider.dart';
5
+import '../../widgets/common/app_button.dart';
6
+
7
+class HomeScreen extends StatelessWidget {
8
+  const HomeScreen({super.key});
9
+
10
+  @override
11
+  Widget build(BuildContext context) {
12
+    final authProvider = Provider.of<AuthProvider>(context);
13
+    
14
+    return Scaffold(
15
+      appBar: AppBar(
16
+        title: const Text('首页'),
17
+        actions: [
18
+          if (authProvider.isAuthenticated)
19
+            IconButton(
20
+              icon: const Icon(Icons.notifications_none),
21
+              onPressed: () {},
22
+            ),
23
+        ],
24
+      ),
25
+      body: SingleChildScrollView(
26
+        child: Padding(
27
+          padding: const EdgeInsets.all(20),
28
+          child: Column(
29
+            crossAxisAlignment: CrossAxisAlignment.start,
30
+            children: [
31
+              // 欢迎区域
32
+              Card(
33
+                elevation: 0,
34
+                shape: RoundedRectangleBorder(
35
+                  borderRadius: BorderRadius.circular(16),
36
+                ),
37
+                color: Colors.blue[50],
38
+                child: Padding(
39
+                  padding: const EdgeInsets.all(20),
40
+                  child: Column(
41
+                    crossAxisAlignment: CrossAxisAlignment.start,
42
+                    children: [
43
+                      Row(
44
+                        children: [
45
+                          CircleAvatar(
46
+                            radius: 30,
47
+                            backgroundColor: Colors.blue[100],
48
+                            child: Icon(
49
+                              Icons.person,
50
+                              size: 30,
51
+                              color: Colors.blue,
52
+                            ),
53
+                          ),
54
+                          const SizedBox(width: 16),
55
+                          Expanded(
56
+                            child: Column(
57
+                              crossAxisAlignment: CrossAxisAlignment.start,
58
+                              children: [
59
+                                Text(
60
+                                  authProvider.isAuthenticated
61
+                                      ? '您好,${authProvider.user?.fullName}'
62
+                                      : '您好,游客',
63
+                                  style: const TextStyle(
64
+                                    fontSize: 18,
65
+                                    fontWeight: FontWeight.bold,
66
+                                  ),
67
+                                ),
68
+                                const SizedBox(height: 4),
69
+                                Text(
70
+                                  authProvider.isAuthenticated
71
+                                      ? '欢迎回来!'
72
+                                      : '请登录以使用完整功能',
73
+                                  style: TextStyle(
74
+                                    fontSize: 14,
75
+                                    color: Colors.grey[600],
76
+                                  ),
77
+                                ),
78
+                              ],
79
+                            ),
80
+                          ),
81
+                        ],
82
+                      ),
83
+                      const SizedBox(height: 20),
84
+                      if (!authProvider.isAuthenticated)
85
+                        AppButton(
86
+                          text: '立即登录',
87
+                          onPressed: () {
88
+                            Navigator.of(context).pushNamed('/login');
89
+                          },
90
+                          backgroundColor: Colors.blue,
91
+                          height: 45,
92
+                        ),
93
+                    ],
94
+                  ),
95
+                ),
96
+              ),
97
+              const SizedBox(height: 30),
98
+              
99
+              // 功能卡片
100
+              const Text(
101
+                '功能服务',
102
+                style: TextStyle(
103
+                  fontSize: 18,
104
+                  fontWeight: FontWeight.bold,
105
+                ),
106
+              ),
107
+              const SizedBox(height: 16),
108
+              GridView.count(
109
+                crossAxisCount: 3,
110
+                shrinkWrap: true,
111
+                physics: const NeverScrollableScrollPhysics(),
112
+                childAspectRatio: 0.9,
113
+                children: [
114
+                  _buildFeatureCard(
115
+                    icon: Icons.newspaper,
116
+                    title: '资讯',
117
+                    color: Colors.green,
118
+                    onTap: () {
119
+                      Navigator.of(context).pushNamed('/news');
120
+                    },
121
+                  ),
122
+                  _buildFeatureCard(
123
+                    icon: Icons.work,
124
+                    title: '服务',
125
+                    color: Colors.orange,
126
+                    onTap: () {
127
+                      Navigator.of(context).pushNamed('/services');
128
+                    },
129
+                  ),
130
+                  _buildFeatureCard(
131
+                    icon: Icons.person,
132
+                    title: '我的',
133
+                    color: Colors.purple,
134
+                    onTap: () {
135
+                      if (authProvider.isAuthenticated) {
136
+                        Navigator.of(context).pushNamed('/profile');
137
+                      } else {
138
+                        Navigator.of(context).pushNamed('/login');
139
+                      }
140
+                    },
141
+                  ),
142
+                  _buildFeatureCard(
143
+                    icon: Icons.settings,
144
+                    title: '设置',
145
+                    color: Colors.grey,
146
+                    onTap: () {},
147
+                  ),
148
+                  _buildFeatureCard(
149
+                    icon: Icons.help,
150
+                    title: '帮助',
151
+                    color: Colors.red,
152
+                    onTap: () {},
153
+                  ),
154
+                  _buildFeatureCard(
155
+                    icon: Icons.info,
156
+                    title: '关于',
157
+                    color: Colors.teal,
158
+                    onTap: () {},
159
+                  ),
160
+                ],
161
+              ),
162
+              const SizedBox(height: 30),
163
+              
164
+              // 快速访问
165
+              const Text(
166
+                '快速访问',
167
+                style: TextStyle(
168
+                  fontSize: 18,
169
+                  fontWeight: FontWeight.bold,
170
+                ),
171
+              ),
172
+              const SizedBox(height: 16),
173
+              Card(
174
+                elevation: 2,
175
+                shape: RoundedRectangleBorder(
176
+                  borderRadius: BorderRadius.circular(12),
177
+                ),
178
+                child: Column(
179
+                  children: [
180
+                    _buildQuickAccessItem(
181
+                      icon: Icons.history,
182
+                      title: '最近访问',
183
+                      subtitle: '查看您最近的操作记录',
184
+                    ),
185
+                    const Divider(height: 1),
186
+                    _buildQuickAccessItem(
187
+                      icon: Icons.star_border,
188
+                      title: '我的收藏',
189
+                      subtitle: '查看您收藏的内容',
190
+                    ),
191
+                    const Divider(height: 1),
192
+                    _buildQuickAccessItem(
193
+                      icon: Icons.download,
194
+                      title: '下载管理',
195
+                      subtitle: '管理您的下载文件',
196
+                    ),
197
+                  ],
198
+                ),
199
+              ),
200
+              const SizedBox(height: 20),
201
+              
202
+              // 系统信息
203
+              Card(
204
+                elevation: 0,
205
+                shape: RoundedRectangleBorder(
206
+                  borderRadius: BorderRadius.circular(12),
207
+                  side: BorderSide(color: Colors.grey[300]!),
208
+                ),
209
+                child: Padding(
210
+                  padding: const EdgeInsets.all(16),
211
+                  child: Column(
212
+                    crossAxisAlignment: CrossAxisAlignment.start,
213
+                    children: [
214
+                      const Text(
215
+                        '系统信息',
216
+                        style: TextStyle(
217
+                          fontSize: 16,
218
+                          fontWeight: FontWeight.bold,
219
+                        ),
220
+                      ),
221
+                      const SizedBox(height: 12),
222
+                      Row(
223
+                        children: [
224
+                          Icon(Icons.check_circle, color: Colors.green, size: 16),
225
+                          const SizedBox(width: 8),
226
+                          const Expanded(
227
+                            child: Text('系统运行正常'),
228
+                          ),
229
+                          Text(
230
+                            'v1.0.0',
231
+                            style: TextStyle(
232
+                              color: Colors.grey[600],
233
+                              fontSize: 12,
234
+                            ),
235
+                          ),
236
+                        ],
237
+                      ),
238
+                    ],
239
+                  ),
240
+                ),
241
+              ),
242
+            ],
243
+          ),
244
+        ),
245
+      ),
246
+      bottomNavigationBar: const BottomNavBar(initialIndex: 0),
247
+    );
248
+  }
249
+  
250
+  Widget _buildFeatureCard({
251
+    required IconData icon,
252
+    required String title,
253
+    required Color color,
254
+    required VoidCallback onTap,
255
+  }) {
256
+    return InkWell(
257
+      onTap: onTap,
258
+      borderRadius: BorderRadius.circular(12),
259
+      child: Card(
260
+        elevation: 2,
261
+        shape: RoundedRectangleBorder(
262
+          borderRadius: BorderRadius.circular(12),
263
+        ),
264
+        child: Column(
265
+          mainAxisAlignment: MainAxisAlignment.center,
266
+          children: [
267
+            Container(
268
+              width: 50,
269
+              height: 50,
270
+              decoration: BoxDecoration(
271
+                color: Color.lerp(Colors.white, Colors.transparent, 0.9)!,  // 0.3表示30%透明
272
+                borderRadius: BorderRadius.circular(25),
273
+              ),
274
+              child: Icon(
275
+                icon,
276
+                color: color,
277
+                size: 28,
278
+              ),
279
+            ),
280
+            const SizedBox(height: 12),
281
+            Text(
282
+              title,
283
+              style: const TextStyle(
284
+                fontSize: 14,
285
+                fontWeight: FontWeight.w500,
286
+              ),
287
+            ),
288
+          ],
289
+        ),
290
+      ),
291
+    );
292
+  }
293
+  
294
+  Widget _buildQuickAccessItem({
295
+    required IconData icon,
296
+    required String title,
297
+    required String subtitle,
298
+  }) {
299
+    return ListTile(
300
+      leading: Icon(icon),
301
+      title: Text(title),
302
+      subtitle: Text(subtitle),
303
+      trailing: const Icon(Icons.chevron_right),
304
+      onTap: () {},
305
+    );
306
+  }
307
+}

+ 172
- 0
lib/presentation/screens/news/news_screen.dart Просмотреть файл

@@ -0,0 +1,172 @@
1
+import 'package:flutter/material.dart';
2
+import '../../navigation/bottom_nav_bar.dart';
3
+
4
+class NewsScreen extends StatelessWidget {
5
+  const NewsScreen({super.key});
6
+
7
+  @override
8
+  Widget build(BuildContext context) {
9
+    return Scaffold(
10
+      appBar: AppBar(
11
+        title: const Text('资讯'),
12
+        actions: [
13
+          IconButton(
14
+            icon: const Icon(Icons.search),
15
+            onPressed: () {},
16
+          ),
17
+        ],
18
+      ),
19
+      body: ListView.builder(
20
+        padding: const EdgeInsets.all(16),
21
+        itemCount: 10,
22
+        itemBuilder: (context, index) {
23
+          return _buildNewsCard(index);
24
+        },
25
+      ),
26
+      bottomNavigationBar: const BottomNavBar(initialIndex: 1),
27
+    );
28
+  }
29
+  
30
+  Widget _buildNewsCard(int index) {
31
+    final titles = [
32
+      'Flutter 3.0 新特性详解',
33
+      'Dart 语言最新更新',
34
+      '移动开发趋势分析',
35
+      '前端框架对比',
36
+      '用户体验设计原则',
37
+      '后端架构最佳实践',
38
+      '数据库性能优化',
39
+      '云原生技术解析',
40
+      '人工智能在移动端的应用',
41
+      '跨平台开发方案比较',
42
+    ];
43
+    
44
+    final subtitles = [
45
+      '深入了解Flutter最新版本的重要更新和改进',
46
+      'Dart语言的最新特性和优化方向',
47
+      '2024年移动开发的重要趋势和技术方向',
48
+      '主流前端框架的优缺点对比分析',
49
+      '提升应用用户体验的关键设计原则',
50
+      '构建高性能后端服务的最佳实践',
51
+      '数据库查询优化和性能调优技巧',
52
+      '云原生技术在微服务中的应用',
53
+      'AI技术在移动应用中的创新应用',
54
+      '各种跨平台开发方案的对比和选择',
55
+    ];
56
+    
57
+    final times = [
58
+      '2小时前',
59
+      '5小时前',
60
+      '昨天',
61
+      '2天前',
62
+      '3天前',
63
+      '1周前',
64
+      '1周前',
65
+      '2周前',
66
+      '2周前',
67
+      '1个月前',
68
+    ];
69
+    
70
+    return Card(
71
+      margin: const EdgeInsets.only(bottom: 16),
72
+      shape: RoundedRectangleBorder(
73
+        borderRadius: BorderRadius.circular(12),
74
+      ),
75
+      child: InkWell(
76
+        onTap: () {},
77
+        borderRadius: BorderRadius.circular(12),
78
+        child: Padding(
79
+          padding: const EdgeInsets.all(16),
80
+          child: Column(
81
+            crossAxisAlignment: CrossAxisAlignment.start,
82
+            children: [
83
+              Row(
84
+                children: [
85
+                  Container(
86
+                    width: 60,
87
+                    height: 60,
88
+                    decoration: BoxDecoration(
89
+                      color: Colors.primaries[index % Colors.primaries.length],
90
+                      borderRadius: BorderRadius.circular(8),
91
+                    ),
92
+                    child: Center(
93
+                      child: Text(
94
+                        '新闻',
95
+                        style: TextStyle(
96
+                          color: Colors.white,
97
+                          fontWeight: FontWeight.bold,
98
+                        ),
99
+                      ),
100
+                    ),
101
+                  ),
102
+                  const SizedBox(width: 16),
103
+                  Expanded(
104
+                    child: Column(
105
+                      crossAxisAlignment: CrossAxisAlignment.start,
106
+                      children: [
107
+                        Text(
108
+                          titles[index],
109
+                          style: const TextStyle(
110
+                            fontSize: 16,
111
+                            fontWeight: FontWeight.bold,
112
+                          ),
113
+                          maxLines: 2,
114
+                          overflow: TextOverflow.ellipsis,
115
+                        ),
116
+                        const SizedBox(height: 4),
117
+                        Text(
118
+                          subtitles[index],
119
+                          style: TextStyle(
120
+                            fontSize: 14,
121
+                            color: Colors.grey[600],
122
+                          ),
123
+                          maxLines: 2,
124
+                          overflow: TextOverflow.ellipsis,
125
+                        ),
126
+                      ],
127
+                    ),
128
+                  ),
129
+                ],
130
+              ),
131
+              const SizedBox(height: 12),
132
+              Row(
133
+                children: [
134
+                  Icon(
135
+                    Icons.schedule,
136
+                    size: 14,
137
+                    color: Colors.grey[500],
138
+                  ),
139
+                  const SizedBox(width: 4),
140
+                  Text(
141
+                    times[index],
142
+                    style: TextStyle(
143
+                      fontSize: 12,
144
+                      color: Colors.grey[500],
145
+                    ),
146
+                  ),
147
+                  const Spacer(),
148
+                  IconButton(
149
+                    icon: Icon(
150
+                      Icons.bookmark_border,
151
+                      color: Colors.grey[500],
152
+                    ),
153
+                    onPressed: () {},
154
+                    iconSize: 20,
155
+                  ),
156
+                  IconButton(
157
+                    icon: Icon(
158
+                      Icons.share,
159
+                      color: Colors.grey[500],
160
+                    ),
161
+                    onPressed: () {},
162
+                    iconSize: 20,
163
+                  ),
164
+                ],
165
+              ),
166
+            ],
167
+          ),
168
+        ),
169
+      ),
170
+    );
171
+  }
172
+}

+ 205
- 0
lib/presentation/screens/profile/profile_detail_screen.dart Просмотреть файл

@@ -0,0 +1,205 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../../../data/models/user.dart';
4
+import '../../providers/user_provider.dart';
5
+import '../../widgets/common/app_button.dart';
6
+import '../../widgets/common/app_text_field.dart';
7
+
8
+class ProfileDetailScreen extends StatefulWidget {
9
+  const ProfileDetailScreen({super.key});
10
+
11
+  @override
12
+  State<ProfileDetailScreen> createState() => _ProfileDetailScreenState();
13
+}
14
+
15
+class _ProfileDetailScreenState extends State<ProfileDetailScreen> {
16
+  late User _currentUser;
17
+  late TextEditingController _nameController;
18
+  late TextEditingController _emailController;
19
+  late TextEditingController _phoneController;
20
+  final _formKey = GlobalKey<FormState>();
21
+
22
+  @override
23
+  void initState() {
24
+    super.initState();
25
+    final userProvider = Provider.of<UserProvider>(context, listen: false);
26
+    _currentUser = userProvider.user ?? User(id: -1, email: '', username: '', createdAt: DateTime.now());
27
+    _nameController = TextEditingController(text: _currentUser.username);
28
+    _emailController = TextEditingController(text: _currentUser.email);
29
+    _phoneController = TextEditingController(text: _currentUser.phone ?? '');
30
+  }
31
+
32
+  @override
33
+  void dispose() {
34
+    _nameController.dispose();
35
+    _emailController.dispose();
36
+    _phoneController.dispose();
37
+    super.dispose();
38
+  }
39
+
40
+  @override
41
+  Widget build(BuildContext context) {
42
+    final userProvider = Provider.of<UserProvider>(context);
43
+
44
+    return Scaffold(
45
+      appBar: AppBar(
46
+        title: const Text('个人信息'),
47
+        actions: [
48
+          if (userProvider.isLoading)
49
+            const Padding(
50
+              padding: EdgeInsets.only(right: 16),
51
+              child: Center(
52
+                child: SizedBox(
53
+                  width: 24,
54
+                  height: 24,
55
+                  child: CircularProgressIndicator(strokeWidth: 2),
56
+                ),
57
+              ),
58
+            ),
59
+        ],
60
+      ),
61
+      body: SingleChildScrollView(
62
+        padding: const EdgeInsets.all(20),
63
+        child: Form(
64
+          key: _formKey,
65
+          child: Column(
66
+            children: [
67
+              // 头像编辑
68
+              GestureDetector(
69
+                onTap: () {
70
+                  // 选择头像
71
+                },
72
+                child: Stack(
73
+                  alignment: Alignment.center,
74
+                  children: [
75
+                    CircleAvatar(
76
+                      radius: 60,
77
+                      backgroundColor: Colors.blue[100],
78
+                      backgroundImage: _currentUser.avatar != null
79
+                          ? NetworkImage(_currentUser.avatar!)
80
+                          : null,
81
+                      child: _currentUser.avatar == null
82
+                          ? const Icon(
83
+                              Icons.person,
84
+                              size: 80,
85
+                              color: Colors.blue,
86
+                            )
87
+                          : null,
88
+                    ),
89
+                    Positioned(
90
+                      bottom: 0,
91
+                      right: 0,
92
+                      child: Container(
93
+                        padding: const EdgeInsets.all(8),
94
+                        decoration: BoxDecoration(
95
+                          color: Colors.blue,
96
+                          borderRadius: BorderRadius.circular(20),
97
+                          border: Border.all(
98
+                            color: Colors.white,
99
+                            width: 2,
100
+                          ),
101
+                        ),
102
+                        child: const Icon(
103
+                          Icons.camera_alt,
104
+                          size: 20,
105
+                          color: Colors.white,
106
+                        ),
107
+                      ),
108
+                    ),
109
+                  ],
110
+                ),
111
+              ),
112
+              const SizedBox(height: 20),
113
+              TextButton(
114
+                onPressed: () {},
115
+                child: const Text(
116
+                  '更换头像',
117
+                  style: TextStyle(color: Colors.blue),
118
+                ),
119
+              ),
120
+              const SizedBox(height: 40),
121
+              
122
+              // 表单字段
123
+              AppTextField(
124
+                controller: _nameController,
125
+                labelText: '姓名',
126
+                hintText: '请输入您的姓名',
127
+                validator: (value) {
128
+                  if (value == null || value.isEmpty) {
129
+                    return '请输入姓名';
130
+                  }
131
+                  return null;
132
+                },
133
+              ),
134
+              const SizedBox(height: 20),
135
+              AppTextField(
136
+                controller: _emailController,
137
+                labelText: '邮箱地址',
138
+                hintText: '请输入邮箱',
139
+                keyboardType: TextInputType.emailAddress,
140
+                enabled: false, // 邮箱通常不可修改
141
+                validator: (value) {
142
+                  if (value == null || value.isEmpty) {
143
+                    return '请输入邮箱地址';
144
+                  }
145
+                  if (!value.contains('@')) {
146
+                    return '请输入有效的邮箱地址';
147
+                  }
148
+                  return null;
149
+                },
150
+              ),
151
+              const SizedBox(height: 20),
152
+              AppTextField(
153
+                controller: _phoneController,
154
+                labelText: '手机号码',
155
+                hintText: '请输入手机号码',
156
+                keyboardType: TextInputType.phone,
157
+              ),
158
+              const SizedBox(height: 40),
159
+              
160
+              // 错误提示
161
+              if (userProvider.error != null)
162
+                Padding(
163
+                  padding: const EdgeInsets.only(bottom: 16),
164
+                  child: Text(
165
+                    userProvider.error!,
166
+                    style: const TextStyle(color: Colors.red),
167
+                  ),
168
+                ),
169
+              
170
+              // 保存按钮
171
+              AppButton(
172
+                text: '保存修改',
173
+                isLoading: userProvider.isLoading,  // 传入加载状态
174
+                enabled: !userProvider.isLoading,   // 加载时禁用按钮
175
+                onPressed: () async {
176
+                  if (_formKey.currentState!.validate()) {
177
+                    final updatedUser = _currentUser.copyWith(
178
+                      fullName: _nameController.text.trim(),
179
+                      // phone: _phoneController.text.trim().isNotEmpty
180
+                      //     ? _phoneController.text.trim()
181
+                      //     : null,
182
+                    );
183
+                    
184
+                    await userProvider.updateUserProfile(updatedUser);
185
+                    
186
+                    if (userProvider.error == null) {
187
+                      ScaffoldMessenger.of(context).showSnackBar(
188
+                        const SnackBar(
189
+                          content: Text('个人信息已更新'),
190
+                          duration: Duration(seconds: 2),
191
+                        ),
192
+                      );
193
+                      Navigator.of(context).pop();
194
+                    }
195
+                  }
196
+                },
197
+              ),
198
+              const SizedBox(height: 20),
199
+            ],
200
+          ),
201
+        ),
202
+      ),
203
+    );
204
+  }
205
+}

+ 342
- 0
lib/presentation/screens/profile/profile_screen.dart Просмотреть файл

@@ -0,0 +1,342 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../../providers/auth_provider.dart';
4
+import '../../providers/user_provider.dart';
5
+import '../../navigation/bottom_nav_bar.dart';
6
+import 'profile_detail_screen.dart';
7
+import '../../widgets/custom/protected_widget.dart';
8
+
9
+class ProfileScreen extends StatelessWidget {
10
+  const ProfileScreen({super.key});
11
+
12
+  @override
13
+  Widget build(BuildContext context) {
14
+    return ProtectedWidget(
15
+      child: _ProfileContent(),
16
+      loadingWidget: const Center(
17
+        child: CircularProgressIndicator(),
18
+      ),
19
+    );
20
+  }
21
+}
22
+
23
+class _ProfileContent extends StatefulWidget {
24
+  @override
25
+  State<_ProfileContent> createState() => _ProfileContentState();
26
+}
27
+
28
+class _ProfileContentState extends State<_ProfileContent> {
29
+  @override
30
+  void initState() {
31
+    super.initState();
32
+    WidgetsBinding.instance.addPostFrameCallback((_) {
33
+      final userProvider = Provider.of<UserProvider>(context, listen: false);
34
+      userProvider.loadUserProfile();
35
+    });
36
+  }
37
+
38
+  @override
39
+  Widget build(BuildContext context) {
40
+    final authProvider = Provider.of<AuthProvider>(context);
41
+    // final userProvider = Provider.of<UserProvider>(context);
42
+    
43
+    return Scaffold(
44
+      appBar: AppBar(
45
+        title: const Text('我的'),
46
+        actions: [
47
+          IconButton(
48
+            icon: const Icon(Icons.settings_outlined),
49
+            onPressed: () {
50
+              // 跳转到设置页面
51
+            },
52
+          ),
53
+        ],
54
+      ),
55
+      body: SingleChildScrollView(
56
+        child: Column(
57
+          children: [
58
+            // 用户信息卡片
59
+            Card(
60
+              margin: const EdgeInsets.all(16),
61
+              shape: RoundedRectangleBorder(
62
+                borderRadius: BorderRadius.circular(16),
63
+              ),
64
+              elevation: 2,
65
+              child: Padding(
66
+                padding: const EdgeInsets.all(20),
67
+                child: Column(
68
+                  children: [
69
+                    GestureDetector(
70
+                      onTap: () {
71
+                        // 点击头像
72
+                      },
73
+                      child: Stack(
74
+                        children: [
75
+                          CircleAvatar(
76
+                            radius: 50,
77
+                            backgroundColor: Colors.blue[100],
78
+                            backgroundImage: authProvider.user?.avatar != null
79
+                                ? NetworkImage(authProvider.user!.avatar!)
80
+                                : null,
81
+                            child: authProvider.user?.avatar == null
82
+                                ? const Icon(
83
+                                    Icons.person,
84
+                                    size: 60,
85
+                                    color: Colors.blue,
86
+                                  )
87
+                                : null,
88
+                          ),
89
+                          Positioned(
90
+                            bottom: 0,
91
+                            right: 0,
92
+                            child: Container(
93
+                              padding: const EdgeInsets.all(6),
94
+                              decoration: BoxDecoration(
95
+                                color: Colors.blue,
96
+                                borderRadius: BorderRadius.circular(20),
97
+                                border: Border.all(
98
+                                  color: Colors.white,
99
+                                  width: 2,
100
+                                ),
101
+                              ),
102
+                              child: const Icon(
103
+                                Icons.edit,
104
+                                size: 16,
105
+                                color: Colors.white,
106
+                              ),
107
+                            ),
108
+                          ),
109
+                        ],
110
+                      ),
111
+                    ),
112
+                    const SizedBox(height: 16),
113
+                    Text(
114
+                      authProvider.user?.fullName ?? '用户',
115
+                      style: const TextStyle(
116
+                        fontSize: 22,
117
+                        fontWeight: FontWeight.bold,
118
+                      ),
119
+                    ),
120
+                    const SizedBox(height: 8),
121
+                    Text(
122
+                      authProvider.user?.email ?? '',
123
+                      style: TextStyle(
124
+                        fontSize: 14,
125
+                        color: Colors.grey[600],
126
+                      ),
127
+                    ),
128
+                    if (authProvider.user?.phone != null)
129
+                      Padding(
130
+                        padding: const EdgeInsets.only(top: 4),
131
+                        child: Text(
132
+                          authProvider.user!.phone!,
133
+                          style: TextStyle(
134
+                            fontSize: 14,
135
+                            color: Colors.grey[600],
136
+                          ),
137
+                        ),
138
+                      ),
139
+                    const SizedBox(height: 16),
140
+                    Row(
141
+                      mainAxisAlignment: MainAxisAlignment.center,
142
+                      children: [
143
+                        _buildStatItem('关注', '128'),
144
+                        _buildVerticalDivider(),
145
+                        _buildStatItem('粉丝', '256'),
146
+                        _buildVerticalDivider(),
147
+                        _buildStatItem('积分', '1024'),
148
+                      ],
149
+                    ),
150
+                  ],
151
+                ),
152
+              ),
153
+            ),
154
+            
155
+            // 菜单项
156
+            Padding(
157
+              padding: const EdgeInsets.symmetric(horizontal: 16),
158
+              child: Column(
159
+                children: [
160
+                  _buildMenuCard(
161
+                    title: '账户设置',
162
+                    items: [
163
+                      _buildMenuItem(
164
+                        icon: Icons.person_outline,
165
+                        title: '个人信息',
166
+                        subtitle: '查看和编辑个人信息',
167
+                        onTap: () {
168
+                          Navigator.of(context).push(
169
+                            MaterialPageRoute(
170
+                              builder: (_) => const ProfileDetailScreen(),
171
+                            ),
172
+                          );
173
+                        },
174
+                      ),
175
+                      _buildMenuItem(
176
+                        icon: Icons.lock_outline,
177
+                        title: '账号安全',
178
+                        subtitle: '修改密码和安全设置',
179
+                        onTap: () {},
180
+                      ),
181
+                      _buildMenuItem(
182
+                        icon: Icons.notifications_none,
183
+                        title: '消息通知',
184
+                        subtitle: '管理通知偏好设置',
185
+                        onTap: () {},
186
+                      ),
187
+                    ],
188
+                  ),
189
+                  const SizedBox(height: 16),
190
+                  _buildMenuCard(
191
+                    title: '我的内容',
192
+                    items: [
193
+                      _buildMenuItem(
194
+                        icon: Icons.bookmark_border,
195
+                        title: '我的收藏',
196
+                        subtitle: '查看收藏的内容',
197
+                        onTap: () {},
198
+                      ),
199
+                      _buildMenuItem(
200
+                        icon: Icons.history,
201
+                        title: '浏览历史',
202
+                        subtitle: '查看最近浏览记录',
203
+                        onTap: () {},
204
+                      ),
205
+                      _buildMenuItem(
206
+                        icon: Icons.download,
207
+                        title: '我的下载',
208
+                        subtitle: '管理下载的文件',
209
+                        onTap: () {},
210
+                      ),
211
+                    ],
212
+                  ),
213
+                  const SizedBox(height: 16),
214
+                  _buildMenuCard(
215
+                    title: '其他',
216
+                    items: [
217
+                      _buildMenuItem(
218
+                        icon: Icons.help_outline,
219
+                        title: '帮助中心',
220
+                        subtitle: '常见问题和帮助文档',
221
+                        onTap: () {},
222
+                      ),
223
+                      _buildMenuItem(
224
+                        icon: Icons.info_outline,
225
+                        title: '关于我们',
226
+                        subtitle: '了解应用信息',
227
+                        onTap: () {},
228
+                      ),
229
+                      _buildMenuItem(
230
+                        icon: Icons.logout,
231
+                        title: '退出登录',
232
+                        subtitle: '安全退出当前账号',
233
+                        onTap: () async {
234
+                          await authProvider.logout();
235
+                          if (mounted) {
236
+                            Navigator.of(context).pushReplacementNamed('/');
237
+                          }
238
+                        },
239
+                      ),
240
+                    ],
241
+                  ),
242
+                  const SizedBox(height: 20),
243
+                  
244
+                  // 版本信息
245
+                  Padding(
246
+                    padding: const EdgeInsets.symmetric(vertical: 20),
247
+                    child: Text(
248
+                      '版本 v1.0.0',
249
+                      style: TextStyle(
250
+                        fontSize: 12,
251
+                        color: Colors.grey[500],
252
+                      ),
253
+                    ),
254
+                  ),
255
+                ],
256
+              ),
257
+            ),
258
+          ],
259
+        ),
260
+      ),
261
+      bottomNavigationBar: const BottomNavBar(initialIndex: 3),
262
+    );
263
+  }
264
+  
265
+  Widget _buildStatItem(String label, String value) {
266
+    return Expanded(
267
+      child: Column(
268
+        children: [
269
+          Text(
270
+            value,
271
+            style: const TextStyle(
272
+              fontSize: 18,
273
+              fontWeight: FontWeight.bold,
274
+            ),
275
+          ),
276
+          const SizedBox(height: 4),
277
+          Text(
278
+            label,
279
+            style: TextStyle(
280
+              fontSize: 12,
281
+              color: Colors.grey[600],
282
+            ),
283
+          ),
284
+        ],
285
+      ),
286
+    );
287
+  }
288
+  
289
+  Widget _buildVerticalDivider() {
290
+    return Container(
291
+      width: 1,
292
+      height: 40,
293
+      color: Colors.grey[300],
294
+      margin: const EdgeInsets.symmetric(horizontal: 16),
295
+    );
296
+  }
297
+  
298
+  Widget _buildMenuCard({
299
+    required String title,
300
+    required List<Widget> items,
301
+  }) {
302
+    return Card(
303
+      elevation: 2,
304
+      shape: RoundedRectangleBorder(
305
+        borderRadius: BorderRadius.circular(12),
306
+      ),
307
+      child: Padding(
308
+        padding: const EdgeInsets.all(16),
309
+        child: Column(
310
+          crossAxisAlignment: CrossAxisAlignment.start,
311
+          children: [
312
+            Text(
313
+              title,
314
+              style: const TextStyle(
315
+                fontSize: 16,
316
+                fontWeight: FontWeight.bold,
317
+              ),
318
+            ),
319
+            const SizedBox(height: 12),
320
+            Column(children: items),
321
+          ],
322
+        ),
323
+      ),
324
+    );
325
+  }
326
+  
327
+  Widget _buildMenuItem({
328
+    required IconData icon,
329
+    required String title,
330
+    required String subtitle,
331
+    required VoidCallback onTap,
332
+  }) {
333
+    return ListTile(
334
+      contentPadding: EdgeInsets.zero,
335
+      leading: Icon(icon),
336
+      title: Text(title),
337
+      subtitle: Text(subtitle),
338
+      trailing: const Icon(Icons.chevron_right),
339
+      onTap: onTap,
340
+    );
341
+  }
342
+}

+ 279
- 0
lib/presentation/screens/services/services_screen.dart Просмотреть файл

@@ -0,0 +1,279 @@
1
+import 'package:flutter/material.dart';
2
+import '../../navigation/bottom_nav_bar.dart';
3
+
4
+class ServicesScreen extends StatelessWidget {
5
+  const ServicesScreen({super.key});
6
+
7
+  @override
8
+  Widget build(BuildContext context) {
9
+    return Scaffold(
10
+      appBar: AppBar(
11
+        title: const Text('服务'),
12
+      ),
13
+      body: SingleChildScrollView(
14
+        padding: const EdgeInsets.all(20),
15
+        child: Column(
16
+          crossAxisAlignment: CrossAxisAlignment.start,
17
+          children: [
18
+            // 搜索框
19
+            Container(
20
+              decoration: BoxDecoration(
21
+                color: Colors.grey[100],
22
+                borderRadius: BorderRadius.circular(25),
23
+              ),
24
+              child: TextField(
25
+                decoration: InputDecoration(
26
+                  hintText: '搜索服务...',
27
+                  border: InputBorder.none,
28
+                  prefixIcon: const Icon(Icons.search),
29
+                  contentPadding: const EdgeInsets.symmetric(
30
+                    vertical: 15,
31
+                    horizontal: 20,
32
+                  ),
33
+                ),
34
+              ),
35
+            ),
36
+            const SizedBox(height: 30),
37
+            
38
+            // 服务分类
39
+            const Text(
40
+              '服务分类',
41
+              style: TextStyle(
42
+                fontSize: 18,
43
+                fontWeight: FontWeight.bold,
44
+              ),
45
+            ),
46
+            const SizedBox(height: 16),
47
+            GridView.count(
48
+              crossAxisCount: 4,
49
+              shrinkWrap: true,
50
+              physics: const NeverScrollableScrollPhysics(),
51
+              childAspectRatio: 1.0,
52
+              children: [
53
+                _buildServiceCategory(
54
+                  icon: Icons.cloud,
55
+                  title: '云服务',
56
+                  color: Colors.blue,
57
+                ),
58
+                _buildServiceCategory(
59
+                  icon: Icons.storage,
60
+                  title: '存储',
61
+                  color: Colors.green,
62
+                ),
63
+                _buildServiceCategory(
64
+                  icon: Icons.security,
65
+                  title: '安全',
66
+                  color: Colors.orange,
67
+                ),
68
+                _buildServiceCategory(
69
+                  icon: Icons.analytics,
70
+                  title: '分析',
71
+                  color: Colors.purple,
72
+                ),
73
+                _buildServiceCategory(
74
+                  icon: Icons.code,
75
+                  title: '开发',
76
+                  color: Colors.red,
77
+                ),
78
+                _buildServiceCategory(
79
+                  icon: Icons.dns,
80
+                  title: '数据库',
81
+                  color: Colors.teal,
82
+                ),
83
+                _buildServiceCategory(
84
+                  icon: Icons.api,
85
+                  title: 'API',
86
+                  color: Colors.pink,
87
+                ),
88
+                _buildServiceCategory(
89
+                  icon: Icons.more_horiz,
90
+                  title: '更多',
91
+                  color: Colors.grey,
92
+                ),
93
+              ],
94
+            ),
95
+            const SizedBox(height: 30),
96
+            
97
+            // 热门服务
98
+            const Text(
99
+              '热门服务',
100
+              style: TextStyle(
101
+                fontSize: 18,
102
+                fontWeight: FontWeight.bold,
103
+              ),
104
+            ),
105
+            const SizedBox(height: 16),
106
+            ListView.builder(
107
+              shrinkWrap: true,
108
+              physics: const NeverScrollableScrollPhysics(),
109
+              itemCount: 5,
110
+              itemBuilder: (context, index) {
111
+                return _buildHotServiceItem(index);
112
+              },
113
+            ),
114
+            const SizedBox(height: 30),
115
+            
116
+            // 推荐服务
117
+            const Text(
118
+              '推荐服务',
119
+              style: TextStyle(
120
+                fontSize: 18,
121
+                fontWeight: FontWeight.bold,
122
+              ),
123
+            ),
124
+            const SizedBox(height: 16),
125
+            Card(
126
+              elevation: 3,
127
+              shape: RoundedRectangleBorder(
128
+                borderRadius: BorderRadius.circular(12),
129
+              ),
130
+              child: Padding(
131
+                padding: const EdgeInsets.all(16),
132
+                child: Column(
133
+                  children: [
134
+                    Row(
135
+                      children: [
136
+                        Container(
137
+                          width: 50,
138
+                          height: 50,
139
+                          decoration: BoxDecoration(
140
+                            color: Colors.blue[100],
141
+                            borderRadius: BorderRadius.circular(25),
142
+                          ),
143
+                          child: const Icon(
144
+                            Icons.star,
145
+                            color: Colors.blue,
146
+                          ),
147
+                        ),
148
+                        const SizedBox(width: 16),
149
+                        Expanded(
150
+                          child: Column(
151
+                            crossAxisAlignment: CrossAxisAlignment.start,
152
+                            children: const [
153
+                              Text(
154
+                                'VIP专属服务',
155
+                                style: TextStyle(
156
+                                  fontSize: 16,
157
+                                  fontWeight: FontWeight.bold,
158
+                                ),
159
+                              ),
160
+                              SizedBox(height: 4),
161
+                              Text(
162
+                                '尊享VIP特权,获得更好的服务体验',
163
+                                style: TextStyle(
164
+                                  fontSize: 14,
165
+                                  color: Colors.grey,
166
+                                ),
167
+                              ),
168
+                            ],
169
+                          ),
170
+                        ),
171
+                      ],
172
+                    ),
173
+                    const SizedBox(height: 16),
174
+                    Row(
175
+                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
176
+                      children: [
177
+                        TextButton(
178
+                          onPressed: () {},
179
+                          child: const Text('了解更多'),
180
+                        ),
181
+                        ElevatedButton(
182
+                          onPressed: () {},
183
+                          style: ElevatedButton.styleFrom(
184
+                            shape: RoundedRectangleBorder(
185
+                              borderRadius: BorderRadius.circular(20),
186
+                            ),
187
+                          ),
188
+                          child: const Text('立即开通'),
189
+                        ),
190
+                      ],
191
+                    ),
192
+                  ],
193
+                ),
194
+              ),
195
+            ),
196
+          ],
197
+        ),
198
+      ),
199
+      bottomNavigationBar: const BottomNavBar(initialIndex: 2),
200
+    );
201
+  }
202
+  
203
+  Widget _buildServiceCategory({
204
+    required IconData icon,
205
+    required String title,
206
+    required Color color,
207
+  }) {
208
+    return Column(
209
+      children: [
210
+        Container(
211
+          width: 60,
212
+          height: 60,
213
+          decoration: BoxDecoration(
214
+            color: Color.lerp(Colors.white, Colors.transparent, 0.9)!,  // 0.3表示30%透明
215
+            borderRadius: BorderRadius.circular(30),
216
+          ),
217
+          child: Icon(
218
+            icon,
219
+            color: color,
220
+            size: 30,
221
+          ),
222
+        ),
223
+        const SizedBox(height: 8),
224
+        Text(
225
+          title,
226
+          style: const TextStyle(
227
+            fontSize: 12,
228
+            fontWeight: FontWeight.w500,
229
+          ),
230
+          textAlign: TextAlign.center,
231
+        ),
232
+      ],
233
+    );
234
+  }
235
+  
236
+  Widget _buildHotServiceItem(int index) {
237
+    final services = [
238
+      {'title': '云服务器', 'desc': '弹性计算,按需付费'},
239
+      {'title': '对象存储', 'desc': '安全可靠的文件存储'},
240
+      {'title': 'CDN加速', 'desc': '全球内容分发网络'},
241
+      {'title': '数据库服务', 'desc': '高性能数据库解决方案'},
242
+      {'title': 'API网关', 'desc': '统一API管理和调度'},
243
+    ];
244
+    
245
+    return Card(
246
+      margin: const EdgeInsets.only(bottom: 12),
247
+      shape: RoundedRectangleBorder(
248
+        borderRadius: BorderRadius.circular(10),
249
+      ),
250
+      child: ListTile(
251
+        leading: Container(
252
+          width: 40,
253
+          height: 40,
254
+          decoration: BoxDecoration(
255
+            color: Color.lerp(
256
+              Colors.primaries[index % Colors.primaries.length],
257
+              Colors.transparent,
258
+              0.9,  // 90% 透明 = 10% 不透明度
259
+            )!,
260
+            borderRadius: BorderRadius.circular(20),
261
+          ),
262
+          child: Icon(
263
+            Icons.cloud_circle,
264
+            color: Colors.primaries[index % Colors.primaries.length],
265
+          ),
266
+        ),
267
+        title: Text(
268
+          services[index]['title']!,
269
+          style: const TextStyle(
270
+            fontWeight: FontWeight.bold,
271
+          ),
272
+        ),
273
+        subtitle: Text(services[index]['desc']!),
274
+        trailing: const Icon(Icons.chevron_right),
275
+        onTap: () {},
276
+      ),
277
+    );
278
+  }
279
+}

+ 73
- 0
lib/presentation/screens/splash_screen.dart Просмотреть файл

@@ -0,0 +1,73 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../providers/auth_provider.dart';
4
+import '../../core/constants/route_constants.dart';
5
+
6
+class SplashScreen extends StatefulWidget {
7
+  const SplashScreen({super.key});
8
+
9
+  @override
10
+  SplashScreenState createState() => SplashScreenState();
11
+}
12
+
13
+class SplashScreenState extends State<SplashScreen> {
14
+  @override
15
+  void initState() {
16
+    super.initState();
17
+    WidgetsBinding.instance.addPostFrameCallback((_) {
18
+      _initializeApp();
19
+    });
20
+  }
21
+
22
+  Future<void> _initializeApp() async {
23
+    final authProvider = Provider.of<AuthProvider>(context, listen: false);
24
+    await authProvider.checkAuthStatus();
25
+    
26
+    // 模拟初始化延迟
27
+    await Future.delayed(const Duration(milliseconds: 1500));
28
+    
29
+    // 导航到首页
30
+    if (mounted) {
31
+      Navigator.of(context).pushReplacementNamed(RouteConstants.home);
32
+    }
33
+  }
34
+
35
+  @override
36
+  Widget build(BuildContext context) {
37
+    return Scaffold(
38
+      backgroundColor: Colors.blue,
39
+      body: Center(
40
+        child: Column(
41
+          mainAxisAlignment: MainAxisAlignment.center,
42
+          children: [
43
+            const Icon(
44
+              Icons.apps,
45
+              size: 100,
46
+              color: Colors.white,
47
+            ),
48
+            const SizedBox(height: 20),
49
+            const Text(
50
+              '采油会',
51
+              style: TextStyle(
52
+                fontSize: 32,
53
+                fontWeight: FontWeight.bold,
54
+                color: Colors.white,
55
+              ),
56
+            ),
57
+            const SizedBox(height: 20),
58
+            SizedBox(
59
+              width: 60,
60
+              height: 60,
61
+              child: CircularProgressIndicator(
62
+                valueColor: AlwaysStoppedAnimation<Color>(
63
+                  Color.lerp(Colors.white, Colors.transparent, 0.3)!,  // 0.3表示30%透明
64
+                ),
65
+                strokeWidth: 4,
66
+              ),
67
+            ),
68
+          ],
69
+        ),
70
+      ),
71
+    );
72
+  }
73
+}

+ 59
- 0
lib/presentation/widgets/common/app_button.dart Просмотреть файл

@@ -0,0 +1,59 @@
1
+import 'package:flutter/material.dart';
2
+
3
+class AppButton extends StatelessWidget {
4
+  final String text;
5
+  final VoidCallback onPressed;
6
+  final bool isLoading;
7
+  final bool enabled;
8
+  final Color? backgroundColor;
9
+  final Color? textColor;
10
+  final double? width;
11
+  final double? height;
12
+  
13
+  const AppButton({
14
+    super.key,
15
+    required this.text,
16
+    required this.onPressed,
17
+    this.isLoading = false,
18
+    this.enabled = true,
19
+    this.backgroundColor,
20
+    this.textColor,
21
+    this.width,
22
+    this.height = 50,
23
+  });
24
+  
25
+  @override
26
+  Widget build(BuildContext context) {
27
+    return SizedBox(
28
+      width: width ?? double.infinity,
29
+      height: height,
30
+      child: ElevatedButton(
31
+        onPressed: enabled && !isLoading ? onPressed : null,
32
+        style: ElevatedButton.styleFrom(
33
+          backgroundColor: backgroundColor ?? Theme.of(context).primaryColor,
34
+          foregroundColor: textColor ?? Colors.white,
35
+          shape: RoundedRectangleBorder(
36
+            borderRadius: BorderRadius.circular(10),
37
+          ),
38
+          elevation: 0,
39
+        ),
40
+        child: isLoading
41
+            ? SizedBox(
42
+                width: 24,
43
+                height: 24,
44
+                child: CircularProgressIndicator(
45
+                  strokeWidth: 2,
46
+                  color: textColor ?? Colors.white,
47
+                ),
48
+              )
49
+            : Text(
50
+                text,
51
+                style: const TextStyle(
52
+                  fontSize: 16,
53
+                  fontWeight: FontWeight.w600,
54
+                ),
55
+              ),
56
+      ),
57
+    );
58
+  }
59
+}

+ 90
- 0
lib/presentation/widgets/common/app_text_field.dart Просмотреть файл

@@ -0,0 +1,90 @@
1
+import 'package:flutter/material.dart';
2
+
3
+class AppTextField extends StatelessWidget {
4
+  final TextEditingController controller;
5
+  final String labelText;
6
+  final String? hintText;
7
+  final TextInputType keyboardType;
8
+  final bool obscureText;
9
+  final bool enabled;
10
+  final int? maxLines;
11
+  final int? maxLength;
12
+  final String? Function(String?)? validator;
13
+  final void Function(String)? onChanged;
14
+  final Widget? suffixIcon;
15
+  final Widget? prefixIcon;
16
+  final bool showCounter;
17
+  
18
+  const AppTextField({
19
+    super.key,
20
+    required this.controller,
21
+    required this.labelText,
22
+    this.hintText,
23
+    this.keyboardType = TextInputType.text,
24
+    this.obscureText = false,
25
+    this.enabled = true,
26
+    this.maxLines = 1,
27
+    this.maxLength,
28
+    this.validator,
29
+    this.onChanged,
30
+    this.suffixIcon,
31
+    this.prefixIcon,
32
+    this.showCounter = false,
33
+  });
34
+  
35
+  @override
36
+  Widget build(BuildContext context) {
37
+    return Column(
38
+      crossAxisAlignment: CrossAxisAlignment.start,
39
+      children: [
40
+        Text(
41
+          labelText,
42
+          style: const TextStyle(
43
+            fontSize: 14,
44
+            fontWeight: FontWeight.w500,
45
+            color: Colors.black87,
46
+          ),
47
+        ),
48
+        const SizedBox(height: 8),
49
+        TextFormField(
50
+          controller: controller,
51
+          keyboardType: keyboardType,
52
+          obscureText: obscureText,
53
+          enabled: enabled,
54
+          maxLines: maxLines,
55
+          maxLength: maxLength,
56
+          validator: validator,
57
+          onChanged: onChanged,
58
+          decoration: InputDecoration(
59
+            hintText: hintText,
60
+            prefixIcon: prefixIcon,
61
+            suffixIcon: suffixIcon,
62
+            border: OutlineInputBorder(
63
+              borderRadius: BorderRadius.circular(10),
64
+              borderSide: const BorderSide(color: Colors.grey),
65
+            ),
66
+            enabledBorder: OutlineInputBorder(
67
+              borderRadius: BorderRadius.circular(10),
68
+              borderSide: const BorderSide(color: Colors.grey),
69
+            ),
70
+            focusedBorder: OutlineInputBorder(
71
+              borderRadius: BorderRadius.circular(10),
72
+              borderSide: BorderSide(color: Theme.of(context).primaryColor),
73
+            ),
74
+            errorBorder: OutlineInputBorder(
75
+              borderRadius: BorderRadius.circular(10),
76
+              borderSide: const BorderSide(color: Colors.red),
77
+            ),
78
+            filled: true,
79
+            fillColor: Colors.grey[50],
80
+            contentPadding: const EdgeInsets.symmetric(
81
+              horizontal: 16,
82
+              vertical: 14,
83
+            ),
84
+            counterText: showCounter ? null : '',
85
+          ),
86
+        ),
87
+      ],
88
+    );
89
+  }
90
+}

+ 28
- 0
lib/presentation/widgets/common/loading_indicator.dart Просмотреть файл

@@ -0,0 +1,28 @@
1
+import 'package:flutter/material.dart';
2
+
3
+class LoadingIndicator extends StatelessWidget {
4
+  final double size;
5
+  final Color color;
6
+  final double strokeWidth;
7
+  
8
+  const LoadingIndicator({
9
+    super.key,
10
+    this.size = 40,
11
+    this.color = Colors.blue,
12
+    this.strokeWidth = 3,
13
+  });
14
+  
15
+  @override
16
+  Widget build(BuildContext context) {
17
+    return Center(
18
+      child: SizedBox(
19
+        width: size,
20
+        height: size,
21
+        child: CircularProgressIndicator(
22
+          valueColor: AlwaysStoppedAnimation<Color>(color),
23
+          strokeWidth: strokeWidth,
24
+        ),
25
+      ),
26
+    );
27
+  }
28
+}

+ 35
- 0
lib/presentation/widgets/custom/protected_widget.dart Просмотреть файл

@@ -0,0 +1,35 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../../providers/auth_provider.dart';
4
+import '../../screens/auth/login_screen.dart';
5
+import '../common/loading_indicator.dart';
6
+
7
+class ProtectedWidget extends StatelessWidget {
8
+  final Widget child;
9
+  final Widget? loadingWidget;
10
+  
11
+  const ProtectedWidget({
12
+    super.key,
13
+    required this.child,
14
+    this.loadingWidget,
15
+  });
16
+  
17
+  @override
18
+  Widget build(BuildContext context) {
19
+    final authProvider = Provider.of<AuthProvider>(context);
20
+    
21
+    if (authProvider.isLoading) {
22
+      return loadingWidget ?? const LoadingIndicator();
23
+    }
24
+    
25
+    if (!authProvider.isAuthenticated) {
26
+      return LoginScreen(
27
+        onSuccess: () {
28
+          // 登录成功后的回调
29
+        },
30
+      );
31
+    }
32
+    
33
+    return child;
34
+  }
35
+}

+ 2
- 0
macos/Flutter/GeneratedPluginRegistrant.swift Просмотреть файл

@@ -5,6 +5,8 @@
5 5
 import FlutterMacOS
6 6
 import Foundation
7 7
 
8
+import shared_preferences_foundation
8 9
 
9 10
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
11
+  SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
10 12
 }

+ 302
- 1
pubspec.lock Просмотреть файл

@@ -1,6 +1,22 @@
1 1
 # Generated by pub
2 2
 # See https://dart.dev/tools/pub/glossary#lockfile
3 3
 packages:
4
+  args:
5
+    dependency: transitive
6
+    description:
7
+      name: args
8
+      sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
9
+      url: "https://pub.flutter-io.cn"
10
+    source: hosted
11
+    version: "2.7.0"
12
+  asn1lib:
13
+    dependency: transitive
14
+    description:
15
+      name: asn1lib
16
+      sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024"
17
+      url: "https://pub.flutter-io.cn"
18
+    source: hosted
19
+    version: "1.6.5"
4 20
   async:
5 21
     dependency: transitive
6 22
     description:
@@ -41,6 +57,22 @@ packages:
41 57
       url: "https://pub.flutter-io.cn"
42 58
     source: hosted
43 59
     version: "1.19.1"
60
+  convert:
61
+    dependency: transitive
62
+    description:
63
+      name: convert
64
+      sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
65
+      url: "https://pub.flutter-io.cn"
66
+    source: hosted
67
+    version: "3.1.2"
68
+  crypto:
69
+    dependency: "direct main"
70
+    description:
71
+      name: crypto
72
+      sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
73
+      url: "https://pub.flutter-io.cn"
74
+    source: hosted
75
+    version: "3.0.7"
44 76
   cupertino_icons:
45 77
     dependency: "direct main"
46 78
     description:
@@ -49,6 +81,14 @@ packages:
49 81
       url: "https://pub.flutter-io.cn"
50 82
     source: hosted
51 83
     version: "1.0.8"
84
+  encrypt:
85
+    dependency: "direct main"
86
+    description:
87
+      name: encrypt
88
+      sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
89
+      url: "https://pub.flutter-io.cn"
90
+    source: hosted
91
+    version: "5.0.3"
52 92
   fake_async:
53 93
     dependency: transitive
54 94
     description:
@@ -57,6 +97,22 @@ packages:
57 97
       url: "https://pub.flutter-io.cn"
58 98
     source: hosted
59 99
     version: "1.3.3"
100
+  ffi:
101
+    dependency: transitive
102
+    description:
103
+      name: ffi
104
+      sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
105
+      url: "https://pub.flutter-io.cn"
106
+    source: hosted
107
+    version: "2.1.4"
108
+  file:
109
+    dependency: transitive
110
+    description:
111
+      name: file
112
+      sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
113
+      url: "https://pub.flutter-io.cn"
114
+    source: hosted
115
+    version: "7.0.1"
60 116
   flutter:
61 117
     dependency: "direct main"
62 118
     description: flutter
@@ -70,11 +126,64 @@ packages:
70 126
       url: "https://pub.flutter-io.cn"
71 127
     source: hosted
72 128
     version: "6.0.0"
129
+  flutter_svg:
130
+    dependency: "direct main"
131
+    description:
132
+      name: flutter_svg
133
+      sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95"
134
+      url: "https://pub.flutter-io.cn"
135
+    source: hosted
136
+    version: "2.2.3"
73 137
   flutter_test:
74 138
     dependency: "direct dev"
75 139
     description: flutter
76 140
     source: sdk
77 141
     version: "0.0.0"
142
+  flutter_web_plugins:
143
+    dependency: transitive
144
+    description: flutter
145
+    source: sdk
146
+    version: "0.0.0"
147
+  get_it:
148
+    dependency: "direct main"
149
+    description:
150
+      name: get_it
151
+      sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1
152
+      url: "https://pub.flutter-io.cn"
153
+    source: hosted
154
+    version: "7.7.0"
155
+  http:
156
+    dependency: "direct main"
157
+    description:
158
+      name: http
159
+      sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
160
+      url: "https://pub.flutter-io.cn"
161
+    source: hosted
162
+    version: "1.6.0"
163
+  http_parser:
164
+    dependency: transitive
165
+    description:
166
+      name: http_parser
167
+      sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
168
+      url: "https://pub.flutter-io.cn"
169
+    source: hosted
170
+    version: "4.1.2"
171
+  intl:
172
+    dependency: "direct main"
173
+    description:
174
+      name: intl
175
+      sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
176
+      url: "https://pub.flutter-io.cn"
177
+    source: hosted
178
+    version: "0.18.1"
179
+  js:
180
+    dependency: transitive
181
+    description:
182
+      name: js
183
+      sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
184
+      url: "https://pub.flutter-io.cn"
185
+    source: hosted
186
+    version: "0.7.2"
78 187
   leak_tracker:
79 188
     dependency: transitive
80 189
     description:
@@ -131,6 +240,14 @@ packages:
131 240
       url: "https://pub.flutter-io.cn"
132 241
     source: hosted
133 242
     version: "1.17.0"
243
+  nested:
244
+    dependency: transitive
245
+    description:
246
+      name: nested
247
+      sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
248
+      url: "https://pub.flutter-io.cn"
249
+    source: hosted
250
+    version: "1.0.0"
134 251
   path:
135 252
     dependency: transitive
136 253
     description:
@@ -139,6 +256,134 @@ packages:
139 256
       url: "https://pub.flutter-io.cn"
140 257
     source: hosted
141 258
     version: "1.9.1"
259
+  path_parsing:
260
+    dependency: transitive
261
+    description:
262
+      name: path_parsing
263
+      sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
264
+      url: "https://pub.flutter-io.cn"
265
+    source: hosted
266
+    version: "1.1.0"
267
+  path_provider_linux:
268
+    dependency: transitive
269
+    description:
270
+      name: path_provider_linux
271
+      sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
272
+      url: "https://pub.flutter-io.cn"
273
+    source: hosted
274
+    version: "2.2.1"
275
+  path_provider_platform_interface:
276
+    dependency: transitive
277
+    description:
278
+      name: path_provider_platform_interface
279
+      sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
280
+      url: "https://pub.flutter-io.cn"
281
+    source: hosted
282
+    version: "2.1.2"
283
+  path_provider_windows:
284
+    dependency: transitive
285
+    description:
286
+      name: path_provider_windows
287
+      sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
288
+      url: "https://pub.flutter-io.cn"
289
+    source: hosted
290
+    version: "2.3.0"
291
+  petitparser:
292
+    dependency: transitive
293
+    description:
294
+      name: petitparser
295
+      sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
296
+      url: "https://pub.flutter-io.cn"
297
+    source: hosted
298
+    version: "7.0.1"
299
+  platform:
300
+    dependency: transitive
301
+    description:
302
+      name: platform
303
+      sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
304
+      url: "https://pub.flutter-io.cn"
305
+    source: hosted
306
+    version: "3.1.6"
307
+  plugin_platform_interface:
308
+    dependency: transitive
309
+    description:
310
+      name: plugin_platform_interface
311
+      sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
312
+      url: "https://pub.flutter-io.cn"
313
+    source: hosted
314
+    version: "2.1.8"
315
+  pointycastle:
316
+    dependency: "direct main"
317
+    description:
318
+      name: pointycastle
319
+      sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
320
+      url: "https://pub.flutter-io.cn"
321
+    source: hosted
322
+    version: "3.9.1"
323
+  provider:
324
+    dependency: "direct main"
325
+    description:
326
+      name: provider
327
+      sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
328
+      url: "https://pub.flutter-io.cn"
329
+    source: hosted
330
+    version: "6.1.5+1"
331
+  shared_preferences:
332
+    dependency: "direct main"
333
+    description:
334
+      name: shared_preferences
335
+      sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
336
+      url: "https://pub.flutter-io.cn"
337
+    source: hosted
338
+    version: "2.5.4"
339
+  shared_preferences_android:
340
+    dependency: transitive
341
+    description:
342
+      name: shared_preferences_android
343
+      sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
344
+      url: "https://pub.flutter-io.cn"
345
+    source: hosted
346
+    version: "2.4.18"
347
+  shared_preferences_foundation:
348
+    dependency: transitive
349
+    description:
350
+      name: shared_preferences_foundation
351
+      sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
352
+      url: "https://pub.flutter-io.cn"
353
+    source: hosted
354
+    version: "2.5.6"
355
+  shared_preferences_linux:
356
+    dependency: transitive
357
+    description:
358
+      name: shared_preferences_linux
359
+      sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
360
+      url: "https://pub.flutter-io.cn"
361
+    source: hosted
362
+    version: "2.4.1"
363
+  shared_preferences_platform_interface:
364
+    dependency: transitive
365
+    description:
366
+      name: shared_preferences_platform_interface
367
+      sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
368
+      url: "https://pub.flutter-io.cn"
369
+    source: hosted
370
+    version: "2.4.1"
371
+  shared_preferences_web:
372
+    dependency: transitive
373
+    description:
374
+      name: shared_preferences_web
375
+      sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
376
+      url: "https://pub.flutter-io.cn"
377
+    source: hosted
378
+    version: "2.4.3"
379
+  shared_preferences_windows:
380
+    dependency: transitive
381
+    description:
382
+      name: shared_preferences_windows
383
+      sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
384
+      url: "https://pub.flutter-io.cn"
385
+    source: hosted
386
+    version: "2.4.1"
142 387
   sky_engine:
143 388
     dependency: transitive
144 389
     description: flutter
@@ -192,6 +437,38 @@ packages:
192 437
       url: "https://pub.flutter-io.cn"
193 438
     source: hosted
194 439
     version: "0.7.7"
440
+  typed_data:
441
+    dependency: transitive
442
+    description:
443
+      name: typed_data
444
+      sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
445
+      url: "https://pub.flutter-io.cn"
446
+    source: hosted
447
+    version: "1.4.0"
448
+  vector_graphics:
449
+    dependency: transitive
450
+    description:
451
+      name: vector_graphics
452
+      sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
453
+      url: "https://pub.flutter-io.cn"
454
+    source: hosted
455
+    version: "1.1.19"
456
+  vector_graphics_codec:
457
+    dependency: transitive
458
+    description:
459
+      name: vector_graphics_codec
460
+      sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
461
+      url: "https://pub.flutter-io.cn"
462
+    source: hosted
463
+    version: "1.1.13"
464
+  vector_graphics_compiler:
465
+    dependency: transitive
466
+    description:
467
+      name: vector_graphics_compiler
468
+      sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
469
+      url: "https://pub.flutter-io.cn"
470
+    source: hosted
471
+    version: "1.1.19"
195 472
   vector_math:
196 473
     dependency: transitive
197 474
     description:
@@ -208,6 +485,30 @@ packages:
208 485
       url: "https://pub.flutter-io.cn"
209 486
     source: hosted
210 487
     version: "15.0.2"
488
+  web:
489
+    dependency: transitive
490
+    description:
491
+      name: web
492
+      sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
493
+      url: "https://pub.flutter-io.cn"
494
+    source: hosted
495
+    version: "1.1.1"
496
+  xdg_directories:
497
+    dependency: transitive
498
+    description:
499
+      name: xdg_directories
500
+      sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
501
+      url: "https://pub.flutter-io.cn"
502
+    source: hosted
503
+    version: "1.1.0"
504
+  xml:
505
+    dependency: transitive
506
+    description:
507
+      name: xml
508
+      sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
509
+      url: "https://pub.flutter-io.cn"
510
+    source: hosted
511
+    version: "6.6.1"
211 512
 sdks:
212 513
   dart: ">=3.10.3 <4.0.0"
213
-  flutter: ">=3.18.0-18.0.pre.54"
514
+  flutter: ">=3.35.0"

+ 17
- 64
pubspec.yaml Просмотреть файл

@@ -1,89 +1,42 @@
1 1
 name: flutter_application_1
2 2
 description: "A new Flutter project."
3
-# The following line prevents the package from being accidentally published to
4
-# pub.dev using `flutter pub publish`. This is preferred for private packages.
5
-publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6
-
7
-# The following defines the version and build number for your application.
8
-# A version number is three numbers separated by dots, like 1.2.43
9
-# followed by an optional build number separated by a +.
10
-# Both the version and the builder number may be overridden in flutter
11
-# build by specifying --build-name and --build-number, respectively.
12
-# In Android, build-name is used as versionName while build-number used as versionCode.
13
-# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14
-# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15
-# Read more about iOS versioning at
16
-# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17
-# In Windows, build-name is used as the major, minor, and patch parts
18
-# of the product and file versions while build-number is used as the build suffix.
19 3
 version: 1.0.0+1
4
+publish_to: 'none'
20 5
 
21 6
 environment:
22 7
   sdk: ^3.10.3
23 8
 
24
-# Dependencies specify other packages that your package needs in order to work.
25
-# To automatically upgrade your package dependencies to the latest versions
26
-# consider running `flutter pub upgrade --major-versions`. Alternatively,
27
-# dependencies can be manually updated by changing the version numbers below to
28
-# the latest version available on pub.dev. To see which dependencies have newer
29
-# versions available, run `flutter pub outdated`.
30 9
 dependencies:
31 10
   flutter:
32 11
     sdk: flutter
33
-
34
-  # The following adds the Cupertino Icons font to your application.
35
-  # Use with the CupertinoIcons class for iOS style icons.
36 12
   cupertino_icons: ^1.0.8
13
+  provider: ^6.1.1
14
+  http: ^1.1.0
15
+  shared_preferences: ^2.2.2
16
+  flutter_svg: ^2.0.9
17
+  intl: ^0.18.1
18
+  get_it: ^7.6.4
19
+  # 安全相关依赖
20
+  crypto: ^3.0.3        # SHA256等哈希算法
21
+  encrypt: ^5.0.1       # AES加密
22
+  pointycastle: ^3.7.3  # 更多加密算法
37 23
 
38 24
 dev_dependencies:
39 25
   flutter_test:
40 26
     sdk: flutter
41
-
42
-  # The "flutter_lints" package below contains a set of recommended lints to
43
-  # encourage good coding practices. The lint set provided by the package is
44
-  # activated in the `analysis_options.yaml` file located at the root of your
45
-  # package. See that file for information about deactivating specific lint
46
-  # rules and activating additional ones.
47 27
   flutter_lints: ^6.0.0
48 28
 
49
-# For information on the generic Dart part of this file, see the
50
-# following page: https://dart.dev/tools/pub/pubspec
51
-
52
-# The following section is specific to Flutter packages.
53 29
 flutter:
54
-
55
-  # The following line ensures that the Material Icons font is
56
-  # included with your application, so that you can use the icons in
57
-  # the material Icons class.
58 30
   uses-material-design: true
59
-
60
-  # To add assets to your application, add an assets section, like this:
61 31
   # assets:
62 32
   #   - images/a_dot_burr.jpeg
63 33
   #   - images/a_dot_ham.jpeg
64 34
 
65
-  # An image asset can refer to one or more resolution-specific "variants", see
66
-  # https://flutter.dev/to/resolution-aware-images
67
-
68
-  # For details regarding adding assets from package dependencies, see
69
-  # https://flutter.dev/to/asset-from-package
70
-
71
-  # To add custom fonts to your application, add a fonts section here,
72
-  # in this "flutter" section. Each entry in this list should have a
73
-  # "family" key with the font family name, and a "fonts" key with a
74
-  # list giving the asset and other descriptors for the font. For
75
-  # example:
76 35
   # fonts:
77
-  #   - family: Schyler
78
-  #     fonts:
79
-  #       - asset: fonts/Schyler-Regular.ttf
80
-  #       - asset: fonts/Schyler-Italic.ttf
81
-  #         style: italic
82
-  #   - family: Trajan Pro
36
+  #   - family: Roboto
83 37
   #     fonts:
84
-  #       - asset: fonts/TrajanPro.ttf
85
-  #       - asset: fonts/TrajanPro_Bold.ttf
86
-  #         weight: 700
87
-  #
88
-  # For details regarding fonts from package dependencies,
89
-  # see https://flutter.dev/to/font-from-package
38
+  #       - asset: assets/fonts/Roboto-Regular.ttf
39
+  #       - asset: assets/fonts/Roboto-Medium.ttf
40
+  #         weight: 500
41
+  #       - asset: assets/fonts/Roboto-Bold.ttf
42
+  #         weight: 700