hou 4 天之前
父节点
当前提交
b586c3f1d9

+ 170 - 0
src/com/mes/component/ProductTypePanel.java

@@ -0,0 +1,170 @@
+package com.mes.component;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.LineBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+
+public class ProductTypePanel extends JPanel {
+
+    public static final String TYPE_ZJ = "ZJ";
+    public static final String TYPE_SJ = "SJ";
+
+    private static final Color COLOR_SELECTED = new Color(24, 144, 255);
+    private static final Color COLOR_BG = new Color(245, 247, 250);
+    private static final Color COLOR_BORDER = new Color(217, 217, 217);
+    private static final Color COLOR_TEXT = new Color(89, 89, 89);
+
+    private final String switchPassword;
+    private String result = TYPE_ZJ;
+    private JButton zjButton;
+    private JButton sjButton;
+    private Runnable onChangeListener;
+    private boolean suppressPasswordCheck = false;
+
+    public ProductTypePanel(String switchPassword) {
+        this.switchPassword = switchPassword;
+        setLayout(new GridLayout(1, 2, 12, 0));
+        setOpaque(false);
+        setBorder(BorderFactory.createCompoundBorder(
+                new LineBorder(COLOR_BORDER, 1, true),
+                new EmptyBorder(8, 10, 8, 10)
+        ));
+        setBackground(COLOR_BG);
+
+        zjButton = createTypeButton("智界", TYPE_ZJ);
+        sjButton = createTypeButton("尚界", TYPE_SJ);
+        add(zjButton);
+        add(sjButton);
+        refreshButtonStyle();
+    }
+
+    private JButton createTypeButton(String title, String type) {
+        JButton button = new JButton(title) {
+            @Override
+            protected void paintComponent(Graphics g) {
+                Graphics2D g2 = (Graphics2D) g.create();
+                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+                if (getModel().isPressed()) {
+                    g2.setColor(COLOR_SELECTED.darker());
+                } else if (type.equals(result)) {
+                    g2.setColor(isEnabled() ? COLOR_SELECTED : COLOR_SELECTED.brighter());
+                } else {
+                    g2.setColor(isEnabled() ? Color.WHITE : new Color(250, 250, 250));
+                }
+                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 16, 16);
+                g2.dispose();
+                super.paintComponent(g);
+            }
+        };
+        button.setName(type);
+        button.setFont(new Font("微软雅黑", Font.BOLD, 26));
+        button.setFocusPainted(false);
+        button.setBorderPainted(false);
+        button.setContentAreaFilled(false);
+        button.setPreferredSize(new Dimension(180, 52));
+        button.addActionListener(e -> selectType(type, e));
+        return button;
+    }
+
+    private void selectType(String type, ActionEvent e) {
+        if (!isEnabled()) {
+            return;
+        }
+        if (type.equals(result)) {
+            return;
+        }
+        if (!suppressPasswordCheck && !verifySwitchPassword()) {
+            refreshButtonStyle();
+            return;
+        }
+        result = type;
+        refreshButtonStyle();
+        if (onChangeListener != null) {
+            onChangeListener.run();
+        }
+    }
+
+    private boolean verifySwitchPassword() {
+        JPasswordField passwordField = new JPasswordField(15);
+        JPanel panel = new JPanel(new BorderLayout(8, 8));
+        panel.add(new JLabel("请输入切换密码:"), BorderLayout.NORTH);
+        panel.add(passwordField, BorderLayout.CENTER);
+
+        int option = JOptionPane.showConfirmDialog(
+                SwingUtilities.getWindowAncestor(this),
+                panel,
+                "切换生产类型",
+                JOptionPane.OK_CANCEL_OPTION,
+                JOptionPane.PLAIN_MESSAGE
+        );
+        if (option != JOptionPane.OK_OPTION) {
+            return false;
+        }
+        if (!switchPassword.equals(new String(passwordField.getPassword()))) {
+            JOptionPane.showMessageDialog(
+                    SwingUtilities.getWindowAncestor(this),
+                    "密码错误,无法切换生产类型",
+                    "提示",
+                    JOptionPane.WARNING_MESSAGE
+            );
+            return false;
+        }
+        return true;
+    }
+
+    private void refreshButtonStyle() {
+        styleButton(zjButton, TYPE_ZJ.equals(result));
+        styleButton(sjButton, TYPE_SJ.equals(result));
+        repaint();
+    }
+
+    private void styleButton(JButton button, boolean selected) {
+        if (!button.isEnabled()) {
+            button.setForeground(selected ? Color.WHITE : new Color(191, 191, 191));
+            return;
+        }
+        if (selected) {
+            button.setForeground(Color.WHITE);
+        } else {
+            button.setForeground(COLOR_TEXT);
+        }
+        button.repaint();
+    }
+
+    public void setOnChangeListener(Runnable onChangeListener) {
+        this.onChangeListener = onChangeListener;
+    }
+
+    public void setResult(String type) {
+        suppressPasswordCheck = true;
+        try {
+            if (TYPE_SJ.equals(type)) {
+                result = TYPE_SJ;
+            } else {
+                result = TYPE_ZJ;
+            }
+            refreshButtonStyle();
+        } finally {
+            suppressPasswordCheck = false;
+        }
+    }
+
+    public String getResult() {
+        return result;
+    }
+
+    public String getTypeName() {
+        return TYPE_SJ.equals(result) ? "尚界" : "智界";
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        zjButton.setEnabled(enabled);
+        sjButton.setEnabled(enabled);
+        refreshButtonStyle();
+        repaint();
+    }
+}

+ 41 - 20
src/com/mes/ui/DataUtil.java

@@ -57,29 +57,36 @@ public class DataUtil {
             String title = testData.name == null ? "" : testData.name;
             String cxm = testData.name == null ? "" : testData.name;
             String result = testData.result == null ? "NG" : testData.result;
-            String testPressure = testData.testPressure == null ? "" : testData.testPressure;
-            String leakVal = testData.leakVal == null ? "" : testData.leakVal;
+            String testPressure = testData.testPressure == null ? "" : testData.testPressure.trim();
+            String leakVal = testData.leakVal == null ? "" : testData.leakVal.trim();
             String remark = QmPlcPoint.buildRemark(testData);
 
+            String testPressureBase64 = Base64Utils.getBase64(testPressure);
+            String leakValBase64 = Base64Utils.getBase64(leakVal);
+            String testTimeBase64 = Base64Utils.getBase64(testTime);
+            String titleBase64 = Base64Utils.getBase64(title);
+            log.info("qmresult upload leakVal='{}' base64='{}', testPressure='{}' base64='{}'",
+                    leakVal, leakValBase64, testPressure, testPressureBase64);
+
             StringBuilder params = new StringBuilder();
             params.append("__ajax=json");
-            params.append("&oprno=").append(oprno);
-            params.append("&lineSn=").append(lineSn);
-            params.append("&sn=").append(sn);
-            params.append("&workNum=").append(user);
-            params.append("&result=").append(result);
-            params.append("&cxm=").append(URLEncoder.encode(cxm, StandardCharsets.UTF_8.name()));
-            params.append("&cq=").append(testData.pressurizingTime == null ? "" : testData.pressurizingTime);
-            params.append("&by=").append(testData.equalizingTime == null ? "" : testData.equalizingTime);
-            params.append("&cs=").append(testData.testingTime == null ? "" : testData.testingTime);
-            params.append("&testPressureUnit=").append(testData.testPressureUnit == null ? "" : testData.testPressureUnit);
-            params.append("&leakValUnit=").append(testData.leakValUnit == null ? "" : testData.leakValUnit);
-            params.append("&testPressure=").append(Base64Utils.getBase64(testPressure));
-            params.append("&leakVal=").append(Base64Utils.getBase64(leakVal));
-            params.append("&testTime=").append(Base64Utils.getBase64(testTime));
-            params.append("&title=").append(Base64Utils.getBase64(title));
-            params.append("&remark=").append(URLEncoder.encode(remark, StandardCharsets.UTF_8.name()));
-            params.append("&deviceType=S7-200SMART");
+            appendUrlParam(params, "oprno", oprno);
+            appendUrlParam(params, "lineSn", lineSn);
+            appendUrlParam(params, "sn", sn);
+            appendUrlParam(params, "workNum", user);
+            appendUrlParam(params, "result", result);
+            appendUrlParam(params, "cxm", cxm);
+            appendUrlParam(params, "cq", testData.pressurizingTime);
+            appendUrlParam(params, "by", testData.equalizingTime);
+            appendUrlParam(params, "cs", testData.testingTime);
+            appendUrlParam(params, "testPressure", testPressureBase64);
+            appendUrlParam(params, "testPressureUnit", testData.testPressureUnit);
+            appendUrlParam(params, "leakVal", leakValBase64);
+            appendUrlParam(params, "leakValUnit", testData.leakValUnit);
+            appendUrlParam(params, "testTime", testTimeBase64);
+            appendUrlParam(params, "title", titleBase64);
+            appendUrlParam(params, "remark", remark);
+            appendUrlParam(params, "deviceType", "S7-200SMART");
 
             log.info("qmresult params="+params);
             String response = doPost(url, params.toString());
@@ -96,6 +103,20 @@ public class DataUtil {
         }
     }
 
+    private static void appendUrlParam(StringBuilder params, String name, String value) {
+        try {
+            params.append("&").append(name).append("=");
+            if (value != null && !value.isEmpty()) {
+                params.append(URLEncoder.encode(value, StandardCharsets.UTF_8.name()));
+            }
+        } catch (UnsupportedEncodingException e) {
+            params.append("&").append(name).append("=");
+            if (value != null) {
+                params.append(value);
+            }
+        }
+    }
+
     public static JSONObject bindWarehouse(String sn,String materielSn,String user,String craft ){
         String ret = "OK";
         try{
@@ -211,7 +232,7 @@ public class DataUtil {
             connection.setReadTimeout(60000000);
             connection.setDoOutput(true);
             connection.setDoInput(true);
-            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            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(StandardCharsets.UTF_8));

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

@@ -198,9 +198,7 @@ public class LoginFarme extends JFrame {
 					if (MesClient.jfxPanel2 == null) {
 						String url = "http://" + MesClient.mes_server_ip + ":8980/js/a/mes/mesProcessCheckRecord/ulist?ucode=" + user_id + "&oprno=" + MesClient.mes_gw + "&lineSn=" + MesClient.mes_line_sn;
 						MesClient.jfxPanel2 = new MesWebView(url);
-						MesClient.jfxPanel2.setSize(990, 550);
-						MesClient.jfxPanel2.setBounds(0, 0, 990, 550);
-						MesClient.indexPanelC.add(MesClient.jfxPanel2);
+						MesClient.indexPanelC.add(MesClient.jfxPanel2, BorderLayout.CENTER);
 					}
 
                     MesClient.getMaterailData();

+ 33 - 6
src/com/mes/ui/MesClient.java

@@ -85,6 +85,7 @@ public class MesClient extends JFrame {
     public static final int PRODUCT_SN_LENGTH = 32;
     public static final String PREFIX_ZJ = "000020021815-01000016";  //智界
     public static final String PREFIX_SJ = "000020022029-01000016";  //尚界
+    public static final boolean PRODUCT_TYPE_SWITCH_ENABLED = false;
     public static final String PRODUCT_TYPE_SWITCH_PASSWORD = "503833";
     public static final String PRODUCT_TYPE_ZJ = ProductTypePanel.TYPE_ZJ;
     public static final String PRODUCT_TYPE_SJ = ProductTypePanel.TYPE_SJ;
@@ -425,13 +426,19 @@ public class MesClient extends JFrame {
         lastDataReady = false;
         PlcService.setMesConnected(false);
         PlcService.setCollectComplete(false);
+        PlcService.clearScanCode();
         MesClient.finish_ok_bt.setEnabled(false);
         MesClient.finish_ng_bt.setEnabled(false);
         product_sn.setText("");
         MesClient.fxlabel.setVisible(false);
 
         MesClient.f_scan_data_bt_1.setEnabled(true);
-        MesClient.setMenuStatus("请扫工件码",0);
+        updateProductTypePanelEnabled(true);
+        if (PRODUCT_TYPE_SWITCH_ENABLED) {
+            MesClient.setMenuStatus("当前生产类型:" + getProductTypeName() + ",请扫工件码",0);
+        } else {
+            MesClient.setMenuStatus("请扫工件码",0);
+        }
 
         updateMaterailData();
     }
@@ -654,12 +661,14 @@ public class MesClient extends JFrame {
             updateProductTypePanelEnabled(false);
 
             if (!PlcService.ensureConnected()) {
+                updateProductTypePanelEnabled(true);
                 MesClient.setMenuStatus("PLC未连接,请检查网络", -1);
                 updatePlcStatus(false);
                 return;
             }
             updatePlcStatus(true);
             if (!PlcService.setCollectComplete(false)) {
+                updateProductTypePanelEnabled(true);
                 MesClient.setMenuStatus("写入V2800.3=0失败", -1);
                 return;
             }
@@ -670,13 +679,22 @@ public class MesClient extends JFrame {
                 MesClient.check_quality_result = false;
                 MesClient.work_status = 0;
                 PlcService.setMesConnected(false);
+                updateProductTypePanelEnabled(true);
                 MesClient.setMenuStatus("请求失败,请重试",-1);
                 return;
             }
             if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
+                if (!PlcService.writeScanCode(scanBarcode)) {
+                    MesClient.check_quality_result = false;
+                    MesClient.work_status = 0;
+                    updateProductTypePanelEnabled(true);
+                    MesClient.setMenuStatus("写入VB3500二维码失败", -1);
+                    return;
+                }
                 if (!PlcService.setMesConnected(true)) {
                     MesClient.check_quality_result = false;
                     MesClient.work_status = 0;
+                    updateProductTypePanelEnabled(true);
                     MesClient.setMenuStatus("写入V2800.7=1失败", -1);
                     return;
                 }
@@ -693,6 +711,7 @@ public class MesClient extends JFrame {
                 MesClient.check_quality_result = false;
                 MesClient.work_status = 0;
                 PlcService.setMesConnected(false);
+                updateProductTypePanelEnabled(true);
                 if(retObj.get("result")==null){
                     MesClient.setMenuStatus("请求失败,请重试",-1);
                 }else{
@@ -866,6 +885,7 @@ public class MesClient extends JFrame {
         toolBar.add(space_4);
 
         tabbedPane = new JTabbedPane(JTabbedPane.TOP);
+        tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
         tabbedPane.setMinimumSize(new Dimension(400, 50));
         tabbedPane.setFont(new Font("宋体", Font.BOLD, 22));
         contentPane.add(tabbedPane);
@@ -1009,10 +1029,9 @@ public class MesClient extends JFrame {
 
 //		searchScrollPane = new JScrollPane((Component) null);
 
-        indexPanelC = new JPanel();
-        searchScrollPaneDj = new JScrollPane(indexPanelC);
-        indexPanelC.setLayout(null);
+        indexPanelC = new JPanel(new BorderLayout());
 
+        if (PRODUCT_TYPE_SWITCH_ENABLED) {
         //新增的面板
         JPanel indexPanelProductType = new JPanel();
         indexPanelProductType.setLayout(null);
@@ -1037,7 +1056,11 @@ public class MesClient extends JFrame {
         productTypePanel.setOnChangeListener(() -> {
             if (work_status == 0) {
                 saveProductTypeToDb();
-                setMenuStatus("当前生产类型:" + getProductTypeName() + ",请扫工件码", 0);
+                if (PRODUCT_TYPE_SWITCH_ENABLED) {
+            setMenuStatus("当前生产类型:" + getProductTypeName() + ",请扫工件码",0);
+        } else {
+            setMenuStatus("请扫工件码",0);
+        }
             }
             refreshProductTypeCurrentLabel();
         });
@@ -1052,9 +1075,10 @@ public class MesClient extends JFrame {
         indexPanelProductType.add(productTypeCurrentLabel);
 
         tabbedPane.addTab("生产类型", new ImageIcon(MesClient.class.getResource("/bg/menu_setting.png")), productTypeScrollPane, null);
+        }
 
 
-        tabbedPane.addTab("开班点检", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), searchScrollPaneDj, null);
+        tabbedPane.addTab("开班点检", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), indexPanelC, null);
 
 
         // ========== 工作记录 Tab(改造:当前工位Tab + 所有工位Tab) ==========
@@ -1135,6 +1159,9 @@ public class MesClient extends JFrame {
         if (sn.length() != PRODUCT_SN_LENGTH) {
             return "工件码长度须为32位,当前" + sn.length() + "位";
         }
+        if (!PRODUCT_TYPE_SWITCH_ENABLED) {
+            return null;
+        }
         String prefix = getProductSnPrefix();
         if (!sn.startsWith(prefix)) {
             return "工件码前缀与当前类型(" + getProductTypeName() + ")不匹配,当前工件码为" + getProductTypeNameBySn(sn);

+ 2 - 0
src/com/mes/util/ErrorMsg.java

@@ -36,6 +36,8 @@ public class ErrorMsg {
                 lmsg = "配件寿命不足";
             }else if(processMsgRet.equalsIgnoreCase("QM")) {
                 lmsg = "两次气密必须间隔15分钟";
+            }else if(processMsgRet.equalsIgnoreCase("MT")) {
+                lmsg = "气密已达最大NG次数";
             }else if(processMsgRet.equalsIgnoreCase("GS")) {
                 lmsg = "工件码格式不正确";
             }else if(processMsgRet.equalsIgnoreCase("CF")) {

+ 54 - 0
src/com/mes/util/PlcService.java

@@ -5,6 +5,8 @@ import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.nio.charset.StandardCharsets;
+
 /**
  * 西门子 S7-200 SMART PLC 通信服务。
  * <p>
@@ -139,9 +141,61 @@ public class PlcService {
 
     public static String readMesData() {
         byte[] data = readMesDataBlock();
+        if (data == null) {
+            log.warn("读取VB3000失败: 返回null");
+            return "";
+        }
+        log.info("读取VB3000完成, byteLength={}", data.length);
         return QmPlcPoint.readPlcString(data);
     }
 
+    /**
+     * 质量校验通过后,将扫码内容写入 VB3500(纯 ASCII 原始字符串,无长度字节前缀)。
+     */
+    public static boolean writeScanCode(String barcode) {
+        if (!ensureConnected()) {
+            return false;
+        }
+        if (barcode == null || barcode.trim().isEmpty()) {
+            log.info("二维码为空,无法写入VB3500");
+            return false;
+        }
+        try {
+            String code = barcode.trim();
+            byte[] codeBytes = code.getBytes(StandardCharsets.US_ASCII);
+            byte[] buffer = new byte[QmPlcPoint.SCAN_CODE_LENGTH];
+            int copyLen = Math.min(codeBytes.length, buffer.length);
+            if (codeBytes.length > buffer.length) {
+                log.info("二维码长度超过VB3500容量({}字节),已截断", QmPlcPoint.SCAN_CODE_LENGTH);
+            }
+            System.arraycopy(codeBytes, 0, buffer, 0, copyLen);
+            s7PLC.writeByte("V" + QmPlcPoint.SCAN_CODE_OFFSET, buffer);
+            log.info("S7-200 SMART 写入 VB3500 二维码(ASCII): {}", code);
+            return true;
+        } catch (Exception e) {
+            connected = false;
+            log.info("写入VB3500失败: {}", e.getMessage());
+            return false;
+        }
+    }
+
+    /** 复位 VB3500 扫码区 */
+    public static boolean clearScanCode() {
+        if (!ensureConnected()) {
+            return false;
+        }
+        try {
+            byte[] empty = new byte[QmPlcPoint.SCAN_CODE_LENGTH];
+            s7PLC.writeByte("V" + QmPlcPoint.SCAN_CODE_OFFSET, empty);
+            log.info("S7-200 SMART 已清空 VB3500");
+            return true;
+        } catch (Exception e) {
+            connected = false;
+            log.info("清空VB3500失败: {}", e.getMessage());
+            return false;
+        }
+    }
+
     private static boolean writeVBit(int byteOffset, int bitOffset, boolean value) {
         if (!ensureConnected()) {
             return false;

+ 32 - 20
src/com/mes/util/QmPlcPoint.java

@@ -26,6 +26,10 @@ public class QmPlcPoint {
     public static final int DATA_OFFSET = 3000;
     public static final int DATA_LENGTH = 400;
 
+    /** VB3500 扫码二维码下发区(纯 ASCII,64 字节) */
+    public static final int SCAN_CODE_OFFSET = 3500;
+    public static final int SCAN_CODE_LENGTH = 64;
+
     /** V2800.0 扫码匹配完成(只读,启用扫码功能时有效) */
     public static final int QR_MATCH_BIT = 0;
     /** V2800.3 上位机采集完成,写1后持续1~3s脉冲;写1时V2800.5自动复位为0 */
@@ -83,6 +87,8 @@ public class QmPlcPoint {
         }
 
         String decoded = decodePlcBytes(data, len);
+        log.info("VB3000原始字节 length={}, hex={}", len, toHex(data, len));
+        log.info("VB3000解码字符串: {}", decoded);
         if (decoded.indexOf('\uFFFD') >= 0) {
             log.warn("VB3000解码仍含乱码, hex={}", toHex(data, len));
         }
@@ -142,6 +148,7 @@ public class QmPlcPoint {
         QmTestData testData = new QmTestData();
         testData.rawData = raw == null ? "" : raw.trim();
         if (testData.rawData.isEmpty()) {
+            log.warn("========== VB3000 原始数据为空,无法解析 ==========");
             return testData;
         }
 
@@ -169,26 +176,31 @@ public class QmPlcPoint {
     }
 
     private static void logParsedMesData(QmTestData testData, String[] parts, int nextIndex) {
-        log.info("========== VB3000 原始数据 ==========");
-        log.info("rawData: {}", testData.rawData);
-        log.info("splitCount: {}, usedParts: {}", parts.length, nextIndex);
-        log.info("---------- 分割后各字段 ----------");
-        log.info("date(日期): {}", testData.date);
-        log.info("time(时间): {}", testData.time);
-        log.info("name(名称): {}", testData.name);
-        log.info("trackingCode(追踪码): {}", testData.trackingCode);
-        log.info("leakVal(泄漏值): {}", testData.leakVal);
-        log.info("leakValUnit(泄漏单位): {}", testData.leakValUnit);
-        log.info("pressurizingTime(充气时间): {}", testData.pressurizingTime);
-        log.info("equalizingTime(平衡时间): {}", testData.equalizingTime);
-        log.info("testingTime(测试时间): {}", testData.testingTime);
-        log.info("testPressure(测试压力): {}", testData.testPressure);
-        log.info("testPressureUnit(压力单位): {}", testData.testPressureUnit);
-        log.info("productionSerial(生产序号): {}", testData.productionSerial);
-        log.info("ambientTemperature(环温): {}", testData.ambientTemperature);
-        log.info("airSourceTemperature(气源温度): {}", testData.airSourceTemperature);
-        log.info("result(结果评价): {}", testData.result);
-        log.info("====================================");
+        StringBuilder sb = new StringBuilder(512);
+        sb.append('\n')
+                .append("========== VB3000 原始数据 ==========\n")
+                .append("rawData: ").append(testData.rawData).append('\n')
+                .append("splitCount: ").append(parts.length)
+                .append(", usedParts: ").append(nextIndex).append('\n')
+                .append("---------- 分割后各字段 ----------\n")
+                .append("date(日期): ").append(testData.date).append('\n')
+                .append("time(时间): ").append(testData.time).append('\n')
+                .append("name(名称): ").append(testData.name).append('\n')
+                .append("trackingCode(追踪码): ").append(testData.trackingCode).append('\n')
+                .append("leakVal(泄漏值): ").append(testData.leakVal).append('\n')
+                .append("leakValUnit(泄漏单位): ").append(testData.leakValUnit).append('\n')
+                .append("pressurizingTime(充气时间): ").append(testData.pressurizingTime).append('\n')
+                .append("equalizingTime(平衡时间): ").append(testData.equalizingTime).append('\n')
+                .append("testingTime(测试时间): ").append(testData.testingTime).append('\n')
+                .append("testPressure(测试压力): ").append(testData.testPressure).append('\n')
+                .append("testPressureUnit(压力单位): ").append(testData.testPressureUnit).append('\n')
+                .append("productionSerial(生产序号): ").append(testData.productionSerial).append('\n')
+                .append("ambientTemperature(环温): ").append(testData.ambientTemperature).append('\n')
+                .append("airSourceTemperature(气源温度): ").append(testData.airSourceTemperature).append('\n')
+                .append("result(结果评价): ").append(testData.result).append('\n')
+                .append("====================================");
+        // 控制台 + D:/Logs/log/log.log + D:/Logs/log/qm-plc.log
+        log.info("{}", sb.toString());
     }
 
     public static String formatTestTime(String dateStr, String timeStr) {

+ 24 - 1
src/resources/logback.xml

@@ -3,7 +3,7 @@
 <configuration>
 
     <!-- 定义日志文件的存储位置及文件名 -->
-    <property name="LOG_HOME" value="C:/Logs"/>
+    <property name="LOG_HOME" value="D:/Logs"/>
     <property name="PROJECT_NAME" value="log"/>
 
     <!-- 控制台输出 -->
@@ -28,6 +28,21 @@
         </rollingPolicy>
         <encoder>
             <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level  %logger{36} - %msg%n</pattern>
+            <immediateFlush>true</immediateFlush>
+        </encoder>
+    </appender>
+
+    <!-- 气密 VB3000 原始/解析专用日志 -->
+    <appender name="QM_PLC_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_HOME}/${PROJECT_NAME}/qm-plc.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${PROJECT_NAME}/archived/qm-plc_%d{yyyy-MM-dd}.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>500MB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level  %logger{36} - %msg%n</pattern>
+            <immediateFlush>true</immediateFlush>
         </encoder>
     </appender>
 
@@ -37,6 +52,14 @@
         <appender-ref ref="FILE" />
     </root>
 
+    <!-- 气密解析日志同时写入 qm-plc.log -->
+    <logger name="com.mes.util.QmPlcPoint" level="INFO" additivity="true">
+        <appender-ref ref="QM_PLC_FILE" />
+    </logger>
+    <logger name="com.mes.util.PlcService" level="INFO" additivity="true">
+        <appender-ref ref="QM_PLC_FILE" />
+    </logger>
+
     <logger name="org.slf4j" level="ERROR"/>
     <logger name="ch.qos.logback" level="ERROR"/>
     <logger name="ch.qos.logback.classic.joran.action.ConfigurationAction" level="OFF"/>