2 Commits 076a3196a7 ... dcb04f0b64

Autor SHA1 Mensaje Fecha
  Yue dcb04f0b64 Fix 修复息屏后扫码出错的问题。 hace 3 días
  Yue 90a9bec73e Update 优化离线数据上传等接口页面 hace 3 días
Se han modificado 24 ficheros con 727 adiciones y 389 borrados
  1. 33 23
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanService.java
  2. 18 12
      UI/CF.APP/chicken_farm/lib/apis/breeding/_query.dart
  3. 50 0
      UI/CF.APP/chicken_farm/lib/apis/breeding/_submit.dart
  4. 4 2
      UI/CF.APP/chicken_farm/lib/apis/breeding/index.dart
  5. 1 1
      UI/CF.APP/chicken_farm/lib/components/vb_dict_select.dart
  6. 48 43
      UI/CF.APP/chicken_farm/lib/core/api/api_service.dart
  7. 1 1
      UI/CF.APP/chicken_farm/lib/core/services/offline_storage_service.dart
  8. 0 147
      UI/CF.APP/chicken_farm/lib/core/services/sync_service.dart
  9. 167 0
      UI/CF.APP/chicken_farm/lib/core/services/upload_service.dart
  10. 1 1
      UI/CF.APP/chicken_farm/lib/core/utils/jwt_token.dart
  11. 4 48
      UI/CF.APP/chicken_farm/lib/main.dart
  12. 4 3
      UI/CF.APP/chicken_farm/lib/pages/account/config_dialog.dart
  13. 42 19
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page.dart
  14. 39 18
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page.dart
  15. 39 15
      UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart
  16. 39 11
      UI/CF.APP/chicken_farm/lib/pages/breeding/individual_culling_page.dart
  17. 35 11
      UI/CF.APP/chicken_farm/lib/pages/breeding/individual_weighing_page.dart
  18. 11 7
      UI/CF.APP/chicken_farm/lib/pages/home/_profile/upload_data_button.dart
  19. 6 20
      UI/CF.APP/chicken_farm/lib/pages/home/_profile/user_info_card.dart
  20. 126 0
      UI/CF.APP/chicken_farm/lib/pages/upload/upload_page.dart
  21. 8 0
      UI/CF.APP/chicken_farm/lib/routes/app_routes.dart
  22. 4 4
      UI/CF.APP/chicken_farm/lib/stores/auth_store.dart
  23. 45 0
      UI/CF.APP/chicken_farm/lib/vb_app.dart
  24. 2 3
      UI/CF.APP/chicken_farm/test/widget_test.dart

+ 33 - 23
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanService.java

@@ -45,21 +45,18 @@ public class ScanService extends Service {
     private final BroadcastReceiver screenReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent == null || intent.getAction() == null || !isScanHeadOpened()) return;
+            if (intent == null || intent.getAction() == null) return;
             
             switch (intent.getAction()) {
                 case Intent.ACTION_SCREEN_ON:
-                    // 屏幕亮起 - 若处于扫描中,恢复解码并重置单次超时
-                    if (isScanning) {
-                        resumeScan();
-                        resetSingleTimeout();
-                    }
+                    // 屏幕亮起 - 重新打开扫描头
+                    Log.d(TAG, "屏幕亮起");
+                    openScanHead();
                     break;
                 case Intent.ACTION_SCREEN_OFF:
-                    // 屏幕熄灭 - 暂停解码,保留扫描头
-                    if (isScanning) {
-                        pauseScan();
-                    }
+                    // 屏幕熄灭 - 关闭扫描头
+                    Log.d(TAG, "屏幕熄灭");
+                    closeScanHead();
                     break;
             }
         }
@@ -156,12 +153,6 @@ public class ScanService extends Service {
      * 逻辑:初始化扫描硬件+注册广播+启动闲置超时+默认开启瞄准灯
      */
     public boolean openScanHead() {
-        if (isScanHeadOpened()) {
-            Log.w(TAG, "扫描头已打开,无需重复调用");
-            resetIdleTimeout(); // 重置闲置超时
-            return true;
-        }
-
         // 1. 懒加载初始化扫描管理器
         lazyInitScanManager();
         if (scanManager == null) {
@@ -171,15 +162,29 @@ public class ScanService extends Service {
             setDefaultParams();
             // 2. 打开扫描头硬件
             scanManager.openScanner();
-            while (!scanManager.isScannerOpen()) {
+            int retryCount = 0;
+            while (!scanManager.isScannerOpen() && retryCount < 10) {
                 Thread.sleep(50);
+                retryCount++;
+            }
+            if (!scanManager.isScannerOpen()) {
+                Log.e(TAG, "扫描头打开超时");
+                return false;
             }
             // 3. 设置默认灯光(照明+瞄准)
             setLightingMode(DEFAULT_LIGHT_MODE);
             // 4. 设置广播模式
             scanManager.setOPMode(ScanConstants.SCAN_OP_MODE_BROADCAST);
-            // 5. 注册广播接收器
-            registerScanReceivers();
+            // 5. 注册广播接收器(只在第一次打开时注册)
+            if (screenReceiver != null && scanReceiver != null) {
+                try {
+                    unregisterReceiver(screenReceiver);
+                    unregisterReceiver(scanReceiver);
+                } catch (Exception e) {
+                    // 忽略注销异常
+                }
+                registerScanReceivers();
+            }
             
             // 6. 启动扫描头闲置超时(核心调整:打开扫描头就启动)
             startIdleTimeout();
@@ -279,10 +284,7 @@ public class ScanService extends Service {
             }
         }
 
-        // 4. 注销广播接收器
-        unregisterScanReceivers();
-
-        // 5. 更新状态
+        // 4. 更新状态
         isScanning = false;
     }
 
@@ -299,6 +301,7 @@ public class ScanService extends Service {
 
         // 1. 若扫描头未打开,自动打开
         if (!isScanHeadOpened()) {
+            Log.d(TAG, "自动打开扫描头");
             boolean openSuccess = openScanHead();
             if (!openSuccess) {
                 return false;
@@ -444,6 +447,8 @@ public class ScanService extends Service {
                 Log.e(TAG, "暂停扫码失败", e);
             }
         }
+        // 确保在暂停后将扫描状态设置为false,以便在屏幕亮起后能重新开始扫描
+        isScanning = false;
     }
 
     /**
@@ -456,7 +461,12 @@ public class ScanService extends Service {
                 Log.d(TAG, "扫码已恢复(屏幕亮起)");
             } catch (Exception e) {
                 Log.e(TAG, "恢复扫码失败", e);
+                // 出错时重置扫描状态,以便下次可以重新开始扫描
+                isScanning = false;
             }
+        } else {
+            // 如果扫描头未打开,重置状态
+            isScanning = false;
         }
     }
 

+ 18 - 12
UI/CF.APP/chicken_farm/lib/apis/breeding/_batch.dart → UI/CF.APP/chicken_farm/lib/apis/breeding/_query.dart

@@ -5,12 +5,12 @@ import 'package:chicken_farm/modes/breeding/family.dart';
 import 'package:chicken_farm/modes/breeding/wing_tag_num.dart';
 import 'package:chicken_farm/modes/page/page_model.dart';
 
-class BatchApi {
-  static final BatchApi _instance = BatchApi._internal();
+class BreedQueryApi {
+  static final BreedQueryApi _instance = BreedQueryApi._internal();
 
-  factory BatchApi() => _instance;
+  factory BreedQueryApi() => _instance;
 
-  BatchApi._internal();
+  BreedQueryApi._internal();
 
   // 预生成的批次模拟数据,仅包含必填字段
   static final List<BatchModel> _mockBatchData = [
@@ -99,10 +99,13 @@ class BatchApi {
     // 使用预生成的模拟数据
     // 根据keyword筛选数据
     List<BatchModel> filteredData = _mockBatchData;
-    if (query != null && query['keyword'] != null && query['keyword'].toString().isNotEmpty) {
+    if (query != null &&
+        query['keyword'] != null &&
+        query['keyword'].toString().isNotEmpty) {
       final keyword = query['keyword'].toString().toLowerCase();
-      filteredData = _mockBatchData.where((batch) => 
-        batch.batchNum.toLowerCase().contains(keyword)).toList();
+      filteredData = _mockBatchData
+          .where((batch) => batch.batchNum.toLowerCase().contains(keyword))
+          .toList();
     }
 
     int page = query != null && query['pageNum'] != null
@@ -180,12 +183,12 @@ class BatchApi {
     final random = Random();
     final count = 5 + random.nextInt(6); // 5到10之间的随机数
     final indices = <int>{};
-    
+
     // 随机选择不重复的索引
     while (indices.length < count) {
       indices.add(random.nextInt(_mockWingTagData.length));
     }
-    
+
     // 根据索引获取数据
     return indices.map((index) => _mockWingTagData[index]).toList();
   }
@@ -205,10 +208,13 @@ class BatchApi {
     // 使用预生成的模拟数据
     // 根据keyword筛选数据
     List<FamilyModel> filteredData = _mockFamilyData;
-    if (query != null && query['keyword'] != null && query['keyword'].toString().isNotEmpty) {
+    if (query != null &&
+        query['keyword'] != null &&
+        query['keyword'].toString().isNotEmpty) {
       final keyword = query['keyword'].toString().toLowerCase();
-      filteredData = _mockFamilyData.where((family) => 
-        family.familyNum.toLowerCase().contains(keyword)).toList();
+      filteredData = _mockFamilyData
+          .where((family) => family.familyNum.toLowerCase().contains(keyword))
+          .toList();
     }
 
     int page = query != null && query['pageNum'] != null

+ 50 - 0
UI/CF.APP/chicken_farm/lib/apis/breeding/_submit.dart

@@ -0,0 +1,50 @@
+import 'package:chicken_farm/core/api/api_option.dart';
+import 'package:chicken_farm/core/api/api_service.dart';
+
+class BreedSubmitApi {
+  static final BreedSubmitApi _instance = BreedSubmitApi._internal();
+
+  factory BreedSubmitApi() => _instance;
+
+  BreedSubmitApi._internal();
+
+  Future<dynamic> create(dynamic data) async {
+    return (await ApiService().postWithOfflineSupport(
+      '/app/breeding/create/',
+      data: data,
+      apiOption: ApiOption.noAlert(),
+    )).check();
+  }
+
+  Future<dynamic> cageChange(dynamic data) async {
+    return (await ApiService().postWithOfflineSupport(
+      '/app/breeding/cageChange/',
+      data: data,
+      apiOption: ApiOption.noAlert(),
+    )).check();
+  }
+
+  Future<dynamic> weight(dynamic data) async {
+    return (await ApiService().postWithOfflineSupport(
+      '/app/breeding/weight/',
+      data: data,
+      apiOption: ApiOption.noAlert(),
+    )).check();
+  }
+
+  Future<dynamic> cull(dynamic data) async {
+    return (await ApiService().postWithOfflineSupport(
+      '/app/breeding/cull/',
+      data: data,
+      apiOption: ApiOption.noAlert(),
+    )).check();
+  }
+
+  Future<dynamic> batchCull(dynamic data) async {
+    return (await ApiService().postWithOfflineSupport(
+      '/app/breeding/batchCull/',
+      data: data,
+      apiOption: ApiOption.noAlert(),
+    )).check();
+  }
+}

+ 4 - 2
UI/CF.APP/chicken_farm/lib/apis/breeding/index.dart

@@ -1,4 +1,5 @@
-import '_batch.dart';
+import '_submit.dart';
+import '_query.dart';
 
 class BreedingApis {
   static final BreedingApis _instance = BreedingApis._internal();
@@ -7,5 +8,6 @@ class BreedingApis {
 
   BreedingApis._internal();
 
-  late final BatchApi batchApi = BatchApi();
+  late final BreedQueryApi queryApi = BreedQueryApi();
+  late final BreedSubmitApi submitApi = BreedSubmitApi();
 }

+ 1 - 1
UI/CF.APP/chicken_farm/lib/components/vb_dict_select.dart

@@ -70,4 +70,4 @@ class _VberDictSelectState extends State<VberDictSelect> {
       showClearButton: widget.showClearButton,
     );
   }
-}
+}

+ 48 - 43
UI/CF.APP/chicken_farm/lib/core/api/api_service.dart

@@ -1,6 +1,5 @@
 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';
@@ -105,7 +104,7 @@ class ApiService {
   }
 
   // 新增支持离线操作的POST方法
-  Future<dynamic> postWithOfflineSupport(
+  Future<ApiOfflineModel> postWithOfflineSupport(
     String path, {
     dynamic data,
     Map<String, dynamic>? queryParameters,
@@ -116,17 +115,12 @@ class ApiService {
     // 先检查是否有待上传的数据,如果有则直接保存到离线队列
     final pendingOps = await OfflineStorageService().getPendingOperations();
     if (pendingOps.isNotEmpty) {
-      // 有待上传数据,直接保存到队列
       final result = await _saveOfflineOperation(path, 'POST', data);
-      // 触发同步流程
-      _triggerSync();
-      // 返回特殊标识表示已保存到离线队列
       return result;
     }
 
-    // 没有待上传数据,尝试直接发送请求
     try {
-      return await post(
+      final result = await post(
         path,
         data: data,
         queryParameters: queryParameters,
@@ -134,20 +128,21 @@ class ApiService {
         options: options,
         cancelToken: cancelToken,
       );
+      return ApiOfflineModel.onLine(result);
     } catch (e) {
       // 如果是网络错误,保存到离线队列
+      logger.e('请求错误: $e');
       if (_isNetworkError(e)) {
         final result = await _saveOfflineOperation(path, 'POST', data);
-
-        // 返回特殊标识表示已保存到离线队列
         return result;
+      } else {
+        rethrow;
       }
-      rethrow;
     }
   }
 
   // 新增支持离线操作的PUT方法
-  Future<dynamic> putWithOfflineSupport(
+  Future<ApiOfflineModel> putWithOfflineSupport(
     String path, {
     dynamic data,
     Map<String, dynamic>? queryParameters,
@@ -160,15 +155,13 @@ class ApiService {
     if (pendingOps.isNotEmpty) {
       // 有待上传数据,直接保存到队列
       final result = await _saveOfflineOperation(path, 'PUT', data);
-      // 触发同步流程
-      _triggerSync();
       // 返回特殊标识表示已保存到离线队列
       return result;
     }
 
     // 没有待上传数据,尝试直接发送请求
     try {
-      return await put(
+      final result = put(
         path,
         data: data,
         queryParameters: queryParameters,
@@ -176,20 +169,22 @@ class ApiService {
         options: options,
         cancelToken: cancelToken,
       );
+      return ApiOfflineModel.onLine(result);
     } catch (e) {
       // 如果是网络错误,保存到离线队列
+      logger.e('请求错误: $e');
       if (_isNetworkError(e)) {
         final result = await _saveOfflineOperation(path, 'PUT', data);
-
         // 返回特殊标识表示已保存到离线队列
         return result;
+      } else {
+        rethrow;
       }
-      rethrow;
     }
   }
 
   /// 创建并保存离线操作的通用方法
-  Future<Map<String, dynamic>> _saveOfflineOperation(
+  Future<ApiOfflineModel> _saveOfflineOperation(
     String endpoint,
     String method,
     dynamic data,
@@ -204,14 +199,15 @@ class ApiService {
 
     final storageService = OfflineStorageService();
     await storageService.saveOperation(offlineOp);
-
-    return {'offline_saved': true, 'operation_id': offlineOp.id};
+    logger.d('离线保存: ${offlineOp.id}');
+    return ApiOfflineModel();
   }
 
   bool _isNetworkError(dynamic error) {
     // 根据实际的错误类型判断是否为网络错误
     final errorString = error.toString().toLowerCase();
-    return errorString.contains('network') ||
+    return errorString.contains('网络连接错误') ||
+        errorString.contains('network') ||
         errorString.contains('connection') ||
         errorString.contains('timeout') ||
         errorString.contains('socket') ||
@@ -222,26 +218,15 @@ class ApiService {
                 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) {
         // 假设后端返回的数据结构是 { "code": 200, "msg": "success", "data": ... }
         final data = response.data;
         final code = data is Map && data.containsKey('code') ? data['code'] : 0;
-        final msg = data is Map && data.containsKey('msg') ? data['msg'] : null;
+        String? msg = data is Map && data.containsKey('msg')
+            ? data['msg']
+            : null;
         if (code == 200) {
           final rData = (data is Map && data.containsKey('data'))
               ? data['data']
@@ -258,29 +243,49 @@ class ApiService {
             AppRouteNames.login,
           );
           return null;
-        } else if (code == 403) {
-          if (apiOption.alert) {
-            ToastUtil.errorAlert(msg ?? '没有权限!');
-          }
-          return null;
         } else {
+          final msg1 = code == 403 ? "没有权限!" : data['msg'] ?? "操作失败";
           if (apiOption.alert) {
-            ToastUtil.errorAlert(data['msg'] ?? '操作失败');
+            ToastUtil.errorAlert(msg1);
+          } else {
+            throw Exception(msg1);
           }
           return null;
         }
       } else {
+        final msg2 = response.statusMessage ?? '请求失败';
         if (apiOption.alert) {
-          ToastUtil.errorAlert(response.statusMessage ?? '请求失败');
+          ToastUtil.errorAlert(msg2);
+        } else {
+          throw Exception(msg2);
         }
         return null;
       }
     } catch (e) {
       logger.e("数据解析失败:$e");
+      final msg = "数据解析失败";
       if (apiOption.alert) {
-        ToastUtil.error('数据解析失败');
+        ToastUtil.errorAlert(msg);
+      } else {
+        throw Exception(msg);
       }
       return null;
     }
   }
 }
+
+class ApiOfflineModel {
+  bool isOffline;
+  dynamic data;
+  ApiOfflineModel({this.isOffline = true, this.data});
+  ApiOfflineModel.onLine(this.data, {this.isOffline = false});
+
+  dynamic check() {
+    if (isOffline) {
+      ToastUtil.success("离线保存成功");
+      return null;
+    } else {
+      return data;
+    }
+  }
+}

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

@@ -5,7 +5,7 @@ class OfflineOperation {
   final String id;
   final String endpoint;
   final String method; // POST, PUT, DELETE 等
-  final Map<String, dynamic> data;
+  final dynamic data;
   final DateTime timestamp;
 
   OfflineOperation({

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

@@ -1,147 +0,0 @@
-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();
-    });
-    */
-  }
-}

+ 167 - 0
UI/CF.APP/chicken_farm/lib/core/services/upload_service.dart

@@ -0,0 +1,167 @@
+import 'package:chicken_farm/core/api/api_option.dart';
+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:chicken_farm/routes/app_routes.dart';
+import 'package:dio/dio.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+
+typedef UploadProgressCallback =
+    void Function(int uploaded, int total, String status);
+typedef UploadCompleteCallback = void Function();
+typedef UploadErrorCallback = void Function(String error);
+
+class UploadService {
+  final ApiService _apiService = ApiService();
+  final OfflineStorageService _storageService = OfflineStorageService();
+  bool _isUploading = false; // 添加上传状态标志
+
+  Future<void> uploadPendingOperations({
+    UploadProgressCallback? onProgress,
+    UploadCompleteCallback? onComplete,
+    UploadErrorCallback? onError,
+    BuildContext? context, // 添加 context 参数用于页面跳转
+  }) async {
+    // 如果正在上传,则直接返回,避免并发执行
+    if (_isUploading) {
+      logger.i('上传已在进行中,跳过本次上传请求');
+      return;
+    }
+
+    // 在上传之前检查网络是否恢复
+    final container = ProviderContainer();
+    final isConnected = await container.read(isConnectedProvider.future);
+    if (!isConnected) {
+      logger.i('网络未连接,取消上传操作');
+      onError?.call('网络未连接');
+      return;
+    }
+
+    // 设置上传状态为正在进行
+    _isUploading = true;
+
+    try {
+      // 获取待处理操作
+      final pendingOperations = await _storageService.getPendingOperations();
+
+      // 如果没有待处理的操作,直接完成
+      if (pendingOperations.isEmpty) {
+        logger.i('没有待上传的数据');
+        onComplete?.call();
+        return;
+      }
+
+      logger.i('开始上传 ${pendingOperations.length} 个离线操作');
+
+      // 按时间戳排序,确保按顺序处理
+      pendingOperations.sort((a, b) => a.timestamp.compareTo(b.timestamp));
+
+      int totalOperations = pendingOperations.length;
+      int uploadedCount = 0;
+
+      onProgress?.call(uploadedCount, totalOperations, '开始上传...');
+
+      for (final operation in pendingOperations) {
+        // 检查网络连接状态,如果断开则暂停上传
+        final isStillConnected = await container.read(
+          isConnectedProvider.future,
+        );
+        if (!isStillConnected) {
+          logger.i('网络连接中断,暂停上传操作');
+          onError?.call('网络连接中断');
+          return;
+        }
+
+        try {
+          bool success = false;
+
+          switch (operation.method) {
+            case 'POST':
+              await _apiService.post(
+                operation.endpoint,
+                data: operation.data,
+                apiOption: ApiOption.noAlertNoLoading(),
+              );
+              success = true;
+              break;
+            case 'PUT':
+              await _apiService.put(
+                operation.endpoint,
+                data: operation.data,
+                apiOption: ApiOption.noAlertNoLoading(),
+              );
+              success = true;
+              break;
+            case 'DELETE':
+              await _apiService.delete(
+                operation.endpoint,
+                data: operation.data,
+                apiOption: ApiOption.noAlertNoLoading(),
+              );
+              success = true;
+              break;
+          }
+
+          if (success) {
+            await _storageService.removeOperation(operation.id);
+            uploadedCount++;
+            logger.i('操作 ${operation.id} 上传成功');
+            onProgress?.call(uploadedCount, totalOperations, '正在上传...');
+          }
+        } catch (e) {
+          logger.e('上传操作 ${operation.id} 失败: $e');
+          // 检查是否为鉴权失败
+          if (_isAuthError(e)) {
+            logger.i('遇到鉴权失败错误,停止上传');
+            onError?.call('鉴权失败,请重新登录');
+            // 鉴权失败时导航到登录页面
+            if (context != null) {
+              // 添加mounted检查以确保context仍然有效
+              if (context.mounted) {
+                context.goNamed(AppRouteNames.login);
+              }
+            }
+            return;
+          }
+          // 如果是网络错误,停止后续上传尝试
+          if (_isNetworkError(e)) {
+            logger.i('遇到网络错误,暂停上传');
+            onError?.call('网络连接失败');
+            return;
+          }
+          // 对于其他错误,继续尝试上传下一个操作
+        }
+      }
+
+      // 上传完成
+      onComplete?.call();
+    } finally {
+      // 上传完成后重置状态
+      _isUploading = 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);
+  }
+}

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

@@ -46,4 +46,4 @@ class JwtToken {
     }
     return null;
   }
-}
+}

+ 4 - 48
UI/CF.APP/chicken_farm/lib/main.dart

@@ -1,60 +1,16 @@
+import 'package:chicken_farm/vb_app.dart';
 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';
 
 void main() async {
   WidgetsFlutterBinding.ensureInitialized();
   await AppConfig.init(); // 初始化配置
-  
+
   final container = ProviderContainer();
   // 初始化LoadingUtil
   LoadingUtil.init(container);
-  
-  // 初始化同步服务
-  final syncService = SyncService();
-  syncService.startAutoSync();
-  
-  runApp(
-    ProviderScope(
-      child: MyApp(),
-    ),
-  );
-}
-
-class MyApp extends ConsumerWidget {
-  const MyApp({super.key});
 
-  @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',
-      debugShowCheckedModeBanner: false,
-      theme: ThemeData(
-        useMaterial3: true,
-        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
-      ),
-      routerConfig: goRouter,
-    );
-  }
-}
+  runApp(ProviderScope(child: VberApp()));
+}

+ 4 - 3
UI/CF.APP/chicken_farm/lib/pages/account/config_dialog.dart

@@ -1,5 +1,6 @@
 import 'package:chicken_farm/core/api/api_client.dart';
 import 'package:chicken_farm/core/config/app_config.dart';
+import 'package:chicken_farm/core/services/connectivity_service.dart';
 import 'package:flutter/material.dart';
 
 class ConfigDialog extends StatefulWidget {
@@ -89,10 +90,10 @@ class _ConfigDialogState extends State<ConfigDialog> {
                 _baseUrlController.text.trim(),
                 _clientIdController.text.trim(),
               );
-              
+
               // 重新初始化API客户端
               ApiClient.clearDio();
-              
+
               if (context.mounted) {
                 Navigator.of(context).pop(true);
               }
@@ -103,4 +104,4 @@ class _ConfigDialogState extends State<ConfigDialog> {
       ],
     );
   }
-}
+}

+ 42 - 19
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page.dart

@@ -1,8 +1,8 @@
+import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/components/vb_rfid_field.dart';
 import 'package:chicken_farm/components/vb_search_select.dart';
 import 'package:chicken_farm/components/vb_select.dart';
-import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:chicken_farm/modes/breeding/batch.dart';
 import 'package:chicken_farm/modes/breeding/family.dart';
 import 'package:chicken_farm/modes/rfid/rfid_model.dart';
@@ -133,7 +133,7 @@ class _BatchCreatePageState extends State<BatchCreatePage> {
           ),
           child: VberSearchSelect<BatchModel>(
             searchApi: ({required dynamic queryParams}) async {
-              final result = await apis.breeding.batchApi.queryPageBatchs(
+              final result = await apis.breeding.queryApi.queryPageBatchs(
                 queryParams,
               );
               return {'rows': result.rows, 'total': result.total};
@@ -181,7 +181,7 @@ class _BatchCreatePageState extends State<BatchCreatePage> {
           ),
           child: VberSearchSelect<FamilyModel>(
             searchApi: ({required dynamic queryParams}) async {
-              final result = await apis.breeding.batchApi.queryPageFamilys(
+              final result = await apis.breeding.queryApi.queryPageFamilys(
                 queryParams,
               );
               return {'rows': result.rows, 'total': result.total};
@@ -261,21 +261,44 @@ class _BatchCreatePageState extends State<BatchCreatePage> {
 
   // 提交数据
   void _handleSubmit() {
-    // 在实际应用中,这里会发送数据到服务器
-    ScaffoldMessenger.of(context).showSnackBar(
-      SnackBar(
-        content: Text('批量淘汰提交成功,共${_rfids.length}只鸡'),
-        backgroundColor: Colors.green,
-      ),
-    );
-
-    for (var rfid in _rfids) {
-      logger.d('提交电子编号: $rfid');
-    }
-
-    // 提交后重置表单
-    setState(() {
-      _rfids.clear();
-    });
+    final data = _rfids.map((rfid) {
+      return {
+        'rfid': rfid,
+        'batchNum': _selectedBatchNum,
+        'familyId': _selectedFamilyId,
+      };
+    }).toList();
+    apis.breeding.submitApi
+        .create(data)
+        .then((_) {
+          if (mounted) {
+            ScaffoldMessenger.of(context).showSnackBar(
+              const SnackBar(
+                content: Text('批量绑定个体成功'),
+                backgroundColor: Colors.green,
+              ),
+            );
+            // 提交后重置表单
+            setState(() {
+              _rfids.clear();
+            });
+          }
+        })
+        .catchError((err) {
+          ToastUtil.error('批量绑定个体失败');
+          if (mounted && err != null) {
+            String errorMessage = err.toString();
+            if (err is Exception) {
+              errorMessage = err.toString();
+            }
+            logger.e(errorMessage);
+            ScaffoldMessenger.of(context).showSnackBar(
+              SnackBar(
+                content: Text(errorMessage),
+                backgroundColor: Colors.red,
+              ),
+            );
+          }
+        });
   }
 }

+ 39 - 18
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page.dart

@@ -1,5 +1,5 @@
+import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/components/vb_rfid_field.dart';
-import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:chicken_farm/modes/rfid/rfid_model.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
@@ -217,22 +217,43 @@ class _BatchCullingPageState extends State<BatchCullingPage> {
 
   // 提交数据
   void _handleSubmit() {
-    // 在实际应用中,这里会发送数据到服务器
-    ScaffoldMessenger.of(context).showSnackBar(
-      SnackBar(
-        content: Text('批量淘汰提交成功,共${_rfids.length}只鸡'),
-        backgroundColor: Colors.green,
-      ),
-    );
-    for (var rfid in _rfids) {
-      logger.d('提交电子编号: $rfid');
-    }
-
-    // 提交后重置表单
-    setState(() {
-      _rfids.clear();
-      _disposalMethod = null;
-      _cullReason = null;
-    });
+    final data = {
+      'rfids': _rfids,
+      'disposal_method': _disposalMethod,
+      'cull_reason': _cullReason,
+    };
+    apis.breeding.submitApi
+        .weight(data)
+        .then((_) {
+          if (mounted) {
+            ScaffoldMessenger.of(context).showSnackBar(
+              const SnackBar(
+                content: Text('批量淘汰提交成功'),
+                backgroundColor: Colors.green,
+              ),
+            );
+            // 提交后重置表单
+            setState(() {
+              _rfids.clear();
+              _disposalMethod = null;
+              _cullReason = null;
+            });
+          }
+        })
+        .catchError((err) {
+          ToastUtil.error('批量淘汰提交失败');
+          if (mounted && err != null) {
+            String errorMessage = err.toString();
+            if (err is Exception) {
+              errorMessage = err.toString();
+            }
+            ScaffoldMessenger.of(context).showSnackBar(
+              SnackBar(
+                content: Text(errorMessage),
+                backgroundColor: Colors.red,
+              ),
+            );
+          }
+        });
   }
 }

+ 39 - 15
UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart

@@ -1,3 +1,4 @@
+import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
@@ -394,20 +395,43 @@ class _CageChangePageState extends State<CageChangePage> {
 
   // 提交数据
   void _handleSubmit() {
-    // 在实际应用中,这里会发送数据到服务器
-    ScaffoldMessenger.of(context).showSnackBar(
-      const SnackBar(content: Text('换笼操作提交成功'), backgroundColor: Colors.green),
-    );
-
-    for (var rfid in _rfids) {
-      logger.d('提交电子编号: $rfid');
-    }
-
-    // 提交后重置表单
-    setState(() {
-      _sourceCageController.clear();
-      _targetCageController.clear();
-      _rfids.clear();
-    });
+    final data = {
+      'source_cage': _sourceCageController.text,
+      'target_cage': _targetCageController.text,
+      'rfids': _rfids,
+    };
+    apis.breeding.submitApi
+        .cageChange(data)
+        .then((_) {
+          if (mounted) {
+            ScaffoldMessenger.of(context).showSnackBar(
+              const SnackBar(
+                content: Text('换笼提交成功'),
+                backgroundColor: Colors.green,
+              ),
+            );
+            // 提交后重置表单
+            setState(() {
+              _sourceCageController.clear();
+              _targetCageController.clear();
+              _rfids.clear();
+            });
+          }
+        })
+        .catchError((err) {
+          ToastUtil.error('换笼提交失败');
+          if (mounted && err != null) {
+            String errorMessage = err.toString();
+            if (err is Exception) {
+              errorMessage = err.toString();
+            }
+            ScaffoldMessenger.of(context).showSnackBar(
+              SnackBar(
+                content: Text(errorMessage),
+                backgroundColor: Colors.red,
+              ),
+            );
+          }
+        });
   }
 }

+ 39 - 11
UI/CF.APP/chicken_farm/lib/pages/breeding/individual_culling_page.dart

@@ -1,3 +1,4 @@
+import 'package:chicken_farm/apis/index.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/components/vb_dict_select.dart';
@@ -134,16 +135,43 @@ class _IndividualCullingPageState extends State<IndividualCullingPage> {
 
   // 提交数据
   void _handleSubmit() {
-    // 在实际应用中,这里会发送数据到服务器
-    ScaffoldMessenger.of(context).showSnackBar(
-      const SnackBar(content: Text('个体淘汰提交成功'), backgroundColor: Colors.green),
-    );
-
-    // 提交后重置表单
-    setState(() {
-      _rfid = null;
-      _cullReason = null;
-      _disposalMethod = null;
-    });
+    final data = {
+      "rfid": _rfid,
+      "cull_reason": _cullReason,
+      "disposal_method": _disposalMethod,
+    };
+    apis.breeding.submitApi
+        .cull(data)
+        .then((_) {
+          if (mounted) {
+            ScaffoldMessenger.of(context).showSnackBar(
+              const SnackBar(
+                content: Text('淘汰提交成功'),
+                backgroundColor: Colors.green,
+              ),
+            );
+            // 提交后重置表单
+            setState(() {
+              _rfid = null;
+              _cullReason = null;
+              _disposalMethod = null;
+            });
+          }
+        })
+        .catchError((err) {
+          ToastUtil.error('淘汰提交失败');
+          if (mounted && err != null) {
+            String errorMessage = err.toString();
+            if (err is Exception) {
+              errorMessage = err.toString();
+            }
+            ScaffoldMessenger.of(context).showSnackBar(
+              SnackBar(
+                content: Text(errorMessage),
+                backgroundColor: Colors.red,
+              ),
+            );
+          }
+        });
   }
 }

+ 35 - 11
UI/CF.APP/chicken_farm/lib/pages/breeding/individual_weighing_page.dart

@@ -1,3 +1,4 @@
+import 'package:chicken_farm/apis/index.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/core/utils/toast.dart';
@@ -215,16 +216,39 @@ class _IndividualWeighingPageState extends State<IndividualWeighingPage> {
 
   // 提交数据
   void _handleSubmit() {
-    // 在实际应用中,这里会发送数据到服务器
-    ScaffoldMessenger.of(context).showSnackBar(
-      const SnackBar(content: Text('个体称重提交成功'), backgroundColor: Colors.green),
-    );
-
-    // 提交后重置表单
-    setState(() {
-      _rfid = null;
-      _weight = null;
-      _weightController.clear();
-    });
+    final data = {"rfid": _rfid, "weight": _weight};
+    apis.breeding.submitApi
+        .weight(data)
+        .then((_) {
+          if (mounted) {
+            ScaffoldMessenger.of(context).showSnackBar(
+              const SnackBar(
+                content: Text('称重提交成功'),
+                backgroundColor: Colors.green,
+              ),
+            );
+            // 提交后重置表单
+            setState(() {
+              _rfid = null;
+              _weight = null;
+              _weightController.clear();
+            });
+          }
+        })
+        .catchError((err) {
+          ToastUtil.error('称重提交失败');
+          if (mounted && err != null) {
+            String errorMessage = err.toString();
+            if (err is Exception) {
+              errorMessage = err.toString();
+            }
+            ScaffoldMessenger.of(context).showSnackBar(
+              SnackBar(
+                content: Text(errorMessage),
+                backgroundColor: Colors.red,
+              ),
+            );
+          }
+        });
   }
 }

+ 11 - 7
UI/CF.APP/chicken_farm/lib/pages/home/_profile/upload_data_button.dart

@@ -1,7 +1,8 @@
 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/toast.dart';
+import 'package:chicken_farm/routes/app_routes.dart';
 import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
 
 class UploadDataButton extends StatelessWidget {
   const UploadDataButton({super.key});
@@ -18,12 +19,15 @@ class UploadDataButton extends StatelessWidget {
             child: ElevatedButton.icon(
               onPressed: pendingCount > 0
                   ? () async {
-                      ToastUtil.show(
-                        '正在上传 $pendingCount 条数据...',
-                      );
-                      final syncService = SyncService();
-                      await syncService.syncPendingOperations();
-                      ToastUtil.success('数据上传完成');
+                      // 使用GoRouter导航到上传页面
+                      final result = await context.pushNamed(AppRouteNames.upload);
+                      
+                      if (result == true) {
+                        ToastUtil.success('数据上传完成');
+                      } else if (result == false) {
+                        ToastUtil.show('已取消上传');
+                      }
+                      
                       // 刷新按钮状态
                       (context as Element).markNeedsBuild();
                     }

+ 6 - 20
UI/CF.APP/chicken_farm/lib/pages/home/_profile/user_info_card.dart

@@ -26,28 +26,14 @@ class UserInfoCard extends StatelessWidget {
                   fontWeight: FontWeight.bold,
                 ),
               ),
-              subtitle: Text(
-                '用户名: ${user.userName}',
-              ),
+              subtitle: Text('用户名: ${user.userName}'),
             ),
             const Divider(),
-            const Text(
-              '基本信息',
-              style: TextStyle(fontWeight: FontWeight.bold),
-            ),
+            const Text('基本信息', style: TextStyle(fontWeight: FontWeight.bold)),
             const SizedBox(height: 10),
-            _buildInfoRow(
-              '手机号码',
-              user.phonenumber ?? '未填写',
-            ),
-            _buildInfoRow(
-              '邮箱地址',
-              user.email ?? '未填写',
-            ),
-            _buildInfoRow(
-              '部门',
-              user.orgName ?? '未分配',
-            ),
+            _buildInfoRow('手机号码', user.phonenumber ?? '未填写'),
+            _buildInfoRow('邮箱地址', user.email ?? '未填写'),
+            _buildInfoRow('部门', user.orgName ?? '未分配'),
             _buildInfoRow(
               '角色',
               (user.roles?.map((r) => r.roleName).join(', ')) ?? '未分配',
@@ -75,4 +61,4 @@ class UserInfoCard extends StatelessWidget {
       ),
     );
   }
-}
+}

+ 126 - 0
UI/CF.APP/chicken_farm/lib/pages/upload/upload_page.dart

@@ -0,0 +1,126 @@
+import 'package:chicken_farm/core/services/upload_service.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class UploadPage extends StatefulWidget {
+  const UploadPage({super.key});
+
+  @override
+  State<UploadPage> createState() => _UploadPageState();
+}
+
+class _UploadPageState extends State<UploadPage> {
+  late UploadService _uploadService;
+  int _totalOperations = 0;
+  int _uploadedOperations = 0;
+  String _status = '准备上传...';
+  bool _isUploading = false;
+  bool _uploadCompleted = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _uploadService = UploadService();
+    _startUpload();
+  }
+
+  Future<void> _startUpload() async {
+    setState(() {
+      _isUploading = true;
+      _status = '开始上传...';
+    });
+
+    try {
+      await _uploadService.uploadPendingOperations(
+        context: context, // 传递 context 给 upload service
+        onProgress: (uploaded, total, status) {
+          setState(() {
+            _uploadedOperations = uploaded;
+            _totalOperations = total;
+            _status = status;
+          });
+        },
+        onComplete: () {
+          setState(() {
+            _isUploading = false;
+            _uploadCompleted = true;
+            _status = '上传完成';
+          });
+        },
+        onError: (message) {
+          setState(() {
+            _isUploading = false;
+            _status = '上传出错: $message';
+          });
+        },
+      );
+    } catch (e) {
+      setState(() {
+        _isUploading = false;
+        _status = '上传异常: ${e.toString()}';
+      });
+    }
+  }
+
+  // 后台上传
+  void _backgroundUpload() {
+    // 返回上一页并传递false表示后台上传
+    context.pop(false);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return PopScope(
+      canPop: !_isUploading, // 只有在不上传时才能返回
+      child: Scaffold(
+        appBar: AppBar(
+          title: const Text('数据上传'),
+          automaticallyImplyLeading: false, // 禁用返回按钮
+        ),
+        body: Padding(
+          padding: const EdgeInsets.all(16.0),
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Text(
+                _status,
+                style: Theme.of(context).textTheme.headlineSmall,
+                textAlign: TextAlign.center,
+              ),
+              const SizedBox(height: 30),
+              if (_totalOperations > 0) ...[
+                LinearProgressIndicator(
+                  value: _uploadedOperations / _totalOperations,
+                  minHeight: 10,
+                ),
+                const SizedBox(height: 10),
+                Text('$_uploadedOperations / $_totalOperations'),
+                const SizedBox(height: 30),
+              ],
+              if (_isUploading) ...[
+                ElevatedButton(
+                  onPressed: _backgroundUpload,
+                  child: const Text('后台上传'),
+                ),
+              ] else if (_uploadCompleted) ...[
+                ElevatedButton(
+                  onPressed: () {
+                    context.pop(true); // 返回上一页并传递结果
+                  },
+                  child: const Text('返回'),
+                ),
+              ] else ...[
+                ElevatedButton(
+                  onPressed: () {
+                    context.pop(false); // 返回上一页并传递结果
+                  },
+                  child: const Text('退出'),
+                ),
+              ],
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 8 - 0
UI/CF.APP/chicken_farm/lib/routes/app_routes.dart

@@ -14,6 +14,7 @@ import '../pages/breeding/cage_change_page.dart';
 import '../pages/breeding/individual_weighing_page.dart';
 import '../pages/breeding/individual_culling_page.dart';
 import '../pages/breeding/batch_culling_page.dart';
+import '../pages/upload/upload_page.dart';
 
 class AppRouteNames {
   static const String splash = '/';
@@ -29,6 +30,7 @@ class AppRouteNames {
   static const String sample = '/sample';
   static const String sampleDetail = '/sample/detail';
   static const String rfidConfig = '/rfid-config';
+  static const String upload = '/upload'; // 新增上传路由名称
 }
 
 class AppRoutes {
@@ -103,6 +105,12 @@ class AppRoutes {
       name: AppRouteNames.rfidConfig,
       builder: (context, state) => const RfidConfigPage(),
     ),
+    // 新增上传路由
+    GoRoute(
+      path: AppRouteNames.upload,
+      name: AppRouteNames.upload,
+      builder: (context, state) => const UploadPage(),
+    ),
   ];
 }
 

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

@@ -95,7 +95,7 @@ class AuthStore extends StateNotifier<AuthInfo> {
         // 检查网络连接状态
         final container = ProviderContainer();
         final isConnected = await container.read(isConnectedProvider.future);
-        
+
         if (isConnected) {
           // 有网络连接,强制获取最新用户信息
           try {
@@ -103,10 +103,10 @@ class AuthStore extends StateNotifier<AuthInfo> {
             if (userInfo == null) {
               throw Exception('用户信息获取失败');
             }
-            
+
             // 保存用户信息到本地存储,供离线时使用
             await JwtToken.saveUserInfo(userInfo);
-            
+
             state = AuthInfo.authenticated(
               token: token,
               user: userInfo.user!,
@@ -260,4 +260,4 @@ class AuthStore extends StateNotifier<AuthInfo> {
 // 添加 Provider 实例
 final authStoreProvider = StateNotifierProvider<AuthStore, AuthInfo>(
   (ref) => AuthStore(),
-);
+);

+ 45 - 0
UI/CF.APP/chicken_farm/lib/vb_app.dart

@@ -0,0 +1,45 @@
+import 'package:chicken_farm/core/services/connectivity_service.dart';
+import 'package:chicken_farm/core/services/offline_storage_service.dart';
+import 'package:chicken_farm/routes/app_routes.dart';
+import 'package:chicken_farm/routes/route_provider.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+
+class VberApp extends ConsumerWidget {
+  const VberApp({super.key});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final goRouter = ref.watch(goRouterProvider);
+
+    // 只在应用启动时检查是否需要上传数据
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      _checkAndNavigateToUpload(goRouter);
+    });
+
+    return MaterialApp.router(
+      title: '养殖场管理系统',
+      debugShowCheckedModeBanner: false,
+      theme: ThemeData(
+        useMaterial3: true,
+        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
+      ),
+      routerConfig: goRouter,
+    );
+  }
+
+  // 检查是否有待上传数据,如果有则导航到上传页面
+  Future<void> _checkAndNavigateToUpload(GoRouter goRouter) async {
+    final container = ProviderContainer();
+    final isConnected = await container.read(isConnectedProvider.future);
+    if (isConnected) {
+      final storageService = OfflineStorageService();
+      final pendingOperations = await storageService.getPendingOperations();
+      if (pendingOperations.isNotEmpty) {
+        // 使用GoRouter进行导航
+        goRouter.pushNamed(AppRouteNames.upload);
+      }
+    }
+  }
+}

+ 2 - 3
UI/CF.APP/chicken_farm/test/widget_test.dart

@@ -5,15 +5,14 @@
 // gestures. You can also use WidgetTester to find child widgets in the widget
 // tree, read text, and verify that the values of widget properties are correct.
 
+import 'package:chicken_farm/vb_app.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 
-import 'package:chicken_farm/main.dart';
-
 void main() {
   testWidgets('Counter increments smoke test', (WidgetTester tester) async {
     // Build our app and trigger a frame.
-    await tester.pumpWidget(const MyApp());
+    await tester.pumpWidget(const VberApp());
 
     // Verify that our counter starts at 0.
     expect(find.text('0'), findsOneWidget);