chefz 3 日 前
コミット
38ea657557

+ 11 - 0
src/com/mes/ui/ConfigParam.java

@@ -28,6 +28,16 @@ public class ConfigParam {
 	public String lineSn;
 	public String task;
 	public String stationNo;
+	/** 胶圈次数功能:是=启用,否=停用 */
+	public String sealRingEnable;
+
+	public String getSealRingEnable() {
+		return sealRingEnable;
+	}
+
+	public void setSealRingEnable(String sealRingEnable) {
+		this.sealRingEnable = sealRingEnable;
+	}
 
 	public String getStationNo() {
 		return stationNo;
@@ -273,6 +283,7 @@ public class ConfigParam {
 				", oprno='" + oprno + '\'' +
 				", oprnoTitle='" + oprnoTitle + '\'' +
 				", lineSn='" + lineSn + '\'' +
+				", sealRingEnable='" + sealRingEnable + '\'' +
 				'}';
 	}
 }

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

@@ -5,6 +5,7 @@ import com.mes.component.MesRadio;
 import com.mes.component.MesWebView;
 import com.mes.util.Base64Utils;
 import com.mes.util.HttpUtils;
+import com.mes.util.QmUploadUtil;
 
 import javax.swing.*;
 import java.awt.*;
@@ -182,6 +183,7 @@ public class LoginFarme extends JFrame {
                     MesClient.user_menu.setText(user_id);
                     MesClient.welcomeWin.setVisible(false);
                     MesClient.mesClientFrame.setVisible(true);
+                    QmUploadUtil.startRetryTimer();
 
 //                    if(MesClient.jfxPanel == null){
 //                        String url = "http://"+ MesClient.mes_server_ip+":8980/js/a/mes/mesProductRecord/work?oprno="+MesClient.mes_gw+"&lineSn="+MesClient.mes_line_sn;

+ 200 - 18
src/com/mes/ui/MesClient.java

@@ -91,7 +91,7 @@ public class MesClient extends JFrame {
     public static JLabel curTaskName;
 
     public static JTable table;
-    public static Object[] columnNames = {"工件码", "测试日期", "压力", "泄露值","结果"};
+    public static Object[] columnNames = {"工件码", "测试日期", "压力", "泄露值","结果","上传状态"};
 //    public static Object[][] rowData = null;
     //    public static Object[][] rowData = {
 //        {"151245P00001213100100024120700111","2024-12-09 12:30:30","60","60","60","200kPa","3ml/min","OK"},
@@ -99,7 +99,7 @@ public class MesClient extends JFrame {
 //        {"151245P00001213100100024120700113","2024-12-09 14:30:30","60","60","60","200kPa","3ml/min","OK"},
 //        {"151245P00001213100100024120700113","2024-12-09 14:30:30","60","60","60","200kPa","3ml/min","OK"},
 //    };
-    public static Object[][] rowData = new Object[12][5];
+    public static Object[][] rowData = new Object[12][6];
     public static JLabel pageLabel;
     public static int pageNo=1 ; // 当前页
     public static int totalPages; // 总页数
@@ -181,13 +181,34 @@ public class MesClient extends JFrame {
         cjTimerText = new Timer();
         cjTimerText.schedule(new TimerTask() {
             public void run() {
-                if(work_status == 0){
-                    product_sn.requestFocusInWindow();
+                if(work_status != 0 || product_sn == null){
+                    return;
+                }
+                // 正在编辑胶圈相关控件时,不要抢焦点,否则阈值输入不了
+                if (isSealRingInputFocused()) {
+                    return;
                 }
+                SwingUtilities.invokeLater(() -> {
+                    if (work_status == 0 && product_sn != null && !isSealRingInputFocused()) {
+                        product_sn.requestFocusInWindow();
+                    }
+                });
             }
         }, 1000,1000);
     }
 
+    private static boolean isSealRingInputFocused() {
+        Component focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
+        if (focus == null) {
+            return false;
+        }
+        return focus == sealRingLimitField
+                || focus == sealRingSaveLimitBt
+                || focus == sealRingResetBt
+                || focus == sealRingCountLabel
+                || focus == sealRingEnable;
+    }
+
     public static java.util.Timer dateBeatTimer;
     public static void startDateTimer() {
         if (dateBeatTimer != null) {
@@ -376,6 +397,14 @@ public class MesClient extends JFrame {
     public static JTextField oprno;
     public static JTextField oprnoTitle;
     public static JTextField lineSn;
+    public static JComboBox sealRingEnable;
+
+    public static JLabel sealRingTitle;
+    public static JLabel sealRingCountLabel;
+    public static JLabel sealRingLimitLbl;
+    public static JTextField sealRingLimitField;
+    public static JButton sealRingSaveLimitBt;
+    public static JButton sealRingResetBt;
 
     public static JLabel taskName;
     public static JTextField taskMaxNumA;
@@ -420,6 +449,9 @@ public class MesClient extends JFrame {
         oprno.setEnabled(ret);
         oprnoTitle.setEnabled(ret);
         lineSn.setEnabled(ret);
+        if (sealRingEnable != null) {
+            sealRingEnable.setEnabled(ret);
+        }
     }
 
     public static void formatConfigData(){
@@ -550,6 +582,14 @@ public class MesClient extends JFrame {
         }
         lineSn.setText(configParam.getLineSn());
 
+        if(configParam.getSealRingEnable() == null||configParam.getSealRingEnable().isEmpty()){
+            configParam.setSealRingEnable("否");
+        }
+        if (sealRingEnable != null) {
+            sealRingEnable.setSelectedItem(configParam.getSealRingEnable());
+        }
+        applySealRingUiVisibility();
+
         if(!oprnoTitle.getText().isEmpty()){
             String stationInfo = oprno.getText() +"-"+ oprnoTitle.getText();
             welcomeWin.setTitle("气密检测客户端--"+stationInfo);
@@ -1031,6 +1071,49 @@ public class MesClient extends JFrame {
         result.setBounds(290, 332, 356, 89);
         indexPanelA.add(result);
 
+        // 胶圈次数(由软件设置启用后显示)
+        sealRingTitle = new JLabel("胶圈次数");
+        sealRingTitle.setHorizontalAlignment(SwingConstants.RIGHT);
+        sealRingTitle.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        sealRingTitle.setBounds(60, 440, 100, 50);
+        indexPanelA.add(sealRingTitle);
+
+        sealRingCountLabel = new JLabel("已提交 0 / 200 次");
+        sealRingCountLabel.setOpaque(true);
+        sealRingCountLabel.setBackground(new Color(240, 248, 255));
+        sealRingCountLabel.setBorder(BorderFactory.createLineBorder(new Color(70, 130, 180)));
+        sealRingCountLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
+        sealRingCountLabel.setForeground(new Color(0, 128, 0));
+        sealRingCountLabel.setHorizontalAlignment(SwingConstants.CENTER);
+        sealRingCountLabel.setBounds(170, 440, 280, 50);
+        indexPanelA.add(sealRingCountLabel);
+
+        sealRingLimitLbl = new JLabel("更换阈值");
+        sealRingLimitLbl.setHorizontalAlignment(SwingConstants.RIGHT);
+        sealRingLimitLbl.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        sealRingLimitLbl.setBounds(470, 440, 90, 50);
+        indexPanelA.add(sealRingLimitLbl);
+
+        sealRingLimitField = new JTextField();
+        sealRingLimitField.setFont(new Font("微软雅黑", Font.PLAIN, 22));
+        sealRingLimitField.setHorizontalAlignment(SwingConstants.CENTER);
+        sealRingLimitField.setBounds(570, 445, 80, 40);
+        indexPanelA.add(sealRingLimitField);
+
+        sealRingSaveLimitBt = new JButton("保存");
+        sealRingSaveLimitBt.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        sealRingSaveLimitBt.setBounds(660, 445, 80, 40);
+        sealRingSaveLimitBt.addActionListener(e -> saveSealRingLimit());
+        indexPanelA.add(sealRingSaveLimitBt);
+
+        sealRingResetBt = new JButton("已更换胶圈");
+        sealRingResetBt.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        sealRingResetBt.setBounds(750, 445, 120, 40);
+        sealRingResetBt.addActionListener(e -> resetSealRingCount());
+        indexPanelA.add(sealRingResetBt);
+
+        applySealRingUiVisibility();
+
         finish_ok_bt = new JButton("OK");
         finish_ok_bt.setEnabled(false);
         finish_ok_bt.addActionListener(new ActionListener() {
@@ -1487,6 +1570,9 @@ public class MesClient extends JFrame {
                     configParam.setOprno(oprno.getText().trim());
                     configParam.setOprnoTitle(oprnoTitle.getText().trim());
                     configParam.setLineSn(lineSn.getText().trim());
+                    if (sealRingEnable != null && sealRingEnable.getSelectedItem() != null) {
+                        configParam.setSealRingEnable(sealRingEnable.getSelectedItem().toString());
+                    }
 
                     System.out.println("configParam:"+JSONObject.toJSONString(configParam));
 
@@ -1497,20 +1583,6 @@ public class MesClient extends JFrame {
                     }
                     isConfigEdit = 0;
 
-//                    MesClient.indexPanelB.removeAll();
-//                    System.out.println("indexPanelB:"+MesClient.indexPanelB.getComponentCount());
-//                    MesClient.jfxPanel2 = null;
-//                    if(MesClient.jfxPanel2 == null){
-//                        String url = "http://"+ MesClient.mes_server_ip+":8980/js/a/mes/mesProcessCheckRecord/ulist?ucode="+MesClient.user_menu.getText()+"&oprno="+MesClient.configParam.getOprno()+"&lineSn="+MesClient.mes_line_sn;
-//                        MesClient.jfxPanel2 = new MesWebView(url);
-//                        MesClient.jfxPanel2.setSize(990, 550);
-//                        MesClient.indexPanelB.add(MesClient.jfxPanel2);
-//                        System.out.println("indexPanelBB:"+MesClient.indexPanelB.getComponentCount());
-//                        MesClient.indexPanelB.revalidate(); // 重新验证布局(如果需要)
-//                        MesClient.indexPanelB.repaint();
-//                        System.out.println("indexPanelCC:"+MesClient.indexPanelB.getComponentCount());
-//                    }
-
                     formatConfigData();
                     serialPortUtils.closePort();
                     serialPortUtils = new SerialPortUtils(configParam.getSerialPort(),configParam.getOprno(),configParam.getOprnoTitle(),configParam.getDevice());
@@ -1627,6 +1699,19 @@ public class MesClient extends JFrame {
         lineSn.setBounds(762, 195, 200, 30);
         indexPanelD.add(lineSn);
 
+        JLabel lblSealRingEnable = new JLabel("胶圈次数");
+        lblSealRingEnable.setHorizontalAlignment(SwingConstants.RIGHT);
+        lblSealRingEnable.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        lblSealRingEnable.setBounds(10, 445, 120, 30);
+        indexPanelD.add(lblSealRingEnable);
+
+        sealRingEnable = new JComboBox();
+        sealRingEnable.setModel(new DefaultComboBoxModel(new String[] {"否", "是"}));
+        sealRingEnable.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        sealRingEnable.setBounds(140, 445, 200, 30);
+        sealRingEnable.setEnabled(false);
+        indexPanelD.add(sealRingEnable);
+
         tabbedPane.addTab("软件设置", new ImageIcon(MesClient.class.getResource("/bg/menu_setting.png")), searchScrollPaneD, null);
 
         JPanel indexPanelTask = new JPanel();
@@ -1965,6 +2050,103 @@ public class MesClient extends JFrame {
         MesClient.status_menu.setText(msg);
     }
 
+    public static boolean isSealRingEnabled() {
+        return configParam != null && "是".equals(configParam.getSealRingEnable());
+    }
+
+    public static void applySealRingUiVisibility() {
+        boolean enabled = isSealRingEnabled();
+        if (sealRingTitle != null) {
+            sealRingTitle.setVisible(enabled);
+        }
+        if (sealRingCountLabel != null) {
+            sealRingCountLabel.setVisible(enabled);
+        }
+        if (sealRingLimitLbl != null) {
+            sealRingLimitLbl.setVisible(enabled);
+        }
+        if (sealRingLimitField != null) {
+            sealRingLimitField.setVisible(enabled);
+        }
+        if (sealRingSaveLimitBt != null) {
+            sealRingSaveLimitBt.setVisible(enabled);
+        }
+        if (sealRingResetBt != null) {
+            sealRingResetBt.setVisible(enabled);
+        }
+        if (enabled) {
+            refreshSealRingUi();
+        }
+    }
+
+    /** 测试结果成功落库/上传后累计胶圈使用次数,达到阈值则报警 */
+    public static void onSealRingUploadSuccess() {
+        if (!isSealRingEnabled()) {
+            return;
+        }
+        int count = JdbcUtils.incrementSealRingCount();
+        int limit = JdbcUtils.getSealRingLimit();
+        refreshSealRingUi();
+        if (count >= limit) {
+            setMenuStatus("胶圈已达更换阈值(" + count + "/" + limit + "),请更换胶圈!", -1);
+            JOptionPane.showMessageDialog(mesClientFrame,
+                    "胶圈已提交 " + count + " 次,达到更换阈值 " + limit + " 次。\n请更换胶圈后点击「已更换胶圈」清零。",
+                    "胶圈更换提醒",
+                    JOptionPane.WARNING_MESSAGE);
+        }
+    }
+
+    public static void refreshSealRingUi() {
+        int count = JdbcUtils.getSealRingCount();
+        int limit = JdbcUtils.getSealRingLimit();
+        if (sealRingLimitField != null) {
+            sealRingLimitField.setText(String.valueOf(limit));
+        }
+        if (sealRingCountLabel != null) {
+            sealRingCountLabel.setText("已提交 " + count + " / " + limit + " 次");
+            if (count >= limit) {
+                sealRingCountLabel.setForeground(Color.RED);
+            } else if (limit > 0 && count >= limit * 0.9) {
+                sealRingCountLabel.setForeground(new Color(200, 120, 0));
+            } else {
+                sealRingCountLabel.setForeground(new Color(0, 128, 0));
+            }
+        }
+    }
+
+    private static void saveSealRingLimit() {
+        if (sealRingLimitField == null) {
+            return;
+        }
+        String text = sealRingLimitField.getText().trim();
+        try {
+            int limit = Integer.parseInt(text);
+            if (limit <= 0) {
+                JOptionPane.showMessageDialog(mesClientFrame, "更换阈值必须大于 0", "提示", JOptionPane.WARNING_MESSAGE);
+                return;
+            }
+            JdbcUtils.setSealRingLimit(limit);
+            refreshSealRingUi();
+            setMenuStatus("胶圈更换阈值已保存为 " + limit + " 次", 0);
+        } catch (NumberFormatException e) {
+            JOptionPane.showMessageDialog(mesClientFrame, "请输入正确的数字", "提示", JOptionPane.WARNING_MESSAGE);
+        }
+    }
+
+    private static void resetSealRingCount() {
+        int confirm = JOptionPane.showConfirmDialog(mesClientFrame,
+                "确认已更换胶圈?\n将把提交次数清零。",
+                "更换胶圈确认",
+                JOptionPane.YES_NO_OPTION,
+                JOptionPane.QUESTION_MESSAGE);
+        if (confirm != JOptionPane.YES_OPTION) {
+            return;
+        }
+        JdbcUtils.resetSealRingCount();
+        refreshSealRingUi();
+        setMenuStatus("胶圈计数已清零,可继续生产", 0);
+    }
+
     public static void getTask(){
         taskParamList = JdbcUtils.getTask();
         System.out.println("taskParamList:"+taskParamList.size());

+ 233 - 10
src/com/mes/util/JdbcUtils.java

@@ -108,8 +108,137 @@ public class JdbcUtils {
 				")";
 		statement.executeUpdate(configRecord2);
 
+		try {
+			statement.executeUpdate("ALTER TABLE test_record ADD COLUMN upload_status VARCHAR(1) DEFAULT '0'");
+		} catch (SQLException ignored) {
+		}
+		try {
+			statement.executeUpdate("ALTER TABLE test_record ADD COLUMN upload_retry INT DEFAULT 0");
+		} catch (SQLException ignored) {
+		}
+
+		// 胶圈次数本地配置(次数/阈值,与软件设置中的启用开关配合)
+		String appConfig = "CREATE TABLE if not exists app_config("
+				+ "cfg_key VARCHAR(64) PRIMARY KEY,"
+				+ "cfg_value VARCHAR(128)"
+				+ ")";
+		statement.executeUpdate(appConfig);
+		ensureConfigDefault("seal_ring_limit", "200");
+		ensureConfigDefault("seal_ring_count", "0");
+
         statement.close();
     }
+
+	private static void ensureConfigDefault(String key, String defaultValue) {
+		try {
+			if (conn == null || conn.isClosed()) {
+				openConnection();
+			}
+			PreparedStatement ps = conn.prepareStatement("SELECT cfg_value FROM app_config WHERE cfg_key=?");
+			ps.setString(1, key);
+			ResultSet rs = ps.executeQuery();
+			boolean exists = rs.next();
+			rs.close();
+			ps.close();
+			if (!exists) {
+				PreparedStatement ins = conn.prepareStatement("INSERT INTO app_config(cfg_key,cfg_value) VALUES(?,?)");
+				ins.setString(1, key);
+				ins.setString(2, defaultValue);
+				ins.executeUpdate();
+				ins.close();
+			}
+		} catch (SQLException e) {
+			log.error("初始化配置失败 key={}", key, e);
+		}
+	}
+
+	public static String getAppConfig(String key, String defaultValue) {
+		try {
+			if (conn == null || conn.isClosed()) {
+				openConnection();
+			}
+			PreparedStatement ps = conn.prepareStatement("SELECT cfg_value FROM app_config WHERE cfg_key=?");
+			ps.setString(1, key);
+			ResultSet rs = ps.executeQuery();
+			String value = defaultValue;
+			if (rs.next()) {
+				value = rs.getString(1);
+			}
+			rs.close();
+			ps.close();
+			return value == null || value.trim().isEmpty() ? defaultValue : value.trim();
+		} catch (SQLException e) {
+			log.error("读取配置失败 key={}", key, e);
+			return defaultValue;
+		}
+	}
+
+	public static void setAppConfig(String key, String value) {
+		try {
+			if (conn == null || conn.isClosed()) {
+				openConnection();
+			}
+			PreparedStatement ps = conn.prepareStatement(
+					"INSERT OR REPLACE INTO app_config(cfg_key,cfg_value) VALUES(?,?)");
+			ps.setString(1, key);
+			ps.setString(2, value);
+			ps.executeUpdate();
+			ps.close();
+		} catch (SQLException e) {
+			log.error("保存配置失败 key={}", key, e);
+		}
+	}
+
+	public static int getSealRingLimit() {
+		try {
+			return Integer.parseInt(getAppConfig("seal_ring_limit", "200"));
+		} catch (Exception e) {
+			return 200;
+		}
+	}
+
+	public static int getSealRingCount() {
+		try {
+			return Integer.parseInt(getAppConfig("seal_ring_count", "0"));
+		} catch (Exception e) {
+			return 0;
+		}
+	}
+
+	public static void setSealRingLimit(int limit) {
+		setAppConfig("seal_ring_limit", String.valueOf(Math.max(1, limit)));
+	}
+
+	public static int incrementSealRingCount() {
+		int count = getSealRingCount() + 1;
+		setAppConfig("seal_ring_count", String.valueOf(count));
+		return count;
+	}
+
+	public static void resetSealRingCount() {
+		setAppConfig("seal_ring_count", "0");
+	}
+
+	public static String getUploadStatus(int id) {
+		try {
+			if (conn == null || conn.isClosed()) {
+				openConnection();
+			}
+			PreparedStatement ps = conn.prepareStatement("SELECT upload_status FROM test_record WHERE id=?");
+			ps.setInt(1, id);
+			ResultSet rs = ps.executeQuery();
+			String status = "0";
+			if (rs.next()) {
+				status = rs.getString(1);
+			}
+			rs.close();
+			ps.close();
+			return status == null ? "0" : status;
+		} catch (SQLException e) {
+			log.error("读取上传状态失败 id={}", id, e);
+			return "0";
+		}
+	}
     
     //插入数据
     public static boolean insertData(String gw, String gy, String bw, String message_type, String sn) {
@@ -159,26 +288,117 @@ public class JdbcUtils {
 	}
 
 	public static boolean insertTestRecord(TestParam testParam){
-		boolean ret = false;
+		return insertTestRecordReturnId(testParam) > 0;
+	}
+
+	public static int insertTestRecordReturnId(TestParam testParam){
 		try {
-			// 确保连接已经打开
 			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
 				JdbcUtils.openConnection();
 			}
 			Statement statement=conn.createStatement();
-			String insertSQL = "INSERT INTO test_record (oprno,oprno_title,ucode, sn, line_sn, create_time, param1,param2,param3,param4,param5,result,remark,device_type)" +
+			String insertSQL = "INSERT INTO test_record (oprno,oprno_title,ucode, sn, line_sn, create_time, param1,param2,param3,param4,param5,result,remark,device_type,upload_status,upload_retry)" +
 					"VALUES('"+testParam.getOprno()+"','" + testParam.getOprnoTitle() + "','"+testParam.getUcode()+"', '" + testParam.getSn() + "', '" + testParam.getLineSn() + "', '" + testParam.getCreateTime() + "', '" + testParam.getParam1() + "', '"
-					+testParam.getParam2()+"','"+testParam.getParam3()+"','"+testParam.getParam4()+"','"+testParam.getParam5()+"','"+testParam.getResult()+"','"+testParam.getRemark()+"','"+testParam.getDeviceType()+"')";
+					+testParam.getParam2()+"','"+testParam.getParam3()+"','"+testParam.getParam4()+"','"+testParam.getParam5()+"','"+testParam.getResult()+"','"+testParam.getRemark()+"','"+testParam.getDeviceType()+"','0',0)";
 			statement.executeUpdate(insertSQL);
+			ResultSet rs = statement.executeQuery("SELECT last_insert_rowid()");
+			int id = -1;
+			if (rs.next()) {
+				id = rs.getInt(1);
+			}
+			rs.close();
 			statement.close();
-			ret = true;
-			log.info("向test_record表插入数据成功: {}", insertSQL);
+			log.info("向test_record表插入数据成功, id={}", id);
+			return id;
 		} catch (SQLException e) {
-			ret = false;
 			log.error("向test_record表插入数据失败: {}", e.getMessage());
 			e.printStackTrace();
 		}
-		return ret;
+		return -1;
+	}
+
+	public static void updateUploadStatus(int id, String status) {
+		try {
+			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+				JdbcUtils.openConnection();
+			}
+			Statement statement = conn.createStatement();
+			statement.executeUpdate("UPDATE test_record SET upload_status='" + status + "' WHERE id=" + id);
+			statement.close();
+		} catch (SQLException e) {
+			log.error("更新upload_status失败: {}", e.getMessage());
+		}
+	}
+
+	public static void incrementUploadRetry(int id) {
+		try {
+			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+				JdbcUtils.openConnection();
+			}
+			Statement statement = conn.createStatement();
+			statement.executeUpdate("UPDATE test_record SET upload_retry=IFNULL(upload_retry,0)+1 WHERE id=" + id);
+			statement.close();
+		} catch (SQLException e) {
+			log.error("更新upload_retry失败: {}", e.getMessage());
+		}
+	}
+
+	public static int getUploadRetry(int id) {
+		try {
+			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+				JdbcUtils.openConnection();
+			}
+			Statement statement = conn.createStatement();
+			ResultSet rs = statement.executeQuery("SELECT upload_retry FROM test_record WHERE id=" + id);
+			if (rs.next()) {
+				return rs.getInt(1);
+			}
+			rs.close();
+			statement.close();
+		} catch (SQLException e) {
+			log.error("查询upload_retry失败: {}", e.getMessage());
+		}
+		return 0;
+	}
+
+	public static List<TestParam> getPendingUploadRecords(int limit) {
+		List<TestParam> list = new ArrayList<>();
+		try {
+			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+				JdbcUtils.openConnection();
+			}
+			Statement statement = conn.createStatement();
+			ResultSet rs = statement.executeQuery(
+					"SELECT * FROM test_record WHERE IFNULL(upload_status,'0') != '1' AND IFNULL(upload_retry,0) < 20 ORDER BY id ASC LIMIT " + limit);
+			while (rs.next()) {
+				list.add(mapTestParam(rs));
+			}
+			rs.close();
+			statement.close();
+		} catch (SQLException e) {
+			log.error("查询待上传记录失败: {}", e.getMessage());
+		}
+		return list;
+	}
+
+	private static TestParam mapTestParam(ResultSet rs) throws SQLException {
+		TestParam testParam = new TestParam();
+		testParam.setId(rs.getInt("id"));
+		testParam.setSn(rs.getString("sn"));
+		testParam.setOprno(rs.getString("oprno"));
+		testParam.setOprnoTitle(rs.getString("oprno_title"));
+		testParam.setUcode(rs.getString("ucode"));
+		testParam.setLineSn(rs.getString("line_sn"));
+		testParam.setCreateTime(rs.getString("create_time"));
+		testParam.setParam1(rs.getString("param1"));
+		testParam.setParam2(rs.getString("param2"));
+		testParam.setParam3(rs.getString("param3"));
+		testParam.setParam4(rs.getString("param4"));
+		testParam.setParam5(rs.getString("param5"));
+		testParam.setResult(rs.getString("result"));
+		testParam.setRemark(rs.getString("remark"));
+		testParam.setDeviceType(rs.getString("device_type"));
+		return testParam;
 	}
 
 	public static int checkSnTimer(String sn,ConfigParam configParam){
@@ -319,8 +539,11 @@ public class JdbcUtils {
 				MesClient.rowData[rowIndex][2] = ret.getString(8)+ret.getString(9);  // param1 (压力)
 				MesClient.rowData[rowIndex][3] = ret.getString(10)+ret.getString(11); // param3 (泄漏值)
 				MesClient.rowData[rowIndex][4] = ret.getString(13); // result (结果)
-//				MesClient.rowData[rowIndex][5] = ret.getString(8);
-//				MesClient.rowData[rowIndex][6] = ret.getString(9);
+				try {
+					MesClient.rowData[rowIndex][5] = QmUploadUtil.formatUploadStatus(ret.getString("upload_status"));
+				} catch (SQLException ignored) {
+					MesClient.rowData[rowIndex][5] = "待上传";
+				}
 				rowIndex++;
 			}
 

+ 179 - 0
src/com/mes/util/QmUploadUtil.java

@@ -0,0 +1,179 @@
+package com.mes.util;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.mes.ui.DataUtil;
+import com.mes.ui.MesClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.SwingUtilities;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * 气密结果本地保存 + 后台异步上传 MES + 失败自动重传(不阻塞主程序)
+ */
+public class QmUploadUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(QmUploadUtil.class);
+    private static Timer retryTimer;
+    private static final long RETRY_INTERVAL_MS = 30000L;
+    private static final int MAX_RETRY = 20;
+
+    private static final ExecutorService uploadExecutor = Executors.newSingleThreadExecutor(r -> {
+        Thread t = new Thread(r, "QmUploadWorker");
+        t.setDaemon(true);
+        return t;
+    });
+
+    public static boolean isUploadSuccess(JSONObject retObj) {
+        return retObj != null && retObj.get("result") != null
+                && retObj.get("result").toString().equalsIgnoreCase("true");
+    }
+
+    public static String formatUploadStatus(String status) {
+        if ("1".equals(status)) {
+            return "已上传";
+        }
+        if ("2".equals(status)) {
+            return "待重传";
+        }
+        return "待上传";
+    }
+
+    /**
+     * 保存本地记录并提交后台上传。返回 true 表示本地保存成功(主流程可继续,不等待 MES 上传)。
+     */
+    public static boolean saveAndUpload(TestParam testParam, boolean resetOnSuccess) {
+        int recordId = JdbcUtils.insertTestRecordReturnId(testParam);
+        if (recordId <= 0) {
+            runOnEdt(() -> MesClient.setMenuStatus("本地记录保存失败", -1));
+            return false;
+        }
+        testParam.setId(recordId);
+        refreshRecordQuery();
+
+        if (!MesClient.mes_enable) {
+            JdbcUtils.updateUploadStatus(recordId, "1");
+            if (MesClient.isSealRingEnabled()) {
+                runOnEdt(MesClient::onSealRingUploadSuccess);
+            }
+            if (resetOnSuccess) {
+                runOnEdt(() -> {
+                    MesClient.resetScanA();
+                    MesClient.setMenuStatus("测试结果已保存,请扫下一件", 0);
+                });
+            }
+            return true;
+        }
+
+        if (resetOnSuccess) {
+            runOnEdt(() -> {
+                MesClient.resetScanA();
+                MesClient.setMenuStatus("测试结果已保存,后台上传中...", 0);
+            });
+        }
+
+        submitAsyncUpload(testParam, resetOnSuccess);
+        return true;
+    }
+
+    public static void uploadRecord(TestParam testParam, boolean resetOnSuccess) {
+        submitAsyncUpload(testParam, resetOnSuccess);
+    }
+
+    private static void submitAsyncUpload(TestParam testParam, boolean resetOnSuccess) {
+        if (testParam == null || testParam.getId() == null || testParam.getId() <= 0) {
+            return;
+        }
+        uploadExecutor.execute(() -> doUpload(testParam, resetOnSuccess));
+    }
+
+    private static void doUpload(TestParam testParam, boolean resetOnSuccess) {
+        if (!MesClient.mes_enable) {
+            JdbcUtils.updateUploadStatus(testParam.getId(), "1");
+            return;
+        }
+        try {
+            JSONObject retObj = DataUtil.qmResultData(testParam);
+            if (isUploadSuccess(retObj)) {
+                String oldStatus = JdbcUtils.getUploadStatus(testParam.getId());
+                JdbcUtils.updateUploadStatus(testParam.getId(), "1");
+                // 仅首次上传成功计入胶圈次数,避免重传重复累计
+                if (!"1".equals(oldStatus) && MesClient.isSealRingEnabled()) {
+                    runOnEdt(MesClient::onSealRingUploadSuccess);
+                }
+                runOnEdt(() -> {
+                    if (resetOnSuccess) {
+                        MesClient.setMenuStatus("测试结果上传成功", 0);
+                    }
+                });
+                refreshRecordQuery();
+                log.info("气密结果上传成功, recordId={}", testParam.getId());
+                return;
+            }
+            markUploadFailed(testParam.getId());
+            log.warn("气密结果上传失败, recordId={}, 将后台重试", testParam.getId());
+        } catch (Exception e) {
+            log.error("气密结果上传异常, recordId=" + testParam.getId(), e);
+            markUploadFailed(testParam.getId());
+        }
+    }
+
+    private static void markUploadFailed(int recordId) {
+        JdbcUtils.updateUploadStatus(recordId, "2");
+        JdbcUtils.incrementUploadRetry(recordId);
+        runOnEdt(() -> MesClient.setMenuStatus("测试结果上传失败,后台自动重试中", -1));
+        refreshRecordQuery();
+    }
+
+    public static void startRetryTimer() {
+        if (retryTimer != null) {
+            retryTimer.cancel();
+        }
+        retryTimer = new Timer("QmUploadRetry", true);
+        retryTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                retryPendingUploads();
+            }
+        }, RETRY_INTERVAL_MS, RETRY_INTERVAL_MS);
+    }
+
+    public static void retryPendingUploads() {
+        if (!MesClient.mes_enable) {
+            return;
+        }
+        List<TestParam> pending = JdbcUtils.getPendingUploadRecords(10);
+        if (pending == null || pending.isEmpty()) {
+            return;
+        }
+        for (TestParam testParam : pending) {
+            int retry = JdbcUtils.getUploadRetry(testParam.getId());
+            if (retry >= MAX_RETRY) {
+                continue;
+            }
+            submitAsyncUpload(testParam, false);
+        }
+    }
+
+    private static void refreshRecordQuery() {
+        runOnEdt(() -> {
+            try {
+                JdbcUtils.getTestData();
+            } catch (Exception ignored) {
+            }
+        });
+    }
+
+    private static void runOnEdt(Runnable action) {
+        if (SwingUtilities.isEventDispatchThread()) {
+            action.run();
+        } else {
+            SwingUtilities.invokeLater(action);
+        }
+    }
+}

+ 1 - 14
src/com/mes/util/SerialPortUtils.java

@@ -164,20 +164,7 @@ public class SerialPortUtils {
 
                                                         String testDate = DateLocalUtils.getCurrentTime();
                                                         testParam.setCreateTime(testDate);
-                                                        JdbcUtils.insertTestRecord(testParam);
-
-                                                        if(MesClient.mes_enable){ // MES开启
-                                                            JSONObject retObj = DataUtil.qmResultData(testParam);
-                                                            if(retObj != null && retObj.get("result") != null && retObj.get("result").toString().equalsIgnoreCase("true")) {
-                                                                MesClient.resetScanA();
-                                                                MesClient.setMenuStatus("测试结果上传成功,请扫下一件",0);
-                                                            }else{
-                                                                MesClient.setMenuStatus("测试结果上传失败,请重试",-1);
-                                                                return;
-                                                            }
-                                                        }else{
-                                                            MesClient.resetScanA();
-                                                        }
+                                                        QmUploadUtil.saveAndUpload(testParam, true);
 
                                                         if(MesClient.configParam.getPrintLabel().equals("是") && lastRet.equals("OK")){
                                                             PrintUtil.printLabel(testParam.getSn(),testParam.getParam1()+testParam.getParam2(),testParam.getParam3()+testParam.getParam4(),testDate);

+ 1 - 14
src/com/mes/util/WorkTimer.java

@@ -111,20 +111,7 @@ public class WorkTimer {
                         testParam.setDeviceType("ateq");
                         testParam.setRemark("");
                         testParam.setCreateTime(testDate);
-                        JdbcUtils.insertTestRecord(testParam);
-
-                        if(MesClient.mes_enable){ // MES开启
-                            JSONObject retObj = DataUtil.qmResultData(testParam);
-                            if(retObj != null && retObj.get("result") != null && retObj.get("result").toString().equalsIgnoreCase("true")) {
-                                MesClient.resetScanA();
-                                MesClient.setMenuStatus("测试结果上传成功,请扫下一件",0);
-                            }else{
-                                MesClient.setMenuStatus("测试结果上传失败,请重试",-1);
-                                return;
-                            }
-                        }else{
-                            MesClient.resetScanA();
-                        }
+                        QmUploadUtil.saveAndUpload(testParam, true);
 
                         if(MesClient.configParam.getPrintLabel().equals("是") && lastRet.equals("OK")){
                             PrintUtil.printLabel(testParam.getSn(),testParam.getParam1()+testParam.getParam2(),testParam.getParam3()+testParam.getParam4(),testDate);

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

@@ -1,4 +1,4 @@
-mes.gw=OP330A
+mes.gw=OP320A
 #mes.server_ip=127.0.0.1
 mes.server_ip=192.168.15.99
 mes.tcp_port=3000