jingbo преди 6 дни
родител
ревизия
103e433fd8

+ 38 - 9
src/com/mes/ui/DataUtil.java

@@ -74,23 +74,49 @@ public class DataUtil {
 
     public static JSONObject sendQualityPc(String sn, String ret, String user, String oprno, String lineSn, String serverIp){
         try{
+            // 提交前确保操作员不为空
+            if (user == null || user.trim().isEmpty()) {
+                MesClient.getUser();
+                user = MesClient.user20;
+            }
             String url = "http://"+serverIp+":8980/js/a/mes/mesProductRecord/pcresult";
             String params = "__ajax=json&oprno="+oprno+"&lineSn="+lineSn+"&sn="+sn+"&result="+ret+"&ucode="+user+"&__sid="+MesClient.sessionid;
-            log.info("params="+params);
+            log.info("sendQualityPc params="+params);
             String result = doPost(url,params);
-            log.info("result="+result);
-            if(result == null || result.equalsIgnoreCase("false")) {
+            log.info("sendQualityPc result="+result);
+            if(result == null || result.trim().isEmpty() || result.equalsIgnoreCase("false")) {
                 return null;
-            }else {
+            }
+            JSONObject json = JSONObject.parseObject(result);
+            if (json == null) {
+                return null;
+            }
+            // 本地入库失败不影响判定 MES 是否已提交成功
+            try {
                 JdbcUtils.insertData(oprno, "100000", params, "MQDW", sn);
-                return JSONObject.parseObject(result);
+            } catch (Exception ex) {
+                log.info("sendQualityPc本地入库异常(不影响结果)="+ex.getMessage());
             }
+            return json;
         }catch (Exception e){
-            log.info("e="+e.getMessage());
+            log.info("sendQualityPc异常="+e.getMessage());
+            e.printStackTrace();
             return null;
         }
     }
 
+    /** 判断 MES 返回是否成功(兼容 result 为 true/"true") */
+    public static boolean isMesSuccess(JSONObject result) {
+        if (result == null) {
+            return false;
+        }
+        Object r = result.get("result");
+        if (r == null) {
+            return false;
+        }
+        return "true".equalsIgnoreCase(String.valueOf(r).trim());
+    }
+
     public static JSONObject upParams(String upparams) {
         try{
             String enconding = "UTF-8";
@@ -100,6 +126,7 @@ public class DataUtil {
             pro.load(br);
             String mes_server_ip = pro.getProperty("mes.server_ip");
             String url = "http://"+mes_server_ip+":8980/js/a/mes/mesProductCmt/batchsave";
+            // 与 OP040 一致:结果 sendQualityPc 与参数 batchsave 分开上传,params 原样提交
             String params = "__ajax=json&params="+upparams;
             log.info("params="+params);
             String result = doPost(url,params);
@@ -110,6 +137,7 @@ public class DataUtil {
                 return JSONObject.parseObject(result);
             }
         }catch (Exception e){
+            log.info("upParams异常="+e.getMessage());
             return null;
         }
     }
@@ -215,10 +243,11 @@ public class DataUtil {
             connection.setReadTimeout(60000);
             connection.setDoOutput(true);
             connection.setDoInput(true);
-            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            // 必须显式 UTF-8:打包 exe 后 JVM 默认编码常为 GBK,getBytes() 会把中文参数打成乱码
+            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
             connection.setRequestProperty("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0");
             os = connection.getOutputStream();
-            os.write(param.getBytes());
+            os.write(param.getBytes("UTF-8"));
             if (connection.getResponseCode() == 200) {
                 is = connection.getInputStream();
                 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
@@ -280,7 +309,7 @@ public class DataUtil {
             String mes_server_ip = pro.getProperty("mes.server_ip");
             String lineSn = pro.getProperty("mes.line_sn").trim();
             
-            String url = "http://" + mes_server_ip + ":8980/js/a/mes/mesProductRecord/ ";
+            String url = "http://" + mes_server_ip + ":8980/js/a/mes/mesProductRecord/listData";
             StringBuilder params = new StringBuilder();
             params.append("__ajax=json");
             params.append("&lineSn=").append(lineSn);

+ 1 - 0
src/com/mes/ui/LoginFarme.java

@@ -188,6 +188,7 @@ public class LoginFarme extends JFrame {
                 MesClient.welcomeWin.setVisible(false);
                 MesClient.mesClientFrame.setVisible(true);
                 MesClient.initDualStationCheckTabs(user_id);
+                MesClient.initDualStationWorkRecordTabs();
                 MesClient.startGetCurSn();
                 MesClient.initWarehouseData();
             }

Файловите разлики са ограничени, защото са твърде много
+ 675 - 219
src/com/mes/ui/MesClient.java


+ 372 - 42
src/com/mes/ui/PlcUtil.java

@@ -6,6 +6,10 @@ import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
 import com.mes.util.DateLocalUtils;
 import com.mes.util.JdbcUtils;
 
+import javax.swing.*;
+import java.awt.*;
+import java.util.List;
+
 /**
  * OP040-OP060 焊接工位 PLC 读写工具(DB200 非优化块)
  */
@@ -122,17 +126,136 @@ public class PlcUtil {
      * 质量检查通过后:发送 MES 允许进站,进入等待启动状态
      */
     public static void onQualityPass(String side) {
+        onQualityPass(side, MesClient.getSideSn(side));
+    }
+
+    public static void onQualityPass(String side, String sn) {
+        if (sn != null && !sn.trim().isEmpty()) {
+            MesClient.setSideSn(side, sn);
+        }
+        final String statusText = "质量合格,已发送允许进站,等待启动";
         if ("A".equals(side)) {
             MesClient.tjFlaga = 1;
             MesClient.mesQualityFlagA = true;
-            MesClient.pxstatus.setText("A:质量合格,已发送允许进站,等待启动");
+            MesClient.hjparamsA.clear();
         } else {
             MesClient.tjFlagb = 1;
             MesClient.mesQualityFlagB = true;
-            MesClient.pxstatus.setText("B:质量合格,已发送允许进站,等待启动");
+            MesClient.hjparamsB.clear();
         }
         setMesAllowEntry(MesClient.s7PLC, true);
-        MesClient.setMenuStatus(MesClient.pxstatus.getText(), 0);
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                MesClient.setSideStatus(side, statusText);
+                MesClient.setMenuStatus(side + ":" + statusText, 0);
+            }
+        });
+    }
+
+    /**
+     * 免扫码工位:空闲时直接发送 MES 允许进站,不扫码、不质检
+     */
+    public static void syncAutoAllowStations(S7PLC s7PLC) {
+        try {
+            // 任一面已在启动中时,不抢写允许进站
+            if (Boolean.TRUE.equals(getAStart(s7PLC)) || Boolean.TRUE.equals(getBStart(s7PLC))) {
+                return;
+            }
+            if (MesClient.isAutoAllowSide("A") && MesClient.tjFlaga == 0) {
+                enterAutoAllowWait("A");
+            } else if (MesClient.isAutoAllowSide("A") && MesClient.tjFlaga == 1) {
+                // 等待启动期间保持允许进站(另一面加工结束后可再次下发)
+                setMesAllowEntry(s7PLC, true);
+            }
+            if (MesClient.isAutoAllowSide("B") && MesClient.tjFlagb == 0) {
+                enterAutoAllowWait("B");
+            } else if (MesClient.isAutoAllowSide("B") && MesClient.tjFlagb == 1) {
+                setMesAllowEntry(s7PLC, true);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 免扫码工位进入等待启动:写允许进站信号
+     */
+    private static void enterAutoAllowWait(String side) {
+        final String statusText = "免扫码工位,已允许进站,可直接启动";
+        if ("A".equals(side)) {
+            MesClient.tjFlaga = 1;
+            MesClient.mesQualityFlagA = true;
+            MesClient.hjparamsA.clear();
+            MesClient.workSnA = "";
+        } else {
+            MesClient.tjFlagb = 1;
+            MesClient.mesQualityFlagB = true;
+            MesClient.hjparamsB.clear();
+            MesClient.workSnB = "";
+        }
+        MesClient.work_status = 1;
+        setMesAllowEntry(MesClient.s7PLC, true);
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                MesClient.setSideSn(side, "免扫码");
+                MesClient.setSideStatus(side, statusText);
+                MesClient.setMenuStatus(side + ":" + statusText, 0);
+                if ("A".equals(side)) {
+                    if (MesClient.scan_bt_a != null) {
+                        MesClient.scan_bt_a.setEnabled(false);
+                    }
+                } else if (MesClient.scan_bt_b != null) {
+                    MesClient.scan_bt_b.setEnabled(false);
+                }
+            }
+        });
+    }
+
+    /**
+     * 免扫码工位加工完成:不采数、不提交,复位后由 syncAutoAllow 再次允许进站
+     */
+    private static void finishAutoAllowSide(String side) {
+        if ("A".equals(side)) {
+            MesClient.tjFlaga = 0;
+            MesClient.mesQualityFlagA = false;
+            MesClient.tjStatusa = 0;
+            MesClient.workSnA = "";
+            MesClient.hjparamsA.clear();
+        } else {
+            MesClient.tjFlagb = 0;
+            MesClient.mesQualityFlagB = false;
+            MesClient.tjStatusb = 0;
+            MesClient.workSnB = "";
+            MesClient.hjparamsB.clear();
+        }
+        if (MesClient.tjFlaga == 0 && MesClient.tjFlagb == 0) {
+            MesClient.work_status = 0;
+        }
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                MesClient.setSideSn(side, "免扫码");
+                MesClient.setSideStatus(side, "免扫码工位加工完成,等待下一件");
+                MesClient.setMenuStatus(side + ":免扫码工位加工完成,不提交数据", 0);
+                if ("A".equals(side)) {
+                    if (MesClient.finish_ok_bt_a != null) {
+                        MesClient.finish_ok_bt_a.setEnabled(false);
+                    }
+                    if (MesClient.finish_ng_bt_a != null) {
+                        MesClient.finish_ng_bt_a.setEnabled(false);
+                    }
+                } else {
+                    if (MesClient.finish_ok_bt_b != null) {
+                        MesClient.finish_ok_bt_b.setEnabled(false);
+                    }
+                    if (MesClient.finish_ng_bt_b != null) {
+                        MesClient.finish_ng_bt_b.setEnabled(false);
+                    }
+                }
+            }
+        });
     }
 
     /**
@@ -143,18 +266,37 @@ public class PlcUtil {
         if (Boolean.TRUE.equals(getAStart(s7PLC))) {
             setMesAllowEntry(s7PLC, false);
         }
+        boolean autoAllow = MesClient.isAutoAllowSide("A");
         if (MesClient.tjFlaga == 1) {
             if (Boolean.TRUE.equals(getAStart(s7PLC))) {
                 MesClient.curFlag = "A";
                 MesClient.tjFlaga = 2;
-                MesClient.pxstatus.setText("A:设备运行中,采集中...");
-                MesClient.hjparams.clear();
+                MesClient.hjparamsA.clear();
+                final String runText = autoAllow ? "免扫码工位运行中(不采集)" : "设备运行中,采集中...";
+                SwingUtilities.invokeLater(new Runnable() {
+                    @Override
+                    public void run() {
+                        MesClient.setSideStatus("A", runText);
+                    }
+                });
             }
         } else if (MesClient.tjFlaga == 2) {
             if (Boolean.TRUE.equals(getAFinish(s7PLC))) {
+                if (autoAllow) {
+                    // 免扫码:不采数、不提交
+                    finishAutoAllowSide("A");
+                    return;
+                }
+                // 完成前最后采一次(此时仍为运行态 tjFlaga=2)
+                collectWeldingParams(s7PLC);
                 MesClient.tjFlaga = 3;
-                MesClient.pxstatus.setText("A:焊接完成,提交结果中...");
-                submitResult("A", MesClient.product_sn.getText(), MesClient.mes_gw1);
+                SwingUtilities.invokeLater(new Runnable() {
+                    @Override
+                    public void run() {
+                        MesClient.setSideStatus("A", "焊接完成,提交结果中...");
+                    }
+                });
+                submitResult("A", MesClient.getSideSn("A"), MesClient.mes_gw1);
             }
         }
     }
@@ -167,27 +309,50 @@ public class PlcUtil {
         if (Boolean.TRUE.equals(getBStart(s7PLC))) {
             setMesAllowEntry(s7PLC, false);
         }
+        boolean autoAllow = MesClient.isAutoAllowSide("B");
         if (MesClient.tjFlagb == 1) {
             if (Boolean.TRUE.equals(getBStart(s7PLC))) {
                 MesClient.curFlag = "B";
                 MesClient.tjFlagb = 2;
-                MesClient.pxstatus.setText("B:设备运行中,采集中...");
-                MesClient.hjparams.clear();
+                MesClient.hjparamsB.clear();
+                final String runText = autoAllow ? "免扫码工位运行中(不采集)" : "设备运行中,采集中...";
+                SwingUtilities.invokeLater(new Runnable() {
+                    @Override
+                    public void run() {
+                        MesClient.setSideStatus("B", runText);
+                    }
+                });
             }
         } else if (MesClient.tjFlagb == 2) {
             if (Boolean.TRUE.equals(getBFinish(s7PLC))) {
+                if (autoAllow) {
+                    // 免扫码:不采数、不提交
+                    finishAutoAllowSide("B");
+                    return;
+                }
+                // 完成前最后采一次(此时仍为运行态 tjFlagb=2)
+                collectWeldingParams(s7PLC);
                 MesClient.tjFlagb = 3;
-                MesClient.pxstatus.setText("B:焊接完成,提交结果中...");
-                submitResult("B", MesClient.product_sn.getText(), MesClient.mes_gw2);
+                SwingUtilities.invokeLater(new Runnable() {
+                    @Override
+                    public void run() {
+                        MesClient.setSideStatus("B", "焊接完成,提交结果中...");
+                    }
+                });
+                submitResult("B", MesClient.getSideSn("B"), MesClient.mes_gw2);
             }
         }
     }
 
     /**
-     * 运行中每秒采集 R1/R2 电流、电压、送丝速度
+     * 运行中每秒采集 R1/R2 焊接参数(DB200)
+     * 只写入内存,不中途落库;加工结束整包一条上传
+     * 免扫码工位不采集
      */
     public static void collectWeldingParams(S7PLC s7PLC) {
-        if (MesClient.tjFlaga != 2 && MesClient.tjFlagb != 2) {
+        boolean collectA = MesClient.tjFlaga == 2 && !MesClient.isAutoAllowSide("A");
+        boolean collectB = MesClient.tjFlagb == 2 && !MesClient.isAutoAllowSide("B");
+        if (!collectA && !collectB) {
             return;
         }
 
@@ -195,22 +360,25 @@ public class PlcUtil {
             Float r1Current = s7PLC.readFloat32(DB + ".4");
             Float r1Voltage = s7PLC.readFloat32(DB + ".8");
             Float r1WireFeed = s7PLC.readFloat32(DB + ".12");
+            Float r1Speed = s7PLC.readFloat32(DB + ".16");
+            Short r1Bead = s7PLC.readInt16(DB + ".2");
+
             Float r2Current = s7PLC.readFloat32(DB + ".24");
             Float r2Voltage = s7PLC.readFloat32(DB + ".28");
             Float r2WireFeed = s7PLC.readFloat32(DB + ".32");
+            Float r2Speed = s7PLC.readFloat32(DB + ".36");
+            Short r2Bead = s7PLC.readInt16(DB + ".22");
 
             String recordTime = DateLocalUtils.getCurrentTime();
-            String paramLine = r1Current + "|" + r1Voltage + "|" + r1WireFeed + "|"
-                    + r2Current + "|" + r2Voltage + "|" + r2WireFeed + "|" + recordTime;
-            MesClient.hjparams.add(paramLine);
+            String paramLine = r1Current + "|" + r1Voltage + "|" + r1WireFeed + "|" + r1Speed + "|" + r1Bead + "|"
+                    + r2Current + "|" + r2Voltage + "|" + r2WireFeed + "|" + r2Speed + "|" + r2Bead + "|" + recordTime;
 
-            String oprno = "A".equals(MesClient.curFlag) ? MesClient.mes_gw1 : MesClient.mes_gw2;
-            String sn = MesClient.product_sn.getText();
-
-            // 每 60 条批量写入本地数据库
-            if (MesClient.hjparams.size() >= 60) {
-                JdbcUtils.insertCmtData(oprno, MesClient.mes_line_sn, sn, JSON.toJSONString(MesClient.hjparams));
-                MesClient.hjparams.clear();
+            // A/B 各自独立缓存,互不 clear,避免一面冲掉另一面
+            if (collectA) {
+                MesClient.hjparamsA.add(paramLine);
+            }
+            if (collectB) {
+                MesClient.hjparamsB.add(paramLine);
             }
         } catch (Exception e) {
             e.printStackTrace();
@@ -218,40 +386,202 @@ public class PlcUtil {
     }
 
     /**
-     * 焊接完成:刷写剩余参数并提交 OK 结果
+     * 刷新左右面 PLC 布尔点位显示(工位识别 / 启动中 / 焊接完成)
+     */
+    public static void refreshSidePointLabels(S7PLC s7PLC) {
+        try {
+            final boolean idA = Boolean.TRUE.equals(s7PLC.readBoolean(DB + ".0.2"));
+            final boolean startA = Boolean.TRUE.equals(s7PLC.readBoolean(DB + ".0.4"));
+            final boolean finishA = Boolean.TRUE.equals(s7PLC.readBoolean(DB + ".40.0"));
+            final boolean idB = Boolean.TRUE.equals(s7PLC.readBoolean(DB + ".0.3"));
+            final boolean startB = Boolean.TRUE.equals(s7PLC.readBoolean(DB + ".0.5"));
+            final boolean finishB = Boolean.TRUE.equals(s7PLC.readBoolean(DB + ".40.1"));
+
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    updateBoolLabel(MesClient.lblPointIdA, idA);
+                    updateBoolLabel(MesClient.lblPointStartA, startA);
+                    updateBoolLabel(MesClient.lblPointFinishA, finishA);
+                    updateBoolLabel(MesClient.lblPointIdB, idB);
+                    updateBoolLabel(MesClient.lblPointStartB, startB);
+                    updateBoolLabel(MesClient.lblPointFinishB, finishB);
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 刷新底部公共焊机参数(DB200 R1/R2 工艺参数)
+     */
+    public static void refreshWelderParamLabels(S7PLC s7PLC) {
+        try {
+            final Float r1Current = s7PLC.readFloat32(DB + ".4");
+            final Float r1Voltage = s7PLC.readFloat32(DB + ".8");
+            final Float r1WireFeed = s7PLC.readFloat32(DB + ".12");
+            final Short r1Bead = s7PLC.readInt16(DB + ".2");
+
+            final Float r2Current = s7PLC.readFloat32(DB + ".24");
+            final Float r2Voltage = s7PLC.readFloat32(DB + ".28");
+            final Float r2WireFeed = s7PLC.readFloat32(DB + ".32");
+            final Short r2Bead = s7PLC.readInt16(DB + ".22");
+
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    setParamText(MesClient.r1_voltage, r1Voltage);
+                    setParamText(MesClient.r1_current, r1Current);
+                    setParamText(MesClient.r1_wire, r1WireFeed);
+                    setParamText(MesClient.r1_bead, r1Bead);
+
+                    setParamText(MesClient.r2_voltage, r2Voltage);
+                    setParamText(MesClient.r2_current, r2Current);
+                    setParamText(MesClient.r2_wire, r2WireFeed);
+                    setParamText(MesClient.r2_bead, r2Bead);
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static void updateBoolLabel(JLabel label, boolean value) {
+        if (label == null) {
+            return;
+        }
+        if (value) {
+            label.setText("TRUE");
+            label.setForeground(new Color(0, 140, 0));
+            label.setBackground(new Color(200, 255, 200));
+        } else {
+            label.setText("FALSE");
+            label.setForeground(Color.GRAY);
+            label.setBackground(new Color(230, 230, 230));
+        }
+    }
+
+    private static void setParamText(JTextField field, Object value) {
+        if (field == null) {
+            return;
+        }
+        if (value == null) {
+            field.setText("0");
+            return;
+        }
+        if (value instanceof Float) {
+            field.setText(String.format("%.2f", (Float) value));
+        } else {
+            field.setText(String.valueOf(value));
+        }
+    }
+
+    /**
+     * 焊接完成:先提交质量结果,再整包上传焊接参数(两套接口分开)
+     * 1) sendQualityPc → MES 质量结果(pcresult)
+     * 2) flushHjParams → 本地 bw_cmt(一个工件一条,多条采数合并)
+     * 3) uploadPendingCmtParams → batchsave
      */
     private static void submitResult(String side, String sn, String oprno) {
-        flushHjParams(oprno, sn);
+        // 再次取内存工件码,避免界面异步导致为空
+        if (sn == null || sn.trim().isEmpty()) {
+            sn = MesClient.getSideSn(side);
+        }
+        if (sn == null || sn.trim().isEmpty()) {
+            MesClient.log.error("{}面提交结果失败:工件码为空 workSnA={} workSnB={}",
+                    side, MesClient.workSnA, MesClient.workSnB);
+            MesClient.setMenuStatus(side + "面工件码为空,无法提交结果,请手动OK/NG", -1);
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    MesClient.setSideStatus(side, "工件码为空,提交失败");
+                    enableManualButtons(side);
+                }
+            });
+            return;
+        }
+        final String finalSn = sn.trim();
+
+        // 刷新操作员
+        MesClient.getUser();
+        MesClient.log.info("{}面开始提交结果 sn={} oprno={} user={}",
+                side, finalSn, oprno, MesClient.user20);
 
-        JSONObject result = DataUtil.sendQualityPc(sn, "OK", MesClient.user20, oprno,
+        // 先提交结果(与参数分开);结果失败仍继续传参数
+        JSONObject result = DataUtil.sendQualityPc(finalSn, "OK", MesClient.user20, oprno,
                 MesClient.mes_line_sn, MesClient.mes_server_ip);
-        if (result != null && "true".equalsIgnoreCase(result.getString("result"))) {
-            MesClient.setMenuStatus(side + "面提交成功", 0);
-            MesClient.pxstatus.setText(side + ":提交成功");
-            MesClient.resetScanA();
+        boolean ok = DataUtil.isMesSuccess(result);
+
+        // 整包落库并上传参数(一个工件一条 content)
+        flushHjParams(side, oprno, finalSn);
+        MesClient.uploadPendingCmtParams();
+
+        if (ok) {
+            MesClient.setMenuStatus(side + "面结果提交成功", 0);
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    MesClient.setSideStatus(side, "提交成功");
+                }
+            });
+            MesClient.resetScan(side);
         } else {
-            MesClient.setMenuStatus(side + "面提交失败,可手动重试", -1);
-            MesClient.pxstatus.setText(side + ":提交失败");
-            MesClient.finish_ok_bt.setEnabled(true);
-            MesClient.finish_ng_bt.setEnabled(true);
-            if ("A".equals(side)) {
-                MesClient.tjStatusa = 1;
-            } else {
-                MesClient.tjStatusb = 1;
+            String msg = (result == null) ? "请求失败或返回空" : result.getString("message");
+            if (msg == null || msg.isEmpty()) {
+                msg = "提交失败";
             }
+            MesClient.log.error("{}面结果提交失败 sn={} oprno={} ret={}",
+                    side, finalSn, oprno, result == null ? "null" : result.toJSONString());
+            MesClient.setMenuStatus(side + "面结果" + msg + ",可手动重试", -1);
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    MesClient.setSideStatus(side, "结果提交失败,可手动OK/NG");
+                    enableManualButtons(side);
+                }
+            });
+        }
+    }
+
+    private static void enableManualButtons(String side) {
+        if ("A".equals(side)) {
+            if (MesClient.finish_ok_bt_a != null) {
+                MesClient.finish_ok_bt_a.setEnabled(true);
+            }
+            if (MesClient.finish_ng_bt_a != null) {
+                MesClient.finish_ng_bt_a.setEnabled(true);
+            }
+            MesClient.tjStatusa = 1;
+        } else {
+            if (MesClient.finish_ok_bt_b != null) {
+                MesClient.finish_ok_bt_b.setEnabled(true);
+            }
+            if (MesClient.finish_ng_bt_b != null) {
+                MesClient.finish_ng_bt_b.setEnabled(true);
+            }
+            MesClient.tjStatusb = 1;
         }
     }
 
     /**
-     * 刷写剩余焊接参数到本地库
+     * 刷写指定面焊接参数到本地库(整包一条,不中途分片)
      */
     public static void flushHjParams(String oprno, String sn) {
-        if (MesClient.hjparams.isEmpty()) {
+        String side = oprno != null && oprno.equals(MesClient.mes_gw2) ? "B" : "A";
+        flushHjParams(side, oprno, sn);
+    }
+
+    public static void flushHjParams(String side, String oprno, String sn) {
+        List<String> list = MesClient.getSideHjParams(side);
+        if (list == null || list.isEmpty()) {
             return;
         }
         try {
-            JdbcUtils.insertCmtData(oprno, MesClient.mes_line_sn, sn, JSON.toJSONString(MesClient.hjparams));
-            MesClient.hjparams.clear();
+            boolean ok = JdbcUtils.insertCmtData(oprno, MesClient.mes_line_sn, sn, JSON.toJSONString(list));
+            if (ok) {
+                list.clear();
+            }
         } catch (Exception e) {
             e.printStackTrace();
         }

+ 5 - 2
src/com/mes/util/Base64Utils.java

@@ -2,10 +2,13 @@ package com.mes.util;
 
 import org.apache.commons.codec.binary.Base64;
 
+import java.nio.charset.StandardCharsets;
+
 public class Base64Utils {
 	public static String getBase64(String str) {
-        byte[] binaryData = str.getBytes();
+        // 显式 UTF-8,避免打包 exe 后默认 GBK 导致中文用户名/密码 Base64 不一致
+        byte[] binaryData = str.getBytes(StandardCharsets.UTF_8);
         String encodedString = Base64.encodeBase64String(binaryData);
         return encodedString;
-	}
+    }
 }

+ 2 - 2
src/com/mes/util/HttpUtils.java

@@ -27,7 +27,7 @@ public class HttpUtils {
             //设置请求类型
             con.setRequestMethod(requestType);
             //设置请求需要返回的数据类型和字符集类型
-            con.setRequestProperty("Content-Type", "application/json;charset=GBK");  
+            con.setRequestProperty("Content-Type", "application/json;charset=UTF-8");  
             //允许写出
             con.setDoOutput(true);
             //允许读入
@@ -80,7 +80,7 @@ public class HttpUtils {
             //设置请求类型
             con.setRequestMethod(requestType);
             //设置请求需要返回的数据类型和字符集类型
-            con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=GBK");
+            con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
             //允许写出
             con.setDoOutput(true);
             //允许读入

+ 7 - 1
src/com/mes/util/JdbcUtils.java

@@ -148,13 +148,19 @@ public class JdbcUtils {
 			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
 				JdbcUtils.openConnection();
 			}
+			// 转义单引号,避免 JSON content 破坏 SQL
+			String safeOprno = oprno == null ? "" : oprno.replace("'", "''");
+			String safeLine = line_sn == null ? "" : line_sn.replace("'", "''");
+			String safeSn = sn == null ? "" : sn.replace("'", "''");
+			String safeContent = content == null ? "" : content.replace("'", "''");
 			Statement statement = conn.createStatement();
 			statement.executeUpdate("INSERT INTO bw_cmt (oprno,line_sn,sn,content,create_time) VALUES"
-					+ " ('" + oprno + "', '" + line_sn + "', '" + sn + "', '" + content + "','" + record_time + "')");
+					+ " ('" + safeOprno + "', '" + safeLine + "', '" + safeSn + "', '" + safeContent + "','" + record_time + "')");
 			statement.close();
 			ret = true;
 		} catch (SQLException e) {
 			ret = false;
+			e.printStackTrace();
 		}
 		return ret;
 	}

+ 5 - 1
src/resources/config/config.properties

@@ -6,4 +6,8 @@ mes.plc_ip=192.168.1.1
 mes.server_ip=192.168.9.180
 mes.line_sn=115XT
 # 设备状态定时上报间隔(分钟)
-mes.device.heartbeat.minutes=5
+mes.device.heartbeat.minutes=5
+# 免扫码自动允许进站工位(逗号分隔)。这些工位不扫码、不采数、不提交结果,直接写 MES 允许进站
+# 示例:mes.auto_allow_stations=OP040A  或多个:OP040A,OP070A
+# 留空则全部工位走正常扫码/采数/提交流程
+mes.auto_allow_stations=OP040A