hou пре 1 недеља
родитељ
комит
f23507d5b8

+ 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();
+    }
+}

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

@@ -8,6 +8,7 @@ import com.github.xingshuangs.iot.protocol.modbus.service.ModbusTcp;
 import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
 import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
 import com.mes.component.MesRadio;
+import com.mes.component.ProductTypePanel;
 import com.mes.component.MesWebView;
 import com.mes.component.MyDialog;
 import com.mes.netty.NettyClient;
@@ -162,6 +163,16 @@ public class MesClient extends JFrame {
     public static JPanel indexPanelB;
     public static MesWebView jfxPanel = null;
     public static MesRadio mesRadioHj;
+    public static ProductTypePanel productTypePanel;
+    public static JLabel productTypeCurrentLabel;
+
+    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;
     public static JPanel indexPanelC;
     public static MesWebView jfxPanel2 = null;
     public static JPanel indexPanelDjB;
@@ -357,10 +368,12 @@ public class MesClient extends JFrame {
     public static JTextField param1;
     public static JTextField param2;
     public static JTextField param3;
+    public static JTextField param4;
     public static JTextField product_sn2;
     public static JTextField param21;
     public static JTextField param22;
     public static JTextField param23;
+    public static JTextField param24;
     public static void startHeartBeatTimer() {
         if(heartBeatTimer!=null) {
             heartBeatTimer.cancel();
@@ -435,6 +448,7 @@ public class MesClient extends JFrame {
     }
 
     public static void initWarehouseData(){
+        loadProductTypeFromDb();
         resetScanA();
         resetScanB();
 
@@ -451,10 +465,12 @@ public class MesClient extends JFrame {
         MesClient.param1.setText("");
         MesClient.param2.setText("");
         MesClient.param3.setText("");
+        MesClient.param4.setText("");
 
         MesClient.param21.setText("");
         MesClient.param22.setText("");
         MesClient.param23.setText("");
+        MesClient.param24.setText("");
 
         MesClient.curSna = "";
         MesClient.tjFlaga = 0;
@@ -469,6 +485,7 @@ public class MesClient extends JFrame {
         updateMaterailData(); // 更新物料
 
         shiftUserCheck();
+        refreshProductTypeLock();
     }
 
     public static int userLoginHours;//用户登录所处小时
@@ -505,10 +522,12 @@ public class MesClient extends JFrame {
         MesClient.param1.setText("");
         MesClient.param2.setText("");
         MesClient.param3.setText("");
+        MesClient.param4.setText("");
 
         MesClient.param21.setText("");
         MesClient.param22.setText("");
         MesClient.param23.setText("");
+        MesClient.param24.setText("");
         MesClient.curSnb = "";
         MesClient.tjFlagb = 0;
         MesClient.checkB = 0;
@@ -522,6 +541,7 @@ public class MesClient extends JFrame {
         updateMaterailData(); // 更新物料
 
 //        shiftUserCheck();
+        refreshProductTypeLock();
     }
 
     //閼惧嘲褰囬悽銊﹀煕20娴o拷
@@ -823,8 +843,10 @@ public class MesClient extends JFrame {
 
         //妫f牠銆�
         JPanel indexPanelA = new JPanel();
-        indexScrollPaneA = new JScrollPane(indexPanelA);
         indexPanelA.setLayout(null);
+        indexScrollPaneA = new JScrollPane(indexPanelA);
+        indexScrollPaneA.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        indexScrollPaneA.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
 
         product_sn = new JTextField();
         product_sn.setText("");
@@ -1026,101 +1048,182 @@ public class MesClient extends JFrame {
         scan_type_text.setBounds(752, 10, 132, 60);
         indexPanelA.add(scan_type_text);
 
-        // 焊机1参数 - 纵向排列(标签在上,数值在下)
+        // 焊机参数:第一行 电压/电流/送丝速度,第二行 程序名(整行)
+        int welder1X = 26;
+        int welder1W = 446;
+        int welder2X = 517;
+        int welder2W = 446;
+        int metricColW = 140;
+        int metricLabelY = 328;
+        int metricFieldY = 348;
+        int metricFieldH = 28;
+        int progLabelY = 386;
+        int progFieldY = 406;
+        int progFieldH = 28;
+
         JLabel lblDyA = new JLabel("电压");
         lblDyA.setHorizontalAlignment(SwingConstants.CENTER);
-        lblDyA.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        lblDyA.setBounds(169, 325, 158, 24);
+        lblDyA.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        lblDyA.setBounds(welder1X, metricLabelY, metricColW, 20);
         indexPanelA.add(lblDyA);
 
         param1 = new JTextField();
         param1.setEnabled(false);
         param1.setEditable(false);
         param1.setHorizontalAlignment(SwingConstants.CENTER);
-        param1.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        param1.setBounds(169, 350, 158, 32);
-        param1.setColumns(10);
+        param1.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        param1.setBounds(welder1X, metricFieldY, metricColW, metricFieldH);
         indexPanelA.add(param1);
 
         JLabel lblDlA = new JLabel("电流");
         lblDlA.setHorizontalAlignment(SwingConstants.CENTER);
-        lblDlA.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        lblDlA.setBounds(169, 393, 158, 24);
+        lblDlA.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        lblDlA.setBounds(welder1X + metricColW + 8, metricLabelY, metricColW, 20);
         indexPanelA.add(lblDlA);
 
         param2 = new JTextField();
         param2.setEnabled(false);
         param2.setEditable(false);
         param2.setHorizontalAlignment(SwingConstants.CENTER);
-        param2.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        param2.setBounds(169, 418, 158, 32);
-        param2.setColumns(10);
+        param2.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        param2.setBounds(welder1X + metricColW + 8, metricFieldY, metricColW, metricFieldH);
         indexPanelA.add(param2);
 
         JLabel lblSsA = new JLabel("送丝速度");
         lblSsA.setHorizontalAlignment(SwingConstants.CENTER);
-        lblSsA.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        lblSsA.setBounds(169, 461, 158, 24);
+        lblSsA.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        lblSsA.setBounds(welder1X + (metricColW + 8) * 2, metricLabelY, metricColW, 20);
         indexPanelA.add(lblSsA);
 
         param3 = new JTextField();
         param3.setEnabled(false);
         param3.setEditable(false);
         param3.setHorizontalAlignment(SwingConstants.CENTER);
-        param3.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        param3.setBounds(169, 486, 158, 32);
-        param3.setColumns(10);
+        param3.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        param3.setBounds(welder1X + (metricColW + 8) * 2, metricFieldY, metricColW, metricFieldH);
         indexPanelA.add(param3);
 
-        // 焊机2参数 - 纵向排列(标签在上,数值在下)
+        JLabel lblProgA = new JLabel("程序名");
+        lblProgA.setHorizontalAlignment(SwingConstants.CENTER);
+        lblProgA.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        lblProgA.setBounds(welder1X, progLabelY, welder1W, 20);
+        indexPanelA.add(lblProgA);
+
+        param4 = new JTextField();
+        param4.setEnabled(false);
+        param4.setEditable(false);
+        param4.setHorizontalAlignment(SwingConstants.CENTER);
+        param4.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        param4.setBounds(welder1X, progFieldY, welder1W, progFieldH);
+        indexPanelA.add(param4);
+
         JLabel lblDyB = new JLabel("电压");
         lblDyB.setHorizontalAlignment(SwingConstants.CENTER);
-        lblDyB.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        lblDyB.setBounds(669, 325, 158, 24);
+        lblDyB.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        lblDyB.setBounds(welder2X, metricLabelY, metricColW, 20);
         indexPanelA.add(lblDyB);
 
         param21 = new JTextField();
         param21.setEnabled(false);
         param21.setEditable(false);
         param21.setHorizontalAlignment(SwingConstants.CENTER);
-        param21.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        param21.setBounds(669, 350, 158, 32);
-        param21.setColumns(10);
+        param21.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        param21.setBounds(welder2X, metricFieldY, metricColW, metricFieldH);
         indexPanelA.add(param21);
 
         JLabel lblDlB = new JLabel("电流");
         lblDlB.setHorizontalAlignment(SwingConstants.CENTER);
-        lblDlB.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        lblDlB.setBounds(669, 393, 158, 24);
+        lblDlB.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        lblDlB.setBounds(welder2X + metricColW + 8, metricLabelY, metricColW, 20);
         indexPanelA.add(lblDlB);
 
         param22 = new JTextField();
         param22.setEnabled(false);
         param22.setEditable(false);
         param22.setHorizontalAlignment(SwingConstants.CENTER);
-        param22.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        param22.setBounds(669, 418, 158, 32);
-        param22.setColumns(10);
+        param22.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        param22.setBounds(welder2X + metricColW + 8, metricFieldY, metricColW, metricFieldH);
         indexPanelA.add(param22);
 
         JLabel lblSsB = new JLabel("送丝速度");
         lblSsB.setHorizontalAlignment(SwingConstants.CENTER);
-        lblSsB.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        lblSsB.setBounds(669, 461, 158, 24);
+        lblSsB.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        lblSsB.setBounds(welder2X + (metricColW + 8) * 2, metricLabelY, metricColW, 20);
         indexPanelA.add(lblSsB);
 
         param23 = new JTextField();
         param23.setEnabled(false);
         param23.setEditable(false);
         param23.setHorizontalAlignment(SwingConstants.CENTER);
-        param23.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 18));
-        param23.setBounds(669, 486, 158, 32);
-        param23.setColumns(10);
+        param23.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        param23.setBounds(welder2X + (metricColW + 8) * 2, metricFieldY, metricColW, metricFieldH);
         indexPanelA.add(param23);
 
+        JLabel lblProgB = new JLabel("程序名");
+        lblProgB.setHorizontalAlignment(SwingConstants.CENTER);
+        lblProgB.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        lblProgB.setBounds(welder2X, progLabelY, welder2W, 20);
+        indexPanelA.add(lblProgB);
+
+        param24 = new JTextField();
+        param24.setEnabled(false);
+        param24.setEditable(false);
+        param24.setHorizontalAlignment(SwingConstants.CENTER);
+        param24.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 16));
+        param24.setBounds(welder2X, progFieldY, welder2W, progFieldH);
+        indexPanelA.add(param24);
+
+        indexPanelA.setPreferredSize(new Dimension(993, 450));
+        indexPanelA.revalidate();
+
         tabbedPane.addTab("工作面板", new ImageIcon(MesClient.class.getResource("/bg/a_side.png")), indexScrollPaneA, null);
         tabbedPane.setEnabledAt(0, true);
 
+        if (PRODUCT_TYPE_SWITCH_ENABLED) {
+        JPanel indexPanelProductType = new JPanel();
+        indexPanelProductType.setLayout(null);
+        JScrollPane productTypeScrollPane = new JScrollPane(indexPanelProductType);
+
+        JLabel productTypeTitle = new JLabel("生产类型设置");
+        productTypeTitle.setFont(new Font("微软雅黑", Font.BOLD, 28));
+        productTypeTitle.setForeground(new Color(64, 64, 64));
+        productTypeTitle.setHorizontalAlignment(SwingConstants.CENTER);
+        productTypeTitle.setBounds(81, 80, 810, 50);
+        indexPanelProductType.add(productTypeTitle);
+
+        JLabel productTypeHint = new JLabel("请选择当前生产的工件类型,切换后将保存到本地并用于扫码校验");
+        productTypeHint.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        productTypeHint.setForeground(new Color(140, 140, 140));
+        productTypeHint.setHorizontalAlignment(SwingConstants.CENTER);
+        productTypeHint.setBounds(81, 140, 810, 40);
+        indexPanelProductType.add(productTypeHint);
+
+        productTypePanel = new ProductTypePanel(PRODUCT_TYPE_SWITCH_PASSWORD);
+        productTypePanel.setBounds(276, 210, 420, 72);
+        productTypePanel.setOnChangeListener(() -> {
+            if (work_status == 0 && work_status2 == 0) {
+                saveProductTypeToDb();
+                if (PRODUCT_TYPE_SWITCH_ENABLED) {
+            setMenuStatus("当前生产类型:" + getProductTypeName() + ",请扫工件码",0);
+        } else {
+            setMenuStatus("请扫工件码",0);
+        }
+            }
+            refreshProductTypeCurrentLabel();
+        });
+        indexPanelProductType.add(productTypePanel);
+
+        productTypeCurrentLabel = new JLabel();
+        productTypeCurrentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 22));
+        productTypeCurrentLabel.setForeground(new Color(64, 64, 64));
+        productTypeCurrentLabel.setHorizontalAlignment(SwingConstants.CENTER);
+        productTypeCurrentLabel.setBounds(81, 310, 810, 40);
+        refreshProductTypeCurrentLabel();
+        indexPanelProductType.add(productTypeCurrentLabel);
+
+        tabbedPane.addTab("生产类型", new ImageIcon(MesClient.class.getResource("/bg/menu_setting.png")), productTypeScrollPane, null);
+        }
+
 
 
         JPanel indexPanelGz = new JPanel();
@@ -1270,6 +1373,93 @@ public class MesClient extends JFrame {
         }
     }
 
+    public static void loadProductTypeFromDb() {
+        if (productTypePanel == null) {
+            return;
+        }
+        String savedType = JdbcUtils.getClientConfig(
+                JdbcUtils.getProductTypeConfigKey(mes_gw),
+                PRODUCT_TYPE_ZJ
+        );
+        if (PRODUCT_TYPE_SJ.equals(savedType)) {
+            productTypePanel.setResult(PRODUCT_TYPE_SJ);
+        } else {
+            productTypePanel.setResult(PRODUCT_TYPE_ZJ);
+        }
+        refreshProductTypeCurrentLabel();
+    }
+
+    public static void refreshProductTypeCurrentLabel() {
+        if (productTypeCurrentLabel != null) {
+            productTypeCurrentLabel.setText("当前选择:" + getProductTypeName() + "(前缀 " + getProductSnPrefix() + ")");
+        }
+    }
+
+    public static void saveProductTypeToDb() {
+        if (productTypePanel == null) {
+            return;
+        }
+        JdbcUtils.saveClientConfig(
+                JdbcUtils.getProductTypeConfigKey(mes_gw),
+                productTypePanel.getResult()
+        );
+    }
+
+    public static String getProductSnPrefix() {
+        if (productTypePanel != null && PRODUCT_TYPE_SJ.equals(productTypePanel.getResult())) {
+            return PREFIX_SJ;
+        }
+        return PREFIX_ZJ;
+    }
+
+    public static String getProductTypeName() {
+        if (productTypePanel != null) {
+            return productTypePanel.getTypeName();
+        }
+        return "智界";
+    }
+
+    private static String getProductTypeNameBySn(String sn) {
+        if (sn.startsWith(PREFIX_SJ)) {
+            return "尚界";
+        }
+        if (sn.startsWith(PREFIX_ZJ)) {
+            return "智界";
+        }
+        return "未知";
+    }
+
+    private static String validateProductSnMessage(String sn) {
+        if (sn == null || sn.isEmpty()) {
+            return "工件码为空,请重试";
+        }
+        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);
+        }
+        return null;
+    }
+
+    private static void updateProductTypePanelEnabled(boolean enabled) {
+        if (productTypePanel != null) {
+            productTypePanel.setEnabled(enabled);
+        }
+    }
+
+    private static void refreshProductTypeLock() {
+        boolean busy = (product_sn != null && !product_sn.getText().trim().isEmpty())
+                || (product_sn2 != null && !product_sn2.getText().trim().isEmpty())
+                || Boolean.TRUE.equals(mesQualityFlagA)
+                || Boolean.TRUE.equals(mesQualityFlagB);
+        updateProductTypePanelEnabled(!busy);
+    }
+
     public static void scanBarcode() {
         String scanBarcodeSn = txtfa.getText().trim();
         txtfa.setText("");
@@ -1285,6 +1475,11 @@ public class MesClient extends JFrame {
                 MesClient.setMenuStatus("镭雕码不能为空",1);
                 return;
             }
+            String validateMsg = validateProductSnMessage(scanBarcodeSn);
+            if (validateMsg != null) {
+                MesClient.setMenuStatus(validateMsg, 1);
+                return;
+            }
             if(MesClient.curPage.equals("A")){
                 System.out.println("typeA");
                 if(!product_sn.getText().isEmpty() && mesQualityFlagA){
@@ -1301,6 +1496,7 @@ public class MesClient extends JFrame {
                 product_sn2.setText(scanBarcodeSn);
             }
             MesClient.setMenuStatus("扫码成功",0);
+            refreshProductTypeLock();
 
             //刷新界面
             mesClientFrame.repaint();

+ 34 - 4
src/com/mes/ui/MesRevice.java

@@ -149,8 +149,14 @@ public class MesRevice {
     public static void updateResultRevice(String processMsgRet,String mes_msg){
         try{
             String sn = ProtocolParam.getSn(mes_msg).trim();
+            String face = resolveFace(mes_msg, sn);
+            if(face == null){
+                System.out.println("updateResultRevice: unable to resolve face, sn=" + sn);
+                return;
+            }
+            boolean isA = "A".equals(face);
             if(processMsgRet.equalsIgnoreCase("OK")) {
-                if(sn.equals(MesClient.product_sn.getText())){
+                if(isA){
                     System.out.println("resetScanA");
                     MesClient.status_menu.setText("A件提交成功");
                     MesClient.pxstatus1.setText("A:提交成功");
@@ -162,15 +168,13 @@ public class MesRevice {
                     MesClient.resetScanB();
                 }
             }else{
-                if(sn.equals(MesClient.product_sn.getText())){
+                if(isA){
                     MesClient.status_menu.setText("A件提交失败");
                     MesClient.pxstatus1.setText("A:提交失败");
-//                    MesClient.finish_ok_bt.setEnabled(true);
                     MesClient.tjStatusa = 1;
                 }else{
                     MesClient.status_menu.setText("B件提交失败");
                     MesClient.pxstatus2.setText("B:提交失败");
-//                    MesClient.finish_ng_bt.setEnabled(true);
                     MesClient.tjStatusb = 1;
                 }
             }
@@ -178,4 +182,30 @@ public class MesRevice {
             e.printStackTrace();
         }
     }
+
+    /** 优先用 oprno 判面别;SN 仅作兜底(避免手动提交清空 SN 后回执误判为对面) */
+    private static String resolveFace(String mes_msg, String sn){
+        String oprno = ProtocolParam.getOprno(mes_msg).trim();
+        if(oprno.equals(MesClient.mes_gw+"A") || oprno.equals(MesClient.mes_gw+"C")){
+            return "A";
+        }
+        if(oprno.equals(MesClient.mes_gw+"B") || oprno.equals(MesClient.mes_gw+"D")){
+            return "B";
+        }
+        String snA = MesClient.product_sn.getText() == null ? "" : MesClient.product_sn.getText().trim();
+        String snB = MesClient.product_sn2.getText() == null ? "" : MesClient.product_sn2.getText().trim();
+        if(!sn.isEmpty() && sn.equals(snA)){
+            return "A";
+        }
+        if(!sn.isEmpty() && sn.equals(snB)){
+            return "B";
+        }
+        if(!sn.isEmpty() && sn.equals(MesClient.curSna == null ? "" : MesClient.curSna.trim())){
+            return "A";
+        }
+        if(!sn.isEmpty() && sn.equals(MesClient.curSnb == null ? "" : MesClient.curSnb.trim())){
+            return "B";
+        }
+        return null;
+    }
 }

+ 14 - 7
src/com/mes/util/IweldCloudUtil.java

@@ -33,14 +33,15 @@ public class IweldCloudUtil {
         public String voltage = "";
         public String current = "";
         public String wireSpeed = "";
+        public String programName = "";
 
         public boolean hasData() {
-            return !voltage.isEmpty() || !current.isEmpty() || !wireSpeed.isEmpty();
+            return !voltage.isEmpty() || !current.isEmpty() || !wireSpeed.isEmpty() || !programName.isEmpty();
         }
 
         @Override
         public String toString() {
-            return "电压=" + voltage + ", 电流=" + current + ", 送丝速度=" + wireSpeed;
+            return "电压=" + voltage + ", 电流=" + current + ", 送丝速度=" + wireSpeed + ", 程序名=" + programName;
         }
     }
 
@@ -258,16 +259,14 @@ public class IweldCloudUtil {
         MesClient.param1.setText(welders[0].voltage);
         MesClient.param2.setText(welders[0].current);
         MesClient.param3.setText(welders[0].wireSpeed);
+        MesClient.param4.setText(welders[0].programName);
         MesClient.param21.setText(welders[1].voltage);
         MesClient.param22.setText(welders[1].current);
         MesClient.param23.setText(welders[1].wireSpeed);
+        MesClient.param24.setText(welders[1].programName);
 
         String recordTime = DateLocalUtils.getCurrentTime();
-        MesClient.hjparams.add(
-                welders[0].voltage + "|" + welders[0].current + "|" + welders[0].wireSpeed + "|" + "" + "|" + "" + "|"
-                        + welders[1].voltage + "|" + welders[1].current + "|" + welders[1].wireSpeed + "|" + "" + "|" + "" + "|"
-                        + recordTime
-        );
+        MesClient.hjparams.add(buildHjParamRecord(welders[0], welders[1], recordTime));
 
         if (MesClient.hjparams.size() == 60) {
             persistHjParams(MesClient.curFlag);
@@ -361,9 +360,17 @@ public class IweldCloudUtil {
                 "weldingAi", "D07", "WeldA", "weldingCurrent", "current", "actualCurrent", "actualCur");
         params.wireSpeed = readField(item,
                 "silkRate", "D18", "SilkRate", "wireFeedSpeed", "wireSpeed", "feedingSpeed", "feedSpeed");
+        params.programName = readField(item, "programName", "ProgramName", "weldProgram", "jobName");
         return params;
     }
 
+    /** 格式与 MES 后台一致:错误码位留空,JOB 位存 programName */
+    private static String buildHjParamRecord(WelderParams welder1, WelderParams welder2, String recordTime) {
+        return welder1.voltage + "|" + welder1.current + "|" + welder1.wireSpeed + "|" + "" + "|" + welder1.programName + "|"
+                + welder2.voltage + "|" + welder2.current + "|" + welder2.wireSpeed + "|" + "" + "|" + welder2.programName + "|"
+                + recordTime;
+    }
+
     private static String readField(JSONObject item, String... keys) {
         for (String key : keys) {
             if (item.containsKey(key) && item.get(key) != null) {

+ 64 - 3
src/com/mes/util/IweldCloudUtilTest.java

@@ -86,6 +86,20 @@ public class IweldCloudUtilTest {
                         + (runInfo2 != null ? "成功" : "失败"));
                 if (runInfo2 != null) {
                     System.out.println("接口状态: " + IweldCloudUtil.describeApiStatus(runInfo2));
+                    System.out.println("原始数据:");
+                    System.out.println(runInfo2);
+                    printProgramNameDebug(runInfo2);
+                }
+                // 再试焊机实时接口,对比云平台“在线”是否来自另一套状态
+                sleepBetweenRequests();
+                String welderUrl = "https://api.iweldcloud.com/ApiServer/rest/WeldWebService/getWeldRunInfo";
+                String runInfo2Welder = IweldCloudUtil.fetchRunInfoForProdCodeWithUrl(prodCode2, welderUrl);
+                System.out.println("prodCode2 走 getWeldRunInfo: "
+                        + (runInfo2Welder != null ? "成功" : "失败/无效"));
+                if (runInfo2Welder != null) {
+                    System.out.println("焊机接口原始数据:");
+                    System.out.println(runInfo2Welder);
+                    printProgramNameDebug(runInfo2Welder);
                 }
                 IweldCloudUtil.WelderParams[] welders2 = IweldCloudUtil.parseWelderParamsFromResponse(runInfo2);
                 printWelderParams("焊机2(prodCode2=" + prodCode2 + ")", welders2, 0);
@@ -171,9 +185,17 @@ public class IweldCloudUtilTest {
                     }
                     if (code.equals(prodCode) || code.equals(prodCode2)) {
                         matched = true;
-                        System.out.println("  " + code + " -> D04=" + device.getString("D04")
-                                + " (" + deviceTypeName(device.getString("D04")) + "), D03="
-                                + device.getString("D03"));
+                        System.out.println("  " + code + " -> 完整字段: " + device.toJSONString());
+                        System.out.println("     D03=" + device.getString("D03")
+                                + ", D04=" + device.getString("D04")
+                                + " (" + deviceTypeName(device.getString("D04")) + ")"
+                                + ", 常见在线字段: D02=" + device.get("D02")
+                                + ", D05=" + device.get("D05")
+                                + ", D06=" + device.get("D06")
+                                + ", D07=" + device.get("D07")
+                                + ", status=" + device.get("status")
+                                + ", online=" + device.get("online")
+                                + ", runStatus=" + device.get("runStatus"));
                     }
                 }
             }
@@ -225,6 +247,45 @@ public class IweldCloudUtilTest {
             return;
         }
         System.out.println(label + ": " + params);
+        if (!params.programName.isEmpty()) {
+            System.out.println(label + " 程序名: " + params.programName);
+        }
+    }
+
+    /** 打印原始 JSON 中 programName 相关字段,便于对比两台设备差异 */
+    private static void printProgramNameDebug(String response) {
+        try {
+            JSONObject json = JSONObject.parseObject(response);
+            JSONArray dataList = json.getJSONArray("dataList");
+            if (dataList == null || dataList.isEmpty()) {
+                System.out.println("programName调试: dataList 为空");
+                return;
+            }
+            JSONObject first = dataList.getJSONObject(0);
+            JSONArray nested = first.getJSONArray("list");
+            JSONObject item = (nested != null && !nested.isEmpty()) ? nested.getJSONObject(0) : first;
+            System.out.println("programName调试: containsKey=" + item.containsKey("programName")
+                    + ", raw=" + item.get("programName")
+                    + ", runStatue=" + item.get("runStatue")
+                    + ", currentStatus=" + item.get("currentStatus")
+                    + ", positionNam=" + item.get("positionNam")
+                    + ", keys含program=" + keysContaining(item, "program"));
+        } catch (Exception e) {
+            System.out.println("programName调试失败: " + e.getMessage());
+        }
+    }
+
+    private static String keysContaining(JSONObject item, String keyword) {
+        StringBuilder sb = new StringBuilder();
+        for (String key : item.keySet()) {
+            if (key != null && key.toLowerCase().contains(keyword.toLowerCase())) {
+                if (sb.length() > 0) {
+                    sb.append(", ");
+                }
+                sb.append(key).append("=").append(item.get(key));
+            }
+        }
+        return sb.length() == 0 ? "(无)" : sb.toString();
     }
 
     private static void sleepBetweenRequests() {

+ 52 - 0
src/com/mes/util/JdbcUtils.java

@@ -75,8 +75,60 @@ public class JdbcUtils {
 				")";
 		statement.executeUpdate(submitRecord);
 
+		String clientConfig = "CREATE TABLE if not exists client_config("
+				+ "config_key VARCHAR(100) PRIMARY KEY,"
+				+ "config_value VARCHAR(500),"
+				+ "update_time DATETIME"
+				+ ")";
+		statement.executeUpdate(clientConfig);
+
         statement.close();
     }
+
+    public static void saveClientConfig(String key, String value) {
+    	try {
+    		if (conn == null || conn.isClosed()) {
+    			openConnection();
+    		}
+    		String sql = "INSERT INTO client_config (config_key, config_value, update_time) VALUES (?, ?, ?) "
+    				+ "ON CONFLICT(config_key) DO UPDATE SET config_value = excluded.config_value, update_time = excluded.update_time";
+    		PreparedStatement ps = conn.prepareStatement(sql);
+    		ps.setString(1, key);
+    		ps.setString(2, value);
+    		ps.setString(3, DateLocalUtils.getCurrentTime());
+    		ps.executeUpdate();
+    		ps.close();
+    	} catch (SQLException e) {
+    		e.printStackTrace();
+    	}
+    }
+
+    public static String getClientConfig(String key, String defaultValue) {
+    	try {
+    		if (conn == null || conn.isClosed()) {
+    			openConnection();
+    		}
+    		String sql = "SELECT config_value FROM client_config WHERE config_key = ?";
+    		PreparedStatement ps = conn.prepareStatement(sql);
+    		ps.setString(1, key);
+    		ResultSet rs = ps.executeQuery();
+    		if (rs.next()) {
+    			String value = rs.getString("config_value");
+    			rs.close();
+    			ps.close();
+    			return value;
+    		}
+    		rs.close();
+    		ps.close();
+    	} catch (SQLException e) {
+    		e.printStackTrace();
+    	}
+    	return defaultValue;
+    }
+
+    public static String getProductTypeConfigKey(String gw) {
+    	return "product_type_" + gw;
+    }
     
     //插入数据
     public static boolean insertData(String gw, String gy, String bw, String message_type, String sn) {

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

@@ -1,4 +1,4 @@
-mes.gw=OP050
+mes.gw=OP060
 #mes.server_ip=127.0.0.1
 mes.server_ip=192.168.16.99
 mes.tcp_port=3000
@@ -12,14 +12,14 @@ iweld.login.url=https://api.iweldcloud.com/ApiServer/Login
 # 实时数据接口类型:robot=机器人(getRobotRunInfo),welder=焊机(getWeldRunInfo)
 iweld.device.type=robot
 # 3号机(D01 以云平台设备制造编码为准)
-iweld.prodCode=2026S0038
+#iweld.prodCode=2026S0038
 # 4号机
-#iweld.prodCode=2026S0065
+iweld.prodCode=2026S0065
 # 焊机2产品编码,留空则仅使用 dataList 第2条或焊机2显示为空
 # 3号机
-iweld.prodCode2=2026S0082
+#iweld.prodCode2=2026S0082
 # 4号机
-#iweld.prodCode2=2026S0084
+iweld.prodCode2=2026S0084
 # 实时数据接口地址(留空则按 iweld.device.type 自动选择)
 #iweld.run.info.url=
 iweld.robot.run.info.url=https://api.iweldcloud.com/ApiServer/rest/WeldWebService/getRobotRunInfo