浏览代码

0606提交

unknown 4 周之前
父节点
当前提交
a8b9e070d1
共有 50 个文件被更改,包括 1509 次插入199 次删除
  1. 18 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/DistributeBatchController.java
  2. 61 22
      app-admin/src/main/java/com/ruoyi/web/controller/app/NFIDReadRecordController.java
  3. 37 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/NFIDReaderController.java
  4. 4 9
      app-admin/src/main/java/com/ruoyi/web/controller/app/PorkSideProduceController.java
  5. 17 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/SlaughterBatchController.java
  6. 4 2
      app-admin/src/main/java/com/ruoyi/web/core/nfid/ClientHandler.java
  7. 37 17
      app-admin/src/main/java/com/ruoyi/web/core/nfid/MultiReaderNFIDProcessor.java
  8. 2 0
      app-common/src/main/java/com/ruoyi/common/utils/DictUtils.java
  9. 1 1
      app-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
  10. 76 0
      app-system/src/main/java/com/ruoyi/app/DTO/ImportDistributeBatchDTO.java
  11. 9 1
      app-system/src/main/java/com/ruoyi/app/DTO/PorkSideProduceDTO.java
  12. 52 7
      app-system/src/main/java/com/ruoyi/app/DTO/SlaughterBatchDTO.java
  13. 14 4
      app-system/src/main/java/com/ruoyi/app/domain/NFIDReaderEvent.java
  14. 1 1
      app-system/src/main/java/com/ruoyi/app/domain/SlaughterRelation.java
  15. 14 0
      app-system/src/main/java/com/ruoyi/app/domain/request/AddPorkSideProduce.java
  16. 12 0
      app-system/src/main/java/com/ruoyi/app/domain/request/ReqSlaughterBatch.java
  17. 1 1
      app-system/src/main/java/com/ruoyi/app/domain/request/SlaughterRelationBatch.java
  18. 7 0
      app-system/src/main/java/com/ruoyi/app/mapper/HookMapper.java
  19. 7 0
      app-system/src/main/java/com/ruoyi/app/mapper/NFIDReadRecordMapper.java
  20. 2 2
      app-system/src/main/java/com/ruoyi/app/mapper/NFIDReaderMapper.java
  21. 11 0
      app-system/src/main/java/com/ruoyi/app/service/IDistributeBatchService.java
  22. 17 0
      app-system/src/main/java/com/ruoyi/app/service/INFIDReadRecordService.java
  23. 1 1
      app-system/src/main/java/com/ruoyi/app/service/INFIDReaderService.java
  24. 10 0
      app-system/src/main/java/com/ruoyi/app/service/ISlaughterBatchService.java
  25. 101 0
      app-system/src/main/java/com/ruoyi/app/service/impl/DistributeBatchServiceImpl.java
  26. 77 9
      app-system/src/main/java/com/ruoyi/app/service/impl/HookBindServiceImpl.java
  27. 65 3
      app-system/src/main/java/com/ruoyi/app/service/impl/NFIDReadRecordServiceImpl.java
  28. 4 3
      app-system/src/main/java/com/ruoyi/app/service/impl/NFIDReaderServiceImpl.java
  29. 85 2
      app-system/src/main/java/com/ruoyi/app/service/impl/PorkOtherProduceServiceImpl.java
  30. 103 3
      app-system/src/main/java/com/ruoyi/app/service/impl/PorkSideProduceServiceImpl.java
  31. 62 0
      app-system/src/main/java/com/ruoyi/app/service/impl/SlaughterBatchServiceImpl.java
  32. 1 1
      app-system/src/main/resources/mapper/app/DistributeBatchMapper.xml
  33. 10 0
      app-system/src/main/resources/mapper/app/HookMapper.xml
  34. 1 1
      app-system/src/main/resources/mapper/app/MonitorMapper.xml
  35. 13 2
      app-system/src/main/resources/mapper/app/NFIDReadRecordMapper.xml
  36. 5 1
      app-system/src/main/resources/mapper/app/NFIDReaderMapper.xml
  37. 3 1
      app-system/src/main/resources/mapper/app/PorkSideProduceMapper.xml
  38. 5 1
      app-system/src/main/resources/mapper/app/SlaughterBatchMapper.xml
  39. 1 1
      app-system/src/main/resources/mapper/app/SlaughterRelationMapper.xml
  40. 19 0
      webUI/src/api/app/NFIDReader.js
  41. 10 0
      webUI/src/api/app/nfidReadRecord.js
  42. 19 15
      webUI/src/components/weightSerial/index.vue
  43. 9 0
      webUI/src/utils/str.js
  44. 128 0
      webUI/src/views/app/NFIDReader/index.vue
  45. 79 12
      webUI/src/views/app/entranceBatch/components/distributeBatch.vue
  46. 8 2
      webUI/src/views/app/monitor/index.vue
  47. 220 63
      webUI/src/views/app/porkSideProduce/index.vue
  48. 25 5
      webUI/src/views/app/porkSideProduceRecord/index.vue
  49. 28 0
      webUI/src/views/app/slaughterBatch/index.vue
  50. 13 6
      webUI/src/views/app/slaughterRelation/index.vue

+ 18 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/DistributeBatchController.java

@@ -6,10 +6,12 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletResponse;
 
+import com.ruoyi.app.DTO.ImportDistributeBatchDTO;
 import com.ruoyi.app.DTO.ValidDistributeListDTO;
 import com.ruoyi.app.DTO.ValidSlaughterCodeDTO;
 import com.ruoyi.app.domain.Purchaser;
 import com.ruoyi.app.domain.request.AddDistributeBatch;
+import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.NumberUtil;
 import com.ruoyi.common.utils.StringUtils;
@@ -34,6 +36,7 @@ import com.ruoyi.app.domain.DistributeBatch;
 import com.ruoyi.app.service.IDistributeBatchService;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.page.TableDataInfo;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 分销批次Controller
@@ -102,6 +105,21 @@ public class DistributeBatchController extends BaseController
     }
 
     /**
+     * 导入分销批次列表
+     */
+    @ApiOperation("导入分销批次列表")
+    @PreAuthorize("@ss.hasPermi('app:distributeBatch:import')")
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file) throws Exception
+    {
+        ExcelUtil<ImportDistributeBatchDTO> util = new ExcelUtil<ImportDistributeBatchDTO>(ImportDistributeBatchDTO.class);
+        List<ImportDistributeBatchDTO> importList = util.importExcel(file.getInputStream());
+        String operName = getUsername();
+        String message = distributeBatchService.importDistributeBatch(importList, operName);
+        return success(message);
+    }
+
+    /**
      * 获取分销批次详细信息
      */
     @ApiOperation("获取分销批次详细信息")

+ 61 - 22
app-admin/src/main/java/com/ruoyi/web/controller/app/NFIDReadRecordController.java

@@ -2,8 +2,10 @@ package com.ruoyi.web.controller.app;
 
 
 import com.ruoyi.app.DTO.NFIDReadGroupDTO;
+import com.ruoyi.app.DTO.PorkSideProduceDTO;
 import com.ruoyi.app.domain.NFIDReadRecord;
 import com.ruoyi.app.domain.NFIDReaderEvent;
+import com.ruoyi.app.domain.PorkSideProduce;
 import com.ruoyi.app.domain.request.ReqValidNFIDRecord;
 import com.ruoyi.app.service.INFIDReadRecordService;
 import com.ruoyi.common.annotation.Log;
@@ -84,6 +86,18 @@ public class NFIDReadRecordController extends BaseController
     }
 
     /**
+     * 查询全部吊钩范围
+     */
+    @ApiOperation("查询全部吊钩范围")
+    @PreAuthorize("@ss.hasPermi('app:NFIDReadRecord:list')")
+    @GetMapping("/hookRange")
+    public AjaxResult hookRange()
+    {
+        NFIDReadGroupDTO res = NFIDReadRecordService.selectHookRange();
+        return success(res);
+    }
+
+    /**
      * 查询nfid识别记录分组详情清单
      */
     @ApiOperation("查询nfid识别记录分组详情清单")
@@ -124,10 +138,11 @@ public class NFIDReadRecordController extends BaseController
     /**
      * 客户端订阅识别记录数据
      */
-    @GetMapping(value = "/subscribe/{clientId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
-    public SseEmitter subscribe(@PathVariable String clientId) {
+    @GetMapping(value = "/subscribe/{readerId}/{clientId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public SseEmitter subscribe(@PathVariable String readerId,@PathVariable String clientId) {
             SseEmitter emitter = new SseEmitter(30_000L); // 超时时间 30 秒
-            emitters.put(clientId, emitter);
+            String key = readerId + ":" + clientId;
+            emitters.put(key, emitter);
             //定期发送心跳
             ExecutorService executor = Executors.newSingleThreadExecutor();
             executor.execute(() -> {
@@ -135,16 +150,16 @@ public class NFIDReadRecordController extends BaseController
                     while (true) {
                         emitter.send(SseEmitter.event()
                                 .comment("ping") // 心跳消息
-                                .reconnectTime(5000)); // 重连时间建议
-                        Thread.sleep(10_000); // 每10秒一次心跳
+                                .reconnectTime(10_000)); // 重连时间建议
+                        Thread.sleep(5_000); // 每10秒一次心跳
                     }
                 } catch (Exception e) {
                     emitter.completeWithError(e);
                 }
             });
             // 监听连接断开
-            emitter.onCompletion(() -> emitters.remove(clientId));
-            emitter.onTimeout(() -> emitters.remove(clientId));
+            emitter.onCompletion(() -> emitters.remove(key));
+            emitter.onTimeout(() -> emitters.remove(key));
 
             return emitter;
     }
@@ -152,7 +167,7 @@ public class NFIDReadRecordController extends BaseController
     /**
      * 客户端主动取消订阅
      */
-    @PostMapping("/unsubscribe/{clientId}")
+    @PostMapping("/unsubscribe/{readerId}/{clientId}")
     public void unsubscribe(@PathVariable String clientId) {
         SseEmitter emitter = emitters.get(clientId);
         if (emitter != null) {
@@ -161,24 +176,48 @@ public class NFIDReadRecordController extends BaseController
         }
     }
 
-    // 监听设备数据事件并广播
+    // 监听设备数据事件并广播 ,广播吊钩绑定的识别记录
     @EventListener
-    public void handleReaderData(NFIDReaderEvent event) {
-        NFIDReadRecord data = event.getData();
-        System.out.println(data);
-        emitters.forEach((clientId, emitter) -> {
-
-            try {
-                emitter.send(SseEmitter.event()
-                        .data(data)
-                        .id(String.valueOf(data.getId()))
-                        .name("reader-data"));
-            } catch (IOException e) {
-                emitter.completeWithError(e);
-                emitters.remove(clientId);
+    public void handleHookBindData(NFIDReaderEvent event) {
+        Object  data = event.getData();
+
+        emitters.forEach((key, emitter) -> {
+            String[] parts = key.split(":");
+            String subject = parts[0];
+            String clientId = parts[1];
+            if (event.getSubject().equals(subject)) {
+                if (data instanceof NFIDReadRecord) {
+                    sendReaderData(emitter, (NFIDReadRecord) data, subject, key);
+                } else if (data instanceof PorkSideProduceDTO) {
+                    sendWeightData(emitter, (PorkSideProduceDTO) data, subject, key);
+                }
             }
         });
     }
 
+    //推送数据
+    public void sendReaderData(SseEmitter emitter, NFIDReadRecord data, String subject,String key) {
+        try {
+            emitter.send(SseEmitter.event()
+                    .data(data)
+                    .id(String.valueOf(data.getCreateTime().getTime())) //使用时间戳作为ID
+                    .name(subject));
+        } catch (IOException e) {
+            emitter.completeWithError(e);
+            emitters.remove(key);  // 清理失效的Emitter
+        }
+    }
 
+    //推送称重数据
+    public void sendWeightData(SseEmitter emitter, PorkSideProduceDTO data, String subject, String key) {
+        try {
+            emitter.send(SseEmitter.event()
+                    .data(data)
+                    .id(String.valueOf(data.getCreateTime().getTime())) //使用时间戳作为ID
+                    .name(subject));
+        } catch (IOException e) {
+            emitter.completeWithError(e);
+            emitters.remove(key);  // 清理失效的Emitter
+        }
+    }
 }

+ 37 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/NFIDReaderController.java

@@ -1,16 +1,53 @@
 package com.ruoyi.web.controller.app;
 
+import com.ruoyi.app.domain.Monitor;
+import com.ruoyi.app.domain.NFIDReader;
 import com.ruoyi.app.service.INFIDReaderService;
 import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.system.service.ISysConfigService;
+import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 @RestController
 @RequestMapping("/app/NFIDReader")
 public class NFIDReaderController extends BaseController {
     @Autowired
     private INFIDReaderService NFIDReaderService;
 
+    public static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+    /**
+     * 查询NFID识别器设备列表
+     */
+    @ApiOperation("查询NFID识别器设备列表")
+    @PreAuthorize("@ss.hasPermi('app:NFIDReader:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(NFIDReader NFIDReader)
+    {
+        startPage();
+        List<NFIDReader> list = NFIDReaderService.selectNFIDReaderList(NFIDReader);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询全部NFID识别器设备列表
+     */
+    @ApiOperation("查询全部NFID识别器设备列表")
+    @PreAuthorize("@ss.hasPermi('app:NFIDReader:list')")
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect(NFIDReader NFIDReader)
+    {
+        List<NFIDReader> list = NFIDReaderService.selectNFIDReaderList(NFIDReader);
+        return success(list);
+    }
 }

+ 4 - 9
app-admin/src/main/java/com/ruoyi/web/controller/app/PorkSideProduceController.java

@@ -80,17 +80,12 @@ public class PorkSideProduceController extends BaseController
     @PreAuthorize("@ss.hasPermi('app:porkSideProduce:add')")
     @Log(title = "白条生产记录", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@Validated @RequestBody AddPorkSideProduce req)
+    public AjaxResult add(@Validated @RequestBody PorkSideProduce porkSideProduce)
     {
-        HookBindDetailDTO hookBind = hookBindService.selectHookBindDetail(req.getHookNo());
-
-        PorkSideProduce porkSideProduce = new PorkSideProduce();
         porkSideProduce.setProductName(PorkSideProduce.DEFAULT_NAME);
-        porkSideProduce.setProduceTime(DateUtils.getNowDate());
-        porkSideProduce.setHookNo(req.getHookNo());
-        porkSideProduce.setSlaughterCode(hookBind.getSlaughterCode());
-        porkSideProduce.setDistributeBatchId(hookBind.getDistributeBatchId());
-        porkSideProduce.setEntranceBatchId(hookBind.getEntranceBatchId());
+        if(porkSideProduce.getProduceTime() == null){
+            porkSideProduce.setProduceTime(DateUtils.getNowDate());
+        }
         porkSideProduce.setCreateBy(getUsername());
         return toAjax(porkSideProduceService.insertPorkSideProduce(porkSideProduce));
     }

+ 17 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/SlaughterBatchController.java

@@ -4,7 +4,10 @@ import java.util.List;
 import javax.servlet.http.HttpServletResponse;
 
 import com.ruoyi.app.DTO.SlaughterBatchDTO;
+import com.ruoyi.app.DTO.SlaughterRelationDTO;
+import com.ruoyi.app.domain.request.ExportSlaughterRelation;
 import com.ruoyi.app.domain.request.ReqSlaughterBatch;
+import io.swagger.annotations.ApiOperation;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
@@ -51,6 +54,20 @@ public class SlaughterBatchController extends BaseController
     }
 
     /**
+     * 导出屠宰批次列表
+     */
+    @ApiOperation("导出屠宰批次列表")
+    @PreAuthorize("@ss.hasPermi('app:slaughterBatch:export')")
+    @Log(title = "血码关系", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ReqSlaughterBatch req)
+    {
+        List<SlaughterBatchDTO> list = slaughterBatchService.exportSlaughterRelationList(req);
+        ExcelUtil<SlaughterBatchDTO> util = new ExcelUtil<>(SlaughterBatchDTO.class);
+        util.exportExcel(response, list, "屠宰批次数据");
+    }
+
+    /**
      * 获取屠宰批次详细信息
      */
     @PreAuthorize("@ss.hasPermi('app:slaughterBatch:query')")

+ 4 - 2
app-admin/src/main/java/com/ruoyi/web/core/nfid/ClientHandler.java

@@ -50,6 +50,8 @@ public class ClientHandler extends ChannelInboundHandlerAdapter {
             if (!frames.isEmpty()) {
                 // 处理帧
                 processFrames(ctx, frames,bytes);
+                //更新识别器状态
+                updateNFIDReader(ctx,NFIDReader.STATUS_INLINE);
             }
         } finally {
             ReferenceCountUtil.release(msg); // 释放ByteBuf
@@ -58,6 +60,7 @@ public class ClientHandler extends ChannelInboundHandlerAdapter {
 
     private void processFrames(ChannelHandlerContext ctx, List<NFIDFrameParser.NFIDFrame> frames,byte[] bytes) {
         for (NFIDFrameParser.NFIDFrame frame : frames) {
+
             // 记录接收到的帧
            //System.out.println("Received frame: " + frame);
 
@@ -82,8 +85,7 @@ public class ClientHandler extends ChannelInboundHandlerAdapter {
                         //字节转换成ascii
                         String DeviceId = new String(params.get(0).getValue(), StandardCharsets.US_ASCII);
                         DeviceSessionManager.registerDevice(ctx.channel(), DeviceId);
-                        //更新识别器状态
-                        updateNFIDReader(ctx,NFIDReader.STATUS_INLINE);
+
                     }
                 //}
             }

+ 37 - 17
app-admin/src/main/java/com/ruoyi/web/core/nfid/MultiReaderNFIDProcessor.java

@@ -7,6 +7,7 @@ import com.lmax.disruptor.dsl.Disruptor;
 import com.lmax.disruptor.dsl.ProducerType;
 import com.ruoyi.app.DTO.HookBindBatchListDTO;
 import com.ruoyi.app.DTO.HookBindDetailDTO;
+import com.ruoyi.app.DTO.PorkSideProduceDTO;
 import com.ruoyi.app.domain.HookBind;
 import com.ruoyi.app.domain.NFIDReadRecord;
 import com.ruoyi.app.domain.PorkSideProduce;
@@ -32,7 +33,7 @@ public class MultiReaderNFIDProcessor {
     // 配置参数
     private static final int BUFFER_FLUSH_SIZE = 200;
     private static final long BUFFER_FLUSH_INTERVAL_MS = 1000;
-    private static final long TAG_EXPIRY_MS = 6*60*60*1000;
+    private static final long TAG_EXPIRY_MS = 5*1000;
     private static final int DISRUPTOR_BUFFER_SIZE = 1024 * 8;
 
     @Autowired
@@ -142,6 +143,12 @@ public class MultiReaderNFIDProcessor {
      * 处理标签事件
      */
     private void handleEvent(TagEvent event, long sequence, boolean endOfBatch) {
+        String spotType = getReaderType(event.readerId);
+        if(Objects.equals(spotType, NFIDReader.WEIGHT_SPOT)){
+            //白条称重的标签识别直接推送前台
+            sendWeightInfo(event);
+            return;
+        }
         //System.out.println("处理标签事件");
         String compositeKey = event.readerId + ":" + event.tagId;
         long now = System.currentTimeMillis();
@@ -149,8 +156,6 @@ public class MultiReaderNFIDProcessor {
         // 1. 检查本地缓存
         Long lastSeen = localCache.getIfPresent(compositeKey);
         if (lastSeen != null && (now - lastSeen) < TAG_EXPIRY_MS) {
-
-           // System.out.println("检查本地缓存");
             return;
         }
 
@@ -158,9 +163,7 @@ public class MultiReaderNFIDProcessor {
         String redisKey = redisCachePrefix + event.readerId;
         try {
             Boolean exists = redisTemplate.opsForHash().hasKey(redisKey, event.tagId);
-           // System.out.println(redisKey+"-"+event.tagId);
             if (exists != null && exists) {
-               // System.out.println("保存"+compositeKey);
                 localCache.put(compositeKey, now);
                 return;
             }
@@ -170,16 +173,9 @@ public class MultiReaderNFIDProcessor {
 
         // 新标签,加入处理流程
         localCache.put(compositeKey, now);
-
-        // 准备批量记录
-       // System.out.println("准备批量记录");
-
-
         // 更新Redis缓存
         updateRedisCache(event.readerId, event.tagId, now);
 
-        String batchName = getReaderType(event.readerId);
-
         TagRecord record = new TagRecord();
         record.tagId = event.tagId;
         record.readerId = event.readerId;
@@ -187,12 +183,36 @@ public class MultiReaderNFIDProcessor {
         batchBuffer.add(record);
 
         // 添加到对应表的缓冲区
-        batchBuffers.computeIfAbsent(batchName, k -> new ArrayList<>())
+        batchBuffers.computeIfAbsent(spotType, k -> new ArrayList<>())
                 .add(record);
         // 检查批量大小
-        if (batchBuffers.get(batchName).size() >= BUFFER_FLUSH_SIZE) {
-            flushBuffer(batchName);
+        if (batchBuffers.get(spotType).size() >= BUFFER_FLUSH_SIZE) {
+            flushBuffer(spotType);
+        }
+    }
+
+    //白条称重
+    private void sendWeightInfo(TagEvent event) {
+        //生成新对象
+        PorkSideProduceDTO porkSideProduce = new PorkSideProduceDTO();
+        porkSideProduce.setEpcNo(event.tagId);
+        porkSideProduce.setHookNo(hookRegisterService.selectHookNoByEpcNo(event.tagId));
+        porkSideProduce.setDeviceSerial(event.readerId);
+        porkSideProduce.setProduceTime(new Timestamp(event.timestamp));
+        porkSideProduce.setCreateTime(new Timestamp(event.timestamp));
+        porkSideProduce.setProductName(PorkSideProduce.DEFAULT_NAME);
+
+        if(!Objects.equals(porkSideProduce.getHookNo(), "")){
+            //获取吊钩绑定关联信息
+            HookBindDetailDTO hookBindDetail = hookBindService.selectHookBindDetail(porkSideProduce.getHookNo());
+            porkSideProduce.setEntranceBatchId(hookBindDetail.getEntranceBatchId());
+            porkSideProduce.setDistributeBatchId(hookBindDetail.getDistributeBatchId());
+            porkSideProduce.setSlaughterCode(hookBindDetail.getSlaughterCode());
+            porkSideProduce.setPurchaserName(hookBindDetail.getPurchaserName());
+            porkSideProduce.setAnimalCertNo(hookBindDetail.getAnimalCertNo());
+            porkSideProduce.setSupplierName(hookBindDetail.getSupplierName());
         }
+        NFIDReadRecordService.noticeNFIDReadRecord(porkSideProduce);
     }
 
     private String getReaderType(String deviceSerial){
@@ -240,8 +260,8 @@ public class MultiReaderNFIDProcessor {
                 //吊钩绑定
                 saveBind(records);
             }else if(Objects.equals(batchName, NFIDReader.WEIGHT_SPOT)){
-                //白条称重
-                saveWeight(records);
+                //白条称重 ---取消设计,白条不直接入库改为仅推送识别结果给前台用户
+                //saveWeight(records);
             }else{
                 throw new Exception("未知点位类型:"+batchName);
             }

+ 2 - 0
app-common/src/main/java/com/ruoyi/common/utils/DictUtils.java

@@ -236,4 +236,6 @@ public class DictUtils
     {
         return CacheConstants.SYS_DICT_KEY + configKey;
     }
+
+
 }

+ 1 - 1
app-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java

@@ -111,7 +111,7 @@ public class SecurityConfig
             .authorizeHttpRequests((requests) -> {
                 permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
                 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                requests.antMatchers("/login", "/loginNoCaptcha","/register", "/captchaImage","/common/addBatch","/app/NFIDReadRecord/subscribe/{clientId}").permitAll()
+                requests.antMatchers("/login", "/loginNoCaptcha","/register", "/captchaImage","/common/addBatch","/app/NFIDReadRecord/subscribe/{readerId}/{clientId}","/app/NFIDReadRecord/unsubscribe/{readerId}/{clientId}").permitAll()
                     // 静态资源,可匿名访问
                     .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                     .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()

+ 76 - 0
app-system/src/main/java/com/ruoyi/app/DTO/ImportDistributeBatchDTO.java

@@ -0,0 +1,76 @@
+package com.ruoyi.app.DTO;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class ImportDistributeBatchDTO {
+    @Excel(name = "检疫证号")
+    private String animalCertNo;
+
+    @Excel(name = "血码")
+    private String slaughterCode;
+
+    @Excel(name = "数量")
+    private Integer amount;
+
+    @Excel(name = "重量")
+    private BigDecimal weight;
+
+    @Excel(name = "待宰圈号")
+    private String pigpenName;
+
+
+    public void setAnimalCertNo(String animalCertNo)
+    {
+        this.animalCertNo = animalCertNo;
+    }
+
+    public String getAnimalCertNo()
+    {
+        return animalCertNo;
+    }
+
+    public void setSlaughterCode(String slaughterCode)
+    {
+        this.slaughterCode = slaughterCode;
+    }
+
+    public String getSlaughterCode()
+    {
+        return slaughterCode;
+    }
+
+    public void setAmount(Integer amount)
+    {
+        this.amount = amount;
+    }
+
+    public Integer getAmount()
+    {
+        return amount;
+    }
+
+    public void setWeight(BigDecimal weight)
+    {
+        this.weight = weight;
+    }
+
+    public BigDecimal getWeight()
+    {
+        return weight;
+    }
+
+    public void setPigpenName(String pigpenName)
+    {
+        this.pigpenName = pigpenName;
+    }
+
+    public String getPigpenName()
+    {
+        return pigpenName;
+    }
+
+}

+ 9 - 1
app-system/src/main/java/com/ruoyi/app/DTO/PorkSideProduceDTO.java

@@ -6,7 +6,7 @@ public class PorkSideProduceDTO extends PorkSideProduce {
     private String purchaserName;
     private String supplierName;
     private String animalCertNo;
-
+    private String deviceName;
     public String getPurchaserName() {
         return purchaserName;
     }
@@ -30,4 +30,12 @@ public class PorkSideProduceDTO extends PorkSideProduce {
     public void setAnimalCertNo(String animalCertNo) {
         this.animalCertNo = animalCertNo;
     }
+
+    public String getDeviceName() {
+        return deviceName;
+    }
+
+    public void setDeviceName(String deviceName) {
+        this.deviceName = deviceName;
+    }
 }

+ 52 - 7
app-system/src/main/java/com/ruoyi/app/DTO/SlaughterBatchDTO.java

@@ -1,30 +1,64 @@
 package com.ruoyi.app.DTO;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
 
 import java.math.BigDecimal;
 import java.util.Date;
 
 public class SlaughterBatchDTO {
+    @Excel(name = "生猪批次")
+    private Long entranceBatchId;
+
+    @Excel(name = "生产批次号")
     private Long id;
 
+    @Excel(name = "屠宰日期",dateFormat="yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date slaughterTime;
-    private String salePlace;
-    private String meatCert;
-    private String animalCert;
+
+    @Excel(name = "血码")
     private String slaughterCode;
-    private Integer amount;
-    private BigDecimal beforeWeight;
-    private String purchaserName;
+
+    @Excel(name = "检疫证号")
+    private String animalCertNo;
+
+    @Excel(name = "供应商")
     private String supplierName;
+
+    @Excel(name = "肉商")
+    private String purchaserName;
+
+    @Excel(name = "品种")
     private String variety;
-    private String animalCertNo;
+
+    @Excel(name = "数量")
+    private Integer amount;
+
+    @Excel(name = "宰前重量(kg)")
+    private BigDecimal beforeWeight;
+
+    @Excel(name = "白条重量(kg)")
     private BigDecimal sideWeight;
+
+    @Excel(name = "猪头重量(kg)")
     private BigDecimal headWeight;
+
+    @Excel(name = "红脏重量(kg)")
     private BigDecimal redWeight;
+
+    @Excel(name = "白脏重量(kg)")
     private BigDecimal whiteWeight;
 
+    @Excel(name = "销售去向" ,dictType = "app_origin_type")
+    private String salePlace;
+
+    @Excel(name = "肉品合格证")
+    private String meatCert;
+
+    @Excel(name = "动物检疫合格证")
+    private String animalCert;
+
     // Getters and Setters
     public Long getId() {
         return id;
@@ -34,6 +68,16 @@ public class SlaughterBatchDTO {
         this.id = id;
     }
 
+    public void setEntranceBatchId(Long entranceBatchId)
+    {
+        this.entranceBatchId = entranceBatchId;
+    }
+
+    public Long getEntranceBatchId()
+    {
+        return entranceBatchId;
+    }
+
     public Date getSlaughterTime() {
         return slaughterTime;
     }
@@ -106,6 +150,7 @@ public class SlaughterBatchDTO {
         this.supplierName = supplierName;
     }
 
+
     public String getVariety() {
         return variety;
     }

+ 14 - 4
app-system/src/main/java/com/ruoyi/app/domain/NFIDReaderEvent.java

@@ -1,16 +1,26 @@
 package com.ruoyi.app.domain;
 
+import org.apache.poi.ss.formula.functions.T;
 import org.springframework.context.ApplicationEvent;
 
-public class NFIDReaderEvent extends ApplicationEvent {
-    private final NFIDReadRecord data;
+public class NFIDReaderEvent<T> extends ApplicationEvent {
 
-    public NFIDReaderEvent(Object source, NFIDReadRecord data) {
+    //吊钩绑定的识别
+    public static final String HookBind = "hookBind-data";
+
+    private final T data;
+    private String subject;
+    public NFIDReaderEvent(Object source, String subject,T data) {
         super(source);
+        this.subject = subject;
         this.data = data;
     }
 
-    public NFIDReadRecord getData() {
+    public String getSubject() {
+        return subject;
+    }
+
+    public T getData() {
         return data;
     }
 }

+ 1 - 1
app-system/src/main/java/com/ruoyi/app/domain/SlaughterRelation.java

@@ -60,7 +60,7 @@ public class SlaughterRelation extends BaseEntity
     }
 
     @NotBlank(message = "血码编号不能为空")
-    @Pattern(regexp = "^[a-zA-Z0-9]{2,4}$",message = "血码编号格式输入错误")
+//    @Pattern(regexp = "^[a-zA-Z0-9]{2,4}$",message = "血码编号格式输入错误")
     public String getSlaughterCode() 
     {
         return slaughterCode;

+ 14 - 0
app-system/src/main/java/com/ruoyi/app/domain/request/AddPorkSideProduce.java

@@ -13,9 +13,13 @@ public class AddPorkSideProduce {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date produceTime;
 
+    /** 所在产线 */
+    private String deviceSerial;
+
     /** 吊钩号 */
     private String hookNo;
 
+
     /** 血码 */
     private String slaughterCode;
 
@@ -36,6 +40,16 @@ public class AddPorkSideProduce {
         return produceTime;
     }
 
+    public void setDeviceSerial(String deviceSerial)
+    {
+        this.deviceSerial = deviceSerial;
+    }
+
+    public String getDeviceSerial()
+    {
+        return deviceSerial;
+    }
+
     public void setHookNo(String hookNo)
     {
         this.hookNo = hookNo;

+ 12 - 0
app-system/src/main/java/com/ruoyi/app/domain/request/ReqSlaughterBatch.java

@@ -11,6 +11,8 @@ public class ReqSlaughterBatch {
     private Date slaughterTime;
 
     private String animalCertNo;
+    private Long distributeBatchId;
+
     public void setSlaughterCode(String slaughterCode)
     {
         this.slaughterCode = slaughterCode;
@@ -39,4 +41,14 @@ public class ReqSlaughterBatch {
     {
         return animalCertNo;
     }
+
+    public void setDistributeBatchId(Long distributeBatchId)
+    {
+        this.distributeBatchId = distributeBatchId;
+    }
+
+    public Long getDistributeBatchId()
+    {
+        return distributeBatchId;
+    }
 }

+ 1 - 1
app-system/src/main/java/com/ruoyi/app/domain/request/SlaughterRelationBatch.java

@@ -19,7 +19,7 @@ public class SlaughterRelationBatch {
     }
 
     @NotBlank(message = "血码编号不能为空")
-    @Pattern(regexp = "^[a-zA-Z0-9]{2,4}$",message = "血码编号格式输入错误")
+//    @Pattern(regexp = "^[a-zA-Z0-9]{2,4}$",message = "血码编号格式输入错误")
     public String getSlaughterCode()
     {
         return slaughterCode;

+ 7 - 0
app-system/src/main/java/com/ruoyi/app/mapper/HookMapper.java

@@ -21,6 +21,13 @@ public interface HookMapper
     public Hook selectHookById(Long id);
 
     /**
+     * 查询默认吊钩
+     *
+     * @return 吊钩
+     */
+    public Hook selectDefaultHook();
+
+    /**
      * 查询吊钩列表
      * 
      * @param hook 吊钩

+ 7 - 0
app-system/src/main/java/com/ruoyi/app/mapper/NFIDReadRecordMapper.java

@@ -77,6 +77,13 @@ public interface NFIDReadRecordMapper
     public List<NFIDReadGroupDTO> selectNFIDReadByGroup();
 
     /**
+     * 将识别记录按照数量分组
+     *
+     * @return nfid识别记录
+     */
+    public NFIDReadGroupDTO selectHookRange();
+
+    /**
      * 获取识别器记录分组详情
      *
      * @return nfid识别记录

+ 2 - 2
app-system/src/main/java/com/ruoyi/app/mapper/NFIDReaderMapper.java

@@ -17,7 +17,7 @@ public interface NFIDReaderMapper
      *
      * @return nfid识别器
      */
-    public List<NFIDReader> selectNFIDReaderList();
+    public List<NFIDReader> selectNFIDReaderList(NFIDReader NFIDReader);
 
     /**
      * 通过设备序列号查询识别器信息
@@ -28,7 +28,7 @@ public interface NFIDReaderMapper
     public NFIDReader selectNFIDReaderByDeviceSerial(String deviceSerial);
 
     /**
-     * 通过设备序列号查询识别器信息
+     * 修改nfid识别器
      *
      * @param NFIDReader 识别器信息
      * @return nfid识别器信息

+ 11 - 0
app-system/src/main/java/com/ruoyi/app/service/IDistributeBatchService.java

@@ -2,10 +2,12 @@ package com.ruoyi.app.service;
 
 import java.util.List;
 
+import com.ruoyi.app.DTO.ImportDistributeBatchDTO;
 import com.ruoyi.app.DTO.ValidDistributeListDTO;
 import com.ruoyi.app.DTO.ValidSlaughterCodeDTO;
 import com.ruoyi.app.domain.DistributeBatch;
 import com.ruoyi.app.domain.HarmlessTreatment;
+import com.ruoyi.common.core.domain.entity.SysUser;
 
 /**
  * 分销批次Service接口
@@ -32,6 +34,15 @@ public interface IDistributeBatchService
     public List<DistributeBatch> selectDistributeBatchList(DistributeBatch distributeBatch);
 
     /**
+     * 导入分销批次
+     *
+     * @param importList 分销批次列表
+     * @param operName 操作用户
+     * @return 结果
+     */
+    public String importDistributeBatch(List<ImportDistributeBatchDTO> importList, String operName);
+
+    /**
      * 校验分销批次的肉商是否唯一
      *
      * @param  distributeBatch 分销批次

+ 17 - 0
app-system/src/main/java/com/ruoyi/app/service/INFIDReadRecordService.java

@@ -3,8 +3,10 @@ package com.ruoyi.app.service;
 import com.ruoyi.app.DTO.HookBindBatchListDTO;
 import com.ruoyi.app.DTO.HookBindDetailDTO;
 import com.ruoyi.app.DTO.NFIDReadGroupDTO;
+import com.ruoyi.app.DTO.PorkSideProduceDTO;
 import com.ruoyi.app.domain.HookBind;
 import com.ruoyi.app.domain.NFIDReadRecord;
+import com.ruoyi.app.domain.PorkSideProduce;
 import com.ruoyi.app.domain.request.EditHookBindBatch;
 import com.ruoyi.app.domain.request.ReqHookBindList;
 import com.ruoyi.app.domain.request.ReqValidNFIDRecord;
@@ -70,9 +72,24 @@ public interface INFIDReadRecordService
     public List<NFIDReadGroupDTO> selectNFIDReadByGroup();
 
     /**
+     * 将识别记录按照数量分组
+     *
+     * @return nfid识别记录
+     */
+    public NFIDReadGroupDTO selectHookRange();
+
+    /**
      * 获取分组详情
      *
      * @return nfid识别记录
      */
     public List<NFIDReadRecord> selectNFIDReadRecordListByGroup(ReqValidNFIDRecord req);
+
+    /**
+     * nfid别成功
+     *
+     * @param porkSideProduceDTO nfid识别的称重信息
+     * @return 结果
+     */
+    public void noticeNFIDReadRecord(PorkSideProduceDTO porkSideProduceDTO);
 }

+ 1 - 1
app-system/src/main/java/com/ruoyi/app/service/INFIDReaderService.java

@@ -26,7 +26,7 @@ public interface INFIDReaderService
      *
      * @return 参数配置集合
      */
-    public List<NFIDReader> selectNFIDReaderList();
+    public List<NFIDReader> selectNFIDReaderList(NFIDReader NFIDReader);
 
     /**
      * 加载nfid识别器缓存数据

+ 10 - 0
app-system/src/main/java/com/ruoyi/app/service/ISlaughterBatchService.java

@@ -3,6 +3,7 @@ package com.ruoyi.app.service;
 import java.util.List;
 
 import com.ruoyi.app.DTO.SlaughterBatchDTO;
+import com.ruoyi.app.DTO.SlaughterRelationDTO;
 import com.ruoyi.app.domain.SlaughterBatch;
 import com.ruoyi.app.domain.request.ReqSlaughterBatch;
 
@@ -31,6 +32,15 @@ public interface ISlaughterBatchService
     public List<SlaughterBatchDTO> selectSlaughterBatchList(ReqSlaughterBatch slaughterBatch);
 
     /**
+     * 导出屠宰批次列表
+     *
+     * @param slaughterBatch 屠宰批次
+     *
+     * @return 血码关系集合
+     */
+    public List<SlaughterBatchDTO> exportSlaughterRelationList(ReqSlaughterBatch slaughterBatch);
+
+    /**
      * 新增屠宰批次
      * 
      * @param slaughterBatch 屠宰批次

+ 101 - 0
app-system/src/main/java/com/ruoyi/app/service/impl/DistributeBatchServiceImpl.java

@@ -2,14 +2,21 @@ package com.ruoyi.app.service.impl;
 
 import java.util.List;
 
+import com.ruoyi.app.DTO.ImportDistributeBatchDTO;
 import com.ruoyi.app.DTO.ValidDistributeListDTO;
 import com.ruoyi.app.DTO.ValidSlaughterCodeDTO;
 import com.ruoyi.app.domain.*;
 import com.ruoyi.app.mapper.*;
+import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.NumberUtil;
+import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.bean.BeanValidators;
+import com.ruoyi.system.service.impl.SysUserServiceImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.ruoyi.app.service.IDistributeBatchService;
@@ -24,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional;
 @Service
 public class DistributeBatchServiceImpl implements IDistributeBatchService 
 {
+    private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class);
     @Autowired
     private DistributeBatchMapper distributeBatchMapper;
 
@@ -41,6 +49,9 @@ public class DistributeBatchServiceImpl implements IDistributeBatchService
 
     @Autowired
     private PorkOtherProduceMapper porkOtherProduceMapper;
+
+    @Autowired
+    private SlaughterRelationMapper slaughterRelationMapper;
     /**
      * 查询分销批次
      * 
@@ -66,6 +77,96 @@ public class DistributeBatchServiceImpl implements IDistributeBatchService
     }
 
     /**
+     * 导入分销批次
+     *
+     * @param importList 分销批次列表
+     * @param operName 操作用户
+     * @return 结果
+     */
+    @Override
+    public String importDistributeBatch(List<ImportDistributeBatchDTO> importList, String operName)
+    {
+        if (StringUtils.isNull(importList) || importList.size() == 0)
+        {
+            throw new ServiceException("导入数据不能为空!");
+        }
+        int successNum = 0;
+        int failureNum = 0;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        for (ImportDistributeBatchDTO item : importList)
+        {
+            try
+            {
+                // 获取检疫证号对应的入场批次
+                EntranceBatch entranceInfo = entranceBatchMapper.checkAnimalCertNoUnique(item.getAnimalCertNo());
+                if (StringUtils.isNull(entranceInfo))
+                {
+                    failureNum++;
+                    failureMsg.append("<br/>" + failureNum + "、检疫证号: " + item.getAnimalCertNo() + " 不存在");
+                    continue;
+                }
+
+                // 获取血码对应的供应商和肉商
+                SlaughterRelation codeInfo = slaughterRelationMapper.checkCodeUnique(item.getSlaughterCode());
+                if (StringUtils.isNull(codeInfo))
+                {
+                    failureNum++;
+                    failureMsg.append("<br/>" + failureNum + "、血码: " + item.getSlaughterCode() + " 不存在");
+                    continue;
+                }
+                DistributeBatch distributeBatch = new DistributeBatch();
+                distributeBatch.setEntranceBatchId(entranceInfo.getId());
+                distributeBatch.setSlaughterCode(item.getSlaughterCode());
+                distributeBatch.setPurchaserId(codeInfo.getPurchaserId());
+                distributeBatch.setSupplierId(codeInfo.getSupplierId());
+                distributeBatch.setAmount(item.getAmount());
+                distributeBatch.setWeight(item.getWeight());
+                distributeBatch.setPigpenName(item.getPigpenName());
+                distributeBatch.setCreateBy(operName);
+                distributeBatch.setCreateTime(DateUtils.getNowDate());
+                distributeBatchMapper.insertDistributeBatch(distributeBatch);
+
+                SlaughterBatch slaughterBatch = new SlaughterBatch();
+                //屠宰日期目前就默认创建分销批次的时间
+                slaughterBatch.setSlaughterTime(distributeBatch.getCreateTime());//屠宰时间
+                slaughterBatch.setDistributeBatchId(distributeBatch.getId());//分销批次
+                slaughterBatch.setEntranceBatchId(distributeBatch.getEntranceBatchId());//进场批次
+                slaughterBatch.setCreateTime(DateUtils.getNowDate());
+                slaughterBatch.setCreateBy(distributeBatch.getCreateBy());
+                //查询肉商信息
+                Purchaser purchaser = purchaserMapper.selectPurchaserById(distributeBatch.getPurchaserId());
+                if(purchaser != null){
+                    slaughterBatch.setSalePlace(purchaser.getSalePlace());//salePlace 销售去向
+                }
+
+                slaughterBatchMapper.insertSlaughterBatch(slaughterBatch);
+
+                successNum++;
+                successMsg.append("<br/>" + successNum + "、分销批次: " + item.getAnimalCertNo()+"-"+item.getSlaughterCode() + " 导入成功");
+
+            }
+            catch (Exception e)
+            {
+                failureNum++;
+                String msg = "<br/>" + failureNum + "、分销批次: " + item.getAnimalCertNo()+"-"+item.getSlaughterCode() + " 导入失败:";
+                failureMsg.append(msg + e.getMessage());
+                log.error(msg, e);
+            }
+        }
+        if (failureNum > 0)
+        {
+            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+            throw new ServiceException(failureMsg.toString());
+        }
+        else
+        {
+            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+        }
+        return successMsg.toString();
+    }
+
+    /**
      * 校验分销批次的肉商是否唯一
      *
      * @param distributeBatch 分销批次

+ 77 - 9
app-system/src/main/java/com/ruoyi/app/service/impl/HookBindServiceImpl.java

@@ -1,20 +1,19 @@
 package com.ruoyi.app.service.impl;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 import com.ruoyi.app.DTO.HookBindBatchListDTO;
 import com.ruoyi.app.DTO.HookBindDetailDTO;
-import com.ruoyi.app.domain.HookRegister;
-import com.ruoyi.app.domain.NFIDReader;
+import com.ruoyi.app.domain.*;
 import com.ruoyi.app.domain.request.EditHookBindBatch;
 import com.ruoyi.app.domain.request.ReqHookBind;
 import com.ruoyi.app.domain.request.ReqHookBindList;
 import com.ruoyi.app.domain.request.ReqHookRegister;
-import com.ruoyi.app.mapper.NFIDReadRecordMapper;
+import com.ruoyi.app.mapper.*;
 import com.ruoyi.common.constant.CacheConstants;
 import com.ruoyi.common.core.redis.RedisCache;
 import com.ruoyi.common.utils.DateUtils;
@@ -22,11 +21,11 @@ import com.ruoyi.common.utils.StringUtils;
 import org.bouncycastle.cert.ocsp.Req;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.ruoyi.app.mapper.HookBindMapper;
-import com.ruoyi.app.domain.HookBind;
 import com.ruoyi.app.service.IHookBindService;
 import org.springframework.transaction.annotation.Transactional;
 
+import javax.annotation.PostConstruct;
+
 import static com.ruoyi.common.utils.uuid.IdUtils.fastSimpleUUID;
 
 /**
@@ -46,6 +45,75 @@ public class HookBindServiceImpl implements IHookBindService
 
     @Autowired
     private RedisCache redisCache;
+
+    //待删
+
+//    @Autowired
+//    private DistributeBatchMapper distributeBatchMapper;
+//
+//    @Autowired
+//    private HookRegisterMapper hookRegisterMapper;
+//
+//    @Autowired
+//    private EntranceBatchMapper entranceBatchMapper;
+//
+//    @PostConstruct
+//    private void buildData(){
+//        List<DistributeBatch> distributeBatches = distributeBatchMapper.selectDistributeBatchList(new DistributeBatch());
+//
+//        List<HookRegister> hookRegisters = hookRegisterMapper.selectHookRegisterList(new ReqHookRegister());
+//
+//        for(DistributeBatch item : distributeBatches){
+//            String batchNo = fastSimpleUUID();
+//            EntranceBatch entranceBatch = entranceBatchMapper.selectEntranceBatchById(item.getEntranceBatchId());
+//            Date bindTime = getRandomTimeBetween10PMAnd3AM(entranceBatch.getEntranceTime());
+//            List<HookBind> hookBindArr = new ArrayList<>();
+//            for (int i = 0; i < item.getAmount(); i++) {
+//                HookBind hookBind = new HookBind();
+//                hookBind.setBatchNo(batchNo);
+//                hookBind.setHookNo(findHookRegister(hookRegisters).getHookNo());
+//                hookBind.setBindTime(bindTime);
+//                hookBind.setDistributeBatchId(item.getId());
+//                hookBind.setSlaughterCode(item.getSlaughterCode());
+//                hookBind.setCreateTime(bindTime);
+//                hookBind.setCreateBy(item.getCreateBy());
+//                hookBindArr.add(hookBind);
+//            }
+//            hookBindMapper.insertHookBindBatch(hookBindArr);
+//        }
+//    }
+//    private static Date getRandomTimeBetween10PMAnd3AM(Date inputDate) {
+//        Random random = new Random();
+//
+//        // 将Date转换为LocalDateTime以便操作
+//        LocalDateTime date = inputDate.toInstant()
+//                .atZone(ZoneId.systemDefault())
+//                .toLocalDateTime();
+//
+//        // 当天晚上10点 (22:00)
+//        LocalDateTime start = date.toLocalDate().atTime(22, 0);
+//
+//        // 次日凌晨3点 (03:00)
+//        LocalDateTime end = date.toLocalDate().plusDays(1).atTime(3, 0);
+//
+//        // 计算两个时间点之间的秒数差
+//        long startSeconds = start.atZone(ZoneId.systemDefault()).toEpochSecond();
+//        long endSeconds = end.atZone(ZoneId.systemDefault()).toEpochSecond();
+//
+//        // 生成随机秒数偏移量
+//        long randomSeconds = startSeconds + (long)(random.nextDouble() * (endSeconds - startSeconds));
+//
+//        // 转换为Date
+//        return Date.from(Instant.ofEpochSecond(randomSeconds)
+//                .atZone(ZoneId.systemDefault())
+//                .toInstant());
+//    }
+//    private HookRegister findHookRegister(List<HookRegister> list){
+//        int index = new Random().nextInt(list.size());
+//        return list.get(index);
+//    }
+    //
+
     /**
      * 查询吊钩绑定
      * 

+ 65 - 3
app-system/src/main/java/com/ruoyi/app/service/impl/NFIDReadRecordServiceImpl.java

@@ -1,19 +1,27 @@
 package com.ruoyi.app.service.impl;
 
+import com.ruoyi.app.DTO.HookBindDetailDTO;
 import com.ruoyi.app.DTO.NFIDReadGroupDTO;
+import com.ruoyi.app.DTO.PorkSideProduceDTO;
 import com.ruoyi.app.domain.NFIDReadRecord;
 import com.ruoyi.app.domain.NFIDReaderEvent;
+import com.ruoyi.app.domain.PorkSideProduce;
 import com.ruoyi.app.domain.request.ReqValidNFIDRecord;
 import com.ruoyi.app.mapper.HookMapper;
 import com.ruoyi.app.mapper.NFIDReadRecordMapper;
+import com.ruoyi.app.service.IHookBindService;
+import com.ruoyi.app.service.IHookRegisterService;
 import com.ruoyi.app.service.INFIDReadRecordService;
 import com.ruoyi.common.utils.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.stereotype.Service;
 
+import java.sql.Timestamp;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -25,18 +33,29 @@ import java.util.concurrent.TimeUnit;
 public class NFIDReadRecordServiceImpl implements INFIDReadRecordService {
     @Autowired
     private NFIDReadRecordMapper NFIDReadRecordMapper;
+    @Autowired
+    private IHookBindService hookBindService;
+
+    @Autowired
+    private IHookRegisterService hookRegisterService;
+
     private final ApplicationEventPublisher eventPublisher;
 
     public NFIDReadRecordServiceImpl(ApplicationEventPublisher eventPublisher) {
         this.eventPublisher = eventPublisher;
 
         //测试使用
-//        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
-//
+      //  ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
+
 //        scheduler.scheduleAtFixedRate(
 //                this::testBatchAdd,
 //                1, 10, TimeUnit.SECONDS
 //        );
+
+//        scheduler.scheduleAtFixedRate(
+//                this::testNotice,
+//                1, 7, TimeUnit.SECONDS
+//        );
     }
 
     //测试批量新增
@@ -49,6 +68,28 @@ public class NFIDReadRecordServiceImpl implements INFIDReadRecordService {
         insertNFIDReadRecordBatch(records);
     }
 
+    //测试称重识别提醒
+    private void testNotice(){
+        PorkSideProduceDTO porkSideProduce = new PorkSideProduceDTO();
+        porkSideProduce.setEpcNo("E28068940000502A8A1624D0");
+        porkSideProduce.setHookNo(hookRegisterService.selectHookNoByEpcNo("E28068940000502A8A1624D0"));
+        porkSideProduce.setDeviceSerial("N60040000241219B04F4");
+        porkSideProduce.setProduceTime(new Date());
+        porkSideProduce.setCreateTime(new Date());
+        porkSideProduce.setProductName(PorkSideProduce.DEFAULT_NAME);
+
+        if(!Objects.equals(porkSideProduce.getHookNo(), "")){
+            //获取吊钩绑定关联信息
+            HookBindDetailDTO hookBindDetail = hookBindService.selectHookBindDetail(porkSideProduce.getHookNo());
+            porkSideProduce.setEntranceBatchId(hookBindDetail.getEntranceBatchId());
+            porkSideProduce.setDistributeBatchId(hookBindDetail.getDistributeBatchId());
+            porkSideProduce.setSlaughterCode(hookBindDetail.getSlaughterCode());
+            porkSideProduce.setPurchaserName(hookBindDetail.getPurchaserName());
+            porkSideProduce.setAnimalCertNo(hookBindDetail.getAnimalCertNo());
+            porkSideProduce.setSupplierName(hookBindDetail.getSupplierName());
+        }
+        noticeNFIDReadRecord(porkSideProduce);
+    }
     /**
      * 查询nfid识别记录信息
      *
@@ -104,7 +145,7 @@ public class NFIDReadRecordServiceImpl implements INFIDReadRecordService {
         if(row >0){
             // 发布事件
             for(NFIDReadRecord record : list){
-                eventPublisher.publishEvent(new NFIDReaderEvent(this, record));
+                eventPublisher.publishEvent(new NFIDReaderEvent<>(this,NFIDReaderEvent.HookBind,record));
             }
         }
         return row;
@@ -131,6 +172,16 @@ public class NFIDReadRecordServiceImpl implements INFIDReadRecordService {
     }
 
     /**
+     * 将识别记录按照数量分组
+     *
+     * @return nfid识别记录
+     */
+    @Override
+    public NFIDReadGroupDTO selectHookRange(){
+        return NFIDReadRecordMapper.selectHookRange();
+    }
+
+    /**
      * 获取分组详情
      *
      * @return nfid识别记录
@@ -139,4 +190,15 @@ public class NFIDReadRecordServiceImpl implements INFIDReadRecordService {
     public List<NFIDReadRecord> selectNFIDReadRecordListByGroup(ReqValidNFIDRecord req){
         return NFIDReadRecordMapper.selectNFIDReadRecordListByGroup(req);
     }
+
+    /**
+     * 提醒识别成功
+     *
+     * @param porkSideProduceDTO nfid识别的称重信息
+     * @return 结果
+     */
+    @Override
+    public void noticeNFIDReadRecord(PorkSideProduceDTO porkSideProduceDTO){
+        eventPublisher.publishEvent(new NFIDReaderEvent<>(this, porkSideProduceDTO.getDeviceSerial(),porkSideProduceDTO));
+    }
 }

+ 4 - 3
app-system/src/main/java/com/ruoyi/app/service/impl/NFIDReaderServiceImpl.java

@@ -79,12 +79,13 @@ public class NFIDReaderServiceImpl implements INFIDReaderService
 
     /**
      * 查询nfid识别器列表
+     * @param NFIDReader nfid识别器
      * @return 参数配置集合
      */
     @Override
-    public List<NFIDReader> selectNFIDReaderList()
+    public List<NFIDReader> selectNFIDReaderList(NFIDReader NFIDReader)
     {
-        return nfidReaderMapper.selectNFIDReaderList();
+        return nfidReaderMapper.selectNFIDReaderList(NFIDReader);
     }
 
     /**
@@ -105,7 +106,7 @@ public class NFIDReaderServiceImpl implements INFIDReaderService
     @Override
     public void loadingConfigCache()
     {
-        List<NFIDReader> NFIDReaderList = nfidReaderMapper.selectNFIDReaderList();
+        List<NFIDReader> NFIDReaderList = nfidReaderMapper.selectNFIDReaderList(new NFIDReader());
         for (NFIDReader nfidReader : NFIDReaderList)
         {
             redisCache.setCacheObject(getCacheKey(nfidReader.getDeviceSerial()), nfidReader);

+ 85 - 2
app-system/src/main/java/com/ruoyi/app/service/impl/PorkOtherProduceServiceImpl.java

@@ -1,17 +1,27 @@
 package com.ruoyi.app.service.impl;
 
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
 import java.util.List;
+import java.util.Random;
 
 import com.ruoyi.app.DTO.PorkOtherProduceDTO;
+import com.ruoyi.app.domain.*;
 import com.ruoyi.app.domain.request.ReqGetPorkOtherProduce;
 import com.ruoyi.app.domain.request.ReqGetPorkSideProduce;
+import com.ruoyi.app.domain.request.ReqHookRegister;
+import com.ruoyi.app.mapper.*;
 import com.ruoyi.common.utils.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.ruoyi.app.mapper.PorkOtherProduceMapper;
-import com.ruoyi.app.domain.PorkOtherProduce;
 import com.ruoyi.app.service.IPorkOtherProduceService;
 
+import javax.annotation.PostConstruct;
+
 /**
  * 猪头生产记录Service业务层处理
  * 
@@ -24,6 +34,79 @@ public class PorkOtherProduceServiceImpl implements IPorkOtherProduceService
     @Autowired
     private PorkOtherProduceMapper porkOtherProduceMapper;
 
+    //待删
+
+//    @Autowired
+//    private DistributeBatchMapper distributeBatchMapper;
+//
+//    @Autowired
+//    private HookRegisterMapper hookRegisterMapper;
+//
+//    @Autowired
+//    private EntranceBatchMapper entranceBatchMapper;
+//
+//    @Autowired
+//    private HookMapper hookMapper;
+//
+//    @PostConstruct
+//    private void buildData(){
+//        List<DistributeBatch> distributeBatches = distributeBatchMapper.selectDistributeBatchList(new DistributeBatch());
+//
+//        for(DistributeBatch item : distributeBatches){
+//            for (int i = 0; i < item.getAmount(); i++) {
+//                EntranceBatch entranceBatch = entranceBatchMapper.selectEntranceBatchById(item.getEntranceBatchId());
+//                Date bindTime = getRandomTimeBetween11PMAnd4AM(entranceBatch.getEntranceTime());
+//                BigDecimal scaleWeight = new BigDecimal(getRandomNumberFormatted());
+//
+//                PorkOtherProduce porkOtherProduce = new PorkOtherProduce();
+//                porkOtherProduce.setProductName("猪头");
+//                porkOtherProduce.setProduceTime(bindTime);
+//                porkOtherProduce.setEntranceBatchId(item.getEntranceBatchId());
+//                porkOtherProduce.setDistributeBatchId(item.getId());
+//                porkOtherProduce.setSlaughterCode(item.getSlaughterCode());
+//                porkOtherProduce.setCreateTime(bindTime);
+//                porkOtherProduce.setCreateBy(item.getCreateBy());
+//                porkOtherProduce.setScaleWeight(scaleWeight);
+//                porkOtherProduce.setFinalWeight(scaleWeight);
+//                porkOtherProduceMapper.insertPorkOtherProduce(porkOtherProduce);
+//            }
+//
+//        }
+//    }
+//    public static String getRandomNumberFormatted() {
+//        Random random = new Random();
+//        double number = 55.0 + (55.0 - 45.0) * random.nextDouble();
+//        DecimalFormat df = new DecimalFormat("#.#"); // 保留一位小数
+//        return df.format(number);
+//    }
+//
+//    private static Date getRandomTimeBetween11PMAnd4AM(Date inputDate) {
+//        Random random = new Random();
+//
+//        // 将Date转换为LocalDateTime以便操作
+//        LocalDateTime date = inputDate.toInstant()
+//                .atZone(ZoneId.systemDefault())
+//                .toLocalDateTime();
+//
+//        // 当天晚上10点 (22:00)
+//        LocalDateTime start = date.toLocalDate().atTime(23, 0);
+//
+//        // 次日凌晨3点 (03:00)
+//        LocalDateTime end = date.toLocalDate().plusDays(1).atTime(4, 0);
+//
+//        // 计算两个时间点之间的秒数差
+//        long startSeconds = start.atZone(ZoneId.systemDefault()).toEpochSecond();
+//        long endSeconds = end.atZone(ZoneId.systemDefault()).toEpochSecond();
+//
+//        // 生成随机秒数偏移量
+//        long randomSeconds = startSeconds + (long)(random.nextDouble() * (endSeconds - startSeconds));
+//
+//        // 转换为Date
+//        return Date.from(Instant.ofEpochSecond(randomSeconds)
+//                .atZone(ZoneId.systemDefault())
+//                .toInstant());
+//    }
+
     /**
      * 查询生产记录
      * 

+ 103 - 3
app-system/src/main/java/com/ruoyi/app/service/impl/PorkSideProduceServiceImpl.java

@@ -1,17 +1,29 @@
 package com.ruoyi.app.service.impl;
 
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
+import java.util.Random;
 
 import com.ruoyi.app.DTO.PorkSideProduceDTO;
-import com.ruoyi.app.domain.HookBind;
+import com.ruoyi.app.domain.*;
 import com.ruoyi.app.domain.request.ReqGetPorkSideProduce;
+import com.ruoyi.app.domain.request.ReqHookRegister;
+import com.ruoyi.app.mapper.*;
 import com.ruoyi.common.utils.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.ruoyi.app.mapper.PorkSideProduceMapper;
-import com.ruoyi.app.domain.PorkSideProduce;
 import com.ruoyi.app.service.IPorkSideProduceService;
 
+import javax.annotation.PostConstruct;
+
+import static com.ruoyi.common.utils.uuid.IdUtils.fastSimpleUUID;
+
 /**
  * 白条生产记录Service业务层处理
  * 
@@ -24,6 +36,94 @@ public class PorkSideProduceServiceImpl implements IPorkSideProduceService
     @Autowired
     private PorkSideProduceMapper porkSideProduceMapper;
 
+    //待删
+
+//    @Autowired
+//    private DistributeBatchMapper distributeBatchMapper;
+//
+//    @Autowired
+//    private HookRegisterMapper hookRegisterMapper;
+//
+//    @Autowired
+//    private EntranceBatchMapper entranceBatchMapper;
+//
+//    @Autowired
+//    private HookMapper hookMapper;
+//
+//    @PostConstruct
+//    private void buildData(){
+//        List<DistributeBatch> distributeBatches = distributeBatchMapper.selectDistributeBatchList(new DistributeBatch());
+//
+//        List<HookRegister> hookRegisters = hookRegisterMapper.selectHookRegisterList(new ReqHookRegister());
+//
+//        Hook hook = hookMapper.selectDefaultHook();
+//
+//        for(DistributeBatch item : distributeBatches){
+//
+//
+//            for (int i = 0; i < item.getAmount(); i++) {
+//                EntranceBatch entranceBatch = entranceBatchMapper.selectEntranceBatchById(item.getEntranceBatchId());
+//                Date bindTime = getRandomTimeBetween11PMAnd4AM(entranceBatch.getEntranceTime());
+//                BigDecimal scaleWeight = new BigDecimal(getRandomNumberFormatted());
+//
+//                PorkSideProduce porkSideProduce = new PorkSideProduce();
+//                porkSideProduce.setProductName(PorkSideProduce.DEFAULT_NAME);
+//                porkSideProduce.setHookNo(findHookRegister(hookRegisters).getHookNo());
+//                porkSideProduce.setHookName(hook.getHookName());
+//                porkSideProduce.setProduceTime(bindTime);
+//                porkSideProduce.setEntranceBatchId(item.getEntranceBatchId());
+//                porkSideProduce.setDistributeBatchId(item.getId());
+//                porkSideProduce.setSlaughterCode(item.getSlaughterCode());
+//                porkSideProduce.setCreateTime(bindTime);
+//                porkSideProduce.setCreateBy(item.getCreateBy());
+//                porkSideProduce.setHookWeight(hook.getHookWeight());
+//                porkSideProduce.setScaleWeight(scaleWeight);
+//                porkSideProduce.setDryLossWeight(BigDecimal.valueOf(0));
+//                porkSideProduce.setFinalWeight(scaleWeight.subtract(hook.getHookWeight()));
+//                porkSideProduceMapper.insertPorkSideProduce(porkSideProduce);
+//            }
+//
+//        }
+//    }
+//    public static String getRandomNumberFormatted() {
+//        Random random = new Random();
+//        double number = 105.0 + (115.0 - 105.0) * random.nextDouble();
+//        DecimalFormat df = new DecimalFormat("#.#"); // 保留一位小数
+//        return df.format(number);
+//    }
+//
+//    private static Date getRandomTimeBetween11PMAnd4AM(Date inputDate) {
+//        Random random = new Random();
+//
+//        // 将Date转换为LocalDateTime以便操作
+//        LocalDateTime date = inputDate.toInstant()
+//                .atZone(ZoneId.systemDefault())
+//                .toLocalDateTime();
+//
+//        // 当天晚上10点 (22:00)
+//        LocalDateTime start = date.toLocalDate().atTime(23, 0);
+//
+//        // 次日凌晨3点 (03:00)
+//        LocalDateTime end = date.toLocalDate().plusDays(1).atTime(4, 0);
+//
+//        // 计算两个时间点之间的秒数差
+//        long startSeconds = start.atZone(ZoneId.systemDefault()).toEpochSecond();
+//        long endSeconds = end.atZone(ZoneId.systemDefault()).toEpochSecond();
+//
+//        // 生成随机秒数偏移量
+//        long randomSeconds = startSeconds + (long)(random.nextDouble() * (endSeconds - startSeconds));
+//
+//        // 转换为Date
+//        return Date.from(Instant.ofEpochSecond(randomSeconds)
+//                .atZone(ZoneId.systemDefault())
+//                .toInstant());
+//    }
+//    private HookRegister findHookRegister(List<HookRegister> list){
+//        int index = new Random().nextInt(list.size());
+//        return list.get(index);
+//    }
+
+
     /**
      * 查询白条生产记录
      * 

+ 62 - 0
app-system/src/main/java/com/ruoyi/app/service/impl/SlaughterBatchServiceImpl.java

@@ -1,9 +1,15 @@
 package com.ruoyi.app.service.impl;
 
+import java.util.Date;
 import java.util.List;
 
 import com.ruoyi.app.DTO.SlaughterBatchDTO;
+import com.ruoyi.app.domain.DistributeBatch;
+import com.ruoyi.app.domain.HookBind;
+import com.ruoyi.app.domain.request.ReqHookRegister;
 import com.ruoyi.app.domain.request.ReqSlaughterBatch;
+import com.ruoyi.app.mapper.DistributeBatchMapper;
+import com.ruoyi.app.mapper.HookBindMapper;
 import com.ruoyi.common.utils.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -11,6 +17,8 @@ import com.ruoyi.app.mapper.SlaughterBatchMapper;
 import com.ruoyi.app.domain.SlaughterBatch;
 import com.ruoyi.app.service.ISlaughterBatchService;
 
+import javax.annotation.PostConstruct;
+
 /**
  * 屠宰批次Service业务层处理
  * 
@@ -23,6 +31,48 @@ public class SlaughterBatchServiceImpl implements ISlaughterBatchService
     @Autowired
     private SlaughterBatchMapper slaughterBatchMapper;
 
+    //待删
+//
+//    @Autowired
+//    private DistributeBatchMapper distributeBatchMapper;
+//
+//    @Autowired
+//    private HookBindMapper hookBindMapper;
+//
+//    @PostConstruct
+//    private void buildData(){
+//        List<DistributeBatch> distributeBatches = distributeBatchMapper.selectDistributeBatchList(new DistributeBatch());
+//
+//        for(DistributeBatch item : distributeBatches){
+//            HookBind hookBindReq = new HookBind();
+//            hookBindReq.setDistributeBatchId(item.getId());
+//            List<HookBind> hookBinds = hookBindMapper.selectHookBindList(hookBindReq);
+//            Date slaughterTime = new Date();
+//            if(hookBinds.size()>0){
+//                slaughterTime = hookBinds.get(0).getBindTime();
+//            }
+//            ReqSlaughterBatch reqSlaughterBatch = new ReqSlaughterBatch();
+//            reqSlaughterBatch.setDistributeBatchId(item.getId());
+//            List<SlaughterBatchDTO> slaughterBatchList = slaughterBatchMapper.selectSlaughterBatchList(reqSlaughterBatch);
+//            if(slaughterBatchList.size() > 0){
+//                //更新时间
+//                SlaughterBatch slaughterBatch = new SlaughterBatch();
+//                slaughterBatch.setSlaughterTime(slaughterTime);
+//                slaughterBatch.setId(slaughterBatchList.get(0).getId());
+//                slaughterBatchMapper.updateSlaughterBatch(slaughterBatch);
+//            }else{
+//                //新增
+//                SlaughterBatch slaughterBatch = new SlaughterBatch();
+//                slaughterBatch.setSlaughterTime(slaughterTime);
+//                slaughterBatch.setDistributeBatchId(item.getId());
+//                slaughterBatch.setEntranceBatchId(item.getEntranceBatchId());
+//                slaughterBatch.setSalePlace("1");
+//                slaughterBatchMapper.insertSlaughterBatch(slaughterBatch);
+//            }
+//        }
+//    }
+
+
     /**
      * 查询屠宰批次
      * 
@@ -48,6 +98,18 @@ public class SlaughterBatchServiceImpl implements ISlaughterBatchService
     }
 
     /**
+     * 查询屠宰批次列表
+     *
+     * @param slaughterBatch 屠宰批次
+     * @return 屠宰批次
+     */
+    @Override
+    public List<SlaughterBatchDTO> exportSlaughterRelationList(ReqSlaughterBatch slaughterBatch)
+    {
+        return slaughterBatchMapper.selectSlaughterBatchList(slaughterBatch);
+    }
+
+    /**
      * 新增屠宰批次
      * 
      * @param slaughterBatch 屠宰批次

+ 1 - 1
app-system/src/main/resources/mapper/app/DistributeBatchMapper.xml

@@ -85,7 +85,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="supplierId != null "> and a.supplier_id = #{supplierId}</if>
             <if test="purchaserId != null "> and a.purchaser_id = #{purchaserId}</if>
         </where>
-        order by a.create_time desc
+        order by a.id desc
     </select>
     
     <select id="selectDistributeBatchById" parameterType="Long" resultMap="DistributeBatchResult">

+ 10 - 0
app-system/src/main/resources/mapper/app/HookMapper.xml

@@ -20,6 +20,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select id, hook_name, hook_weight,default_flag, create_time, create_by, update_time, update_by, del_flag from hook
     </sql>
 
+    <select id="selectDefaultHook"  resultMap="HookResult">
+        <include refid="selectHookVo"/>
+        <where>
+            del_flag = '0' and
+            default_flag = 0
+        </where>
+        order by id desc
+        limit 1
+    </select>
+
     <select id="selectHookList" parameterType="Hook" resultMap="HookResult">
         <include refid="selectHookVo"/>
         <where>

+ 1 - 1
app-system/src/main/resources/mapper/app/MonitorMapper.xml

@@ -38,7 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="monitorName != null  and monitorName != ''"> and a.monitor_name like concat('%', #{monitorName}, '%')</if>
             <if test="regionId != null  and regionId != ''"> and a.region_id =#{regionId}</if>
         </where>
-        order by a.create_time desc
+        order by a.id desc
     </select>
 
     <select id="checkMonitorNameUnique" parameterType="String" resultMap="MonitorResult">

+ 13 - 2
app-system/src/main/resources/mapper/app/NFIDReadRecordMapper.xml

@@ -30,14 +30,14 @@
         select hook_no
         from nfid_read_record
         <where>
-            bind_flag = 0
+            hook_no != '' and bind_flag = 0
             <if test="hookNos != null  and hookNos != ''"> or hook_no in
                 <foreach item="hookNo" collection = "hookNos" open="(" separator="," close=")">
                     #{hookNo}
                 </foreach>
             </if>
         </where>
-        group by hook_no
+        group by hook_no,create_time
         order by create_time desc
     </select>
 
@@ -104,6 +104,17 @@
             hook_no ASC;
     </select>
 
+    <!--获取所有吊钩管理中最大和最小的吊钩号范围,不分组-->
+    <select id="selectHookRange" resultMap="NFIDReadGroupDTOResult">
+        SELECT
+            MIN(hook_no) AS min_hook_no,
+            MAX(hook_no) AS max_hook_no
+        FROM
+            hook_register
+        WHERE
+            del_flag = '0'
+    </select>
+
     <!--获取吊钩识别记录根据上面的分组-->
     <select id="selectNFIDReadRecordListByGroup" parameterType="com.ruoyi.app.domain.request.ReqValidNFIDRecord" resultMap="NFIDReadRecordResult">
         select id, device_serial,epc_no, hook_no,bind_flag,create_time

+ 5 - 1
app-system/src/main/resources/mapper/app/NFIDReaderMapper.xml

@@ -14,9 +14,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
 
-    <select id="selectNFIDReaderList" resultMap="NFIDReaderResult">
+    <select id="selectNFIDReaderList" parameterType="NFIDReader" resultMap="NFIDReaderResult">
         select id, device_serial,device_name, device_spot,last_active_time,status
         from nfid_reader
+        <where>
+            1=1
+            <if test="deviceSpot != null  and deviceSpot != ''"> and device_spot = #{deviceSpot}</if>
+        </where>
     </select>
     
     <select id="selectNFIDReaderByDeviceSerial" parameterType="String" resultMap="NFIDReaderResult">

+ 3 - 1
app-system/src/main/resources/mapper/app/PorkSideProduceMapper.xml

@@ -50,6 +50,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="purchaserName"    column="purchaser_name"    />
         <result property="supplierName"    column="supplier_name"    />
         <result property="animalCertNo"    column="animal_cert_no"    />
+        <result property="deviceName"    column="device_name"    />
     </resultMap>
 
     <resultMap type="com.ruoyi.app.DTO.TimeAndWeightDTO" id="TimeAndWeightDTOResult">
@@ -62,12 +63,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </sql>
 
     <select id="selectPorkSideProduceList" parameterType="com.ruoyi.app.domain.request.ReqGetPorkSideProduce" resultMap="PorkSideProduceDTOResult">
-        select a.*,c.purchaser_name,d.supplier_name,e.animal_cert_no
+        select a.*,c.purchaser_name,d.supplier_name,e.animal_cert_no,f.device_name
         from pork_side_produce as a
         left join distribute_batch as b on b.id = a.distribute_batch_id and b.del_flag = '0'
         left join purchaser as c on c.id = b.purchaser_id and c.del_flag = '0'
         left join supplier as d on d.id = b.supplier_id and d.del_flag = '0'
         left join entrance_batch as e on e.id = a.entrance_batch_id and e.del_flag = '0'
+        left join nfid_reader as f on f.device_serial = a.device_serial
         <where>
             a.del_flag = '0'
             <if test="keyword != null  and keyword != ''"> and (a.slaughter_code like concat('%', #{keyword}, '%') or e.animal_cert_no like concat('%', #{keyword}, '%'))</if>

+ 5 - 1
app-system/src/main/resources/mapper/app/SlaughterBatchMapper.xml

@@ -22,6 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <resultMap id="slaughterBatchDTOResultMap" type="com.ruoyi.app.DTO.SlaughterBatchDTO">
         <!-- 主表 -->
         <id property="id" column="id"/>
+        <result property="entranceBatchId"    column="entrance_batch_id"    />
         <result property="slaughterTime" column="slaughter_time"/>
         <result property="salePlace" column="sale_place"/>
         <result property="meatCert" column="meat_cert"/>
@@ -39,6 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="supplierName" column="supplier_name"/>
 
         <!-- 入场批次 -->
+        <result property="entranceId" column="entrance_id"/>
         <result property="variety" column="variety"/>
         <result property="animalCertNo" column="animal_cert_no"/>
 
@@ -52,6 +54,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <resultMap id="SlaughterReportResultMap" type="com.ruoyi.app.DTO.SlaughterReportDTO">
         <!-- 主表 -->
         <id property="id" column="id"/>
+
         <result property="slaughterTime" column="slaughter_time"/>
         <result property="salePlace" column="sale_place"/>
         <result property="meatCert" column="meat_cert"/>
@@ -91,7 +94,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </sql>
 
     <select id="selectSlaughterBatchList" parameterType="com.ruoyi.app.domain.request.ReqSlaughterBatch" resultMap="slaughterBatchDTOResultMap">
-        select a.id,a.slaughter_time,a.sale_place,a.meat_cert,a.animal_cert,
+        select a.id,a.slaughter_time,a.sale_place,a.meat_cert,a.animal_cert,a.entrance_batch_id,
         b.slaughter_code,b.amount,b.weight as before_weight,
         c.purchaser_name,
         d.supplier_name,
@@ -107,6 +110,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         left join entrance_batch as e on e.id = a.entrance_batch_id and e.del_flag = '0'
         <where>
             a.del_flag = '0'
+            <if test="distributeBatchId != null "> and a.distribute_batch_id = #{distributeBatchId}</if>
             <if test="slaughterTime != null "> and DATE(a.slaughter_time) = DATE(#{slaughterTime})</if>
             <if test="animalCertNo != null  and animalCertNo != ''"> and e.animal_cert_no like concat('%', #{animalCertNo}, '%')</if>
             <if test="slaughterCode != null  and slaughterCode != ''"> and b.slaughter_code like concat('%', #{slaughterCode}, '%')</if>

+ 1 - 1
app-system/src/main/resources/mapper/app/SlaughterRelationMapper.xml

@@ -126,7 +126,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <!--判断血码是否已存在-->
     <select id="checkCodeUnique" parameterType="String"  resultMap="SlaughterRelationResult">
-        select id, slaughter_code from slaughter_relation where slaughter_code = #{slaughterCode} and del_flag = '0' limit 1
+        select id, slaughter_code,purchaser_id,supplier_id from slaughter_relation where slaughter_code = #{slaughterCode} and del_flag = '0' limit 1
     </select>
 
     <!--判断当前供应商下肉商是否已有血码-->

+ 19 - 0
webUI/src/api/app/NFIDReader.js

@@ -0,0 +1,19 @@
+import request from '@/utils/request'
+
+// 查询识别器设备列表
+export function listNFIDReader(query) {
+  return request({
+    url: '/app/NFIDReader/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询全部识别器设备列表
+export function listAllNFIDReader(query) {
+  return request({
+    url: '/app/NFIDReader/optionselect',
+    method: 'get',
+    params: query
+  })
+}

+ 10 - 0
webUI/src/api/app/nfidReadRecord.js

@@ -17,3 +17,13 @@ export function listValidNFIDRecord(query) {
     params: query
   })
 }
+
+//取消订阅
+export function unsubscribeRead(readerId,clientId) {
+  return request({
+    url: '/app/NFIDReadRecord/unsubscribe/'+readerId+'/'+clientId,
+    method: 'post',
+  })
+}
+
+

+ 19 - 15
webUI/src/components/weightSerial/index.vue

@@ -49,14 +49,14 @@
           type="primary"
           v-if="isConnected && isReading"
           @click="stopReader"
-          size="medium"
+          size="large"
           icon="el-icon-check"
         ></el-button>
         <el-button
           class="ml20"
           type="primary"
           v-if="isConnected && !isReading"
-          size="medium"
+          size="large"
           @click="startReader"
           icon="el-icon-refresh"
         ></el-button>
@@ -94,7 +94,7 @@ export default {
     form: {
       handler(val) {
         console.log(val);
-        this.$emit("confirm", val.receivedData);
+        this.$emit("confirm", val.receivedData?parseFloat(val.receivedData):0);
       },
       deep: true,
       immediate: true,
@@ -210,7 +210,7 @@ export default {
     submitWeight() {
       if (!(this.isSupported && this.isConnected)) {
         //手动输入的情况
-        this.$emit("confirm", this.form.receivedData);
+        this.$emit("confirm", this.form.receivedData?parseFloat(this.form.receivedData):0);
       }
     },
     async connect() {
@@ -267,24 +267,28 @@ export default {
         this.reader?.cancel();
         console.log("停止读取");
       }*/
-      try {
+      if(this.isConnected && this.isReading){
+        try {
         await this.$store.dispatch('serial/stopReading').then(()=>{
           console.log('停止读取完成')
+
           //this.$emit("confirm", this.form.receivedData);
         })
       } catch (error) {
         console.error('停止读取失败:', error)
       }
-
+      }
     },
     //重新读取
     async startReader() {
-      this.buffer = [];
-      //this.listenToData();
-      try {
-        await this.$store.dispatch('serial/startReading')
-      } catch (error) {
-        console.error('重新读取失败:', error)
+      if(this.isConnected && !this.isReading){
+        this.buffer = [];
+        //this.listenToData();
+        try {
+          await this.$store.dispatch('serial/startReading')
+        } catch (error) {
+          console.error('重新读取失败:', error)
+        }
       }
     },
     async listenToData() {
@@ -440,12 +444,12 @@ export default {
 <style>
 /* 样式保持不变 */
 .large-input{
-  font-size: 42px;
+  font-size: 32px;
   width: 300px;
 }
 .large-input>.el-input__inner{
-  height: 80px;
-  line-height: 80px;
+  height: 60px;
+  line-height: 60px;
 }
 .serialInput-block{
   display: flex;

+ 9 - 0
webUI/src/utils/str.js

@@ -12,3 +12,12 @@ export function sliceStr(str,num=100) {
     }
     return str;
 }
+
+export function getRandomString(length = 8) {
+  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+  let result = '';
+  for (let i = 0; i < length; i++) {
+    result += chars.charAt(Math.floor(Math.random() * chars.length));
+  }
+  return result;
+}

+ 128 - 0
webUI/src/views/app/NFIDReader/index.vue

@@ -0,0 +1,128 @@
+<template>
+  <div class="app-container">
+    <el-row :gutter="10" class="mb8">
+      <right-toolbar
+        :showSearch.sync="showSearch"
+        @queryTable="getList"
+      ></right-toolbar>
+    </el-row>
+
+    <el-table
+      v-loading="loading"
+      :data="NFIDReaderList"
+      @selection-change="handleSelectionChange"
+    >
+      <!-- <el-table-column type="selection" width="55" align="center" /> -->
+      <!-- <el-table-column label="ID" align="center" prop="id" width="80" /> -->
+      <el-table-column label="设备序列号" align="center" prop="deviceSerial" />
+      <el-table-column label="设备名称" align="center" prop="deviceName" />
+      <el-table-column label="设备点位" align="center" prop="deviceSpot">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.deviceSpot=='bind'">吊钩绑定</el-tag>
+          <el-tag v-if="scope.row.deviceSpot=='weight'">白条称重</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="最近通信时间" align="center" prop="lastActiveTime">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.lastActiveTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <el-tag type="info" v-if="scope.row.status=='0'">离线</el-tag>
+          <el-tag v-if="scope.row.status=='1'">在线</el-tag>
+          <el-tag type="danger" v-if="scope.row.status=='2'">异常</el-tag>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import {
+  listNFIDReader,
+  listAllNFIDReader
+} from "@/api/app/NFIDReader";
+export default {
+  name: "NFIDReader",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 设备表格数据
+      NFIDReaderList: [],
+      regionOptions: [],
+      // 弹出层标题
+      title: "",
+      isView:false,
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+
+      },
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询设备列表 */
+    getList() {
+      this.loading = true;
+      listNFIDReader(this.queryParams).then((response) => {
+        this.NFIDReaderList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+  },
+};
+</script>

+ 79 - 12
webUI/src/views/app/entranceBatch/components/distributeBatch.vue

@@ -55,6 +55,17 @@
           >删除</el-button
         >
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="el-icon-upload2"
+          size="mini"
+          @click="handleImport"
+          v-hasPermi="['app:distributeBatch:import']"
+          >导入</el-button
+        >
+      </el-col>
       <right-toolbar
         :showSearch.sync="showSearch"
         @queryTable="getList"
@@ -271,7 +282,11 @@
           </el-select>
         </el-form-item>
         <el-form-item label="肉商" prop="purchaserId">
-          <el-select v-model="form.purchaserId" placeholder="请选择肉商" disabled>
+          <el-select
+            v-model="form.purchaserId"
+            placeholder="请选择肉商"
+            disabled
+          >
             <el-option
               v-for="item in purchaserOptions"
               :key="item.id"
@@ -302,6 +317,22 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+
+    <!-- 导入对话框 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+        <div class="el-upload__tip text-center" slot="tip">
+          <span>仅允许导入xls、xlsx格式文件。</span>
+
+        </div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -316,6 +347,7 @@ import {
 import { getAllPurchaser } from "@/api/app/purchaser";
 import { getAllSlaughterRelation } from "@/api/app/slaughterRelation";
 import { getAllPigpen } from "@/api/app/pigpen";
+import { getToken } from "@/utils/auth";
 export default {
   name: "DistributeBatch",
   props: {
@@ -329,8 +361,8 @@ export default {
     },
     disabled: {
       type: Boolean,
-      default:false
-    }
+      default: false,
+    },
   },
   watch: {
     entranceBatchId: {
@@ -364,13 +396,15 @@ export default {
       }
     };
     var valiAmount = (rule, value, callback) => {
-          const regex = /^([1-9]\d{0,2})$/;
-            if ( !(regex.test(value) && parseInt(value) >= 1 && parseInt(value) <= 999)) {
-                callback(new Error('数量范围(1~999)'));
-            } else {
-                callback();
-            }
-        };
+      const regex = /^([1-9]\d{0,2})$/;
+      if (
+        !(regex.test(value) && parseInt(value) >= 1 && parseInt(value) <= 999)
+      ) {
+        callback(new Error("数量范围(1~999)"));
+      } else {
+        callback();
+      }
+    };
     return {
       // 遮罩层
       loading: true,
@@ -402,6 +436,19 @@ export default {
         entranceBatchId: this.entranceBatchId,
         slaughterCode: null,
       },
+      // 导入参数
+      upload: {
+        // 是否显示弹出层(导入)
+        open: false,
+        // 弹出层标题(导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/app/distributeBatch/importData"
+      },
       // 表单参数
       form: {},
       batchForm: {},
@@ -434,7 +481,7 @@ export default {
             message: "数量不能为空",
             trigger: "blur",
           },
-          { validator: valiAmount, trigger: "blur" }
+          { validator: valiAmount, trigger: "blur" },
         ],
         weight: [{ validator: valiWeight, trigger: "blur" }],
       },
@@ -540,7 +587,27 @@ export default {
         this.slaughterRelationOptions = response.data;
       });
     },
-
+    /** 导入按钮操作 */
+    handleImport() {
+      this.upload.title = "导入"
+      this.upload.open = true
+    },
+     // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true
+    },
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false
+      this.upload.isUploading = false
+      this.$refs.upload.clearFiles()
+      this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
+      this.getList()
+    },
+    // 提交上传文件
+    submitFileForm() {
+      this.$refs.upload.submit()
+    },
     // 取消按钮
     cancel() {
       this.open = false;

+ 8 - 2
webUI/src/views/app/monitor/index.vue

@@ -230,6 +230,8 @@ export default {
         monitorName: null,
         regionId: null,
       },
+      deviceId:null,
+      channelId:null,
       // 表单参数
       form: {},
       // 表单校验
@@ -361,17 +363,21 @@ export default {
     //打开监控
     handleOpenVideo(row) {
       this.videoOpen = true;
+      this.deviceId = row.deviceId;
+      this.channelId = row.channelId;
       playStart({deviceId:row.deviceId,channelId:row.channelId}).then((response) => {
           //console.log(response)
           if(response.data){
-            this.videoUrl = response.data.flv;
+            this.videoUrl = response.data.https_flv;
+            //4432是 视频流的实际端口,需要把它替换成https的4431,然后通过nginx代理到4432
+            this.videoUrl = this.videoUrl.replace(":4432",":4431")
           }
 
       });
     },
     handleCloseVideo(){
       this.videoUrl ="";
-      playStop({deviceId:row.deviceId,channelId:row.channelId}).then((response) => {
+      playStop({deviceId:this.deviceId,channelId:this.channelId}).then((response) => {
           console.log(response)
       });
     },

+ 220 - 63
webUI/src/views/app/porkSideProduce/index.vue

@@ -98,7 +98,7 @@
     >
       <el-table-column type="selection" width="55" align="center" />
       <!-- <el-table-column label="ID" align="center" prop="id" width="80" /> -->
-      <el-table-column label="产品名称" align="center" prop="productName" />
+      <el-table-column label="产品名称" align="center" prop="productName" width="120" />
       <el-table-column
         label="屠宰时间"
         align="center"
@@ -109,31 +109,33 @@
           <span>{{ parseTime(scope.row.produceTime) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="吊钩号" align="center" prop="hookNo" />
-      <el-table-column label="血码" align="center" prop="slaughterCode" />
-      <el-table-column label="检疫证号" align="center" prop="animalCertNo" />
-      <el-table-column label="供应商" align="center" prop="supplierName" />
-      <el-table-column label="肉商" align="center" prop="purchaserName" />
-      <el-table-column label="吊钩类型" align="center" prop="hookName">
+      <el-table-column label="吊钩号" align="center" prop="hookNo" width="120" />
+      <el-table-column label="血码" align="center" prop="slaughterCode" width="120" />
+      <el-table-column label="检疫证号" align="center" prop="animalCertNo" width="150" />
+      <el-table-column label="供应商" align="center" prop="supplierName" width="120" />
+      <el-table-column label="肉商" align="center" prop="purchaserName" width="120" />
+      <el-table-column label="吊钩类型" align="center" prop="hookName" width="120">
         <template slot-scope="scope">
           <span>{{ showValue(scope.row.hookName) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="吊钩扣重(kg)" align="center" prop="hookWeight">
+      <el-table-column label="吊钩扣重(kg)" align="center" prop="hookWeight" width="120">
         <template slot-scope="scope">
           <span>{{ showValue(scope.row.hookWeight) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="干损重量(kg)" align="center" prop="dryLossWeight">
+      <el-table-column label="干损重量(kg)" align="center" prop="dryLossWeight" width="120">
         <template slot-scope="scope">
           <span>{{ showValue(scope.row.dryLossWeight) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="宰后重量(kg)" align="center" prop="finalWeight">
+      <el-table-column label="宰后重量(kg)" align="center" prop="finalWeight" width="120">
         <template slot-scope="scope">
           <span>{{ showValue(scope.row.finalWeight) }}</span>
         </template>
       </el-table-column>
+      <el-table-column label="识别器" align="center" prop="deviceName" width="160">
+      </el-table-column>
       <el-table-column
         label="操作"
         fixed="right"
@@ -215,7 +217,7 @@
       </el-row>
     </el-dialog>
     <!-- 添加或修改白条生产记录对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+    <el-dialog :title="title" :visible.sync="open"  width="800px" append-to-body @close="closeDialog">
       <el-form
         ref="form"
         :model="form"
@@ -224,8 +226,21 @@
         label-width="120px"
       >
         <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="称重产线" prop="deviceSerial" v-if="!this.form.id">
+              <el-radio-group v-model="deviceSerial" @input="selectLine">
+                <el-radio
+                  v-for="(item, i) in NFIDReaders"
+                  :key="i"
+                  :label="item.deviceSerial"
+                  border
+                  >{{ item.deviceName }}</el-radio
+                >
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
           <el-col :span="12">
-            <el-form-item label="产品名称" prop="productName" v-if="form.id">
+            <el-form-item label="产品名称" prop="productName">
               <el-input
                 v-model="form.productName"
                 placeholder="请输入产品名称"
@@ -235,7 +250,7 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="生产时间" prop="produceTime" v-if="form.id">
+            <el-form-item label="生产时间" prop="produceTime">
               <el-date-picker
                 clearable
                 v-model="form.produceTime"
@@ -243,7 +258,7 @@
                 format="yyyy-MM-dd HH:mm:ss"
                 value-format="yyyy-MM-dd HH:mm:ss"
                 placeholder="请选择生产时间"
-                style="width: 240px;"
+                style="width: 240px"
               >
               </el-date-picker>
             </el-form-item>
@@ -254,19 +269,20 @@
           <el-input
             v-model="form.hookNo"
             placeholder="请输入吊钩号"
-            @blur="fillInput"
+
+            class="large-input"
             style="width: 630px"
           />
         </el-form-item>
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="血码" prop="slaughterCode" v-if="form.id">
+            <el-form-item label="血码" prop="slaughterCode">
               <el-select
                 v-model="form.slaughterCode"
                 filterable
                 placeholder="请选择血码"
                 @change="getDistribute()"
-                style="width: 240px;"
+                style="width: 240px"
               >
                 <el-option
                   v-for="item in slaughterRelationOptions"
@@ -278,27 +294,51 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="检疫证号" prop="animalCertNo" v-if="form.id">
-              <el-input v-model="form.animalCertNo" placeholder="" style="width: 240px;" disabled />
+            <el-form-item label="检疫证号" prop="animalCertNo">
+              <el-input
+                v-model="form.animalCertNo"
+                placeholder=""
+                style="width: 240px"
+                disabled
+              />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="肉商" prop="purchaserName" v-if="form.id">
-              <el-input v-model="form.purchaserName" placeholder="" style="width: 240px;" disabled />
+            <el-form-item label="肉商" prop="purchaserName">
+              <el-input
+                v-model="form.purchaserName"
+                placeholder=""
+                style="width: 240px"
+                disabled
+              />
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="供应商" prop="supplierName" v-if="form.id">
-              <el-input v-model="form.supplierName" placeholder="" style="width: 240px;" disabled />
+            <el-form-item label="供应商" prop="supplierName">
+              <el-input
+                v-model="form.supplierName"
+                placeholder=""
+                style="width: 240px"
+                disabled
+              />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="吊钩类型" prop="hookName" v-if="form.id">
-              <el-select
+          <el-col :span="24">
+            <el-form-item label="吊钩类型" prop="hookName">
+              <el-radio-group v-model="form.hookName" @input="getHookType">
+                <el-radio
+                  v-for="(item, i) in hookOptions"
+                  :key="i"
+                  :label="item.hookName"
+                  border
+                  >{{ item.hookName }}</el-radio
+                >
+              </el-radio-group>
+              <!-- <el-select
                 v-model="form.hookName"
                 filterable
                 placeholder="请选择吊钩类型"
@@ -311,11 +351,11 @@
                   :label="item.hookName"
                   :value="item.hookName"
                 ></el-option>
-              </el-select>
+              </el-select> -->
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="吊钩扣重(kg)" prop="hookWeight" v-if="form.id">
+            <el-form-item label="吊钩扣重(kg)" prop="hookWeight">
               <el-input
                 v-model="form.hookWeight"
                 placeholder="请输入吊钩扣重"
@@ -325,6 +365,18 @@
               />
             </el-form-item>
           </el-col>
+          <el-col :span="12">
+            <el-form-item label="干损重量(kg)" prop="dryLossWeight">
+              <el-input
+                v-model="form.dryLossWeight"
+                disabled
+                style="width: 240px"
+              />
+              <span style="color: #f00; margin-left: 20px"
+                >【当前干损比为:{{ this.dryLossRatio }}%】</span
+              >
+            </el-form-item>
+          </el-col>
         </el-row>
 
         <SerialInput
@@ -332,27 +384,21 @@
           :type="1"
           ref="SerialInputRef"
           :disabled="isView"
-          v-if="form.id"
           @confirm="confirmWeight"
         />
 
-        <el-form-item label="干损重量(kg)" prop="dryLossWeight" v-if="form.id">
+        <el-form-item label="宰后重量(kg)" prop="finalWeight">
           <el-input
-            v-model="form.dryLossWeight"
-            disabled
+            v-model="form.finalWeight"
+            class="large-input"
             style="width: 240px"
+            disabled
           />
-          <span style="color: #f00; margin-left: 20px"
-            >【当前干损比为:{{ this.dryLossRatio }}%】</span
-          >
-        </el-form-item>
-        <el-form-item label="宰后重量(kg)" prop="finalWeight" v-if="form.id">
-          <el-input v-model="form.finalWeight" style="width: 240px" disabled />
         </el-form-item>
       </el-form>
-      <div slot="footer" class="dialog-footer" v-if="!isView">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
+      <div slot="footer" class="dialog-footer" v-show="!isView">
+        <el-button type="primary" size="large" @click="submitForm">确 定</el-button>
+        <el-button size="large" @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
   </div>
@@ -366,12 +412,16 @@ import {
   addPorkSideProduce,
   updatePorkSideProduce,
 } from "@/api/app/porkSideProduce";
-import { getHookBindDetail,cleanBind } from "@/api/app/hookBind";
+import { listAllNFIDReader } from "@/api/app/NFIDReader";
+import { unsubscribeRead } from "@/api/app/nfidReadRecord";
+import { getHookBindDetail, cleanBind } from "@/api/app/hookBind";
 import { getValidDistributeBatch } from "@/api/app/distributeBatch";
 import { getAllHook } from "@/api/app/hook";
 import { getAllSlaughterRelation } from "@/api/app/slaughterRelation";
 import { getNowRatio } from "@/api/app/dryLossRatio";
 import SerialInput from "@/components/weightSerial/index.vue";
+import { getRandomString } from "@/utils/str";
+import { isEmpty } from "@/utils/validate";
 export default {
   name: "PorkSideProduce",
   components: {
@@ -394,6 +444,10 @@ export default {
       }
     };
     return {
+      //SSE
+      emitter:null,
+      clientId:"",
+      deviceSerial:null,
       //串口信息
       // 遮罩层
       loading: true,
@@ -411,6 +465,7 @@ export default {
       porkSideProduceList: [],
       hookOptions: [],
       slaughterRelationOptions: [],
+      NFIDReaders: [],
       distributeList: [],
       dryLossRatio: 0,
       regexRule:
@@ -474,8 +529,91 @@ export default {
   created() {
     this.getList();
     this.getHookOptions();
+    this.getAllNFIDReader();
+    this.getSlaughterRelationOptions();
+  },
+  mounted() {
+    // 监听全局键盘事件
+    document.addEventListener("keydown", this.handleGlobalKeyDown);
+  },
+  beforeDestroy() {
+    // 组件销毁时移除监听
+    document.removeEventListener("keydown", this.handleGlobalKeyDown);
   },
   methods: {
+    handleGlobalKeyDown(event) {
+      if (event.key === "Enter") {
+        event.preventDefault(); // 阻止默认行为
+        if(this.open && !this.isView){
+          //弹窗已打开,并且是新增或修改的操作
+          this.$refs.SerialInputRef.stopReader().then(()=>{
+            this.submitForm();
+          })
+        }
+      }
+
+    },
+    //选择识别的产线
+    selectLine(){
+      this.unSubNFIDRecord();
+      this.subNFIDRecord();
+    },
+    //订阅识别记录
+    subNFIDRecord(){
+      console.log("订阅识别记录")
+      if(isEmpty(this.clientId)){
+        this.clientId = getRandomString(12);
+      }
+
+      let url = process.env.VUE_APP_BASE_API + "/app/NFIDReadRecord/subscribe/"+this.deviceSerial+"/"+this.clientId;
+      this.emitter = new EventSource(url);
+
+      this.emitter.onmessage = (event) => {
+        console.log(event.data);
+      };
+
+      this.emitter.addEventListener(this.deviceSerial, (event) => {
+        console.log(`[自定义事件] ${event.data}`);
+        const data = JSON.parse(event.data);
+       // this.form.produceTime = data.produceTime;
+        this.form.deviceSerial = data.deviceSerial;
+        this.form.hookNo = data.hookNo;
+        this.form.slaughterCode = data.slaughterCode;
+        this.form.entranceBatchId = data.entranceBatchId;
+        this.form.distributeBatchId = data.distributeBatchId;
+        this.form.supplierName = data.supplierName;
+        this.form.purchaserName = data.purchaserName;
+        this.form.animalCertNo = data.animalCertNo;
+
+      });
+
+      this.emitter.onerror = (error) => {
+        console.error("SSE 错误:", error);
+      };
+    },
+    //取消订阅
+    unSubNFIDRecord(){
+      console.log("取消订阅")
+      if (this.emitter) {
+        this.emitter.close();
+        this.emitter = null;
+        unsubscribeRead(this.deviceSerial,this.clientId )
+      }
+    },
+    closeDialog(){
+      this.unSubNFIDRecord();
+      this.deviceSerial = null;
+      this.clientId = null;
+    },
+    //获取全部白条产线
+    getAllNFIDReader() {
+      let param = {
+        deviceSpot: "weight",
+      };
+      listAllNFIDReader(param).then((response) => {
+        this.NFIDReaders = response.data;
+      });
+    },
     //空值显示 -
     showValue(value) {
       return value != null ? value : "-";
@@ -494,20 +632,28 @@ export default {
       this.form.scaleWeight = val;
       this.caclutateWeight();
     },
+    //首次需要选择默认产线
+    setDefaultDevice() {
+      if(isEmpty(this.form.deviceSerial)){
+        this.$set(this.form, "deviceSerial", this.NFIDReaders && this.NFIDReaders.length > 0
+            ? this.NFIDReaders[0].deviceSerial
+            : null,);
+      }
+    },
     //设置默认吊钩
-    setDefaultHook(){
-      this.hookOptions.forEach((item)=>{
-          if(item.defaultFlag){
-            console.log(item)
-            this.$set(this.form, "hookName", item.hookName);
-            this.$set(this.form, "hookWeight", item.hookWeight);
-            this.caclutateWeight();
-          }
-        })
+    setDefaultHook() {
+      this.hookOptions.forEach((item) => {
+        if (item.defaultFlag) {
+          console.log(item);
+          this.$set(this.form, "hookName", item.hookName);
+          this.$set(this.form, "hookWeight", item.hookWeight);
+          this.caclutateWeight();
+        }
+      });
     },
     //计算数量
     caclutateWeight() {
-      console.log("计算数量")
+      console.log("计算数量");
       let finalWeight =
         this.form.scaleWeight - this.form.hookWeight > 0
           ? this.form.scaleWeight - this.form.hookWeight
@@ -597,14 +743,12 @@ export default {
     getSlaughterRelationOptions() {
       getAllSlaughterRelation({}).then((response) => {
         this.slaughterRelationOptions = response.data;
-
       });
     },
     /** 查询吊钩清单 */
     getHookOptions() {
       getAllHook().then((response) => {
         this.hookOptions = response.data;
-
       });
     },
     /** 查询白条生产记录列表 */
@@ -626,7 +770,8 @@ export default {
       this.form = {
         id: null,
         productName: "白条",
-        produceTime: null,
+        produceTime: this.parseTime(new Date()),
+        deviceSerial:null,
         hookNo: null,
         slaughterCode: null,
         supplierName: null,
@@ -641,7 +786,8 @@ export default {
         finalWeight: null,
       };
       this.resetForm("form");
-      this.setDefaultHook()
+      this.setDefaultHook();
+      //this.setDefaultDevice();
       this.getRatio();
     },
     /** 搜索按钮操作 */
@@ -670,8 +816,6 @@ export default {
     /** 修改按钮操作 */
     handleUpdate(row) {
       this.reset();
-
-      this.getSlaughterRelationOptions();
       const id = row.id || this.ids;
       getPorkSideProduce(id).then((response) => {
         this.form = response.data;
@@ -679,7 +823,7 @@ export default {
         this.isView = false;
         this.title = "修改白条生产记录";
         //是否需要设置默认吊钩
-        if(this.form.hookName == null && this.form.hookWeight==null){
+        if (this.form.hookName == null && this.form.hookWeight == null) {
           this.setDefaultHook();
         }
       });
@@ -712,7 +856,10 @@ export default {
           } else {
             addPorkSideProduce(this.form).then((response) => {
               this.$modal.msgSuccess("新增成功");
-              this.open = false;
+              this.reset();
+              //重新启动串口重量数据
+              this.$refs.SerialInputRef.startReader();
+              //this.open = false;
               this.getList();
             });
           }
@@ -734,10 +881,9 @@ export default {
         .catch(() => {});
     },
     //清除吊钩绑定关系
-    handleCleanBind(){
+    handleCleanBind() {
       cleanBind().then((response) => {
-          this.$modal.msgSuccess("清除成功");
-
+        this.$modal.msgSuccess("清除成功");
       });
     },
     /** 导出按钮操作 */
@@ -753,3 +899,14 @@ export default {
   },
 };
 </script>
+<style>
+/* 样式保持不变 */
+.large-input {
+  font-size: 32px;
+  width: 300px;
+}
+.large-input > .el-input__inner {
+  height: 60px;
+  line-height: 60px;
+}
+</style>

+ 25 - 5
webUI/src/views/app/porkSideProduceRecord/index.vue

@@ -208,9 +208,18 @@
           </el-col>
         </el-row>
         <el-row :gutter="20">
-          <el-col :span="12">
+          <el-col :span="24">
             <el-form-item label="吊钩类型" prop="hookName">
-              <el-select
+              <el-radio-group v-model="form.hookName" @input="getHookType">
+                <el-radio
+                  v-for="(item, i) in hookOptions"
+                  :key="i"
+                  :label="item.hookName"
+                  border
+                  >{{ item.hookName }}</el-radio
+                >
+              </el-radio-group>
+              <!-- <el-select
                 v-model="form.hookName"
                 filterable
                 placeholder="请选择吊钩类型"
@@ -223,10 +232,10 @@
                   :label="item.hookName"
                   :value="item.hookName"
                 ></el-option>
-              </el-select>
+              </el-select> -->
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="24">
             <el-form-item label="吊钩扣重(kg)" prop="hookWeight">
               <el-input
                 v-model="form.hookWeight"
@@ -258,7 +267,7 @@
           >
         </el-form-item>
         <el-form-item label="宰后重量(kg)" prop="finalWeight">
-          <el-input v-model="form.finalWeight"  style="width: 240px;" disabled />
+          <el-input v-model="form.finalWeight" class="large-input" style="width: 240px;" disabled />
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer" v-if="!isView">
@@ -567,3 +576,14 @@ export default {
   },
 };
 </script>
+<style>
+/* 样式保持不变 */
+.large-input {
+  font-size: 32px;
+  width: 300px;
+}
+.large-input > .el-input__inner {
+  height: 60px;
+  line-height: 60px;
+}
+</style>

+ 28 - 0
webUI/src/views/app/slaughterBatch/index.vue

@@ -32,6 +32,17 @@
     </el-form>
 
     <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          @click="handleExport"
+          v-hasPermi="['app:slaughterBatch:export']"
+          >导出</el-button
+        >
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -355,6 +366,23 @@ export default {
         this.title = "查看屠宰批次";
       });
     },
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download(
+        "app/slaughterBatch/export",
+        {
+
+        },
+        `屠宰生产清单_${new Date().getTime()}.xlsx`
+      );
+      this.exportParam = {
+        ids: [],
+        supplierIds: [],
+      };
+      //清除选中状态
+      this.$refs.supplierTable.clearSelection();
+      this.$refs.codeTable.clearSelection();
+    },
     /** 提交按钮 */
     submitForm() {
       this.$refs["form"].validate(valid => {

+ 13 - 6
webUI/src/views/app/slaughterRelation/index.vue

@@ -173,6 +173,13 @@
           </template>
         </el-table-column>
       </el-table>
+      <pagination
+        v-show="total>0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
     </el-dialog>
     <!-- 添加血码关系对话框 -->
     <el-dialog
@@ -181,13 +188,13 @@
       width="800px"
       append-to-body
     >
-    <el-alert
+    <!-- <el-alert
           class="mb20"
           title="血码编号格式:1位字母+3位数字"
           type="info"
           :closable="false"
           show-icon>
-        </el-alert>
+        </el-alert> -->
       <el-form
         ref="batchForm"
         :model="batchForm"
@@ -248,11 +255,11 @@
                     message: '血码编号不能为空',
                     trigger: 'blur',
                   },
-                  {
+                  /*{
                     pattern: /^[a-zA-Z0-9]{2,4}$/,
                     message: '血码编号格式输入错误',
                     trigger: 'blur',
-                  },
+                  },*/
                 ]"
               >
                 <el-input
@@ -417,11 +424,11 @@ export default {
       rules: {
         slaughterCode: [
           { required: true, message: "血码编号不能为空", trigger: "blur" },
-          {
+          /*{
             pattern: /^[a-zA-Z0-9]{2,4}$/,
             message: "血码编号格式输入错误",
             trigger: "blur",
-          },
+          },*/
         ],
         supplierId: [
           { required: true, message: "关联供应商不能为空", trigger: "blur" },