فهرست منبع

Add 增加了离线模式的excel导出功能

Yue 4 روز پیش
والد
کامیت
c852d01364
29فایلهای تغییر یافته به همراه1029 افزوده شده و 22 حذف شده
  1. 91 6
      UI/CF.APP/chicken_farm/lib/apis/breeding/_submit.dart
  2. 3 3
      UI/CF.APP/chicken_farm/lib/core/config/app_config.dart
  3. 21 0
      UI/CF.APP/chicken_farm/lib/core/config/breed_config.dart
  4. 0 0
      UI/CF.APP/chicken_farm/lib/core/config/env/dev_config.dart
  5. 0 0
      UI/CF.APP/chicken_farm/lib/core/config/env/dev_offline_config.dart
  6. 0 0
      UI/CF.APP/chicken_farm/lib/core/config/env/prod_config.dart
  7. 0 0
      UI/CF.APP/chicken_farm/lib/core/config/env/prod_offline_config.dart
  8. 25 0
      UI/CF.APP/chicken_farm/lib/core/config/excel_header_map.dart
  9. 234 0
      UI/CF.APP/chicken_farm/lib/core/db/sqlite_manager.dart
  10. 97 0
      UI/CF.APP/chicken_farm/lib/core/db/table_config.dart
  11. 163 0
      UI/CF.APP/chicken_farm/lib/core/services/breeding_data_service.dart
  12. 174 0
      UI/CF.APP/chicken_farm/lib/core/utils/excel_export_util.dart
  13. 3 0
      UI/CF.APP/chicken_farm/lib/main.dart
  14. 14 0
      UI/CF.APP/chicken_farm/lib/modes/api/result_model.dart
  15. 8 2
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page.dart
  16. 6 4
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page_win.dart
  17. 4 2
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page.dart
  18. 2 0
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page_win.dart
  19. 4 2
      UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart
  20. 4 2
      UI/CF.APP/chicken_farm/lib/pages/breeding/individual_culling_page.dart
  21. 6 1
      UI/CF.APP/chicken_farm/lib/pages/breeding/individual_weighing_page.dart
  22. 121 0
      UI/CF.APP/chicken_farm/lib/pages/excel/export_data_page.dart
  23. 26 0
      UI/CF.APP/chicken_farm/lib/pages/home/_profile/export_data_button.dart
  24. 3 0
      UI/CF.APP/chicken_farm/lib/pages/home/profile.dart
  25. 7 0
      UI/CF.APP/chicken_farm/lib/routes/app_routes.dart
  26. 2 0
      UI/CF.APP/chicken_farm/macos/Flutter/GeneratedPluginRegistrant.swift
  27. 7 0
      UI/CF.APP/chicken_farm/pubspec.yaml
  28. 3 0
      UI/CF.APP/chicken_farm/windows/flutter/generated_plugin_registrant.cc
  29. 1 0
      UI/CF.APP/chicken_farm/windows/flutter/generated_plugins.cmake

+ 91 - 6
UI/CF.APP/chicken_farm/lib/apis/breeding/_submit.dart

@@ -1,5 +1,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';
 
 class BreedSubmitApi {
   static final BreedSubmitApi _instance = BreedSubmitApi._internal();
@@ -8,15 +10,35 @@ class BreedSubmitApi {
 
   BreedSubmitApi._internal();
 
-  Future<dynamic> create(dynamic data) async {
-    return (await ApiService().postWithOfflineSupport(
-      '/app/breeding/create/',
-      data: data,
-      apiOption: ApiOption.noAlert(),
-    )).check();
+  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<dynamic> 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': '数据已保存到本地'};
   }
 
   Future<dynamic> cageChange(dynamic data) async {
+    if (AppConfig.isOffline) {
+      return await cageChangeOffline(data);
+    }
     return (await ApiService().postWithOfflineSupport(
       '/app/breeding/cageChange/',
       data: data,
@@ -24,7 +46,23 @@ class BreedSubmitApi {
     )).check();
   }
 
+  Future<dynamic> 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': '数据已保存到本地'};
+  }
+
   Future<dynamic> weight(dynamic data) async {
+    if (AppConfig.isOffline) {
+      return await weightOffline(data);
+    }
     return (await ApiService().postWithOfflineSupport(
       '/app/breeding/weight/',
       data: data,
@@ -32,7 +70,23 @@ class BreedSubmitApi {
     )).check();
   }
 
+  Future<dynamic> weightOffline(dynamic data) async {
+    // 保存到本地数据库
+    Map<String, dynamic> cullData = {
+      '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': '数据已保存到本地'};
+  }
+
   Future<dynamic> cull(dynamic data) async {
+    if (AppConfig.isOffline) {
+      return await cullOffline(data);
+    }
     return (await ApiService().postWithOfflineSupport(
       '/app/breeding/cull/',
       data: data,
@@ -40,11 +94,42 @@ class BreedSubmitApi {
     )).check();
   }
 
+  Future<dynamic> cullOffline(dynamic data) async {
+    // 保存到本地数据库
+    Map<String, dynamic> cullData = {
+      'rfid': data['rfid'],
+      'cull_reason': data['cullReason'],
+      'disposal_method': data['disposalMethod'],
+      'date': data['date'],
+      'is_export': 0,
+    };
+    await BreedingDataService().insertCull(cullData);
+    return {'success': true, 'message': '数据已保存到本地'};
+  }
+
   Future<dynamic> batchCull(dynamic data) async {
+    if (AppConfig.isOffline) {
+      return await batchCullOffline(data);
+    }
     return (await ApiService().postWithOfflineSupport(
       '/app/breeding/batchCull/',
       data: data,
       apiOption: ApiOption.noAlert(),
     )).check();
   }
+
+  Future<dynamic> batchCullOffline(dynamic data) async {
+    List<String> ids = data['rfids'];
+    List<Map<String, dynamic>> batchCullData = ids.map((id) {
+      return {
+        'rfid': id,
+        'cull_time': data['cull_time'],
+        'cull_reason': data['cull_reason'],
+        'date': data['date'],
+        'is_export': 0,
+      };
+    }).toList();
+    await BreedingDataService().batchInsertCull(batchCullData);
+    return {'success': true, 'message': '数据已保存到本地'};
+  }
 }

+ 3 - 3
UI/CF.APP/chicken_farm/lib/core/config/app_config.dart

@@ -1,6 +1,6 @@
-// import 'package:chicken_farm/core/config/dev_config.dart';
-import 'package:chicken_farm/core/config/dev_offline_config.dart';
-// import 'package:chicken_farm/core/config/prod_config.dart';
+// import 'package:chicken_farm/core/config/env/dev_config.dart';
+import 'package:chicken_farm/core/config/env/dev_offline_config.dart';
+// import 'package:chicken_farm/core/config/env/prod_config.dart';
 import 'package:chicken_farm/core/utils/storage.dart';
 
 class AppConfig {

+ 21 - 0
UI/CF.APP/chicken_farm/lib/core/config/breed_config.dart

@@ -0,0 +1,21 @@
+class BreedConfig {
+  static const String chicken = 'chicken';
+  static const String weight = 'weight';
+  static const String cageChange = 'cage_change';
+  static const String cull = 'cull';
+
+  static String getName(String templateKey) {
+    switch (templateKey) {
+      case BreedConfig.chicken:
+        return '个体绑定';
+      case BreedConfig.cageChange:
+        return '换笼记录';
+      case BreedConfig.weight:
+        return '称重记录';
+      case BreedConfig.cull:
+        return '淘汰记录';
+      default:
+        return templateKey;
+    }
+  }
+}

+ 0 - 0
UI/CF.APP/chicken_farm/lib/core/config/dev_config.dart → UI/CF.APP/chicken_farm/lib/core/config/env/dev_config.dart


+ 0 - 0
UI/CF.APP/chicken_farm/lib/core/config/dev_offline_config.dart → UI/CF.APP/chicken_farm/lib/core/config/env/dev_offline_config.dart


+ 0 - 0
UI/CF.APP/chicken_farm/lib/core/config/prod_config.dart → UI/CF.APP/chicken_farm/lib/core/config/env/prod_config.dart


+ 0 - 0
UI/CF.APP/chicken_farm/lib/core/config/prod_offline_config.dart → UI/CF.APP/chicken_farm/lib/core/config/env/prod_offline_config.dart


+ 25 - 0
UI/CF.APP/chicken_farm/lib/core/config/excel_header_map.dart

@@ -0,0 +1,25 @@
+import 'package:chicken_farm/core/config/breed_config.dart';
+
+class ExcelHeaderMap {
+  static final Map<String, Map<String, String>> templates = {
+    BreedConfig.chicken: {
+      '批次号': 'batch_num',
+      '家系号': 'family_id',
+      '电子编号': 'rfid',
+      '创建时间': 'date',
+    },
+    BreedConfig.cageChange: {
+      '源笼号': 'source_cage',
+      '目标笼号': 'target_cage',
+      '电子编号': 'rfid',
+      '创建时间': 'date',
+    },
+    BreedConfig.weight: {'体重': 'weight', '电子编号': 'rfid', '创建时间': 'date'},
+    BreedConfig.cull: {
+      '淘汰原因': 'cull_reason',
+      '处置方式': 'disposal_method',
+      '电子编号': 'rfid',
+      '创建时间': 'date',
+    },
+  };
+}

+ 234 - 0
UI/CF.APP/chicken_farm/lib/core/db/sqlite_manager.dart

@@ -0,0 +1,234 @@
+import 'dart:async';
+import 'dart:io';
+import 'package:chicken_farm/core/db/table_config.dart';
+import 'package:chicken_farm/core/utils/datetime_util.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
+import 'package:path/path.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:path_provider/path_provider.dart';
+
+class SqliteManager {
+  // 单例模式
+  static final SqliteManager _instance = SqliteManager._internal();
+  factory SqliteManager() => _instance;
+  SqliteManager._internal();
+
+  // 核心配置
+  String? _dbName;
+  int _dbVersion = 1;
+  final List<TableModel> _tableConfigs = [];
+  Database? _database;
+
+  /// 初始化数据库(仅需传入数据库名和表配置)
+  /// [dbName] 数据库名称
+  /// [version] 数据库版本(默认1)
+  Future<void> init({required String dbName, int version = 1}) async {
+    _dbName = dbName;
+    _dbVersion = version;
+    if (TableConfig.defaultConfigs().isEmpty) {
+      throw Exception('请传入表配置列表');
+    }
+    _tableConfigs.addAll(TableConfig.defaultConfigs());
+    logger.d('数据库配置初始化完成: $dbName (v$version)');
+  }
+
+  /// 内部方法:获取数据库连接(自动创建/升级)
+  Future<Database> _getDatabase() 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}');
+    // 2. 拼接自定义的 db 子文件夹路径
+    String dbFolderPath = join(documentsDir.path, 'db');
+    // 3. 检查并创建 db 文件夹(不存在则创建)
+    Directory dbFolder = Directory(dbFolderPath);
+    if (!await dbFolder.exists()) {
+      await dbFolder.create(recursive: true); // recursive: true 支持多级文件夹创建
+      logger.d('已创建 db 文件夹:$dbFolderPath');
+    }
+
+    // 4. 拼接最终的数据库文件路径(db文件夹 + 数据库名)
+    String dbPath = join(dbFolderPath, _dbName!);
+    logger.d('数据库文件路径:$dbPath');
+
+    // 打开/创建数据库
+    _database = await openDatabase(
+      dbPath,
+      version: _dbVersion,
+      onCreate: (db, version) async {
+        // 批量创建表
+        for (var config in _tableConfigs) {
+          await db.execute(config.createSql);
+        }
+        logger.d('数据库创建完成,表数量: ${_tableConfigs.length}');
+      },
+      onUpgrade: (db, oldVersion, newVersion) async {
+        // 执行表升级逻辑
+        for (var config in _tableConfigs) {
+          for (int v = oldVersion + 1; v <= newVersion; v++) {
+            if (config.upgradeSqls.containsKey(v)) {
+              await db.execute(config.upgradeSqls[v]!);
+              logger.d('表 ${config.tableName} 升级到版本 $v');
+            }
+          }
+        }
+      },
+    );
+
+    return _database!;
+  }
+
+  /// 内部方法:执行操作并自动关闭连接
+  Future<T> _executeWithAutoClose<T>(
+    Future<T> Function(Database db) action,
+  ) async {
+    // 获取连接
+    final db = await _getDatabase();
+
+    try {
+      // 执行核心操作
+      final result = await action(db);
+      return result;
+    } finally {
+      // 操作完成后自动关闭连接
+      if (db.isOpen) {
+        await db.close();
+        _database = null;
+      }
+    }
+  }
+
+  // ===================== 通用CRUD方法(自动管理连接) =====================
+
+  /// 插入数据
+  Future<int> insert({
+    required String table,
+    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);
+    });
+  }
+
+  /// 批量插入数据
+  Future<void> batchInsert({
+    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);
+    });
+  }
+
+  /// 查询数据
+  Future<List<Map<String, dynamic>>> query({
+    required String table,
+    List<String>? columns,
+    String? where,
+    List<dynamic>? whereArgs,
+    String? groupBy,
+    String? having,
+    String? orderBy,
+    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,
+      );
+    });
+  }
+
+  /// 更新数据
+  Future<int> update({
+    required String table,
+    required Map<String, dynamic> data,
+    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);
+    });
+  }
+
+  /// 删除数据
+  Future<int> delete({
+    required String table,
+    String? where,
+    List<dynamic>? whereArgs,
+  }) async {
+    return _executeWithAutoClose((db) async {
+      return await db.delete(table, where: where, whereArgs: whereArgs);
+    });
+  }
+
+  /// 执行原生SQL
+  Future<void> executeSql(String sql, [List<dynamic>? arguments]) async {
+    return _executeWithAutoClose((db) async {
+      await db.execute(sql, arguments);
+    });
+  }
+
+  /// 执行事务(事务内保持连接,事务结束后关闭)
+  Future<T> transaction<T>(Future<T> Function(Transaction txn) action) async {
+    return _executeWithAutoClose((db) async {
+      return await db.transaction<T>(action);
+    });
+  }
+
+  /// 手动关闭数据库(备用方法)
+  Future<void> closeDb() async {
+    if (_database != null && _database!.isOpen) {
+      await _database!.close();
+      _database = null;
+      logger.d('数据库已手动关闭');
+    }
+  }
+
+  /// 删除数据库文件
+  Future<void> deleteDb() async {
+    if (_dbName == null) {
+      throw Exception('请先调用 init 方法初始化数据库配置');
+    }
+    await closeDb();
+    Directory documentsDir = await getApplicationDocumentsDirectory();
+    String dbPath = join(documentsDir.path, _dbName!);
+    await deleteDatabase(dbPath);
+    logger.d('数据库文件已删除: $dbPath');
+  }
+
+  /// 清空表数据
+  Future<int> clearTable(String table) async {
+    return _executeWithAutoClose((db) async {
+      return await db.delete(table);
+    });
+  }
+}

+ 97 - 0
UI/CF.APP/chicken_farm/lib/core/db/table_config.dart

@@ -0,0 +1,97 @@
+import 'package:chicken_farm/core/config/breed_config.dart';
+
+class TableConfig {
+  static const String chicken = 'b_${BreedConfig.chicken}';
+  static const String weight = 'b_${BreedConfig.weight}';
+  static const String cageChange = 'b_${BreedConfig.cageChange}';
+  static const String cull = 'b_${BreedConfig.cull}';
+
+  static List<TableModel> defaultConfigs() {
+    return [
+      // 绑定鸡
+      TableModel(
+        tableName: TableConfig.chicken,
+        createSql:
+            '''
+          CREATE TABLE IF NOT EXISTS ${TableConfig.chicken} (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            rfid TEXT,
+            batch_num TEXT,
+            family_id TEXT,
+            is_export INTEGER,
+            date TEXT,
+            create_time TEXT,
+            update_time TEXT
+          )
+        ''',
+        upgradeSqls: {},
+      ),
+      // 称重
+      TableModel(
+        tableName: TableConfig.weight,
+        createSql:
+            '''
+          CREATE TABLE IF NOT EXISTS ${TableConfig.weight} (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            rfid TEXT,
+            weight TEXT,
+            is_export INTEGER,
+            date TEXT,
+            create_time TEXT,
+            update_time TEXT
+          )
+        ''',
+        upgradeSqls: {},
+      ),
+      // 换笼
+      TableModel(
+        tableName: TableConfig.cageChange,
+        createSql:
+            '''
+          CREATE TABLE IF NOT EXISTS ${TableConfig.cageChange} (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            rfid TEXT,
+            source_cage TEXT,
+            target_cage TEXT,
+            is_export INTEGER,
+            date TEXT,
+            create_time TEXT,
+            update_time TEXT
+          )
+        ''',
+        upgradeSqls: {},
+      ),
+      // 淘汰
+      TableModel(
+        tableName: TableConfig.cull,
+        createSql:
+            '''
+          CREATE TABLE IF NOT EXISTS ${TableConfig.cull} (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            rfid TEXT,
+            cull_reason TEXT,
+            disposal_method TEXT,
+            is_export INTEGER,
+            date TEXT,
+            create_time TEXT,
+            update_time TEXT
+          )
+        ''',
+        upgradeSqls: {},
+      ),
+    ];
+  }
+}
+
+// 表结构配置模型
+class TableModel {
+  final String tableName;
+  final String createSql;
+  final Map<int, String> upgradeSqls; // 版本号 -> 升级SQL
+
+  TableModel({
+    required this.tableName,
+    required this.createSql,
+    this.upgradeSqls = const {},
+  });
+}

+ 163 - 0
UI/CF.APP/chicken_farm/lib/core/services/breeding_data_service.dart

@@ -0,0 +1,163 @@
+import 'package:chicken_farm/core/config/breed_config.dart';
+import 'package:chicken_farm/core/db/sqlite_manager.dart';
+import 'package:chicken_farm/core/db/table_config.dart';
+import 'package:chicken_farm/core/utils/excel_export_util.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
+import 'package:chicken_farm/modes/api/result_model.dart';
+
+/// 离线养殖数据服务
+/// 用于存储和导出离线模式下用户提交的养殖数据
+class BreedingDataService {
+  static final BreedingDataService _instance = BreedingDataService._internal();
+
+  factory BreedingDataService() => _instance;
+
+  BreedingDataService._internal();
+
+  final SqliteManager _sqliteManager = SqliteManager();
+
+  Future<void> insertChicken(Map<String, dynamic> data) async {
+    _sqliteManager.insert(table: TableConfig.chicken, data: data);
+  }
+
+  Future<void> batchInsertChicken(List<Map<String, dynamic>> dataList) async {
+    _sqliteManager.batchInsert(table: TableConfig.chicken, dataList: dataList);
+  }
+
+  Future<void> insertCageChange(Map<String, dynamic> data) async {
+    _sqliteManager.insert(table: TableConfig.cageChange, data: data);
+  }
+
+  Future<void> insertWeight(Map<String, dynamic> data) async {
+    _sqliteManager.insert(table: TableConfig.weight, data: data);
+  }
+
+  Future<void> insertCull(Map<String, dynamic> data) async {
+    _sqliteManager.insert(table: TableConfig.cull, data: data);
+  }
+
+  Future<void> batchInsertCull(List<Map<String, dynamic>> dataList) async {
+    _sqliteManager.batchInsert(table: TableConfig.cull, dataList: dataList);
+  }
+
+  Future<List<Map<String, dynamic>>> queryAllChickenList() async {
+    final list = _sqliteManager.query(
+      table: TableConfig.chicken,
+      where: 'is_export = 0',
+    );
+    return list;
+  }
+
+  Future<List<Map<String, dynamic>>> queryAllCageChangeList() async {
+    final list = _sqliteManager.query(
+      table: TableConfig.cageChange,
+      where: 'is_export = 0',
+    );
+    return list;
+  }
+
+  Future<List<Map<String, dynamic>>> queryAllWeightList() async {
+    final list = _sqliteManager.query(
+      table: TableConfig.weight,
+      where: 'is_export = 0',
+    );
+    return list;
+  }
+
+  Future<List<Map<String, dynamic>>> queryAllCullList() async {
+    final list = _sqliteManager.query(
+      table: TableConfig.cull,
+      where: 'is_export = 0',
+    );
+    return list;
+  }
+
+  Future<ResultModel> exportData(String type) async {
+    String tableName = "";
+    String templateKey = "";
+    switch (type) {
+      case BreedConfig.chicken:
+        tableName = TableConfig.chicken;
+        templateKey = BreedConfig.chicken;
+        break;
+      case BreedConfig.cageChange:
+        tableName = TableConfig.cageChange;
+        templateKey = BreedConfig.cageChange;
+        break;
+      case BreedConfig.weight:
+        tableName = TableConfig.weight;
+        templateKey = BreedConfig.weight;
+        break;
+      case BreedConfig.cull:
+        tableName = TableConfig.cull;
+        templateKey = 'cull';
+        break;
+      case "all":
+        return await exportAll();
+    }
+    final result = ResultModel.fail("");
+    if (tableName.isEmpty || templateKey.isEmpty) {
+      result.message = "导出类型[$type]不支持";
+    }
+    String msg = "";
+    String typeName = BreedConfig.getName(templateKey);
+    try {
+      List<Map<String, dynamic>> listData = await _sqliteManager.query(
+        table: tableName,
+        where: 'is_export = 0',
+      );
+      if (listData.isNotEmpty) {
+        String path = await ExcelExportUtil.export(templateKey, listData);
+        if (path.isNotEmpty) {
+          int r = await _sqliteManager.update(
+            table: tableName,
+            data: {'is_export': 1},
+            where: 'is_export = 0',
+          );
+          if (r > 0) {
+            result.message = path;
+            result.success = true;
+          } else {
+            result.message = "$typeName没有新的数据";
+          }
+        } else {
+          result.message = "$typeName导出过程出错";
+        }
+      } else {
+        result.message = "$typeName没有需要导出的数据";
+      }
+    } catch (e) {
+      logger.e(e);
+      result.message = "$typeName导出异常";
+    }
+    logger.d(result.message);
+    return result;
+  }
+
+  Future<ResultModel> exportAll() async {
+    String errMsg = "";
+    String successMsg = "";
+
+    final List<String> exportTypes = [
+      BreedConfig.chicken,
+      BreedConfig.cageChange,
+      BreedConfig.weight,
+      BreedConfig.cull,
+    ];
+
+    // 遍历每种类型并执行导出操作
+    for (String type in exportTypes) {
+      ResultModel result = await exportData(type);
+
+      if (result.success) {
+        successMsg += "${successMsg.isEmpty ? "" : ";"}${result.message}";
+      } else {
+        errMsg += "${errMsg.isEmpty ? "" : ";"}${result.message}";
+      }
+    }
+
+    return errMsg.isEmpty
+        ? ResultModel.success(successMsg)
+        : ResultModel.fail(errMsg);
+  }
+}

+ 174 - 0
UI/CF.APP/chicken_farm/lib/core/utils/excel_export_util.dart

@@ -0,0 +1,174 @@
+import 'dart:io';
+import 'package:chicken_farm/core/config/breed_config.dart';
+import 'package:chicken_farm/core/config/excel_header_map.dart';
+import 'package:chicken_farm/core/utils/datetime_util.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
+import 'package:excel/excel.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:open_filex/open_filex.dart';
+
+/// Excel 导出工具类(仅支持 excel: ^4.0.0)
+class ExcelExportUtil {
+  /// 预配置表头模板
+  /// 格式:{模板key: {Excel列名: 数据字段名}}
+  static final Map<String, Map<String, String>> _headerTemplates =
+      ExcelHeaderMap.templates;
+
+  /// 注册自定义表头模板
+  static void registerHeaderTemplate(
+    String templateKey,
+    Map<String, String> headerMap,
+  ) {
+    if (templateKey.isEmpty || headerMap.isEmpty) {
+      throw ArgumentError('模板key和表头映射不能为空');
+    }
+    _headerTemplates[templateKey] = headerMap;
+  }
+
+  static Future<String> export(
+    String templateKey,
+    List<Map<String, dynamic>> dataList,
+  ) async {
+    return await exportExcel(
+      templateKey: templateKey,
+      dataList: dataList,
+      fileName:
+          '${BreedConfig.getName(templateKey)}_${DateTimeUtil.format(DateTime.now(), pattern: "yyyyMMddHHmmss")}',
+      filePath:
+          'excels/${DateTimeUtil.format(DateTime.now(), pattern: "yyyy-MM/yyyy-MM-dd")}',
+    );
+  }
+
+  /// 核心导出方法
+  /// [dataList] 待导出数据(List<Map>)
+  /// [templateKey] 表头模板key
+  /// [fileName] 导出文件名(无需.xlsx后缀)
+  /// [sheetName] 工作表名称(默认Sheet1)
+  static Future<String> exportExcel({
+    required String templateKey,
+    required List<Map<String, dynamic>> dataList,
+    required String fileName,
+    String filePath = "excel",
+    String sheetName = 'Sheet1',
+  }) async {
+    try {
+      // 1. 参数校验
+      if (dataList.isEmpty) throw Exception('导出数据不能为空');
+      if (!_headerTemplates.containsKey(templateKey)) {
+        throw Exception('模板[$templateKey]未注册');
+      }
+      if (fileName.isEmpty) throw Exception('文件名不能为空');
+
+      // 2. Android权限申请
+      if (Platform.isAndroid) {
+        final permission = Permission.manageExternalStorage;
+        if (!await permission.isGranted) {
+          if (!await permission.request().isGranted) {
+            throw Exception('存储权限被拒绝,请前往设置开启');
+          }
+        }
+      }
+
+      // 3. 获取表头和字段映射
+      final headerMap = _headerTemplates[templateKey]!;
+      final titles = headerMap.keys.toList();
+      final fields = headerMap.values.toList();
+
+      // 4. 创建Excel和工作表
+      final excel = Excel.createExcel();
+      final sheet = excel[sheetName];
+
+      // 5. 写入表头(excel 4.0.0 专用API)
+      for (int col = 0; col < titles.length; col++) {
+        final cell = sheet.cell(
+          CellIndex.indexByColumnRow(columnIndex: col, rowIndex: 0),
+        );
+        cell.value = TextCellValue(titles[col]);
+        cell.cellStyle = CellStyle(
+          bold: true,
+          fontSize: 14,
+          backgroundColorHex: ExcelColor.fromHexString('#4285F4'),
+          fontColorHex: ExcelColor.white,
+        );
+      }
+
+      // 6. 写入数据行
+      for (int row = 0; row < dataList.length; row++) {
+        final data = dataList[row];
+        for (int col = 0; col < fields.length; col++) {
+          final field = fields[col];
+          dynamic value = data[field] ?? '';
+
+          final cell = sheet.cell(
+            CellIndex.indexByColumnRow(columnIndex: col, rowIndex: row + 1),
+          );
+          // 数据格式化
+          if (value is DateTime) {
+            value = value.toString().substring(0, 19);
+          } else if (value is bool) {
+            value = value ? '是' : '否';
+          } else if (value is num) {
+            value = value.toString();
+          }
+
+          cell.value = TextCellValue(value.toString());
+          cell.cellStyle = CellStyle(
+            fontSize: 12,
+            fontColorHex: ExcelColor.fromHexString('#333333'),
+          );
+        }
+      }
+
+      // 7. 调整列宽
+      for (int col = 0; col < titles.length; col++) {
+        sheet.setColumnWidth(col, 22);
+      }
+
+      // 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();
+      // 8.2 拼接自定义文件夹路径并创建
+      final customDir = Directory('${downloadDir.path}/$filePath');
+      if (!await customDir.exists()) {
+        await customDir.create(recursive: true);
+        logger.d('自定义文件夹创建成功:${customDir.path}');
+      }
+
+      // 8.3 处理文件名(过滤特殊字符)
+      final safeFileName = fileName.replaceAll(RegExp(r'[\/:*?"<>|]'), '_');
+      final fullFilePath = '${customDir.path}/$safeFileName.xlsx';
+
+      // 8.4 保存文件
+      await File(fullFilePath).writeAsBytes(excel.save()!);
+
+      // 9. 打开文件
+      await OpenFilex.open(fullFilePath);
+      logger.d('导出成功:$fullFilePath');
+      return fullFilePath;
+    } catch (e) {
+      logger.d('导出失败:$e');
+      return "";
+    }
+  }
+
+  /// 获取已注册的模板列表
+  static List<String> getRegisteredTemplates() =>
+      _headerTemplates.keys.toList();
+}

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

@@ -1,3 +1,4 @@
+import 'package:chicken_farm/core/db/sqlite_manager.dart';
 import 'package:chicken_farm/vb_app.dart';
 import 'package:chicken_farm/core/config/app_config.dart';
 import 'package:chicken_farm/core/utils/loading.dart';
@@ -8,6 +9,8 @@ void main() async {
   WidgetsFlutterBinding.ensureInitialized();
   await AppConfig.init(); // 初始化配置
 
+  await SqliteManager().init(dbName: 'breeding.db');
+
   final container = ProviderContainer();
   // 初始化LoadingUtil
   LoadingUtil.init(container);

+ 14 - 0
UI/CF.APP/chicken_farm/lib/modes/api/result_model.dart

@@ -0,0 +1,14 @@
+class ResultModel {
+  bool success;
+  String? message;
+  dynamic data;
+
+  ResultModel({this.success = true, this.message, this.data});
+  ResultModel.success(this.message, {this.data}) : success = true;
+  ResultModel.fail(this.message) : success = false;
+
+  ResultModel.fromJson(Map<String, dynamic> json)
+    : success = json['success'],
+      message = json['message'],
+      data = json['data'];
+}

+ 8 - 2
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page.dart

@@ -1,4 +1,5 @@
 import 'package:chicken_farm/core/config/app_config.dart';
+import 'package:chicken_farm/core/utils/datetime_util.dart';
 import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/components/vb_rfid_field.dart';
@@ -329,10 +330,15 @@ class _BatchCreatePageState extends State<BatchCreatePage> {
   // 提交数据
   void _handleSubmit() {
     final data = _rfids.map((rfid) {
-      return {'rfid': rfid, 'batchNum': _batchNum, 'familyId': _familyId};
+      return {
+        'rfid': rfid,
+        'batchNum': _batchNum,
+        'familyId': _familyId,
+        'date': DateTimeUtil.format(DateTime.now()),
+      };
     }).toList();
     apis.breeding.submitApi
-        .create(data)
+        .bindChicken(data)
         .then((_) {
           if (mounted) {
             ScaffoldMessenger.of(context).showSnackBar(

+ 6 - 4
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page_win.dart

@@ -1,3 +1,4 @@
+import 'package:chicken_farm/core/utils/datetime_util.dart';
 import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/components/vb_search_select.dart';
@@ -235,13 +236,14 @@ class _BatchCreatePageWinState extends State<BatchCreatePageWin> {
         'rfid': rfid,
         'batchNum': _selectedBatchNum,
         'familyId': _selectedFamilyId,
+        'date': DateTimeUtil.format(DateTime.now()),
       };
     }).toList();
-    apis.breeding.submitApi.create(data);
-    apis.breeding.submitApi.create(data);
-    apis.breeding.submitApi.create(data);
+    apis.breeding.submitApi.bindChicken(data);
+    apis.breeding.submitApi.bindChicken(data);
+    apis.breeding.submitApi.bindChicken(data);
     apis.breeding.submitApi
-        .create(data)
+        .bindChicken(data)
         .then((_) {
           if (mounted) {
             ScaffoldMessenger.of(context).showSnackBar(

+ 4 - 2
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page.dart

@@ -1,5 +1,6 @@
 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/modes/rfid/rfid_model.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
@@ -219,8 +220,9 @@ class _BatchCullingPageState extends State<BatchCullingPage> {
   void _handleSubmit() {
     final data = {
       'rfids': _rfids,
-      'disposal_method': _disposalMethod,
-      'cull_reason': _cullReason,
+      'disposalMethod': _disposalMethod,
+      'cullReason': _cullReason,
+      'date': DateTimeUtil.format(DateTime.now()),
     };
     apis.breeding.submitApi
         .weight(data)

+ 2 - 0
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page_win.dart

@@ -1,4 +1,5 @@
 import 'package:chicken_farm/apis/index.dart';
+import 'package:chicken_farm/core/utils/datetime_util.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/components/vb_dict_select.dart';
@@ -204,6 +205,7 @@ class _BatchCullingPageWinState extends State<BatchCullingPageWin> {
       'rfids': _rfids,
       'disposal_method': _disposalMethod,
       'cull_reason': _cullReason,
+      'date': DateTimeUtil.format(DateTime.now()),
     };
     apis.breeding.submitApi
         .weight(data)

+ 4 - 2
UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart

@@ -1,4 +1,5 @@
 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';
@@ -400,9 +401,10 @@ class _CageChangePageState extends State<CageChangePage> {
   // 提交数据
   void _handleSubmit() {
     final data = {
-      'source_cage': _sourceCageController.text,
-      'target_cage': _targetCageController.text,
+      'sourceCage': _sourceCageController.text,
+      'targetCage': _targetCageController.text,
       'rfids': _rfids,
+      'date': DateTimeUtil.format(DateTime.now()),
     };
     apis.breeding.submitApi
         .cageChange(data)

+ 4 - 2
UI/CF.APP/chicken_farm/lib/pages/breeding/individual_culling_page.dart

@@ -1,4 +1,5 @@
 import 'package:chicken_farm/apis/index.dart';
+import 'package:chicken_farm/core/utils/datetime_util.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/components/vb_dict_select.dart';
@@ -137,8 +138,9 @@ class _IndividualCullingPageState extends State<IndividualCullingPage> {
   void _handleSubmit() {
     final data = {
       "rfid": _rfid,
-      "cull_reason": _cullReason,
-      "disposal_method": _disposalMethod,
+      "cullReason": _cullReason,
+      "disposalMethod": _disposalMethod,
+      'date': DateTimeUtil.format(DateTime.now()),
     };
     apis.breeding.submitApi
         .cull(data)

+ 6 - 1
UI/CF.APP/chicken_farm/lib/pages/breeding/individual_weighing_page.dart

@@ -1,4 +1,5 @@
 import 'package:chicken_farm/apis/index.dart';
+import 'package:chicken_farm/core/utils/datetime_util.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/core/utils/toast.dart';
@@ -216,7 +217,11 @@ class _IndividualWeighingPageState extends State<IndividualWeighingPage> {
 
   // 提交数据
   void _handleSubmit() {
-    final data = {"rfid": _rfid, "weight": _weight};
+    final data = {
+      "rfid": _rfid,
+      "weight": _weight,
+      'date': DateTimeUtil.format(DateTime.now()),
+    };
     apis.breeding.submitApi
         .weight(data)
         .then((_) {

+ 121 - 0
UI/CF.APP/chicken_farm/lib/pages/excel/export_data_page.dart

@@ -0,0 +1,121 @@
+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:flutter/material.dart';
+import 'package:chicken_farm/core/utils/toast.dart';
+
+class ExcelExportPage extends StatefulWidget {
+  const ExcelExportPage({super.key});
+
+  @override
+  State<ExcelExportPage> createState() => _ExcelExportPageState();
+}
+
+class _ExcelExportPageState extends State<ExcelExportPage> {
+  bool _exporting = false;
+
+  // 导出选项
+  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': '导出淘汰记录数据'},
+  ];
+
+  Future<void> _exportData(String type) async {
+    setState(() {
+      _exporting = true;
+    });
+
+    try {
+      // String filePath;
+      // if (type == 'all') {
+      //   filePath = await BreedingDataService().exportAllDataToExcel();
+      // } else {
+      //   filePath = await BreedingDataService().exportDataToExcelByType(type);
+      // }
+
+      // ToastUtil.success('导出成功: $filePath');
+
+      // // 尝试打开文件
+      // 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);
+      if (result.success) {
+        ToastUtil.success('导出成功,保存在${result.message}');
+      } else {
+        ToastUtil.error('导出失败: ${result.message}');
+      }
+    } catch (e) {
+      ToastUtil.error('导出失败: ${e.toString()}');
+    } finally {
+      if (mounted) {
+        setState(() {
+          _exporting = false;
+        });
+      }
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: VberAppBar(title: '导出数据'),
+      body: Padding(
+        padding: const EdgeInsets.all(16.0),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            const Center(
+              child: Text(
+                '选择导出类型',
+                style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
+              ),
+            ),
+            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,
+                        ),
+                      ),
+                      subtitle: Text(option['description']),
+                      trailing: _exporting
+                          ? const CircularProgressIndicator()
+                          : IconButton(
+                              icon: const Icon(Icons.download),
+                              onPressed: _exporting
+                                  ? null
+                                  : () => _exportData(option['type']),
+                            ),
+                    ),
+                  );
+                },
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 26 - 0
UI/CF.APP/chicken_farm/lib/pages/home/_profile/export_data_button.dart

@@ -0,0 +1,26 @@
+import 'package:chicken_farm/routes/app_routes.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class ExportDataButton extends StatelessWidget {
+  const ExportDataButton({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return ElevatedButton.icon(
+      onPressed: () async {
+        // 跳转到导出数据页面
+        if (context.mounted) {
+          context.pushNamed(AppRouteNames.excelExport);
+        }
+      },
+      icon: const Icon(Icons.download),
+      label: const Text('导出数据'),
+      style: ElevatedButton.styleFrom(
+        minimumSize: const Size(double.infinity, 50),
+        // backgroundColor: Colors.green.withValues(alpha: 0.2),
+        // foregroundColor: Colors.green,
+      ),
+    );
+  }
+}

+ 3 - 0
UI/CF.APP/chicken_farm/lib/pages/home/profile.dart

@@ -10,6 +10,7 @@ import 'package:chicken_farm/pages/home/_profile/dict_config_button.dart';
 // import 'package:chicken_farm/pages/home/_profile/rfid_config_button.dart';
 // import 'package:chicken_farm/pages/home/_profile/scanner_light_button.dart';
 import 'package:chicken_farm/pages/home/_profile/logout_button.dart';
+import 'package:chicken_farm/pages/home/_profile/export_data_button.dart';
 
 final tapCountProvider = StateProvider<int>((ref) => 0);
 final lastTapTimeProvider = StateProvider<DateTime>((ref) => DateTime.now());
@@ -77,6 +78,8 @@ class ProfilePage extends ConsumerWidget {
                         ],
                         const ClearCacheButton(),
                         const SizedBox(height: 20),
+                        const ExportDataButton(),
+                        const SizedBox(height: 20),
                       ] else ...[
                         UserInfoCard(user: authState.user!),
                         const UploadDataButton(),

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

@@ -1,4 +1,5 @@
 import 'package:chicken_farm/pages/dict/dict_manage_page.dart';
+import 'package:chicken_farm/pages/excel/export_data_page.dart';
 import 'package:chicken_farm/stores/auth_store.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -38,6 +39,7 @@ class AppRouteNames {
   static const String rfidConfig = '/rfid-config';
   static const String upload = '/upload';
   static const String dictManage = '/dict_manage';
+  static const String excelExport = '/excel_export';
 }
 
 class AppRoutes {
@@ -132,6 +134,11 @@ class AppRoutes {
       name: AppRouteNames.dictManage,
       builder: (context, state) => const DictManagePage(),
     ),
+    GoRoute(
+      path: AppRouteNames.excelExport,
+      name: AppRouteNames.excelExport,
+      builder: (context, state) => const ExcelExportPage(),
+    ),
   ];
 }
 

+ 2 - 0
UI/CF.APP/chicken_farm/macos/Flutter/GeneratedPluginRegistrant.swift

@@ -12,6 +12,7 @@ import flutter_image_compress_macos
 import mobile_scanner
 import path_provider_foundation
 import shared_preferences_foundation
+import sqflite_darwin
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
   AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
@@ -21,4 +22,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
   MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
   SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
+  SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
 }

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

@@ -58,6 +58,13 @@ dependencies:
   flutter_image_compress: ^2.3.0
   connectivity_plus: ^5.0.1
   uuid: ^4.2.1
+  sqflite: ^2.3.0
+  path: ^1.8.0
+  excel: ^4.0.0
+  open_filex: ^4.3.2
+   # 权限处理(Android/iOS)
+  permission_handler: ^11.3.1
+  
 
 dev_dependencies:
   flutter_test:

+ 3 - 0
UI/CF.APP/chicken_farm/windows/flutter/generated_plugin_registrant.cc

@@ -9,6 +9,7 @@
 #include <audioplayers_windows/audioplayers_windows_plugin.h>
 #include <connectivity_plus/connectivity_plus_windows_plugin.h>
 #include <file_selector_windows/file_selector_windows.h>
+#include <permission_handler_windows/permission_handler_windows_plugin.h>
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
   AudioplayersWindowsPluginRegisterWithRegistrar(
@@ -17,4 +18,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
       registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
   FileSelectorWindowsRegisterWithRegistrar(
       registry->GetRegistrarForPlugin("FileSelectorWindows"));
+  PermissionHandlerWindowsPluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
 }

+ 1 - 0
UI/CF.APP/chicken_farm/windows/flutter/generated_plugins.cmake

@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
   audioplayers_windows
   connectivity_plus
   file_selector_windows
+  permission_handler_windows
 )
 
 list(APPEND FLUTTER_FFI_PLUGIN_LIST