浏览代码

Add 添加离线模式及上传功能

Yue 1 周之前
父节点
当前提交
40c9267728

+ 134 - 0
UI/CF.APP/chicken_farm/lib/core/api/api_service.dart

@@ -1,5 +1,6 @@
 import 'package:chicken_farm/core/api/api_option.dart';
 import 'package:chicken_farm/core/services/navigation_service.dart';
+import 'package:chicken_farm/core/services/sync_service.dart';
 import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:chicken_farm/core/utils/toast.dart';
 import 'package:chicken_farm/routes/app_routes.dart';
@@ -7,6 +8,8 @@ import 'package:dio/dio.dart';
 import 'package:go_router/go_router.dart';
 import 'api_client.dart';
 import '../errors/error_handler.dart';
+import 'package:chicken_farm/core/services/offline_storage_service.dart';
+import 'package:uuid/uuid.dart';
 
 class ApiService {
   final Dio _dio = ApiClient.instance;
@@ -101,6 +104,137 @@ class ApiService {
     }
   }
 
+  // 新增支持离线操作的POST方法
+  Future<dynamic> postWithOfflineSupport(
+    String path, {
+    dynamic data,
+    Map<String, dynamic>? queryParameters,
+    ApiOption? apiOption,
+    Options? options,
+    CancelToken? cancelToken,
+  }) async {
+    // 先检查是否有待上传的数据,如果有则直接保存到离线队列
+    final pendingOps = await OfflineStorageService().getPendingOperations();
+    if (pendingOps.isNotEmpty) {
+      // 有待上传数据,直接保存到队列
+      final result = await _saveOfflineOperation(path, 'POST', data);
+      // 触发同步流程
+      _triggerSync();
+      // 返回特殊标识表示已保存到离线队列
+      return result;
+    }
+
+    // 没有待上传数据,尝试直接发送请求
+    try {
+      return await post(
+        path,
+        data: data,
+        queryParameters: queryParameters,
+        apiOption: apiOption,
+        options: options,
+        cancelToken: cancelToken,
+      );
+    } catch (e) {
+      // 如果是网络错误,保存到离线队列
+      if (_isNetworkError(e)) {
+        final result = await _saveOfflineOperation(path, 'POST', data);
+
+        // 返回特殊标识表示已保存到离线队列
+        return result;
+      }
+      rethrow;
+    }
+  }
+
+  // 新增支持离线操作的PUT方法
+  Future<dynamic> putWithOfflineSupport(
+    String path, {
+    dynamic data,
+    Map<String, dynamic>? queryParameters,
+    ApiOption? apiOption,
+    Options? options,
+    CancelToken? cancelToken,
+  }) async {
+    // 先检查是否有待上传的数据,如果有则直接保存到离线队列
+    final pendingOps = await OfflineStorageService().getPendingOperations();
+    if (pendingOps.isNotEmpty) {
+      // 有待上传数据,直接保存到队列
+      final result = await _saveOfflineOperation(path, 'PUT', data);
+      // 触发同步流程
+      _triggerSync();
+      // 返回特殊标识表示已保存到离线队列
+      return result;
+    }
+
+    // 没有待上传数据,尝试直接发送请求
+    try {
+      return await put(
+        path,
+        data: data,
+        queryParameters: queryParameters,
+        apiOption: apiOption,
+        options: options,
+        cancelToken: cancelToken,
+      );
+    } catch (e) {
+      // 如果是网络错误,保存到离线队列
+      if (_isNetworkError(e)) {
+        final result = await _saveOfflineOperation(path, 'PUT', data);
+
+        // 返回特殊标识表示已保存到离线队列
+        return result;
+      }
+      rethrow;
+    }
+  }
+
+  /// 创建并保存离线操作的通用方法
+  Future<Map<String, dynamic>> _saveOfflineOperation(
+    String endpoint,
+    String method,
+    dynamic data,
+  ) async {
+    final offlineOp = OfflineOperation(
+      id: const Uuid().v4(),
+      endpoint: endpoint,
+      method: method,
+      data: data ?? {},
+      timestamp: DateTime.now(),
+    );
+
+    final storageService = OfflineStorageService();
+    await storageService.saveOperation(offlineOp);
+
+    return {'offline_saved': true, 'operation_id': offlineOp.id};
+  }
+
+  bool _isNetworkError(dynamic error) {
+    // 根据实际的错误类型判断是否为网络错误
+    final errorString = error.toString().toLowerCase();
+    return errorString.contains('network') ||
+        errorString.contains('connection') ||
+        errorString.contains('timeout') ||
+        errorString.contains('socket') ||
+        (error is DioException &&
+            (error.type == DioExceptionType.connectionTimeout ||
+                error.type == DioExceptionType.sendTimeout ||
+                error.type == DioExceptionType.receiveTimeout ||
+                error.type == DioExceptionType.connectionError));
+  }
+
+  void _triggerSync() {
+    // 触发离线操作同步流程
+    // 使用 SyncService 来处理同步逻辑,避免重复代码
+    Future.microtask(() async {
+      try {
+        final syncService = SyncService();
+        await syncService.syncPendingOperations();
+      } catch (e) {
+        logger.e('同步离线操作时出错: $e');
+      }
+    });
+  }
+
   dynamic _handleResponse(Response response, ApiOption apiOption) {
     try {
       if (response.statusCode == 200 || response.statusCode == 201) {

+ 74 - 0
UI/CF.APP/chicken_farm/lib/core/services/connectivity_service.dart

@@ -0,0 +1,74 @@
+import 'dart:io';
+import 'package:chicken_farm/core/config/app_config.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
+import 'package:connectivity_plus/connectivity_plus.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:dio/dio.dart';
+
+class ConnectivityService {
+  final Connectivity _connectivity = Connectivity();
+
+  Stream<bool> get isConnectedStream async* {
+    // 检查初始连接状态
+    yield await _checkConnection();
+
+    // 监听网络状态变化
+    await for (var _ in _connectivity.onConnectivityChanged) {
+      yield await _checkConnection();
+    }
+  }
+
+  Future<bool> _checkConnection() async {
+    try {
+      // 解析 baseUrl 获取主机名和端口
+      final uri = Uri.parse(AppConfig.baseUrl);
+      final host = uri.host;
+      final port = uri.port;
+      final scheme = uri.scheme;
+
+      if (host.isEmpty) {
+        logger.e('无效的主机地址: ${AppConfig.baseUrl}');
+        return false;
+      }
+
+      logger.d('正在检查连接: 主机=$host, 端口=$port, 协议=$scheme');
+
+      // 对于局域网地址,我们直接尝试连接而不是仅做DNS查找
+      try {
+        final dio = Dio();
+        // 设置较短的超时时间以快速响应
+        dio.options.connectTimeout = const Duration(seconds: 10);
+        dio.options.receiveTimeout = const Duration(seconds: 10);
+
+        // 尝试访问基础URL来检查连接
+        final response = await dio.get(AppConfig.baseUrl);
+        logger.d('连接检查成功: 状态码=${response.statusCode}');
+        return response.statusCode != null && response.statusCode! < 400;
+      } catch (e) {
+        logger.d('HTTP连接检查失败: $e');
+        // 如果HTTP检查失败,退回到基本的连通性检查
+        // 检查基本网络连接状态
+        final result = await InternetAddress.lookup(host);
+        final hasConnection =
+            result.isNotEmpty && result[0].rawAddress.isNotEmpty;
+        logger.d('DNS查找结果: $hasConnection');
+        return hasConnection;
+      }
+    } on SocketException catch (e) {
+      logger.d('网络连接检查: 无网络连接 - $e');
+      return false;
+    } on FormatException catch (e) {
+      logger.d('网络连接检查: URL格式错误 - $e');
+      return false;
+    } catch (e) {
+      logger.d('网络连接检查: 其他错误 - $e');
+      return false;
+    }
+  }
+}
+
+final connectivityServiceProvider = Provider((ref) => ConnectivityService());
+
+final isConnectedProvider = StreamProvider<bool>((ref) {
+  return ref.watch(connectivityServiceProvider).isConnectedStream;
+});

+ 82 - 0
UI/CF.APP/chicken_farm/lib/core/services/offline_storage_service.dart

@@ -0,0 +1,82 @@
+import 'dart:convert';
+import 'package:shared_preferences/shared_preferences.dart';
+
+class OfflineOperation {
+  final String id;
+  final String endpoint;
+  final String method; // POST, PUT, DELETE 等
+  final Map<String, dynamic> data;
+  final DateTime timestamp;
+
+  OfflineOperation({
+    required this.id,
+    required this.endpoint,
+    required this.method,
+    required this.data,
+    required this.timestamp,
+  });
+
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'endpoint': endpoint,
+      'method': method,
+      'data': data,
+      'timestamp': timestamp.toIso8601String(),
+    };
+  }
+
+  factory OfflineOperation.fromJson(Map<String, dynamic> json) {
+    return OfflineOperation(
+      id: json['id'],
+      endpoint: json['endpoint'],
+      method: json['method'],
+      data: json['data'],
+      timestamp: DateTime.parse(json['timestamp']),
+    );
+  }
+}
+
+class OfflineStorageService {
+  static const String _offlineOperationsKey = 'offline_operations';
+
+  Future<List<OfflineOperation>> getPendingOperations() async {
+    final prefs = await SharedPreferences.getInstance();
+    final operationsJson = prefs.getStringList(_offlineOperationsKey) ?? [];
+
+    return operationsJson
+        .map((jsonStr) => OfflineOperation.fromJson(jsonDecode(jsonStr)))
+        .toList();
+  }
+
+  Future<void> saveOperation(OfflineOperation operation) async {
+    final prefs = await SharedPreferences.getInstance();
+    final operations = await getPendingOperations();
+
+    operations.add(operation);
+
+    final operationsJson = operations
+        .map((op) => jsonEncode(op.toJson()))
+        .toList();
+
+    await prefs.setStringList(_offlineOperationsKey, operationsJson);
+  }
+
+  Future<void> removeOperation(String id) async {
+    final prefs = await SharedPreferences.getInstance();
+    final operations = await getPendingOperations();
+
+    operations.removeWhere((op) => op.id == id);
+
+    final operationsJson = operations
+        .map((op) => jsonEncode(op.toJson()))
+        .toList();
+
+    await prefs.setStringList(_offlineOperationsKey, operationsJson);
+  }
+
+  Future<void> clearAllOperations() async {
+    final prefs = await SharedPreferences.getInstance();
+    await prefs.remove(_offlineOperationsKey);
+  }
+}

+ 147 - 0
UI/CF.APP/chicken_farm/lib/core/services/sync_service.dart

@@ -0,0 +1,147 @@
+import 'package:chicken_farm/core/api/api_service.dart';
+import 'package:chicken_farm/core/services/offline_storage_service.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
+import 'package:chicken_farm/core/services/connectivity_service.dart';
+import 'package:dio/dio.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class SyncService {
+  final ApiService _apiService = ApiService();
+  final OfflineStorageService _storageService = OfflineStorageService();
+  bool _isSyncing = false; // 添加同步状态标志
+
+  Future<void> syncPendingOperations() async {
+    // 如果正在同步,则直接返回,避免并发执行
+    if (_isSyncing) {
+      logger.i('同步已在进行中,跳过本次同步请求');
+      return;
+    }
+
+    // 在同步之前检查网络是否恢复
+    final container = ProviderContainer();
+    final isConnected = await container.read(isConnectedProvider.future);
+    if (!isConnected) {
+      logger.i('网络未连接,取消同步操作');
+      return;
+    }
+
+    // 设置同步状态为正在进行
+    _isSyncing = true;
+
+    try {
+      // 循环同步直到没有新的待处理操作
+      bool hasMoreOperations = true;
+      while (hasMoreOperations && _isSyncing) {
+        final pendingOperations = await _storageService.getPendingOperations();
+
+        // 如果没有待处理的操作,跳出循环
+        if (pendingOperations.isEmpty) {
+          hasMoreOperations = false;
+          continue;
+        }
+
+        logger.i('开始同步 ${pendingOperations.length} 个离线操作');
+
+        // 按时间戳排序,确保按顺序处理
+        pendingOperations.sort((a, b) => a.timestamp.compareTo(b.timestamp));
+
+        int processedCount = 0;
+        for (final operation in pendingOperations) {
+          // 检查网络连接状态,如果断开则暂停同步
+          final isStillConnected = await container.read(
+            isConnectedProvider.future,
+          );
+          if (!isStillConnected) {
+            logger.i('网络连接中断,暂停同步操作');
+            break;
+          }
+
+          try {
+            bool success = false;
+
+            switch (operation.method) {
+              case 'POST':
+                await _apiService.post(
+                  operation.endpoint,
+                  data: operation.data,
+                );
+                success = true;
+                break;
+              case 'PUT':
+                await _apiService.put(operation.endpoint, data: operation.data);
+                success = true;
+                break;
+              case 'DELETE':
+                await _apiService.delete(
+                  operation.endpoint,
+                  data: operation.data,
+                );
+                success = true;
+                break;
+            }
+
+            if (success) {
+              await _storageService.removeOperation(operation.id);
+              logger.i('操作 ${operation.id} 同步成功');
+              processedCount++;
+            }
+          } catch (e) {
+            logger.e('同步操作 ${operation.id} 失败: $e');
+            // 检查是否为鉴权失败
+            if (_isAuthError(e)) {
+              logger.i('遇到鉴权失败错误,停止同步');
+              hasMoreOperations = false;
+              break;
+            }
+            // 如果是网络错误,停止后续同步尝试
+            if (_isNetworkError(e)) {
+              logger.i('遇到网络错误,暂停同步');
+              hasMoreOperations = false;
+              break;
+            }
+            // 对于其他错误,继续尝试同步下一个操作
+          }
+        }
+
+        // 如果没有处理任何操作,跳出循环
+        if (processedCount == 0) {
+          hasMoreOperations = false;
+        }
+      }
+    } finally {
+      // 同步完成后重置状态
+      _isSyncing = false;
+      logger.i('同步完成');
+    }
+  }
+
+  bool _isAuthError(dynamic error) {
+    // 检查是否为鉴权错误 (401 或 403)
+    if (error is DioException) {
+      return error.response?.statusCode == 401 ||
+          error.response?.statusCode == 403;
+    }
+    // 根据错误消息判断是否为鉴权错误
+    final errorString = error.toString().toLowerCase();
+    return errorString.contains('unauthorized') ||
+        errorString.contains('forbidden') ||
+        errorString.contains('401') ||
+        errorString.contains('403');
+  }
+
+  bool _isNetworkError(dynamic error) {
+    // 复用 ApiService 中的网络错误判断逻辑
+    final apiService = ApiService();
+    return (apiService as dynamic)._isNetworkError(error);
+  }
+
+  Future<void> startAutoSync() async {
+    // 可以启动一个周期性同步任务
+    // 这里只是示例,实际可能需要更复杂的调度机制
+    /*
+    Timer.periodic(Duration(minutes: 5), (timer) async {
+      await syncPendingOperations();
+    });
+    */
+  }
+}

+ 26 - 1
UI/CF.APP/chicken_farm/lib/core/utils/jwt_token.dart

@@ -1,8 +1,11 @@
+import 'dart:convert';
 import 'package:chicken_farm/core/utils/storage.dart';
+import 'package:chicken_farm/modes/user/user_info_model.dart';
 
 class JwtToken {
   static const String tokenKey = "CF_TOKEN";
   static const String refreshTokenKey = "CF_REFRESH_TOKEN";
+  static const String userInfoKey = "CF_USER_INFO";
 
   static Future<String?> getToken() async {
     return await StorageUtils.get(tokenKey);
@@ -20,5 +23,27 @@ class JwtToken {
   static Future<void> clear() async {
     await StorageUtils.remove(tokenKey);
     await StorageUtils.remove(refreshTokenKey);
+    await StorageUtils.remove(userInfoKey);
   }
-}
+
+  /// 保存用户信息到本地存储
+  static Future<void> saveUserInfo(UserInfoModel userInfo) async {
+    final userInfoJson = jsonEncode(userInfo.toJson());
+    await StorageUtils.set(userInfoKey, userInfoJson);
+  }
+
+  /// 从本地存储获取用户信息
+  static Future<UserInfoModel?> getUserInfo() async {
+    final userInfoJson = await StorageUtils.get(userInfoKey);
+    if (userInfoJson != null) {
+      try {
+        final userInfoMap = jsonDecode(userInfoJson);
+        return UserInfoModel.fromJson(userInfoMap);
+      } catch (e) {
+        // 解析失败,清除损坏的数据
+        await StorageUtils.remove(userInfoKey);
+      }
+    }
+    return null;
+  }
+}

+ 21 - 0
UI/CF.APP/chicken_farm/lib/main.dart

@@ -1,6 +1,8 @@
 import 'package:chicken_farm/core/config/app_config.dart';
 import 'package:chicken_farm/core/utils/loading.dart';
 import 'package:chicken_farm/routes/route_provider.dart';
+import 'package:chicken_farm/core/services/sync_service.dart';
+import 'package:chicken_farm/core/services/connectivity_service.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 
@@ -12,6 +14,10 @@ void main() async {
   // 初始化LoadingUtil
   LoadingUtil.init(container);
   
+  // 初始化同步服务
+  final syncService = SyncService();
+  syncService.startAutoSync();
+  
   runApp(
     ProviderScope(
       child: MyApp(),
@@ -25,6 +31,21 @@ class MyApp extends ConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     final goRouter = ref.watch(goRouterProvider);
+    
+    // 监听网络状态变化,如果变为连接状态则尝试同步
+    ref.listen(isConnectedProvider, (_, snapshot) {
+      snapshot.when(
+        data: (data) {
+          if (data == true) {
+            // 网络恢复,尝试同步离线数据
+            final syncService = SyncService();
+            syncService.syncPendingOperations();
+          }
+        },
+        loading: () => null,
+        error: (error, stackTrace) => null,
+      );
+    });
 
     return MaterialApp.router(
       title: 'Chicken Farm',

+ 107 - 17
UI/CF.APP/chicken_farm/lib/pages/home/profile.dart

@@ -1,3 +1,5 @@
+import 'package:chicken_farm/core/services/offline_storage_service.dart';
+import 'package:chicken_farm/core/services/sync_service.dart';
 import 'package:chicken_farm/core/utils/storage.dart';
 import 'package:chicken_farm/core/utils/toast.dart';
 import 'package:chicken_farm/stores/auth_store.dart';
@@ -5,6 +7,8 @@ import 'package:chicken_farm/stores/config_store.dart';
 import 'package:chicken_farm/stores/dict_stroe.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:chicken_farm/core/services/connectivity_service.dart';
+import 'package:chicken_farm/pages/account/config_dialog.dart';
 
 class ProfilePage extends ConsumerWidget {
   const ProfilePage({super.key});
@@ -80,6 +84,49 @@ class ProfilePage extends ConsumerWidget {
                         ),
                       ),
                       const SizedBox(height: 20),
+                      // 显示待上传数据数量的按钮
+                      FutureBuilder<List<OfflineOperation>>(
+                        future: OfflineStorageService().getPendingOperations(),
+                        builder: (context, snapshot) {
+                          if (snapshot.connectionState ==
+                                  ConnectionState.done &&
+                              snapshot.hasData) {
+                            final pendingCount = snapshot.data!.length;
+                            return SizedBox(
+                              width: double.infinity,
+                              child: ElevatedButton.icon(
+                                onPressed: pendingCount > 0
+                                    ? () async {
+                                        ToastUtil.show(
+                                          '正在上传 $pendingCount 条数据...',
+                                        );
+                                        final syncService = SyncService();
+                                        await syncService
+                                            .syncPendingOperations();
+                                        ToastUtil.success('数据上传完成');
+                                        // 刷新按钮状态
+                                        (context as Element).markNeedsBuild();
+                                      }
+                                    : null, // 禁用按钮
+                                icon: const Icon(Icons.upload),
+                                label: Text('上传数据 ($pendingCount条待上传)'),
+                                style: ElevatedButton.styleFrom(
+                                  padding: const EdgeInsets.symmetric(
+                                    horizontal: 30,
+                                    vertical: 15,
+                                  ),
+                                  textStyle: const TextStyle(fontSize: 16),
+                                ),
+                              ),
+                            );
+                          }
+                          return const SizedBox(
+                            width: double.infinity,
+                            child: Center(child: CircularProgressIndicator()),
+                          );
+                        },
+                      ),
+                      const SizedBox(height: 20),
                       SizedBox(
                         width: double.infinity,
                         child: ElevatedButton.icon(
@@ -104,28 +151,71 @@ class ProfilePage extends ConsumerWidget {
                           ),
                         ),
                       ),
+                      const SizedBox(height: 20),
+                      SizedBox(
+                        width: double.infinity,
+                        child: ElevatedButton.icon(
+                          onPressed: () async {
+                            final result = await showDialog(
+                              context: context,
+                              builder: (context) => const ConfigDialog(),
+                            );
+                            // 如果配置发生了变化,显示提示
+                            if (result == true && context.mounted) {
+                              ToastUtil.success('配置已保存');
+                            }
+                          },
+                          icon: const Icon(Icons.settings),
+                          label: const Text('服务配置'),
+                          style: ElevatedButton.styleFrom(
+                            padding: const EdgeInsets.symmetric(
+                              horizontal: 30,
+                              vertical: 15,
+                            ),
+                            textStyle: const TextStyle(fontSize: 16),
+                          ),
+                        ),
+                      ),
                     ],
                   ),
                 ),
               ),
-              SizedBox(
-                width: double.infinity,
-                child: ElevatedButton.icon(
-                  onPressed: () {
-                    ToastUtil.confirm('确定要退出登录吗?', () => authStore.logout());
-                  },
-                  icon: const Icon(Icons.logout),
-                  label: const Text('退出登录'),
-                  style: ElevatedButton.styleFrom(
-                    // 保持背景色不变,只改变文字颜色为红色
-                    foregroundColor: Colors.red,
-                    padding: const EdgeInsets.symmetric(
-                      horizontal: 30,
-                      vertical: 15,
+              // 网络状态感知的退出登录按钮
+              Consumer(
+                builder: (context, ref, child) {
+                  final isConnectedAsync = ref.watch(isConnectedProvider);
+                  return SizedBox(
+                    width: double.infinity,
+                    child: ElevatedButton.icon(
+                      onPressed: isConnectedAsync.when(
+                        data: (isConnected) {
+                          // 在线状态下允许退出登录,离线状态下禁用
+                          return isConnected
+                              ? () {
+                                  ToastUtil.confirm(
+                                    '确定要退出登录吗?',
+                                    () => authStore.logout(),
+                                  );
+                                }
+                              : null;
+                        },
+                        loading: () => null,
+                        error: (_, _) => null,
+                      ),
+                      icon: const Icon(Icons.logout),
+                      label: const Text('退出登录'),
+                      style: ElevatedButton.styleFrom(
+                        // 保持背景色不变,只改变文字颜色为红色
+                        foregroundColor: Colors.red,
+                        padding: const EdgeInsets.symmetric(
+                          horizontal: 30,
+                          vertical: 15,
+                        ),
+                        textStyle: const TextStyle(fontSize: 16),
+                      ),
                     ),
-                    textStyle: const TextStyle(fontSize: 16),
-                  ),
-                ),
+                  );
+                },
               ),
             ] else ...[
               const Center(child: Text('加载中...')),

+ 59 - 17
UI/CF.APP/chicken_farm/lib/stores/auth_store.dart

@@ -1,5 +1,6 @@
 import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/core/config/app_config.dart';
+import 'package:chicken_farm/core/services/connectivity_service.dart';
 import 'package:chicken_farm/core/services/navigation_service.dart';
 import 'package:chicken_farm/core/utils/jwt_token.dart';
 import 'package:chicken_farm/core/utils/logger.dart';
@@ -91,29 +92,62 @@ class AuthStore extends StateNotifier<AuthInfo> {
     try {
       final token = await JwtToken.getToken();
       if (token != null) {
-        // 如果有token,则设置为已认证状态
-        // 尝试获取用户信息以验证token有效性
-        try {
-          final userInfo = await apis.loginApi.getInfo();
-          if (userInfo == null) {
-            throw Exception('用户信息获取失败');
+        // 检查网络连接状态
+        final container = ProviderContainer();
+        final isConnected = await container.read(isConnectedProvider.future);
+        
+        if (isConnected) {
+          // 有网络连接,强制获取最新用户信息
+          try {
+            final userInfo = await apis.loginApi.getInfo();
+            if (userInfo == null) {
+              throw Exception('用户信息获取失败');
+            }
+            
+            // 保存用户信息到本地存储,供离线时使用
+            await JwtToken.saveUserInfo(userInfo);
+            
+            state = AuthInfo.authenticated(
+              token: token,
+              user: userInfo.user!,
+              permissions: userInfo.permissions,
+              roles: userInfo.roles,
+            );
+            logger.i('在线模式下已登录 state: $state');
+          } catch (e) {
+            // 网络连接但是获取用户信息失败,可能是token过期
+            logger.e('在线模式下获取用户信息失败: $e');
+            await JwtToken.clear();
+            state = AuthInfo.unauthenticated();
+          }
+        } else {
+          // 无网络连接,使用离线模式
+          try {
+            // 尝试从本地存储获取上次登录的用户信息
+            final cachedUserInfo = await JwtToken.getUserInfo();
+            if (cachedUserInfo != null && cachedUserInfo.user != null) {
+              state = AuthInfo.authenticated(
+                token: token,
+                user: cachedUserInfo.user!,
+                permissions: cachedUserInfo.permissions ?? [],
+                roles: cachedUserInfo.roles ?? [],
+              );
+              logger.i('离线模式下使用缓存用户信息: $state');
+            } else {
+              // 没有缓存的用户信息,不能进行离线认证
+              logger.w('离线模式下无缓存用户信息,无法进行离线认证');
+              state = AuthInfo.unauthenticated();
+            }
+          } catch (e) {
+            logger.e('离线模式初始化失败: $e');
+            state = AuthInfo.unauthenticated();
           }
-          state = AuthInfo.authenticated(
-            token: token,
-            user: userInfo.user!,
-            permissions: userInfo.permissions,
-            roles: userInfo.roles,
-          );
-          logger.i('已登录 state: $state');
-        } catch (e) {
-          // Token无效,清除本地存储
-          await JwtToken.clear();
-          state = AuthInfo.unauthenticated();
         }
       } else {
         state = AuthInfo.unauthenticated();
       }
     } catch (e) {
+      logger.e('认证初始化失败: $e');
       state = AuthInfo.unauthenticated();
     }
   }
@@ -131,6 +165,10 @@ class AuthStore extends StateNotifier<AuthInfo> {
       if (userInfo == null) {
         throw Exception('用户信息获取失败');
       }
+
+      // 保存用户信息到本地存储,供离线时使用
+      await JwtToken.saveUserInfo(userInfo);
+
       state = AuthInfo.authenticated(
         token: token,
         user: userInfo.user!,
@@ -187,6 +225,10 @@ class AuthStore extends StateNotifier<AuthInfo> {
       if (userInfo == null) {
         throw Exception('用户信息获取失败');
       }
+
+      // 更新本地存储的用户信息
+      await JwtToken.saveUserInfo(userInfo);
+
       state = state.copyWith(
         user: userInfo.user,
         permissions: userInfo.permissions,

+ 2 - 0
UI/CF.APP/chicken_farm/pubspec.yaml

@@ -56,6 +56,8 @@ dependencies:
   image_picker: ^1.2.1
   path_provider: ^2.1.5
   flutter_image_compress: ^2.3.0
+  connectivity_plus: ^5.0.1
+  uuid: ^4.2.1
 
 dev_dependencies:
   flutter_test: