瀏覽代碼

Add 增加电子编号个体查询页面

Yue 3 周之前
父節點
當前提交
2afbfaf48c

+ 12 - 0
UI/CF.APP/chicken_farm/lib/apis/breeding/_query.dart

@@ -1,5 +1,6 @@
 import 'package:chicken_farm/core/api/api_service.dart';
 import 'package:chicken_farm/modes/breeding/batch.dart';
+import 'package:chicken_farm/modes/breeding/chicken.dart';
 import 'package:chicken_farm/modes/breeding/family.dart';
 import 'package:chicken_farm/modes/api/page_model.dart';
 
@@ -45,4 +46,15 @@ class BreedQueryApi {
     }
     return PageResultModel.empty();
   }
+
+  Future<ChickenModel?> queryChicken(String electronicId) async {
+    final result = await ApiService().get(
+      '$apiPrefix/getChicken/$electronicId',
+    );
+    if (result.success && result.data != null) {
+      return ChickenModel.fromJson(result.data);
+    } else {
+      return null;
+    }
+  }
 }

+ 6 - 3
UI/CF.APP/chicken_farm/lib/components/vb_win_electronic_id_field.dart

@@ -20,6 +20,7 @@ class VbWinElectronicIdField extends StatefulWidget {
   final bool multiple; // 是否为多标签模式
   final bool? multipleScan; // 是否开启多标签扫描模式
   final ValueChanged<int>? onDeleteTag; // 仅在multiple模式下使用
+  final int scanMilliseconds; // 扫描时间,到达时间后自动停止扫描
 
   const VbWinElectronicIdField({
     super.key,
@@ -34,6 +35,7 @@ class VbWinElectronicIdField extends StatefulWidget {
     this.multiple = false,
     this.multipleScan,
     this.onDeleteTag,
+    this.scanMilliseconds = 1100,
   });
 
   @override
@@ -45,12 +47,13 @@ class _VbWinElectronicIdFieldState extends State<VbWinElectronicIdField> {
   bool _isMultiple = false; // 内部管理多标签扫描模式
   final List<String> _scannedTags = []; // 存储扫描到的标签
   Timer? _autoStopTimer; // 自动停止扫描的定时器
+  int _scanMilliseconds = 1100;
 
   @override
   void initState() {
     super.initState();
     _isMultiple = widget.multipleScan ?? widget.multiple;
-
+    _scanMilliseconds = widget.scanMilliseconds;
     WinReaderManager.instance.registerCallbacks(
       onDataReceived: _handleTagReceived,
       onError: _handleScanError,
@@ -138,7 +141,7 @@ class _VbWinElectronicIdFieldState extends State<VbWinElectronicIdField> {
         _isScanning = false;
       });
     }
-    _autoStopTimer = Timer(const Duration(milliseconds: 1100), () {
+    _autoStopTimer = Timer(Duration(milliseconds: _scanMilliseconds), () {
       if (_isScanning) {
         // 确保仍在扫描状态
         _stopScan();
@@ -147,7 +150,7 @@ class _VbWinElectronicIdFieldState extends State<VbWinElectronicIdField> {
   }
 
   void _stopScan() async {
-    ToastUtil.info("停止识别电子标签");
+    // ToastUtil.info("停止识别电子标签");
     await WinReaderManager.instance.stopRead();
     // 取消自动停止定时器
     _autoStopTimer?.cancel();

+ 70 - 0
UI/CF.APP/chicken_farm/lib/modes/breeding/chicken.dart

@@ -0,0 +1,70 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'chicken.g.dart';
+
+@JsonSerializable()
+class ChickenModel {
+  int id;
+
+  String electronicId;
+  String batchNum;
+
+  int gender;
+
+  DateTime hatchDate;
+
+  // String wingTagNum;
+
+  // String legTag;
+
+  // int familyId;
+
+  String familyNum;
+
+  // int cageId;
+
+  String? cageNum;
+
+  DateTime? cullTime;
+
+  int? status;
+
+  int? cullReason;
+
+  int? disposalMethod;
+
+  int? currentAge;
+
+  String? feedSopName;
+
+  String? drugSopName;
+
+  String? vaccineSopName;
+
+  ChickenModel({
+    required this.id,
+    required this.electronicId,
+    required this.batchNum,
+    required this.gender,
+    required this.hatchDate,
+    // required this.wingTagNum,
+    // required this.legTag,
+    // required this.familyId,
+    required this.familyNum,
+    // required this.cageId,
+    this.cageNum,
+    this.cullTime,
+    this.status,
+    this.cullReason,
+    this.disposalMethod,
+    this.currentAge,
+    this.feedSopName,
+    this.drugSopName,
+    this.vaccineSopName,
+  });
+
+  factory ChickenModel.fromJson(Map<String, dynamic> json) =>
+      _$ChickenModelFromJson(json);
+
+  Map<String, dynamic> toJson() => _$ChickenModelToJson(this);
+}

+ 46 - 0
UI/CF.APP/chicken_farm/lib/modes/breeding/chicken.g.dart

@@ -0,0 +1,46 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'chicken.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+ChickenModel _$ChickenModelFromJson(Map<String, dynamic> json) => ChickenModel(
+  id: (json['id'] as num).toInt(),
+  electronicId: json['electronicId'] as String,
+  batchNum: json['batchNum'] as String,
+  gender: (json['gender'] as num).toInt(),
+  hatchDate: DateTime.parse(json['hatchDate'] as String),
+  familyNum: json['familyNum'] as String,
+  cageNum: json['cageNum'] as String,
+  cullTime: json['cullTime'] == null
+      ? null
+      : DateTime.parse(json['cullTime'] as String),
+  status: (json['status'] as num?)?.toInt(),
+  cullReason: (json['cullReason'] as num?)?.toInt(),
+  disposalMethod: (json['disposalMethod'] as num?)?.toInt(),
+  currentAge: (json['currentAge'] as num?)?.toInt(),
+  feedSopName: json['feedSopName'] as String?,
+  drugSopName: json['drugSopName'] as String?,
+  vaccineSopName: json['vaccineSopName'] as String?,
+);
+
+Map<String, dynamic> _$ChickenModelToJson(ChickenModel instance) =>
+    <String, dynamic>{
+      'id': instance.id,
+      'electronicId': instance.electronicId,
+      'batchNum': instance.batchNum,
+      'gender': instance.gender,
+      'hatchDate': instance.hatchDate.toIso8601String(),
+      'familyNum': instance.familyNum,
+      'cageNum': instance.cageNum,
+      'cullTime': instance.cullTime?.toIso8601String(),
+      'status': instance.status,
+      'cullReason': instance.cullReason,
+      'disposalMethod': instance.disposalMethod,
+      'currentAge': instance.currentAge,
+      'feedSopName': instance.feedSopName,
+      'drugSopName': instance.drugSopName,
+      'vaccineSopName': instance.vaccineSopName,
+    };

+ 343 - 0
UI/CF.APP/chicken_farm/lib/pages/breeding/individual_query_page.dart

@@ -0,0 +1,343 @@
+import 'dart:async';
+import 'dart:io';
+import 'package:chicken_farm/components/vb_dict_label.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
+import 'package:flutter/material.dart';
+import 'package:chicken_farm/apis/breeding/_query.dart';
+import 'package:chicken_farm/components/vb_electronic_id_field.dart';
+import 'package:chicken_farm/components/vb_win_electronic_id_field.dart';
+import 'package:chicken_farm/core/utils/toast.dart';
+import 'package:chicken_farm/modes/breeding/chicken.dart';
+import 'package:intl/intl.dart';
+
+class IndividualQueryPage extends StatefulWidget {
+  const IndividualQueryPage({super.key});
+
+  @override
+  State<IndividualQueryPage> createState() => _IndividualQueryPageState();
+}
+
+class _IndividualQueryPageState extends State<IndividualQueryPage> {
+  final BreedQueryApi _breedQueryApi = BreedQueryApi();
+  ChickenModel? _chicken;
+  String? _electronicId;
+  bool _isLoading = false;
+  final TextEditingController _idController = TextEditingController();
+  Timer? _debounceTimer; // 添加Timer变量用于管理延时操作
+
+  // 添加手动模式状态
+  bool _isManualMode = false;
+
+  @override
+  void initState() {
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    // 取消任何待处理的计时器
+    if (_debounceTimer != null) {
+      _debounceTimer!.cancel();
+    }
+    _idController.dispose();
+    super.dispose();
+  }
+
+  void _queryChickenOnce(String id) {
+    if (_debounceTimer != null) {
+      _debounceTimer!.cancel();
+    }
+
+    _debounceTimer = Timer(Duration(milliseconds: 800), () {
+      _queryChicken(id);
+    });
+  }
+
+  Future<void> _queryChicken(String id) async {
+    if (id.isEmpty) {
+      ToastUtil.warning('请输入电子编号');
+      return;
+    }
+
+    setState(() {
+      _isLoading = true;
+    });
+
+    try {
+      final chicken = await _breedQueryApi.queryChicken(id.trim());
+      if (chicken != null) {
+        if (mounted) {
+          // 遵循Flutter异步操作与UI更新安全规范
+          setState(() {
+            _chicken = chicken;
+          });
+        }
+      } else {
+        if (mounted) {
+          // 遵循Flutter异步操作与UI更新安全规范
+          ToastUtil.error('未找到该编号对应的个体信息');
+          setState(() {
+            _chicken = null;
+          });
+        }
+      }
+    } catch (e) {
+      if (mounted) {
+        // 遵循Flutter异步操作与UI更新安全规范
+        ToastUtil.error('查询失败: $e');
+        logger.e(e);
+        setState(() {
+          _chicken = null;
+        });
+      }
+    } finally {
+      if (mounted) {
+        // 遵循Flutter异步操作与UI更新安全规范
+        setState(() {
+          _isLoading = false;
+        });
+      }
+    }
+  }
+
+  // 手动查询方法
+  void _manualQueryChicken() {
+    if (_idController.text.isEmpty) {
+      ToastUtil.warning('请输入电子编号');
+      return;
+    }
+    _queryChicken(_idController.text);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: const Text('个体查询')),
+      body: Padding(
+        padding: const EdgeInsets.all(16.0),
+        child: Column(
+          children: [
+            // 按钮行 - 手动/自动切换按钮
+            Row(
+              mainAxisAlignment: MainAxisAlignment.end, // 靠右对齐
+              children: [
+                // 手动/自动切换按钮
+                SizedBox(
+                  // 设置固定宽度
+                  width: 120,
+                  child: ElevatedButton(
+                    onPressed: () {
+                      setState(() {
+                        _isManualMode = !_isManualMode;
+                        _idController.clear();
+                        _chicken = null;
+                        _electronicId = null;
+                      });
+                    },
+                    style: ElevatedButton.styleFrom(
+                      backgroundColor: _isManualMode
+                          ? Colors.blue
+                          : Colors.orange,
+                      foregroundColor: Colors.white,
+                    ),
+                    child: Text(_isManualMode ? '识别查询' : '手动查询'),
+                  ),
+                ),
+              ],
+            ),
+            const SizedBox(height: 8), // 添加一点间距
+            // 电子标签输入区域 - 根据模式动态显示
+            if (!_isManualMode) ...[
+              _buildPlatformSpecificField(),
+            ] else ...[
+              // 手动输入模式
+              Row(
+                children: [
+                  Expanded(
+                    child: TextField(
+                      controller: _idController,
+                      decoration: const InputDecoration(
+                        labelText: '电子编号',
+                        border: OutlineInputBorder(),
+                        hintText: '请输入电子编号',
+                      ),
+                    ),
+                  ),
+                  const SizedBox(width: 8),
+                  ElevatedButton(
+                    onPressed: _manualQueryChicken,
+                    style: ElevatedButton.styleFrom(
+                      backgroundColor: Colors.green,
+                      foregroundColor: Colors.white,
+                    ),
+                    child: const Text('查询'),
+                  ),
+                ],
+              ),
+            ],
+            const SizedBox(height: 16),
+            // 个体信息展示区域
+            if (_chicken != null) ...[
+              Expanded(
+                child: SingleChildScrollView(
+                  child: Card(
+                    child: Padding(
+                      padding: const EdgeInsets.all(16.0),
+                      child: Column(
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: [
+                          // 基本信息
+                          const Text(
+                            '基本信息',
+                            style: TextStyle(
+                              fontSize: 18,
+                              fontWeight: FontWeight.bold,
+                            ),
+                          ),
+                          const SizedBox(height: 10),
+                          _buildInfoRow('电子编号:', _chicken!.electronicId),
+                          _buildInfoRow('批次号:', _chicken!.batchNum),
+                          _buildInfoRow('家系编号:', _chicken!.familyNum),
+                          _buildInfoRow('笼号:', _chicken!.cageNum ?? '无'),
+                          _buildInfoRow2(
+                            '性别:',
+                            VberDictLabel(
+                              dictType: "chicken_gender",
+                              value: _chicken!.gender.toString(),
+                            ),
+                          ),
+                          _buildInfoRow(
+                            '孵化日期:',
+                            DateFormat(
+                              'yyyy-MM-dd',
+                            ).format(_chicken!.hatchDate),
+                          ),
+                          if (_chicken!.currentAge != null)
+                            _buildInfoRow('当前日龄:', '${_chicken!.currentAge} 天'),
+                          _buildInfoRow2(
+                            '养殖状态:',
+                            VberDictLabel(
+                              dictType: "chicken_status",
+                              value: _chicken!.status.toString(),
+                            ),
+                          ),
+
+                          if (_chicken!.cullReason != null)
+                            _buildInfoRow2(
+                              '淘汰原因:',
+                              VberDictLabel(
+                                dictType: "chicken_cull_reason",
+                                value: _chicken!.cullReason.toString(),
+                              ),
+                            ),
+                          if (_chicken!.disposalMethod != null)
+                            _buildInfoRow2(
+                              '处理方式:',
+                              VberDictLabel(
+                                dictType: "chicken_disposal_method",
+                                value: _chicken!.disposalMethod.toString(),
+                              ),
+                            ),
+                          if (_chicken!.cullTime != null)
+                            _buildInfoRow(
+                              '淘汰时间:',
+                              DateFormat(
+                                'yyyy-MM-dd HH:mm:ss',
+                              ).format(_chicken!.cullTime!),
+                            ),
+                          const SizedBox(height: 20),
+
+                          // SOP信息
+                          // const Text(
+                          //   'SOP信息',
+                          //   style: TextStyle(
+                          //     fontSize: 18,
+                          //     fontWeight: FontWeight.bold,
+                          //   ),
+                          // ),
+                          // const SizedBox(height: 10),
+                          // _buildInfoRow('饲料SOP:', _chicken!.feedSopName ?? '无'),
+                          // _buildInfoRow('用药SOP:', _chicken!.drugSopName ?? '无'),
+                          // _buildInfoRow(
+                          //   '疫苗SOP:',
+                          //   _chicken!.vaccineSopName ?? '无',
+                          // ),
+                        ],
+                      ),
+                    ),
+                  ),
+                ),
+              ),
+            ] else if (!_isLoading)
+              Expanded(
+                child: Center(
+                  child: Text(
+                    '请${_isManualMode ? "输入" : "识别"}电子编号查询',
+                    style: Theme.of(
+                      context,
+                    ).textTheme.titleMedium?.copyWith(color: Colors.grey[600]),
+                  ),
+                ),
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  // 根据平台构建特定字段组件
+  Widget _buildPlatformSpecificField() {
+    return VberElectronicIdsField(
+      electronicId: _electronicId,
+      scanMilliseconds: 1000,
+      onIdScanned: (id) {
+        setState(() {
+          _electronicId = id;
+        });
+        _queryChickenOnce(id);
+      },
+      label: '电子编号',
+      placeholder: '未识别',
+    );
+  }
+
+  Widget _buildInfoRow(String label, String value) {
+    return Padding(
+      padding: const EdgeInsets.only(bottom: 8.0),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          SizedBox(
+            width: 80,
+            child: Text(
+              label,
+              style: const TextStyle(fontWeight: FontWeight.w500),
+            ),
+          ),
+          Expanded(
+            child: Text(value, style: const TextStyle(color: Colors.black87)),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildInfoRow2(String label, Widget value) {
+    return Padding(
+      padding: const EdgeInsets.only(bottom: 8.0),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          SizedBox(
+            width: 80,
+            child: Text(
+              label,
+              style: const TextStyle(fontWeight: FontWeight.w500),
+            ),
+          ),
+          value,
+        ],
+      ),
+    );
+  }
+}

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

@@ -17,6 +17,7 @@ import '../pages/breeding/batch_create_page_win.dart';
 import '../pages/breeding/cage_change_page.dart';
 import '../pages/breeding/individual_weighing_page.dart';
 import '../pages/breeding/individual_culling_page.dart';
+import '../pages/breeding/individual_query_page.dart';
 import '../pages/breeding/batch_culling_page.dart';
 import '../pages/breeding/batch_culling_page_win.dart';
 import '../pages/upload/upload_page.dart';
@@ -33,6 +34,7 @@ class AppRouteNames {
   static const String cageChange = '/cage_change';
   static const String individualWeighing = '/individual_weighing';
   static const String individualCulling = '/individual_culling';
+  static const String individualQuery = '/individual_query'; // 个体查询
   static const String batchCulling = '/batch_culling';
   static const String batchCullingWin = '/batch_culling_win'; // Windows平台批量淘汰
   static const String sample = '/sample';
@@ -106,6 +108,11 @@ class AppRoutes {
       name: AppRouteNames.individualCulling,
       builder: (context, state) => const IndividualCullingPage(),
     ),
+    GoRoute(
+      path: AppRouteNames.individualQuery,
+      name: AppRouteNames.individualQuery,
+      builder: (context, state) => const IndividualQueryPage(),
+    ),
     GoRoute(
       path: AppRouteNames.batchCulling,
       name: AppRouteNames.batchCulling,
@@ -172,4 +179,4 @@ class SplashScreen extends ConsumerWidget {
     });
     return const Scaffold(body: Center(child: CircularProgressIndicator()));
   }
-}
+}

+ 7 - 0
UI/CF.APP/chicken_farm/lib/stores/menu_store.dart

@@ -45,6 +45,13 @@ class MenuStore {
       permission: PermissionKeys.chickenCulling,
       platform: 1,
     ),
+    MenuItem(
+      name: '个体查询',
+      routeName: AppRouteNames.individualQuery,
+      icon: Icons.search,
+      permission: "",
+      platform: 1,
+    ),
     MenuItem(
       name: '点检签到',
       routeName: AppRouteNames.checkin,

+ 1 - 1
UI/CF.APP/chicken_farm/windows/runner/reader/reader_service.cpp

@@ -132,7 +132,7 @@ bool ReaderService::StartRead()
                                                    {
     while (timer_running_) {
       SendTagList();
-      std::this_thread::sleep_for(std::chrono::seconds(1));
+      std::this_thread::sleep_for(std::chrono::milliseconds(500));
     } });
 
   // // Start a separate thread to automatically stop reading after 3 seconds