package com.mes.ui; import com.alibaba.fastjson2.JSONObject; import com.mes.component.MesWebView; import com.mes.device.DeviceStateReporter; import com.mes.util.ConfigUtils; import com.mes.util.JdbcUtils; import com.mes.util.StationConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; import static com.mes.ui.WorkStationPanel.*; public class MultiStationClient extends JFrame { private static final Logger log = LoggerFactory.getLogger(MultiStationClient.class); private static final int DEFAULT_DEVICE_HEARTBEAT_MINUTES = 5; private static int deviceHeartbeatMinutes = DEFAULT_DEVICE_HEARTBEAT_MINUTES; private List stationPanels = new ArrayList<>(); // 当前工作站配置中的所有工位,用于开机、心跳、停机逐个上报。 private List stationConfigs = new ArrayList<>(); private Timer deviceReportTimer; // 避免窗口关闭和退出菜单连续触发时重复上报停机。 private boolean deviceStopReported = true; private JButton userMenu; private JLabel statusLabel; private JLabel heartBeatLabel; public static JFrame welcomeWin; public static MultiStationClient mainFrame; public static JScrollPane searchScrollPaneWork; // 已弃用,改为使用WebView public static JScrollPane searchScrollPaneCheck; public static JPanel indexPaneWork; // 已弃用,改为使用WebView public static JPanel indexPaneCheck; public static MesWebView workRecordPanel = null; public static MesWebView checkPanel = null; public static void main(String[] args) { /* if (LockUtil.getInstance().isAppActive()) { JOptionPane.showMessageDialog(null, "已有一个程序在运行,程序退出"); return; }*/ EventQueue.invokeLater(() -> { try { MesClient.readProperty(); Properties props = ConfigUtils.loadProperties(); // 设备运行心跳间隔可通过 mes.device.heartbeat.minutes 配置,未配置默认 5 分钟。 deviceHeartbeatMinutes = parseDeviceHeartbeatMinutes(props); log.info("client config={}, stations={}, proxyEnabled={}, proxyBaseUrl={}", ConfigUtils.getConfigPath(), props.getProperty("mes.stations", ""), props.getProperty("plc.proxy.enabled", "false"), props.getProperty("plc.proxy.base_url", "")); System.out.println("client config=" + ConfigUtils.getConfigPath() + ", stations=" + props.getProperty("mes.stations", "") + ", proxy=" + props.getProperty("plc.proxy.base_url", "")); JdbcUtils.getConn(); welcomeWin = new LoginFarme(); welcomeWin.setVisible(true); MultiStationClient frame = new MultiStationClient(); frame.setVisible(false); } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "启动失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } }); } public MultiStationClient() { mainFrame = this; initUI(); } private void initUI() { setTitle("MES系统客户端 - 多工位模式"); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { int choice = JOptionPane.showConfirmDialog(null, "确定要关闭窗口吗?", "关闭确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (choice == JOptionPane.YES_OPTION) { // 关闭窗口视为设备停机,先同步上报所有工位再退出。 reportDeviceStopped(); dispose(); System.exit(0); } } }); setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/bg/logo.png"))); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu fileMenu = new JMenu("用户"); fileMenu.setIcon(new ImageIcon(getClass().getResource("/bg/user.png"))); fileMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20)); menuBar.add(fileMenu); JMenuItem exitMenuItem = new JMenuItem("退出"); exitMenuItem.setIcon(new ImageIcon(getClass().getResource("/bg/logoff.png"))); exitMenuItem.setFont(new Font("微软雅黑", Font.PLAIN, 18)); exitMenuItem.addActionListener(e -> { int choice = JOptionPane.showConfirmDialog(null, "确定要退出吗?", "退出确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (choice == JOptionPane.YES_OPTION) { // 菜单退出同样视为设备停机。 reportDeviceStopped(); System.exit(0); } }); fileMenu.add(exitMenuItem); JMenu settingMenu = new JMenu("设置"); settingMenu.setIcon(new ImageIcon(getClass().getResource("/bg/menu_setting.png"))); settingMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20)); menuBar.add(settingMenu); JToolBar toolBar = new JToolBar(); toolBar.setFloatable(false); JLabel statusLabelTitle = new JLabel("状态:"); statusLabelTitle.setFont(new Font("微软雅黑", Font.PLAIN, 24)); toolBar.add(statusLabelTitle); statusLabel = new JLabel("已连接MES服务器"); statusLabel.setForeground(Color.GREEN); statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 24)); statusLabel.setBackground(Color.BLACK); toolBar.add(statusLabel); toolBar.add(Box.createHorizontalStrut(20)); JLabel heartBeatTitle = new JLabel("心跳:"); heartBeatTitle.setFont(new Font("微软雅黑", Font.PLAIN, 24)); toolBar.add(heartBeatTitle); heartBeatLabel = new JLabel(new ImageIcon(getClass().getResource("/bg/green_dot.png"))); toolBar.add(heartBeatLabel); toolBar.add(Box.createHorizontalStrut(20)); JLabel userTitle = new JLabel("登录用户:"); userTitle.setFont(new Font("微软雅黑", Font.PLAIN, 24)); toolBar.add(userTitle); userMenu = new JButton("未知用户"); userMenu.setForeground(Color.GREEN); userMenu.setFont(new Font("微软雅黑", Font.PLAIN, 24)); userMenu.setBackground(Color.BLACK); toolBar.add(userMenu); add(toolBar, BorderLayout.NORTH); JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.setFont(new Font("微软雅黑", Font.PLAIN, 20)); try { List stations = StationConfig.loadAllStations(); // 保存配置中的全部工位,状态上报时按列表逐个提交。 stationConfigs = new ArrayList<>(stations); if (stations.isEmpty()) { JOptionPane.showMessageDialog(this, "未配置任何工位", "错误", JOptionPane.ERROR_MESSAGE); return; } JPanel mainPanel = new JPanel(new GridLayout(2, 3, 1, 1)); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); mainPanel.setBackground(Color.WHITE); for (StationConfig config : stations) { WorkStationPanel panel = new WorkStationPanel(config); stationPanels.add(panel); mainPanel.add(panel); addResetMenuItem(settingMenu, panel, config); } tabbedPane.addTab("工作面板",new ImageIcon(MesClient.class.getResource("/bg/a_side.png")), new JScrollPane(mainPanel)); indexPaneCheck = new JPanel(); searchScrollPaneCheck = new JScrollPane(indexPaneCheck); indexPaneCheck.setLayout(null); // tabbedPane.addTab("开班点检", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")),searchScrollPaneCheck); indexPaneWork = new JPanel(); searchScrollPaneWork = new JScrollPane(indexPaneWork); // tabbedPane.addTab("生产记录",new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")),searchScrollPaneWork); add(tabbedPane, BorderLayout.CENTER); } catch (IOException e) { JOptionPane.showMessageDialog(this, "加载工位配置失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } pack(); setExtendedState(JFrame.MAXIMIZED_BOTH); } public void setUser(String user) { userMenu.setText(user); if (stationPanels != null) { for (WorkStationPanel panel : stationPanels) { panel.setCurrentUser(user); } } } public synchronized void reportDeviceRunning() { deviceStopReported = false; // 登录成功后上报开机/运行,当前工作站内每个工位都提交一次。 DeviceStateReporter.reportStationsAsync(stationConfigs, DeviceStateReporter.STATE_RUNNING, "工位登录,开始运行"); startDeviceReportTimer(); log.info("device running report submitted for {} stations", stationConfigs.size()); } public synchronized void reportDeviceStopped() { stopDeviceReportTimer(); if (deviceStopReported) { return; } // 退出程序前同步上报停机,降低异步线程未执行完就退出的风险。 int successCount = DeviceStateReporter.reportStationsSync(stationConfigs, DeviceStateReporter.STATE_STOP, "工位退出,停止运行"); deviceStopReported = true; log.info("device stop report submitted for {} stations, success={}", stationConfigs.size(), successCount); } private void startDeviceReportTimer() { stopDeviceReportTimer(); long intervalMs = deviceHeartbeatMinutes * 60L * 1000L; deviceReportTimer = new Timer("device-report-timer", true); deviceReportTimer.schedule(new TimerTask() { @Override public void run() { // 定时补报运行状态,保持 MES 侧设备在线状态。 DeviceStateReporter.reportStationsAsync(stationConfigs, DeviceStateReporter.STATE_RUNNING, "运行心跳"); } }, intervalMs, intervalMs); log.info("device heartbeat timer started, intervalMinutes={}", deviceHeartbeatMinutes); } private void stopDeviceReportTimer() { if (deviceReportTimer != null) { deviceReportTimer.cancel(); deviceReportTimer = null; } } private static int parseDeviceHeartbeatMinutes(Properties props) { try { int minutes = Integer.parseInt(props.getProperty("mes.device.heartbeat.minutes", String.valueOf(DEFAULT_DEVICE_HEARTBEAT_MINUTES)).trim()); return minutes > 0 ? minutes : DEFAULT_DEVICE_HEARTBEAT_MINUTES; } catch (Exception e) { return DEFAULT_DEVICE_HEARTBEAT_MINUTES; } } private void addResetMenuItem(JMenu settingMenu, WorkStationPanel panel, StationConfig config) { JMenuItem resetMenu = new JMenuItem("刷新" + config.getGw() + "工件"); resetMenu.setIcon(new ImageIcon(MesClient.class.getResource("/bg/reset_logo.png"))); resetMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20)); resetMenu.addActionListener(e -> panel.resetScanA()); settingMenu.add(resetMenu); } }