张世豪
4 天以前 a3b05960fe629e9006b45d61618b01f724e757fd
src/dikuai/Dikuaiguanli.java
@@ -30,6 +30,7 @@
import zhuye.MapRenderer;
import zhuye.Shouye;
import zhuye.Coordinate;
import gecaoji.Device;
/**
 * 地块管理面板 - 卡片式布局设计
@@ -71,7 +72,7 @@
   private ImageIcon workUnselectedIcon;
   private ImageIcon boundaryVisibleIcon;
   private ImageIcon boundaryHiddenIcon;
   private static final int BOUNDARY_TOGGLE_ICON_SIZE = 48;
   private static final int BOUNDARY_TOGGLE_ICON_SIZE = 24;
   private Map<String, ObstacleSummary> obstacleSummaryCache = Collections.emptyMap();
   public Dikuaiguanli(String landNumber) {
@@ -165,7 +166,7 @@
         for (Dikuai dikuai : allDikuai.values()) {
            JPanel card = createDikuaiCard(dikuai);
            cardsPanel.add(card);
            cardsPanel.add(Box.createRigidArea(new Dimension(0, 15)));
            cardsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
         }
      }
      
@@ -245,11 +246,11 @@
      
      // 地块编号
      contentPanel.add(createCardInfoItem("地块编号:", getDisplayValue(dikuai.getLandNumber(), "未知")));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      // 添加时间
      contentPanel.add(createCardInfoItem("添加时间:", getDisplayValue(dikuai.getCreateTime(), "未知")));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      // 地块面积
      String landArea = dikuai.getLandArea();
@@ -259,23 +260,68 @@
         landArea = "未知";
      }
      contentPanel.add(createCardInfoItem("地块面积:", landArea));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      JPanel mowingPatternPanel = createCardInfoItem("割草模式:",
         formatMowingPatternForDisplay(dikuai.getMowingPattern()));
      configureInteractiveLabel(getInfoItemTitleLabel(mowingPatternPanel),
         () -> editMowingPattern(dikuai),
         "点击查看/编辑割草模式");
      contentPanel.add(mowingPatternPanel);
      contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      // 割草机割刀宽度
      String mowingBladeWidthValue = dikuai.getMowingBladeWidth();
      String displayBladeWidth = "未设置";
      if (mowingBladeWidthValue != null && !"-1".equals(mowingBladeWidthValue) && !mowingBladeWidthValue.trim().isEmpty()) {
         try {
            double bladeWidthMeters = Double.parseDouble(mowingBladeWidthValue.trim());
            double bladeWidthCm = bladeWidthMeters * 100.0;
            displayBladeWidth = String.format("%.2f厘米", bladeWidthCm);
         } catch (NumberFormatException e) {
            displayBladeWidth = "未设置";
         }
      }
      JPanel mowingBladeWidthPanel = createCardInfoItem("割草机割刀宽度:", displayBladeWidth);
      contentPanel.add(mowingBladeWidthPanel);
      contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      String mowingWidthValue = dikuai.getMowingWidth();
      String displayWidth = "未设置";
      if (mowingWidthValue != null && !"-1".equals(mowingWidthValue) && !mowingWidthValue.trim().isEmpty()) {
         displayWidth = mowingWidthValue + "厘米";
      }
      JPanel mowingWidthPanel = createCardInfoItem("割草宽度:", displayWidth);
      contentPanel.add(mowingWidthPanel);
      contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      // 割草安全距离
      String displaySafetyDistance = "未设置";
      Device device = Device.getActiveDevice();
      if (device != null) {
         String safetyDistanceValue = device.getMowingSafetyDistance();
         if (safetyDistanceValue != null && !"-1".equals(safetyDistanceValue) && !safetyDistanceValue.trim().isEmpty()) {
            try {
               double distanceMeters = Double.parseDouble(safetyDistanceValue.trim());
               // 如果值大于100,认为是厘米,需要转换为米
               if (distanceMeters > 100) {
                  distanceMeters = distanceMeters / 100.0;
               }
               displaySafetyDistance = String.format("%.2f米", distanceMeters);
            } catch (NumberFormatException e) {
               displaySafetyDistance = "未设置";
            }
         }
      }
      JPanel mowingSafetyDistancePanel = createCardInfoItem("割草安全距离:", displaySafetyDistance);
      contentPanel.add(mowingSafetyDistancePanel);
      contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      // 返回点坐标(带修改按钮)
      contentPanel.add(createCardInfoItemWithButton("返回点坐标:",
         getDisplayValue(dikuai.getReturnPointCoordinates(), "未设置"),
         "修改", e -> editReturnPoint(dikuai)));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
      // 地块边界坐标(带显示顶点按钮)
      JPanel boundaryPanel = createBoundaryInfoItem(dikuai,
         getTruncatedValue(dikuai.getBoundaryCoordinates(), 12, "未设置"));
      setInfoItemTooltip(boundaryPanel, dikuai.getBoundaryCoordinates());
      configureInteractiveLabel(getInfoItemTitleLabel(boundaryPanel),
         () -> editBoundaryCoordinates(dikuai),
         "点击查看/编辑地块边界坐标");
      contentPanel.add(boundaryPanel);
   contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      ObstacleSummary obstacleSummary = getObstacleSummaryFromCache(dikuai.getLandNumber());
      JPanel obstaclePanel = createCardInfoItemWithButton("障碍物:",
@@ -288,61 +334,40 @@
         () -> showObstacleManagementPage(dikuai),
         "点击查看/管理障碍物");
      contentPanel.add(obstaclePanel);
   contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      // 地块边界坐标(带显示顶点按钮)
      JPanel boundaryPanel = createBoundaryInfoItem(dikuai);
      configureInteractiveLabel(getInfoItemTitleLabel(boundaryPanel),
         () -> editBoundaryCoordinates(dikuai),
         "点击查看/编辑地块边界坐标");
      contentPanel.add(boundaryPanel);
   contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      // 路径坐标(带查看按钮)
      JPanel pathPanel = createCardInfoItemWithButton("路径坐标:",
         getTruncatedValue(dikuai.getPlannedPath(), 12, "未设置"),
         "复制", e -> copyCoordinatesAction("路径坐标", dikuai.getPlannedPath()));
      setInfoItemTooltip(pathPanel, dikuai.getPlannedPath());
      JPanel pathPanel = createCardInfoItemWithButtonOnly("路径坐标:",
         "查看", e -> editPlannedPath(dikuai));
      configureInteractiveLabel(getInfoItemTitleLabel(pathPanel),
         () -> editPlannedPath(dikuai),
         "点击查看/编辑路径坐标");
      contentPanel.add(pathPanel);
   contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
   contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      JPanel baseStationPanel = createCardInfoItemWithButton("基站坐标:",
         getTruncatedValue(dikuai.getBaseStationCoordinates(), 12, "未设置"),
         "复制", e -> copyCoordinatesAction("基站坐标", dikuai.getBaseStationCoordinates()));
      setInfoItemTooltip(baseStationPanel, dikuai.getBaseStationCoordinates());
      configureInteractiveLabel(getInfoItemTitleLabel(baseStationPanel),
         () -> editBaseStationCoordinates(dikuai),
         "点击查看/编辑基站坐标");
      contentPanel.add(baseStationPanel);
   contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
   JPanel baseStationPanel = createCardInfoItemWithButtonOnly("基站坐标:",
      "查看", e -> editBaseStationCoordinates(dikuai));
   configureInteractiveLabel(getInfoItemTitleLabel(baseStationPanel),
      () -> editBaseStationCoordinates(dikuai),
      "点击查看/编辑基站坐标");
   contentPanel.add(baseStationPanel);
   contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      JPanel boundaryOriginalPanel = createCardInfoItemWithButton("边界原始坐标:",
         getTruncatedValue(dikuai.getBoundaryOriginalCoordinates(), 12, "未设置"),
         "复制", e -> copyCoordinatesAction("边界原始坐标", dikuai.getBoundaryOriginalCoordinates()));
      setInfoItemTooltip(boundaryOriginalPanel, dikuai.getBoundaryOriginalCoordinates());
      configureInteractiveLabel(getInfoItemTitleLabel(boundaryOriginalPanel),
         () -> editBoundaryOriginalCoordinates(dikuai),
         "点击查看/编辑边界原始坐标");
      contentPanel.add(boundaryOriginalPanel);
      contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
      JPanel mowingPatternPanel = createCardInfoItemWithButton("割草模式:",
         getTruncatedValue(dikuai.getMowingPattern(), 12, "未设置"),
         "复制", e -> copyCoordinatesAction("割草模式", dikuai.getMowingPattern()));
      setInfoItemTooltip(mowingPatternPanel, dikuai.getMowingPattern());
      configureInteractiveLabel(getInfoItemTitleLabel(mowingPatternPanel),
         () -> editMowingPattern(dikuai),
         "点击查看/编辑割草模式");
      contentPanel.add(mowingPatternPanel);
      contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
      String mowingWidthValue = dikuai.getMowingWidth();
      String widthSource = null;
      if (mowingWidthValue != null && !"-1".equals(mowingWidthValue) && !mowingWidthValue.trim().isEmpty()) {
         widthSource = mowingWidthValue + "厘米";
      }
      String displayWidth = getTruncatedValue(widthSource, 12, "未设置");
      JPanel mowingWidthPanel = createCardInfoItemWithButton("割草宽度:",
         displayWidth,
         "编辑", e -> editMowingWidth(dikuai));
      setInfoItemTooltip(mowingWidthPanel, widthSource);
      contentPanel.add(mowingWidthPanel);
      contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
   JPanel boundaryOriginalPanel = createCardInfoItemWithButtonOnly("边界原始坐标:",
      "查看", e -> editBoundaryOriginalCoordinates(dikuai));
   configureInteractiveLabel(getInfoItemTitleLabel(boundaryOriginalPanel),
      () -> editBoundaryOriginalCoordinates(dikuai),
      "点击查看/编辑边界原始坐标");
   contentPanel.add(boundaryOriginalPanel);
      contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      JPanel completedTrackPanel = createCardInfoItemWithButton("已完成割草路径:",
         getTruncatedValue(dikuai.getMowingTrack(), 12, "未记录"),
@@ -361,11 +386,16 @@
      JButton generatePathBtn = createPrimaryFooterButton("路径规划");
      generatePathBtn.addActionListener(e -> showPathPlanningPage(dikuai));
      JButton navigationPreviewBtn = createPrimaryFooterButton("导航预览");
      navigationPreviewBtn.addActionListener(e -> startNavigationPreview(dikuai));
      JPanel footerPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
      footerPanel.setBackground(CARD_BACKGROUND);
      footerPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
      footerPanel.add(generatePathBtn);
      footerPanel.add(Box.createHorizontalStrut(12));
      footerPanel.add(navigationPreviewBtn);
      footerPanel.add(Box.createHorizontalStrut(12));
      footerPanel.add(deleteBtn);
      card.add(footerPanel, BorderLayout.SOUTH);
@@ -387,6 +417,7 @@
      
      itemPanel.add(labelComp, BorderLayout.WEST);
      itemPanel.add(valueComp, BorderLayout.EAST);
      itemPanel.putClientProperty("titleLabel", labelComp);
      
      return itemPanel;
   }
@@ -425,15 +456,43 @@
      return itemPanel;
   }
      private JPanel createBoundaryInfoItem(Dikuai dikuai, String displayValue) {
   private JPanel createCardInfoItemWithButtonOnly(String label, String buttonText, ActionListener listener) {
      JPanel itemPanel = new JPanel(new BorderLayout());
      itemPanel.setBackground(CARD_BACKGROUND);
      // 增加高度以确保按钮完整显示(按钮高度约24-28像素,加上上下边距)
      itemPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 35));
      itemPanel.setPreferredSize(new Dimension(Integer.MAX_VALUE, 30));
      itemPanel.setMinimumSize(new Dimension(0, 28));
      JLabel labelComp = new JLabel(label);
      labelComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
      labelComp.setForeground(LIGHT_TEXT);
      JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
      rightPanel.setBackground(CARD_BACKGROUND);
      // 添加垂直内边距以确保按钮不被裁剪
      rightPanel.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));
      JButton button = createSmallLinkButton(buttonText, listener);
      rightPanel.add(button);
      itemPanel.add(labelComp, BorderLayout.WEST);
      itemPanel.add(rightPanel, BorderLayout.CENTER);
      itemPanel.putClientProperty("titleLabel", labelComp);
      return itemPanel;
   }
      private JPanel createBoundaryInfoItem(Dikuai dikuai) {
         JPanel itemPanel = new JPanel(new BorderLayout());
         itemPanel.setBackground(CARD_BACKGROUND);
         // 增加高度以确保按钮下边缘完整显示(按钮高度56,加上上下边距)
         int rowHeight = Math.max(60, BOUNDARY_TOGGLE_ICON_SIZE + 16);
         // 增加高度以确保按钮下边缘完整显示(按钮高度28,加上上下边距)
         int rowHeight = Math.max(30, BOUNDARY_TOGGLE_ICON_SIZE + 8);
         Dimension rowDimension = new Dimension(Integer.MAX_VALUE, rowHeight);
         itemPanel.setMaximumSize(rowDimension);
         itemPanel.setPreferredSize(rowDimension);
         itemPanel.setMinimumSize(new Dimension(0, 56));
         itemPanel.setMinimumSize(new Dimension(0, 28));
         JLabel labelComp = new JLabel("地块边界:");
         labelComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
@@ -445,18 +504,25 @@
         rightPanel.setBackground(CARD_BACKGROUND);
         rightPanel.setBorder(BorderFactory.createEmptyBorder(verticalPadding, 0, verticalPadding, 0));
         JLabel valueComp = new JLabel(displayValue);
         valueComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
         valueComp.setForeground(TEXT_COLOR);
         // 状态提示文字标签
         JLabel statusLabel = new JLabel();
         statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
         statusLabel.setForeground(LIGHT_TEXT);
         JButton toggleButton = createBoundaryToggleButton(dikuai);
         // 将状态标签和按钮关联,以便在按钮状态变化时更新标签
         toggleButton.putClientProperty("statusLabel", statusLabel);
         rightPanel.add(valueComp);
         // 初始化状态文字
         String landNumber = dikuai.getLandNumber();
         boolean isVisible = boundaryPointVisibility.getOrDefault(landNumber, false);
         updateBoundaryStatusLabel(statusLabel, isVisible);
         rightPanel.add(statusLabel);
         rightPanel.add(toggleButton);
         itemPanel.add(labelComp, BorderLayout.WEST);
         itemPanel.add(rightPanel, BorderLayout.CENTER);
         itemPanel.putClientProperty("valueLabel", valueComp);
         itemPanel.putClientProperty("titleLabel", labelComp);
         return itemPanel;
@@ -471,7 +537,7 @@
         button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
         button.setMargin(new Insets(0, 0, 0, 0));
         button.setIconTextGap(0);
         button.setPreferredSize(new Dimension(56, 56));
         button.setPreferredSize(new Dimension(28, 28));
         String landNumber = dikuai.getLandNumber();
         boolean isVisible = boundaryPointVisibility.getOrDefault(landNumber, false);
@@ -499,6 +565,24 @@
            button.setOpaque(true);
         }
         button.setToolTipText(active ? "隐藏边界点序号" : "显示边界点序号");
         // 更新状态提示文字
         Object statusLabelObj = button.getClientProperty("statusLabel");
         if (statusLabelObj instanceof JLabel) {
            JLabel statusLabel = (JLabel) statusLabelObj;
            updateBoundaryStatusLabel(statusLabel, active);
         }
      }
      private void updateBoundaryStatusLabel(JLabel statusLabel, boolean active) {
         if (statusLabel == null) {
            return;
         }
         if (active) {
            statusLabel.setText("已开启边界点显示");
         } else {
            statusLabel.setText("已关闭边界点显示");
         }
      }
      private void ensureBoundaryToggleIconsLoaded() {
@@ -859,6 +943,31 @@
   }
   /**
    * 启动导航预览
    */
   private void startNavigationPreview(Dikuai dikuai) {
      if (dikuai == null) {
         return;
      }
      Window owner = SwingUtilities.getWindowAncestor(this);
      // 获取地块管理对话框,准备在打开导航预览时关闭
      Window managementWindow = null;
      if (owner instanceof JDialog) {
         managementWindow = owner;
      }
      // 关闭地块管理页面
      if (managementWindow != null) {
         managementWindow.dispose();
      }
      // 启动导航预览
      daohangyulan.getInstance().startNavigationPreview(dikuai);
   }
   /**
    * 显示路径规划页面
    */
   private void showPathPlanningPage(Dikuai dikuai) {
@@ -940,6 +1049,7 @@
      dialog.setVisible(true);
   }
   private void generateMowingPath(Dikuai dikuai) {
      if (dikuai == null) {
         return;
@@ -1183,6 +1293,21 @@
      return section;
   }
   private String formatMowingPatternForDisplay(String patternValue) {
      String sanitized = sanitizeValueOrNull(patternValue);
      if (sanitized == null) {
         return "未设置";
      }
      String normalized = normalizeExistingMowingPattern(sanitized);
      if ("parallel".equals(normalized)) {
         return "平行模式 (parallel)";
      }
      if ("spiral".equals(normalized)) {
         return "螺旋模式 (spiral)";
      }
      return sanitized;
   }
   private String formatMowingPatternForDialog(String patternValue) {
      String sanitized = sanitizeValueOrNull(patternValue);
      if (sanitized == null) {