Parcourir la source

Add win平台识别电子编号组件

Yue il y a 3 semaines
Parent
commit
51378edd8c

+ 226 - 0
UI/CF.APP/chicken_farm/lib/components/vb_win_electronic_id_field.dart

@@ -0,0 +1,226 @@
+import 'package:chicken_farm/core/utils/logger.dart';
+import 'package:chicken_farm/core/services/win/win_reader_manager.dart';
+import 'package:chicken_farm/core/utils/toast.dart';
+import 'package:flutter/material.dart';
+import 'dart:async';
+
+/// Windows平台电子标签字段组件,支持单个或多个标签显示
+///
+/// 当[multiple]为true时,显示已识别的标签数量
+/// 当[multiple]为false时,显示单个标签的具体值
+class VbWinElectronicIdField extends StatefulWidget {
+  final List<String>? electronicIds;
+  final String? electronicId;
+  final ValueChanged<String>? onIdScanned; // 电子标签识别成功回调
+  final ValueChanged<List<String>>? onIdsScanned; // 电子标签识别成功回调
+  final String label; // 显示的标签文本
+  final String placeholder; // 未识别时显示的占位符文本
+  final String multiplePlaceholder; // 多个模式下未识别时显示的占位符文本
+  final String multipleFormat; // 多个模式下有值时的显示格式,包含一个%d占位符表示数量
+  final bool multiple; // 是否为多标签模式
+  final bool? multipleScan; // 是否开启多标签扫描模式
+  final ValueChanged<int>? onDeleteTag; // 仅在multiple模式下使用
+
+  const VbWinElectronicIdField({
+    super.key,
+    this.electronicIds,
+    this.electronicId,
+    this.onIdScanned,
+    this.onIdsScanned,
+    this.label = '电子标签',
+    this.placeholder = '未识别',
+    this.multiplePlaceholder = '未识别',
+    this.multipleFormat = '已识别 %d 枚电子标签',
+    this.multiple = false,
+    this.multipleScan,
+    this.onDeleteTag,
+  });
+
+  @override
+  State<VbWinElectronicIdField> createState() => _VbWinElectronicIdFieldState();
+}
+
+class _VbWinElectronicIdFieldState extends State<VbWinElectronicIdField> {
+  bool _isScanning = false; // 内部管理扫描状态
+  bool _isMultiple = false; // 内部管理多标签扫描模式
+  final List<String> _scannedTags = []; // 存储扫描到的标签
+  Timer? _autoStopTimer; // 自动停止扫描的定时器
+
+  @override
+  void initState() {
+    super.initState();
+    _isMultiple = widget.multipleScan ?? widget.multiple;
+
+    WinReaderManager.instance.registerCallbacks(
+      onDataReceived: _handleTagReceived,
+      onError: _handleScanError,
+      onConnect: (success) {
+        if (success) {
+          ToastUtil.success('读卡器连接成功');
+        } else {
+          ToastUtil.warning('读卡器连接断开');
+        }
+      },
+    );
+    if (!WinReaderManager.instance.isConnected) {
+      WinReaderManager.instance.autoConnect();
+    }
+  }
+
+  @override
+  void dispose() {
+    // 取消自动停止定时器
+    _autoStopTimer?.cancel();
+    _autoStopTimer = null;
+
+    WinReaderManager.instance.clearCallbacks();
+    super.dispose();
+  }
+
+  void _handleTagReceived(String data) {
+    logger.d('电子标签识别成功,已识别标签:$data');
+
+    if (_isMultiple) {
+      // 多标签模式:添加到列表中,避免重复
+      if (!_scannedTags.contains(data)) {
+        _scannedTags.add(data);
+
+        setState(() {
+          _isScanning = false;
+        });
+
+        if (widget.onIdsScanned != null) {
+          widget.onIdsScanned!(_scannedTags);
+        }
+      }
+    } else {
+      // 单标签模式:直接使用接收到的数据
+      setState(() {
+        _isScanning = false;
+      });
+
+      if (widget.onIdScanned != null) {
+        widget.onIdScanned!(data);
+      }
+    }
+  }
+
+  void _handleScanError(String error) {
+    ToastUtil.error(error);
+    setState(() {
+      _isScanning = false;
+    });
+  }
+
+  void _scanTag() async {
+    if (!WinReaderManager.instance.isConnected) {
+      ToastUtil.error("请先连接读卡器设备");
+      return;
+    }
+
+    setState(() {
+      _isScanning = true;
+      if (_isMultiple) {
+        _scannedTags.clear(); // 清空之前的标签列表
+      }
+    });
+
+    ToastUtil.show('开始识别电子标签', duration: 1);
+
+    bool result = await WinReaderManager.instance.startRead();
+    if (!result) {
+      ToastUtil.errorAlert('读卡器读取失败,请检查读卡器是否正常');
+      setState(() {
+        _isScanning = false;
+      });
+    }
+    _autoStopTimer = Timer(const Duration(seconds: 5), () {
+      if (_isScanning) {
+        // 确保仍在扫描状态
+        _stopScan();
+      }
+    });
+  }
+
+  void _stopScan() async {
+    ToastUtil.info("停止识别电子标签");
+    await WinReaderManager.instance.stopRead();
+    // 取消自动停止定时器
+    _autoStopTimer?.cancel();
+    _autoStopTimer = null;
+    setState(() {
+      _isScanning = false;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Text(widget.label, style: const TextStyle(fontWeight: FontWeight.bold)),
+        const SizedBox(height: 10),
+        Container(
+          padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
+          decoration: BoxDecoration(
+            border: Border.all(color: Colors.grey),
+            borderRadius: BorderRadius.circular(8),
+          ),
+          child: Row(
+            children: [
+              Expanded(
+                child: Text(
+                  _getDisplayText(),
+                  style: TextStyle(
+                    color: _hasValue() ? Colors.black : Colors.grey,
+                    fontSize: 16,
+                  ),
+                ),
+              ),
+              IconButton(
+                icon: _isScanning
+                    ? const SizedBox(
+                        width: 20,
+                        height: 20,
+                        child: CircularProgressIndicator(strokeWidth: 2),
+                      )
+                    : Icon(
+                        widget.multiple ? Icons.add : Icons.qr_code_scanner,
+                        size: 20,
+                      ),
+                onPressed: _isScanning
+                    ? null
+                    : (_isScanning ? _stopScan : _scanTag),
+              ),
+            ],
+          ),
+        ),
+      ],
+    );
+  }
+
+  /// 获取显示的文本
+  String _getDisplayText() {
+    if (widget.multiple) {
+      if (widget.electronicIds == null || widget.electronicIds!.isEmpty) {
+        return widget.multiplePlaceholder;
+      } else {
+        return widget.multipleFormat.replaceAll(
+          '%d',
+          widget.electronicIds!.length.toString(),
+        );
+      }
+    } else {
+      return widget.electronicId ?? widget.placeholder;
+    }
+  }
+
+  /// 判断是否有值
+  bool _hasValue() {
+    if (widget.multiple) {
+      return widget.electronicIds != null && widget.electronicIds!.isNotEmpty;
+    } else {
+      return widget.electronicId != null;
+    }
+  }
+}

+ 28 - 0
UI/CF.APP/chicken_farm/lib/core/services/win/win_reader_manager.dart

@@ -116,6 +116,34 @@ class WinReaderManager {
     }
   }
 
+  /// 自动扫描并连接到第一个USB设备
+  Future<bool> autoConnect() async {
+    logger.i("开始自动连接设备");
+    
+    // 先扫描USB设备
+    List<String>? devices = await scanUsb();
+    
+    if (devices == null || devices.isEmpty) {
+      logger.e("未找到任何USB设备");
+      _onError?.call("未找到任何USB设备");
+      return false;
+    }
+    
+    logger.i("找到 ${devices.length} 个USB设备: $devices");
+    
+    // 连接到第一个设备(索引为0)
+    int deviceIndex = 0;
+    bool result = await connect(deviceIndex);
+    
+    if (result) {
+      logger.i("自动连接设备成功,索引: $deviceIndex");
+    } else {
+      logger.e("自动连接设备失败,索引: $deviceIndex");
+    }
+    
+    return result;
+  }
+
   /// 断开连接
   Future<void> disconnect() async {
     if (!_isConnected) {

+ 47 - 11
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page_win.dart

@@ -2,6 +2,7 @@ import 'package:chicken_farm/core/utils/datetime_util.dart';
 import 'package:chicken_farm/apis/index.dart';
 import 'package:chicken_farm/components/vb_search_select.dart';
 import 'package:chicken_farm/components/vb_select.dart';
+import 'package:chicken_farm/components/vb_win_electronic_id_field.dart';
 import 'package:chicken_farm/modes/breeding/batch.dart';
 import 'package:chicken_farm/modes/breeding/family.dart';
 import 'package:flutter/material.dart';
@@ -199,16 +200,51 @@ class _BatchCreatePageWinState extends State<BatchCreatePageWin> {
   }
 
   Widget _buildElectronicIdSection() {
-    return Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: [
-        const Text('请输入或扫描电子编号', style: TextStyle(fontWeight: FontWeight.bold)),
-        const SizedBox(height: 10),
-        Container(
-          padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
-          decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
-        ),
-      ],
+    return VbWinElectronicIdField(
+      electronicIds: _electronicIds,
+      onIdsScanned: (List<String> idList) {
+        // 过滤出未存在的RFID
+        final newIds = idList
+            .where((id) => !_electronicIds.contains(id))
+            .toList();
+
+        if (newIds.isNotEmpty) {
+          setState(() {
+            // 将新的ElectronicId添加到列表中
+            for (var id in newIds) {
+              _electronicIds.insert(0, id);
+            }
+          });
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(
+              content: Text("新增 ${newIds.length} 枚电子编号"),
+              backgroundColor: Colors.green,
+            ),
+          );
+          // ToastUtil.success("新增 ${newIds.length} 枚电子编号");
+          // if (newIds.length == idList.length) {
+          //   // 全都是新添加的
+          //   ToastUtil.success("成功添加 ${newIds.length} 枚新的电子编号");
+          // } else {
+          //   // 部分是重复的
+          //   ToastUtil.info("新增 ${newIds.length} 枚电子编号");
+          //   // ,${idList.length - newIds.length} 枚已存在
+          // }
+        } else {
+          // 所有RFID都已存在
+          // ToastUtil.info("电子编号已存在");
+          ScaffoldMessenger.of(context).showSnackBar(
+            const SnackBar(
+              content: Text('电子编号已存在'),
+              backgroundColor: Colors.orange,
+            ),
+          );
+        }
+      },
+      multiple: true,
+      label: '电子编号',
+      multiplePlaceholder: '未识别',
+      multipleFormat: '已识别 %d 枚电子编号',
     );
   }
 
@@ -257,4 +293,4 @@ class _BatchCreatePageWinState extends State<BatchCreatePageWin> {
       }
     });
   }
-}
+}

+ 8 - 9
UI/CF.APP/chicken_farm/lib/pages/serial/serial_setting_page.dart

@@ -44,7 +44,11 @@ class _SerialSettingPageState extends State<SerialSettingPage> {
       onError: _onError,
     );
     _loadUsbPorts();
-    // 监听数据流 - 使用WinReaderManager内部处理,无需重复监听
+    final connected = _readerManager.isConnected;
+    if (connected) {
+      _readerManager.disconnect();
+    }
+    _readerManager.autoConnect();
   }
 
   /// 加载可用Usb列表
@@ -551,14 +555,9 @@ class _SerialSettingPageState extends State<SerialSettingPage> {
 
   @override
   void dispose() {
-    // 在页面销毁时断开设备连接
-    if (_isConnected) {
-      _readerManager.disconnect();
-    }
-    
-    _readerManager.clearCallbacks();
-    _readerManager.dispose();
-    _dataController.dispose();
+    // _readerManager.clearCallbacks();
+    // _readerManager.dispose();
+    // _dataController.dispose();
     super.dispose();
   }
 }