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

新增app纯界面实现框架

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

+ 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:

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


+ 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 Просмотреть файл


+ 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
+}

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


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


+ 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 email;
3
+  final String password;
4
+
5
+  LoginRequest({
6
+    required this.email,
7
+    required this.password,
8
+  });
9
+
10
+  Map<String, dynamic> toJson() {
11
+    return {
12
+      'email': email,
13
+      'password': password,
14
+    };
15
+  }
16
+}

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

@@ -0,0 +1,22 @@
1
+class RegisterRequest {
2
+  final String email;
3
+  final String password;
4
+  final String name;
5
+  final String? phone;
6
+
7
+  RegisterRequest({
8
+    required this.email,
9
+    required this.password,
10
+    required this.name,
11
+    this.phone,
12
+  });
13
+
14
+  Map<String, dynamic> toJson() {
15
+    return {
16
+      'email': email,
17
+      'password': password,
18
+      'name': name,
19
+      'phone': phone,
20
+    };
21
+  }
22
+}

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

@@ -0,0 +1,59 @@
1
+class User {
2
+  final String id;
3
+  final String email;
4
+  final String name;
5
+  final String? avatarUrl;
6
+  final String? phone;
7
+  final DateTime? createdAt;
8
+
9
+  User({
10
+    required this.id,
11
+    required this.email,
12
+    required this.name,
13
+    this.avatarUrl,
14
+    this.phone,
15
+    this.createdAt,
16
+  });
17
+
18
+  factory User.fromJson(Map<String, dynamic> json) {
19
+    return User(
20
+      id: json['id'] ?? '',
21
+      email: json['email'] ?? '',
22
+      name: json['name'] ?? '',
23
+      avatarUrl: json['avatar_url'],
24
+      phone: json['phone'],
25
+      createdAt: json['created_at'] != null
26
+          ? DateTime.parse(json['created_at'])
27
+          : null,
28
+    );
29
+  }
30
+
31
+  Map<String, dynamic> toJson() {
32
+    return {
33
+      'id': id,
34
+      'email': email,
35
+      'name': name,
36
+      'avatar_url': avatarUrl,
37
+      'phone': phone,
38
+      'created_at': createdAt?.toIso8601String(),
39
+    };
40
+  }
41
+
42
+  User copyWith({
43
+    String? id,
44
+    String? email,
45
+    String? name,
46
+    String? avatarUrl,
47
+    String? phone,
48
+    DateTime? createdAt,
49
+  }) {
50
+    return User(
51
+      id: id ?? this.id,
52
+      email: email ?? this.email,
53
+      name: name ?? this.name,
54
+      avatarUrl: avatarUrl ?? this.avatarUrl,
55
+      phone: phone ?? this.phone,
56
+      createdAt: createdAt ?? this.createdAt,
57
+    );
58
+  }
59
+}

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

@@ -0,0 +1,97 @@
1
+import '../datasources/local/shared_prefs.dart';
2
+import '../models/user.dart';
3
+import '../models/auth/login_request.dart';
4
+import '../models/auth/register_request.dart';
5
+import '../models/api_response.dart';
6
+
7
+class AuthRepository {
8
+  final SharedPrefs localDataSource;
9
+
10
+  AuthRepository({required this.localDataSource});
11
+
12
+  // 模拟登录
13
+  Future<ApiResponse<User>> login(LoginRequest request) async {
14
+    await Future.delayed(const Duration(seconds: 1));
15
+    
16
+    // 模拟成功登录
17
+    if (request.email == 'test@example.com' && request.password == '123456') {
18
+      final user = User(
19
+        id: '1',
20
+        email: request.email,
21
+        name: '测试用户',
22
+        avatarUrl: 'https://ui-avatars.com/api/?name=测试用户',
23
+        phone: '13800138000',
24
+        createdAt: DateTime.now(),
25
+      );
26
+      
27
+      // 保存token和用户数据
28
+      await localDataSource.setAuthToken('mock_jwt_token_${user.id}');
29
+      await localDataSource.setUserData(user.toJson().toString());
30
+      
31
+      return ApiResponse<User>(
32
+        success: true,
33
+        message: '登录成功',
34
+        data: user,
35
+      );
36
+    } else {
37
+      return ApiResponse<User>(
38
+        success: false,
39
+        message: '邮箱或密码错误',
40
+      );
41
+    }
42
+  }
43
+
44
+  // 模拟注册
45
+  Future<ApiResponse<User>> register(RegisterRequest request) async {
46
+    await Future.delayed(const Duration(seconds: 1));
47
+    
48
+    final user = User(
49
+      id: DateTime.now().millisecondsSinceEpoch.toString(),
50
+      email: request.email,
51
+      name: request.name,
52
+      phone: request.phone,
53
+      createdAt: DateTime.now(),
54
+    );
55
+    
56
+    // 保存token和用户数据
57
+    await localDataSource.setAuthToken('mock_jwt_token_${user.id}');
58
+    await localDataSource.setUserData(user.toJson().toString());
59
+    
60
+    return ApiResponse<User>(
61
+      success: true,
62
+      message: '注册成功',
63
+      data: user,
64
+    );
65
+  }
66
+
67
+  // 检查登录状态
68
+  Future<bool> isLoggedIn() async {
69
+    final token = localDataSource.getAuthToken();
70
+    return token != null && token.isNotEmpty;
71
+  }
72
+
73
+  // 获取当前用户
74
+  Future<User?> getCurrentUser() async {
75
+    final userData = localDataSource.getUserData();
76
+    if (userData != null) {
77
+      try {
78
+        // 这里简单处理,实际应该解析JSON
79
+        return User(
80
+          id: '1',
81
+          email: 'test@example.com',
82
+          name: '测试用户',
83
+        );
84
+      } catch (e) {
85
+        return null;
86
+      }
87
+    }
88
+    return null;
89
+  }
90
+
91
+  // 注销
92
+  Future<bool> logout() async {
93
+    await localDataSource.removeAuthToken();
94
+    await localDataSource.removeUserData();
95
+    return true;
96
+  }
97
+}

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


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

@@ -0,0 +1,50 @@
1
+import '../datasources/local/shared_prefs.dart';
2
+import '../models/user.dart';
3
+import '../models/api_response.dart';
4
+
5
+class UserRepository {
6
+  final SharedPrefs localDataSource;
7
+
8
+  UserRepository({required this.localDataSource});
9
+
10
+  // 模拟获取用户信息
11
+  Future<ApiResponse<User>> getUserProfile() async {
12
+    await Future.delayed(const Duration(seconds: 1));
13
+    
14
+    final userData = localDataSource.getUserData();
15
+    if (userData != null) {
16
+      final user = User(
17
+        id: '1',
18
+        email: 'test@example.com',
19
+        name: '测试用户',
20
+        avatarUrl: 'https://ui-avatars.com/api/?name=测试用户',
21
+        phone: '13800138000',
22
+        createdAt: DateTime.now(),
23
+      );
24
+      
25
+      return ApiResponse<User>(
26
+        success: true,
27
+        message: '获取成功',
28
+        data: user,
29
+      );
30
+    }
31
+    
32
+    return ApiResponse<User>(
33
+      success: false,
34
+      message: '未登录',
35
+    );
36
+  }
37
+
38
+  // 模拟更新用户信息
39
+  Future<ApiResponse<User>> updateUserProfile(User user) async {
40
+    await Future.delayed(const Duration(seconds: 1));
41
+    
42
+    await localDataSource.setUserData(user.toJson().toString());
43
+    
44
+    return ApiResponse<User>(
45
+      success: true,
46
+      message: '更新成功',
47
+      data: user,
48
+    );
49
+  }
50
+}

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


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


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


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

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

+ 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;
27
+      notifyListeners();
28
+    }
29
+  }
30
+  
31
+  Future<void> login(String email, String password) async {
32
+    _isLoading = true;
33
+    _error = null;
34
+    notifyListeners();
35
+    
36
+    try {
37
+      final response = await authRepository.login(
38
+        LoginRequest(email: email, 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 email, String password, String name, String? phone) async {
56
+    _isLoading = true;
57
+    _error = null;
58
+    notifyListeners();
59
+    
60
+    try {
61
+      final response = await authRepository.register(
62
+        RegisterRequest(email: email, password: password, name: name, phone: phone),
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
+}

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

@@ -0,0 +1,191 @@
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,
69
+                  labelText: '邮箱地址',
70
+                  hintText: '请输入邮箱',
71
+                  keyboardType: TextInputType.emailAddress,
72
+                  prefixIcon: const Icon(Icons.email_outlined),
73
+                  validator: (value) {
74
+                    if (value == null || value.isEmpty) {
75
+                      return '请输入邮箱地址';
76
+                    }
77
+                    if (!value.contains('@')) {
78
+                      return '请输入有效的邮箱地址';
79
+                    }
80
+                    return null;
81
+                  },
82
+                ),
83
+                const SizedBox(height: 20),
84
+                AppTextField(
85
+                  controller: _passwordController,
86
+                  labelText: '密码',
87
+                  hintText: '请输入密码',
88
+                  obscureText: true,
89
+                  prefixIcon: const Icon(Icons.lock_outline),
90
+                  validator: (value) {
91
+                    if (value == null || value.isEmpty) {
92
+                      return '请输入密码';
93
+                    }
94
+                    if (value.length < 6) {
95
+                      return '密码至少需要6位字符';
96
+                    }
97
+                    return null;
98
+                  },
99
+                ),
100
+                const SizedBox(height: 30),
101
+                if (authProvider.error != null)
102
+                  Padding(
103
+                    padding: const EdgeInsets.only(bottom: 16),
104
+                    child: Text(
105
+                      authProvider.error!,
106
+                      style: const TextStyle(
107
+                        color: Colors.red,
108
+                        fontSize: 14,
109
+                      ),
110
+                    ),
111
+                  ),
112
+                if (authProvider.isLoading)
113
+                  const LoadingIndicator()
114
+                else
115
+                  AppButton(
116
+                    text: '登录',
117
+                    onPressed: () async {
118
+                      if (_formKey.currentState!.validate()) {
119
+                        await authProvider.login(
120
+                          _emailController.text.trim(),
121
+                          _passwordController.text,
122
+                        );
123
+                        if (authProvider.isAuthenticated) {
124
+                          widget.onSuccess?.call();
125
+                          Navigator.of(context).pushReplacementNamed(RouteConstants.home);
126
+                        }
127
+                      }
128
+                    },
129
+                  ),
130
+                const SizedBox(height: 20),
131
+                Center(
132
+                  child: TextButton(
133
+                    onPressed: () {
134
+                      Navigator.of(context).pushNamed(RouteConstants.register);
135
+                    },
136
+                    child: const Text(
137
+                      '还没有账号?立即注册',
138
+                      style: TextStyle(
139
+                        fontSize: 14,
140
+                        color: Colors.blue,
141
+                      ),
142
+                    ),
143
+                  ),
144
+                ),
145
+                const SizedBox(height: 30),
146
+                const Row(
147
+                  children: [
148
+                    Expanded(child: Divider()),
149
+                    Padding(
150
+                      padding: EdgeInsets.symmetric(horizontal: 16),
151
+                      child: Text('或'),
152
+                    ),
153
+                    Expanded(child: Divider()),
154
+                  ],
155
+                ),
156
+                const SizedBox(height: 20),
157
+                Row(
158
+                  mainAxisAlignment: MainAxisAlignment.center,
159
+                  children: [
160
+                    IconButton(
161
+                      onPressed: () {},
162
+                      icon: Container(
163
+                        padding: const EdgeInsets.all(10),
164
+                        decoration: BoxDecoration(
165
+                          color: Colors.grey[100],
166
+                          borderRadius: BorderRadius.circular(50),
167
+                        ),
168
+                        child: const Icon(Icons.wechat, color: Colors.green),
169
+                      ),
170
+                    ),
171
+                    IconButton(
172
+                      onPressed: () {},
173
+                      icon: Container(
174
+                        padding: const EdgeInsets.all(10),
175
+                        decoration: BoxDecoration(
176
+                          color: Colors.grey[100],
177
+                          borderRadius: BorderRadius.circular(50),
178
+                        ),
179
+                        child: const Icon(Icons.wechat, color: Colors.blue),
180
+                      ),
181
+                    ),
182
+                  ],
183
+                ),
184
+              ],
185
+            ),
186
+          ),
187
+        ),
188
+      ),
189
+    );
190
+  }
191
+}

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

@@ -0,0 +1,220 @@
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
+                          _emailController.text.trim(),
159
+                          _passwordController.text,
160
+                          _nameController.text,
161
+                          _phoneController.text.isNotEmpty
162
+                              ? _phoneController.text
163
+                              : null,
164
+                        );
165
+                        if (authProvider.isAuthenticated) {
166
+                          Navigator.of(context).pushReplacementNamed(
167
+                            RouteConstants.home,
168
+                          );
169
+                        }
170
+                      }
171
+                    },
172
+                  ),
173
+                const SizedBox(height: 20),
174
+                Center(
175
+                  child: TextButton(
176
+                    onPressed: () {
177
+                      Navigator.of(context).pop();
178
+                    },
179
+                    child: const Text(
180
+                      '已有账号?立即登录',
181
+                      style: TextStyle(
182
+                        fontSize: 14,
183
+                        color: Colors.blue,
184
+                      ),
185
+                    ),
186
+                  ),
187
+                ),
188
+                const SizedBox(height: 40),
189
+                Padding(
190
+                  padding: const EdgeInsets.symmetric(horizontal: 16),
191
+                  child: RichText(
192
+                    textAlign: TextAlign.center,
193
+                    text: const TextSpan(
194
+                      style: TextStyle(
195
+                        fontSize: 12,
196
+                        color: Colors.grey,
197
+                      ),
198
+                      children: [
199
+                        TextSpan(text: '注册即表示您同意我们的'),
200
+                        TextSpan(
201
+                          text: '服务条款',
202
+                          style: TextStyle(color: Colors.blue),
203
+                        ),
204
+                        TextSpan(text: '和'),
205
+                        TextSpan(
206
+                          text: '隐私政策',
207
+                          style: TextStyle(color: Colors.blue),
208
+                        ),
209
+                      ],
210
+                    ),
211
+                  ),
212
+                ),
213
+              ],
214
+            ),
215
+          ),
216
+        ),
217
+      ),
218
+    );
219
+  }
220
+}

+ 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?.name}'
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: '', email: '', name: '');
27
+    _nameController = TextEditingController(text: _currentUser.name);
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.avatarUrl != null
79
+                          ? NetworkImage(_currentUser.avatarUrl!)
80
+                          : null,
81
+                      child: _currentUser.avatarUrl == 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
+                      name: _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
+}

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

@@ -0,0 +1,340 @@
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?.avatarUrl != null
79
+                                ? NetworkImage(authProvider.user!.avatarUrl!)
80
+                                : null,
81
+                            child: authProvider.user?.avatarUrl == 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?.name ?? '用户',
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
+                          Navigator.of(context).pushReplacementNamed('/');
236
+                        },
237
+                      ),
238
+                    ],
239
+                  ),
240
+                  const SizedBox(height: 20),
241
+                  
242
+                  // 版本信息
243
+                  Padding(
244
+                    padding: const EdgeInsets.symmetric(vertical: 20),
245
+                    child: Text(
246
+                      '版本 v1.0.0',
247
+                      style: TextStyle(
248
+                        fontSize: 12,
249
+                        color: Colors.grey[500],
250
+                      ),
251
+                    ),
252
+                  ),
253
+                ],
254
+              ),
255
+            ),
256
+          ],
257
+        ),
258
+      ),
259
+      bottomNavigationBar: const BottomNavBar(initialIndex: 3),
260
+    );
261
+  }
262
+  
263
+  Widget _buildStatItem(String label, String value) {
264
+    return Expanded(
265
+      child: Column(
266
+        children: [
267
+          Text(
268
+            value,
269
+            style: const TextStyle(
270
+              fontSize: 18,
271
+              fontWeight: FontWeight.bold,
272
+            ),
273
+          ),
274
+          const SizedBox(height: 4),
275
+          Text(
276
+            label,
277
+            style: TextStyle(
278
+              fontSize: 12,
279
+              color: Colors.grey[600],
280
+            ),
281
+          ),
282
+        ],
283
+      ),
284
+    );
285
+  }
286
+  
287
+  Widget _buildVerticalDivider() {
288
+    return Container(
289
+      width: 1,
290
+      height: 40,
291
+      color: Colors.grey[300],
292
+      margin: const EdgeInsets.symmetric(horizontal: 16),
293
+    );
294
+  }
295
+  
296
+  Widget _buildMenuCard({
297
+    required String title,
298
+    required List<Widget> items,
299
+  }) {
300
+    return Card(
301
+      elevation: 2,
302
+      shape: RoundedRectangleBorder(
303
+        borderRadius: BorderRadius.circular(12),
304
+      ),
305
+      child: Padding(
306
+        padding: const EdgeInsets.all(16),
307
+        child: Column(
308
+          crossAxisAlignment: CrossAxisAlignment.start,
309
+          children: [
310
+            Text(
311
+              title,
312
+              style: const TextStyle(
313
+                fontSize: 16,
314
+                fontWeight: FontWeight.bold,
315
+              ),
316
+            ),
317
+            const SizedBox(height: 12),
318
+            Column(children: items),
319
+          ],
320
+        ),
321
+      ),
322
+    );
323
+  }
324
+  
325
+  Widget _buildMenuItem({
326
+    required IconData icon,
327
+    required String title,
328
+    required String subtitle,
329
+    required VoidCallback onTap,
330
+  }) {
331
+    return ListTile(
332
+      contentPadding: EdgeInsets.zero,
333
+      leading: Icon(icon),
334
+      title: Text(title),
335
+      subtitle: Text(subtitle),
336
+      trailing: const Icon(Icons.chevron_right),
337
+      onTap: onTap,
338
+    );
339
+  }
340
+}

+ 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
+}

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

@@ -0,0 +1,71 @@
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
+    _initializeApp();
18
+  }
19
+
20
+  Future<void> _initializeApp() async {
21
+    final authProvider = Provider.of<AuthProvider>(context, listen: false);
22
+    await authProvider.checkAuthStatus();
23
+    
24
+    // 模拟初始化延迟
25
+    await Future.delayed(const Duration(milliseconds: 1500));
26
+    
27
+    // 导航到首页
28
+    if (mounted) {
29
+      Navigator.of(context).pushReplacementNamed(RouteConstants.home);
30
+    }
31
+  }
32
+
33
+  @override
34
+  Widget build(BuildContext context) {
35
+    return Scaffold(
36
+      backgroundColor: Colors.blue,
37
+      body: Center(
38
+        child: Column(
39
+          mainAxisAlignment: MainAxisAlignment.center,
40
+          children: [
41
+            const Icon(
42
+              Icons.apps,
43
+              size: 100,
44
+              color: Colors.white,
45
+            ),
46
+            const SizedBox(height: 20),
47
+            const Text(
48
+              '采油会',
49
+              style: TextStyle(
50
+                fontSize: 32,
51
+                fontWeight: FontWeight.bold,
52
+                color: Colors.white,
53
+              ),
54
+            ),
55
+            const SizedBox(height: 20),
56
+            SizedBox(
57
+              width: 60,
58
+              height: 60,
59
+              child: CircularProgressIndicator(
60
+                valueColor: AlwaysStoppedAnimation<Color>(
61
+                  Color.lerp(Colors.white, Colors.transparent, 0.3)!,  // 0.3表示30%透明
62
+                ),
63
+                strokeWidth: 4,
64
+              ),
65
+            ),
66
+          ],
67
+        ),
68
+      ),
69
+    );
70
+  }
71
+}

+ 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
 }

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

@@ -1,6 +1,14 @@
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"
4 12
   async:
5 13
     dependency: transitive
6 14
     description:
@@ -57,6 +65,22 @@ packages:
57 65
       url: "https://pub.flutter-io.cn"
58 66
     source: hosted
59 67
     version: "1.3.3"
68
+  ffi:
69
+    dependency: transitive
70
+    description:
71
+      name: ffi
72
+      sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
73
+      url: "https://pub.flutter-io.cn"
74
+    source: hosted
75
+    version: "2.1.4"
76
+  file:
77
+    dependency: transitive
78
+    description:
79
+      name: file
80
+      sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
81
+      url: "https://pub.flutter-io.cn"
82
+    source: hosted
83
+    version: "7.0.1"
60 84
   flutter:
61 85
     dependency: "direct main"
62 86
     description: flutter
@@ -70,11 +94,56 @@ packages:
70 94
       url: "https://pub.flutter-io.cn"
71 95
     source: hosted
72 96
     version: "6.0.0"
97
+  flutter_svg:
98
+    dependency: "direct main"
99
+    description:
100
+      name: flutter_svg
101
+      sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95"
102
+      url: "https://pub.flutter-io.cn"
103
+    source: hosted
104
+    version: "2.2.3"
73 105
   flutter_test:
74 106
     dependency: "direct dev"
75 107
     description: flutter
76 108
     source: sdk
77 109
     version: "0.0.0"
110
+  flutter_web_plugins:
111
+    dependency: transitive
112
+    description: flutter
113
+    source: sdk
114
+    version: "0.0.0"
115
+  get_it:
116
+    dependency: "direct main"
117
+    description:
118
+      name: get_it
119
+      sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1
120
+      url: "https://pub.flutter-io.cn"
121
+    source: hosted
122
+    version: "7.7.0"
123
+  http:
124
+    dependency: "direct main"
125
+    description:
126
+      name: http
127
+      sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
128
+      url: "https://pub.flutter-io.cn"
129
+    source: hosted
130
+    version: "1.6.0"
131
+  http_parser:
132
+    dependency: transitive
133
+    description:
134
+      name: http_parser
135
+      sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
136
+      url: "https://pub.flutter-io.cn"
137
+    source: hosted
138
+    version: "4.1.2"
139
+  intl:
140
+    dependency: "direct main"
141
+    description:
142
+      name: intl
143
+      sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
144
+      url: "https://pub.flutter-io.cn"
145
+    source: hosted
146
+    version: "0.18.1"
78 147
   leak_tracker:
79 148
     dependency: transitive
80 149
     description:
@@ -131,6 +200,14 @@ packages:
131 200
       url: "https://pub.flutter-io.cn"
132 201
     source: hosted
133 202
     version: "1.17.0"
203
+  nested:
204
+    dependency: transitive
205
+    description:
206
+      name: nested
207
+      sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
208
+      url: "https://pub.flutter-io.cn"
209
+    source: hosted
210
+    version: "1.0.0"
134 211
   path:
135 212
     dependency: transitive
136 213
     description:
@@ -139,6 +216,126 @@ packages:
139 216
       url: "https://pub.flutter-io.cn"
140 217
     source: hosted
141 218
     version: "1.9.1"
219
+  path_parsing:
220
+    dependency: transitive
221
+    description:
222
+      name: path_parsing
223
+      sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
224
+      url: "https://pub.flutter-io.cn"
225
+    source: hosted
226
+    version: "1.1.0"
227
+  path_provider_linux:
228
+    dependency: transitive
229
+    description:
230
+      name: path_provider_linux
231
+      sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
232
+      url: "https://pub.flutter-io.cn"
233
+    source: hosted
234
+    version: "2.2.1"
235
+  path_provider_platform_interface:
236
+    dependency: transitive
237
+    description:
238
+      name: path_provider_platform_interface
239
+      sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
240
+      url: "https://pub.flutter-io.cn"
241
+    source: hosted
242
+    version: "2.1.2"
243
+  path_provider_windows:
244
+    dependency: transitive
245
+    description:
246
+      name: path_provider_windows
247
+      sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
248
+      url: "https://pub.flutter-io.cn"
249
+    source: hosted
250
+    version: "2.3.0"
251
+  petitparser:
252
+    dependency: transitive
253
+    description:
254
+      name: petitparser
255
+      sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
256
+      url: "https://pub.flutter-io.cn"
257
+    source: hosted
258
+    version: "7.0.1"
259
+  platform:
260
+    dependency: transitive
261
+    description:
262
+      name: platform
263
+      sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
264
+      url: "https://pub.flutter-io.cn"
265
+    source: hosted
266
+    version: "3.1.6"
267
+  plugin_platform_interface:
268
+    dependency: transitive
269
+    description:
270
+      name: plugin_platform_interface
271
+      sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
272
+      url: "https://pub.flutter-io.cn"
273
+    source: hosted
274
+    version: "2.1.8"
275
+  provider:
276
+    dependency: "direct main"
277
+    description:
278
+      name: provider
279
+      sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
280
+      url: "https://pub.flutter-io.cn"
281
+    source: hosted
282
+    version: "6.1.5+1"
283
+  shared_preferences:
284
+    dependency: "direct main"
285
+    description:
286
+      name: shared_preferences
287
+      sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
288
+      url: "https://pub.flutter-io.cn"
289
+    source: hosted
290
+    version: "2.5.4"
291
+  shared_preferences_android:
292
+    dependency: transitive
293
+    description:
294
+      name: shared_preferences_android
295
+      sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
296
+      url: "https://pub.flutter-io.cn"
297
+    source: hosted
298
+    version: "2.4.18"
299
+  shared_preferences_foundation:
300
+    dependency: transitive
301
+    description:
302
+      name: shared_preferences_foundation
303
+      sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
304
+      url: "https://pub.flutter-io.cn"
305
+    source: hosted
306
+    version: "2.5.6"
307
+  shared_preferences_linux:
308
+    dependency: transitive
309
+    description:
310
+      name: shared_preferences_linux
311
+      sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
312
+      url: "https://pub.flutter-io.cn"
313
+    source: hosted
314
+    version: "2.4.1"
315
+  shared_preferences_platform_interface:
316
+    dependency: transitive
317
+    description:
318
+      name: shared_preferences_platform_interface
319
+      sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
320
+      url: "https://pub.flutter-io.cn"
321
+    source: hosted
322
+    version: "2.4.1"
323
+  shared_preferences_web:
324
+    dependency: transitive
325
+    description:
326
+      name: shared_preferences_web
327
+      sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
328
+      url: "https://pub.flutter-io.cn"
329
+    source: hosted
330
+    version: "2.4.3"
331
+  shared_preferences_windows:
332
+    dependency: transitive
333
+    description:
334
+      name: shared_preferences_windows
335
+      sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
336
+      url: "https://pub.flutter-io.cn"
337
+    source: hosted
338
+    version: "2.4.1"
142 339
   sky_engine:
143 340
     dependency: transitive
144 341
     description: flutter
@@ -192,6 +389,38 @@ packages:
192 389
       url: "https://pub.flutter-io.cn"
193 390
     source: hosted
194 391
     version: "0.7.7"
392
+  typed_data:
393
+    dependency: transitive
394
+    description:
395
+      name: typed_data
396
+      sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
397
+      url: "https://pub.flutter-io.cn"
398
+    source: hosted
399
+    version: "1.4.0"
400
+  vector_graphics:
401
+    dependency: transitive
402
+    description:
403
+      name: vector_graphics
404
+      sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
405
+      url: "https://pub.flutter-io.cn"
406
+    source: hosted
407
+    version: "1.1.19"
408
+  vector_graphics_codec:
409
+    dependency: transitive
410
+    description:
411
+      name: vector_graphics_codec
412
+      sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
413
+      url: "https://pub.flutter-io.cn"
414
+    source: hosted
415
+    version: "1.1.13"
416
+  vector_graphics_compiler:
417
+    dependency: transitive
418
+    description:
419
+      name: vector_graphics_compiler
420
+      sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
421
+      url: "https://pub.flutter-io.cn"
422
+    source: hosted
423
+    version: "1.1.19"
195 424
   vector_math:
196 425
     dependency: transitive
197 426
     description:
@@ -208,6 +437,30 @@ packages:
208 437
       url: "https://pub.flutter-io.cn"
209 438
     source: hosted
210 439
     version: "15.0.2"
440
+  web:
441
+    dependency: transitive
442
+    description:
443
+      name: web
444
+      sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
445
+      url: "https://pub.flutter-io.cn"
446
+    source: hosted
447
+    version: "1.1.1"
448
+  xdg_directories:
449
+    dependency: transitive
450
+    description:
451
+      name: xdg_directories
452
+      sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
453
+      url: "https://pub.flutter-io.cn"
454
+    source: hosted
455
+    version: "1.1.0"
456
+  xml:
457
+    dependency: transitive
458
+    description:
459
+      name: xml
460
+      sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
461
+      url: "https://pub.flutter-io.cn"
462
+    source: hosted
463
+    version: "6.6.1"
211 464
 sdks:
212 465
   dart: ">=3.10.3 <4.0.0"
213
-  flutter: ">=3.18.0-18.0.pre.54"
466
+  flutter: ">=3.35.0"

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

@@ -1,89 +1,38 @@
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
37 19
 
38 20
 dev_dependencies:
39 21
   flutter_test:
40 22
     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 23
   flutter_lints: ^6.0.0
48 24
 
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 25
 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 26
   uses-material-design: true
59
-
60
-  # To add assets to your application, add an assets section, like this:
61 27
   # assets:
62 28
   #   - images/a_dot_burr.jpeg
63 29
   #   - images/a_dot_ham.jpeg
64 30
 
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 31
   # 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
32
+  #   - family: Roboto
83 33
   #     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
34
+  #       - asset: assets/fonts/Roboto-Regular.ttf
35
+  #       - asset: assets/fonts/Roboto-Medium.ttf
36
+  #         weight: 500
37
+  #       - asset: assets/fonts/Roboto-Bold.ttf
38
+  #         weight: 700