Browse Source

Update 完善RFID读取

Yue 4 days ago
parent
commit
f3105f5dbf
26 changed files with 1159 additions and 778 deletions
  1. 7 1
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/MainActivity.java
  2. 7 4
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidConstants.java
  3. 5 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidListener.java
  4. 47 8
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidManager.java
  5. 129 34
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidMethodCallHandler.java
  6. 58 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidModel.java
  7. 262 172
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidService.java
  8. 19 16
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanService.java
  9. 50 11
      UI/CF.APP/chicken_farm/lib/components/vb_rfid_field.dart
  10. 27 6
      UI/CF.APP/chicken_farm/lib/core/services/connectivity_service.dart
  11. 46 11
      UI/CF.APP/chicken_farm/lib/core/services/pda/rfid_channel.dart
  12. 100 38
      UI/CF.APP/chicken_farm/lib/core/services/pda/rfid_manager.dart
  13. 1 1
      UI/CF.APP/chicken_farm/lib/core/services/pda/scan_channel.dart
  14. 17 0
      UI/CF.APP/chicken_farm/lib/core/utils/toast.dart
  15. 21 0
      UI/CF.APP/chicken_farm/lib/modes/rfid/rfid_model.dart
  16. 17 0
      UI/CF.APP/chicken_farm/lib/modes/rfid/rfid_model.g.dart
  17. 266 0
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page.dart
  18. 52 78
      UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page.dart
  19. 0 376
      UI/CF.APP/chicken_farm/lib/pages/breeding/bind_wing_tag_page.dart
  20. 17 9
      UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart
  21. 2 2
      UI/CF.APP/chicken_farm/lib/pages/breeding/individual_culling_page.dart
  22. 2 2
      UI/CF.APP/chicken_farm/lib/pages/breeding/individual_weighing_page.dart
  23. 1 3
      UI/CF.APP/chicken_farm/lib/pages/home/menu_buttons.dart
  24. 3 3
      UI/CF.APP/chicken_farm/lib/routes/app_routes.dart
  25. 1 1
      UI/CF.APP/chicken_farm/lib/stores/auth_store.dart
  26. 2 2
      UI/CF.APP/chicken_farm/lib/stores/menu_store.dart

+ 7 - 1
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/MainActivity.java

@@ -98,7 +98,13 @@ public class MainActivity extends FlutterActivity {
                 return true; // 表示事件已被处理
             }
         }
-              
+        // 处理619按键事件
+        if (keyCode == 619) {
+            if (rfidMethodCallHandler != null) {
+                rfidMethodCallHandler.onKeyEvent(keyCode);
+                return true; // 表示事件已被处理
+            }
+        }        
         // 其他按键处理
         return super.onKeyDown(keyCode, event);
     }

+ 7 - 4
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidConstants.java

@@ -12,19 +12,22 @@ public final class RfidConstants {
     public static final String DEVPORT2 = "/dev/ttyS2";
     
     // 默认波特率列表
-    public static final int[] BAUD_LIST = {115200, 460800, 57600};
+    // public static final int[] BAUD_LIST = {115200, 460800, 57600};
+    public static final int[] BAUD_LIST = {115200};
     
     // 默认功率
-    public static final int DEFAULT_POWER = 10;
+    public static final int DEFAULT_POWER = 7;
+    public static final int POWER_HIGH = 10;
+    public static final int POWER_LOW = 7;
 
     // 默认Q值
     public static final int DEFAULT_Q_VALUE = 0;
 
     // 默认扫描时间
-    public static final int DEFAULT_SCAN_TIME = 5;
+    public static final int DEFAULT_SCAN_TIME = 10;
 
     // 默认会话
-    public static final int DEFAULT_SESSION = 3;
+    public static final int DEFAULT_SESSION = 0;
 
     // 默认唤醒锁超时时间(10分钟)
     public static final long WAKE_LOCK_TIMEOUT_MS = 10 * 60 * 1000L;

+ 5 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidListener.java

@@ -36,4 +36,9 @@ public interface RfidListener {
      * @param error 错误信息
      */
     void onScanError(String error);
+    /**
+     * 扫描信息回调
+     * @param info 扫描信息
+     */
+    void onScanInfo(String info);
 }

+ 47 - 8
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidManager.java

@@ -113,7 +113,24 @@ public class RfidManager {
      * 连接RFID设备(使用默认重试次数)
      */
     public boolean connect() {
-        checkServiceBound();
+        //checkServiceBound();
+        if(!isBound){
+            Log.e(TAG, "RFID连接前绑定服务");
+            bindService();
+            int i = 0;
+            while (!isBound&&i<15) {
+                try {
+                    Thread.sleep(100);
+                    i++;
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        if (rfidService == null) {
+            Log.e(TAG, "RFID服务未绑定");
+            return false;
+        }
         return rfidService.connect();
     }
 
@@ -121,7 +138,24 @@ public class RfidManager {
      * 连接RFID设备(指定重试次数)
      */
     public boolean connect(int retryTimes) {
-        checkServiceBound();
+        // checkServiceBound();
+        if(!isBound){
+            Log.e(TAG, "RFID连接前绑定服务");
+            bindService();
+            int i = 0;
+            while (!isBound&&i<15) {
+                try {
+                    Thread.sleep(100);
+                    i++;
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        if (rfidService == null) {
+            Log.e(TAG, "RFID服务未绑定");
+            return false;
+        }
         return rfidService.connect(retryTimes);
     }
 
@@ -131,14 +165,15 @@ public class RfidManager {
     public void disconnect() {
         checkServiceBound();
         rfidService.disconnect();
+        unbindService();
     }
 
     /**
      * 开始扫描标签
      */
-    public boolean startScan() {
+    public boolean startScan(boolean isMultipleTag) {
         checkServiceBound();
-        return rfidService.startScan();
+        return rfidService.startScan(isMultipleTag);
     }
 
     /**
@@ -193,6 +228,10 @@ public class RfidManager {
         rfidService.setScanIdleTimeoutMs(timeoutMs);
     }
 
+    public void setMultipleTag(boolean multipleTag) {
+        rfidService.setMultipleTag(multipleTag);
+    }
+
     // -------------------------- 代理状态查询API --------------------------
     public ReaderParameter getReaderParameter() {
         checkServiceBound();
@@ -204,10 +243,6 @@ public class RfidManager {
         return rfidService.getPower();
     }
 
-    public Map<String, Integer> getTagMap() {
-        checkServiceBound();
-        return rfidService.getTagMap();
-    }
 
     public boolean isConnected() {
         return isBound && rfidService != null && rfidService.isConnected();
@@ -221,6 +256,10 @@ public class RfidManager {
         return isBound && rfidService != null && rfidService.isScreenOn();
     }
 
+    public boolean isMultipleTag(){
+        return isBound && rfidService != null && rfidService.isMultipleTag();
+    }
+
     public int getConnectRetryTimes() {
         checkServiceBound();
         return rfidService.getConnectRetryTimes();

+ 129 - 34
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidMethodCallHandler.java

@@ -9,6 +9,8 @@ import androidx.annotation.NonNull;
 
 import com.rfid.InventoryTagMap;
 
+import com.vber.chicken_farm.rfid.RfidModel;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -51,8 +53,8 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
         methodChannel.setMethodCallHandler(this);
         EventChannel eventChannel = new EventChannel(messenger, EVENT_CHANNEL_NAME);
         eventChannel.setStreamHandler(this);
-
-        Log.d(TAG, "RFID Flutter桥接初始化完成");
+        bindRfidService();
+        Log.d(TAG, "RFID Flutter MethodChannel初始化完成");
     }
 
     // -------------------------- MethodChannel实现 --------------------------
@@ -63,9 +65,9 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
             case "bindService":
                 handleBindService(result);
                 break;
-            case "unbindService":
-                handleUnbindService(result);
-                break;
+            // case "unbindService":
+            //     handleUnbindService(result);
+            //     break;
             case "connect":
                 int retryTimes = call.argument("retryTimes") != null ? call.argument("retryTimes") : RfidConstants.DEFAULT_CONNECT_RETRY_TIMES;
                 handleConnect(retryTimes, result);
@@ -74,7 +76,8 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
                 handleDisconnect(result);
                 break;
             case "startScan":
-                handleStartScan(result);
+                boolean multipleTag = call.argument("multipleTag") != null ? call.argument("multipleTag") : false;
+                handleStartScan(result, multipleTag);
                 break;
             case "stopScan":
                 handleStopScan(result);
@@ -83,8 +86,14 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
                 handleSetReaderParameter(call.argument("param"), result);
                 break;
             case "setPower":
-                int power = call.argument("power") != null ? call.argument("power") : RfidConstants.DEFAULT_POWER;
-                handleSetPower(power, result);
+                int powerSet = call.argument("power") != null ? call.argument("power") : RfidConstants.DEFAULT_POWER;
+                handleSetPower(powerSet, result);
+                break;
+            case "getReaderParameter":
+                handleGetReaderParameter(result);
+                break;
+            case "getPower":
+                handleGetPower(result);
                 break;
             case "setScanIdleTimeout":
                 long timeoutMs = call.argument("timeoutMs") != null ? call.argument("timeoutMs") : RfidConstants.SCAN_IDLE_TIMEOUT_MS;
@@ -94,6 +103,10 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
                 int retry = call.argument("retryTimes") != null ? call.argument("retryTimes") : RfidConstants.DEFAULT_CONNECT_RETRY_TIMES;
                 handleSetConnectRetryTimes(retry, result);
                 break;
+            case "setMultipleTag":
+                boolean multiple = call.argument("multiple") != null ? call.argument("multiple") : false;
+                handleSetMultipleTag(multiple, result);
+                break;
             default:
                 result.notImplemented();
                 Log.w(TAG, "未实现的方法调用: " + call.method);
@@ -101,42 +114,55 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
         }
     }
 
-    /**
+     /**
      * 处理绑定服务请求
      */
-    private void handleBindService(Result result) {
+    private void bindRfidService() {
         try {
+            Log.d(TAG, "请求绑定RFID服务");
             rfidManager.bindService();
-            result.success(true);
-            Log.d(TAG, "RFID服务绑定请求已处理");
         } catch (Exception e) {
             Log.e(TAG, "绑定服务失败", e);
-            result.error("BIND_FAILED", "绑定RFID服务失败: " + e.getMessage(), null);
         }
     }
 
     /**
-     * 处理绑服务请求
+     * 处理绑服务请求
      */
-    private void handleUnbindService(Result result) {
+    private void handleBindService(Result result) {
         try {
-            rfidManager.unbindService();
-            result.success(null);
-            Log.d(TAG, "RFID服务解绑请求已处理");
+            Log.d(TAG, "请求绑定RFID服务");
+            rfidManager.bindService();
+            result.success(true);
         } catch (Exception e) {
-            Log.e(TAG, "绑服务失败", e);
-            result.error("UNBIND_FAILED", "绑RFID服务失败: " + e.getMessage(), null);
+            Log.e(TAG, "绑服务失败", e);
+            result.error("BIND_FAILED", "绑RFID服务失败: " + e.getMessage(), null);
         }
     }
 
+    // /**
+    //  * 处理解绑服务请求
+    //  */
+    // private void handleUnbindService(Result result) {
+    //     try {
+    //         rfidManager.unbindService();
+    //         result.success(null);
+    //         Log.d(TAG, "RFID服务解绑请求已处理");
+    //     } catch (Exception e) {
+    //         Log.e(TAG, "解绑服务失败", e);
+    //         result.error("UNBIND_FAILED", "解绑RFID服务失败: " + e.getMessage(), null);
+    //     }
+    // }
+
     /**
      * 处理连接设备请求
      */
     private void handleConnect(int retryTimes, Result result) {
         try {
+            Log.d(TAG, "处理RFID连接请求,重试次数: " + retryTimes);
             boolean success = rfidManager.connect(retryTimes);
             result.success(success);
-            Log.d(TAG, "RFID连接请求已处理,重试次数: " + retryTimes);
+            Log.d(TAG, "RFID连接请求已处理。 ");
         } catch (Exception e) {
             Log.e(TAG, "连接设备失败", e);
             result.error("CONNECT_FAILED", "连接RFID设备失败: " + e.getMessage(), null);
@@ -160,9 +186,9 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
     /**
      * 处理开始扫描请求
      */
-    private void handleStartScan(Result result) {
+    private void handleStartScan(Result result,boolean isMultipleTag) {
         try {
-            boolean success = rfidManager.startScan();
+            boolean success = rfidManager.startScan(isMultipleTag);
             result.success(success);
             if (success) {
                 Log.d(TAG, "RFID开始扫描请求已处理");
@@ -200,6 +226,17 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
         }
     }
 
+    private void handleSetMultipleTag(boolean multipleTag, Result result){
+        try {
+            rfidManager.setMultipleTag(multipleTag);
+            result.success(null);
+            Log.d(TAG, "设置设备是否扫描多标签: " + multipleTag);
+        } catch (Exception e) {
+            Log.e(TAG, "设置设备是否扫描多标签", e);
+            result.error("SET_MULTIPLE_TAG_FAILED", "设置设备是否扫描多标签: " + e.getMessage(), null);
+        }
+    }
+
     /**
      * 处理设置功率请求
      */
@@ -213,6 +250,48 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
             result.error("SET_POWER_FAILED", "设置功率失败: " + e.getMessage(), null);
         }
     }
+    
+    /**
+     * 处理获取阅读器参数请求
+     */
+    private void handleGetReaderParameter(Result result) {
+        try {
+            ReaderParameter param = rfidManager.getReaderParameter();
+            if (param != null) {
+                Map<String, Object> paramMap = new HashMap<>();
+                paramMap.put("QValue", param.QValue);
+                paramMap.put("Session", param.Session);
+                paramMap.put("ScanTime", param.ScanTime);
+                paramMap.put("TidLen", param.TidLen);
+                paramMap.put("TidPtr", param.TidPtr);
+                paramMap.put("AsciiPtr", param.AsciiPtr);
+                paramMap.put("Interval", param.Interval);
+                result.success(paramMap);
+                Log.d(TAG, "获取阅读器参数成功: " + paramMap);
+            } else {
+                result.success(new HashMap<>());
+                Log.d(TAG, "获取阅读器参数为空");
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "获取阅读器参数失败", e);
+            result.error("GET_PARAM_FAILED", "获取阅读器参数失败: " + e.getMessage(), null);
+        }
+    }
+
+    /**
+     * 处理获取功率请求
+     */
+    private void handleGetPower(Result result) {
+        try {
+            int power = rfidManager.getPower();
+            result.success(power);
+            Log.d(TAG, "获取功率成功: " + power);
+        } catch (Exception e) {
+            Log.e(TAG, "获取功率失败", e);
+            result.error("GET_POWER_FAILED", "获取功率失败: " + e.getMessage(), null);
+        }
+    }
+
     /**
      * 处理设置扫描空闲超时请求
      */
@@ -265,22 +344,34 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
     @Override
     public void onTagScanned(List<InventoryTagMap> tags) {
         // 转换标签数据为Flutter可解析的格式
-        List<Map<String, Object>> tagList = new ArrayList<>();
-        for (InventoryTagMap tag : tags) {
-            if (tag != null && tag.strEPC != null) {
-                Map<String, Object> tagMap = new HashMap<>();
-                tagMap.put("epc", tag.strEPC.trim());
-                tagMap.put("count", rfidManager.getTagMap().getOrDefault(tag.strEPC.trim(), 1));
-                tagList.add(tagMap);
-            }
+        List<RfidModel> rfidModelList = RfidModel.fromInventoryTagMapList(tags);
+        
+        // 将RfidModel列表转换为Map列表以便Flutter可以解析
+        List<Map<String, Object>> rfidMapList = new ArrayList<>();
+        for (RfidModel model : rfidModelList) {
+            rfidMapList.add(model.toMap());
         }
-        sendEvent(RfidEventType.TAG_SCANNED.ordinal(), tagList, null);
+        
+        Log.d(RfidConstants.TAG, "通知扫描结果:" + rfidModelList);
+        sendEvent(RfidEventType.TAG_SCANNED.ordinal(), rfidMapList, null);
     }
 
     @Override
     public void onScanError(String error) {
         sendEvent(RfidEventType.SCAN_ERROR.ordinal(), null, error);
     }
+    @Override
+    public void onScanInfo(String info) {
+        sendEvent(RfidEventType.SCAN_INFO.ordinal(), null, info);
+    }
+
+    // 新增方法:处理按键事件并通知Flutter
+    public void onKeyEvent(int keyCode) {
+        if (keyCode == 619) {
+            // 向Flutter发送按键619事件
+            sendEvent(RfidEventType.KEY_PRESS.ordinal(), "KEY_619", null);
+        } 
+    }
 
     // -------------------------- EventChannel实现 --------------------------
     @Override
@@ -310,8 +401,10 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
             if (data != null) {
                 event.put("data", data);
             }
-            if (error != null) {
+            if(type == RfidEventType.SCAN_ERROR.ordinal() ){
                 event.put("error", error);
+            }else if(type == RfidEventType.SCAN_INFO.ordinal() ){
+                event.put("info", error);
             }
             eventSink.success(event);
         });
@@ -337,6 +430,8 @@ public class RfidMethodCallHandler implements MethodCallHandler, RfidListener, E
         CONNECT_FAILED,  // 1 - 连接失败
         DISCONNECTED,   // 2 - 断开连接
         TAG_SCANNED,     // 3 - 扫描到标签
-        SCAN_ERROR       // 4 - 扫描错误
+        SCAN_INFO,       // 4 - 扫描信息
+        KEY_PRESS,       // 5 - 按键事件
+        SCAN_ERROR       // 6 - 扫描错误
     }
 }

+ 58 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidModel.java

@@ -0,0 +1,58 @@
+package com.vber.chicken_farm.rfid;
+
+import com.rfid.InventoryTagMap;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RfidModel {
+    public String uid;
+    public int count;
+
+    // 默认构造函数
+    public RfidModel() {
+    }
+
+    // 带参数的构造函数
+    public RfidModel(String uid, int count) {
+        this.uid = uid;
+        this.count = count;
+    }
+
+    // 从InventoryTagMap转换为RfidModel的构造函数
+    public RfidModel(InventoryTagMap inventoryTagMap) {
+        if (inventoryTagMap != null) {
+            this.uid = inventoryTagMap.strEPC != null ? inventoryTagMap.strEPC : "";
+            this.count = inventoryTagMap.nReadCount;
+        }
+    }
+
+    // 静态方法:将InventoryTagMap列表转换为RfidModel列表
+    public static List<RfidModel> fromInventoryTagMapList(List<InventoryTagMap> inventoryTagMapList) {
+        List<RfidModel> rfidModelList = new ArrayList<>();
+        if (inventoryTagMapList != null) {
+            for (InventoryTagMap inventoryTagMap : inventoryTagMapList) {
+                rfidModelList.add(new RfidModel(inventoryTagMap));
+            }
+        }
+        return rfidModelList;
+    }
+
+    // toString方法便于调试
+    @Override
+    public String toString() {
+        return "RfidModel{" +
+                "uid='" + uid + '\'' +
+                ", count=" + count +
+                '}';
+    }
+
+    // 转换为Map的方法,用于Flutter序列化
+    public Map<String, Object> toMap() {
+        Map<String, Object> map = new HashMap<>();
+        map.put("uid", uid);
+        map.put("count", count);
+        return map;
+    }
+}

+ 262 - 172
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/rfid/RfidService.java

@@ -1,14 +1,18 @@
 package com.vber.chicken_farm.rfid;
 
+import androidx.annotation.NonNull;
 import android.app.Service;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.SoundPool;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager;
 import android.util.Log;
 
@@ -20,6 +24,9 @@ import com.rfid.trans.ReadTag;
 import com.rfid.trans.ReaderParameter;
 import com.rfid.trans.TagCallback;
 
+import com.vber.chicken_farm.R;
+import com.vber.chicken_farm.service.SoundService;
+
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -36,7 +43,13 @@ import java.util.concurrent.Executors;
  * 5. 线程安全的状态管理
  */
 public class RfidService extends Service {
-    public static final String TAG = RfidConstants.TAG;
+
+    private static final String TAG = RfidConstants.TAG;
+    private static final int MSG_UPDATE_LISTVIEW = 0;
+    private static final int MSG_STOP_SCAN = 1;
+    
+    private SoundService soundService;
+
     // 核心状态变量
     private ILcUhfProduct rrlib;
     private boolean isConnected = false;
@@ -46,23 +59,26 @@ public class RfidService extends Service {
     private ExecutorService executorService;
     private ReaderParameter readerParameter;
     private int power;
-    private final Map<String, Integer> tagMap = new ConcurrentHashMap<>();
     private RfidListener listener;
     private final RfidBinder mBinder = new RfidBinder(this);
 
     // 可配置参数(默认值从常量类读取)
     private int connectRetryTimes = RfidConstants.DEFAULT_CONNECT_RETRY_TIMES;
     private long wakeLockTimeoutMs = RfidConstants.WAKE_LOCK_TIMEOUT_MS;
-    private String[] devPorts = {RfidConstants.DEVPORT, RfidConstants.DEVPORT2};
+    private String[] devPorts = {RfidConstants.DEVPORT};
     private int[] baudRates = RfidConstants.BAUD_LIST;
     private long scanReconnectDelayMs = RfidConstants.DEFAULT_SCAN_RECONNECT_DELAY_MS;
     private long scanIdleTimeoutMs = RfidConstants.SCAN_IDLE_TIMEOUT_MS;
     private int asciiNum;
     public static int ErrorCount;
     public static int ErrorCRC;
+    public boolean isMultipleTag = false; 
+
 
     // 扫描空闲超时处理器
     private Handler idleHandler;
+    // 扫描消息处理器(处理扫描结果)
+    private Handler msgHandler;
     private Runnable idleDisconnectRunnable;
 
     // 屏幕状态广播接收器(处理息屏/亮屏)
@@ -92,6 +108,8 @@ public class RfidService extends Service {
         initIdleHandler(); // 初始化空闲超时处理器
         executorService = Executors.newSingleThreadExecutor();
         initReader();
+        initMsgHandler();
+        initSound();
         registerScreenStateReceiver(); // 注册屏幕状态监听
     }
 
@@ -111,10 +129,11 @@ public class RfidService extends Service {
                 Log.d(RfidConstants.TAG, "扫描空闲超时(" + scanIdleTimeoutMs / 1000 + "秒),自动关闭连接");
                 disconnect(); // 自动断开连接
                 if (listener != null) {
-                    listener.onScanError("扫描空闲超时,已自动断开连接");
+                    listener.onScanInfo("扫描空闲超时,已自动断开连接");
                 }
             }
         };
+
     }
 
     /**
@@ -125,7 +144,7 @@ public class RfidService extends Service {
             // 移除之前的任务,重新计时
             idleHandler.removeCallbacks(idleDisconnectRunnable);
             idleHandler.postDelayed(idleDisconnectRunnable, scanIdleTimeoutMs);
-            Log.d(RfidConstants.TAG, "扫描空闲计时器已重置,超时时间:" + scanIdleTimeoutMs / 1000 + "秒");
+            // Log.d(RfidConstants.TAG, "扫描空闲计时器已重置,超时时间:" + scanIdleTimeoutMs / 1000 + "秒");
         }
     }
 
@@ -161,7 +180,7 @@ public class RfidService extends Service {
     private void initReader() {
         try {
             PowerUtil.power("1"); // 开启电源
-            rrlib = new LcModule(this).createProduct();
+            rrlib = new LcModule(this).createProduct(0x20);//0x20:国芯  0x10:荣睿
             if (rrlib == null) {
                 throw new Exception("创建RFID阅读器实例失败");
             }
@@ -175,6 +194,47 @@ public class RfidService extends Service {
         }
     }
 
+    private void initMsgHandler() {
+        msgHandler = new Handler() {
+            @Override
+            public void handleMessage(@NonNull Message msg) {
+                super.handleMessage(msg);
+                try {
+                    switch (msg.what) {
+                        case MSG_UPDATE_LISTVIEW:
+                            if (listener != null) {
+                                List<InventoryTagMap> inventoryTagMapList = rrlib.getInventoryTagMapList();
+                                listener.onTagScanned(inventoryTagMapList);
+                            }
+                            break;
+                        case MSG_STOP_SCAN:
+                            stopScan();
+                         default:
+                            break;
+                    }
+                } catch (Exception ex) {
+                    ex.toString();
+                }
+            }
+        };
+    }
+
+    private void initSound() {
+        // 使用SoundService播放音效
+        soundService = new SoundService(this);
+        Log.d(RfidConstants.TAG, "初始化音效服务成功");
+
+        // if(rrlib != null){
+        //     SoundPool soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 5);
+        //     soundPool.load(this, R.raw.barcodebeep, 1);
+        //     rrlib.setsoundid(0, soundPool);
+        //     Log.d(RfidConstants.TAG, "初始化音效成功");
+        //     // rrlib.beginSound(true);
+
+        //     rrlib.playSound();
+        // }
+    }
+
     /**
      * 注册屏幕状态广播(处理息屏/亮屏)
      */
@@ -194,6 +254,7 @@ public class RfidService extends Service {
             executorService.execute(() -> {
                 try {
                     rrlib.StopRead();
+                    isScanning = false;
                     resetIdleTimeout(); // 暂停扫描后启动空闲计时
                     Log.d(RfidConstants.TAG, "息屏暂停扫描,保留设备连接");
                 } catch (Exception e) {
@@ -214,14 +275,17 @@ public class RfidService extends Service {
             executorService.execute(() -> {
                 try {
                     Thread.sleep(scanReconnectDelayMs); // 延迟恢复
-                    rrlib.StartRead();
-                    isScanning = true;
+                    // rrlib.StartRead();
+                    // isScanning = true;
                     acquireWakeLock();
-                    stopIdleTimeout(); // 恢复扫描后停止空闲计时
-                    Log.d(RfidConstants.TAG, "亮屏恢复RFID扫描成功");
-                    if (listener != null) {
-                        listener.onTagScanned(rrlib.getInventoryTagMapList()); // 触发一次扫描回调
+                    if(!isConnected){
+                        connect();
                     }
+                    // stopIdleTimeout(); // 恢复扫描后停止空闲计时
+                    // Log.d(RfidConstants.TAG, "亮屏恢复RFID扫描成功");
+                    // if (listener != null) {
+                    //     listener.onTagScanned(rrlib.getInventoryTagMapList()); // 触发一次扫描回调
+                    // }
                 } catch (InterruptedException e) {
                     Thread.currentThread().interrupt();
                     Log.e(RfidConstants.TAG, "恢复扫描线程被中断", e);
@@ -388,65 +452,71 @@ public class RfidService extends Service {
             }
         }
 
-        executorService.execute(() -> {
-            boolean connected = false;
-            int result = -1;
-
-            try {
-                for (int i = 0; i < retryTimes && !connected; i++) {
-                    Log.d(RfidConstants.TAG, "连接重试第 " + (i + 1) + " 次");
-                    for (String port : devPorts) {
-                        for (int baud : baudRates) {
-                            // 尝试连接端口
-                            result = rrlib.Connect(port, baud);
-                            Log.d(RfidConstants.TAG, "Connect to " + port + " with baud " + baud + ": " + result);
-
-                            if (result == 0) {
-                                // 连接成功
-                                isConnected = true;
-                                connected = true;
-                                Log.d(RfidConstants.TAG, "RFID设备连接成功:端口=" + port + ",波特率=" + baud);
-                                if (listener != null) {
-                                    listener.onConnectSuccess(port, baud);
-                                }
-                                // 连接成功后立即获取阅读器参数
-                                readerParameter = rrlib.GetInventoryParameter();
-                                byte[] Version = new byte[4];
-                                byte[] Power = new byte[1];
-                                byte[] band = new byte[1];
-                                byte[] MaxFre = new byte[1];
-                                byte[] MinFre = new byte[1];
-                                byte[] BeepEn = new byte[1];
-                                byte[] Ant = new byte[1];
-                                byte[] ScanTime = new byte[1];
-                                byte[] powermode = new byte[1];
-
-                                int b_result = rrlib.GetUHFInformation(Version, Power, band, MaxFre, MinFre, BeepEn, Ant);
-                                if (b_result == 0) {
-                                   this.power = Power[0];
-                                }
-                                Log.d(RfidConstants.TAG, "Reader parameter: " + readerParameter);
-                                return; // 跳出所有循环
+        boolean connected = false;
+        int result = -1;
+        try {
+            for (int i = 1; i <= retryTimes && !connected; i++) {
+                Log.d(RfidConstants.TAG, "====> 第 " + i + " 次连接 <====");
+                for (String port : devPorts) {
+                    for (int baud : baudRates) {
+                        // 尝试连接端口
+                        result = rrlib.Connect(port, baud);
+                        Log.i(RfidConstants.TAG, "RFID设备连接到" + port + ",波率: " + baud + ",结果: " + result);
+
+                        if (result == 0) {
+                            // 连接成功
+                            isConnected = true;
+                            connected = true;
+                            Log.d(RfidConstants.TAG, "RFID设备连接成功:端口=" + port + ",波特率=" + baud);
+                            if (listener != null) {
+                                listener.onConnectSuccess(port, baud);
                             }
+                            // 连接成功后立即获取阅读器参数
+                            readerParameter = rrlib.GetInventoryParameter();
+                            byte[] Version = new byte[4];
+                            byte[] Power = new byte[1];
+                            byte[] band = new byte[1];
+                            byte[] MaxFre = new byte[1];
+                            byte[] MinFre = new byte[1];
+                            byte[] BeepEn = new byte[1];
+                            byte[] Ant = new byte[1];
+                            byte[] ScanTime = new byte[1];
+                            byte[] powermode = new byte[1];
+
+                            int b_result = rrlib.GetUHFInformation(Version, Power, band, MaxFre, MinFre, BeepEn, Ant);
+                            if (b_result == 0) {
+                                this.power = Power[0];
+                                Log.d(RfidConstants.TAG, "Reader 功率: " + this.power);
+                            }
+                            Log.d(RfidConstants.TAG, "Reader 参数: " + readerParameter);
+                            Log.d(RfidConstants.TAG, "Reader QValue: " + readerParameter.QValue);
+                            Log.d(RfidConstants.TAG, "Reader Session: " + readerParameter.Session);
+                            Log.d(RfidConstants.TAG, "Reader ScanTime: " + readerParameter.ScanTime);
+                            Log.d(RfidConstants.TAG, "Reader Interval: " + readerParameter.Interval);
+                            Log.d(RfidConstants.TAG, "Reader TidLen: " + readerParameter.TidLen);
+                            Log.d(RfidConstants.TAG, "Reader TidPtr: " + readerParameter.TidPtr);
+                            break; // 跳出所有循环
+                        }else {
+                            Log.e(RfidConstants.TAG, "连接["+ i +"]: " + result);
                         }
                     }
-                }
+                } 
+            }
 
-                // 所有重试失败
-                if (!connected) {
-                    Log.e(RfidConstants.TAG, "RFID设备连接失败,已重试 " + retryTimes + " 次");
-                    if (listener != null) {
-                        listener.onConnectFailed();
-                    }
-                }
-            } catch (Exception e) {
-                Log.e(RfidConstants.TAG, "Connect error", e);
-                isConnected = false;
+            // 所有重试失败
+            if (!connected) {
+                Log.e(RfidConstants.TAG, "RFID设备连接失败,已重试 " + retryTimes + " 次");
                 if (listener != null) {
                     listener.onConnectFailed();
                 }
             }
-        });
+        } catch (Exception e) {
+            Log.e(RfidConstants.TAG, "Connect error", e);
+            isConnected = false;
+            if (listener != null) {
+                listener.onConnectFailed();
+            }
+        }
 
         return false; // 异步连接,返回false表示正在连接中
     }
@@ -480,11 +550,12 @@ public class RfidService extends Service {
             }
         });
     }
-
-    /**
-     * 开始扫描标签
-     */
-    public boolean startScan() {
+    
+    public boolean startScan(boolean multipleTag) {
+        Log.d(RfidConstants.TAG, "=====>RFID设备开始扫描");
+        if(!isConnected){
+            connect();
+        }
         if (!isConnected) {
             Log.e(RfidConstants.TAG, "RFID设备未连接,无法开始扫描");
             if (listener != null) {
@@ -497,38 +568,60 @@ public class RfidService extends Service {
             stopIdleTimeout(); // 重复启动也重置计时
             return true;
         }
-
+        if(this.isMultipleTag != multipleTag){
+            this.isMultipleTag = multipleTag;
+            setReaderParameter(null);
+            setPower();
+        }
+        Log.d(RfidConstants.TAG, "RFID设备扫描,功率:" + getPower() + "W");
+        return _startScan();
+    }
+    /**
+     * 开始扫描标签
+     */
+    private boolean _startScan() {
         isScanning = true;
         acquireWakeLock(); // 息屏保持唤醒
         stopIdleTimeout(); // 开始扫描,停止空闲计时
-
-        executorService.execute(() -> {
-            try {
-                // 清空历史标签数据
-                tagMap.clear();
-                rrlib.getInventoryTagMapList().clear();
-                rrlib.getInventoryTagResultList().clear();
-                MsgCallback callback = new MsgCallback();
-                // 设置扫描回调
-                rrlib.SetCallBack(callback);
-                ErrorCount = 0;
-                ErrorCRC = 0;
-                // 启动扫描
-                int result = rrlib.StartRead();
-                if (result != 0) {
-                    throw new Exception("Start scan failed with result: " + result);
-                }
-                Log.d(RfidConstants.TAG, "RFID扫描已启动");
-            } catch (Exception e) {
-                Log.e(RfidConstants.TAG, "Start scan error", e);
+        try {
+            // 清空历史标签数据
+            rrlib.getInventoryTagMapList().clear();
+            rrlib.getInventoryTagResultList().clear();
+            MsgCallback callback = new MsgCallback();
+            // 设置扫描回调
+            rrlib.SetCallBack(callback);
+            ErrorCount = 0;
+            ErrorCRC = 0;
+            // 启动扫描
+            int result = rrlib.StartRead();
+            if (result != 0) {
+                // throw new Exception("未能识别到电子标签!");
                 isScanning = false;
-                releaseWakeLock();
-                resetIdleTimeout(); // 启动失败,启动空闲计时
                 if (listener != null) {
-                    listener.onScanError("开始扫描失败:" + e.getMessage());
+                    listener.onScanError("未能识别到电子标签!");
                 }
+                return false;
             }
-        });
+            Log.d(RfidConstants.TAG, "RFID扫描已启动");
+            // 1 秒后停止扫描
+            idleHandler.postDelayed(() -> {
+                Log.d(RfidConstants.TAG, "1秒RFID扫描自动停止");
+                if (isScanning) {
+                    stopScan();
+                    if(listener != null && (!isMultipleTag || isMultipleTag && rrlib.getInventoryTagMapList().size()==0 )){
+                        listener.onScanError("未扫描到电子编号");
+                    }
+                }
+            }, 1 * 1000);
+        } catch (Exception e) {
+            Log.e(RfidConstants.TAG, "扫描失败:", e);
+            isScanning = false;
+            releaseWakeLock();
+            resetIdleTimeout(); // 启动失败,启动空闲计时
+            if (listener != null) {
+                listener.onScanError("扫描失败:" + e.getMessage());
+            }
+        }
 
         return true;
     }
@@ -538,24 +631,21 @@ public class RfidService extends Service {
      */
     public void stopScan() {
         if (!isScanning) {
-            Log.w(RfidConstants.TAG, "RFID未在扫描中,无需停止");
+            // Log.w(RfidConstants.TAG, "RFID未在扫描中,无需停止");
             return;
         }
-
-        executorService.execute(() -> {
-            try {
-                rrlib.StopRead();
-                isScanning = false;
-                releaseWakeLock(); // 释放唤醒锁
-                resetIdleTimeout(); // 停止扫描,启动空闲计时
-                Log.d(RfidConstants.TAG, "RFID扫描已停止");
-            } catch (Exception e) {
-                Log.e(RfidConstants.TAG, "Stop scan error", e);
-                if (listener != null) {
-                    listener.onScanError("停止扫描失败:" + e.getMessage());
-                }
+        try {
+            rrlib.StopRead();
+            isScanning = false;
+            releaseWakeLock(); // 释放唤醒锁
+            resetIdleTimeout(); // 停止扫描,启动空闲计时
+            Log.d(RfidConstants.TAG, "RFID扫描已停止");
+        } catch (Exception e) {
+            Log.e(RfidConstants.TAG, "Stop scan error", e);
+            if (listener != null) {
+                listener.onScanError("停止扫描失败:" + e.getMessage());
             }
-        });
+        }
     }
 
     /**
@@ -586,26 +676,26 @@ public class RfidService extends Service {
         } else {
             paramToUse = param;
         }
+        paramToUse.QValue = isMultipleTag ? 4 : 0;
         // 打印参数
+        Log.d(RfidConstants.TAG, "isMultipleTag: " + isMultipleTag);
         Log.d(RfidConstants.TAG, "ScanTime: " + paramToUse.ScanTime);
         Log.d(RfidConstants.TAG, "QValue: " + paramToUse.QValue);
         Log.d(RfidConstants.TAG, "Session: " + paramToUse.Session);
         Log.d(RfidConstants.TAG, "TidLen: " + paramToUse.TidLen);
         Log.d(RfidConstants.TAG, "TidPtr: " + paramToUse.TidPtr);
         Log.d(RfidConstants.TAG, "AsciiPtr: " + paramToUse.AsciiPtr);
-        Log.d(RfidConstants.TAG, "jgTimes: " + paramToUse.Interval);
-        executorService.execute(() -> {
-            try {
-                rrlib.SetInventoryParameter(paramToUse);
-                this.readerParameter = paramToUse;
-                Log.d(RfidConstants.TAG, "阅读器参数设置成功");
-            } catch (Exception e) {
-                Log.e(RfidConstants.TAG, "Set parameter error", e);
-                if (listener != null) {
-                    listener.onScanError("设置阅读器参数失败:" + e.getMessage());
-                }
+        Log.d(RfidConstants.TAG, "Interval: " + paramToUse.Interval);
+        try {
+            rrlib.SetInventoryParameter(paramToUse);
+            this.readerParameter = paramToUse;
+            Log.d(RfidConstants.TAG, "阅读器参数设置成功");
+        } catch (Exception e) {
+            Log.e(RfidConstants.TAG, "阅读器参数设置错误:", e);
+            if (listener != null) {
+                listener.onScanError("设置阅读器参数失败:" + e.getMessage());
             }
-        });
+        }
     }
 
     public void setPower(int power) {
@@ -624,18 +714,20 @@ public class RfidService extends Service {
             Log.w(RfidConstants.TAG, "功率不能大于33W,忽略设置");
             return;
         }
-        executorService.execute(() -> {
-            try {
-                rrlib.SetRfPower(power);
-                this.power = power;
-                Log.d(RfidConstants.TAG, "功率设置成功");
-            } catch (Exception e) {
-                Log.e(RfidConstants.TAG, "Set power error", e);
-                if (listener != null) {
-                    listener.onScanError("设置功率失败:" + e.getMessage());
-                }
+        try {
+            rrlib.SetRfPower(power);
+            this.power = power;
+            Log.d(RfidConstants.TAG, "功率设置成功");
+        } catch (Exception e) {
+            Log.e(RfidConstants.TAG, "设置功率失败:", e);
+            if (listener != null) {
+                listener.onScanError("设置功率失败:" + e.getMessage());
             }
-        });
+        }
+    }
+
+    public void setMultipleTag(boolean multipleTag) {
+        this.isMultipleTag = multipleTag;
     }
 
     // -------------------------- 状态查询API --------------------------
@@ -648,9 +740,7 @@ public class RfidService extends Service {
         return power;
     }
 
-    public Map<String, Integer> getTagMap() {
-        return new ConcurrentHashMap<>(tagMap); // 返回副本,避免外部修改
-    }
+
 
     public boolean isConnected() {
         return isConnected;
@@ -664,6 +754,10 @@ public class RfidService extends Service {
         return isScreenOn;
     }
 
+    public boolean isMultipleTag() {
+        return isMultipleTag;
+    }
+
     public int getConnectRetryTimes() {
         return connectRetryTimes;
     }
@@ -677,20 +771,17 @@ public class RfidService extends Service {
     }
 
     // -------------------------- 内部工具方法 --------------------------
-    /**
-     * 更新标签计数
-     */
-    private void updateTagMap(List<InventoryTagMap> tags) {
-        for (InventoryTagMap tag : tags) {
-            if (tag != null && tag.strEPC != null) {
-                String epc = tag.strEPC.trim();
-                if (!epc.isEmpty()) {
-                    tagMap.put(epc, tagMap.getOrDefault(epc, 0) + 1);
-                }
-            }
+
+
+    private void setPower() { 
+        if(isMultipleTag){
+            setPower(RfidConstants.POWER_HIGH);
+            Log.d(RfidConstants.TAG, "RFID设备启动多标签扫描,功率:" + getPower() + "W");
+        }else{
+            setPower(RfidConstants.POWER_LOW);
+            Log.d(RfidConstants.TAG, "RFID设备启动单标签扫描,功率:" + getPower() + "W");
         }
     }
-
     /**
      * 获取唤醒锁(处理息屏场景)
      */
@@ -738,52 +829,50 @@ public class RfidService extends Service {
             if (arg != null) {
                 // 读取EPC并处理编码格式
                 String epc = arg.epcId.toUpperCase();
-                
                 int encodingFormat = readerParameter.AsciiPtr;
                 if (encodingFormat == 1 && readerParameter.TidLen == 0) {
                     asciiNum = 1;
                 } else {
                     asciiNum = 0;
                 }
-                
                 // 转换EPC为ASCII格式(如果需要)
                 if (asciiNum == 1) {
                     epc = hexToAscii(epc);
                 }
+                final String processedEpc = epc;  // 创建一个局部final变量供lambda使用
+                InventoryTagMap m = rrlib.getInventoryTagMapList().stream()
+                .filter(item -> processedEpc.equals(item.strEPC))
+                .findFirst()
+                .orElse(null);
 
-                InventoryTagMap m;
-                Integer findIndex = tagMap.get(epc);
-                Log.d(RfidConstants.TAG, "tagCallback: findIndex -->" + findIndex);
-
-                if (findIndex == null) {
+                if (m == null) {
                     // 新标签:播放提示音并添加到列表
-                    rrlib.beginSound(false);
-                    tagMap.put(epc, tagMap.size());
+                    Log.d(RfidConstants.TAG, "tagCallback: new InventoryTagMap :" +m);
                     m = new InventoryTagMap();
-                    m.strEPC = epc;
+                    m.strEPC = processedEpc;
                     m.antenna = arg.antId;
                     m.strRSSI = String.valueOf(arg.rssi);
                     m.nReadCount = 1;
                     rrlib.getInventoryTagMapList().add(m);
+                    // 使用SoundService播放音效
+                    soundService.playBeep();
                 } else {
-                    // 已有标签:更新计数和信号强度
-                    rrlib.beginSound(true);
-                    m = rrlib.getInventoryTagMapList().get(findIndex);
-                    m.antenna |= arg.antId;
+                    if(!isMultipleTag){
+                        msgHandler.removeMessages(MSG_STOP_SCAN);
+                        msgHandler.sendEmptyMessage(MSG_STOP_SCAN);
+                        if(isScanning){
+                            msgHandler.removeMessages(MSG_UPDATE_LISTVIEW);
+                            msgHandler.sendEmptyMessageDelayed(MSG_UPDATE_LISTVIEW, 50);
+                        }
+                    }
                     m.nReadCount++;
-                    m.strRSSI = String.valueOf(arg.rssi);
                 }
-
-                // 更新标签计数并触发回调
-                updateTagMap(rrlib.getInventoryTagMapList());
-                resetIdleTimeout(); // 有数据,重置空闲计时
-                if (listener != null) {
-                    listener.onTagScanned(rrlib.getInventoryTagMapList());
+                if(isMultipleTag){
+                    Log.d(RfidConstants.TAG, "tagCallback: TAG_COUNT:" + rrlib.getInventoryTagMapList().size());
+                    msgHandler.removeMessages(MSG_UPDATE_LISTVIEW);
+                    msgHandler.sendEmptyMessageDelayed(MSG_UPDATE_LISTVIEW, 100);
                 }
-            } else {
-                rrlib.beginSound(false);
-            }
-
+            } 
         }
 
         @Override
@@ -808,7 +897,7 @@ public class RfidService extends Service {
             isScanning = false;
             resetIdleTimeout();
             if (listener != null) {
-                listener.onScanError("扫描已完成");
+                listener.onScanInfo("扫描已完成");
             }
         }
 
@@ -822,6 +911,7 @@ public class RfidService extends Service {
             return 0;
         }
     }
+    
 
     private static String hexToAscii(String hexStr) {
         StringBuilder output = new StringBuilder("");

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

@@ -15,7 +15,7 @@ import android.util.Log;
 /**
  * 扫码核心服务(扫描头独立管理+双超时+低功耗)
  * 适配场景:
- * 1. 提前打开扫描头(进入场景时),扫码时直接复用,提升响应速度;
+ * 1. 打开扫描头(进入场景时),扫码时直接复用,提升响应速度;
  * 2. 单次扫码超时(5秒):停止解码,保留扫描头;
  * 3. 闲置超时(5分钟):无新扫码请求则关闭扫描头+注销广播;
  * 4. 离开场景可手动关闭扫描头,立即释放资源;
@@ -37,7 +37,6 @@ public class ScanService extends Service {
     private ScanCallback scanCallback;
     private PowerManager.WakeLock wakeLock;
     private boolean isScanning = false; // 是否正在解码
-    private boolean isScanHeadOpened = false; // 扫描头是否打开
     private Handler scanHandler; // 统一处理延迟任务
     private Runnable singleTimeoutRunnable; // 单次扫码超时任务
     private Runnable idleTimeoutRunnable; // 扫描头闲置超时任务
@@ -46,7 +45,7 @@ public class ScanService extends Service {
     private final BroadcastReceiver screenReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent == null || intent.getAction() == null || !isScanHeadOpened) return;
+            if (intent == null || intent.getAction() == null || !isScanHeadOpened()) return;
             
             switch (intent.getAction()) {
                 case Intent.ACTION_SCREEN_ON:
@@ -153,11 +152,11 @@ public class ScanService extends Service {
 
     // -------------------------- 扫描头独立管理API(给Flutter调用) --------------------------
     /**
-     * 提前打开扫描头(进入场景时调用)
+     * 打开扫描头(进入场景时调用)
      * 逻辑:初始化扫描硬件+注册广播+启动闲置超时+默认开启瞄准灯
      */
     public boolean openScanHead() {
-        if (isScanHeadOpened) {
+        if (isScanHeadOpened()) {
             Log.w(TAG, "扫描头已打开,无需重复调用");
             resetIdleTimeout(); // 重置闲置超时
             return true;
@@ -172,6 +171,9 @@ public class ScanService extends Service {
             setDefaultParams();
             // 2. 打开扫描头硬件
             scanManager.openScanner();
+            while (!scanManager.isScannerOpen()) {
+                Thread.sleep(50);
+            }
             // 3. 设置默认灯光(照明+瞄准)
             setLightingMode(DEFAULT_LIGHT_MODE);
             // 4. 设置广播模式
@@ -179,10 +181,9 @@ public class ScanService extends Service {
             // 5. 注册广播接收器
             registerScanReceivers();
             
-            isScanHeadOpened = true;
             // 6. 启动扫描头闲置超时(核心调整:打开扫描头就启动)
             startIdleTimeout();
-            Log.d(TAG, "扫描头已提前打开(闲置超时:" + SCAN_IDLE_TIMEOUT_MS + "ms)");
+            Log.d(TAG, "扫描头已打开(闲置超时:" + SCAN_IDLE_TIMEOUT_MS + "ms)");
             return true;
         } catch (Exception e) {
             Log.e(TAG, "打开扫描头失败", e);
@@ -269,7 +270,7 @@ public class ScanService extends Service {
         }
 
         // 3. 关闭扫描头硬件
-        if (scanManager != null && isScanHeadOpened) {
+        if (scanManager != null && isScanHeadOpened()) {
             try {
                 scanManager.closeScanner();
                 Log.d(TAG, "扫描头已手动关闭");
@@ -283,7 +284,6 @@ public class ScanService extends Service {
 
         // 5. 更新状态
         isScanning = false;
-        isScanHeadOpened = false;
     }
 
     // -------------------------- 扫码解码API(给Flutter调用) --------------------------
@@ -297,8 +297,8 @@ public class ScanService extends Service {
             return true;
         }
 
-        // 1. 若扫描头未打开,自动打开(兼容未提前打开的场景)
-        if (!isScanHeadOpened) {
+        // 1. 若扫描头未打开,自动打开
+        if (!isScanHeadOpened()) {
             boolean openSuccess = openScanHead();
             if (!openSuccess) {
                 return false;
@@ -353,7 +353,7 @@ public class ScanService extends Service {
     private void startIdleTimeout() {
         cancelIdleTimeout(); // 先取消旧任务
         idleTimeoutRunnable = () -> {
-            if (isScanHeadOpened && !isScanning) {
+            if (isScanHeadOpened() && !isScanning) {
                 Log.d(TAG, "扫描头闲置超时(" + SCAN_IDLE_TIMEOUT_MS + "ms),自动关闭");
                 closeScanHead(); // 关闭扫描头+注销广播
                 if (scanCallback != null) {
@@ -368,7 +368,7 @@ public class ScanService extends Service {
      * 重置扫描头闲置超时(扫码/手动刷新时调用)
      */
     public void resetIdleTimeout() {
-        if (isScanHeadOpened && idleTimeoutRunnable != null) {
+        if (isScanHeadOpened() && idleTimeoutRunnable != null) {
             scanHandler.removeCallbacks(idleTimeoutRunnable);
             scanHandler.postDelayed(idleTimeoutRunnable, SCAN_IDLE_TIMEOUT_MS);
             Log.d(TAG, "扫描头闲置超时已重置");
@@ -450,7 +450,7 @@ public class ScanService extends Service {
      * 恢复扫码(屏幕亮起时):重新解码+重置单次超时
      */
     private void resumeScan() {
-        if (scanManager != null && isScanHeadOpened) {
+        if (scanManager != null && isScanHeadOpened()) {
             try {
                 scanManager.startDecode();
                 Log.d(TAG, "扫码已恢复(屏幕亮起)");
@@ -503,14 +503,17 @@ public class ScanService extends Service {
     }
 
     public boolean isScanHeadOpened() {
-        return isScanHeadOpened;
+        if (scanManager == null) {
+            return false;
+        }
+        return scanManager.isScannerOpen();
     }
     
     /**
      * 设置灯光模式
      */
     public void setLightingMode(int mode) {
-        if (scanManager != null && isScanHeadOpened) {
+        if (scanManager != null && isScanHeadOpened()) {
             try {
                 // {DecoderConfigValues.ParamID.CODE128_ENABLE} 条码模式
                 // {DecoderConfigValues.ParamID.LIGHTS_MODE} 灯光模式

+ 50 - 11
UI/CF.APP/chicken_farm/lib/components/vb_rfid_field.dart

@@ -1,20 +1,25 @@
+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/core/services/pda/rfid_manager.dart';
 import 'package:chicken_farm/core/utils/toast.dart';
 
 /// RFID字段组件,支持单个或多个RFID显示
 ///
-/// 当[multiple]为true时,显示已扫描的RFID数量
+/// 当[multiple]为true时,显示已识别的RFID数量
 /// 当[multiple]为false时,显示单个RFID的具体值
 class VberRfidField extends StatefulWidget {
   final List<String>? rfids;
   final String? rfid;
-  final ValueChanged<String>? onRfidScanned; // RFID扫描成功回调
+  final ValueChanged<String>? onRfidScanned; // RFID识别成功回调
+  final ValueChanged<List<RfidModel>>? onRfidsScanned; // RFID识别成功回调
+  final ValueChanged<String>? onKeyPress; // 按键事件
   final bool multiple;
+  final bool? multipleScan;
   final ValueChanged<int>? onDeleteRfid; // 仅在multiple模式下使用
   final String label; // 显示的标签文本
-  final String placeholder; // 未扫描时显示的占位符文本
-  final String multiplePlaceholder; // 多个模式下未扫描时显示的占位符文本
+  final String placeholder; // 未识别时显示的占位符文本
+  final String multiplePlaceholder; // 多个模式下未识别时显示的占位符文本
   final String multipleFormat; // 多个模式下有值时的显示格式,包含一个%d占位符表示数量
 
   const VberRfidField({
@@ -22,12 +27,15 @@ class VberRfidField extends StatefulWidget {
     this.rfids,
     this.rfid,
     this.onRfidScanned,
+    this.onRfidsScanned,
+    this.onKeyPress,
     this.multiple = false,
+    this.multipleScan,
     this.onDeleteRfid,
     this.label = '电子编号',
-    this.placeholder = '未扫描',
-    this.multiplePlaceholder = '未扫描',
-    this.multipleFormat = '已扫描 %d 只鸡',
+    this.placeholder = '未识别',
+    this.multiplePlaceholder = '未识别',
+    this.multipleFormat = '已识别 %d 枚电子编号',
   });
 
   @override
@@ -36,12 +44,19 @@ class VberRfidField extends StatefulWidget {
 
 class _VberRfidFieldState extends State<VberRfidField> {
   late final RfidManager _rfidManager;
-  bool _isScanning = false; // 内部管理扫描状态
+  bool _isScanning = false; // 内部管理识别状态
+  bool _isMultiple = false; // 内部管理识别状态
 
   @override
   void initState() {
     super.initState();
-    _rfidManager = RfidManager(_handleRfidScanned, null);
+    _rfidManager = RfidManager(
+      onTagScanned: _handleRfidsScanned,
+      onRFIDScanned: _handleRfidScanned,
+      onKeyPress: _handleKeyPress,
+      onErrorScanned: _handleScanError,
+    );
+    _isMultiple = widget.multipleScan ?? widget.multiple;
     _rfidManager.initRfid();
   }
 
@@ -52,6 +67,7 @@ class _VberRfidFieldState extends State<VberRfidField> {
   }
 
   void _handleRfidScanned(String rfid) {
+    logger.d('识别成功,已识别电子编号:$rfid');
     setState(() {
       _isScanning = false;
     });
@@ -60,12 +76,35 @@ class _VberRfidFieldState extends State<VberRfidField> {
     }
   }
 
+  void _handleRfidsScanned(List<RfidModel> rfidList) {
+    logger.d('识别成功,已识别电子编号:$rfidList');
+    setState(() {
+      _isScanning = false;
+    });
+    if (widget.onRfidsScanned != null) {
+      widget.onRfidsScanned!(rfidList);
+    }
+  }
+
+  void _handleScanError(String error) {
+    ToastUtil.error(error);
+    setState(() {
+      _isScanning = false;
+    });
+  }
+
+  void _handleKeyPress(String keyCode) {
+    if (keyCode == 'KEY_619') {
+      _scanRfid();
+    }
+  }
+
   void _scanRfid() {
     setState(() {
       _isScanning = true;
     });
-    ToastUtil.success('开始扫描RFID');
-    _rfidManager.startScan();
+    ToastUtil.success('开始识别电子编号');
+    _rfidManager.startScan(isMultiple: _isMultiple);
   }
 
   @override

+ 27 - 6
UI/CF.APP/chicken_farm/lib/core/services/connectivity_service.dart

@@ -5,6 +5,11 @@ import 'package:connectivity_plus/connectivity_plus.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:dio/dio.dart';
 
+// 添加防抖动机制,避免频繁检查连接状态
+const Duration _connectionCheckDebounce = Duration(seconds: 5);
+DateTime? _lastCheckTime;
+bool? _lastCheckResult;
+
 class ConnectivityService {
   final Connectivity _connectivity = Connectivity();
 
@@ -19,6 +24,15 @@ class ConnectivityService {
   }
 
   Future<bool> _checkConnection() async {
+    // 实现防抖动逻辑,避免短时间内重复检查
+    final now = DateTime.now();
+    if (_lastCheckTime != null && 
+        now.difference(_lastCheckTime!) < _connectionCheckDebounce) {
+      return _lastCheckResult ?? false;
+    }
+    
+    _lastCheckTime = now;
+    
     try {
       // 解析 baseUrl 获取主机名和端口
       final uri = Uri.parse(AppConfig.baseUrl);
@@ -28,6 +42,7 @@ class ConnectivityService {
 
       if (host.isEmpty) {
         logger.e('无效的主机地址: ${AppConfig.baseUrl}');
+        _lastCheckResult = false;
         return false;
       }
 
@@ -36,14 +51,16 @@ class ConnectivityService {
       // 对于局域网地址,我们直接尝试连接而不是仅做DNS查找
       try {
         final dio = Dio();
-        // 设置短的超时时间以快速响应
-        dio.options.connectTimeout = const Duration(seconds: 10);
-        dio.options.receiveTimeout = const Duration(seconds: 10);
+        // 设置短的超时时间以快速响应
+        dio.options.connectTimeout = const Duration(seconds: 3);
+        dio.options.receiveTimeout = const Duration(seconds: 3);
 
         // 尝试访问基础URL来检查连接
-        final response = await dio.get(AppConfig.baseUrl);
+        final response = await dio.get('${uri.scheme}://${uri.host}:${uri.port}/',
+            options: Options(method: 'HEAD')); // 使用 HEAD 请求减少数据传输
         logger.d('连接检查成功: 状态码=${response.statusCode}');
-        return response.statusCode != null && response.statusCode! < 400;
+        _lastCheckResult = response.statusCode != null && response.statusCode! < 400;
+        return _lastCheckResult!;
       } catch (e) {
         logger.d('HTTP连接检查失败: $e');
         // 如果HTTP检查失败,退回到基本的连通性检查
@@ -52,16 +69,20 @@ class ConnectivityService {
         final hasConnection =
             result.isNotEmpty && result[0].rawAddress.isNotEmpty;
         logger.d('DNS查找结果: $hasConnection');
+        _lastCheckResult = hasConnection;
         return hasConnection;
       }
     } on SocketException catch (e) {
       logger.d('网络连接检查: 无网络连接 - $e');
+      _lastCheckResult = false;
       return false;
     } on FormatException catch (e) {
       logger.d('网络连接检查: URL格式错误 - $e');
+      _lastCheckResult = false;
       return false;
     } catch (e) {
       logger.d('网络连接检查: 其他错误 - $e');
+      _lastCheckResult = false;
       return false;
     }
   }
@@ -71,4 +92,4 @@ final connectivityServiceProvider = Provider((ref) => ConnectivityService());
 
 final isConnectedProvider = StreamProvider<bool>((ref) {
   return ref.watch(connectivityServiceProvider).isConnectedStream;
-});
+});

+ 46 - 11
UI/CF.APP/chicken_farm/lib/core/services/pda/rfid_channel.dart

@@ -18,14 +18,14 @@ class RfidChannel {
     }
   }
 
-  /// 解绑RFID服务
-  static Future<void> unbindService() async {
-    try {
-      await _channel.invokeMethod('unbindService');
-    } on PlatformException catch (e) {
-      logger.e('解绑RFID服务失败: ${e.message}');
-    }
-  }
+  // /// 解绑RFID服务
+  // static Future<void> unbindService() async {
+  //   try {
+  //     await _channel.invokeMethod('unbindService');
+  //   } on PlatformException catch (e) {
+  //     logger.e('解绑RFID服务失败: ${e.message}');
+  //   }
+  // }
 
   /// 连接RFID设备
   /// [retryTimes] 连接重试次数(默认使用常量配置的3次)
@@ -51,9 +51,11 @@ class RfidChannel {
   }
 
   /// 开始扫描标签
-  static Future<bool> startScan() async {
+  static Future<bool> startScan(bool isMultiple) async {
     try {
-      final bool result = await _channel.invokeMethod('startScan');
+      final bool result = await _channel.invokeMethod('startScan', {
+        "multipleTag": isMultiple,
+      });
       return result;
     } on PlatformException catch (e) {
       logger.e('开始RFID扫描失败: ${e.message}');
@@ -92,6 +94,35 @@ class RfidChannel {
     }
   }
 
+  /// 获取阅读器参数
+  static Future<Map<String, dynamic>> getReaderParameter() async {
+    try {
+      final Map<dynamic, dynamic>? result = await _channel.invokeMapMethod(
+        'getReaderParameter',
+      );
+      // 检查结果是否为空
+      if (result == null) {
+        return {};
+      }
+      // 将动态键转换为字符串键
+      return result.map((key, value) => MapEntry(key.toString(), value));
+    } on PlatformException catch (e) {
+      logger.e('获取阅读器参数失败: ${e.message}');
+      return {};
+    }
+  }
+
+  /// 获取功率
+  static Future<int> getPower() async {
+    try {
+      final int result = await _channel.invokeMethod('getPower');
+      return result;
+    } on PlatformException catch (e) {
+      logger.e('获取功率失败: ${e.message}');
+      return 0;
+    }
+  }
+
   /// 监听RFID回调事件
   static Stream<RfidEvent> get rfidEvents {
     return _eventChannel.receiveBroadcastStream().map((dynamic event) {
@@ -109,8 +140,9 @@ class RfidEvent {
   final RfidEventType type;
   final dynamic data;
   final String? error;
+  final String? info;
 
-  RfidEvent({required this.type, this.data, this.error});
+  RfidEvent({required this.type, this.data, this.error, this.info});
 
   /// 从原生返回的Map解析事件
   factory RfidEvent.fromMap(Map<dynamic, dynamic> map) {
@@ -118,6 +150,7 @@ class RfidEvent {
       type: RfidEventType.values[map['type'] as int],
       data: map['data'],
       error: map['error'] as String?,
+      info: map['info'] as String?,
     );
   }
 }
@@ -128,5 +161,7 @@ enum RfidEventType {
   connectFailed, // 连接失败
   disconnected, // 断开连接
   tagScanned, // 扫描到标签
+  scanInfo, // 扫描信息
+  onKeyPress, // 按键按下
   scanError, // 扫描错误
 }

+ 100 - 38
UI/CF.APP/chicken_farm/lib/core/services/pda/rfid_manager.dart

@@ -1,31 +1,43 @@
 import 'package:chicken_farm/core/utils/logger.dart';
-import 'package:chicken_farm/core/utils/toast.dart';
+import 'package:chicken_farm/modes/rfid/rfid_model.dart';
 import 'rfid_channel.dart';
 
 // 定义处理扫描到的标签数据的回调函数类型
-typedef TagScannedCallback = void Function(List<Map<String, dynamic>> tags);
+typedef TagScannedCallback = void Function(List<RfidModel> tags);
 typedef RFIDScannedCallback = void Function(String rfId);
+typedef ScannedErrorCallback = void Function(String error);
+typedef ScannOnKeyPress = void Function(String error);
 
 /// RFID 工具类(纯逻辑调用)
 class RfidManager {
   // 存储标签扫描回调函数
   final TagScannedCallback? _onTagScanned;
   final RFIDScannedCallback? _onRFIDScanned;
+  final ScannedErrorCallback? _onErrorScanned;
+  final ScannOnKeyPress? _onKeyPress;
 
-  RfidManager(this._onRFIDScanned, this._onTagScanned);
+  RfidManager({
+    TagScannedCallback? onTagScanned,
+    RFIDScannedCallback? onRFIDScanned,
+    ScannedErrorCallback? onErrorScanned,
+    ScannOnKeyPress? onKeyPress,
+  }) : _onTagScanned = onTagScanned,
+       _onRFIDScanned = onRFIDScanned,
+       _onErrorScanned = onErrorScanned,
+       _onKeyPress = onKeyPress;
   // 事件监听
   void initRfid() async {
-    // 1. 绑定服务
-    final bool isBound = await RfidChannel.bindService();
-    if (!isBound) {
-      logger.d("❌ RFID服务绑定失败");
-      return;
-    }
-    logger.i("✅ RFID服务绑定成功");
+    // // 1. 绑定服务
+    // final bool isBound = await RfidChannel.bindService();
+    // if (!isBound) {
+    //   logger.d("❌ RFID服务绑定失败");
+    //   return;
+    // }
+    // logger.i("✅ RFID服务绑定成功");
 
     // 2. 设置配置项
-    await RfidChannel.setConnectRetryTimes(5); // 连接重试5次
-    await RfidChannel.setScanIdleTimeout(5 * 60 * 1000); // 5分钟空闲超时
+    // await RfidChannel.setConnectRetryTimes(5); // 连接重试5次
+    // await RfidChannel.setScanIdleTimeout(5 * 60 * 1000); // 5分钟空闲超时
 
     // 3. 监听RFID事件
     RfidChannel.rfidEvents.listen((RfidEvent event) {
@@ -38,16 +50,32 @@ class RfidManager {
           break;
         case RfidEventType.tagScanned:
           logger.i("📶 扫描到标签: ${event.data}");
-          // 处理标签数据(如解析EPC、计数等)
           _handleScannedTags(event.data);
           break;
         case RfidEventType.scanError:
           logger.e("❌ 扫描错误: ${event.error}");
-          ToastUtil.error("扫描出错");
+          // ToastUtil.error("扫描出错");
+          _onErrorScanned?.call(event.error ?? "未知错误");
+          break;
+        case RfidEventType.scanInfo:
+          logger.i("❌ 扫描信息: ${event.info}");
+          // if (event.info != null) {
+          //   ToastUtil.info("${event.info}");
+          // }
           break;
         case RfidEventType.disconnected:
           logger.i("🔌 设备已断开连接");
           break;
+        case RfidEventType.onKeyPress: // 处理按键事件
+          if (_onKeyPress != null) {
+            _onKeyPress(event.data);
+          } else {
+            logger.d("前端扫码按键:${event.data}");
+            if (event.data == 'KEY_619') {
+              startScan();
+            }
+          }
+          break;
       }
     });
 
@@ -56,8 +84,8 @@ class RfidManager {
   }
 
   /// 开始扫描
-  Future<void> startScan() async {
-    final bool isStarted = await RfidChannel.startScan();
+  Future<void> startScan({bool isMultiple = false}) async {
+    final bool isStarted = await RfidChannel.startScan(isMultiple);
     if (isStarted) {
       logger.i("▶️ 扫描已启动");
     } else {
@@ -74,34 +102,68 @@ class RfidManager {
   /// 断开连接并解绑服务
   Future<void> disposeRfid() async {
     await RfidChannel.stopScan();
-    await RfidChannel.disconnect();
-    await RfidChannel.unbindService();
+    // await RfidChannel.disconnect();
     logger.i("🔚 RFID资源已释放");
   }
 
   /// 处理扫描到的标签数据
   void _handleScannedTags(dynamic tagsData) {
-    List<Map<String, dynamic>> tags = List<Map<String, dynamic>>.from(tagsData);
-    for (var tag in tags) {
-      try {
-        // 打印标签的所有键值对
-        StringBuffer sb = StringBuffer();
-        sb.write("|RFID标签数据详情| ");
-        tag.forEach((key, value) {
-          sb.write("$key: $value, ");
-        });
-        logger.i(sb.toString());
-
-        // 保留原有的epc和count日志
-        String epc = tag['epc'] ?? '未知';
-        int count = tag['count'] ?? 0;
-        logger.i("📌 标签EPC: $epc | 扫描次数: $count");
-      } catch (e) {
-        logger.e("❌ 扫描解析错误: $e");
+    try {
+      logger.i("📌 原始标签数据: $tagsData");
+
+      // 确保tagsData是List类型
+      if (tagsData is! List) {
+        logger.e("❌ 标签数据不是List类型: ${tagsData.runtimeType}");
+        return;
+      }
+
+      // 转换每个标签数据为RfidModel对象
+      List<RfidModel> tags = tagsData.map<RfidModel>((tag) {
+        try {
+          // 将Map的键从Object类型转换为String类型
+          if (tag is Map) {
+            final Map<String, dynamic> convertedTag = tag.map(
+              (key, value) => MapEntry(key.toString(), value),
+            );
+            return RfidModel.fromJson(convertedTag);
+          } else if (tag is Map<String, dynamic>) {
+            return RfidModel.fromJson(tag);
+          } else {
+            logger.e("❌ 单个标签数据不是Map类型: ${tag.runtimeType}");
+            return RfidModel(uid: '', count: 0); // 返回默认值
+          }
+        } catch (e) {
+          logger.e("❌ 单个标签转换错误: $e");
+          return RfidModel(uid: '', count: 0); // 返回默认值
+        }
+      }).toList();
+
+      // 记录转换后的标签
+      for (var tag in tags) {
+        logger.i("📌 标签UID: ${tag.uid} | 扫描次数: ${tag.count}");
+      }
+
+      // 检查是否有有效的标签数据
+      if (tags.isEmpty) {
+        logger.w("⚠️  标签列表为空");
+        return;
       }
+
+      // 检查第一个标签是否有效
+      if (tags[0].uid.isEmpty) {
+        logger.w("⚠️  第一个标签的UID为空");
+        return;
+      }
+
+      // 如果提供了回调函数,调用它处理标签数据
+      logger.i("📌 扫描到RFID标签: ${tags[0].uid} ${tags[0].count}");
+      _onRFIDScanned?.call(tags[0].uid);
+      _onTagScanned?.call(tags);
+    } catch (e, stackTrace) {
+      logger.e("❌ 扫描解析错误: $e");
+      logger.e("❌ 错误详情: $tagsData");
+      logger.e("❌ 错误堆栈: $stackTrace");
+      _onErrorScanned?.call(e.toString());
     }
-    // 如果提供了回调函数,调用它处理标签数据
-    _onRFIDScanned?.call(tags[0]['epc']);
-    _onTagScanned?.call(tags);
   }
 }

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

@@ -42,7 +42,7 @@ class ScanChannel {
   }
 
   // -------------------------- 扫描头管理API --------------------------
-  /// 提前打开扫描头(进入扫码场景时调用)
+  /// 打开扫描头(进入扫码场景时调用)
   static Future<bool> openScanHead() async {
     try {
       final bool result = await _channel.invokeMethod('openScanHead');

+ 17 - 0
UI/CF.APP/chicken_farm/lib/core/utils/toast.dart

@@ -3,6 +3,10 @@ import 'package:flutter/material.dart';
 import 'package:fluttertoast/fluttertoast.dart';
 
 class ToastUtil {
+  // 用于跟踪最近显示的Toast消息
+  static final Set<String> _recentMessages = {};
+  static const Duration _deduplicationDuration = Duration(seconds: 3);
+
   static void success(
     String message, [
     Toast? toastLength = Toast.LENGTH_SHORT,
@@ -228,6 +232,19 @@ class ToastUtil {
     Color? textColor = Colors.white,
     double? fontSize = 16.0,
   ]) {
+    // 检查消息是否在去重期内
+    if (_recentMessages.contains(message)) {
+      return;
+    }
+
+    // 添加消息到集合中
+    _recentMessages.add(message);
+    
+    // 设置定时器,在一定时间后移除消息记录
+    Future.delayed(_deduplicationDuration, () {
+      _recentMessages.remove(message);
+    });
+
     Fluttertoast.showToast(
       msg: message,
       toastLength: toastLength,

+ 21 - 0
UI/CF.APP/chicken_farm/lib/modes/rfid/rfid_model.dart

@@ -0,0 +1,21 @@
+
+import 'package:json_annotation/json_annotation.dart';
+
+part 'rfid_model.g.dart';
+
+@JsonSerializable()
+class RfidModel{
+
+  String uid;
+  int count;
+
+  RfidModel({
+    required this.uid,
+    this.count = 0,
+  });
+
+  factory RfidModel.fromJson(Map<String, dynamic> json) =>
+      _$RfidModelFromJson(json);
+
+  Map<String, dynamic> toJson() => _$RfidModelToJson(this);
+}

+ 17 - 0
UI/CF.APP/chicken_farm/lib/modes/rfid/rfid_model.g.dart

@@ -0,0 +1,17 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'rfid_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+RfidModel _$RfidModelFromJson(Map<String, dynamic> json) => RfidModel(
+  uid: json['uid'] as String,
+  count: (json['count'] as num?)?.toInt() ?? 0,
+);
+
+Map<String, dynamic> _$RfidModelToJson(RfidModel instance) => <String, dynamic>{
+  'uid': instance.uid,
+  'count': instance.count,
+};

+ 266 - 0
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_create_page.dart

@@ -0,0 +1,266 @@
+import 'package:chicken_farm/apis/index.dart';
+import 'package:chicken_farm/components/vb_rfid_field.dart';
+import 'package:chicken_farm/components/vb_search_select.dart';
+import 'package:chicken_farm/components/vb_select.dart';
+import 'package:chicken_farm/modes/breeding/batch.dart';
+import 'package:chicken_farm/modes/breeding/family.dart';
+import 'package:chicken_farm/modes/rfid/rfid_model.dart';
+import 'package:flutter/material.dart';
+import 'package:chicken_farm/components/vb_app_bar.dart';
+import 'package:chicken_farm/core/utils/toast.dart';
+
+class BatchCreatePage extends StatefulWidget {
+  const BatchCreatePage({super.key});
+
+  @override
+  State<BatchCreatePage> createState() => _BatchCreatePageState();
+}
+
+class _BatchCreatePageState extends State<BatchCreatePage> {
+  final List<String> _rfids = [];
+  String? _selectedBatchNum;
+  String? _selectedFamilyId;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: const VberAppBar(title: '个体绑定', showLeftButton: true),
+      body: Padding(
+        padding: const EdgeInsets.all(16.0),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            // 批次选择
+            _buildBatchSearchSelect(),
+            const SizedBox(height: 10),
+            // 家系号选择
+            _buildFamilySearchSelect(),
+            const SizedBox(height: 20),
+            // 电子编号区域
+            _buildRfidSection(),
+            const SizedBox(height: 20),
+            // 提交按钮
+            SizedBox(
+              width: double.infinity,
+              child: ElevatedButton(
+                onPressed:
+                    _rfids.isNotEmpty &&
+                        _selectedBatchNum != null &&
+                        _selectedFamilyId != null
+                    ? _handleSubmit
+                    : null,
+                style: ElevatedButton.styleFrom(
+                  backgroundColor:
+                      _rfids.isNotEmpty &&
+                          _selectedBatchNum != null &&
+                          _selectedFamilyId != null
+                      ? Colors.blue
+                      : Colors.grey,
+                  foregroundColor: Colors.white,
+                ),
+                child: const Text('提交'),
+              ),
+            ),
+            const SizedBox(height: 20),
+            // 已识别的电子编号列表
+            if (_rfids.isNotEmpty) ...[
+              const Text(
+                '已识别的电子编号',
+                style: TextStyle(fontWeight: FontWeight.bold),
+              ),
+              const SizedBox(height: 10),
+              Expanded(
+                child: Container(
+                  padding: const EdgeInsets.all(10),
+                  decoration: BoxDecoration(
+                    border: Border.all(color: Colors.grey),
+                    borderRadius: BorderRadius.circular(8),
+                  ),
+                  child: ListView.builder(
+                    padding: EdgeInsets.zero,
+                    itemCount: _rfids.length,
+                    itemBuilder: (context, index) {
+                      return ListTile(
+                        visualDensity: VisualDensity.compact,
+                        contentPadding: const EdgeInsets.symmetric(
+                          horizontal: 2,
+                          vertical: 0,
+                        ),
+                        title: Text(_rfids[index]),
+                        trailing: IconButton(
+                          icon: const Icon(
+                            Icons.delete,
+                            size: 18,
+                            color: Colors.red,
+                          ),
+                          onPressed: () => _removeRfid(index),
+                        ),
+                      );
+                    },
+                  ),
+                ),
+              ),
+            ],
+            const SizedBox(height: 20),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildBatchSearchSelect() {
+    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),
+            borderRadius: BorderRadius.circular(8),
+          ),
+          child: VberSearchSelect<BatchModel>(
+            searchApi: ({required dynamic queryParams}) async {
+              final result = await apis.breeding.batchApi.queryPageBatchs(
+                queryParams,
+              );
+              return {'rows': result.rows, 'total': result.total};
+            },
+            converter: (BatchModel data) {
+              return SelectOption(
+                label: '${data.batchNum}(${data.batchName})',
+                value: data.batchNum,
+                extra: data,
+              );
+            },
+            value: _selectedBatchNum,
+            hint: '批次号',
+            onChanged: (String? value) {
+              setState(() {
+                _selectedBatchNum = value;
+                // 当切换批次时,重置后续选项和数据
+                _selectedFamilyId = null;
+              });
+
+              // // 获取翅号数据
+              // if (value != null) {
+              //   // 使用Future.microtask确保在下一个微任务中加载数据,避免在构建过程中修改状态
+              //   Future.microtask(() => _loadWingTags(value));
+              // }
+            },
+            hideUnderline: true,
+          ),
+        ),
+      ],
+    );
+  }
+
+  Widget _buildFamilySearchSelect() {
+    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),
+            borderRadius: BorderRadius.circular(8),
+          ),
+          child: VberSearchSelect<FamilyModel>(
+            searchApi: ({required dynamic queryParams}) async {
+              final result = await apis.breeding.batchApi.queryPageFamilys(
+                queryParams,
+              );
+              return {'rows': result.rows, 'total': result.total};
+            },
+            converter: (FamilyModel data) {
+              return SelectOption(
+                label: data.familyNum,
+                value: data.id.toString(),
+                extra: data,
+              );
+            },
+            value: _selectedFamilyId,
+            hint: '家系号',
+            onChanged: (String? value) {
+              setState(() {
+                _selectedFamilyId = value;
+              });
+            },
+            hideUnderline: true,
+          ),
+        ),
+      ],
+    );
+  }
+
+  Widget _buildRfidSection() {
+    return VberRfidField(
+      rfids: _rfids,
+      onRfidsScanned: (List<RfidModel> scannedRfids) {
+        // 过滤出未存在的RFID
+        final newRfids = scannedRfids
+            .where((rfid) => !_rfids.contains(rfid.uid))
+            .toList();
+
+        if (newRfids.isNotEmpty) {
+          setState(() {
+            // 将新的RFID添加到列表中
+            for (var rfid in newRfids) {
+              _rfids.add(rfid.uid);
+            }
+          });
+          if (newRfids.length == scannedRfids.length) {
+            // 全都是新添加的
+            ToastUtil.success("成功添加 ${newRfids.length} 枚新的电子编号");
+          } else {
+            // 部分是重复的
+            ToastUtil.info(
+              "新增 ${newRfids.length} 枚电子编号,${scannedRfids.length - newRfids.length} 枚已存在",
+            );
+          }
+        } else {
+          // 所有RFID都已存在
+          ToastUtil.info("所有电子编号均已存在");
+        }
+      },
+      multiple: true,
+      label: '电子编号',
+      multiplePlaceholder: '未识别',
+      multipleFormat: '已识别 %d 枚电子编号',
+    );
+  }
+
+  // // 清空所有已识别的电子编号
+  // void _clearRfids() {
+  //   setState(() {
+  //     _rfids.clear();
+  //   });
+  //   ToastUtil.info('已清空所有电子编号');
+  // }
+
+  // 移除指定索引的电子编号
+  void _removeRfid(int index) {
+    setState(() {
+      _rfids.removeAt(index);
+    });
+  }
+
+  // 提交数据
+  void _handleSubmit() {
+    // 在实际应用中,这里会发送数据到服务器
+    ScaffoldMessenger.of(context).showSnackBar(
+      SnackBar(
+        content: Text('批量淘汰提交成功,共${_rfids.length}只鸡'),
+        backgroundColor: Colors.green,
+      ),
+    );
+
+    // 提交后重置表单
+    setState(() {
+      _rfids.clear();
+    });
+  }
+}

+ 52 - 78
UI/CF.APP/chicken_farm/lib/pages/breeding/batch_culling_page.dart

@@ -1,3 +1,5 @@
+import 'package:chicken_farm/components/vb_rfid_field.dart';
+import 'package:chicken_farm/modes/rfid/rfid_model.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/components/vb_dict_select.dart';
@@ -14,7 +16,6 @@ class _BatchCullingPageState extends State<BatchCullingPage> {
   final List<String> _rfids = [];
   String? _cullReason;
   String? _disposalMethod;
-  bool _isScanningRfid = false;
 
   @override
   Widget build(BuildContext context) {
@@ -38,11 +39,17 @@ class _BatchCullingPageState extends State<BatchCullingPage> {
             SizedBox(
               width: double.infinity,
               child: ElevatedButton(
-                onPressed: _rfids.isNotEmpty && _disposalMethod != null
+                onPressed:
+                    _rfids.isNotEmpty &&
+                        _cullReason != null &&
+                        _disposalMethod != null
                     ? _handleSubmit
                     : null,
                 style: ElevatedButton.styleFrom(
-                  backgroundColor: _rfids.isNotEmpty && _disposalMethod != null
+                  backgroundColor:
+                      _rfids.isNotEmpty &&
+                          _cullReason != null &&
+                          _disposalMethod != null
                       ? Colors.blue
                       : Colors.grey,
                   foregroundColor: Colors.white,
@@ -51,10 +58,10 @@ class _BatchCullingPageState extends State<BatchCullingPage> {
               ),
             ),
             const SizedBox(height: 20),
-            // 已扫描的电子编号列表
+            // 已识别的电子编号列表
             if (_rfids.isNotEmpty) ...[
               const Text(
-                '已扫描的电子编号',
+                '已识别的电子编号',
                 style: TextStyle(fontWeight: FontWeight.bold),
               ),
               const SizedBox(height: 10),
@@ -98,47 +105,39 @@ class _BatchCullingPageState extends State<BatchCullingPage> {
   }
 
   Widget _buildRfidSection() {
-    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),
-            borderRadius: BorderRadius.circular(8),
-          ),
-          child: Row(
-            children: [
-              Expanded(
-                child: Text(
-                  _rfids.isEmpty ? '未扫描' : '已扫描 ${_rfids.length} 只鸡',
-                  style: TextStyle(
-                    color: _rfids.isNotEmpty ? Colors.black : Colors.grey,
-                    fontSize: 16,
-                  ),
-                ),
-              ),
-              // 根据是否有已扫描的RFID决定显示什么按钮
-              if (_rfids.isEmpty) ...[
-                IconButton(
-                  icon: _isScanningRfid
-                      ? const SizedBox(
-                          width: 20,
-                          height: 20,
-                          child: CircularProgressIndicator(strokeWidth: 2),
-                        )
-                      : const Icon(Icons.qr_code_scanner, size: 20),
-                  onPressed: _simulateScanMultipleRfids,
-                ),
-              ] else ...[
-                TextButton(onPressed: _clearRfids, child: const Text('清空编号')),
-              ],
-            ],
-          ),
-        ),
-      ],
+    return VberRfidField(
+      rfids: _rfids,
+      onRfidsScanned: (List<RfidModel> scannedRfids) {
+        // 过滤出未存在的RFID
+        final newRfids = scannedRfids
+            .where((rfid) => !_rfids.contains(rfid.uid))
+            .toList();
+
+        if (newRfids.isNotEmpty) {
+          setState(() {
+            // 将新的RFID添加到列表中
+            for (var rfid in newRfids) {
+              _rfids.add(rfid.uid);
+            }
+          });
+          if (newRfids.length == scannedRfids.length) {
+            // 全都是新添加的
+            ToastUtil.success("成功添加 ${newRfids.length} 枚新的电子编号");
+          } else {
+            // 部分是重复的
+            ToastUtil.info(
+              "新增 ${newRfids.length} 枚电子编号,${scannedRfids.length - newRfids.length} 枚已存在",
+            );
+          }
+        } else {
+          // 所有RFID都已存在
+          ToastUtil.info("所有电子编号均已存在");
+        }
+      },
+      multiple: true,
+      label: '电子编号',
+      multiplePlaceholder: '未识别',
+      multipleFormat: '已识别 %d 枚电子编号',
     );
   }
 
@@ -198,38 +197,13 @@ class _BatchCullingPageState extends State<BatchCullingPage> {
     );
   }
 
-  // 模拟一次性扫描多个电子编号
-  void _simulateScanMultipleRfids() {
-    if (_isScanningRfid) return;
-
-    setState(() {
-      _isScanningRfid = true;
-    });
-
-    // 模拟扫描过程
-    Future.delayed(const Duration(seconds: 1), () {
-      if (mounted) {
-        setState(() {
-          // 一次性添加多个电子编号(模拟实际情况)
-          _rfids.clear(); // 确保是新的扫描结果
-          final now = DateTime.now().millisecondsSinceEpoch;
-          for (int i = 0; i < 5; i++) {
-            _rfids.add('RFID-${now + i}');
-          }
-          _isScanningRfid = false;
-        });
-        ToastUtil.success('成功扫描${_rfids.length}个电子编号');
-      }
-    });
-  }
-
-  // 清空所有已扫描的电子编号
-  void _clearRfids() {
-    setState(() {
-      _rfids.clear();
-    });
-    ToastUtil.info('已清空所有电子编号');
-  }
+  // // 清空所有已识别的电子编号
+  // void _clearRfids() {
+  //   setState(() {
+  //     _rfids.clear();
+  //   });
+  //   ToastUtil.info('已清空所有电子编号');
+  // }
 
   // 移除指定索引的电子编号
   void _removeRfid(int index) {

+ 0 - 376
UI/CF.APP/chicken_farm/lib/pages/breeding/bind_wing_tag_page.dart

@@ -1,376 +0,0 @@
-import 'package:chicken_farm/components/vb_select.dart';
-import 'package:chicken_farm/core/utils/logger.dart';
-import 'package:chicken_farm/stores/dict_stroe.dart';
-import 'package:flutter/material.dart';
-import 'package:chicken_farm/components/vb_app_bar.dart';
-import 'package:chicken_farm/core/utils/toast.dart';
-import 'package:chicken_farm/components/vb_search_select.dart';
-import 'package:chicken_farm/apis/index.dart';
-import 'package:chicken_farm/modes/breeding/batch.dart';
-import 'package:chicken_farm/modes/breeding/family.dart';
-import 'package:chicken_farm/modes/breeding/wing_tag_num.dart';
-
-class BindWingTagNumPage extends StatefulWidget {
-  const BindWingTagNumPage({super.key});
-
-  @override
-  State<BindWingTagNumPage> createState() => _BindWingTagNumPageState();
-}
-
-class _BindWingTagNumPageState extends State<BindWingTagNumPage> {
-  String? _selectedBatchNum;
-  String? _selectedFamilyId;
-
-  // 翅号列表 - 根据选中的批次获取
-  List<WingTagNumModel> _wingTags = [];
-
-  // 电子编号列表 - 识别后获取的数据
-  List<String> _rfIds = [];
-  List<Map<String, dynamic>> _refIdWingTags = [];
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: const VberAppBar(title: '翅号绑定', showLeftButton: true),
-      body: Padding(
-        padding: const EdgeInsets.all(16.0),
-        child: Column(
-          children: [
-            // 批次选择
-            _buildBatchSearchSelect(),
-
-            const SizedBox(height: 10),
-
-            // 家系号选择
-            _buildFamilySearchSelect(),
-
-            const SizedBox(height: 20),
-
-            // 显示扫描的电子编号数量
-            if (_refIdWingTags.isNotEmpty)
-              Container(
-                width: double.infinity,
-                padding: const EdgeInsets.all(16),
-                decoration: BoxDecoration(
-                  border: Border.all(color: Colors.grey),
-                  borderRadius: BorderRadius.circular(8),
-                ),
-                child: Text(
-                  '已扫描电子编号数量: ${_refIdWingTags.length}',
-                  style: const TextStyle(
-                    fontSize: 16,
-                    fontWeight: FontWeight.bold,
-                  ),
-                ),
-              ),
-
-            const SizedBox(height: 20),
-
-            // 按钮区域 - 根据不同状态显示不同按钮
-            SizedBox(
-              width: double.infinity,
-              child: _refIdWingTags.isEmpty
-                  ? ElevatedButton(
-                      onPressed:
-                          (_selectedBatchNum != null &&
-                              _selectedFamilyId != null)
-                          ? _handleReidentify
-                          : null,
-                      style: ElevatedButton.styleFrom(
-                        backgroundColor:
-                            (_selectedBatchNum != null &&
-                                _selectedFamilyId != null)
-                            ? Colors.blue
-                            : Colors.grey,
-                        foregroundColor: Colors.white,
-                      ),
-                      child: const Text('扫描电子编号'),
-                    )
-                  : Row(
-                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
-                      children: [
-                        Expanded(
-                          child: ElevatedButton(
-                            onPressed: _handleClear,
-                            style: ElevatedButton.styleFrom(
-                              backgroundColor: Colors.orange,
-                              foregroundColor: Colors.white,
-                            ),
-                            child: const Text('清空编号'),
-                          ),
-                        ),
-                        const SizedBox(width: 16),
-                        Expanded(
-                          child: ElevatedButton(
-                            onPressed: _handleSubmit,
-                            style: ElevatedButton.styleFrom(
-                              backgroundColor: Colors.blue,
-                              foregroundColor: Colors.white,
-                            ),
-                            child: const Text('提交'),
-                          ),
-                        ),
-                      ],
-                    ),
-            ),
-
-            const SizedBox(height: 20),
-
-            // 显示refId和翅号的列表
-            Expanded(
-              child: _refIdWingTags.isEmpty
-                  ? const Center(
-                      child: Text(
-                        '请先选择批次和家系号,然后点击扫描电子编号',
-                        style: TextStyle(color: Colors.grey),
-                      ),
-                    )
-                  : ListView.builder(
-                      itemCount: _refIdWingTags.length,
-                      itemBuilder: (context, index) {
-                        final item = _refIdWingTags[index];
-                        return Card(
-                          margin: const EdgeInsets.symmetric(vertical: 4),
-                          child: ListTile(
-                            title: Column(
-                              mainAxisSize: MainAxisSize.min,
-                              crossAxisAlignment: CrossAxisAlignment.start,
-                              children: [
-                                Text('电子编号: ${item['refId']}'),
-                                const SizedBox(height: 3),
-                                Text('翅号: ${item['wingTag']}'),
-                                const SizedBox(height: 3),
-                                FutureBuilder<String>(
-                                  future: DictStore()
-                                      .getLabelByTypeAndValue(
-                                        'chicken_gender',
-                                        item['gender'],
-                                      )
-                                      .then((value) => value ?? '未知'),
-                                  builder:
-                                      (
-                                        BuildContext context,
-                                        AsyncSnapshot<String> snapshot,
-                                      ) {
-                                        if (snapshot.hasData) {
-                                          return Text('性别: ${snapshot.data!}');
-                                        } else if (snapshot.hasError) {
-                                          return Text('性别: 获取失败');
-                                        } else {
-                                          return const Text('性别: 加载中...');
-                                        }
-                                      },
-                                ),
-                                // const SizedBox(height: 8),
-                                // Text('批次: ${item['batchNum']}'),
-                                // const SizedBox(height: 8),
-                                // Text('家系号: ${item['familyId']}'),
-                                // Row(
-                                //   children: [
-                                //     const Text('性别: '),
-                                //     Expanded(
-                                //       child: VberDictSelect(
-                                //         dictType: "chicken_gender",
-                                //         value: item['gender'],
-                                //         onChanged: (value) {
-                                //           // 更新性别值的逻辑可以在这里添加
-                                //           // 例如:
-                                //           // setState(() {
-                                //           //   item['gender'] = value;
-                                //           // });
-                                //         },
-                                //       ),
-                                //     ),
-                                //   ],
-                                // ),
-                              ],
-                            ),
-                          ),
-                        );
-                      },
-                    ),
-            ),
-          ],
-        ),
-      ),
-    );
-  }
-
-  Widget _buildBatchSearchSelect() {
-    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),
-            borderRadius: BorderRadius.circular(8),
-          ),
-          child: VberSearchSelect<BatchModel>(
-            searchApi: ({required dynamic queryParams}) async {
-              final result = await apis.breeding.batchApi.queryPageBatchs(
-                queryParams,
-              );
-              return {'rows': result.rows, 'total': result.total};
-            },
-            converter: (BatchModel data) {
-              return SelectOption(
-                label: '${data.batchNum}(${data.batchName})',
-                value: data.batchNum,
-                extra: data,
-              );
-            },
-            value: _selectedBatchNum,
-            hint: '批次号',
-            onChanged: (String? value) {
-              setState(() {
-                _selectedBatchNum = value;
-                // 当切换批次时,重置后续选项和数据
-                _selectedFamilyId = null;
-                _wingTags = [];
-                _rfIds = [];
-              });
-
-              // 获取翅号数据
-              if (value != null) {
-                // 使用Future.microtask确保在下一个微任务中加载数据,避免在构建过程中修改状态
-                Future.microtask(() => _loadWingTags(value));
-              }
-            },
-            hideUnderline: true,
-          ),
-        ),
-      ],
-    );
-  }
-
-  Widget _buildFamilySearchSelect() {
-    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),
-            borderRadius: BorderRadius.circular(8),
-          ),
-          child: VberSearchSelect<FamilyModel>(
-            searchApi: ({required dynamic queryParams}) async {
-              final result = await apis.breeding.batchApi.queryPageFamilys(
-                queryParams,
-              );
-              return {'rows': result.rows, 'total': result.total};
-            },
-            converter: (FamilyModel data) {
-              return SelectOption(
-                label: data.familyNum,
-                value: data.id.toString(),
-                extra: data,
-              );
-            },
-            value: _selectedFamilyId,
-            hint: '家系号',
-            onChanged: (String? value) {
-              setState(() {
-                _selectedFamilyId = value;
-              });
-            },
-            hideUnderline: true,
-          ),
-        ),
-      ],
-    );
-  }
-
-  // 根据批次获取翅号
-  void _loadWingTags(String batchId) async {
-    try {
-      final result = await Apis().breeding.batchApi.queryWingTags({
-        'batchNum': batchId,
-      });
-      logger.d('获取翅号成功: $result');
-      setState(() {
-        _wingTags = result;
-      });
-    } catch (e) {
-      ToastUtil.error('获取翅号失败: $e');
-    }
-  }
-
-  // 生成refIds数组
-  List<String> _generateRfIds() {
-    return List.generate(8, (index) {
-      return 'REF-${DateTime.timestamp().toString()}-$index';
-    });
-  }
-
-  // 模拟识别refId
-  void _handleIdentifyRfIds() async {
-    if (_selectedBatchNum == null) {
-      ToastUtil.error('请选择批次号');
-      return;
-    }
-    if (_selectedFamilyId == null) {
-      ToastUtil.error('请选择家系号');
-      return;
-    }
-
-    // 模拟识别过程,生成一些refId
-    final refIds = _generateRfIds();
-
-    // 检查是否有超出翅号数量的refId
-    if (refIds.length > _wingTags.length) {
-      await ToastUtil.errorAlert('错误:电子编号数量超过可用翅号数量,请重新识别');
-      FocusManager.instance.primaryFocus?.unfocus();
-    } else {
-      setState(() {
-        _refIdWingTags = refIds
-            .map(
-              (id) => {
-                'refId': id,
-                'wingTag': _wingTags[refIds.indexOf(id)].wingTagNum,
-                'familyId': _selectedFamilyId,
-                'batchNum': _selectedBatchNum,
-                'gender': "2",
-              },
-            )
-            .toList();
-      });
-    }
-  }
-
-  // 扫描电子编号
-  void _handleReidentify() {
-    _rfIds.clear();
-    _refIdWingTags.clear();
-    _handleIdentifyRfIds();
-  }
-
-  // 处理清空编号
-  void _handleClear() {
-    setState(() {
-      _refIdWingTags.clear();
-    });
-  }
-
-  // 处理提交
-  void _handleSubmit() {
-    // 实际应用中这里会发送数据到服务器
-    ScaffoldMessenger.of(context).showSnackBar(
-      const SnackBar(content: Text('翅号绑定提交成功'), backgroundColor: Colors.green),
-    );
-
-    // 提交后重置状态
-    setState(() {
-      _refIdWingTags.clear();
-      // 重新获取翅号
-      if (_selectedBatchNum != null) {
-        _loadWingTags(_selectedBatchNum!);
-      }
-    });
-  }
-}

+ 17 - 9
UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart

@@ -157,10 +157,10 @@ class _CageChangePageState extends State<CageChangePage> {
                 ),
               ),
               const SizedBox(height: 20),
-              // 已扫描的电子编号列表
+              // 已识别的电子编号列表
               if (_rfids.isNotEmpty) ...[
                 const Text(
-                  '已扫描的电子编号',
+                  '已识别的电子编号',
                   style: TextStyle(fontWeight: FontWeight.bold),
                 ),
                 const SizedBox(height: 10),
@@ -298,15 +298,23 @@ class _CageChangePageState extends State<CageChangePage> {
     return VberRfidField(
       rfids: _rfids,
       onRfidScanned: (rfid) {
-        setState(() {
-          _rfids.insert(0, rfid);
-        });
-        ToastUtil.success("标签已添加");
+        // 检查是否已存在相同的RFID标签
+        bool isDuplicate = _rfids.any((existingRfid) => existingRfid == rfid);
+        if (isDuplicate) {
+          ToastUtil.info("电子编号已存在");
+        } else {
+          // 如果不重复,添加到列表中
+          setState(() {
+            _rfids.insert(0, rfid);
+          });
+          ToastUtil.success("添加新的电子编号");
+        }
       },
       multiple: true,
-      label: '鸡数量',
-      multiplePlaceholder: '未扫描',
-      multipleFormat: '已扫描 %d 只鸡',
+      multipleScan: false,
+      label: '电子编号',
+      multiplePlaceholder: '未识别',
+      multipleFormat: '已识别 %d 枚电子编号',
     );
   }
 

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

@@ -69,10 +69,10 @@ class _IndividualCullingPageState extends State<IndividualCullingPage> {
         setState(() {
           _rfid = rfid;
         });
-        ToastUtil.success('电子编号扫描成功');
+        ToastUtil.success('电子编号识别成功');
       },
       label: '电子编号',
-      placeholder: '未扫描',
+      placeholder: '未识别',
     );
   }
 

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

@@ -61,10 +61,10 @@ class _IndividualWeighingPageState extends State<IndividualWeighingPage> {
         setState(() {
           _rfid = rfid;
         });
-        ToastUtil.success('电子编号扫描成功');
+        ToastUtil.success('电子编号识别成功');
       },
       label: '电子编号',
-      placeholder: '未扫描',
+      placeholder: '未识别',
     );
   }
 

+ 1 - 3
UI/CF.APP/chicken_farm/lib/pages/home/menu_buttons.dart

@@ -35,9 +35,7 @@ class MenuButtons extends ConsumerWidget {
                 height: 50,
                 child: ElevatedButton.icon(
                   onPressed: () {
-                    // 立即给予按钮按下反馈
                     Future.delayed(const Duration(milliseconds: 100), () {
-                      // 检查context是否仍然有效
                       if (context.mounted) {
                         context.pushNamed(visibleMenuItems[i].routeName);
                       }
@@ -56,4 +54,4 @@ class MenuButtons extends ConsumerWidget {
       ),
     );
   }
-}
+}

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

@@ -9,7 +9,7 @@ import '../pages/checkin/checkin_page.dart';
 import '../pages/checkin/checkin_record_page.dart';
 import '../pages/sample/sample_query_page.dart';
 import '../pages/sample/sample_detail_page.dart';
-import '../pages/breeding/bind_wing_tag_page.dart';
+import '../pages/breeding/batch_create_page.dart';
 import '../pages/breeding/cage_change_page.dart';
 import '../pages/breeding/individual_weighing_page.dart';
 import '../pages/breeding/individual_culling_page.dart';
@@ -76,7 +76,7 @@ class AppRoutes {
     GoRoute(
       path: AppRouteNames.bindwingTagNum,
       name: AppRouteNames.bindwingTagNum,
-      builder: (context, state) => const BindWingTagNumPage(),
+      builder: (context, state) => const BatchCreatePage(),
     ),
     GoRoute(
       path: AppRouteNames.cageChange,
@@ -121,4 +121,4 @@ class SplashScreen extends ConsumerWidget {
     });
     return const Scaffold(body: Center(child: CircularProgressIndicator()));
   }
-}
+}

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

@@ -260,4 +260,4 @@ class AuthStore extends StateNotifier<AuthInfo> {
 // 添加 Provider 实例
 final authStoreProvider = StateNotifierProvider<AuthStore, AuthInfo>(
   (ref) => AuthStore(),
-);
+);

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

@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
 class MenuStore {
   static const List<MenuItem> menuItems = [
     MenuItem(
-      name: '翅号绑定',
+      name: '个体绑定',
       routeName: AppRouteNames.bindwingTagNum,
       icon: Icons.tag_outlined,
       permission: 'device:device:query',
@@ -47,4 +47,4 @@ class MenuStore {
       permission: 'experiment:sample:query',
     ),
   ];
-}
+}