liuwei 6 dienas atpakaļ
vecāks
revīzija
4ac83b4581

+ 2 - 2
src/com/mes/component/PlcSignalMonitor.java

@@ -298,11 +298,11 @@ public class PlcSignalMonitor {
     private void updateStatusLabel(JLabel label, boolean active) {
         if (label != null) {
             if (active) {
-                label.setText("ON");
+                label.setText("1");
                 label.setBackground(new Color(0, 200, 0));
                 label.setForeground(Color.WHITE);
             } else {
-                label.setText("OFF");
+                label.setText("0");
                 label.setBackground(new Color(200, 200, 200));
                 label.setForeground(Color.DARK_GRAY);
             }

+ 117 - 158
src/com/mes/component/WorkJPanelOP40.java

@@ -134,7 +134,16 @@ public class WorkJPanelOP40 extends JPanel {
             JdbcUtils.setCache(cacheName, JSON.toJSONString(cacheData));
         }
 
-        if(position.equals("A") || position.equals("B") || position.equals("C") || position.equals("D") || position.equals("E") || position.equals("F")){
+        // A/B/C 由 mes.side 切换东西侧;D-F 西 / G-L 东 逻辑保留
+        if(position.equals("A") || position.equals("B") || position.equals("C")){
+            if(MesClient.isEastSide()){
+                s7PLC = new S7PLC(EPlcType.S1500, plcIp2);
+                isScan = 2;
+            }else{
+                s7PLC = new S7PLC(EPlcType.S1500, plcIp1);
+                isScan = 1;
+            }
+        }else if(position.equals("D") || position.equals("E") || position.equals("F")){
             s7PLC = new S7PLC(EPlcType.S1500, plcIp1);
             isScan = 1;
         }else{
@@ -300,20 +309,20 @@ public class WorkJPanelOP40 extends JPanel {
 
         feedinglabel = new JLabel("请求上料");
         feedinglabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedinglabel.setIcon(imageGreen);
+        feedinglabel.setIcon(imageGray); // 默认 0,等 PLC 轮询后再亮
         feedinglabel.setForeground(Color.BLACK);
         feedinglabel.setBounds(0, 150, workWidth/3, 30);
         this.add(feedinglabel);
 
-        feedingmeslabel = new JLabel("MES扫码反馈");
+        feedingmeslabel = new JLabel("MES允许上料");
         feedingmeslabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedingmeslabel.setIcon(imageGreen);
+        feedingmeslabel.setIcon(imageGray);
         feedingmeslabel.setForeground(Color.BLACK);
         feedingmeslabel.setBounds(workWidth/3, 150, workWidth/3, 30);
         feedingmeslabel.addMouseListener(new MouseAdapter() {
             public void mouseClicked(MouseEvent e) {
                 boolean ret = readAllowFeeding();
-                int choice = JOptionPane.showConfirmDialog(null, "确定修改MES扫码反馈结果为"+(ret?"false":"true")+"吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+                int choice = JOptionPane.showConfirmDialog(null, "确定修改MES允许上料结果为"+(ret?"false":"true")+"吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
                 if (choice == JOptionPane.YES_OPTION) {
                     writeAllowFeeding(!ret);
                 }
@@ -323,28 +332,28 @@ public class WorkJPanelOP40 extends JPanel {
 
         runlabel = new JLabel("设备运行中");
         runlabel.setHorizontalAlignment(SwingConstants.LEFT);
-        runlabel.setIcon(imageGreen);
+        runlabel.setIcon(imageGray);
         runlabel.setForeground(Color.BLACK);
         runlabel.setBounds(workWidth*2/3, 150, workWidth/3, 30);
         this.add(runlabel);
 
         blankinglabel = new JLabel("下料请求");
         blankinglabel.setHorizontalAlignment(SwingConstants.LEFT);
-        blankinglabel.setIcon(imageGreen);
+        blankinglabel.setIcon(imageGray);
         blankinglabel.setForeground(Color.BLACK);
         blankinglabel.setBounds(0, 190, workWidth/3, 30);
         this.add(blankinglabel);
 
-        blankingmeslabel = new JLabel("MES下料反馈");
+        blankingmeslabel = new JLabel("MES允许下料");
         blankingmeslabel.setHorizontalAlignment(SwingConstants.LEFT);
-        blankingmeslabel.setIcon(imageGreen);
+        blankingmeslabel.setIcon(imageGray);
         blankingmeslabel.setForeground(Color.BLACK);
         blankingmeslabel.setBounds(workWidth/3, 190, workWidth/3, 30);
         this.add(blankingmeslabel);
         blankingmeslabel.addMouseListener(new MouseAdapter() {
             public void mouseClicked(MouseEvent e) {
                 boolean ret = readAllowBlanking();
-                int choice = JOptionPane.showConfirmDialog(null, "确定修改MES下料反馈结果为"+(ret?"false":"true")+"吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+                int choice = JOptionPane.showConfirmDialog(null, "确定修改MES允许下料结果为"+(ret?"false":"true")+"吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
                 if (choice == JOptionPane.YES_OPTION) {
                     writeAllowBlanking(!ret);
                 }
@@ -353,14 +362,14 @@ public class WorkJPanelOP40 extends JPanel {
 
         feedingtodevicelabel = new JLabel("上料到设备");
         feedingtodevicelabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedingtodevicelabel.setIcon(imageGreen);
+        feedingtodevicelabel.setIcon(imageGray);
         feedingtodevicelabel.setForeground(Color.BLACK);
         feedingtodevicelabel.setBounds(workWidth*2/3, 190, workWidth/3, 30);
         this.add(feedingtodevicelabel);
 
         feedingtotransferlabel = new JLabel("上料到中转车");
         feedingtotransferlabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedingtotransferlabel.setIcon(imageGreen);
+        feedingtotransferlabel.setIcon(imageGray);
         feedingtotransferlabel.setForeground(Color.BLACK);
         feedingtotransferlabel.setBounds(0, 230, workWidth/3, 30);
         this.add(feedingtotransferlabel);
@@ -368,7 +377,7 @@ public class WorkJPanelOP40 extends JPanel {
 
         transfertodevicelabel = new JLabel("中转车上料到设备");
         transfertodevicelabel.setHorizontalAlignment(SwingConstants.LEFT);
-        transfertodevicelabel.setIcon(imageGreen);
+        transfertodevicelabel.setIcon(imageGray);
         transfertodevicelabel.setForeground(Color.BLACK);
         transfertodevicelabel.setBounds(workWidth/3, 230, workWidth/3, 30);
         this.add(transfertodevicelabel);
@@ -487,14 +496,11 @@ public class WorkJPanelOP40 extends JPanel {
                         Boolean retFeedingAllow = readAllowFeeding();
                         feedingmeslabel.setIcon(retFeedingAllow?imageGreen:imageGray);
 
+                        // 与 auto3 一致:PLC 不再请求上料时,清掉 MES 允许上料,避免信号粘滞
                         if(!retFeeding && retFeedingAllow){
                             writeAllowFeeding(false);
                         }
 
-                        if(retFeeding){
-                            writeAllowFeeding(true);
-                        }
-
                         Boolean retBlanking = readBlanking();
                         blankinglabel.setIcon(retBlanking?imageGreen:imageGray);
 
@@ -517,8 +523,10 @@ public class WorkJPanelOP40 extends JPanel {
                         Boolean retRunning = readRunning();
                         runlabel.setIcon(retRunning?imageGreen:imageGray);
 
+                        // 无加工码却请求下料:允许下料并复位,防止死锁(对齐 auto3)
                         if(retBlanking && productSn.getText().isEmpty()){
                             writeAllowBlanking(true);
+                            resetBlanking();
                         }
 
                         // 只在状态变化时打印日志,减少日志量
@@ -554,35 +562,16 @@ public class WorkJPanelOP40 extends JPanel {
                         }
 
 
-                        if(MesClient.transferEnabled && plcDb.equals("DB7005") && toTransfer){
-                            // 工位F/L且去中转车:触发中转车扫码流程(码赋值给中转车,不影响工位6)
-                            log.info("[{}]tjFeedingFlag=0且retFeeding=true且toTransfer=true,触发中转车扫码", oprno+pos);
-                            barCodeForTransfer();
-                        }
-
                         if(tjFeedingFlag == 0){
-                            if((toDevice || transferToDevice) && !retRunning){
+                            if(retFeeding){
                                 if(MesClient.transferEnabled && plcDb.equals("DB7005") && toTransfer){
-//                                    // 工位F/L且去中转车:触发中转车扫码流程(码赋值给中转车,不影响工位6)
-//                                    log.info("[{}]tjFeedingFlag=0且retFeeding=true且toTransfer=true,触发中转车扫码", oprno+pos);
-//                                    barCodeForTransfer();
+                                    log.info("[{}]tjFeedingFlag=0且retFeeding=true且toTransfer=true,触发中转车扫码", oprno+pos);
+                                    barCodeForTransfer();
                                 } else {
-                                    // 正常工位加工:触发工位扫码流程
-                                    log.info("[{}]tjFeedingFlag=0且retFeeding=true,触发工位扫码", oprno+pos);
+                                    log.info("[{}]tjFeedingFlag=0且retFeeding=true,触发工位取码", oprno+pos);
                                     barCode();
                                 }
                             }
-                            /*if(retFeeding){
-                                if(plcDb.equals("DB7005") && toTransfer){
-//                                    // 工位F/L且去中转车:触发中转车扫码流程(码赋值给中转车,不影响工位6)
-//                                    log.info("[{}]tjFeedingFlag=0且retFeeding=true且toTransfer=true,触发中转车扫码", oprno+pos);
-//                                    barCodeForTransfer();
-                                } else {
-                                    // 正常工位加工:触发工位扫码流程
-                                    log.info("[{}]tjFeedingFlag=0且retFeeding=true,触发工位扫码", oprno+pos);
-                                    barCode();
-                                }
-                            }*/
 
                         }else if(tjFeedingFlag == 1){
                             if(tjFlag == 0 && retRunning){
@@ -602,17 +591,15 @@ public class WorkJPanelOP40 extends JPanel {
                                         log.info("[{}]提交中转台数据...", oprno+pos);
                                         JSONObject retObj = DataUtil.saveSn(oprno+pos,productSnFeeding.getText().trim(),transferSn,transferFloor,MesClient.user_menu.getText());
                                         log.info("[{}]中转台提交结果: {}", oprno+pos, retObj);
-                                        if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                                            tjTransferStatus = 0; // 提交成功,重置状态
+                                        if(retObj != null && retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
+                                            tjTransferStatus = 0;
                                             resetFeeding();
                                         }else{
-                                            tjTransferStatus = 0; // 提交失败,重置状态允许重试
-                                            if(retObj.get("result")==null){
+                                            tjTransferStatus = 0;
+                                            if(retObj == null || retObj.get("result")==null){
                                                 setMenuStatus("请求失败,请重试",-1);
-                                            }else{
-                                                if(retObj.get("result").toString().equalsIgnoreCase("false")){
-                                                    setMenuStatus(retObj.getString("message"),-1);
-                                                }
+                                            }else if(retObj.get("result").toString().equalsIgnoreCase("false")){
+                                                setMenuStatus(retObj.getString("message"),-1);
                                             }
                                         }
                                     }
@@ -638,14 +625,10 @@ public class WorkJPanelOP40 extends JPanel {
 
                             }
                         }else if(tjFlag == 2){
-                            if(!retRunning || retBlanking){ // 提交结果
+                            // 与 auto3 一致:只认 PLC 请求下料,不停机即报工
+                            if(retBlanking){
                                 upResult();
                             }
-                            // 只在状态变化时打印,避免日志过多
-                            // log.info("[{}]tjFlag=2, 等待下料信号, retBlanking={}", oprno+pos, retBlanking);
-//                            if(retBlanking){
-//                                upResult();
-//                            }
                         }else if(tjFlag == 3){
                             // 只在状态变化时打印,避免日志过多
                             // log.info("[{}]tjFlag=3, 等待上传完成重置, tjQualityStatus={}", oprno+pos, tjQualityStatus);
@@ -658,54 +641,48 @@ public class WorkJPanelOP40 extends JPanel {
                     log.error(e.getMessage());
                 }
             }
-        }, 100,3000);
+        }, 100,1500);
     }
 
     public void upResult(){
-        log.info("[{}]tjFlag 2->3,收到下料信号,准备上传结果, tjQualityStatus={}", oprno+pos, tjQualityStatus);
+        if(tjQualityStatus == 1){
+            log.info("[{}]tjQualityStatus=1,跳过上传(已在上传中)", oprno+pos);
+            return;
+        }
+        String sn = productSn.getText() == null ? "" : productSn.getText().trim();
+        if(sn.isEmpty() || sn.equals("空结果")){
+            log.warn("[{}]下料时加工码为空,允许下料并复位", oprno+pos);
+            writeAllowBlanking(true);
+            resetBlanking();
+            return;
+        }
+        log.info("[{}]tjFlag 2->3,收到下料信号,准备上传结果, sn={}", oprno+pos, sn);
         tjFlag = 3;
-        tjQualityStatus = 0; // 强制重置,确保能触发上传
+        tjQualityStatus = 1;
         setMenuStatus("工件加工结束,结果上传中", 0);
-
-        if(tjQualityStatus == 0){
-            tjQualityStatus = 1;
-//                                    log.info("[{}]开始异步上传质量数据, productSn={}", oprno+pos, productSn.getText());
-            log.info("[{}]开始上传质量数据, productSn={}", oprno+pos, productSn.getText());
-
-            // 异步执行上传任务,防止阻塞
-//                                    ExecutorService executor = Executors.newCachedThreadPool();
-//                                    executor.submit(() -> {
-            // 你的异步任务代码
-            log.info("["+oprno+pos+"]质量上传任务开始, sn="+productSn.getText());
-            try {
-                JSONObject retObj = DataUtil.sendQuality(productSn.getText(),oprno+pos,"OK",MesClient.user_menu.getText());
-                log.info("["+oprno+pos+"]质量上传返回结果: "+retObj);
-                if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                    log.info("["+oprno+pos+"]质量上传成功,执行resetBlanking");
-                    resetBlanking();
+        try {
+            JSONObject retObj = DataUtil.sendQuality(sn, oprno+pos, "OK", MesClient.user_menu.getText());
+            log.info("[{}]质量上传返回结果: {}", oprno+pos, retObj);
+            if(retObj != null && retObj.get("result") != null
+                    && retObj.get("result").toString().equalsIgnoreCase("true")) {
+                log.info("[{}]质量上传成功,执行resetBlanking", oprno+pos);
+                resetBlanking();
+            }else{
+                tjQualityStatus = 0;
+                tjFlag = 2;
+                if(retObj == null || retObj.get("result") == null){
+                    log.error("[{}]质量上传失败,result为null", oprno+pos);
+                    setMenuStatus("请求失败,请重试", -1);
                 }else{
-                    tjQualityStatus = 0; // 上传失败,重置状态允许重试
-                    if(retObj.get("result")==null){
-                        log.error("["+oprno+pos+"]质量上传失败,result为null");
-                        setMenuStatus("请求失败,请重试",-1);
-                    }else{
-                        if(retObj.get("result").toString().equalsIgnoreCase("false")){
-                            log.error("["+oprno+pos+"]质量上传失败: "+retObj.getString("message"));
-                            setMenuStatus(retObj.getString("message"),-1);
-                        }
-                    }
+                    log.error("[{}]质量上传失败: {}", oprno+pos, retObj.getString("message"));
+                    setMenuStatus(retObj.getString("message"), -1);
                 }
-            } catch (Exception e) {
-                tjQualityStatus = 0; // 异常时重置状态允许重试
-                log.error("["+oprno+pos+"]质量上传异常: "+e.getMessage(), e);
-                Thread.currentThread().interrupt();
             }
-            log.info("["+oprno+pos+"]质量上传任务结束");
-//                                    });
-            // 关闭ExecutorService,不再接受新任务,等待已提交的任务完成
-//                                    executor.shutdown();
-        }else{
-            log.info("[{}]tjQualityStatus={},跳过上传(已在上传中)", oprno+pos, tjQualityStatus);
+        } catch (Exception e) {
+            tjQualityStatus = 0;
+            tjFlag = 2;
+            log.error("[{}]质量上传异常: {}", oprno+pos, e.getMessage(), e);
+            setMenuStatus("请求失败,请重试", -1);
         }
     }
 
@@ -714,58 +691,47 @@ public class WorkJPanelOP40 extends JPanel {
             return;
         }
         String sn = readQrcode();
-        if(sn.equals("")){
-            if(productSnFeeding.getText().isEmpty()){
-                scanRetryCount++;
-                if(scanRetryCount > MAX_SCAN_RETRY){
-                    productSnFeeding.setText("空结果");
-                    this.setMenuStatus("扫码失败(已重试" + scanRetryCount + "次),请人工处理", -1);
-                    log.warn("[{}]扫码失败,已达最大重试次数,等待人工处理", oprno+pos);
-                }else{
-                    this.setMenuStatus("扫码结果为空,准备重试(第" + scanRetryCount + "次)", -1);
-                    log.info("[{}]扫码结果为空,将在下次轮询时重试(第{}次)", oprno+pos, scanRetryCount);
-                }
-            }
+        if(sn == null){
+            sn = "";
+        }
+        sn = sn.trim();
+        if(sn.isEmpty()){
+            // 对齐 auto3:码未就绪不写死「空结果」,下个轮询继续取
+            setMenuStatus("等待扫码站扫码", 0);
+            return;
+        }
+        productSnFeeding.setText(sn);
+        lastSn = sn;
+        consumeScanSn(sn);
+        JSONObject retObj = DataUtil.checkQuality(sn, oprno+pos, MesClient.user_menu.getText());
+        if(retObj != null && retObj.get("result") != null
+                && retObj.get("result").toString().equalsIgnoreCase("true")) {
+            tjFeedingFlag = 1;
+            scanRetryCount = 0;
+            setMenuStatus("工件可以加工", 0);
+            writeAllowFeeding(true);
         }else{
-            productSnFeeding.setText(sn);
-            lastSn = sn;
-            // 查询工件质量
-            JSONObject retObj = DataUtil.checkQuality(sn,oprno+pos,MesClient.user_menu.getText());
-            if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                tjFeedingFlag = 1;//可加工
-                scanRetryCount = 0; // 成功后重置重试计数
-                setMenuStatus("工件可以加工", 0);
-                writeAllowFeeding(true);
-//                resetFeedingBtn.setEnabled(false);
+            writeAllowFeeding(false);
+            resetFeedingBtn.setEnabled(true);
+            if(retObj == null || retObj.get("result") == null){
+                setMenuStatus("请求失败,请重试", -1);
             }else{
-                writeAllowFeeding(false);
-                resetFeedingBtn.setEnabled(true);
-                // 网络失败时清空productSnFeeding,允许重试
-//                scanRetryCount++;
-//                if(scanRetryCount > MAX_SCAN_RETRY){
-//                    log.warn("[{}]checkQuality失败,已达最大重试次数({}次),等待人工处理, sn={}", oprno+pos, scanRetryCount, sn);
-//                    if(retObj.get("result")==null){
-//                        setMenuStatus("请求超时(已重试" + scanRetryCount + "次),请人工处理",-1);
-//                    }else{
-//                        if(retObj.get("result").toString().equalsIgnoreCase("false")){
-//                            setMenuStatus(retObj.getString("message"),-1);
-//                        }
-//                    }
-//                }else{
-//                    log.info("[{}]checkQuality失败,清空productSnFeeding准备重试(第{}次), sn={}", oprno+pos, scanRetryCount, sn);
-//                    productSnFeeding.setText(""); // 清空,允许下次轮询重新扫码
-//                    if(retObj.get("result")==null){
-//                        setMenuStatus("请求超时,准备重试(第" + scanRetryCount + "次)",-1);
-//                    }else{
-//                        if(retObj.get("result").toString().equalsIgnoreCase("false")){
-//                            setMenuStatus(retObj.getString("message") + ",准备重试",-1);
-//                        }
-//                    }
-//                }
+                setMenuStatus(retObj.getString("message"), -1);
             }
         }
     }
 
+    private void consumeScanSn(String sn){
+        if(sn == null || sn.isEmpty()){
+            return;
+        }
+        if(isScan == 1 && sn.equals(MesClient.scanSnA)){
+            MesClient.scanSnA = "";
+        }else if(isScan == 2 && sn.equals(MesClient.scanSnB)){
+            MesClient.scanSnB = "";
+        }
+    }
+
     /**
      * 专门给中转车扫码的方法(仅工位F/L使用)
      * 触发摄像头扫码,但码赋值给中转车的productSnFeeding,不影响工位6的状态
@@ -1101,32 +1067,24 @@ public class WorkJPanelOP40 extends JPanel {
                     log.info("[{}]中转车上料到设备: transferSn={}, transferFloor={}", oprno+pos, transferSn, transferFloor);
                     JSONObject retObj = DataUtil.getSn(oprno+pos,transferSn,transferFloor,MesClient.user_menu.getText());
                     log.info("[{}]getSn返回结果: {}", oprno+pos, retObj);
-                    if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
+                    if(retObj != null && retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
                         sn = retObj.getString("data");
                         log.info("[{}]中转车获取到工件码: {}", oprno+pos, sn);
                         this.setMenuStatus("允许工件加工",0);
-//                        writeAllowFeeding(true);
                     }else{
-//                        writeAllowFeeding(false);
-                        if(retObj.get("result")==null){
+                        if(retObj == null || retObj.get("result")==null){
                             log.error("[{}]getSn失败: result为null", oprno+pos);
                             this.setMenuStatus("请求失败,请重试",-1);
-                        }else{
-                            if(retObj.get("result").toString().equalsIgnoreCase("false")){
-                                log.error("[{}]getSn失败: {}", oprno+pos, retObj.getString("message"));
-                                this.setMenuStatus(retObj.getString("message"),-1);
-                            }
+                        }else if(retObj.get("result").toString().equalsIgnoreCase("false")){
+                            log.error("[{}]getSn失败: {}", oprno+pos, retObj.getString("message"));
+                            this.setMenuStatus(retObj.getString("message"),-1);
                         }
                     }
                 }else{
                     if(isScan == 1){
-                        log.info("[{}]触发西侧摄像头扫码", oprno+pos);
-//                        sn = MesClient.scanQrcode1();
-                        sn = MesClient.scanSnA;
+                        sn = MesClient.scanSnA == null ? "" : MesClient.scanSnA;
                     }else{
-                        log.info("[{}]触发东侧摄像头扫码", oprno+pos);
-                        sn = MesClient.scanSnB;
-//                        sn = MesClient.scanQrcode2();
+                        sn = MesClient.scanSnB == null ? "" : MesClient.scanSnB;
                     }
                 }
                 log.info("[{}]扫码完成,结果: {}", oprno+pos, sn);
@@ -1184,10 +1142,11 @@ public class WorkJPanelOP40 extends JPanel {
 
     // 获取工位站点标注
     private String getStationLabel(String position) {
+        String side = MesClient.isEastSide() ? "东侧" : "西侧";
         switch (position) {
-            case "A": return "西侧1号机";
-            case "B": return "西侧2号机";
-            case "C": return "西侧3号机";
+            case "A": return side + "1号机";
+            case "B": return side + "2号机";
+            case "C": return side + "3号机";
             case "D": return "西侧4号机";
             case "E": return "西侧5号机";
             case "F": return "西侧6号机";

+ 67 - 16
src/com/mes/component/WorkJPanelScan.java

@@ -15,6 +15,8 @@ import javax.swing.*;
 import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.Timer;
@@ -32,6 +34,9 @@ public class WorkJPanelScan extends JPanel {
 
     public String lineSn = "CZ060";
 
+    /** 工件码格式例如 CZ0600016A090001,共 16 位 */
+    public static final int SN_LEN = 16;
+
     public JLabel feedinglabel;
 
     public JLabel feedingmeslabel;
@@ -148,13 +153,24 @@ public class WorkJPanelScan extends JPanel {
         productSnFeeding.setHorizontalAlignment(SwingConstants.CENTER);
         productSnFeeding.setForeground(Color.BLACK);
         productSnFeeding.setFont(new Font("微软雅黑", Font.PLAIN, 16));
-        productSnFeeding.setEditable(false);
+        productSnFeeding.setEditable(true);
+        productSnFeeding.setToolTipText("可手动输入工件码后按回车确认");
         productSnFeeding.setBounds(workWidth/5, 50, workWidth*3/5, 40);
+        productSnFeeding.addKeyListener(new KeyAdapter() {
+            @Override
+            public void keyPressed(KeyEvent e) {
+                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+                    applyManualSn();
+                }
+            }
+        });
         productSnFeeding.addMouseListener(new MouseAdapter() {
             public void mouseClicked(MouseEvent e) {
-                String sn = productSnFeeding.getText().trim();
-                if(!sn.isEmpty()){
-                    MesClient.createQRCode(productSnFeeding.getText(),oprno+pos);
+                if (e.getClickCount() >= 2) {
+                    String sn = productSnFeeding.getText().trim();
+                    if (!sn.isEmpty()) {
+                        MesClient.createQRCode(sn, oprno + pos);
+                    }
                 }
             }
         });
@@ -181,20 +197,20 @@ public class WorkJPanelScan extends JPanel {
 
         feedinglabel = new JLabel("请求扫码");
         feedinglabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedinglabel.setIcon(imageGreen);
+        feedinglabel.setIcon(imageGray); // 默认 0
         feedinglabel.setForeground(Color.BLACK);
         feedinglabel.setBounds(0, 150, workWidth/3, 30);
         this.add(feedinglabel);
 
-        feedingmeslabel = new JLabel("MES扫码反馈");
+        feedingmeslabel = new JLabel("MES允许扫码");
         feedingmeslabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedingmeslabel.setIcon(imageGreen);
+        feedingmeslabel.setIcon(imageGray);
         feedingmeslabel.setForeground(Color.BLACK);
         feedingmeslabel.setBounds(workWidth/3, 150, workWidth/3, 30);
         feedingmeslabel.addMouseListener(new MouseAdapter() {
             public void mouseClicked(MouseEvent e) {
                 boolean ret = readScanFinish();
-                int choice = JOptionPane.showConfirmDialog(null, "确定修改MES扫码反馈结果为"+(ret?"false":"true")+"吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+                int choice = JOptionPane.showConfirmDialog(null, "确定修改MES允许扫码结果为"+(ret?"false":"true")+"吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
                 if (choice == JOptionPane.YES_OPTION) {
                     writeScanFinish(!ret);
                 }
@@ -290,7 +306,12 @@ public class WorkJPanelScan extends JPanel {
                         feedingmeslabel.setIcon(retFeedingAllow?imageGreen:imageGray);
 
                         if(retFeeding){
-                            barCode2();
+                            String current = productSnFeeding.getText() == null ? "" : productSnFeeding.getText().trim();
+                            if(isValidSn(current)){
+                                writeScanFinish(true);
+                            }else{
+                                barCode2();
+                            }
                         }else{
                             writeScanFinish(false);
                         }
@@ -304,18 +325,48 @@ public class WorkJPanelScan extends JPanel {
 
     public void barCode2(){
         String sn = readQrcode();
+        if(sn == null){
+            sn = "";
+        }
+        sn = sn.trim();
+        if(!isValidSn(sn)){
+            setMenuStatus("扫码结果为空或格式不符", -1);
+            return;
+        }
         if(pos.equals("A")){
-            MesClient.scanSnA = sn.trim();
+            MesClient.scanSnA = sn;
         }else{
-            MesClient.scanSnB = sn.trim();
+            MesClient.scanSnB = sn;
         }
-        if(sn.length() != 19){
-            setMenuStatus("扫码结果为空", -1);
+        setMenuStatus("扫码成功", 0);
+        productSnFeeding.setText(sn);
+        writeScanFinish(true);
+    }
+
+    /** 手动输入工件码:回车确认,写入全局码并回写 PLC 扫码完成 */
+    public void applyManualSn(){
+        String sn = productSnFeeding.getText() == null ? "" : productSnFeeding.getText().trim();
+        if(sn.isEmpty()){
+            setMenuStatus("请输入工件码", -1);
+            return;
+        }
+        if(!isValidSn(sn)){
+            setMenuStatus("工件码应为16位(如 CZ0600016A090001),当前" + sn.length() + "位", -1);
+            return;
+        }
+        if(pos.equals("A")){
+            MesClient.scanSnA = sn;
         }else{
-            setMenuStatus("扫码成功", 0);
-            productSnFeeding.setText(sn);
-            writeScanFinish(true);
+            MesClient.scanSnB = sn;
         }
+        productSnFeeding.setText(sn);
+        writeScanFinish(true);
+        setMenuStatus("手动录入成功", 0);
+        log.info("[{}]手动录入工件码: {}", oprno + pos, sn);
+    }
+
+    public static boolean isValidSn(String sn){
+        return sn != null && sn.length() == SN_LEN;
     }
 
     public void resetFeeding() {

+ 59 - 64
src/com/mes/ui/MesClient.java

@@ -80,13 +80,23 @@ public class MesClient extends JFrame {
     public static int scanFlag2 = 0;
     
     // MES启用/屏蔽状态(true=启用,false=屏蔽)
-    public static boolean mesEnabledWest = true;  // 西侧MES启用状态(A-F)
-    public static boolean mesEnabledEast = true;  // 东侧MES启用状态(G-L)
+    public static boolean mesEnabledWest = true;  // 西侧MES启用状态
+    public static boolean mesEnabledEast = true;  // 东侧MES启用状态
+    // 产线侧:west=西侧 / east=东侧(config: mes.side)
+    public static String mesSide = "east";
     // 中转车启用(false=隐藏中转UI并跳过中转业务;扫码逻辑不变)
     public static boolean transferEnabled = false;
     // OPC UA 直连采集(false=不连机床、不采参、隐藏CNC监控Tab)
     public static boolean opcuaEnabled = false;
 
+    public static boolean isEastSide() {
+        return mesSide != null && "east".equalsIgnoreCase(mesSide.trim());
+    }
+
+    public static boolean isWestSide() {
+        return !isEastSide();
+    }
+
 
     public static WorkJPanelOP40 workJPanelOP40A;
     public static WorkJPanelOP40 workJPanelOP40B;
@@ -220,6 +230,14 @@ public class MesClient extends JFrame {
             transferEnabled = Boolean.parseBoolean(pro.getProperty("mes.transfer_enabled").trim());
         }
         log.info("中转车启用: {}", transferEnabled);
+        if (pro.getProperty("mes.side") != null) {
+            mesSide = pro.getProperty("mes.side").trim();
+        }
+        if (!"west".equalsIgnoreCase(mesSide) && !"east".equalsIgnoreCase(mesSide)) {
+            log.warn("mes.side={} 无效,默认 east", mesSide);
+            mesSide = "east";
+        }
+        log.info("产线侧: {} ({})", isEastSide() ? "东侧" : "西侧", mesSide);
         if (pro.getProperty("mes.opcua_enabled") != null) {
             opcuaEnabled = Boolean.parseBoolean(pro.getProperty("mes.opcua_enabled").trim());
         }
@@ -994,6 +1012,15 @@ public class MesClient extends JFrame {
 //        nettyClient = null;
     }
 
+    private static JPanel wrapStationCard(JComponent station, int width, int height) {
+        station.setBounds(12, 12, width, height);
+        JPanel wrap = new JPanel(null);
+        wrap.setBackground(Color.WHITE);
+        wrap.setBorder(BorderFactory.createLineBorder(new Color(0xC8C8C8)));
+        wrap.add(station);
+        return wrap;
+    }
+
     public MesClient() {
         setIconImage(Toolkit.getDefaultToolkit().getImage(MesClient.class.getResource("/bg/logo.png")));
         setTitle("MES系统客户端:OP055 CNC侧铣");
@@ -1111,8 +1138,7 @@ public class MesClient extends JFrame {
                 log.info("东侧MES状态切换: {}", mesEnabledEast ? "启用" : "屏蔽");
             }
         });
-        // 东侧 MES 菜单隐藏(现场仅西侧三台);逻辑保留
-        // settingMenu.add(mesEastMenu);
+        settingMenu.add(mesEastMenu);
 
         contentPane = new JPanel();
         contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
@@ -1184,35 +1210,30 @@ public class MesClient extends JFrame {
         tabbedPane.setFont(new Font("宋体", Font.BOLD, 22));
         contentPane.add(tabbedPane);
 
-        // 现场仅 3 台 CNC(A/B/C);D–L 字段保留不删,不创建面板
+        // 现场 3 台 CNC + 本侧扫码:2×2;由 mes.side 切换东西侧
         JPanel indexPanel1 = new JPanel();
         indexScrollPane1 = new JScrollPane(indexPanel1);
-        indexPanel1.setLayout(new GridLayout(1, 3, 5, 5));
-        indexPanel1.setBackground(Color.LIGHT_GRAY);
+        indexScrollPane1.setBorder(null);
+        indexPanel1.setLayout(new GridLayout(2, 2, 8, 8));
+        indexPanel1.setBorder(new EmptyBorder(8, 8, 8, 8));
+        indexPanel1.setBackground(new Color(0xE6E6E6));
 
-        JPanel indexPanel40A = new JPanel();
-        indexPanel40A.setLayout(null);
-        indexPanel40A.setBackground(Color.WHITE);
         workJPanelOP40A = new WorkJPanelOP40("A");
-        workJPanelOP40A.setBounds(10, 10, workJPanelOP40A.workWidth, workJPanelOP40A.workHeight);
-        indexPanel40A.add(workJPanelOP40A);
-        indexPanel1.add(indexPanel40A);
-
-        JPanel indexPanel40B = new JPanel();
-        indexPanel40B.setLayout(null);
-        indexPanel40B.setBackground(Color.WHITE);
+        indexPanel1.add(wrapStationCard(workJPanelOP40A, workJPanelOP40A.workWidth, workJPanelOP40A.workHeight));
         workJPanelOP40B = new WorkJPanelOP40("B");
-        workJPanelOP40B.setBounds(10, 10, workJPanelOP40B.workWidth, workJPanelOP40B.workHeight);
-        indexPanel40B.add(workJPanelOP40B);
-        indexPanel1.add(indexPanel40B);
-
-        JPanel indexPanel40C = new JPanel();
-        indexPanel40C.setLayout(null);
-        indexPanel40C.setBackground(Color.WHITE);
+        indexPanel1.add(wrapStationCard(workJPanelOP40B, workJPanelOP40B.workWidth, workJPanelOP40B.workHeight));
         workJPanelOP40C = new WorkJPanelOP40("C");
-        workJPanelOP40C.setBounds(10, 10, workJPanelOP40C.workWidth, workJPanelOP40C.workHeight);
-        indexPanel40C.add(workJPanelOP40C);
-        indexPanel1.add(indexPanel40C);
+        indexPanel1.add(wrapStationCard(workJPanelOP40C, workJPanelOP40C.workWidth, workJPanelOP40C.workHeight));
+
+        if (isEastSide()) {
+            workJPanelScanA = null;
+            workJPanelScanB = new WorkJPanelScan("B");
+            indexPanel1.add(wrapStationCard(workJPanelScanB, workJPanelScanB.workWidth, workJPanelScanB.workHeight));
+        } else {
+            workJPanelScanB = null;
+            workJPanelScanA = new WorkJPanelScan("A");
+            indexPanel1.add(wrapStationCard(workJPanelScanA, workJPanelScanA.workWidth, workJPanelScanA.workHeight));
+        }
 
         workJPanelOP40D = null;
         workJPanelOP40E = null;
@@ -1224,54 +1245,28 @@ public class MesClient extends JFrame {
         workJPanelOP40K = null;
         workJPanelOP40L = null;
 
-        tabbedPane.addTab("OP055:A-C", null, indexScrollPane1, null);
+        tabbedPane.addTab("OP055", null, indexScrollPane1, null);
         tabbedPane.setEnabledAt(0, true);
 
-
-        // 扫码站 Tab(中转车关闭时不创建中转车面板)
-        JPanel indexPanelTransfer = new JPanel();
-        JScrollPane indexScrollPaneTransfer = new JScrollPane(indexPanelTransfer);
-        indexPanelTransfer.setBackground(Color.LIGHT_GRAY);
-
+        // 中转车开启时单独一页;关闭则扫码已在首页第四格,不再留空 Tab
         if (transferEnabled) {
-            indexPanelTransfer.setLayout(new GridLayout(2, 2, 5, 5));
+            JPanel indexPanelTransfer = new JPanel();
+            JScrollPane indexScrollPaneTransfer = new JScrollPane(indexPanelTransfer);
+            indexPanelTransfer.setBackground(new Color(0xE6E6E6));
+            indexPanelTransfer.setLayout(new GridLayout(2, 2, 8, 8));
+            indexPanelTransfer.setBorder(new EmptyBorder(8, 8, 8, 8));
 
-            // 中转车1 - 连接PLC3(西侧192.168.90.83) DB7005
-            JPanel indexPanelTransfer1 = new JPanel();
-            indexPanelTransfer1.setLayout(null);
-            indexPanelTransfer1.setBackground(Color.WHITE);
             workJPanelOP40Transfer1 = new WorkJPanelOP40Transfer("A");
-            workJPanelOP40Transfer1.setBounds(10, 10, workJPanelOP40Transfer1.workWidth, workJPanelOP40Transfer1.workHeight);
-            indexPanelTransfer1.add(workJPanelOP40Transfer1);
-            indexPanelTransfer.add(indexPanelTransfer1);
-
-            // 中转车2 - 连接PLC4(东侧192.168.90.84) DB7005
-            JPanel indexPanelTransfer2 = new JPanel();
-            indexPanelTransfer2.setLayout(null);
-            indexPanelTransfer2.setBackground(Color.WHITE);
+            indexPanelTransfer.add(wrapStationCard(workJPanelOP40Transfer1, workJPanelOP40Transfer1.workWidth, workJPanelOP40Transfer1.workHeight));
             workJPanelOP40Transfer2 = new WorkJPanelOP40Transfer("B");
-            workJPanelOP40Transfer2.setBounds(10, 10, workJPanelOP40Transfer2.workWidth, workJPanelOP40Transfer2.workHeight);
-            indexPanelTransfer2.add(workJPanelOP40Transfer2);
-            indexPanelTransfer.add(indexPanelTransfer2);
+            indexPanelTransfer.add(wrapStationCard(workJPanelOP40Transfer2, workJPanelOP40Transfer2.workWidth, workJPanelOP40Transfer2.workHeight));
+
+            tabbedPane.addTab("中转车", null, indexScrollPaneTransfer, null);
         } else {
-            indexPanelTransfer.setLayout(new GridLayout(1, 1, 5, 5));
             workJPanelOP40Transfer1 = null;
             workJPanelOP40Transfer2 = null;
         }
 
-        JPanel indexPanelScanA = new JPanel();
-        indexPanelScanA.setLayout(null);
-        indexPanelScanA.setBackground(Color.WHITE);
-        workJPanelScanA = new WorkJPanelScan("A");
-        workJPanelScanA.setBounds(10, 10, workJPanelScanA.workWidth, workJPanelScanA.workHeight);
-        indexPanelScanA.add(workJPanelScanA);
-        indexPanelTransfer.add(indexPanelScanA);
-
-        // 东侧扫码站隐藏,不创建面板;类/字段/scanSnB 保留
-        workJPanelScanB = null;
-
-        tabbedPane.addTab(transferEnabled ? "中转车" : "扫码站", null, indexScrollPaneTransfer, null);
-
 
         // ========== 工作记录 Tab(改造:OP055子工位Tab + 所有工位) ==========
         // 创建工作记录的子 TabbedPane

+ 2 - 0
src/resources/config/config.properties

@@ -3,6 +3,8 @@ mes.server_ip=192.168.17.170
 mes.line_sn=CZ060
 # 中转车:false=隐藏UI并跳过中转逻辑;true=恢复中转
 mes.transfer_enabled=false
+# 产线侧:west=西侧(PLC83/枪56/scanSnA)  east=东侧(PLC84/枪55/scanSnB)
+mes.side=east
 # OPC UA 直连采集:false=不连机床、不采参、隐藏CNC监控Tab;true=恢复
 mes.opcua_enabled=false