package baseStation; import javax.swing.*; import gecaoji.Device; import publicway.buttonset; import java.awt.*; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Properties; public class BaseStationDialog extends JDialog { private final Color THEME_COLOR; private final Device device; private final BaseStation baseStation; private JLabel deviceIdLabel; private JLabel statusLabel; private JLabel updateTimeLabel; private JLabel positionLabel; private JLabel simCardLabel; private Timer refreshTimer; public BaseStationDialog(Component parent, Color themeColor, Device device, BaseStation baseStation) { super(resolveOwner(parent), "基准站信息", Dialog.ModalityType.APPLICATION_MODAL); this.THEME_COLOR = themeColor; this.device = device; this.baseStation = baseStation != null ? baseStation : new BaseStation(); this.baseStation.load(); initializeDialog(400, 800); initializeBaseStationContent(); startRefreshTimer(); if (getOwner() == null) { setLocationRelativeTo(null); } } private static Window resolveOwner(Component parent) { if (parent instanceof Window) { return (Window) parent; } if (parent != null) { Window owner = SwingUtilities.getWindowAncestor(parent); if (owner != null) { return owner; } } return null; } private void initializeDialog(int width, int height) { setSize(width, height); setPreferredSize(new Dimension(width, height)); setMinimumSize(new Dimension(width, height)); setLocationRelativeTo(getParent()); setResizable(false); getContentPane().setBackground(Color.WHITE); } private void initializeBaseStationContent() { JPanel contentPanel = new JPanel(); contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); contentPanel.setBorder(BorderFactory.createEmptyBorder(25, 10, 25, 25)); // 左边距改为10像素 contentPanel.setBackground(Color.WHITE); JPanel deviceIdPanel = createHorizontalInfoPanel("基准站编号:", getDeviceIdDisplay(), Color.BLACK); contentPanel.add(deviceIdPanel); contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); // 状态信息 JPanel statusPanel = createHorizontalInfoPanel("基准站状态:", getStatusText(), getStatusColor()); contentPanel.add(statusPanel); contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); // 数据更新时间 JPanel updateTimePanel = createHorizontalInfoPanel("数据更新时间:", getUpdateTimeDisplay(), Color.BLACK); contentPanel.add(updateTimePanel); contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); // 基准站位置 JPanel positionPanel = createHorizontalInfoPanel("基准站位置:", getBaseStationPositionDisplay(), Color.BLACK); contentPanel.add(positionPanel); contentPanel.add(Box.createRigidArea(new Dimension(0, 30))); JPanel simCardPanel = createHorizontalInfoPanel("基准站卡号:", getSimCardDisplay(), Color.BLACK); contentPanel.add(simCardPanel); contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); JPanel tutorialRow = createActionPanel("查看安装教程", "查看", new Color(70, 130, 180), e -> showInstallationTutorial()); contentPanel.add(tutorialRow); contentPanel.add(Box.createRigidArea(new Dimension(0, 12))); JPanel lockRow = createActionPanel("锁定基准站位置", "锁定", THEME_COLOR, e -> lockBaseStationPosition()); contentPanel.add(lockRow); contentPanel.add(Box.createRigidArea(new Dimension(0, 30))); // 按钮面板 contentPanel.add(Box.createVerticalGlue()); getContentPane().add(contentPanel); refreshLabels(); } private JPanel createHorizontalInfoPanel(String title, String value, Color valueColor) { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); panel.setBackground(Color.WHITE); panel.setAlignmentX(Component.LEFT_ALIGNMENT); panel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 40)); JLabel titleLabel = new JLabel(title); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14)); titleLabel.setForeground(Color.DARK_GRAY); titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT); // 创建值标签(不再使用文本框) JLabel valueLabel = new JLabel(value); valueLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); valueLabel.setForeground(valueColor); valueLabel.setBackground(Color.WHITE); valueLabel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30)); // 根据字段类型保存引用 if ("基准站编号:".equals(title)) { deviceIdLabel = valueLabel; } else if ("基准站状态:".equals(title)) { statusLabel = valueLabel; } else if ("数据更新时间:".equals(title)) { updateTimeLabel = valueLabel; } else if ("基准站位置:".equals(title)) { positionLabel = valueLabel; final Color clickableColor = THEME_COLOR != null ? THEME_COLOR : new Color(70, 130, 180); titleLabel.setForeground(clickableColor); titleLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); titleLabel.setToolTipText("点击修改基准站位置"); titleLabel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) { promptEditBaseStationPosition(); } } @Override public void mouseEntered(MouseEvent e) { titleLabel.setForeground(brightenColor(clickableColor)); } @Override public void mouseExited(MouseEvent e) { titleLabel.setForeground(clickableColor); } }); } else if ("基准站卡号:".equals(title)) { simCardLabel = valueLabel; } // 修改布局:标题和值标签都左对齐,中间添加3像素间隔 panel.add(titleLabel); panel.add(Box.createRigidArea(new Dimension(3, 0))); // 添加3像素间隔 panel.add(valueLabel); return panel; } private void promptEditBaseStationPosition() { if (baseStation == null) { JOptionPane.showMessageDialog(this, "基准站信息未初始化,无法修改位置。", "错误", JOptionPane.ERROR_MESSAGE); return; } baseStation.load(); String currentRaw = baseStation.getInstallationCoordinates(); String normalizedCurrent = canonicalizeCoordinateValue(currentRaw); boolean hasExistingCoordinate = normalizedCurrent != null && !"-1".equals(normalizedCurrent); JTextField inputField = new JTextField(hasExistingCoordinate ? normalizedCurrent : ""); inputField.setColumns(28); JPanel dialogPanel = new JPanel(new BorderLayout(0, 8)); JLabel hintLabel = new JLabel("请输入新的基准站坐标(示例:3949.90238860,N,11616.75692000,E)"); hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); dialogPanel.add(hintLabel, BorderLayout.NORTH); dialogPanel.add(inputField, BorderLayout.CENTER); JOptionPane optionPane = new JOptionPane(dialogPanel, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); JDialog dialog = optionPane.createDialog(this, "修改基准站位置"); dialog.setModal(true); dialog.pack(); Dimension packedSize = dialog.getSize(); Window ownerWindow = getOwner(); int referenceWidth = ownerWindow != null ? ownerWindow.getWidth() : getWidth(); if (referenceWidth <= 0) { referenceWidth = 400; } dialog.setSize(new Dimension(referenceWidth, packedSize.height)); dialog.setResizable(false); dialog.setLocationRelativeTo(this); dialog.setVisible(true); Object selectedValue = optionPane.getValue(); if (!(selectedValue instanceof Integer) || ((Integer) selectedValue) != JOptionPane.OK_OPTION) { return; } String userInput = inputField.getText(); if (userInput == null) { return; } String trimmed = userInput.trim(); if (trimmed.isEmpty()) { int confirm = JOptionPane.showConfirmDialog(this, "未输入坐标,保存后将视为未安装状态,是否继续?", "确认", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); if (confirm != JOptionPane.OK_OPTION) { return; } } String valueToSave = trimmed.isEmpty() ? "-1" : canonicalizeCoordinateValue(trimmed); if (!"-1".equals(valueToSave) && !looksLikeCoordinateFormat(valueToSave)) { int confirm = JOptionPane.showConfirmDialog(this, "坐标格式似乎不符合“纬度,方向,经度,方向”的预期,仍然保存吗?", "格式确认", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); if (confirm != JOptionPane.OK_OPTION) { return; } } if (normalizedCurrent.equals(valueToSave)) { JOptionPane.showMessageDialog(this, "基准站位置未发生变化。", "提示", JOptionPane.INFORMATION_MESSAGE); return; } baseStation.updateInstallationCoordinates(valueToSave); baseStation.load(); if (device != null) { device.setBaseStationCoordinates(valueToSave); updatePropertiesFile("baseStationCoordinates", valueToSave); } refreshLabels(); JOptionPane.showMessageDialog(this, "基准站位置已更新。", "更新成功", JOptionPane.INFORMATION_MESSAGE); } private boolean looksLikeCoordinateFormat(String value) { if (value == null) { return false; } String[] parts = value.split(","); if (parts.length != 4) { return false; } for (String part : parts) { if (part.trim().isEmpty()) { return false; } } return true; } private JPanel createActionPanel(String title, String buttonText, Color buttonColor, ActionListener actionListener) { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); panel.setBackground(Color.WHITE); panel.setAlignmentX(Component.LEFT_ALIGNMENT); panel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 45)); JLabel titleLabel = new JLabel(title); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14)); titleLabel.setForeground(Color.DARK_GRAY); JButton actionButton = buttonset.createStyledButton(buttonText, buttonColor); actionButton.addActionListener(actionListener); panel.add(titleLabel); panel.add(Box.createHorizontalGlue()); panel.add(actionButton); return panel; } private Color brightenColor(Color color) { int r = Math.min(255, color.getRed() + 30); int g = Math.min(255, color.getGreen() + 30); int b = Math.min(255, color.getBlue() + 30); return new Color(r, g, b); } private void startRefreshTimer() { refreshTimer = new Timer(1000, e -> refreshLabels()); refreshTimer.start(); } private void refreshLabels() { if (baseStation != null) { baseStation.load(); } if (deviceIdLabel != null) { deviceIdLabel.setText(getDeviceIdDisplay()); } if (statusLabel != null) { statusLabel.setText(getStatusText()); statusLabel.setForeground(getStatusColor()); } if (updateTimeLabel != null) { updateTimeLabel.setText(getUpdateTimeDisplay()); } if (positionLabel != null) { positionLabel.setText(getBaseStationPositionDisplay()); } if (simCardLabel != null) { simCardLabel.setText(getSimCardDisplay()); } } public void refreshData() { refreshLabels(); } private String getDeviceIdDisplay() { if (baseStation == null) { return "未录入"; } String deviceId = baseStation.getDeviceId(); if (deviceId == null) { return "未录入"; } String trimmed = deviceId.trim(); if (trimmed.isEmpty() || "-1".equals(trimmed)) { return "未录入"; } return trimmed; } private String getStatusText() { long updateMillis = getLastUpdateTimeMillis(); if (updateMillis <= 0) { return "离线 ✗"; } long diff = System.currentTimeMillis() - updateMillis; return diff < 10_000 ? "在线 ✓" : "离线 ✗"; } private Color getStatusColor() { long updateMillis = getLastUpdateTimeMillis(); if (updateMillis > 0 && System.currentTimeMillis() - updateMillis < 10_000) { return new Color(0, 150, 0); } return Color.GRAY; } private String getUpdateTimeDisplay() { long updateMillis = getLastUpdateTimeMillis(); if (updateMillis <= 0) { return "初始化无数据"; } try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(new Date(updateMillis)); } catch (Exception e) { return String.valueOf(updateMillis); } } private String getBaseStationPositionDisplay() { if (baseStation == null) { return "设备没有安装固定"; } String coordinates = baseStation.getInstallationCoordinates(); String canonical = canonicalizeCoordinateValue(coordinates); if (canonical == null || "-1".equals(canonical)) { return "设备没有安装固定"; } return canonical; } private String getSimCardDisplay() { if (baseStation == null) { return "未绑定"; } String number = baseStation.getIotSimCardNumber(); if (number == null) { return "未绑定"; } String trimmed = number.trim(); if (trimmed.isEmpty() || "-1".equals(trimmed)) { return "未绑定"; } return trimmed; } private long getLastUpdateTimeMillis() { if (baseStation == null) { return -1; } String updateTime = baseStation.getDataUpdateTime(); if (updateTime == null) { return -1; } String trimmed = updateTime.trim(); if (trimmed.isEmpty() || "-1".equals(trimmed)) { return -1; } try { long value = Long.parseLong(trimmed); if (value < 1_000_000_000_000L) { return value * 1000; } return value; } catch (NumberFormatException e) { return -1; } } private boolean hasBaseStationId() { if (baseStation == null) { return false; } String deviceId = baseStation.getDeviceId(); if (deviceId == null) { return false; } String trimmed = deviceId.trim(); return !trimmed.isEmpty() && !"-1".equals(trimmed); } // Normalizes coordinate strings into degree-minute format when possible for consistent display/storage private String canonicalizeCoordinateValue(String value) { if (value == null) { return "-1"; } String trimmed = value.trim(); if (trimmed.isEmpty() || "-1".equals(trimmed)) { return "-1"; } String[] parts = trimmed.split(","); if (parts.length != 4) { return trimmed; } String latDirRaw = parts[1] == null ? "" : parts[1].trim(); String lonDirRaw = parts[3] == null ? "" : parts[3].trim(); if (latDirRaw.isEmpty() || lonDirRaw.isEmpty()) { return trimmed; } String latDir = latDirRaw.toUpperCase(Locale.ROOT); String lonDir = lonDirRaw.toUpperCase(Locale.ROOT); if (!isValidDirection(latDir, true) || !isValidDirection(lonDir, false)) { return trimmed; } String formattedLat = convertToDegreeMinuteString(parts[0], latDir, true); String formattedLon = convertToDegreeMinuteString(parts[2], lonDir, false); if (formattedLat == null || formattedLon == null) { return trimmed; } return formattedLat + "," + latDir + "," + formattedLon + "," + lonDir; } private boolean isValidDirection(String direction, boolean isLatitude) { if (direction == null) { return false; } if (isLatitude) { return "N".equals(direction) || "S".equals(direction); } return "E".equals(direction) || "W".equals(direction); } // Converts decimal degrees or degree-minute input into a canonical degree-minute string (8 decimal places) private String convertToDegreeMinuteString(String rawValue, String direction, boolean isLatitude) { double decimal = parseCoordinateToDecimalDegrees(rawValue, direction, isLatitude); if (!Double.isFinite(decimal)) { return null; } double absDecimal = Math.abs(decimal); int degrees = (int) Math.floor(absDecimal); double minutes = (absDecimal - degrees) * 60.0d; double degreeMinutes = degrees * 100.0d + minutes; return String.format(Locale.US, "%.8f", degreeMinutes); } private double parseCoordinateToDecimalDegrees(String rawValue, String direction, boolean isLatitude) { if (rawValue == null) { return Double.NaN; } String trimmed = rawValue.trim(); if (trimmed.isEmpty()) { return Double.NaN; } double numeric; try { numeric = Double.parseDouble(trimmed); } catch (NumberFormatException ex) { return Double.NaN; } double abs = Math.abs(numeric); double boundary = isLatitude ? 90d : 180d; double decimal; if (abs <= boundary) { decimal = abs; } else { double degrees = Math.floor(abs / 100d); double minutes = abs - degrees * 100d; decimal = degrees + minutes / 60d; } String dirUpper = direction == null ? "" : direction.trim().toUpperCase(Locale.ROOT); if ("S".equals(dirUpper) || "W".equals(dirUpper)) { decimal = -decimal; } else if (!"N".equals(dirUpper) && !"E".equals(dirUpper) && numeric < 0d) { decimal = -decimal; } return decimal; } private void lockBaseStationPosition() { // 显示警告确认对话框 int result = JOptionPane.showConfirmDialog( this, "
" + "基准站位置锁定确认