瀏覽代碼

Update 优化api请求返回结果,优化文件数据库连接,优化excel导出

Yue 1 月之前
父節點
當前提交
9a7acb683a
共有 22 個文件被更改,包括 1004 次插入612 次删除
  1. 5 0
      UI/CF.APP/chicken_farm/android/app/src/main/AndroidManifest.xml
  2. 19 12
      UI/CF.APP/chicken_farm/lib/apis/_login.dart
  3. 117 75
      UI/CF.APP/chicken_farm/lib/apis/breeding/_submit.dart
  4. 17 10
      UI/CF.APP/chicken_farm/lib/apis/device/_inspection_rule.dart
  5. 14 10
      UI/CF.APP/chicken_farm/lib/apis/experiment/_sample.dart
  6. 8 3
      UI/CF.APP/chicken_farm/lib/apis/system/_config.dart
  7. 11 63
      UI/CF.APP/chicken_farm/lib/apis/system/_dict.dart
  8. 30 53
      UI/CF.APP/chicken_farm/lib/core/api/api_service.dart
  9. 168 59
      UI/CF.APP/chicken_farm/lib/core/db/sqlite_manager.dart
  10. 86 13
      UI/CF.APP/chicken_farm/lib/core/services/breeding_data_service.dart
  11. 76 22
      UI/CF.APP/chicken_farm/lib/core/utils/excel_export_util.dart
  12. 3 1
      UI/CF.APP/chicken_farm/lib/main.dart
  13. 27 2
      UI/CF.APP/chicken_farm/lib/modes/api/result_model.dart
  14. 27 32
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page.dart
  15. 27 32
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page_win.dart
  16. 30 33
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page.dart
  17. 30 33
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page_win.dart
  18. 29 33
      UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart
  19. 30 33
      UI/CF.APP/chicken_farm/lib/pages/breeding/individual_culling_page.dart
  20. 29 33
      UI/CF.APP/chicken_farm/lib/pages/breeding/individual_weighing_page.dart
  21. 11 3
      UI/CF.APP/chicken_farm/lib/pages/checkin/checkin_record_page.dart
  22. 210 57
      UI/CF.APP/chicken_farm/lib/pages/excel/export_data_page.dart

+ 5 - 0
UI/CF.APP/chicken_farm/android/app/src/main/AndroidManifest.xml

@@ -8,6 +8,11 @@
     <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
     <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
+    
+    <!-- 存储权限 -->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
 
     <!-- 蓝牙功能声明 -->
     <uses-feature android:name="android.hardware.bluetooth" android:required="true" />

+ 19 - 12
UI/CF.APP/chicken_farm/lib/apis/_login.dart

@@ -11,29 +11,36 @@ class LoginApi {
   LoginApi._internal();
 
   Future<AuthResultModel> login(LoginModel data) async {
-    final response = await ApiService().post('/auth/login', data: data);
-    return AuthResultModel.fromJson(response);
+    final result = await ApiService().post('/auth/login', data: data);
+    if (result.success && result.data != null) {
+      return AuthResultModel.fromJson(result.data);
+    } else {
+      throw result.message;
+    }
   }
 
   Future<AuthResultModel> refreshToken(String refreshToken) async {
-    final response = await ApiService().post(
+    final result = await ApiService().post(
       '/auth/refreshToken',
       data: {"refreshToken": refreshToken},
     );
-    return AuthResultModel.fromJson(response);
+    if (result.success && result.data != null) {
+      return AuthResultModel.fromJson(result.data);
+    } else {
+      throw result.message;
+    }
   }
 
   Future<UserInfoModel?> getInfo() async {
-    final response = await ApiService().get('/system/user/getInfo');
-    if (response == null) return null;
-    return UserInfoModel.fromJson(response);
+    final result = await ApiService().get('/system/user/getInfo');
+    if (result.success && result.data != null) {
+      return UserInfoModel.fromJson(result.data);
+    } else {
+      return null;
+    }
   }
 
   Future<void> logout() async {
-    return ApiService().post('/auth/logout');
-  }
-
-  Future<dynamic> getRouters() async {
-    return await ApiService().get('/system/menu/getRouters');
+    ApiService().post('/auth/logout');
   }
 }

+ 117 - 75
UI/CF.APP/chicken_farm/lib/apis/breeding/_submit.dart

@@ -2,6 +2,7 @@ import 'package:chicken_farm/core/api/api_option.dart';
 import 'package:chicken_farm/core/api/api_service.dart';
 import 'package:chicken_farm/core/config/app_config.dart';
 import 'package:chicken_farm/core/services/breeding_data_service.dart';
+import 'package:chicken_farm/modes/api/result_model.dart';
 
 class BreedSubmitApi {
   static final BreedSubmitApi _instance = BreedSubmitApi._internal();
@@ -10,91 +11,120 @@ class BreedSubmitApi {
 
   BreedSubmitApi._internal();
 
-  Future<dynamic> bindChicken(dynamic data) async {
-    if (AppConfig.isOffline) {
-      return await bindChickenOffline(data);
-    } else {
-      return (await ApiService().postWithOfflineSupport(
-        '/app/breeding/create/',
-        data: data,
-        apiOption: ApiOption.noAlert(),
-      )).check();
+  Future<ResultModel> bindChicken(dynamic data) async {
+    try {
+      if (AppConfig.isOffline) {
+        return await bindChickenOffline(data);
+      } else {
+        return await ApiService().postWithOfflineSupport(
+          '/app/breeding/create/',
+          data: data,
+          apiOption: ApiOption.noAlert(),
+        );
+      }
+    } catch (e) {
+      return ResultModel(success: false, message: e.toString(), data: null);
     }
   }
 
-  Future<dynamic> bindChickenOffline(dynamic data) async {
+  Future<ResultModel> bindChickenOffline(dynamic data) async {
     // 保存到本地数据库
-    Map<String, dynamic> bindData = {
-      'rfid': data['rfid'],
-      'batch_num': data['batchNum'],
-      'family_id': data['familyId'],
-      'date': data['date'],
-      'is_export': 0,
-    };
-    await BreedingDataService().insertChicken(bindData);
-    return {'success': true, 'message': '数据已保存到本地'};
+    List<Map<String, dynamic>> list = data.map<Map<String, dynamic>>((item) {
+      return {
+        'rfid': item['rfid'],
+        'batch_num': item['batchNum'],
+        'family_id': item['familyId'],
+        'date': item['date'],
+        'is_export': 0,
+      };
+    }).toList();
+    if (await BreedingDataService().batchInsertChicken(list)) {
+      return ResultModel.success('数据保存成功');
+    } else {
+      return ResultModel.fail('数据保存失败');
+    }
   }
 
-  Future<dynamic> cageChange(dynamic data) async {
-    if (AppConfig.isOffline) {
-      return await cageChangeOffline(data);
+  Future<ResultModel> cageChange(dynamic data) async {
+    try {
+      if (AppConfig.isOffline) {
+        return await cageChangeOffline(data);
+      }
+      return await ApiService().postWithOfflineSupport(
+        '/app/breeding/cageChange/',
+        data: data,
+        apiOption: ApiOption.noAlert(),
+      );
+    } catch (e) {
+      return ResultModel(success: false, message: e.toString(), data: null);
     }
-    return (await ApiService().postWithOfflineSupport(
-      '/app/breeding/cageChange/',
-      data: data,
-      apiOption: ApiOption.noAlert(),
-    )).check();
   }
 
-  Future<dynamic> cageChangeOffline(dynamic data) async {
+  Future<ResultModel> cageChangeOffline(dynamic data) async {
     // 保存到本地数据库
-    Map<String, dynamic> changeData = {
-      'rfid': data['rfid'],
-      'source_cage': data['sourceCage'],
-      'target_cage': data['targetCage'],
-      'date': data['date'],
-      'is_export': 0,
-    };
-    await BreedingDataService().insertCageChange(changeData);
-    return {'success': true, 'message': '数据已保存到本地'};
+    final ids = data['rfids'] as List<String>;
+    List<Map<String, dynamic>> list = ids.map<Map<String, dynamic>>((id) {
+      return {
+        'rfid': id,
+        'source_cage': data['sourceCage'],
+        'target_cage': data['targetCage'],
+        'date': data['date'],
+        'is_export': 0,
+      };
+    }).toList();
+    if (await BreedingDataService().batchInsertCageChange(list)) {
+      return ResultModel.success('数据保存成功');
+    } else {
+      return ResultModel.fail('数据保存失败');
+    }
   }
 
-  Future<dynamic> weight(dynamic data) async {
-    if (AppConfig.isOffline) {
-      return await weightOffline(data);
+  Future<ResultModel> weight(dynamic data) async {
+    try {
+      if (AppConfig.isOffline) {
+        return await weightOffline(data);
+      }
+      return await ApiService().postWithOfflineSupport(
+        '/app/breeding/weight/',
+        data: data,
+        apiOption: ApiOption.noAlert(),
+      );
+    } catch (e) {
+      return ResultModel(success: false, message: e.toString(), data: null);
     }
-    return (await ApiService().postWithOfflineSupport(
-      '/app/breeding/weight/',
-      data: data,
-      apiOption: ApiOption.noAlert(),
-    )).check();
   }
 
-  Future<dynamic> weightOffline(dynamic data) async {
+  Future<ResultModel> weightOffline(dynamic data) async {
     // 保存到本地数据库
-    Map<String, dynamic> cullData = {
+    Map<String, dynamic> weightData = {
       'rfid': data['rfid'],
       'weight': data['weight'],
       'date': data['date'],
       'is_export': 0,
     };
-    await BreedingDataService().insertCull(cullData);
-    await BreedingDataService().insertWeight(data as Map<String, dynamic>);
-    return {'success': true, 'message': '数据已保存到本地'};
+    if (await BreedingDataService().insertWeight(weightData)) {
+      return ResultModel.success('数据保存成功');
+    } else {
+      return ResultModel.fail('数据保存失败');
+    }
   }
 
-  Future<dynamic> cull(dynamic data) async {
-    if (AppConfig.isOffline) {
-      return await cullOffline(data);
+  Future<ResultModel> cull(dynamic data) async {
+    try {
+      if (AppConfig.isOffline) {
+        return await cullOffline(data);
+      }
+      return await ApiService().postWithOfflineSupport(
+        '/app/breeding/cull/',
+        data: data,
+        apiOption: ApiOption.noAlert(),
+      );
+    } catch (e) {
+      return ResultModel(success: false, message: e.toString(), data: null);
     }
-    return (await ApiService().postWithOfflineSupport(
-      '/app/breeding/cull/',
-      data: data,
-      apiOption: ApiOption.noAlert(),
-    )).check();
   }
 
-  Future<dynamic> cullOffline(dynamic data) async {
+  Future<ResultModel> cullOffline(dynamic data) async {
     // 保存到本地数据库
     Map<String, dynamic> cullData = {
       'rfid': data['rfid'],
@@ -103,33 +133,45 @@ class BreedSubmitApi {
       'date': data['date'],
       'is_export': 0,
     };
-    await BreedingDataService().insertCull(cullData);
-    return {'success': true, 'message': '数据已保存到本地'};
+    if (await BreedingDataService().insertCull(cullData)) {
+      return ResultModel.success('数据保存成功');
+    } else {
+      return ResultModel.fail('数据保存失败');
+    }
   }
 
-  Future<dynamic> batchCull(dynamic data) async {
-    if (AppConfig.isOffline) {
-      return await batchCullOffline(data);
+  Future<ResultModel> batchCull(dynamic data) async {
+    try {
+      if (AppConfig.isOffline) {
+        return await batchCullOffline(data);
+      }
+      return await ApiService().postWithOfflineSupport(
+        '/app/breeding/batchCull/',
+        data: data,
+        apiOption: ApiOption.noAlert(),
+      );
+    } catch (e) {
+      return ResultModel(success: false, message: e.toString(), data: null);
     }
-    return (await ApiService().postWithOfflineSupport(
-      '/app/breeding/batchCull/',
-      data: data,
-      apiOption: ApiOption.noAlert(),
-    )).check();
   }
 
-  Future<dynamic> batchCullOffline(dynamic data) async {
+  Future<ResultModel> batchCullOffline(dynamic data) async {
     List<String> ids = data['rfids'];
-    List<Map<String, dynamic>> batchCullData = ids.map((id) {
+    List<Map<String, dynamic>> batchCullData = ids.map<Map<String, dynamic>>((
+      id,
+    ) {
       return {
         'rfid': id,
-        'cull_time': data['cull_time'],
-        'cull_reason': data['cull_reason'],
+        'cull_reason': data['cullReason'],
+        'disposal_method': data['disposalMethod'],
         'date': data['date'],
         'is_export': 0,
       };
     }).toList();
-    await BreedingDataService().batchInsertCull(batchCullData);
-    return {'success': true, 'message': '数据已保存到本地'};
+    if (await BreedingDataService().batchInsertCull(batchCullData)) {
+      return ResultModel.success('数据保存成功');
+    } else {
+      return ResultModel.fail('数据保存失败');
+    }
   }
 }

+ 17 - 10
UI/CF.APP/chicken_farm/lib/apis/device/_inspection_rule.dart

@@ -1,4 +1,5 @@
 import 'package:chicken_farm/core/api/api_service.dart';
+import 'package:chicken_farm/modes/api/result_model.dart';
 import 'package:chicken_farm/modes/device/inspection_rule/checkin_log.dart';
 import 'package:chicken_farm/modes/device/inspection_rule/inspection_rule.dart';
 
@@ -10,26 +11,32 @@ class InspectionRuleApi {
   InspectionRuleApi._internal();
 
   Future<InspectionRuleModel?> queryRule(String id) async {
-    final response = await ApiService().get('/device/inspectionRule/$id');
-    if (response == null) return null;
-    return InspectionRuleModel.fromJson(response);
+    final result = await ApiService().get('/device/inspectionRule/$id');
+    if (result.success && result.data != null) {
+      return InspectionRuleModel.fromJson(result.data);
+    } else {
+      return null;
+    }
   }
 
   Future<List<CheckinLogModel>> queryCheckinList(String id) async {
-    final response = await ApiService().get(
+    final result = await ApiService().get(
       '/device/inspectionRule/queryCheckinList/$id',
     );
-    if (response == null) return List.empty();
-    return response
-        .map<CheckinLogModel>((e) => CheckinLogModel.fromJson(e))
-        .toList();
+    if (result.success && result.data != null) {
+      return result.data
+          .map<CheckinLogModel>((e) => CheckinLogModel.fromJson(e))
+          .toList();
+    } else {
+      return [];
+    }
   }
 
-  Future<dynamic> checkIn(String id) async {
+  Future<ResultModel> checkIn(String id) async {
     return await ApiService().post('/device/inspectionRule/checkIn/$id');
   }
 
-  Future<dynamic> checkInWithPhoto(String id, dynamic data) async {
+  Future<ResultModel> checkInWithPhoto(String id, dynamic data) async {
     return await ApiService().post(
       '/device/inspectionRule/checkInWithPhoto/$id',
       data: data,

+ 14 - 10
UI/CF.APP/chicken_farm/lib/apis/experiment/_sample.dart

@@ -10,20 +10,24 @@ class SampleApi {
   SampleApi._internal();
 
   Future<SampleModel?> querySample(String id) async {
-    final response = await ApiService().get(
-      '/experiment/sample/querySample/$id',
-    );
-    if (response == null) return null;
-    return SampleModel.fromJson(response);
+    final result = await ApiService().get('/experiment/sample/querySample/$id');
+    if (result.success && result.data != null) {
+      return SampleModel.fromJson(result.data);
+    } else {
+      return null;
+    }
   }
 
   Future<List<SampleFlowLogModel>> queryFlowLogs(String id) async {
-    final response = await ApiService().get(
+    final result = await ApiService().get(
       '/experiment/sample/queryFlowLogs/$id',
     );
-    if (response == null) return [];
-    return response
-        .map<SampleFlowLogModel>((e) => SampleFlowLogModel.fromJson(e))
-        .toList();
+    if (result.success && result.data != null) {
+      return result.data
+          .map<SampleFlowLogModel>((e) => SampleFlowLogModel.fromJson(e))
+          .toList();
+    } else {
+      return [];
+    }
   }
 }

+ 8 - 3
UI/CF.APP/chicken_farm/lib/apis/system/_config.dart

@@ -1,4 +1,5 @@
 import 'package:chicken_farm/core/api/api_service.dart';
+import 'package:chicken_farm/modes/api/result_model.dart';
 import 'package:chicken_farm/modes/system/config.dart';
 
 class ConfigApi {
@@ -16,13 +17,17 @@ class ConfigApi {
   }
 
   Future<ConfigModel?> getConfigKey(String configKey) async {
-    final response = await ApiService().get(
+    final result = await ApiService().get(
       '/system/config/configKey/$configKey',
     );
-    return response != null ? ConfigModel.fromJson(response) : null;
+    if (result.success && result.data != null) {
+      return ConfigModel.fromJson(result.data);
+    } else {
+      return null;
+    }
   }
 
-  Future<dynamic> refreshCache() async {
+  Future<ResultModel> refreshCache() async {
     return await ApiService().delete('/system/config/refreshCache');
   }
 }

+ 11 - 63
UI/CF.APP/chicken_farm/lib/apis/system/_dict.dart

@@ -1,4 +1,5 @@
 import 'package:chicken_farm/core/api/api_service.dart';
+import 'package:chicken_farm/modes/api/result_model.dart';
 import 'package:chicken_farm/modes/system/dict.dart';
 
 class DictApi {
@@ -8,83 +9,30 @@ class DictApi {
 
   DictApi._internal();
 
-  Future<dynamic> getTypes() async {
+  Future<ResultModel> getTypes() async {
     return await ApiService().get(
       '/system/dict/type/list',
       queryParameters: {'pageSize': 10000},
     );
   }
 
-  Future<dynamic> getType(String id) async {
+  Future<ResultModel> getType(String id) async {
     return await ApiService().get('/system/dict/type/$id');
   }
 
   // 修改此方法以正确处理返回的字典数据列表
   Future<List<DictDataModel>?> getDicts(String type) async {
-    try {
-      final response = await ApiService().get('/system/dict/data/type/$type');
-      if (response != null) {
-        // 根据RuoYi的标准API格式处理响应
-        if (response is List) {
-          // 直接是列表的情况
-          return response
-              .map(
-                (item) => item is Map<String, dynamic>
-                    ? DictDataModel.fromJson(item)
-                    : DictDataModel(
-                        dictCode: 0,
-                        dictSort: 0,
-                        dictLabel: '',
-                        dictValue: '',
-                        dictType: type,
-                      ),
-              )
-              .toList();
-        } else if (response is Map<String, dynamic>) {
-          // 包含在data字段中的情况
-          if (response['data'] is List) {
-            return (response['data'] as List)
-                .map(
-                  (item) => item is Map<String, dynamic>
-                      ? DictDataModel.fromJson(item)
-                      : DictDataModel(
-                          dictCode: 0,
-                          dictSort: 0,
-                          dictLabel: '',
-                          dictValue: '',
-                          dictType: type,
-                        ),
-                )
-                .toList();
-          } else if (response['rows'] is List) {
-            // 包含在rows字段中的情况
-            return (response['rows'] as List)
-                .map(
-                  (item) => item is Map<String, dynamic>
-                      ? DictDataModel.fromJson(item)
-                      : DictDataModel(
-                          dictCode: 0,
-                          dictSort: 0,
-                          dictLabel: '',
-                          dictValue: '',
-                          dictType: type,
-                        ),
-                )
-                .toList();
-          } else {
-            // 单个对象的情况
-            return [DictDataModel.fromJson(response)];
-          }
-        }
-      }
-      return null;
-    } catch (e) {
-      // 出现异常时返回null
-      return null;
+    final result = await ApiService().get('/system/dict/data/type/$type');
+    if (result.success && result.data != null) {
+      return result.data
+          .map<DictDataModel>((e) => DictDataModel.fromJson(e))
+          .toList();
+    } else {
+      return [];
     }
   }
 
-  Future<dynamic> refreshCache() async {
+  Future<ResultModel> refreshCache() async {
     return await ApiService().delete('/system/dict/type/refreshCache');
   }
 }

+ 30 - 53
UI/CF.APP/chicken_farm/lib/core/api/api_service.dart

@@ -2,6 +2,7 @@ import 'package:chicken_farm/core/api/api_option.dart';
 import 'package:chicken_farm/core/services/navigation_service.dart';
 import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:chicken_farm/core/utils/toast.dart';
+import 'package:chicken_farm/modes/api/result_model.dart';
 import 'package:chicken_farm/routes/app_routes.dart';
 import 'package:dio/dio.dart';
 import 'package:go_router/go_router.dart';
@@ -13,7 +14,7 @@ import 'package:uuid/uuid.dart';
 class ApiService {
   final Dio _dio = ApiClient.instance;
 
-  Future<dynamic> get(
+  Future<ResultModel> get(
     String path, {
     Map<String, dynamic>? queryParameters,
     ApiOption? apiOption,
@@ -34,7 +35,7 @@ class ApiService {
     }
   }
 
-  Future<dynamic> post(
+  Future<ResultModel> post(
     String path, {
     dynamic data,
     Map<String, dynamic>? queryParameters,
@@ -57,7 +58,7 @@ class ApiService {
     }
   }
 
-  Future<dynamic> put(
+  Future<ResultModel> put(
     String path, {
     dynamic data,
     Map<String, dynamic>? queryParameters,
@@ -80,7 +81,7 @@ class ApiService {
     }
   }
 
-  Future<dynamic> delete(
+  Future<ResultModel> delete(
     String path, {
     dynamic data,
     Map<String, dynamic>? queryParameters,
@@ -104,7 +105,7 @@ class ApiService {
   }
 
   // 新增支持离线操作的POST方法
-  Future<ApiOfflineModel> postWithOfflineSupport(
+  Future<ResultModel> postWithOfflineSupport(
     String path, {
     dynamic data,
     Map<String, dynamic>? queryParameters,
@@ -115,12 +116,11 @@ class ApiService {
     // 先检查是否有待上传的数据,如果有则直接保存到离线队列
     final pendingOps = await OfflineStorageService().getPendingOperations();
     if (pendingOps.isNotEmpty) {
-      final result = await _saveOfflineOperation(path, 'POST', data);
-      return result;
+      return (await _saveOfflineOperation(path, 'POST', data)).convert();
     }
 
     try {
-      final result = await post(
+      return await post(
         path,
         data: data,
         queryParameters: queryParameters,
@@ -128,21 +128,18 @@ 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;
+        return (await _saveOfflineOperation(path, 'POST', data)).convert();
       }
+      return ResultModel.fail('请求出错');
     }
   }
 
   // 新增支持离线操作的PUT方法
-  Future<ApiOfflineModel> putWithOfflineSupport(
+  Future<ResultModel> putWithOfflineSupport(
     String path, {
     dynamic data,
     Map<String, dynamic>? queryParameters,
@@ -153,15 +150,12 @@ class ApiService {
     // 先检查是否有待上传的数据,如果有则直接保存到离线队列
     final pendingOps = await OfflineStorageService().getPendingOperations();
     if (pendingOps.isNotEmpty) {
-      // 有待上传数据,直接保存到队列
-      final result = await _saveOfflineOperation(path, 'PUT', data);
-      // 返回特殊标识表示已保存到离线队列
-      return result;
+      return (await _saveOfflineOperation(path, 'PUT', data)).convert();
     }
 
     // 没有待上传数据,尝试直接发送请求
     try {
-      final result = put(
+      return await put(
         path,
         data: data,
         queryParameters: queryParameters,
@@ -169,17 +163,13 @@ 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;
+        return (await _saveOfflineOperation(path, 'PUT', data)).convert();
       }
+      return ResultModel.fail('请求出错');
     }
   }
 
@@ -218,7 +208,7 @@ class ApiService {
                 error.type == DioExceptionType.connectionError));
   }
 
-  dynamic _handleResponse(Response response, ApiOption apiOption) {
+  ResultModel _handleResponse(Response response, ApiOption apiOption) {
     try {
       if (response.statusCode == 200 || response.statusCode == 201) {
         // 假设后端返回的数据结构是 { "code": 200, "msg": "success", "data": ... }
@@ -234,7 +224,7 @@ class ApiService {
           if (apiOption.alert) {
             ToastUtil.success(msg ?? '操作成功');
           }
-          return rData;
+          return ResultModel(message: msg ?? "", data: rData);
         } else if (code == 401) {
           if (apiOption.alert) {
             ToastUtil.errorAlert(msg ?? '请先登录!');
@@ -242,50 +232,37 @@ class ApiService {
           NavigationService.navigatorKey.currentContext?.goNamed(
             AppRouteNames.login,
           );
-          return null;
+          return ResultModel.fail(msg ?? '请先登录!');
         } else {
           final msg1 = code == 403 ? "没有权限!" : data['msg'] ?? "操作失败";
           if (apiOption.alert) {
             ToastUtil.errorAlert(msg1);
-          } else {
-            throw Exception(msg1);
           }
-          return null;
+          // else {
+          //   throw Exception(msg1);
+          // }
+          return ResultModel.fail(msg1);
         }
       } else {
         final msg2 = response.statusMessage ?? '请求失败';
         if (apiOption.alert) {
           ToastUtil.errorAlert(msg2);
-        } else {
-          throw Exception(msg2);
         }
-        return null;
+        // else {
+        //   throw Exception(msg2);
+        // }
+        return ResultModel.fail(msg2);
       }
     } catch (e) {
       logger.e("数据解析失败:$e");
       final msg = "数据解析失败";
       if (apiOption.alert) {
         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;
+      // else {
+      //   throw Exception(msg);
+      // }
+      return ResultModel.fail(msg);
     }
   }
 }

+ 168 - 59
UI/CF.APP/chicken_farm/lib/core/db/sqlite_manager.dart

@@ -15,10 +15,15 @@ class SqliteManager {
 
   // 核心配置
   String? _dbName;
+  String _dbPath = '';
   int _dbVersion = 1;
   final List<TableModel> _tableConfigs = [];
   Database? _database;
 
+  // 延时关闭相关变量
+  Timer? _closeTimer;
+  int _pendingRequests = 0;
+
   /// 初始化数据库(仅需传入数据库名和表配置)
   /// [dbName] 数据库名称
   /// [version] 数据库版本(默认1)
@@ -30,26 +35,23 @@ class SqliteManager {
     }
     _tableConfigs.addAll(TableConfig.defaultConfigs());
     logger.d('数据库配置初始化完成: $dbName (v$version)');
+    _dbPath = await _getDbPath();
+    await _getDatabase();
   }
 
-  /// 内部方法:获取数据库连接(自动创建/升级)
-  Future<Database> _getDatabase() async {
+  Future<String> _getDbPath() async {
     if (_dbName == null) {
       throw Exception('请先调用 init 方法初始化数据库配置');
     }
-
-    // 如果已有打开的连接,直接返回
-    if (_database != null && _database!.isOpen) {
-      return _database!;
-    }
-
     // 获取数据库路径
     // Directory documentsDir = await getApplicationDocumentsDirectory();
     // String dbPath = join(documentsDir.path, _dbName!);
 
     // 1. 获取应用文档根目录
-    Directory documentsDir = await getApplicationDocumentsDirectory();
-    logger.d('应用文档根目录:${documentsDir.path}');
+    Directory? documentsDir = await getExternalStorageDirectory();
+    // logger.d('应用文档根目录1:${documentsDir?.path}');
+    documentsDir ??= await getApplicationDocumentsDirectory();
+    // logger.d('应用文档根目录2:${documentsDir.path}');
     // 2. 拼接自定义的 db 子文件夹路径
     String dbFolderPath = join(documentsDir.path, 'db');
     // 3. 检查并创建 db 文件夹(不存在则创建)
@@ -62,10 +64,22 @@ class SqliteManager {
     // 4. 拼接最终的数据库文件路径(db文件夹 + 数据库名)
     String dbPath = join(dbFolderPath, _dbName!);
     logger.d('数据库文件路径:$dbPath');
+    return dbPath;
+  }
+
+  /// 内部方法:获取数据库连接(自动创建/升级)
+  Future<Database> _getDatabase() async {
+    if (_dbPath.isEmpty) {
+      throw Exception('请先调用 _getDbPath 获取数据库路径');
+    }
+    // 如果已有打开的连接,直接返回
+    if (_database != null && _database!.isOpen) {
+      return _database!;
+    }
 
     // 打开/创建数据库
     _database = await openDatabase(
-      dbPath,
+      _dbPath,
       version: _dbVersion,
       onCreate: (db, version) async {
         // 批量创建表
@@ -86,7 +100,10 @@ class SqliteManager {
         }
       },
     );
-
+    if (_database == null) {
+      throw Exception('数据库初始化失败');
+    }
+    logger.d('数据库连接成功');
     return _database!;
   }
 
@@ -94,22 +111,33 @@ class SqliteManager {
   Future<T> _executeWithAutoClose<T>(
     Future<T> Function(Database db) action,
   ) async {
-    // 获取连接
+    // 取消之前的关闭定时器(如果有)
+    _closeTimer?.cancel();
+    _pendingRequests++;
     final db = await _getDatabase();
-
     try {
-      // 执行核心操作
       final result = await action(db);
       return result;
     } finally {
-      // 操作完成后自动关闭连接
-      if (db.isOpen) {
-        await db.close();
-        _database = null;
+      _pendingRequests--;
+      if (_pendingRequests <= 0) {
+        _scheduleCloseDatabase();
       }
     }
   }
 
+  /// 安排延时关闭数据库
+  void _scheduleCloseDatabase() {
+    _closeTimer?.cancel();
+    _closeTimer = Timer(Duration(seconds: 5), () {
+      if (_database != null && _database!.isOpen) {
+        _database!.close();
+        _database = null;
+        logger.d('数据库连接已延时关闭');
+      }
+    });
+  }
+
   // ===================== 通用CRUD方法(自动管理连接) =====================
 
   /// 插入数据
@@ -118,10 +146,20 @@ class SqliteManager {
     required Map<String, dynamic> data,
     ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace,
   }) async {
-    data["create_time"] = DateTimeUtil.format(DateTime.now());
-    return _executeWithAutoClose((db) async {
-      return await db.insert(table, data, conflictAlgorithm: conflictAlgorithm);
-    });
+    try {
+      data["create_time"] = DateTimeUtil.format(DateTime.now());
+      return _executeWithAutoClose((db) async {
+        return await db.insert(
+          table,
+          data,
+          conflictAlgorithm: conflictAlgorithm,
+        );
+      });
+    } on Exception catch (e, s) {
+      logger.e("插入数据失败:表[$table] 数据[$data]", e, s);
+      logger.e("插入数据失败:表[$table] 数据[$data] 错误: $e\n堆栈跟踪: $s");
+      return -1;
+    }
   }
 
   /// 批量插入数据
@@ -129,14 +167,18 @@ class SqliteManager {
     required String table,
     required List<Map<String, dynamic>> dataList,
   }) async {
-    return _executeWithAutoClose((db) async {
-      final batch = db.batch();
-      for (var data in dataList) {
-        data["create_time"] = DateTimeUtil.format(DateTime.now());
-        batch.insert(table, data);
-      }
-      await batch.commit(noResult: true);
-    });
+    try {
+      return _executeWithAutoClose((db) async {
+        final batch = db.batch();
+        for (var data in dataList) {
+          data["create_time"] = DateTimeUtil.format(DateTime.now());
+          batch.insert(table, data);
+        }
+        await batch.commit(noResult: true);
+      });
+    } on Exception catch (e, s) {
+      logger.e("批量插入数据失败:表[$table] 数据条数[${dataList.length}] 错误: $e\n堆栈跟踪: $s");
+    }
   }
 
   /// 查询数据
@@ -151,19 +193,26 @@ class SqliteManager {
     int? limit,
     int? offset,
   }) async {
-    return _executeWithAutoClose((db) async {
-      return await db.query(
-        table,
-        columns: columns,
-        where: where,
-        whereArgs: whereArgs,
-        groupBy: groupBy,
-        having: having,
-        orderBy: orderBy,
-        limit: limit,
-        offset: offset,
+    try {
+      return _executeWithAutoClose((db) async {
+        return await db.query(
+          table,
+          columns: columns,
+          where: where,
+          whereArgs: whereArgs,
+          groupBy: groupBy,
+          having: having,
+          orderBy: orderBy,
+          limit: limit,
+          offset: offset,
+        );
+      });
+    } on Exception catch (e, s) {
+      logger.e(
+        "查询数据失败:表[$table] 条件[where:$where, whereArgs:$whereArgs] 错误: $e\n堆栈跟踪: $s",
       );
-    });
+      return [];
+    }
   }
 
   /// 更新数据
@@ -173,10 +222,17 @@ class SqliteManager {
     String? where,
     List<dynamic>? whereArgs,
   }) async {
-    data["update_time"] = DateTimeUtil.format(DateTime.now());
-    return _executeWithAutoClose((db) async {
-      return await db.update(table, data, where: where, whereArgs: whereArgs);
-    });
+    try {
+      data["update_time"] = DateTimeUtil.format(DateTime.now());
+      return _executeWithAutoClose((db) async {
+        return await db.update(table, data, where: where, whereArgs: whereArgs);
+      });
+    } on Exception catch (e, s) {
+      logger.e(
+        "更新数据失败:表[$table] 数据[$data] 条件[where:$where, whereArgs:$whereArgs] 错误: $e\n堆栈跟踪: $s",
+      );
+      return -1;
+    }
   }
 
   /// 删除数据
@@ -185,27 +241,75 @@ class SqliteManager {
     String? where,
     List<dynamic>? whereArgs,
   }) async {
-    return _executeWithAutoClose((db) async {
-      return await db.delete(table, where: where, whereArgs: whereArgs);
-    });
+    try {
+      return _executeWithAutoClose((db) async {
+        return await db.delete(table, where: where, whereArgs: whereArgs);
+      });
+    } on Exception catch (e, s) {
+      logger.e(
+        "删除数据失败:表[$table] 条件[where:$where, whereArgs:$whereArgs] 错误: $e\n堆栈跟踪: $s",
+      );
+      return -1;
+    }
+  }
+
+  /// 查询符合条件的数据条数
+  Future<int> queryCount(
+    String table, {
+    String? where,
+    List<dynamic>? whereArgs,
+  }) async {
+    try {
+      return _executeWithAutoClose((db) async {
+        final result = await db.query(
+          table,
+          columns: ['COUNT(*) as count'],
+          where: where,
+          whereArgs: whereArgs,
+        );
+
+        if (result.isNotEmpty) {
+          final count = result.first['count'];
+          return count is int ? count : int.parse(count.toString());
+        }
+
+        return 0;
+      });
+    } on Exception catch (e, s) {
+      logger.e(
+        "查询数据条数失败:表[$table] 条件[where:$where, whereArgs:$whereArgs] 错误: $e\n堆栈跟踪: $s",
+      );
+      return -1;
+    }
   }
 
   /// 执行原生SQL
   Future<void> executeSql(String sql, [List<dynamic>? arguments]) async {
-    return _executeWithAutoClose((db) async {
-      await db.execute(sql, arguments);
-    });
+    try {
+      return _executeWithAutoClose((db) async {
+        await db.execute(sql, arguments);
+      });
+    } on Exception catch (e, s) {
+      logger.e("执行SQL失败:SQL[$sql] 参数[$arguments] 错误: $e\n堆栈跟踪: $s");
+    }
   }
 
   /// 执行事务(事务内保持连接,事务结束后关闭)
   Future<T> transaction<T>(Future<T> Function(Transaction txn) action) async {
-    return _executeWithAutoClose((db) async {
-      return await db.transaction<T>(action);
-    });
+    try {
+      return _executeWithAutoClose((db) async {
+        return await db.transaction<T>(action);
+      });
+    } on Exception catch (e, s) {
+      logger.e("执行事务失败 错误: $e\n堆栈跟踪: $s");
+      rethrow;
+    }
   }
 
   /// 手动关闭数据库(备用方法)
   Future<void> closeDb() async {
+    _closeTimer?.cancel();
+    _pendingRequests = 0;
     if (_database != null && _database!.isOpen) {
       await _database!.close();
       _database = null;
@@ -227,8 +331,13 @@ class SqliteManager {
 
   /// 清空表数据
   Future<int> clearTable(String table) async {
-    return _executeWithAutoClose((db) async {
-      return await db.delete(table);
-    });
+    try {
+      return _executeWithAutoClose((db) async {
+        return await db.delete(table);
+      });
+    } on Exception catch (e, s) {
+      logger.e("清空表数据失败:表[$table] 错误: $e\n堆栈跟踪: $s");
+      return -1;
+    }
   }
 }

+ 86 - 13
UI/CF.APP/chicken_farm/lib/core/services/breeding_data_service.dart

@@ -16,28 +16,94 @@ class BreedingDataService {
 
   final SqliteManager _sqliteManager = SqliteManager();
 
-  Future<void> insertChicken(Map<String, dynamic> data) async {
-    _sqliteManager.insert(table: TableConfig.chicken, data: data);
+  Future<bool> insertChicken(Map<String, dynamic> data) async {
+    try {
+      return (await _sqliteManager.insert(
+            table: TableConfig.chicken,
+            data: data,
+          )) >
+          0;
+    } catch (e) {
+      logger.e('插入个体出错:$e');
+      return false;
+    }
   }
 
-  Future<void> batchInsertChicken(List<Map<String, dynamic>> dataList) async {
-    _sqliteManager.batchInsert(table: TableConfig.chicken, dataList: dataList);
+  Future<bool> batchInsertChicken(List<Map<String, dynamic>> dataList) async {
+    try {
+      await _sqliteManager.batchInsert(
+        table: TableConfig.chicken,
+        dataList: dataList,
+      );
+      return true;
+    } catch (e) {
+      logger.e('批量插入个体出错:$e');
+      return false;
+    }
   }
 
-  Future<void> insertCageChange(Map<String, dynamic> data) async {
-    _sqliteManager.insert(table: TableConfig.cageChange, data: data);
+  Future<bool> insertCageChange(Map<String, dynamic> data) async {
+    try {
+      return await _sqliteManager.insert(
+            table: TableConfig.cageChange,
+            data: data,
+          ) >
+          0;
+    } catch (e) {
+      logger.e('插入换笼记录出错:$e');
+      return false;
+    }
   }
 
-  Future<void> insertWeight(Map<String, dynamic> data) async {
-    _sqliteManager.insert(table: TableConfig.weight, data: data);
+  Future<bool> batchInsertCageChange(
+    List<Map<String, dynamic>> dataList,
+  ) async {
+    try {
+      await _sqliteManager.batchInsert(
+        table: TableConfig.cageChange,
+        dataList: dataList,
+      );
+      return true;
+    } catch (e) {
+      logger.e('批量插入换笼记录出错:$e');
+      return false;
+    }
   }
 
-  Future<void> insertCull(Map<String, dynamic> data) async {
-    _sqliteManager.insert(table: TableConfig.cull, data: data);
+  Future<bool> insertWeight(Map<String, dynamic> data) async {
+    try {
+      return await _sqliteManager.insert(
+            table: TableConfig.weight,
+            data: data,
+          ) >
+          0;
+    } catch (e) {
+      logger.e('插入称重记录出错:$e');
+      return false;
+    }
+  }
+
+  Future<bool> insertCull(Map<String, dynamic> data) async {
+    try {
+      return await _sqliteManager.insert(table: TableConfig.cull, data: data) >
+          0;
+    } catch (e) {
+      logger.e('插入淘汰记录出错:$e');
+      return false;
+    }
   }
 
-  Future<void> batchInsertCull(List<Map<String, dynamic>> dataList) async {
-    _sqliteManager.batchInsert(table: TableConfig.cull, dataList: dataList);
+  Future<bool> batchInsertCull(List<Map<String, dynamic>> dataList) async {
+    try {
+      await _sqliteManager.batchInsert(
+        table: TableConfig.cull,
+        dataList: dataList,
+      );
+      return true;
+    } catch (e) {
+      logger.e('批量插入淘汰记录出错:$e');
+      return false;
+    }
   }
 
   Future<List<Map<String, dynamic>>> queryAllChickenList() async {
@@ -72,6 +138,14 @@ class BreedingDataService {
     return list;
   }
 
+  Future<int> queryCount(String tableName) async {
+    final count = await _sqliteManager.queryCount(
+      tableName,
+      where: "is_export = 0",
+    );
+    return count;
+  }
+
   Future<ResultModel> exportData(String type) async {
     String tableName = "";
     String templateKey = "";
@@ -99,7 +173,6 @@ class BreedingDataService {
     if (tableName.isEmpty || templateKey.isEmpty) {
       result.message = "导出类型[$type]不支持";
     }
-    String msg = "";
     String typeName = BreedConfig.getName(templateKey);
     try {
       List<Map<String, dynamic>> listData = await _sqliteManager.query(

+ 76 - 22
UI/CF.APP/chicken_farm/lib/core/utils/excel_export_util.dart

@@ -62,10 +62,16 @@ class ExcelExportUtil {
 
       // 2. Android权限申请
       if (Platform.isAndroid) {
-        final permission = Permission.manageExternalStorage;
-        if (!await permission.isGranted) {
-          if (!await permission.request().isGranted) {
-            throw Exception('存储权限被拒绝,请前往设置开启');
+        final storagePermission = Permission.manageExternalStorage;
+
+        // 检查是否有管理外部存储权限
+        if (!await storagePermission.isGranted) {
+          // 尝试请求权限
+          var status = await storagePermission.request();
+          if (!status.isGranted) {
+            // 如果管理外部存储权限被拒绝,引导用户去设置页面
+            await openAppSettings();
+            throw Exception('存储权限被拒绝,请在设置中开启存储权限');
           }
         }
       }
@@ -127,23 +133,8 @@ class ExcelExportUtil {
 
       // 8. 处理保存路径(核心修改:创建自定义文件夹)
       // 8.1 获取下载目录
-      Directory? downloadDir = await getDownloadsDirectory();
-      if (downloadDir == null) {
-        if (Platform.isAndroid) {
-          // Android 下载目录
-          downloadDir = await getExternalStorageDirectory();
-          if (downloadDir != null) {
-            downloadDir = Directory(
-              '${downloadDir.parent.parent.parent.parent}/Download',
-            );
-          }
-        } else if (Platform.isIOS) {
-          // iOS 下载目录
-          downloadDir = await getApplicationSupportDirectory();
-          downloadDir = Directory('${downloadDir.path}/Download');
-        }
-      }
-      downloadDir ??= await getApplicationDocumentsDirectory();
+      final downloadDir = await _getSafeDownloadDirectory();
+
       // 8.2 拼接自定义文件夹路径并创建
       final customDir = Directory('${downloadDir.path}/$filePath');
       if (!await customDir.exists()) {
@@ -164,8 +155,71 @@ class ExcelExportUtil {
       return fullFilePath;
     } catch (e) {
       logger.d('导出失败:$e');
-      return "";
+      rethrow;
+    }
+  }
+
+  /// 安全地获取下载目录路径
+  static Future<Directory> _getSafeDownloadDirectory() async {
+    // 获取下载目录
+    Directory? downloadDir = await getDownloadsDirectory();
+
+    // if (downloadDir == null) {
+    //   logger.e("获取下载目录失败,换种方法重新获取...");
+    //   if (Platform.isAndroid) {
+    //     // Android 下载目录
+    //     downloadDir = await getExternalStorageDirectory();
+    //     logger.i("Android 根目录: ${downloadDir?.path}");
+    //     if (downloadDir != null) {
+    //       downloadDir = Directory(
+    //         '${downloadDir.parent.parent.parent.parent}/Download',
+    //       );
+    //       logger.i("Android 下载目录: ${downloadDir.path}");
+    //     }
+    //   } else if (Platform.isIOS) {
+    //     // iOS 下载目录
+    //     downloadDir = await getApplicationSupportDirectory();
+    //     downloadDir = Directory('${downloadDir.path}/Download');
+    //   }
+    // }
+
+    // 安全地处理 Android 下载目录路径
+    if (downloadDir != null) {
+      try {
+        // 安全地访问父目录层级
+        Directory? parentDir = downloadDir;
+        int levelsUp = 0;
+        const maxLevelsUp = 4; // 最多向上4级目录
+
+        // 尝试向上访问最多4级目录
+        while (parentDir != null && levelsUp < maxLevelsUp) {
+          if (parentDir.parent.path != parentDir.path) {
+            parentDir = parentDir.parent;
+            levelsUp++;
+          } else {
+            // 已经到达根目录,无法继续向上
+            break;
+          }
+        }
+
+        // 如果成功向上访问了足够的层级,则构造新的下载目录路径
+        if (levelsUp >= 4) {
+          downloadDir = Directory('${parentDir!.path}/Download');
+          logger.i("重构后的 Android 下载目录: ${downloadDir.path}");
+        } else {
+          // 向上访问层级不够,使用原始下载目录
+          logger.w("无法安全重构下载目录路径,使用原始下载目录: ${downloadDir.path}");
+        }
+      } catch (e) {
+        // 出现任何异常都回退到原始下载目录
+      }
     }
+
+    logger.i("下载目录: ${downloadDir?.path}");
+    downloadDir ??= await getApplicationDocumentsDirectory();
+    logger.i("下载目录2: ${downloadDir.path}");
+
+    return downloadDir;
   }
 
   /// 获取已注册的模板列表

+ 3 - 1
UI/CF.APP/chicken_farm/lib/main.dart

@@ -9,7 +9,9 @@ void main() async {
   WidgetsFlutterBinding.ensureInitialized();
   await AppConfig.init(); // 初始化配置
 
-  await SqliteManager().init(dbName: 'breeding.db');
+  if (AppConfig.isOffline) {
+    await SqliteManager().init(dbName: 'breeding.db');
+  }
 
   final container = ProviderContainer();
   // 初始化LoadingUtil

+ 27 - 2
UI/CF.APP/chicken_farm/lib/modes/api/result_model.dart

@@ -1,9 +1,9 @@
 class ResultModel {
   bool success;
-  String? message;
+  String message;
   dynamic data;
 
-  ResultModel({this.success = true, this.message, this.data});
+  ResultModel({this.success = true, this.message = "", this.data});
   ResultModel.success(this.message, {this.data}) : success = true;
   ResultModel.fail(this.message) : success = false;
 
@@ -12,3 +12,28 @@ class ResultModel {
       message = json['message'],
       data = json['data'];
 }
+
+class ApiOfflineModel extends ResultModel {
+  bool isOffline;
+  dynamic _data;
+
+  @override
+  dynamic get data => _data;
+
+  @override
+  set data(dynamic value) {
+    _data = value;
+  }
+
+  ApiOfflineModel({this.isOffline = true});
+  ApiOfflineModel.onLine(this._data, {this.isOffline = false})
+    : super(data: _data);
+
+  ResultModel convert() {
+    if (success) {
+      return ResultModel.success(isOffline ? "离线保存成功" : "保存成功", data: _data);
+    } else {
+      return ResultModel.fail(isOffline ? "离线保存失败:$message" : "保存失败:$message");
+    }
+  }
+}

+ 27 - 32
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page.dart

@@ -337,37 +337,32 @@ class _BatchCreatePageState extends State<BatchCreatePage> {
         'date': DateTimeUtil.format(DateTime.now()),
       };
     }).toList();
-    apis.breeding.submitApi
-        .bindChicken(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,
-              ),
-            );
-          }
-        });
+    apis.breeding.submitApi.bindChicken(data).then((res) {
+      if (res.success) {
+        if (res.message.isNotEmpty) {
+          ToastUtil.success(res.message);
+        }
+        if (mounted) {
+          ScaffoldMessenger.of(context).showSnackBar(
+            const SnackBar(
+              content: Text('批量绑定个体成功'),
+              backgroundColor: Colors.green,
+            ),
+          );
+          // 提交后重置表单
+          setState(() {
+            _rfids.clear();
+          });
+        }
+      } else {
+        ToastUtil.error('批量绑定个体失败');
+        if (mounted && res.message.isNotEmpty) {
+          logger.e(res.message);
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(content: Text(res.message), backgroundColor: Colors.red),
+          );
+        }
+      }
+    });
   }
 }

+ 27 - 32
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page_win.dart

@@ -242,37 +242,32 @@ class _BatchCreatePageWinState extends State<BatchCreatePageWin> {
     apis.breeding.submitApi.bindChicken(data);
     apis.breeding.submitApi.bindChicken(data);
     apis.breeding.submitApi.bindChicken(data);
-    apis.breeding.submitApi
-        .bindChicken(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,
-              ),
-            );
-          }
-        });
+    apis.breeding.submitApi.bindChicken(data).then((res) {
+      if (res.success) {
+        if (res.message.isNotEmpty) {
+          ToastUtil.success(res.message);
+        }
+        if (mounted) {
+          ScaffoldMessenger.of(context).showSnackBar(
+            const SnackBar(
+              content: Text('批量绑定个体成功'),
+              backgroundColor: Colors.green,
+            ),
+          );
+          // 提交后重置表单
+          setState(() {
+            _rfids.clear();
+          });
+        }
+      } else {
+        ToastUtil.error('批量绑定个体失败');
+        if (mounted && res.message.isNotEmpty) {
+          logger.e(res.message);
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(content: Text(res.message), backgroundColor: Colors.red),
+          );
+        }
+      }
+    });
   }
 }

+ 30 - 33
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page.dart

@@ -1,6 +1,7 @@
 import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/components/vb_rfid_field.dart';
 import 'package:chicken_farm/core/utils/datetime_util.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';
@@ -224,38 +225,34 @@ class _BatchCullingPageState extends State<BatchCullingPage> {
       'cullReason': _cullReason,
       'date': DateTimeUtil.format(DateTime.now()),
     };
-    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,
-              ),
-            );
-          }
-        });
+    apis.breeding.submitApi.weight(data).then((res) {
+      if (res.success) {
+        if (res.message.isNotEmpty) {
+          ToastUtil.success(res.message);
+        }
+        if (mounted) {
+          ScaffoldMessenger.of(context).showSnackBar(
+            const SnackBar(
+              content: Text('批量淘汰提交成功'),
+              backgroundColor: Colors.green,
+            ),
+          );
+          // 提交后重置表单
+          setState(() {
+            _rfids.clear();
+            // _cullReason = null;
+            // _disposalMethod = null;
+          });
+        }
+      } else {
+        ToastUtil.error('批量淘汰提交失败');
+        if (mounted && res.message.isNotEmpty) {
+          logger.e(res.message);
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(content: Text(res.message), backgroundColor: Colors.red),
+          );
+        }
+      }
+    });
   }
 }

+ 30 - 33
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page_win.dart

@@ -1,5 +1,6 @@
 import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/core/utils/datetime_util.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/components/vb_dict_select.dart';
@@ -207,38 +208,34 @@ class _BatchCullingPageWinState extends State<BatchCullingPageWin> {
       'cull_reason': _cullReason,
       'date': DateTimeUtil.format(DateTime.now()),
     };
-    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,
-              ),
-            );
-          }
-        });
+    apis.breeding.submitApi.weight(data).then((res) {
+      if (res.success) {
+        if (res.message.isNotEmpty) {
+          ToastUtil.success(res.message);
+        }
+        if (mounted) {
+          ScaffoldMessenger.of(context).showSnackBar(
+            const SnackBar(
+              content: Text('批量淘汰提交成功'),
+              backgroundColor: Colors.green,
+            ),
+          );
+          // 提交后重置表单
+          setState(() {
+            _rfids.clear();
+            // _cullReason = null;
+            // _disposalMethod = null;
+          });
+        }
+      } else {
+        ToastUtil.error('批量淘汰提交失败');
+        if (mounted && res.message.isNotEmpty) {
+          logger.e(res.message);
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(content: Text(res.message), backgroundColor: Colors.red),
+          );
+        }
+      }
+    });
   }
 }

+ 29 - 33
UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart

@@ -406,38 +406,34 @@ class _CageChangePageState extends State<CageChangePage> {
       'rfids': _rfids,
       'date': DateTimeUtil.format(DateTime.now()),
     };
-    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,
-              ),
-            );
-          }
-        });
+    apis.breeding.submitApi.cageChange(data).then((res) {
+      if (res.success) {
+        if (res.message.isNotEmpty) {
+          ToastUtil.success(res.message);
+        }
+        if (mounted) {
+          ScaffoldMessenger.of(context).showSnackBar(
+            const SnackBar(
+              content: Text('换笼提交成功'),
+              backgroundColor: Colors.green,
+            ),
+          );
+          // 提交后重置表单
+          setState(() {
+            _sourceCageController.clear();
+            _targetCageController.clear();
+            _rfids.clear();
+          });
+        }
+      } else {
+        ToastUtil.error('换笼提交失败');
+        if (mounted && res.message.isNotEmpty) {
+          logger.e(res.message);
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(content: Text(res.message), backgroundColor: Colors.red),
+          );
+        }
+      }
+    });
   }
 }

+ 30 - 33
UI/CF.APP/chicken_farm/lib/pages/breeding/individual_culling_page.dart

@@ -1,5 +1,6 @@
 import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/core/utils/datetime_util.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/components/vb_dict_select.dart';
@@ -142,38 +143,34 @@ class _IndividualCullingPageState extends State<IndividualCullingPage> {
       "disposalMethod": _disposalMethod,
       'date': DateTimeUtil.format(DateTime.now()),
     };
-    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,
-              ),
-            );
-          }
-        });
+    apis.breeding.submitApi.cull(data).then((res) {
+      if (res.success) {
+        if (res.message.isNotEmpty) {
+          ToastUtil.success(res.message);
+        }
+        if (mounted) {
+          ScaffoldMessenger.of(context).showSnackBar(
+            const SnackBar(
+              content: Text('淘汰提交成功'),
+              backgroundColor: Colors.green,
+            ),
+          );
+          // 提交后重置表单
+          setState(() {
+            _rfid = null;
+            // _cullReason = null;
+            // _disposalMethod = null;
+          });
+        }
+      } else {
+        ToastUtil.error('淘汰提交失败');
+        if (mounted && res.message.isNotEmpty) {
+          logger.e(res.message);
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(content: Text(res.message), backgroundColor: Colors.red),
+          );
+        }
+      }
+    });
   }
 }

+ 29 - 33
UI/CF.APP/chicken_farm/lib/pages/breeding/individual_weighing_page.dart

@@ -222,38 +222,34 @@ class _IndividualWeighingPageState extends State<IndividualWeighingPage> {
       "weight": _weight,
       'date': DateTimeUtil.format(DateTime.now()),
     };
-    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,
-              ),
-            );
-          }
-        });
+    apis.breeding.submitApi.weight(data).then((res) {
+      if (res.success) {
+        if (res.message.isNotEmpty) {
+          ToastUtil.success(res.message);
+        }
+        if (mounted) {
+          ScaffoldMessenger.of(context).showSnackBar(
+            const SnackBar(
+              content: Text('称重提交成功'),
+              backgroundColor: Colors.green,
+            ),
+          );
+          // 提交后重置表单
+          setState(() {
+            _rfid = null;
+            _weight = null;
+            _weightController.clear();
+          });
+        }
+      } else {
+        ToastUtil.error('称重提交失败');
+        if (mounted && res.message.isNotEmpty) {
+          logger.e(res.message);
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(content: Text(res.message), backgroundColor: Colors.red),
+          );
+        }
+      }
+    });
   }
 }

+ 11 - 3
UI/CF.APP/chicken_farm/lib/pages/checkin/checkin_record_page.dart

@@ -82,9 +82,17 @@ class _CheckinRecordPageState extends State<CheckinRecordPage> {
                 ),
               });
 
-              await InspectionRuleApi().checkInWithPhoto(widget.id, formData);
-              if (mounted) {
-                _loadData(); // 刷新数据
+              final result = await InspectionRuleApi().checkInWithPhoto(
+                widget.id,
+                formData,
+              );
+              if (result.success) {
+                ToastUtil.success('签到成功!');
+                if (mounted) {
+                  _loadData(); // 刷新数据
+                }
+              } else {
+                ToastUtil.error('签到失败!');
               }
             } catch (e) {
               logger.e('签到失败: $e');

+ 210 - 57
UI/CF.APP/chicken_farm/lib/pages/excel/export_data_page.dart

@@ -1,6 +1,8 @@
+import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/core/config/breed_config.dart';
 import 'package:chicken_farm/core/services/breeding_data_service.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/core/utils/toast.dart';
 
@@ -12,44 +14,75 @@ class ExcelExportPage extends StatefulWidget {
 }
 
 class _ExcelExportPageState extends State<ExcelExportPage> {
-  bool _exporting = false;
+  String? _exportingType;
+  final BreedingDataService _breedingDataService = BreedingDataService();
 
-  // 导出选项
-  final List<Map<String, dynamic>> _exportOptions = [
-    {'type': 'all', 'name': '全部数据', 'description': '导出所有类型的养殖数据'},
-    {'type': BreedConfig.chicken, 'name': null, 'description': '导出个体绑定数据'},
-    {'type': BreedConfig.cageChange, 'name': null, 'description': '导出换笼记录数据'},
-    {'type': BreedConfig.weight, 'name': null, 'description': '导出称重记录数据'},
-    {'type': BreedConfig.cull, 'name': null, 'description': '导出淘汰记录数据'},
-  ];
+  // 导出选项状态管理
+  late Future<List<Map<String, dynamic>>> _exportOptionsFuture;
 
-  Future<void> _exportData(String type) async {
+  @override
+  void initState() {
+    super.initState();
+    _insetTestData();
+    _refreshExportOptions();
+  }
+
+  void _refreshExportOptions() {
     setState(() {
-      _exporting = true;
+      _exportOptionsFuture = _loadExportOptions();
     });
+  }
 
-    try {
-      // String filePath;
-      // if (type == 'all') {
-      //   filePath = await BreedingDataService().exportAllDataToExcel();
-      // } else {
-      //   filePath = await BreedingDataService().exportDataToExcelByType(type);
-      // }
+  Future<List<Map<String, dynamic>>> _loadExportOptions() async {
+    final List<Map<String, dynamic>> exportOptions = [];
 
-      // ToastUtil.success('导出成功: $filePath');
+    // 动态添加各类型选项,并查询对应数量
+    final List<String> types = [
+      BreedConfig.chicken,
+      BreedConfig.cageChange,
+      BreedConfig.weight,
+      BreedConfig.cull,
+    ];
+    int allCount = 0;
+    for (final type in types) {
+      final count = await _breedingDataService.queryCount('b_$type');
+      allCount += count;
+      exportOptions.add({
+        'type': type,
+        'name': BreedConfig.getName(type),
+        'description': '导出${BreedConfig.getName(type)}数据',
+        'count': count,
+      });
+    }
+    exportOptions.add({
+      'type': 'all',
+      'name': '全部数据',
+      'description': '导出所有类型的养殖数据',
+      'count': allCount,
+    });
+
+    return exportOptions;
+  }
+
+  Future<void> _exportData(String type) async {
+    setState(() {
+      _exportingType = type;
+    });
 
-      // // 尝试打开文件
-      // final result = await OpenFile.open(filePath);
-      // if (result.type != ResultType.done) {
-      //   if (mounted) {
-      //     ScaffoldMessenger.of(
-      //       context,
-      //     ).showSnackBar(SnackBar(content: Text('文件已保存至: $filePath')));
-      //   }
-      // }
-      final result = await BreedingDataService().exportData(type);
+    try {
+      final result = await _breedingDataService.exportData(type);
       if (result.success) {
-        ToastUtil.success('导出成功,保存在${result.message}');
+        ToastUtil.success('导出成功');
+        if (mounted) {
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(
+              content: Text('数据已保存到目录:${result.message}'),
+              backgroundColor: Colors.green,
+            ),
+          );
+        }
+        // 导出成功后刷新选项
+        _refreshExportOptions();
       } else {
         ToastUtil.error('导出失败: ${result.message}');
       }
@@ -58,12 +91,87 @@ class _ExcelExportPageState extends State<ExcelExportPage> {
     } finally {
       if (mounted) {
         setState(() {
-          _exporting = false;
+          _exportingType = null;
         });
       }
     }
   }
 
+  Future<void> _insetTestData() async {
+    for (int i = 0; i < 2; i++) {
+      apis.breeding.submitApi
+          .bindChicken({
+            'rfid': 'test_$i',
+            'batchNum': '666',
+            'familyId': '888',
+            'date': '2025-12-11',
+          })
+          .then(
+            (r) => {
+              if (r.success)
+                {logger.d('submitApi bindChicken: success')}
+              else
+                {logger.d('submitApi bindChicken: fail $i ${r.message}')},
+            },
+          );
+      apis.breeding.submitApi
+          .cageChange({
+            'rfids': ['test_1$i', 'test_2$i', 'test_3$i'],
+            'sourceCage': '12333',
+            'targetCage': '23333',
+            'date': '2025-12-11',
+          })
+          .then(
+            (r) => {
+              if (r.success)
+                {logger.d('submitApi cageChange: success')}
+              else
+                {logger.d('submitApi cageChange: fail $i ${r.message}')},
+            },
+          );
+      apis.breeding.submitApi
+          .weight({'rfid': 'test_$i', 'weight': '1.23', 'date': '2025-12-11'})
+          .then(
+            (r) => {
+              if (r.success)
+                {logger.d('submitApi weight: success')}
+              else
+                {logger.d('submitApi weight: fail $i ${r.message}')},
+            },
+          );
+      apis.breeding.submitApi
+          .cull({
+            'rfid': 'test_$i',
+            'cullReason': '1',
+            'disposalMethod': '1',
+            'date': '2025-12-11',
+          })
+          .then(
+            (r) => {
+              if (r.success)
+                {logger.d('submitApi cull: success')}
+              else
+                {logger.d('submitApi cull: fail $i ${r.message}')},
+            },
+          );
+      apis.breeding.submitApi
+          .batchCull({
+            'rfids': ['test_1$i', 'test_2$i', 'test_3$i'],
+            'cullReason': '1',
+            'disposalMethod': '1',
+            'date': '2025-12-11',
+          })
+          .then(
+            (r) => {
+              if (r.success)
+                {logger.d('submitApi batchCull: success')}
+              else
+                {logger.d('submitApi batchCull: fail $i ${r.message}')},
+            },
+          );
+    }
+  }
+
   @override
   Widget build(BuildContext context) {
     return Scaffold(
@@ -81,34 +189,79 @@ class _ExcelExportPageState extends State<ExcelExportPage> {
             ),
             const SizedBox(height: 30),
             Expanded(
-              child: ListView.builder(
-                itemCount: _exportOptions.length,
-                itemBuilder: (context, index) {
-                  final option = _exportOptions[index];
-                  return Card(
-                    margin: const EdgeInsets.only(
-                      bottom: 20,
-                      left: 12.0,
-                      right: 12.0,
-                    ),
-                    child: ListTile(
-                      title: Text(
-                        option['name'] ?? BreedConfig.getName(option['type']),
-                        style: TextStyle(
-                          fontSize: 18,
-                          fontWeight: FontWeight.bold,
-                        ),
+              child: FutureBuilder<List<Map<String, dynamic>>>(
+                future: _exportOptionsFuture,
+                builder: (context, snapshot) {
+                  if (snapshot.connectionState == ConnectionState.waiting) {
+                    return const Center(child: CircularProgressIndicator());
+                  } else if (snapshot.hasError) {
+                    return Center(
+                      child: Column(
+                        mainAxisAlignment: MainAxisAlignment.center,
+                        children: [
+                          const Text('加载导出选项失败'),
+                          ElevatedButton(
+                            onPressed: _refreshExportOptions,
+                            child: const Text('重新加载'),
+                          ),
+                        ],
                       ),
-                      subtitle: Text(option['description']),
-                      trailing: _exporting
-                          ? const CircularProgressIndicator()
-                          : IconButton(
-                              icon: const Icon(Icons.download),
-                              onPressed: _exporting
-                                  ? null
-                                  : () => _exportData(option['type']),
+                    );
+                  } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
+                    return const Center(child: Text('暂无导出选项'));
+                  }
+
+                  final options = snapshot.data!;
+                  return ListView.builder(
+                    itemCount: options.length,
+                    itemBuilder: (context, index) {
+                      final option = options[index];
+                      final isCurrentExporting =
+                          _exportingType == option['type'];
+                      final dataCount = option['count'] as int? ?? 0;
+
+                      return Card(
+                        margin: const EdgeInsets.only(
+                          bottom: 20,
+                          left: 12.0,
+                          right: 12.0,
+                        ),
+                        child: ListTile(
+                          title: Text(
+                            option['name'],
+                            style: const TextStyle(
+                              fontSize: 18,
+                              fontWeight: FontWeight.bold,
                             ),
-                    ),
+                          ),
+                          subtitle: Column(
+                            crossAxisAlignment: CrossAxisAlignment.start,
+                            children: [
+                              Text(option['description']),
+                              if (dataCount > 0) Text('未导出数量: $dataCount'),
+                            ],
+                          ),
+                          trailing: isCurrentExporting
+                              ? const CircularProgressIndicator()
+                              : dataCount == 0
+                              ? const IconButton(
+                                  icon: Icon(Icons.download),
+                                  onPressed: null,
+                                )
+                              : IconButton(
+                                  icon: const Icon(Icons.download),
+                                  onPressed: _exportingType != null
+                                      ? null
+                                      : () => _exportData(option['type']),
+                                ),
+                          onTap: isCurrentExporting
+                              ? null
+                              : dataCount > 0
+                              ? () => _exportData(option['type'])
+                              : null,
+                        ),
+                      );
+                    },
                   );
                 },
               ),