| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087 |
- 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<StationContext> contexts;
- private List<WorkflowEngine> engines;
- private List<WorkstationPanel> panels;
- private Map<Integer, IDeviceDriver> 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<StationContext> getContexts() {
- return contexts;
- }
- public List<WorkflowEngine> getEngines() {
- return engines;
- }
- public List<WorkstationPanel> 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);
- }
- }
|