hou 4 dni temu
rodzic
commit
94e866c6a9

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

+ 166 - 1
src/com/mes/ui/MesClient.java

@@ -2,6 +2,7 @@ package com.mes.ui;
 
 import com.alibaba.fastjson2.JSONObject;
 import com.mes.component.MesRadio;
+import com.mes.component.ProductTypePanel;
 import com.mes.component.MesWebView;
 import com.mes.util.DateLocalUtils;
 import com.mes.util.HttpUtils;
@@ -86,6 +87,16 @@ public class MesClient extends JFrame {
     public static JPanel indexPanelC;
     public static MesWebView jfxPanel2 = 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 JTable table;
     public static Object[] columnNames = { "物料名称", "绑定批次", "剩余次数", "操作" };
@@ -394,6 +405,7 @@ public class MesClient extends JFrame {
             log.error("PLC 初始化失败", e);
             setMenuStatus("PLC 连接失败: " + e.getMessage(), -1);
         }
+        loadProductTypeFromDb();
         resetScanA();
     }
 
@@ -407,7 +419,12 @@ public class MesClient extends JFrame {
         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);
+        }
 //        MesClient.setMenuStatus("开班点检,请先进行OKNG样件测试",-1);
 
         updateMaterailData();
@@ -465,6 +482,85 @@ public class MesClient extends JFrame {
         return barcodeRet;
     }
 
+    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);
+        }
+    }
+
     public static void scanBarcode() {
         if(work_status == 1){
             JOptionPane.showMessageDialog(mesClientFrame,"工作中,勿扫码","提示窗口", JOptionPane.INFORMATION_MESSAGE);
@@ -481,6 +577,7 @@ public class MesClient extends JFrame {
         //弹窗扫工件码
         String scanBarcode = JOptionPane.showInputDialog(null, scanBarcodeTitle);
         if(scanBarcode!=null&&!scanBarcode.equalsIgnoreCase("")) {
+            scanBarcode = scanBarcode.trim();
             //获取用户
             getUser();
             //获取扫码内容36位
@@ -495,11 +592,25 @@ public class MesClient extends JFrame {
             //刷新界面
             mesClientFrame.repaint();
 
+            String validateMsg = validateProductSnMessage(scanBarcode);
+            if (validateMsg != null) {
+                check_quality_result = false;
+                work_status = 0;
+                MesClient.finish_ok_bt.setEnabled(false);
+                MesClient.finish_ng_bt.setEnabled(false);
+                MesClient.f_scan_data_bt_1.setEnabled(true);
+                updateProductTypePanelEnabled(true);
+                MesClient.setMenuStatus(validateMsg, -1);
+                return;
+            }
+            updateProductTypePanelEnabled(false);
+
             // 查询工件质量
             JSONObject retObj = DataUtil.checkQuality(scanBarcode,user20);
             if(retObj == null){
                 MesClient.check_quality_result = false;
                 MesClient.work_status = 0;
+                updateProductTypePanelEnabled(true);
                 MesClient.setMenuStatus("请求失败,请重试",-1);
                 return;
             }
@@ -515,6 +626,7 @@ public class MesClient extends JFrame {
             }else{
                 MesClient.check_quality_result = false;
                 MesClient.work_status = 0;
+                updateProductTypePanelEnabled(true);
                 if(retObj.get("result")==null){
                     MesClient.setMenuStatus("请求失败,请重试",-1);
                 }else{
@@ -535,6 +647,8 @@ public class MesClient extends JFrame {
             if (!PlcService.isInitialized()) {
                 PlcService.init(mes_plc_ip);
             }
+            String sn = MesClient.product_sn.getText().trim();
+            PlcService.writeBarcode(sn);
             PlcService.writeAllowStart();
             MesClient.status_menu.setText("该工件可以加工,等待设备完成...");
             PlcService.startWatchProcessDone(() -> {
@@ -547,6 +661,7 @@ public class MesClient extends JFrame {
             MesClient.check_quality_result = false;
             MesClient.work_status = 0;
             MesClient.f_scan_data_bt_1.setEnabled(true);
+            updateProductTypePanelEnabled(true);
             MesClient.setMenuStatus("PLC通信失败: " + e.getMessage(), -1);
         }
     }
@@ -561,6 +676,11 @@ public class MesClient extends JFrame {
             MesClient.setMenuStatus("工件码为空,请重试", -1);
             return;
         }
+        String validateMsg = validateProductSnMessage(sn);
+        if (validateMsg != null) {
+            MesClient.setMenuStatus(validateMsg, -1);
+            return;
+        }
 
         MesClient.finish_ok_bt.setEnabled(false);
         MesClient.finish_ng_bt.setEnabled(false);
@@ -837,6 +957,51 @@ public class MesClient extends JFrame {
         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) {
+                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);
+        }
+
 //		searchScrollPane = new JScrollPane((Component) null);
 
         indexPanelC = new JPanel(new BorderLayout());

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

@@ -86,9 +86,62 @@ public class JdbcUtils {
 				")";
 		statement.executeUpdate(opIpConfig);
 
+		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();
+    		log.info("保存本地配置: {}={}", key, value);
+    	} catch (SQLException e) {
+    		log.info("保存本地配置失败: {}", e.getMessage());
+    	}
+    }
+
+    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) {
+    		log.info("读取本地配置失败: {}", e.getMessage());
+    	}
+    	return defaultValue;
+    }
+
+    public static String getProductTypeConfigKey(String gw) {
+    	return "product_type_" + gw;
+    }
+
 	public static String getServerIpByOp(String op) {
 		try {
 			if (op == null || op.trim().isEmpty()) {

+ 32 - 2
src/com/mes/util/PlcService.java

@@ -15,8 +15,11 @@ public class PlcService {
     private static final Logger log = LoggerFactory.getLogger(PlcService.class);
 
     private static final String ADDR_ALLOW_START = "V0";
+    private static final String ADDR_ALLOW_START_BIT = "M2.0";
     private static final String ADDR_PROCESS_DONE = "V2";
     private static final String ADDR_UPLOAD_DONE = "V4";
+    /** VB20:工件二维码(S200 SMART STRING,首字节为长度) */
+    private static final String ADDR_BARCODE = "V20";
 
     private static final int MAX_RETRY = 3;
     private static final int RETRY_DELAY_MS = 500;
@@ -57,9 +60,18 @@ public class PlcService {
         }
     }
 
+    public static void writeBarcode(String barcode) {
+        if (barcode == null || barcode.isEmpty()) {
+            throw new IllegalArgumentException("工件二维码不能为空");
+        }
+        writeString(ADDR_BARCODE, barcode);
+        log.info("已写入 VB20={} (工件二维码)", barcode);
+    }
+
     public static void writeAllowStart() {
+        writeBoolean(ADDR_ALLOW_START_BIT, true);
         writeInt16(ADDR_ALLOW_START, (short) 1);
-        log.info("已写入 VW0=1 (mes允许启动信号)");
+        log.info("已写入 M2.0=1, VW0=1 (mes允许启动信号)");
     }
 
     public static short readProcessDone() {
@@ -72,9 +84,10 @@ public class PlcService {
     }
 
     public static void resetAllowAndUpload() {
+        writeBoolean(ADDR_ALLOW_START_BIT, false);
         writeInt16(ADDR_ALLOW_START, (short) 0);
         writeInt16(ADDR_UPLOAD_DONE, (short) 0);
-        log.info("已重置 VW0=0, VW4=0");
+        log.info("已重置 M2.0=0, VW0=0, VW4=0");
     }
 
     public static synchronized void startWatchProcessDone(Runnable onProcessDone) {
@@ -124,6 +137,23 @@ public class PlcService {
         });
     }
 
+    private static void writeBoolean(String address, boolean value) {
+        ensureConnected();
+        executeWithRetry("写入 " + address, () -> {
+            s7PLC.writeBoolean(address, value);
+            return null;
+        });
+    }
+
+    private static void writeString(String address, String value) {
+        ensureConnected();
+        final String writeValue = value;
+        executeWithRetry("写入 " + address, () -> {
+            s7PLC.writeString(address, writeValue);
+            return null;
+        });
+    }
+
     private static short readInt16(String address) {
         ensureConnected();
         return executeWithRetry("读取 " + address, () -> s7PLC.readInt16(address));