|
|
@@ -75,10 +75,20 @@ public class MainFrame extends JFrame {
|
|
|
// 需要密码解锁的设置菜单项
|
|
|
private JSeparator editModeSeparator;
|
|
|
private JCheckBoxMenuItem editModeItem;
|
|
|
- private JSeparator layoutSeparator;
|
|
|
private JMenuItem saveLayoutItem;
|
|
|
private JMenuItem exportLayoutItem;
|
|
|
private JMenuItem importLayoutItem;
|
|
|
+ // 项目/设备子菜单(mes123解锁后显示)
|
|
|
+ private JMenu projectDeviceMenu;
|
|
|
+ private JMenuItem switchProjectItem;
|
|
|
+ private JMenuItem switchStationItem;
|
|
|
+ private JMenuItem deviceIpItem;
|
|
|
+ private JMenuItem featuresItem;
|
|
|
+ private JMenuItem deviceInfoUiItem;
|
|
|
+ // 界面布局子菜单(mes123解锁后显示)
|
|
|
+ private JMenu layoutMenu;
|
|
|
+ // 工具栏项目显示按钮
|
|
|
+ private JButton projectMenu;
|
|
|
|
|
|
// 三击解锁相关
|
|
|
private int userMenuClickCount = 0;
|
|
|
@@ -86,6 +96,12 @@ public class MainFrame extends JFrame {
|
|
|
private static final int TRIPLE_CLICK_INTERVAL = 1500; // 1.5秒内完成3次点击
|
|
|
private static final String WORKFLOW_PASSWORD = "mes123";
|
|
|
|
|
|
+ // 免密码解锁序列:2次用户 -> 2次项目 -> 2次心跳
|
|
|
+ private int unlockStage = 0; // 0=等用户, 1=等项目, 2=等心跳
|
|
|
+ private int unlockStageCount = 0; // 当前阶段已点击次数
|
|
|
+ private long unlockLastClickTime = 0;
|
|
|
+ private static final int UNLOCK_INTERVAL = 2000; // 每次点击最大间隔 2s
|
|
|
+
|
|
|
public MainFrame() {
|
|
|
this(StationConfig.getInstance());
|
|
|
}
|
|
|
@@ -107,9 +123,19 @@ public class MainFrame extends JFrame {
|
|
|
* 初始化窗口
|
|
|
*/
|
|
|
private void initFrame() {
|
|
|
- String title = "MES系统客户端:" + getStationCodesString();
|
|
|
- if (config.getStationName() != null && !config.getStationName().isEmpty()) {
|
|
|
- title += " - " + config.getStationName();
|
|
|
+ String codes = getStationCodesString();
|
|
|
+ // 标题优先用 OprnoRegistry 按当前项目推导的工位名称,其次 yaml 的 name
|
|
|
+ String projectId = com.mes.core.ProjectConfigManager.getInstance().getCurrentProjectId();
|
|
|
+ String name = "";
|
|
|
+ if (!config.getStations().isEmpty()) {
|
|
|
+ name = com.mes.core.OprnoRegistry.getGwDes(projectId, config.getStations().get(0).getCode());
|
|
|
+ }
|
|
|
+ if ((name == null || name.isEmpty()) && config.getStationName() != null) {
|
|
|
+ name = config.getStationName();
|
|
|
+ }
|
|
|
+ String title = "MES系统客户端:" + codes;
|
|
|
+ if (name != null && !name.isEmpty() && !name.equals(codes)) {
|
|
|
+ title += " - " + name;
|
|
|
}
|
|
|
setTitle(title);
|
|
|
|
|
|
@@ -195,44 +221,77 @@ public class MainFrame extends JFrame {
|
|
|
editModeSeparator = new JSeparator();
|
|
|
editModeSeparator.setVisible(false);
|
|
|
settingMenu.add(editModeSeparator);
|
|
|
-
|
|
|
- // UI编辑模式(默认隐藏,密码解锁后显示)
|
|
|
+
|
|
|
+ // ========== 子菜单:项目与设备(默认隐藏,密码解锁后显示) ==========
|
|
|
+ projectDeviceMenu = new JMenu("项目与设备");
|
|
|
+ projectDeviceMenu.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_setting.png"))));
|
|
|
+ projectDeviceMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20));
|
|
|
+ projectDeviceMenu.setVisible(false);
|
|
|
+ settingMenu.add(projectDeviceMenu);
|
|
|
+
|
|
|
+ switchProjectItem = new JMenuItem();
|
|
|
+ switchProjectItem.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_setting.png"))));
|
|
|
+ switchProjectItem.setFont(new Font("微软雅黑", Font.PLAIN, 20));
|
|
|
+ switchProjectItem.addActionListener(e -> showSwitchProjectDialog());
|
|
|
+ projectDeviceMenu.add(switchProjectItem);
|
|
|
+
|
|
|
+ switchStationItem = new JMenuItem();
|
|
|
+ switchStationItem.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_setting.png"))));
|
|
|
+ switchStationItem.setFont(new Font("微软雅黑", Font.PLAIN, 20));
|
|
|
+ switchStationItem.addActionListener(e -> showSwitchStationDialog());
|
|
|
+ projectDeviceMenu.add(switchStationItem);
|
|
|
+
|
|
|
+ deviceIpItem = new JMenuItem();
|
|
|
+ deviceIpItem.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_setting.png"))));
|
|
|
+ deviceIpItem.setFont(new Font("微软雅黑", Font.PLAIN, 20));
|
|
|
+ deviceIpItem.addActionListener(e -> showChangeDeviceIpDialog());
|
|
|
+ projectDeviceMenu.add(deviceIpItem);
|
|
|
+
|
|
|
+ projectDeviceMenu.addSeparator();
|
|
|
+
|
|
|
+ featuresItem = new JMenuItem("工艺流程配置");
|
|
|
+ featuresItem.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_setting.png"))));
|
|
|
+ featuresItem.setFont(new Font("微软雅黑", Font.PLAIN, 20));
|
|
|
+ featuresItem.addActionListener(e -> showFeaturesDialog());
|
|
|
+ projectDeviceMenu.add(featuresItem);
|
|
|
+
|
|
|
+ deviceInfoUiItem = new JMenuItem("设备信息显示...");
|
|
|
+ deviceInfoUiItem.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/menu_setting.png"))));
|
|
|
+ deviceInfoUiItem.setFont(new Font("微软雅黑", Font.PLAIN, 20));
|
|
|
+ deviceInfoUiItem.addActionListener(e -> showDeviceInfoUiDialog());
|
|
|
+ projectDeviceMenu.add(deviceInfoUiItem);
|
|
|
+
|
|
|
+ refreshMenuTexts(); // 首次填充显示当前值
|
|
|
+
|
|
|
+ // ========== 子菜单:界面布局(默认隐藏,密码解锁后显示) ==========
|
|
|
+ layoutMenu = new JMenu("界面布局");
|
|
|
+ layoutMenu.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/resources/image/bg/bar_setting.png"))));
|
|
|
+ layoutMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20));
|
|
|
+ layoutMenu.setVisible(false);
|
|
|
+ settingMenu.add(layoutMenu);
|
|
|
+
|
|
|
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布局(默认隐藏,密码解锁后显示)
|
|
|
+ editModeItem.addItemListener(e -> toggleEditMode(editModeItem.isSelected()));
|
|
|
+ layoutMenu.add(editModeItem);
|
|
|
+
|
|
|
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);
|
|
|
-
|
|
|
- // 导出布局配置(默认隐藏,密码解锁后显示)
|
|
|
+ layoutMenu.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);
|
|
|
-
|
|
|
- // 导入布局配置(默认隐藏,密码解锁后显示)
|
|
|
+ layoutMenu.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);
|
|
|
+ layoutMenu.add(importLayoutItem);
|
|
|
|
|
|
// 内容面板
|
|
|
contentPane = new JPanel();
|
|
|
@@ -274,6 +333,7 @@ public class MainFrame extends JFrame {
|
|
|
heartBeatMenu.setForeground(Color.GREEN);
|
|
|
heartBeatMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20));
|
|
|
heartBeatMenu.setBackground(Color.BLACK);
|
|
|
+ heartBeatMenu.addActionListener(e -> onUnlockClick("heart"));
|
|
|
toolBar.add(heartBeatMenu);
|
|
|
|
|
|
toolBar.add(new JLabel(" "));
|
|
|
@@ -293,6 +353,33 @@ public class MainFrame extends JFrame {
|
|
|
userMenu.addActionListener(e -> onUserMenuClicked());
|
|
|
toolBar.add(userMenu);
|
|
|
|
|
|
+ toolBar.add(new JLabel(" "));
|
|
|
+
|
|
|
+ // 当前项目显示(只读展示,切换在"设置"菜单中)
|
|
|
+ JLabel projectLabel = new JLabel("项目:");
|
|
|
+ projectLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
|
|
+ projectLabel.setForeground(Color.BLACK);
|
|
|
+ projectLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
|
|
|
+ projectLabel.setBackground(Color.LIGHT_GRAY);
|
|
|
+ toolBar.add(projectLabel);
|
|
|
+
|
|
|
+ projectMenu = new JButton("");
|
|
|
+ projectMenu.setForeground(Color.YELLOW);
|
|
|
+ projectMenu.setFont(new Font("微软雅黑", Font.PLAIN, 22));
|
|
|
+ projectMenu.setBackground(Color.BLACK);
|
|
|
+ projectMenu.setFocusable(false);
|
|
|
+ projectMenu.addActionListener(e -> onUnlockClick("project"));
|
|
|
+ toolBar.add(projectMenu);
|
|
|
+ updateProjectDisplay();
|
|
|
+
|
|
|
+ // 订阅项目切换 → 刷新工具栏显示
|
|
|
+ com.mes.core.ProjectConfigManager.getInstance().addListener(new com.mes.core.ProjectConfigManager.Listener() {
|
|
|
+ @Override
|
|
|
+ public void onProjectChanged(com.mes.core.ProjectConfigManager.Project project) {
|
|
|
+ SwingUtilities.invokeLater(() -> updateProjectDisplay());
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
// 标签页
|
|
|
tabbedPane = new JTabbedPane(JTabbedPane.TOP);
|
|
|
tabbedPane.setMinimumSize(new Dimension(400, 50));
|
|
|
@@ -996,31 +1083,77 @@ public class MainFrame extends JFrame {
|
|
|
// ========== 流程Tab解锁相关方法 ==========
|
|
|
|
|
|
/**
|
|
|
- * 用户按钮点击处理 - 三击解锁流程Tab
|
|
|
+ * 用户按钮点击处理
|
|
|
+ * - 同步驱动"2用户→2项目→2心跳"免密码序列
|
|
|
+ * - 保留原三击密码弹框解锁
|
|
|
*/
|
|
|
private void onUserMenuClicked() {
|
|
|
+ // 1) 先处理免密码序列
|
|
|
+ onUnlockClick("user");
|
|
|
+
|
|
|
+ // 2) 保留三击密码解锁
|
|
|
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();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 免密码解锁序列:用户x2 → 项目x2 → 心跳x2
|
|
|
+ * 每次点击间隔超过 UNLOCK_INTERVAL 或点错按钮则重置
|
|
|
+ */
|
|
|
+ private void onUnlockClick(String source) {
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
+ if (now - unlockLastClickTime > UNLOCK_INTERVAL) {
|
|
|
+ // 超时重置
|
|
|
+ unlockStage = 0;
|
|
|
+ unlockStageCount = 0;
|
|
|
+ }
|
|
|
+ unlockLastClickTime = now;
|
|
|
+
|
|
|
+ String expected;
|
|
|
+ switch (unlockStage) {
|
|
|
+ case 0: expected = "user"; break;
|
|
|
+ case 1: expected = "project"; break;
|
|
|
+ case 2: expected = "heart"; break;
|
|
|
+ default: unlockStage = 0; unlockStageCount = 0; return;
|
|
|
+ }
|
|
|
+ if (!expected.equals(source)) {
|
|
|
+ // 点错按钮,重置(但如果 source==user 说明可能是新一轮开始)
|
|
|
+ unlockStage = 0;
|
|
|
+ unlockStageCount = "user".equals(source) ? 1 : 0;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ unlockStageCount++;
|
|
|
+ if (unlockStageCount >= 2) {
|
|
|
+ // 进入下一阶段
|
|
|
+ unlockStage++;
|
|
|
+ unlockStageCount = 0;
|
|
|
+ if (unlockStage >= 3) {
|
|
|
+ // 六步完成,解锁
|
|
|
+ unlockStage = 0;
|
|
|
+ if (workflowTabVisible) {
|
|
|
+ hideWorkflowTab();
|
|
|
+ } else {
|
|
|
+ showWorkflowTab();
|
|
|
+ log.info("免密码解锁成功");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* 显示密码输入对话框
|
|
|
@@ -1059,11 +1192,8 @@ public class MainFrame extends JFrame {
|
|
|
|
|
|
// 同时显示隐藏的设置菜单项
|
|
|
editModeSeparator.setVisible(true);
|
|
|
- editModeItem.setVisible(true);
|
|
|
- layoutSeparator.setVisible(true);
|
|
|
- saveLayoutItem.setVisible(true);
|
|
|
- exportLayoutItem.setVisible(true);
|
|
|
- importLayoutItem.setVisible(true);
|
|
|
+ projectDeviceMenu.setVisible(true);
|
|
|
+ layoutMenu.setVisible(true);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -1078,10 +1208,622 @@ public class MainFrame extends JFrame {
|
|
|
|
|
|
// 同时隐藏设置菜单项
|
|
|
editModeSeparator.setVisible(false);
|
|
|
- editModeItem.setVisible(false);
|
|
|
- layoutSeparator.setVisible(false);
|
|
|
- saveLayoutItem.setVisible(false);
|
|
|
- exportLayoutItem.setVisible(false);
|
|
|
- importLayoutItem.setVisible(false);
|
|
|
+ projectDeviceMenu.setVisible(false);
|
|
|
+ layoutMenu.setVisible(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 项目切换/设备IP修改 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 刷新工具栏上的项目显示
|
|
|
+ */
|
|
|
+ private void updateProjectDisplay() {
|
|
|
+ if (projectMenu == null) return;
|
|
|
+ com.mes.core.ProjectConfigManager pc = com.mes.core.ProjectConfigManager.getInstance();
|
|
|
+ com.mes.core.ProjectConfigManager.Project p = pc.getCurrentProject();
|
|
|
+ if (p != null) {
|
|
|
+ projectMenu.setText(p.getDisplay());
|
|
|
+ projectMenu.setToolTipText(String.format(
|
|
|
+ "<html>当前项目:%s<br>工件码前缀:%s<br>冷板码前缀:%s<br>底护板码前缀:%s</html>",
|
|
|
+ p.getDisplay(), p.getProductPrefix(), p.getColdPlatePrefix(), p.getBottomPlatePrefix()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 弹出切换项目对话框
|
|
|
+ */
|
|
|
+ private void showSwitchProjectDialog() {
|
|
|
+ com.mes.core.ProjectConfigManager pc = com.mes.core.ProjectConfigManager.getInstance();
|
|
|
+ java.util.List<com.mes.core.ProjectConfigManager.Project> projects = pc.getProjects();
|
|
|
+
|
|
|
+ String[] options = new String[projects.size()];
|
|
|
+ int currentIndex = 0;
|
|
|
+ for (int i = 0; i < projects.size(); i++) {
|
|
|
+ com.mes.core.ProjectConfigManager.Project p = projects.get(i);
|
|
|
+ options[i] = String.format("%s (工件:%s 冷板:%s 底护板:%s)",
|
|
|
+ p.getDisplay(), p.getProductPrefix(), p.getColdPlatePrefix(), p.getBottomPlatePrefix());
|
|
|
+ if (p.getId().equalsIgnoreCase(pc.getCurrentProjectId())) {
|
|
|
+ currentIndex = i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ JComboBox<String> combo = new JComboBox<>(options);
|
|
|
+ combo.setSelectedIndex(currentIndex);
|
|
|
+ combo.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+
|
|
|
+ JPanel panel = new JPanel(new BorderLayout(5, 5));
|
|
|
+ panel.add(new JLabel("选择项目:"), BorderLayout.NORTH);
|
|
|
+ panel.add(combo, BorderLayout.CENTER);
|
|
|
+
|
|
|
+ int result = JOptionPane.showConfirmDialog(this, panel,
|
|
|
+ "切换项目", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
|
|
|
+ if (result != JOptionPane.OK_OPTION) return;
|
|
|
+
|
|
|
+ int idx = combo.getSelectedIndex();
|
|
|
+ if (idx < 0 || idx >= projects.size()) return;
|
|
|
+ com.mes.core.ProjectConfigManager.Project target = projects.get(idx);
|
|
|
+ if (target.getId().equalsIgnoreCase(pc.getCurrentProjectId())) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ pc.switchProject(target.getId());
|
|
|
+
|
|
|
+ // 重置所有工位,避免沿用旧项目扫到一半的码
|
|
|
+ for (int i = 0; i < engines.size(); i++) {
|
|
|
+ engines.get(i).reset();
|
|
|
+ if (i < panels.size()) panels.get(i).reset();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换项目后同步工位名(标题 + context 的 stationName)
|
|
|
+ String newProjectId = target.getId();
|
|
|
+ for (StationContext ctx : contexts) {
|
|
|
+ String newName = com.mes.core.OprnoRegistry.getGwDes(newProjectId, ctx.getStationCode());
|
|
|
+ if (newName == null || newName.isEmpty()) newName = ctx.getStationCode();
|
|
|
+ ctx.updateStation(null, newName, null);
|
|
|
+ }
|
|
|
+ if (!config.getStations().isEmpty()) {
|
|
|
+ String code = config.getStations().get(0).getCode();
|
|
|
+ String name = com.mes.core.OprnoRegistry.getGwDes(newProjectId, code);
|
|
|
+ String title = "MES系统客户端:" + code;
|
|
|
+ if (name != null && !name.isEmpty() && !name.equals(code)) title += " - " + name;
|
|
|
+ setTitle(title);
|
|
|
+ }
|
|
|
+ refreshMenuTexts();
|
|
|
+ updateProjectDisplay();
|
|
|
+
|
|
|
+ JOptionPane.showMessageDialog(this,
|
|
|
+ "已切换到:" + target.getDisplay() + "\n前缀校验立即生效,所有工位已重置",
|
|
|
+ "切换成功", JOptionPane.INFORMATION_MESSAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 弹出修改拉铆设备IP对话框
|
|
|
+ */
|
|
|
+ private void showChangeDeviceIpDialog() {
|
|
|
+ com.mes.core.ProjectConfigManager pc = com.mes.core.ProjectConfigManager.getInstance();
|
|
|
+ String currentIp = pc.getDeviceIp();
|
|
|
+
|
|
|
+ String input = (String) JOptionPane.showInputDialog(this,
|
|
|
+ "请输入拉铆设备IP:",
|
|
|
+ "修改拉铆设备IP",
|
|
|
+ JOptionPane.PLAIN_MESSAGE,
|
|
|
+ null, null, currentIp);
|
|
|
+
|
|
|
+ if (input == null) return;
|
|
|
+ input = input.trim();
|
|
|
+ if (input.isEmpty()) return;
|
|
|
+ if (!isValidIp(input)) {
|
|
|
+ JOptionPane.showMessageDialog(this, "IP格式不正确: " + input,
|
|
|
+ "错误", JOptionPane.ERROR_MESSAGE);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (input.equals(currentIp)) return;
|
|
|
+
|
|
|
+ // 1. 持久化
|
|
|
+ pc.setDeviceIp(input);
|
|
|
+ // 2. 覆盖当前 StationConfig 中的连接IP
|
|
|
+ for (StationConfig.DeviceConnection conn : config.getDeviceConnections()) {
|
|
|
+ conn.setIp(input);
|
|
|
+ }
|
|
|
+ // 3. 断开并重连设备驱动
|
|
|
+ reconnectDeviceDrivers();
|
|
|
+ refreshMenuTexts();
|
|
|
+
|
|
|
+ JOptionPane.showMessageDialog(this,
|
|
|
+ "拉铆设备IP已更新为:" + input + "\n已尝试重新连接设备",
|
|
|
+ "修改成功", JOptionPane.INFORMATION_MESSAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 断开并重新创建设备驱动,重新注入到各工位上下文
|
|
|
+ */
|
|
|
+ private void reconnectDeviceDrivers() {
|
|
|
+ // 断开旧驱动
|
|
|
+ if (deviceDrivers != null) {
|
|
|
+ for (IDeviceDriver d : deviceDrivers.values()) {
|
|
|
+ try { d.disconnect(); } catch (Exception e) { log.warn("断开旧设备驱动异常", e); }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新创建
|
|
|
+ Map<Integer, IDeviceDriver> newDrivers = new HashMap<>();
|
|
|
+ if (config.isDeviceEnabled()) {
|
|
|
+ newDrivers = DeviceDriverFactory.createDrivers(config);
|
|
|
+ }
|
|
|
+ this.deviceDrivers = newDrivers;
|
|
|
+
|
|
|
+ // 重新注入到每个工位上下文
|
|
|
+ for (int i = 0; i < contexts.size(); i++) {
|
|
|
+ StationContext ctx = contexts.get(i);
|
|
|
+ if (deviceDrivers.containsKey(i)) {
|
|
|
+ ctx.setDeviceDriver(deviceDrivers.get(i));
|
|
|
+ }
|
|
|
+ StationConfig.DeviceInfoRow row2 = config.getDeviceInfoRow(1);
|
|
|
+ if (row2 != null) {
|
|
|
+ int connIdx = row2.getConnectionIndex();
|
|
|
+ if (deviceDrivers.containsKey(connIdx)) {
|
|
|
+ ctx.setDeviceDriver2(deviceDrivers.get(connIdx));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("设备驱动已重新连接: IP={}", com.mes.core.ProjectConfigManager.getInstance().getDeviceIp());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 简单的IPv4格式校验
|
|
|
+ */
|
|
|
+ private boolean isValidIp(String ip) {
|
|
|
+ if (ip == null) return false;
|
|
|
+ String[] parts = ip.split("\\.");
|
|
|
+ if (parts.length != 4) return false;
|
|
|
+ for (String part : parts) {
|
|
|
+ try {
|
|
|
+ int n = Integer.parseInt(part);
|
|
|
+ if (n < 0 || n > 255) return false;
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 刷新"项目与设备"子菜单的显示文本,把当前值显示在菜单项后面
|
|
|
+ */
|
|
|
+ private void refreshMenuTexts() {
|
|
|
+ com.mes.core.ProjectConfigManager pc = com.mes.core.ProjectConfigManager.getInstance();
|
|
|
+ if (switchProjectItem != null) {
|
|
|
+ com.mes.core.ProjectConfigManager.Project p = pc.getCurrentProject();
|
|
|
+ switchProjectItem.setText("切换项目 (当前: " + (p != null ? p.getDisplay() : "?") + ")");
|
|
|
+ }
|
|
|
+ if (switchStationItem != null && !config.getStations().isEmpty()) {
|
|
|
+ String code = config.getStations().get(0).getCode();
|
|
|
+ String des = com.mes.core.OprnoRegistry.getGwDes(pc.getCurrentProjectId(), code);
|
|
|
+ if (des == null || des.isEmpty()) des = "未知工位";
|
|
|
+ switchStationItem.setText("修改工位号 (当前: " + code + " - " + des + ")");
|
|
|
+ }
|
|
|
+ if (deviceIpItem != null) {
|
|
|
+ deviceIpItem.setText("修改拉铆设备IP (当前: " + pc.getDeviceIp() + ")");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 弹出修改工位号对话框
|
|
|
+ * 工位列表按当前项目(P1-P4)展示;工位名称由 OprnoRegistry 自动推导
|
|
|
+ */
|
|
|
+ private void showSwitchStationDialog() {
|
|
|
+ com.mes.core.ProjectConfigManager pc = com.mes.core.ProjectConfigManager.getInstance();
|
|
|
+ String projectId = pc.getCurrentProjectId();
|
|
|
+ com.mes.core.ProjectConfigManager.Project project = pc.getCurrentProject();
|
|
|
+ String currentCode = config.getStations().isEmpty() ? "" : config.getStations().get(0).getCode();
|
|
|
+
|
|
|
+ // 拆分当前code为 "基础码" + "后缀"
|
|
|
+ String currentBase = com.mes.core.OprnoRegistry.formatOprno(currentCode);
|
|
|
+ String currentSuffix = currentCode.length() > currentBase.length()
|
|
|
+ ? currentCode.substring(currentBase.length()).toUpperCase() : "";
|
|
|
+
|
|
|
+ // 工位号下拉(固定项,不可编辑)
|
|
|
+ JComboBox<String> codeCombo = new JComboBox<>();
|
|
|
+ codeCombo.setEditable(false);
|
|
|
+ codeCombo.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+ for (String c : com.mes.core.OprnoRegistry.listCodes(projectId)) {
|
|
|
+ codeCombo.addItem(c);
|
|
|
+ }
|
|
|
+ if (com.mes.core.OprnoRegistry.listCodes(projectId).contains(currentBase)) {
|
|
|
+ codeCombo.setSelectedItem(currentBase);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 后缀下拉(空 / A-L)
|
|
|
+ String[] suffixes = new String[]{"(无)", "A","B","C","D","E","F","G","H","I","J","K","L"};
|
|
|
+ JComboBox<String> suffixCombo = new JComboBox<>(suffixes);
|
|
|
+ suffixCombo.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+ suffixCombo.setSelectedItem(currentSuffix.isEmpty() ? "(无)" : currentSuffix);
|
|
|
+
|
|
|
+ // 预览
|
|
|
+ JLabel previewLabel = new JLabel(" ");
|
|
|
+ previewLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
|
|
+ previewLabel.setForeground(new Color(0x1c, 0x6e, 0xa4));
|
|
|
+
|
|
|
+ // 工位名显示
|
|
|
+ JLabel desLabel = new JLabel(" ");
|
|
|
+ desLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
|
|
+ desLabel.setForeground(new Color(0x3a, 0x7a, 0xc0));
|
|
|
+
|
|
|
+ Runnable refresh = () -> {
|
|
|
+ String base = (String) codeCombo.getSelectedItem();
|
|
|
+ String sfx = (String) suffixCombo.getSelectedItem();
|
|
|
+ if (base == null) base = "";
|
|
|
+ String sfxReal = ("(无)".equals(sfx) || sfx == null) ? "" : sfx;
|
|
|
+ String full = base + sfxReal;
|
|
|
+ previewLabel.setText("最终工位号: " + (full.isEmpty() ? "-" : full));
|
|
|
+ String des = com.mes.core.OprnoRegistry.getGwDes(projectId, full);
|
|
|
+ desLabel.setText("工位名称: " + (des.isEmpty() ? "(未知工位)" : des));
|
|
|
+ };
|
|
|
+ refresh.run();
|
|
|
+ codeCombo.addActionListener(e -> refresh.run());
|
|
|
+ suffixCombo.addActionListener(e -> refresh.run());
|
|
|
+
|
|
|
+ JPanel panel = new JPanel(new java.awt.GridBagLayout());
|
|
|
+ java.awt.GridBagConstraints gbc = new java.awt.GridBagConstraints();
|
|
|
+ gbc.insets = new Insets(4, 4, 4, 4);
|
|
|
+ gbc.anchor = java.awt.GridBagConstraints.WEST;
|
|
|
+
|
|
|
+ gbc.gridx = 0; gbc.gridy = 0;
|
|
|
+ panel.add(new JLabel("当前项目:"), gbc);
|
|
|
+ gbc.gridx = 1; gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
|
|
+ JLabel projectLbl = new JLabel(project != null ? project.getDisplay() : projectId);
|
|
|
+ projectLbl.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+ panel.add(projectLbl, gbc);
|
|
|
+
|
|
|
+ gbc.gridx = 0; gbc.gridy = 1; gbc.fill = java.awt.GridBagConstraints.NONE;
|
|
|
+ panel.add(new JLabel("工位号:"), gbc);
|
|
|
+ gbc.gridx = 1; gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
|
|
+ panel.add(codeCombo, gbc);
|
|
|
+
|
|
|
+ gbc.gridx = 0; gbc.gridy = 2; gbc.fill = java.awt.GridBagConstraints.NONE;
|
|
|
+ panel.add(new JLabel("后缀:"), gbc);
|
|
|
+ gbc.gridx = 1; gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
|
|
+ panel.add(suffixCombo, gbc);
|
|
|
+
|
|
|
+ gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2;
|
|
|
+ panel.add(previewLabel, gbc);
|
|
|
+ gbc.gridy = 4;
|
|
|
+ panel.add(desLabel, gbc);
|
|
|
+
|
|
|
+ int result = JOptionPane.showConfirmDialog(this, panel, "修改工位号",
|
|
|
+ JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
|
|
|
+ if (result != JOptionPane.OK_OPTION) return;
|
|
|
+
|
|
|
+ String base = (String) codeCombo.getSelectedItem();
|
|
|
+ String sfx = (String) suffixCombo.getSelectedItem();
|
|
|
+ String sfxReal = ("(无)".equals(sfx) || sfx == null) ? "" : sfx;
|
|
|
+ String newCode = (base == null ? "" : base) + sfxReal;
|
|
|
+ if (newCode.isEmpty()) {
|
|
|
+ JOptionPane.showMessageDialog(this, "工位号不能为空", "错误", JOptionPane.ERROR_MESSAGE);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (newCode.equalsIgnoreCase(currentCode)) return;
|
|
|
+
|
|
|
+ pc.setStationOverride(newCode, null);
|
|
|
+ applyStationChange(newCode);
|
|
|
+
|
|
|
+ String des = com.mes.core.OprnoRegistry.getGwDes(projectId, newCode);
|
|
|
+ JOptionPane.showMessageDialog(this,
|
|
|
+ "工位已更新为:" + newCode + (des.isEmpty() ? "" : (" - " + des)) + "\n已刷新界面并向MES重新同步",
|
|
|
+ "修改成功", JOptionPane.INFORMATION_MESSAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 热更新工位号:更新 config/context/dispatcher/面板显示/窗口标题,并重新向MES同步
|
|
|
+ */
|
|
|
+ private void applyStationChange(String newCode) {
|
|
|
+ String oldCode = config.getStations().isEmpty() ? "" : config.getStations().get(0).getCode();
|
|
|
+ String projectId = com.mes.core.ProjectConfigManager.getInstance().getCurrentProjectId();
|
|
|
+
|
|
|
+ // 1. 更新 config
|
|
|
+ if (!config.getStations().isEmpty()) {
|
|
|
+ config.getStations().get(0).setCode(newCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 更新 context(推导工位名)
|
|
|
+ String name = com.mes.core.OprnoRegistry.getGwDes(projectId, newCode);
|
|
|
+ if (name == null || name.isEmpty()) name = newCode;
|
|
|
+ for (StationContext ctx : contexts) {
|
|
|
+ if (ctx.getStationIndex() == 0) {
|
|
|
+ ctx.updateStation(newCode, name, null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. dispatcher 注销旧的、注册新的
|
|
|
+ if (messageDispatcher != null && !oldCode.isEmpty() && !oldCode.equalsIgnoreCase(newCode)) {
|
|
|
+ messageDispatcher.unregisterStation(oldCode);
|
|
|
+ if (!engines.isEmpty() && !contexts.isEmpty()) {
|
|
|
+ messageDispatcher.registerStation(newCode, engines.get(0), contexts.get(0));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 刷新 WorkstationPanel 工位标签
|
|
|
+ for (WorkstationPanel p : panels) {
|
|
|
+ p.updateStationCode(newCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 重置工位并向 MES 重新同步
|
|
|
+ for (int i = 0; i < engines.size(); i++) {
|
|
|
+ engines.get(i).reset();
|
|
|
+ if (i < panels.size()) panels.get(i).reset();
|
|
|
+ contexts.get(i).setStatusMessage("工位已切换,请扫下一件", 0);
|
|
|
+ }
|
|
|
+ if (tcpClient != null && !contexts.isEmpty()) {
|
|
|
+ tcpClient.sendSync(contexts.get(0).getStationCode());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5b. 重建过程参数采集器(collector 以工位号为 key)
|
|
|
+ if (prodDataUploader != null && config.hasProdParams()) {
|
|
|
+ prodDataUploader.clearCollectors();
|
|
|
+ for (int i = 0; i < contexts.size(); i++) {
|
|
|
+ StationContext c = contexts.get(i);
|
|
|
+ ProdDataCollector collector = new ProdDataCollector(config.getProdParamsConfig());
|
|
|
+ WorkflowEngine eng = engines.get(i);
|
|
|
+ for (com.mes.step.IWorkflowStep step : eng.getSteps()) {
|
|
|
+ if (step instanceof WaitCompleteStep) {
|
|
|
+ ((WaitCompleteStep) step).setProdDataCollector(collector);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ prodDataUploader.addCollector(c.getStationCode(), collector);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 窗口标题
|
|
|
+ String title = "MES系统客户端:" + newCode;
|
|
|
+ if (!name.isEmpty() && !name.equals(newCode)) title += " - " + name;
|
|
|
+ setTitle(title);
|
|
|
+
|
|
|
+ // 7. 刷新菜单文字
|
|
|
+ refreshMenuTexts();
|
|
|
+
|
|
|
+ log.info("工位已切换: {} -> {} ({})", oldCode, newCode, name);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 弹出工艺流程配置对话框
|
|
|
+ * 用户勾选启用的功能,保存后软重启所有工位流程
|
|
|
+ */
|
|
|
+ private void showFeaturesDialog() {
|
|
|
+ com.mes.core.ProjectConfigManager pc = com.mes.core.ProjectConfigManager.getInstance();
|
|
|
+ // yaml 定义的功能集合 + 当前有效值
|
|
|
+ Map<String, Boolean> yamlFeatures = config.getYamlFeatures();
|
|
|
+ if (yamlFeatures.isEmpty()) {
|
|
|
+ JOptionPane.showMessageDialog(this, "station.yaml 中未定义 workflow.features",
|
|
|
+ "提示", JOptionPane.WARNING_MESSAGE);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 功能显示名映射
|
|
|
+ Map<String, String> labels = new LinkedHashMap<>();
|
|
|
+ labels.put("cold_plate", "扫+校验冷板码");
|
|
|
+ labels.put("bottom_plate", "扫+校验底护板码");
|
|
|
+ labels.put("riveting", "拉铆流程(心跳+计数+等待+停心跳)");
|
|
|
+ labels.put("prod_params", "拉铆过程参数采集");
|
|
|
+
|
|
|
+ // 构建复选框(按 yaml 声明顺序)
|
|
|
+ JPanel panel = new JPanel();
|
|
|
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
|
|
+ panel.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 15));
|
|
|
+
|
|
|
+ Map<String, JCheckBox> checkBoxes = new LinkedHashMap<>();
|
|
|
+ for (String key : yamlFeatures.keySet()) {
|
|
|
+ String label = labels.getOrDefault(key, key);
|
|
|
+ JCheckBox cb = new JCheckBox(label, config.isFeatureEnabled(key));
|
|
|
+ cb.setFont(new Font("微软雅黑", Font.PLAIN, 18));
|
|
|
+ cb.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
|
+ panel.add(cb);
|
|
|
+ panel.add(Box.createVerticalStrut(6));
|
|
|
+ checkBoxes.put(key, cb);
|
|
|
+ }
|
|
|
+ // cold_plate 与 bottom_plate 互斥(勾一个自动取消另一个)
|
|
|
+ final JCheckBox cold = checkBoxes.get("cold_plate");
|
|
|
+ final JCheckBox bottom = checkBoxes.get("bottom_plate");
|
|
|
+ if (cold != null && bottom != null) {
|
|
|
+ cold.addActionListener(ev -> { if (cold.isSelected()) bottom.setSelected(false); });
|
|
|
+ bottom.addActionListener(ev -> { if (bottom.isSelected()) cold.setSelected(false); });
|
|
|
+ }
|
|
|
+ JLabel hint = new JLabel("<html><font color='gray' size='3'>冷板/底护板二选一;保存后将重建所有工位流程并重置</font></html>");
|
|
|
+ hint.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
|
+ panel.add(hint);
|
|
|
+
|
|
|
+ int result = JOptionPane.showConfirmDialog(this, panel,
|
|
|
+ "工艺流程配置", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
|
|
|
+ if (result != JOptionPane.OK_OPTION) return;
|
|
|
+
|
|
|
+ // 收集勾选结果
|
|
|
+ Map<String, Boolean> override = new LinkedHashMap<>();
|
|
|
+ for (Map.Entry<String, JCheckBox> e : checkBoxes.entrySet()) {
|
|
|
+ override.put(e.getKey(), e.getValue().isSelected());
|
|
|
+ }
|
|
|
+ // 二次兜底:如果用户绕过监听同时勾了两个,强制只保留冷板
|
|
|
+ if (Boolean.TRUE.equals(override.get("cold_plate"))
|
|
|
+ && Boolean.TRUE.equals(override.get("bottom_plate"))) {
|
|
|
+ override.put("bottom_plate", false);
|
|
|
+ }
|
|
|
+ pc.setFeaturesOverride(override);
|
|
|
+
|
|
|
+ // 物料码输入框联动:勾了冷板或底护板 → 显示;都没勾 → 隐藏
|
|
|
+ boolean coldOn = Boolean.TRUE.equals(override.get("cold_plate"));
|
|
|
+ boolean bottomOn = Boolean.TRUE.equals(override.get("bottom_plate"));
|
|
|
+ pc.setDeviceInfoRowsOverride(
|
|
|
+ pc.getDeviceRow1EnabledOverride(), pc.getDeviceRow1LabelOverride(),
|
|
|
+ pc.getDeviceRow2EnabledOverride(), pc.getDeviceRow2LabelOverride(),
|
|
|
+ coldOn || bottomOn);
|
|
|
+
|
|
|
+ // 重新加载 yaml 配置(会按新 override 展开 steps)
|
|
|
+ StationConfig.reload();
|
|
|
+ this.config = StationConfig.getInstance();
|
|
|
+ // 重新应用当前设备IP覆盖
|
|
|
+ String ip = pc.getDeviceIp();
|
|
|
+ for (StationConfig.DeviceConnection conn : config.getDeviceConnections()) {
|
|
|
+ conn.setIp(ip);
|
|
|
+ }
|
|
|
+
|
|
|
+ rebuildWorkflows();
|
|
|
+
|
|
|
+ // 按功能开关同步面板显示(物料码输入框显隐、label 文字)
|
|
|
+ for (WorkstationPanel p : panels) {
|
|
|
+ p.refreshDisplayFromConfig(config);
|
|
|
+ }
|
|
|
+
|
|
|
+ JOptionPane.showMessageDialog(this,
|
|
|
+ "工艺流程已更新,所有工位已重建",
|
|
|
+ "配置成功", JOptionPane.INFORMATION_MESSAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 软重启工作流:重新构建所有工位的流程引擎、重新注入 panel 和过程参数采集器
|
|
|
+ * 不断开 TCP,不销毁设备驱动(IP变化由 showChangeDeviceIpDialog 另行处理)
|
|
|
+ */
|
|
|
+ private void rebuildWorkflows() {
|
|
|
+ log.info("开始重建工作流...");
|
|
|
+
|
|
|
+ // 先停止过程参数上传器并清空采集器
|
|
|
+ if (prodDataUploader != null) {
|
|
|
+ prodDataUploader.stop();
|
|
|
+ prodDataUploader.clearCollectors();
|
|
|
+ prodDataUploader = null;
|
|
|
+ }
|
|
|
+ // 若 features 启用了 prod_params 且 yaml 配置存在,重新创建 uploader
|
|
|
+ if (config.hasProdParams()) {
|
|
|
+ int uploadInterval = config.getProdParamsConfig().getUploadInterval();
|
|
|
+ prodDataUploader = new ProdDataUploader(
|
|
|
+ config.getServerIp(), config.getHttpPort(), uploadInterval);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 逐个工位重建
|
|
|
+ for (int i = 0; i < engines.size(); i++) {
|
|
|
+ WorkflowEngine engine = engines.get(i);
|
|
|
+ StationContext ctx = contexts.get(i);
|
|
|
+
|
|
|
+ // 停止旧流程
|
|
|
+ engine.stop();
|
|
|
+
|
|
|
+ // 用新配置重建步骤
|
|
|
+ engine.buildFromConfig(config.getWorkflowSteps(), stepFactory);
|
|
|
+
|
|
|
+ // 重新注入 panel(扫码框弹出)
|
|
|
+ if (i < panels.size()) {
|
|
|
+ WorkstationPanel p = panels.get(i);
|
|
|
+ for (com.mes.step.IWorkflowStep step : engine.getSteps()) {
|
|
|
+ stepFactory.injectPanel(step, p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新注入过程参数采集器
|
|
|
+ if (prodDataUploader != null && config.hasProdParams()) {
|
|
|
+ ProdDataCollector collector = new ProdDataCollector(config.getProdParamsConfig());
|
|
|
+ for (com.mes.step.IWorkflowStep step : engine.getSteps()) {
|
|
|
+ if (step instanceof WaitCompleteStep) {
|
|
|
+ ((WaitCompleteStep) step).setProdDataCollector(collector);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ prodDataUploader.addCollector(ctx.getStationCode(), collector);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重置并重新启动
|
|
|
+ engine.reset();
|
|
|
+ engine.start();
|
|
|
+ if (i < panels.size()) panels.get(i).reset();
|
|
|
+ ctx.setStatusMessage("工艺已更新,请扫下一件", 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 刷新监控面板
|
|
|
+ if (workflowMonitorPanel != null) {
|
|
|
+ workflowMonitorPanel.setWorkstations(engines, contexts);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 启动新的过程参数上传器
|
|
|
+ if (prodDataUploader != null) {
|
|
|
+ prodDataUploader.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("工作流重建完成, 步骤数={}", config.getWorkflowSteps().size());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 弹出"设备信息显示"对话框
|
|
|
+ * 只允许勾选启用/禁用 + 改 label,寄存器地址不在UI改(留yaml)
|
|
|
+ */
|
|
|
+ private void showDeviceInfoUiDialog() {
|
|
|
+ com.mes.core.ProjectConfigManager pc = com.mes.core.ProjectConfigManager.getInstance();
|
|
|
+ java.util.List<StationConfig.DeviceInfoRow> rows = config.getDeviceInfoRows();
|
|
|
+
|
|
|
+ // 第1行
|
|
|
+ StationConfig.DeviceInfoRow r1 = rows.size() > 0 ? rows.get(0) : null;
|
|
|
+ JCheckBox enableA = new JCheckBox("启用第1行", r1 != null && r1.isEnabled());
|
|
|
+ enableA.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+ JTextField labelA = new JTextField(r1 != null && r1.getLabel() != null ? r1.getLabel() : "");
|
|
|
+ labelA.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+
|
|
|
+ // 第2行
|
|
|
+ StationConfig.DeviceInfoRow r2 = rows.size() > 1 ? rows.get(1) : null;
|
|
|
+ JCheckBox enableB = new JCheckBox("启用第2行", r2 != null && r2.isEnabled());
|
|
|
+ enableB.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+ JTextField labelB = new JTextField(r2 != null && r2.getLabel() != null ? r2.getLabel() : "");
|
|
|
+ labelB.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+
|
|
|
+ // 物料码输入框显隐
|
|
|
+ JCheckBox showMaterial = new JCheckBox("显示物料码输入框", config.isShowMaterialInput());
|
|
|
+ showMaterial.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+
|
|
|
+ JPanel panel = new JPanel(new java.awt.GridBagLayout());
|
|
|
+ java.awt.GridBagConstraints gbc = new java.awt.GridBagConstraints();
|
|
|
+ gbc.insets = new Insets(4, 4, 4, 4);
|
|
|
+ gbc.anchor = java.awt.GridBagConstraints.WEST;
|
|
|
+ // 第1行
|
|
|
+ gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
|
|
|
+ panel.add(enableA, gbc);
|
|
|
+ gbc.gridy = 1; gbc.gridwidth = 1;
|
|
|
+ panel.add(new JLabel("标签:"), gbc);
|
|
|
+ gbc.gridx = 1; gbc.fill = java.awt.GridBagConstraints.HORIZONTAL; gbc.weightx = 1;
|
|
|
+ labelA.setPreferredSize(new Dimension(160, 26));
|
|
|
+ panel.add(labelA, gbc);
|
|
|
+ // 第2行
|
|
|
+ gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2; gbc.fill = java.awt.GridBagConstraints.NONE; gbc.weightx = 0;
|
|
|
+ panel.add(enableB, gbc);
|
|
|
+ gbc.gridy = 3; gbc.gridwidth = 1;
|
|
|
+ panel.add(new JLabel("标签:"), gbc);
|
|
|
+ gbc.gridx = 1; gbc.fill = java.awt.GridBagConstraints.HORIZONTAL; gbc.weightx = 1;
|
|
|
+ labelB.setPreferredSize(new Dimension(160, 26));
|
|
|
+ panel.add(labelB, gbc);
|
|
|
+ // 提示
|
|
|
+ gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2; gbc.fill = java.awt.GridBagConstraints.NONE;
|
|
|
+ panel.add(showMaterial, gbc);
|
|
|
+ gbc.gridy = 5;
|
|
|
+ JLabel tip = new JLabel("<html><font color='gray' size='3'>寄存器地址需改 station.yaml</font></html>");
|
|
|
+ panel.add(tip, gbc);
|
|
|
+
|
|
|
+ int result = JOptionPane.showConfirmDialog(this, panel, "设备信息显示",
|
|
|
+ JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
|
|
|
+ if (result != JOptionPane.OK_OPTION) return;
|
|
|
+
|
|
|
+ pc.setDeviceInfoRowsOverride(
|
|
|
+ enableA.isSelected(), labelA.getText(),
|
|
|
+ enableB.isSelected(), labelB.getText(),
|
|
|
+ showMaterial.isSelected());
|
|
|
+
|
|
|
+ // 重新加载 config 让 UI 覆盖生效
|
|
|
+ StationConfig.reload();
|
|
|
+ this.config = StationConfig.getInstance();
|
|
|
+ // 重新应用运行时IP
|
|
|
+ for (StationConfig.DeviceConnection conn : config.getDeviceConnections()) {
|
|
|
+ conn.setIp(pc.getDeviceIp());
|
|
|
+ }
|
|
|
+ // 刷新工位面板显示(启用/禁用行、label)
|
|
|
+ for (WorkstationPanel p : panels) {
|
|
|
+ p.refreshDisplayFromConfig(config);
|
|
|
+ }
|
|
|
+ // 若显示开关改变涉及组件增删,强制重载布局
|
|
|
+ for (WorkstationPanel p : panels) {
|
|
|
+ try { p.reloadLayoutConfig(); } catch (Exception ex) { /* 忽略 */ }
|
|
|
+ }
|
|
|
+
|
|
|
+ JOptionPane.showMessageDialog(this,
|
|
|
+ "设备信息显示配置已更新\n如行数变化布局未立即生效,请重启程序",
|
|
|
+ "配置成功", JOptionPane.INFORMATION_MESSAGE);
|
|
|
}
|
|
|
}
|