chefz 1 week ago
parent
commit
1180fd8cbd

+ 122 - 0
src/com/mes/ui/DataUtil.java

@@ -2,7 +2,9 @@ package com.mes.ui;
 
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
+import com.mes.util.ErrorMsg;
 import com.mes.util.JdbcUtils;
+import com.mes.util.OkngRecordParam;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -15,6 +17,126 @@ import java.util.List;
 
 public class DataUtil {
     public static final Logger log = LoggerFactory.getLogger(DataUtil.class);
+    private static String cachedUniversalSn = null;
+
+    public static JSONObject getMesConfig(String key) {
+        try {
+            String mes_server_ip = MesClient.mes_server_ip;
+            String url = "http://" + mes_server_ip + ":8980/js/a/mes/mesProductRecord/getConfig";
+            String params = "__ajax=json&key=" + key;
+            String result = doPost(url, params);
+            if (result == null || result.trim().isEmpty() || result.equalsIgnoreCase("false")) {
+                return null;
+            }
+            return JSONObject.parseObject(result);
+        } catch (Exception e) {
+            log.error("getMesConfig error, key={}", key, e);
+            return null;
+        }
+    }
+
+    public static boolean isUniversalSn(String sn) {
+        if (sn == null || sn.trim().isEmpty()) {
+            return false;
+        }
+        if (cachedUniversalSn == null) {
+            JSONObject cfg = getMesConfig("mes.universal.sn");
+            if (cfg != null && cfg.get("data") != null) {
+                cachedUniversalSn = cfg.get("data").toString();
+            } else {
+                cachedUniversalSn = "";
+            }
+        }
+        return !cachedUniversalSn.isEmpty() && cachedUniversalSn.contains(sn.trim());
+    }
+
+    public static void refreshUniversalSnCache() {
+        cachedUniversalSn = null;
+    }
+
+    private static boolean oprnoMatches(String recordOprno, String gw) {
+        if (recordOprno == null || gw == null) {
+            return false;
+        }
+        recordOprno = recordOprno.trim();
+        gw = gw.trim();
+        if (recordOprno.equals(gw)) {
+            return true;
+        }
+        if (gw.length() == 5 && recordOprno.startsWith(gw)) {
+            return true;
+        }
+        if (recordOprno.length() == 5 && gw.startsWith(recordOprno)) {
+            return true;
+        }
+        return recordOprno.contains(gw) || gw.contains(recordOprno);
+    }
+
+    public static String getStationResultFromMes(String sn, String oprno) {
+        if (sn == null || sn.trim().isEmpty() || oprno == null || oprno.trim().isEmpty()) {
+            return null;
+        }
+        WorkRecordResp resp = getWorkRecordList(oprno, sn.trim(), 1, 20);
+        if (!resp.isResult() || resp.getList() == null) {
+            return null;
+        }
+        String targetSn = sn.trim();
+        for (WorkRecordData data : resp.getList()) {
+            if (data.getSn() == null || !targetSn.equals(data.getSn().trim())) {
+                continue;
+            }
+            if (!oprnoMatches(data.getOprno(), oprno)) {
+                continue;
+            }
+            String content = data.getContent();
+            if ("OK".equals(content) || "NG".equals(content)) {
+                return content;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 综合 pccheck + 已加工校验。返回 null 表示可加工;非 null 为拒绝原因。
+     */
+    public static String resolveQualityCheckMessage(String sn, String user) {
+        JSONObject retObj = checkQuality(sn, user);
+        if (retObj == null) {
+            return "请求失败,请重试";
+        }
+        Object resultObj = retObj.get("result");
+        String checkCode = retObj.getString("data");
+        if (resultObj == null) {
+            return "请求失败,请重试";
+        }
+        if (!"true".equalsIgnoreCase(resultObj.toString())) {
+            String msg = retObj.getString("message");
+            if (msg != null && !msg.trim().isEmpty()) {
+                return msg;
+            }
+            if (checkCode != null && !checkCode.isEmpty()) {
+                String lx = MesClient.mes_gw != null && MesClient.mes_gw.length() >= 5
+                        ? MesClient.mes_gw.substring(2, 5) : "";
+                return ErrorMsg.getErrorMsg(checkCode, lx);
+            }
+            return "该工件本工位不可加工";
+        }
+
+        if (!isUniversalSn(sn)) {
+            String mesResult = getStationResultFromMes(sn, MesClient.mes_gw);
+            if ("OK".equals(mesResult) || "NG".equals(mesResult)) {
+                return "该工件本工位已加工,结果:" + mesResult;
+            }
+            OkngRecordParam local = JdbcUtils.findLatestOkngRecord(MesClient.mes_gw, sn.trim());
+            if (local != null && ("OK".equals(local.getResult()) || "NG".equals(local.getResult()))) {
+                if (!"1".equals(local.getUploadStatus())) {
+                    return "该工件结果正在上传,请稍候";
+                }
+                return "该工件本工位已加工,结果:" + local.getResult();
+            }
+        }
+        return null;
+    }
 
     public static JSONObject checkQuality(String sn, String user){
         try{

+ 2 - 9
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.OkngUploadUtil;
 
 import javax.swing.*;
 import java.awt.*;
@@ -183,15 +184,7 @@ public class LoginFarme extends JFrame {
                     MesClient.user_menu.setText(user_id);
                     MesClient.welcomeWin.setVisible(false);
                     MesClient.mesClientFrame.setVisible(true);
-
-					// 工作记录
-					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;
-						MesClient.jfxPanel = new MesWebView(url);
-						MesClient.jfxPanel.setSize(990, 550);
-						MesClient.jfxPanel.setBounds(0, 0, 990, 550);
-						MesClient.indexPanelB.add(MesClient.jfxPanel);
-					}
+                    OkngUploadUtil.startRetryTimer();
 
 					// 开班点检
 					if (MesClient.jfxPanel2 == null) {

+ 77 - 65
src/com/mes/ui/MesClient.java

@@ -6,6 +6,8 @@ import com.mes.component.MesWebView;
 import com.mes.util.DateLocalUtils;
 import com.mes.util.HttpUtils;
 import com.mes.util.JdbcUtils;
+import com.mes.util.OkngRecordParam;
+import com.mes.util.OkngUploadUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import javax.swing.*;
@@ -79,7 +81,8 @@ public class MesClient extends JFrame {
 
     public static JFrame welcomeWin;
 
-    public static JPanel indexPanelB; // 已弃用,改为使用 WorkRecordPanel
+    public static JPanel indexPanelB;
+    public static WorkRecordPanel workRecordPanel;
     public static MesWebView jfxPanel = null;
     public static JPanel indexPanelC;
     public static MesWebView jfxPanel2 = null;
@@ -409,6 +412,15 @@ public class MesClient extends JFrame {
 //        shiftUserCheck();
     }
 
+    private static void disableOkNgButtons() {
+        if (finish_ok_bt != null) {
+            finish_ok_bt.setEnabled(false);
+        }
+        if (finish_ng_bt != null) {
+            finish_ng_bt.setEnabled(false);
+        }
+    }
+
     public static int userLoginHours;//用户登录所处小时
     //换班用户信息检查
     private static  void  shiftUserCheck() {
@@ -490,33 +502,22 @@ public class MesClient extends JFrame {
             //刷新界面
             mesClientFrame.repaint();
 
-            // 查询工件质量
-            JSONObject retObj = DataUtil.checkQuality(scanBarcode,user20);
-            if(retObj == null){
+            // 查询工件质量(含已加工校验,万能码除外)
+            String qualityMsg = DataUtil.resolveQualityCheckMessage(scanBarcode, user20);
+            if (qualityMsg != null) {
                 MesClient.check_quality_result = false;
                 MesClient.work_status = 0;
-                MesClient.setMenuStatus("请求失败,请重试",-1);
+                disableOkNgButtons();
+                MesClient.setMenuStatus(qualityMsg, -1);
                 return;
             }
-            if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                MesClient.status_menu.setForeground(Color.GREEN);
-                MesClient.check_quality_result = true;//质量合格,可以绑定加工
-                MesClient.status_menu.setText("该工件可以加工");
-                MesClient.work_status = 1;
-                MesClient.f_scan_data_bt_1.setEnabled(false);
-                MesClient.finish_ok_bt.setEnabled(true);
-                MesClient.finish_ng_bt.setEnabled(true);
-            }else{
-                MesClient.check_quality_result = false;
-                MesClient.work_status = 0;
-                if(retObj.get("result")==null){
-                    MesClient.setMenuStatus("请求失败,请重试",-1);
-                }else{
-                    if(retObj.get("result").toString().equalsIgnoreCase("false")){
-                        MesClient.setMenuStatus(retObj.getString("message"),-1);
-                    }
-                }
-            }
+            MesClient.status_menu.setForeground(Color.GREEN);
+            MesClient.check_quality_result = true;
+            MesClient.status_menu.setText("该工件可以加工");
+            MesClient.work_status = 1;
+            MesClient.f_scan_data_bt_1.setEnabled(false);
+            MesClient.finish_ok_bt.setEnabled(true);
+            MesClient.finish_ng_bt.setEnabled(true);
         }else {
             MesClient.setMenuStatus("请扫工件码,请重试",-1);
 //            JOptionPane.showMessageDialog(mesClientFrame,"请扫工件码","提示窗口", JOptionPane.INFORMATION_MESSAGE);
@@ -715,32 +716,36 @@ public class MesClient extends JFrame {
         finish_ok_bt.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent e) {
                 if(work_status == 1 && check_quality_result){
+                    finish_ok_bt.setEnabled(false);
+                    finish_ng_bt.setEnabled(false);
                     getUser();
                     String sn = MesClient.product_sn.getText().trim();
                     if(sn.isEmpty()){
                         MesClient.setMenuStatus("工件码为空,请重试",-1);
+                        finish_ok_bt.setEnabled(true);
+                        finish_ng_bt.setEnabled(true);
                         return;
                     }
-
-                    JSONObject retObj = DataUtil.sendQuality(sn,"OK",user20);
-                    if(retObj == null){
-                        MesClient.setMenuStatus("请求失败,请重试",-1);
+                    String dupMsg = DataUtil.resolveQualityCheckMessage(sn, user20);
+                    if (dupMsg != null) {
+                        MesClient.setMenuStatus(dupMsg, -1);
+                        MesClient.work_status = 0;
+                        MesClient.check_quality_result = false;
+                        MesClient.f_scan_data_bt_1.setEnabled(true);
                         return;
                     }
-                    System.out.println(retObj);
-                    if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                        MesClient.resetScanA();
-                        MesClient.setMenuStatus("结果提交成功,请扫下一件",0);
-                        MesClient.scan_type = 1;
-                        MesClient.scanBarcode();
-                    }else{
-                        if(retObj.get("result")==null){
-                            MesClient.setMenuStatus("请求失败,请重试",-1);
-                        }else{
-                            if(retObj.get("result").toString().equalsIgnoreCase("false")){
-                                MesClient.setMenuStatus(retObj.getString("message"),-1);
-                            }
-                        }
+
+                    OkngRecordParam record = new OkngRecordParam();
+                    record.setSn(sn);
+                    record.setOprno(MesClient.mes_gw);
+                    record.setLineSn(MesClient.mes_line_sn);
+                    record.setUcode(user20);
+                    record.setResult("OK");
+                    record.setCreateTime(DateLocalUtils.getCurrentTime());
+                    if (!OkngUploadUtil.saveAndUpload(record, true)) {
+                        MesClient.setMenuStatus("本地记录保存失败,请重试", -1);
+                        finish_ok_bt.setEnabled(true);
+                        finish_ng_bt.setEnabled(true);
                     }
                 }
             }
@@ -756,31 +761,36 @@ public class MesClient extends JFrame {
         finish_ng_bt.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent e) {
                 if(work_status == 1 && check_quality_result){
+                    finish_ok_bt.setEnabled(false);
+                    finish_ng_bt.setEnabled(false);
                     getUser();
                     String sn = MesClient.product_sn.getText().trim();
                     if(sn.isEmpty()){
                         MesClient.setMenuStatus("工件码为空,请重试",-1);
+                        finish_ok_bt.setEnabled(true);
+                        finish_ng_bt.setEnabled(true);
                         return;
                     }
-
-                    JSONObject retObj = DataUtil.sendQuality(sn,"NG",user20);
-                    if(retObj == null){
-                        MesClient.setMenuStatus("请求失败,请重试",-1);
+                    String dupMsg = DataUtil.resolveQualityCheckMessage(sn, user20);
+                    if (dupMsg != null) {
+                        MesClient.setMenuStatus(dupMsg, -1);
+                        MesClient.work_status = 0;
+                        MesClient.check_quality_result = false;
+                        MesClient.f_scan_data_bt_1.setEnabled(true);
                         return;
                     }
-                    if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                        MesClient.resetScanA();
-                        MesClient.setMenuStatus("结果提交成功,请扫下一件",0);
-                        MesClient.scan_type = 1;
-                        MesClient.scanBarcode();
-                    }else{
-                        if(retObj.get("result")==null){
-                            MesClient.setMenuStatus("请求失败,请重试",-1);
-                        }else{
-                            if(retObj.get("result").toString().equalsIgnoreCase("false")){
-                                MesClient.setMenuStatus(retObj.getString("message"),-1);
-                            }
-                        }
+
+                    OkngRecordParam record = new OkngRecordParam();
+                    record.setSn(sn);
+                    record.setOprno(MesClient.mes_gw);
+                    record.setLineSn(MesClient.mes_line_sn);
+                    record.setUcode(user20);
+                    record.setResult("NG");
+                    record.setCreateTime(DateLocalUtils.getCurrentTime());
+                    if (!OkngUploadUtil.saveAndUpload(record, true)) {
+                        MesClient.setMenuStatus("本地记录保存失败,请重试", -1);
+                        finish_ok_bt.setEnabled(true);
+                        finish_ng_bt.setEnabled(true);
                     }
                 }
             }
@@ -803,12 +813,10 @@ public class MesClient extends JFrame {
         tabbedPane.addTab("开班点检", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), searchScrollPaneDj, null);
 
 
-        // ========== 工作记录 Tab(改造:当前工位Tab + 所有工位Tab) ==========
-        // 旧代码已注释:
-         indexPanelB = new JPanel();
-         searchScrollPane = new JScrollPane(indexPanelB);
-         indexPanelB.setLayout(null);
-         tabbedPane.addTab("工作记录", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), searchScrollPane, null);
+        // ========== 工作记录 Tab(本地记录 + 分页查询) ==========
+        workRecordPanel = new WorkRecordPanel(mes_gw, mes_line_sn);
+        tabbedPane.addTab("工作记录", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), workRecordPanel, null);
+        int workRecordTabIndex = tabbedPane.getTabCount() - 1;
 
          // 创建“软件设置”页面,并加入主Tab页签
          JPanel softwareSettingPanel = buildSoftwareSettingPanel();
@@ -853,6 +861,10 @@ public class MesClient extends JFrame {
                 int selectedIndex = tabbedPane.getSelectedIndex();
                 System.out.println("selectedIndex:"+selectedIndex);
 
+                if (selectedIndex == workRecordTabIndex && workRecordPanel != null) {
+                    workRecordPanel.refreshData();
+                }
+
                 if (softwareTabReverting) {
                     return;
                 }

+ 9 - 0
src/com/mes/ui/WorkRecordData.java

@@ -10,6 +10,15 @@ public class WorkRecordData {
     private String updateBy;    // 作业人员
     private String updateDate;  // 日期
     private String content;     // 工位结果
+    private String uploadStatus; // 上传状态
+
+    public String getUploadStatus() {
+        return uploadStatus;
+    }
+
+    public void setUploadStatus(String uploadStatus) {
+        this.uploadStatus = uploadStatus;
+    }
 
     public String getId() {
         return id;

+ 7 - 3
src/com/mes/ui/WorkRecordPanel.java

@@ -1,5 +1,6 @@
 package com.mes.ui;
 
+import com.mes.util.JdbcUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -21,7 +22,7 @@ public class WorkRecordPanel extends JPanel {
     private static final Logger log = LoggerFactory.getLogger(WorkRecordPanel.class);
 
     // 表格列名
-    private static final String[] COLUMN_NAMES = {"工件码", "工位号", "工位结果", "作业人员", "日期"};
+    private static final String[] COLUMN_NAMES = {"工件码", "工位号", "工位结果", "作业人员", "日期", "上传状态"};
 
     // 分页配置
     private static final int PAGE_SIZE = 20;
@@ -245,6 +246,8 @@ public class WorkRecordPanel extends JPanel {
         // 日期
         column = table.getColumnModel().getColumn(4);
         column.setPreferredWidth(150);
+        column = table.getColumnModel().getColumn(5);
+        column.setPreferredWidth(80);
     }
 
     /**
@@ -304,7 +307,7 @@ public class WorkRecordPanel extends JPanel {
                 });
 
                 // 调用接口查询数据
-                return DataUtil.getWorkRecordList(finalSearchOprno, finalSearchSn, pageNo, PAGE_SIZE);
+                return JdbcUtils.queryLocalWorkRecords(finalSearchOprno, finalSearchSn, pageNo, PAGE_SIZE);
             }
 
             @Override
@@ -373,7 +376,8 @@ public class WorkRecordPanel extends JPanel {
                         data.getOprno(),
                         data.getContent(),
                         data.getUpdateBy(),
-                        data.getUpdateDate()
+                        data.getUpdateDate(),
+                        data.getUploadStatus() != null ? data.getUploadStatus() : "待上传"
                 };
                 tableModel.addRow(row);
             }

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

@@ -3,12 +3,17 @@ package com.mes.util;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.mes.ui.WorkRecordData;
+import com.mes.ui.WorkRecordResp;
+
+import java.util.ArrayList;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.List;
 
 public class JdbcUtils {
 	public static final Logger log =  LoggerFactory.getLogger(JdbcUtils.class);
@@ -67,6 +72,28 @@ public class JdbcUtils {
 				")";
 		statement.executeUpdate(opIpConfig);
 
+		String okngRecord = "CREATE TABLE if not exists okng_record(\n" +
+				"   id INTEGER PRIMARY KEY AUTOINCREMENT,\n" +
+				"   sn VARCHAR(48),\n" +
+				"   oprno VARCHAR(48),\n" +
+				"   line_sn VARCHAR(48),\n" +
+				"   ucode VARCHAR(48),\n" +
+				"   result VARCHAR(10),\n" +
+				"   create_time DATETIME,\n" +
+				"   upload_status VARCHAR(1) DEFAULT '0',\n" +
+				"   upload_retry INT DEFAULT 0\n" +
+				")";
+		statement.executeUpdate(okngRecord);
+
+		try {
+			statement.executeUpdate("ALTER TABLE okng_record ADD COLUMN upload_status VARCHAR(1) DEFAULT '0'");
+		} catch (SQLException ignored) {
+		}
+		try {
+			statement.executeUpdate("ALTER TABLE okng_record ADD COLUMN upload_retry INT DEFAULT 0");
+		} catch (SQLException ignored) {
+		}
+
         statement.close();
     }
 
@@ -257,6 +284,217 @@ public class JdbcUtils {
 		return ret;
 	}
 
+	private static String safe(String value) {
+		return value == null ? "" : value.replace("'", "''");
+	}
+
+	public static int insertOkngRecordReturnId(OkngRecordParam param) {
+		try {
+			ensureConnection();
+			if (conn == null || conn.isClosed()) {
+				return -1;
+			}
+			Statement statement = conn.createStatement();
+			String insertSQL = "INSERT INTO okng_record (sn, oprno, line_sn, ucode, result, create_time, upload_status, upload_retry)" +
+					"VALUES('" + safe(param.getSn()) + "','" + safe(param.getOprno()) + "','" + safe(param.getLineSn()) + "','" +
+					safe(param.getUcode()) + "','" + safe(param.getResult()) + "','" + safe(param.getCreateTime()) + "','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();
+			return id;
+		} catch (SQLException e) {
+			log.error("插入okng_record失败: {}", e.getMessage());
+		}
+		return -1;
+	}
+
+	public static void updateOkngUploadStatus(int id, String status) {
+		try {
+			ensureConnection();
+			if (conn == null || conn.isClosed()) {
+				return;
+			}
+			Statement statement = conn.createStatement();
+			statement.executeUpdate("UPDATE okng_record SET upload_status='" + status + "' WHERE id=" + id);
+			statement.close();
+		} catch (SQLException e) {
+			log.error("更新okng upload_status失败: {}", e.getMessage());
+		}
+	}
+
+	public static void incrementOkngUploadRetry(int id) {
+		try {
+			ensureConnection();
+			if (conn == null || conn.isClosed()) {
+				return;
+			}
+			Statement statement = conn.createStatement();
+			statement.executeUpdate("UPDATE okng_record SET upload_retry=IFNULL(upload_retry,0)+1 WHERE id=" + id);
+			statement.close();
+		} catch (SQLException e) {
+			log.error("更新okng upload_retry失败: {}", e.getMessage());
+		}
+	}
+
+	public static int getOkngUploadRetry(int id) {
+		try {
+			ensureConnection();
+			if (conn == null || conn.isClosed()) {
+				return 0;
+			}
+			Statement statement = conn.createStatement();
+			ResultSet rs = statement.executeQuery("SELECT upload_retry FROM okng_record WHERE id=" + id);
+			if (rs.next()) {
+				return rs.getInt(1);
+			}
+			rs.close();
+			statement.close();
+		} catch (SQLException e) {
+			log.error("查询okng upload_retry失败: {}", e.getMessage());
+		}
+		return 0;
+	}
+
+	public static List<OkngRecordParam> getPendingOkngUploadRecords(int limit) {
+		List<OkngRecordParam> list = new ArrayList<>();
+		try {
+			ensureConnection();
+			if (conn == null || conn.isClosed()) {
+				return list;
+			}
+			Statement statement = conn.createStatement();
+			ResultSet rs = statement.executeQuery(
+					"SELECT * FROM okng_record WHERE IFNULL(upload_status,'0') != '1' AND IFNULL(upload_retry,0) < 20 ORDER BY id ASC LIMIT " + limit);
+			while (rs.next()) {
+				list.add(mapOkngRecord(rs));
+			}
+			rs.close();
+			statement.close();
+		} catch (SQLException e) {
+			log.error("查询待上传okng记录失败: {}", e.getMessage());
+		}
+		return list;
+	}
+
+	private static OkngRecordParam mapOkngRecord(ResultSet rs) throws SQLException {
+		OkngRecordParam param = new OkngRecordParam();
+		param.setId(rs.getInt("id"));
+		param.setSn(rs.getString("sn"));
+		param.setOprno(rs.getString("oprno"));
+		param.setLineSn(rs.getString("line_sn"));
+		param.setUcode(rs.getString("ucode"));
+		param.setResult(rs.getString("result"));
+		param.setCreateTime(rs.getString("create_time"));
+		try {
+			param.setUploadStatus(rs.getString("upload_status"));
+		} catch (SQLException ignored) {
+			param.setUploadStatus("0");
+		}
+		return param;
+	}
+
+	public static OkngRecordParam findLatestOkngRecord(String oprno, String sn) {
+		if (oprno == null || sn == null || sn.trim().isEmpty()) {
+			return null;
+		}
+		try {
+			ensureConnection();
+			if (conn == null || conn.isClosed()) {
+				return null;
+			}
+			Statement statement = conn.createStatement();
+			ResultSet rs = statement.executeQuery(
+					"SELECT * FROM okng_record WHERE oprno='" + safe(oprno.trim()) +
+							"' AND sn='" + safe(sn.trim()) + "' ORDER BY id DESC LIMIT 1");
+			OkngRecordParam param = null;
+			if (rs.next()) {
+				param = mapOkngRecord(rs);
+			}
+			rs.close();
+			statement.close();
+			return param;
+		} catch (SQLException e) {
+			log.error("查询okng_record失败: {}", e.getMessage());
+		}
+		return null;
+	}
+
+	public static WorkRecordResp queryLocalWorkRecords(String oprno, String sn, int pageNo, int pageSize) {
+		WorkRecordResp resp = new WorkRecordResp();
+		try {
+			ensureConnection();
+			if (conn == null || conn.isClosed()) {
+				resp.setResult(false);
+				resp.setMessage("数据库未连接");
+				return resp;
+			}
+			StringBuilder where = new StringBuilder(" WHERE 1=1");
+			if (oprno != null && !oprno.isEmpty()) {
+				where.append(" AND oprno='").append(safe(oprno)).append("'");
+			}
+			if (sn != null && !sn.isEmpty()) {
+				where.append(" AND sn LIKE '%").append(safe(sn)).append("%'");
+			}
+			String countSql = "SELECT count(*) as total FROM okng_record" + where;
+			String dataSql = "SELECT * FROM okng_record" + where +
+					" ORDER BY id DESC LIMIT " + pageSize + " OFFSET " + ((pageNo - 1) * pageSize);
+
+			Statement statement = conn.createStatement();
+			ResultSet countRs = statement.executeQuery(countSql);
+			long total = 0;
+			if (countRs.next()) {
+				total = countRs.getLong("total");
+			}
+			countRs.close();
+
+			ResultSet rs = statement.executeQuery(dataSql);
+			List<WorkRecordData> list = new ArrayList<>();
+			while (rs.next()) {
+				WorkRecordData data = new WorkRecordData();
+				data.setId(String.valueOf(rs.getInt("id")));
+				data.setSn(rs.getString("sn"));
+				data.setOprno(rs.getString("oprno"));
+				data.setUpdateBy(rs.getString("ucode"));
+				data.setUpdateDate(rs.getString("create_time"));
+				data.setContent(rs.getString("result"));
+				try {
+					data.setUploadStatus(formatUploadStatus(rs.getString("upload_status")));
+				} catch (SQLException ignored) {
+					data.setUploadStatus("待上传");
+				}
+				list.add(data);
+			}
+			rs.close();
+			statement.close();
+
+			resp.setResult(true);
+			resp.setCount(total);
+			resp.setPageNo(pageNo);
+			resp.setPageSize(pageSize);
+			resp.setList(list);
+		} catch (SQLException e) {
+			log.error("查询本地工作记录失败: {}", e.getMessage());
+			resp.setResult(false);
+			resp.setMessage(e.getMessage());
+		}
+		return resp;
+	}
+
+	private static String formatUploadStatus(String status) {
+		if ("1".equals(status)) {
+			return "已上传";
+		}
+		if ("2".equals(status)) {
+			return "待重传";
+		}
+		return "待上传";
+	}
+
     
     public static void close(){
         try {

+ 77 - 0
src/com/mes/util/OkngRecordParam.java

@@ -0,0 +1,77 @@
+package com.mes.util;
+
+public class OkngRecordParam {
+
+    private Integer id;
+    private String sn;
+    private String oprno;
+    private String lineSn;
+    private String ucode;
+    private String result;
+    private String createTime;
+    private String uploadStatus;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getSn() {
+        return sn;
+    }
+
+    public void setSn(String sn) {
+        this.sn = sn;
+    }
+
+    public String getOprno() {
+        return oprno;
+    }
+
+    public void setOprno(String oprno) {
+        this.oprno = oprno;
+    }
+
+    public String getLineSn() {
+        return lineSn;
+    }
+
+    public void setLineSn(String lineSn) {
+        this.lineSn = lineSn;
+    }
+
+    public String getUcode() {
+        return ucode;
+    }
+
+    public void setUcode(String ucode) {
+        this.ucode = ucode;
+    }
+
+    public String getResult() {
+        return result;
+    }
+
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getUploadStatus() {
+        return uploadStatus;
+    }
+
+    public void setUploadStatus(String uploadStatus) {
+        this.uploadStatus = uploadStatus;
+    }
+}

+ 151 - 0
src/com/mes/util/OkngUploadUtil.java

@@ -0,0 +1,151 @@
+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;
+
+/**
+ * OK/NG 结果本地保存 + 后台异步上传 MES + 失败自动重传
+ */
+public class OkngUploadUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(OkngUploadUtil.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, "OkngUploadWorker");
+        t.setDaemon(true);
+        return t;
+    });
+
+    public static String formatUploadStatus(String status) {
+        if ("1".equals(status)) {
+            return "已上传";
+        }
+        if ("2".equals(status)) {
+            return "待重传";
+        }
+        return "待上传";
+    }
+
+    /**
+     * 保存本地记录并提交后台上传。返回 true 表示本地保存成功,主流程可继续。
+     */
+    public static boolean saveAndUpload(OkngRecordParam param, boolean resetOnSuccess) {
+        int recordId = JdbcUtils.insertOkngRecordReturnId(param);
+        if (recordId <= 0) {
+            runOnEdt(() -> MesClient.setMenuStatus("本地记录保存失败", -1));
+            return false;
+        }
+        param.setId(recordId);
+        refreshWorkRecord();
+
+        if (resetOnSuccess) {
+            runOnEdt(() -> {
+                MesClient.finish_ok_bt.setEnabled(false);
+                MesClient.finish_ng_bt.setEnabled(false);
+                MesClient.f_scan_data_bt_1.setEnabled(false);
+                MesClient.setMenuStatus("结果已保存,后台上传中...", 0);
+            });
+        }
+
+        submitAsyncUpload(param);
+        return true;
+    }
+
+    private static void submitAsyncUpload(OkngRecordParam param) {
+        if (param == null || param.getId() == null || param.getId() <= 0) {
+            return;
+        }
+        uploadExecutor.execute(() -> doUpload(param));
+    }
+
+    private static void doUpload(OkngRecordParam param) {
+        try {
+            JSONObject retObj = DataUtil.sendQuality(param.getSn(), param.getResult(), param.getUcode());
+            if (isUploadSuccess(retObj)) {
+                JdbcUtils.updateOkngUploadStatus(param.getId(), "1");
+                runOnEdt(() -> {
+                    MesClient.setMenuStatus("结果上传成功", 0);
+                    MesClient.resetScanA();
+                    MesClient.scan_type = 1;
+                    MesClient.scanBarcode();
+                });
+                refreshWorkRecord();
+                log.info("OKNG结果上传成功, recordId={}", param.getId());
+                return;
+            }
+            markUploadFailed(param.getId());
+            log.warn("OKNG结果上传失败, recordId={}, 将后台重试", param.getId());
+        } catch (Exception e) {
+            log.error("OKNG结果上传异常, recordId=" + param.getId(), e);
+            markUploadFailed(param.getId());
+        }
+    }
+
+    private static boolean isUploadSuccess(JSONObject retObj) {
+        return retObj != null && retObj.get("result") != null
+                && retObj.get("result").toString().equalsIgnoreCase("true");
+    }
+
+    private static void markUploadFailed(int recordId) {
+        JdbcUtils.updateOkngUploadStatus(recordId, "2");
+        JdbcUtils.incrementOkngUploadRetry(recordId);
+        runOnEdt(() -> MesClient.setMenuStatus("结果上传失败,后台自动重试中", -1));
+        refreshWorkRecord();
+    }
+
+    public static void startRetryTimer() {
+        if (retryTimer != null) {
+            retryTimer.cancel();
+        }
+        retryTimer = new Timer("OkngUploadRetry", true);
+        retryTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                retryPendingUploads();
+            }
+        }, RETRY_INTERVAL_MS, RETRY_INTERVAL_MS);
+    }
+
+    public static void retryPendingUploads() {
+        List<OkngRecordParam> pending = JdbcUtils.getPendingOkngUploadRecords(10);
+        if (pending == null || pending.isEmpty()) {
+            return;
+        }
+        for (OkngRecordParam param : pending) {
+            int retry = JdbcUtils.getOkngUploadRetry(param.getId());
+            if (retry >= MAX_RETRY) {
+                continue;
+            }
+            submitAsyncUpload(param);
+        }
+    }
+
+    private static void refreshWorkRecord() {
+        runOnEdt(() -> {
+            if (MesClient.workRecordPanel != null) {
+                MesClient.workRecordPanel.refreshData();
+            }
+        });
+    }
+
+    private static void runOnEdt(Runnable action) {
+        if (SwingUtilities.isEventDispatchThread()) {
+            action.run();
+        } else {
+            SwingUtilities.invokeLater(action);
+        }
+    }
+}