package com.mes.ui; import com.mes.core.*; import com.mes.device.DeviceDriverFactory; import com.mes.device.IDeviceDriver; import com.mes.prod.ProdDataCollector; import com.mes.prod.ProdDataUploader; import com.mes.step.StartWorkStep; import com.mes.step.StepFactory; import com.mes.step.UploadResultStep; import com.mes.step.WaitCompleteStep; import com.mes.tcp.MesTcpClient; import com.mes.tcp.MessageDispatcher; import com.mes.ui.component.WorkstationPanel; import com.mes.ui.component.WorkflowMonitorPanel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.List; import java.util.Timer; /** * 主窗口 - 与OP150版本UI保持一致 */ public class MainFrame extends JFrame { private static final Logger log = LoggerFactory.getLogger(MainFrame.class); // 配置 private StationConfig config; // TCP通信 private MesTcpClient tcpClient; private MessageDispatcher messageDispatcher; // 工位管理 private List contexts; private List engines; private List panels; private Map deviceDrivers; // 步骤工厂 private StepFactory stepFactory; // 过程参数上传器 private ProdDataUploader prodDataUploader; // 当前用户和会话信息 private String currentUser = ""; private String sessionId; private int userAuth; private int loginHour; // UI组件 private JPanel contentPane; private JPanel workPanel; // 工作面板引用,用于编辑模式切换 private JTabbedPane tabbedPane; private JButton heartBeatMenu; private JButton userMenu; private JButton toolbarStatusMenu; // 单工位时的状态显示 private Timer heartBeatDisplayTimer; // WebView组件(参考op150) private com.mes.ui.component.MesWebView shiftCheckWebView; // 开班点检 private com.mes.ui.component.MesWebView workRecordWebView; // 工作记录 // 流程监控面板 private WorkflowMonitorPanel workflowMonitorPanel; private boolean workflowTabVisible = false; // 需要密码解锁的设置菜单项 private JSeparator editModeSeparator; private JCheckBoxMenuItem editModeItem; private JSeparator layoutSeparator; private JMenuItem saveLayoutItem; private JMenuItem exportLayoutItem; private JMenuItem importLayoutItem; // 三击解锁相关 private int userMenuClickCount = 0; private long lastUserMenuClickTime = 0; private static final int TRIPLE_CLICK_INTERVAL = 1500; // 1.5秒内完成3次点击 private static final String WORKFLOW_PASSWORD = "mes123"; public MainFrame() { this(StationConfig.getInstance()); } public MainFrame(StationConfig config) { this.config = config; this.contexts = new ArrayList<>(); this.engines = new ArrayList<>(); this.panels = new ArrayList<>(); this.deviceDrivers = new HashMap<>(); initFrame(); initComponents(); initTcpClient(); initWorkstations(); } /** * 初始化窗口 */ private void initFrame() { String title = "MES系统客户端:" + getStationCodesString(); if (config.getStationName() != null && !config.getStationName().isEmpty()) { title += " - " + config.getStationName(); } setTitle(title); setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/resources/image/bg/logo.png"))); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // 根据工位数量设置窗口初始宽度:双工位宽度是单工位的两倍 int stationCount = config.getStations() != null ? config.getStations().size() : 1; int baseWidth = 1024; int windowWidth = stationCount > 1 ? (int)(baseWidth * 1) : baseWidth; setBounds(0, 0, windowWidth, 768); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onWindowClosing(); } }); } /** * 初始化UI组件 - 参照OP150版本 */ private void initComponents() { // 菜单栏 JMenuBar menuBar = new JMenuBar(); menuBar.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 26)); setJMenuBar(menuBar); // 用户菜单 JMenu fileMenu = new JMenu("用户"); fileMenu.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/user.png")))); fileMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20)); menuBar.add(fileMenu); JMenuItem exitMenuItem = new JMenuItem("退出"); exitMenuItem.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/logoff.png")))); exitMenuItem.setFont(new Font("微软雅黑", Font.PLAIN, 22)); exitMenuItem.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { logoff(); } }); fileMenu.add(exitMenuItem); // 设置菜单 JMenu settingMenu = new JMenu("设置"); settingMenu.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_setting.png")))); settingMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20)); menuBar.add(settingMenu); // 重连MES JMenuItem resetTcpMenu = new JMenuItem("重连MES"); resetTcpMenu.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/reset_logo.png")))); resetTcpMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20)); resetTcpMenu.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { reconnectMes(); } }); settingMenu.add(resetTcpMenu); // 为每个工位添加刷新菜单 for (int i = 0; i < config.getStationCount(); i++) { final int stationIndex = i; StationConfig.StationInfo station = config.getStation(i); if (station != null) { JMenuItem refreshMenu = new JMenuItem("刷新" + station.getCode()); refreshMenu.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/reset_logo.png")))); refreshMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20)); refreshMenu.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { resetStation(stationIndex); } }); settingMenu.add(refreshMenu); } } editModeSeparator = new JSeparator(); editModeSeparator.setVisible(false); settingMenu.add(editModeSeparator); // UI编辑模式(默认隐藏,密码解锁后显示) editModeItem = new JCheckBoxMenuItem("UI编辑模式", false); editModeItem.setFont(new Font("微软雅黑", Font.PLAIN, 20)); editModeItem.addItemListener(e -> { boolean selected = editModeItem.isSelected(); toggleEditMode(selected); }); editModeItem.setVisible(false); settingMenu.add(editModeItem); layoutSeparator = new JSeparator(); layoutSeparator.setVisible(false); settingMenu.add(layoutSeparator); // 保存UI布局(默认隐藏,密码解锁后显示) saveLayoutItem = new JMenuItem("保存UI布局"); saveLayoutItem.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/save_bg.png")))); saveLayoutItem.setFont(new Font("微软雅黑", Font.PLAIN, 20)); saveLayoutItem.addActionListener(e -> saveUILayout()); saveLayoutItem.setVisible(false); settingMenu.add(saveLayoutItem); // 导出布局配置(默认隐藏,密码解锁后显示) exportLayoutItem = new JMenuItem("导出布局配置"); exportLayoutItem.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/download.png")))); exportLayoutItem.setFont(new Font("微软雅黑", Font.PLAIN, 20)); exportLayoutItem.addActionListener(e -> exportUILayout()); exportLayoutItem.setVisible(false); settingMenu.add(exportLayoutItem); // 导入布局配置(默认隐藏,密码解锁后显示) importLayoutItem = new JMenuItem("导入布局配置"); importLayoutItem.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/open_file.png")))); importLayoutItem.setFont(new Font("微软雅黑", Font.PLAIN, 20)); importLayoutItem.addActionListener(e -> importUILayout()); importLayoutItem.setVisible(false); settingMenu.add(importLayoutItem); // 内容面板 contentPane = new JPanel(); contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); setContentPane(contentPane); contentPane.setLayout(new BorderLayout(0, 0)); // 工具栏 - 完全参考OP40 JToolBar toolBar = new JToolBar(); contentPane.add(toolBar, BorderLayout.NORTH); // 状态标签 JLabel statusLabel = new JLabel("状态:"); statusLabel.setHorizontalAlignment(SwingConstants.CENTER); statusLabel.setForeground(Color.BLACK); statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20)); statusLabel.setBackground(Color.LIGHT_GRAY); toolBar.add(statusLabel); toolbarStatusMenu = new JButton("等待加工信号"); toolbarStatusMenu.setForeground(Color.GREEN); toolbarStatusMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20)); toolbarStatusMenu.setBackground(Color.BLACK); toolBar.add(toolbarStatusMenu); toolBar.add(new JLabel(" ")); // 心跳标签 JLabel heartBeatLabel = new JLabel("心跳:"); heartBeatLabel.setHorizontalAlignment(SwingConstants.CENTER); heartBeatLabel.setForeground(Color.BLACK); heartBeatLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20)); heartBeatLabel.setBackground(Color.LIGHT_GRAY); toolBar.add(heartBeatLabel); // 心跳显示(带时间)- 完全参考OP40 heartBeatMenu = new JButton(""); heartBeatMenu.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/green_dot.png")))); heartBeatMenu.setForeground(Color.GREEN); heartBeatMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20)); heartBeatMenu.setBackground(Color.BLACK); toolBar.add(heartBeatMenu); toolBar.add(new JLabel(" ")); // 用户标签 JLabel userLabel = new JLabel("登录用户:"); userLabel.setHorizontalAlignment(SwingConstants.CENTER); userLabel.setForeground(Color.BLACK); userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20)); userLabel.setBackground(Color.LIGHT_GRAY); toolBar.add(userLabel); userMenu = new JButton(""); userMenu.setForeground(Color.GREEN); userMenu.setFont(new Font("微软雅黑", Font.PLAIN, 22)); userMenu.setBackground(Color.BLACK); userMenu.addActionListener(e -> onUserMenuClicked()); toolBar.add(userMenu); // 标签页 tabbedPane = new JTabbedPane(JTabbedPane.TOP); tabbedPane.setMinimumSize(new Dimension(400, 50)); tabbedPane.setFont(new Font("宋体", Font.BOLD, 22)); contentPane.add(tabbedPane); // 工作面板 - 直接添加,支持等比例缩放 JPanel workPanel = createWorkPanel(); tabbedPane.addTab("工作面板", new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/a_side.png"))), workPanel, null); tabbedPane.setEnabledAt(0, true); // 开班点检Tab - 使用WebView(参考op150) // URL格式: http://{server_ip}:8980/js/a/mes/mesProcessCheckRecord/ulist?oprno={oprno}&lineSn={line_sn} String shiftCheckOprno = config.getStation(0) != null ? config.getStation(0).getCode() : ""; String shiftCheckUrl = String.format("http://%s:%d/js/a/mes/mesProcessCheckRecord/ulist?oprno=%s&lineSn=%s", config.getServerIp(), config.getHttpPort(), shiftCheckOprno, config.getLineSn()); shiftCheckWebView = new com.mes.ui.component.MesWebView(shiftCheckUrl); tabbedPane.addTab("开班点检", new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_data_preprocess.png"))), shiftCheckWebView, null); // 工作记录Tab - 使用WebView(参考op150) // URL格式: http://{server_ip}:8980/js/a/mes/mesProductRecord/work?oprno={oprno}&lineSn={line_sn} String workRecordOprno = config.getStation(0) != null ? config.getStation(0).getCode() : ""; String workRecordUrl = String.format("http://%s:%d/js/a/mes/mesProductRecord/work?oprno=%s&lineSn=%s", config.getServerIp(), config.getHttpPort(), workRecordOprno, config.getLineSn()); workRecordWebView = new com.mes.ui.component.MesWebView(workRecordUrl); tabbedPane.addTab("工作记录", new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_data_preprocess.png"))), workRecordWebView, null); // 流程监控Tab - 默认隐藏,需要三击用户按钮并输入密码解锁 workflowMonitorPanel = new WorkflowMonitorPanel(); // 切换Tab时自动刷新 tabbedPane.addChangeListener(e -> { int selectedIndex = tabbedPane.getSelectedIndex(); if (selectedIndex < 0) return; String tabTitle = tabbedPane.getTitleAt(selectedIndex); if ("开班点检".equals(tabTitle) && shiftCheckWebView != null) { shiftCheckWebView.reload(); } else if ("工作记录".equals(tabTitle) && workRecordWebView != null) { workRecordWebView.reload(); } else if ("流程".equals(tabTitle) && workflowMonitorPanel != null) { workflowMonitorPanel.refresh(); } }); log.info("UI组件初始化完成"); } /** * 创建工作面板 */ private JPanel createWorkPanel() { workPanel = new JPanel(); if (config.isDualMode()) { // 双工位布局 - 使用GridLayout自适应,中间有间距 workPanel.setLayout(new GridLayout(1, 2, 20, 0)); workPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); } else { // 单工位布局 - 使用 BorderLayout 让面板填满整个工作区域 // 这样面板大小固定,不会随模式切换而变化 workPanel.setLayout(new BorderLayout()); workPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); } // 创建工位面板 for (int i = 0; i < config.getStationCount(); i++) { StationConfig.StationInfo stationInfo = config.getStation(i); if (stationInfo != null) { // 单工位不显示header(工位号和状态栏),双工位显示 boolean showHeader = config.isDualMode(); WorkstationPanel panel = new WorkstationPanel( stationInfo.getCode(), stationInfo.getIndex(), config, showHeader ); // 单工位时,把工具栏状态按钮绑定到面板 if (!showHeader) { panel.setExternalStatusMenu(toolbarStatusMenu); } panels.add(panel); if (config.isDualMode()) { workPanel.add(panel); } else { // 单工位:面板填满整个工作区域 workPanel.add(panel, BorderLayout.CENTER); } } } return workPanel; } /** * 初始化TCP客户端 */ private void initTcpClient() { messageDispatcher = new MessageDispatcher(); tcpClient = new MesTcpClient(config); tcpClient.setDispatcher(messageDispatcher); tcpClient.setConnectionListener(new MesTcpClient.ConnectionListener() { @Override public void onConnected() { SwingUtilities.invokeLater(() -> updateConnectionStatus(true)); for (StationContext ctx : contexts) { tcpClient.sendSync(ctx.getStationCode()); } } @Override public void onDisconnected() { SwingUtilities.invokeLater(() -> updateConnectionStatus(false)); } @Override public void onReconnecting() { SwingUtilities.invokeLater(() -> { heartBeatMenu.setIcon(new ImageIcon(Objects.requireNonNull( getClass().getResource("/resources/image/bg/grey_dot.png")))); }); } }); stepFactory = new StepFactory(tcpClient); log.info("TCP客户端初始化完成"); } /** * 初始化工位 */ private void initWorkstations() { if (config.isDeviceEnabled()) { deviceDrivers = DeviceDriverFactory.createDrivers(config); } // 初始化过程参数上传器 if (config.hasProdParams()) { int uploadInterval = config.getProdParamsConfig().getUploadInterval(); prodDataUploader = new ProdDataUploader( config.getServerIp(), config.getHttpPort(), uploadInterval); } for (int i = 0; i < config.getStationCount(); i++) { StationConfig.StationInfo stationInfo = config.getStation(i); if (stationInfo == null) continue; // 创建上下文 StationContext context = StationContext.fromConfig(stationInfo, config.getLineSn()); context.setDeviceEnabled(config.isDeviceEnabled()); if (deviceDrivers.containsKey(i)) { context.setDeviceDriver(deviceDrivers.get(i)); } // 设置第二行设备驱动(可能来自不同IP的设备) StationConfig.DeviceInfoRow row2 = config.getDeviceInfoRow(1); if (row2 != null) { int connIdx = row2.getConnectionIndex(); if (deviceDrivers.containsKey(connIdx)) { context.setDeviceDriver2(deviceDrivers.get(connIdx)); } } contexts.add(context); // 创建流程引擎 WorkflowEngine engine = new WorkflowEngine(context); engine.buildFromConfig(config.getWorkflowSteps(), stepFactory); // 注入过程参数采集器到WaitCompleteStep if (config.hasProdParams()) { ProdDataCollector collector = new ProdDataCollector(config.getProdParamsConfig()); for (com.mes.step.IWorkflowStep step : engine.getSteps()) { if (step instanceof WaitCompleteStep) { ((WaitCompleteStep) step).setProdDataCollector(collector); log.info("[{}] 过程参数采集器已注入", stationInfo.getCode()); } } prodDataUploader.addCollector(stationInfo.getCode(), collector); } engine.setListener(new WorkflowEngine.WorkflowListener() { @Override public void onStepStarted(WorkflowEngine eng, com.mes.step.IWorkflowStep step) { log.debug("[{}] 步骤开始: {}", context.getStationCode(), step.getStepName()); } @Override public void onStepCompleted(WorkflowEngine eng, com.mes.step.IWorkflowStep step, boolean success) { log.debug("[{}] 步骤完成: {} (成功={})", context.getStationCode(), step.getStepName(), success); } @Override public void onWorkflowCompleted(WorkflowEngine eng) { log.info("[{}] 流程完成", context.getStationCode()); eng.reset(); context.setStatusMessage("结果已提交,请扫下一件", 0); } @Override public void onWorkflowError(WorkflowEngine eng, String error) { log.error("[{}] 流程错误: {}", context.getStationCode(), error); } }); engines.add(engine); // 注册到消息分发器 messageDispatcher.registerStation(stationInfo.getCode(), engine, context); // 绑定UI面板 if (i < panels.size()) { WorkstationPanel panel = panels.get(i); context.setUiPanel(panel); // 注入panel引用到需要弹出扫码框的步骤 for (com.mes.step.IWorkflowStep step : engine.getSteps()) { stepFactory.injectPanel(step, panel); } bindPanelEvents(panel, context, engine); log.info("[{}] UI面板绑定完成", stationInfo.getCode()); } log.info("工位初始化完成: {}", stationInfo.getCode()); } // 设置流程监控面板的工位列表 if (workflowMonitorPanel != null) { workflowMonitorPanel.setWorkstations(engines, contexts); log.info("流程监控面板初始化完成"); } // 启动过程参数上传器 if (prodDataUploader != null) { prodDataUploader.start(); } } /** * 绑定面板事件 */ private void bindPanelEvents(WorkstationPanel panel, StationContext context, WorkflowEngine engine) { panel.setListener(new WorkstationPanel.WorkstationPanelListener() { @Override public void onProductScanned(WorkstationPanel p, String productSn) { context.setProductSn(productSn); context.setUser(currentUser); engine.triggerCurrentStep(); } @Override public void onMaterialScanned(WorkstationPanel p, String materialSn) { context.setMaterialSn(materialSn); engine.triggerCurrentStep(); } @Override public void onOkClicked(WorkstationPanel p) { submitResult(context, engine, "OK"); } @Override public void onNgClicked(WorkstationPanel p) { submitResult(context, engine, "NG"); } @Override public void onUnbindClicked(WorkstationPanel p) { log.info("[{}] 解绑操作", context.getStationCode()); // TODO: 实现解绑逻辑 } }); } /** * 提交结果 * 手动模式下:用户点击OK/NG后,按顺序执行 StartWorkStep -> UploadResultStep */ private void submitResult(StationContext context, WorkflowEngine engine, String result) { log.info("[{}] 用户提交结果: {}, 当前状态: qualityPassed={}, workStarted={}, waitingForUserAction={}", context.getStationCode(), result, context.isQualityPassed(), context.isWorkStarted(), context.isWaitingForUserAction()); com.mes.step.IWorkflowStep currentStep = engine.getCurrentStep(); log.info("[{}] 当前步骤: {}", context.getStationCode(), currentStep != null ? currentStep.getStepId() : "null"); // 设置结果到 UploadResultStep(不管当前在哪一步) com.mes.step.IWorkflowStep uploadStep = engine.getStep("upload_result"); if (uploadStep instanceof UploadResultStep) { ((UploadResultStep) uploadStep).setResult(result); } if (currentStep instanceof UploadResultStep) { // 当前步骤已经是上传结果,直接触发 log.info("[{}] 当前步骤是 UploadResultStep,直接触发", context.getStationCode()); engine.triggerCurrentStep(); } else if (currentStep instanceof StartWorkStep) { // 当前步骤是开始工作(手动模式),标记用户已确认并触发 log.info("[{}] 手动模式:触发 StartWorkStep", context.getStationCode()); context.setWaitingForUserAction(false); engine.triggerCurrentStep(); } else { // 其他情况:直接发送(兼容旧逻辑) log.warn("[{}] 当前步骤非预期: {}, 直接发送结果", context.getStationCode(), currentStep != null ? currentStep.getStepId() : "null"); // 先发送 MKSW,再发送 MQDW tcpClient.sendStartWork( context.getProcessedProductSn(), context.getUser(), context.getStationCode() ); if (uploadStep instanceof UploadResultStep) { tcpClient.sendQuality( context.getProcessedProductSn(), result, context.getUser(), context.getStationCode() ); } } } /** * 更新连接状态 */ private void updateConnectionStatus(boolean connected) { if (connected) { heartBeatMenu.setIcon(new ImageIcon(Objects.requireNonNull( getClass().getResource("/resources/image/bg/green_dot.png")))); startHeartBeatDisplay(); } else { heartBeatMenu.setIcon(new ImageIcon(Objects.requireNonNull( getClass().getResource("/resources/image/bg/grey_dot.png")))); stopHeartBeatDisplay(); } } /** * 启动心跳显示 */ private void startHeartBeatDisplay() { stopHeartBeatDisplay(); heartBeatDisplayTimer = new Timer("HeartBeatDisplay", true); heartBeatDisplayTimer.scheduleAtFixedRate(new TimerTask() { private boolean green = true; @Override public void run() { SwingUtilities.invokeLater(() -> { // 更新时间显示 String timeStr = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()); heartBeatMenu.setText(timeStr); if (green) { heartBeatMenu.setIcon(new ImageIcon(Objects.requireNonNull( getClass().getResource("/resources/image/bg/green_dot.png")))); } else { heartBeatMenu.setIcon(new ImageIcon(Objects.requireNonNull( getClass().getResource("/resources/image/bg/grey_dot.png")))); } green = !green; }); } }, 0, 1000); } /** * 停止心跳显示 */ private void stopHeartBeatDisplay() { if (heartBeatDisplayTimer != null) { heartBeatDisplayTimer.cancel(); heartBeatDisplayTimer = null; } } /** * 获取工位号字符串 */ private String getStationCodesString() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < config.getStationCount(); i++) { if (i > 0) sb.append("/"); StationConfig.StationInfo station = config.getStation(i); if (station != null) { sb.append(station.getCode()); } } return sb.toString(); } /** * 重连MES */ private void reconnectMes() { log.info("手动重连MES"); tcpClient.disconnect(); tcpClient.connect(); } /** * 重置工位 */ private void resetStation(int stationIndex) { if (stationIndex >= 0 && stationIndex < contexts.size()) { StationContext context = contexts.get(stationIndex); WorkflowEngine engine = engines.get(stationIndex); log.info("[{}] 手动刷新工位", context.getStationCode()); context.setStatusMessage("已手动刷新,请扫码", 0); engine.reset(); if (stationIndex < panels.size()) { panels.get(stationIndex).reset(); } } } /** * 退出登录 */ private void logoff() { int result = JOptionPane.showConfirmDialog(this, "确定要退出登录吗?", "确认", JOptionPane.YES_NO_OPTION); if (result == JOptionPane.YES_OPTION) { shutdown(); System.exit(0); } } /** * 窗口关闭处理 */ private void onWindowClosing() { int result = JOptionPane.showConfirmDialog(this, "确定要退出程序吗?", "确认退出", JOptionPane.YES_NO_OPTION); if (result == JOptionPane.YES_OPTION) { shutdown(); System.exit(0); } } /** * 关闭资源 */ public void shutdown() { log.info("正在关闭..."); stopHeartBeatDisplay(); // 停止过程参数上传器(会尝试上传剩余数据) if (prodDataUploader != null) { prodDataUploader.stop(); } // 停止流程监控面板的刷新 if (workflowMonitorPanel != null) { workflowMonitorPanel.stopRefresh(); } for (WorkflowEngine engine : engines) { engine.destroy(); } for (IDeviceDriver driver : deviceDrivers.values()) { driver.disconnect(); } if (tcpClient != null) { tcpClient.disconnect(); } log.info("关闭完成"); } /** * 启动 */ public void start() { tcpClient.connect(); for (WorkflowEngine engine : engines) { engine.start(); } log.info("主窗口启动完成"); } // ========== Getters & Setters ========== public void setCurrentUser(String user) { this.currentUser = user; userMenu.setText(user); for (StationContext ctx : contexts) { ctx.setUser(user); } } public void setSessionId(String sessionId) { this.sessionId = sessionId; } public String getSessionId() { return sessionId; } public void setUserAuth(int auth) { this.userAuth = auth; } public int getUserAuth() { return userAuth; } public void setLoginHour(int hour) { this.loginHour = hour; } public int getLoginHour() { return loginHour; } public boolean isAdmin() { return userAuth == 2; } public StationConfig getConfig() { return config; } public MesTcpClient getTcpClient() { return tcpClient; } public MessageDispatcher getMessageDispatcher() { return messageDispatcher; } public List getContexts() { return contexts; } public List getEngines() { return engines; } public List getPanels() { return panels; } // ========== UI编辑模式相关方法 ========== /** * 切换编辑模式 */ private void toggleEditMode(boolean editMode) { log.info("切换UI编辑模式: {}", editMode); // 双工位模式下,编辑模式只显示一个面板(因为布局是同步的) if (config.isDualMode() && panels.size() > 1) { if (editMode) { // 进入编辑模式:隐藏第二个面板,调整布局 panels.get(1).setVisible(false); workPanel.setLayout(new GridLayout(1, 1, 0, 0)); // 只对第一个面板启用编辑模式 panels.get(0).setEditMode(true); } else { // 退出编辑模式:先退出编辑状态 panels.get(0).setEditMode(false); // 同步布局到第二个面板(重新加载配置) panels.get(1).reloadLayoutConfig(); // 恢复双面板布局 workPanel.setLayout(new GridLayout(1, 2, 20, 0)); panels.get(1).setVisible(true); } workPanel.revalidate(); workPanel.repaint(); } else { // 单工位模式:直接切换 for (WorkstationPanel panel : panels) { panel.setEditMode(editMode); } } if (editMode) { String modeHint = config.isDualMode() ? "\n注意:双工位模式下只显示一个编辑面板,布局会同步到两个工位" : ""; JOptionPane.showMessageDialog(this, "已进入UI编辑模式\n\n" + "操作说明:\n" + "• 拖动组件可调整位置\n" + "• 拖动边缘和角落可调整大小\n" + "• 右键组件可访问更多选项\n" + "• 组件会自动吸附到网格\n" + "• 编辑完成后记得保存布局\n\n" + "注意:编辑模式下工位功能将被禁用" + modeHint, "UI编辑模式", JOptionPane.INFORMATION_MESSAGE); } } /** * 切换网格显示 */ private void toggleGrid(boolean show) { for (WorkstationPanel panel : panels) { panel.toggleGrid(show); } } /** * 保存UI布局 */ private void saveUILayout() { if (panels.isEmpty()) { JOptionPane.showMessageDialog(this, "没有可保存的工位面板", "提示", JOptionPane.WARNING_MESSAGE); return; } // 保存第一个面板的布局(单/双工位共享布局) panels.get(0).saveLayoutConfig(); // 保存后自动退出编辑模式(找到菜单项并取消选中) exitEditModeAfterSave(); log.info("UI布局已保存"); } /** * 保存后退出编辑模式 */ private void exitEditModeAfterSave() { // 查找设置菜单中的 UI编辑模式 选项并取消勾选 JMenuBar menuBar = getJMenuBar(); if (menuBar != null) { for (int i = 0; i < menuBar.getMenuCount(); i++) { JMenu menu = menuBar.getMenu(i); if (menu != null && "设置".equals(menu.getText())) { for (int j = 0; j < menu.getItemCount(); j++) { JMenuItem item = menu.getItem(j); if (item instanceof JCheckBoxMenuItem) { JCheckBoxMenuItem checkItem = (JCheckBoxMenuItem) item; if ("UI编辑模式".equals(checkItem.getText()) && checkItem.isSelected()) { checkItem.setSelected(false); // 触发 itemStateChanged 事件 toggleEditMode(false); log.info("保存后自动退出编辑模式"); break; } } } break; } } } } /** * 导出UI布局 */ private void exportUILayout() { if (panels.isEmpty()) { JOptionPane.showMessageDialog(this, "没有可导出的工位面板", "提示", JOptionPane.WARNING_MESSAGE); return; } panels.get(0).exportLayoutConfig(); } /** * 导入UI布局 */ private void importUILayout() { if (panels.isEmpty()) { JOptionPane.showMessageDialog(this, "没有可导入布局的工位面板", "提示", JOptionPane.WARNING_MESSAGE); return; } panels.get(0).importLayoutConfig(); // 询问是否应用到所有工位 if (panels.size() > 1) { int result = JOptionPane.showConfirmDialog(this, "是否将导入的布局应用到所有工位?", "应用布局", JOptionPane.YES_NO_OPTION); if (result == JOptionPane.YES_OPTION) { // TODO: 如果需要,可以同步布局到其他工位面板 log.info("布局将应用到所有工位"); } } } // ========== 流程Tab解锁相关方法 ========== /** * 用户按钮点击处理 - 三击解锁流程Tab */ private void onUserMenuClicked() { long now = System.currentTimeMillis(); if (now - lastUserMenuClickTime > TRIPLE_CLICK_INTERVAL) { // 超时,重新计数 userMenuClickCount = 1; } else { userMenuClickCount++; } lastUserMenuClickTime = now; if (userMenuClickCount >= 3) { userMenuClickCount = 0; if (workflowTabVisible) { // 已显示,再次三击则隐藏 hideWorkflowTab(); } else { // 弹出密码输入框 showWorkflowPasswordDialog(); } } } /** * 显示密码输入对话框 */ private void showWorkflowPasswordDialog() { JPasswordField passwordField = new JPasswordField(); passwordField.setFont(new Font("微软雅黑", Font.PLAIN, 16)); int result = JOptionPane.showConfirmDialog(this, passwordField, "请输入密码", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); if (result == JOptionPane.OK_OPTION) { String input = new String(passwordField.getPassword()); if (WORKFLOW_PASSWORD.equals(input)) { showWorkflowTab(); } else { JOptionPane.showMessageDialog(this, "密码错误", "提示", JOptionPane.ERROR_MESSAGE); } } } /** * 显示流程Tab */ private void showWorkflowTab() { if (!workflowTabVisible) { tabbedPane.addTab("流程", new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_setting.png"))), workflowMonitorPanel, null); workflowTabVisible = true; tabbedPane.setSelectedComponent(workflowMonitorPanel); workflowMonitorPanel.refresh(); log.info("流程监控Tab已解锁显示"); } // 同时显示隐藏的设置菜单项 editModeSeparator.setVisible(true); editModeItem.setVisible(true); layoutSeparator.setVisible(true); saveLayoutItem.setVisible(true); exportLayoutItem.setVisible(true); importLayoutItem.setVisible(true); } /** * 隐藏流程Tab */ private void hideWorkflowTab() { if (workflowTabVisible) { tabbedPane.remove(workflowMonitorPanel); workflowTabVisible = false; log.info("流程监控Tab已隐藏"); } // 同时隐藏设置菜单项 editModeSeparator.setVisible(false); editModeItem.setVisible(false); layoutSeparator.setVisible(false); saveLayoutItem.setVisible(false); exportLayoutItem.setVisible(false); importLayoutItem.setVisible(false); } }