张世豪
4 天以前 32c98d4855b6178554c787103dc956d161e152b3
src/zhangaiwu/AddDikuai.java
@@ -27,10 +27,13 @@
import dikuai.Dikuaiguanli;
import bianjie.bianjieguihua2;
import lujing.Lunjingguihua;
import set.Setsys;
import ui.UIConfig;
import zhuye.MowerLocationData;
import zhuye.Shouye;
import zhuye.Coordinate;
import zhuye.buttonset;
import gecaoji.Device;
/**
 * 新增地块对话框 - 多步骤表单设计
@@ -63,7 +66,10 @@
    private JTextField landNumberField;
    private JTextField areaNameField;
    private JComboBox<String> mowingPatternCombo;
    private JSpinner mowingWidthSpinner;
    private JTextField mowingWidthField; // 割草机割刀宽度
    private JTextField overlapDistanceField; // 相邻行重叠距离
    private JTextField mowingSafetyDistanceField; // 割草安全距离
    private JLabel calculatedMowingWidthLabel; // 计算后的割草宽度显示
    private JPanel previewPanel;
    private Map<String, JPanel> drawingOptionPanels = new HashMap<>();
    
@@ -290,10 +296,6 @@
        formGroup.add(areaNameField);
        formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
        formGroup.add(hintLabel);
    formGroup.add(Box.createRigidArea(new Dimension(0, 20)));
    JPanel obstacleSection = createObstacleSummarySection();
    formGroup.add(obstacleSection);
        
        stepPanel.add(formGroup);
        stepPanel.add(Box.createVerticalGlue());
@@ -741,8 +743,9 @@
                if (!optionPanel.isEnabled()) {
                    return;
                }
                selectDrawingOption(optionPanel, type);
                startEndDrawingBtn.setEnabled(true); // 选择后启用按钮
                if (selectDrawingOption(optionPanel, type, true)) {
                    startEndDrawingBtn.setEnabled(true); // 选择后启用按钮
                }
            }
            
            @Override
@@ -764,7 +767,15 @@
        return optionPanel;
    }
    
    private void selectDrawingOption(JPanel optionPanel, String type) {
    private boolean selectDrawingOption(JPanel optionPanel, String type, boolean userTriggered) {
        if (optionPanel == null) {
            return false;
        }
        if (userTriggered && "handheld".equalsIgnoreCase(type) && !hasConfiguredHandheldMarker()) {
            JOptionPane.showMessageDialog(this, "请先去系统设置添加便携打点器编号", "提示", JOptionPane.WARNING_MESSAGE);
            return false;
        }
        // 重置之前选中的选项
        if (selectedOptionPanel != null) {
            selectedOptionPanel.setBorder(BorderFactory.createLineBorder(BORDER_COLOR, 2));
@@ -774,7 +785,7 @@
                ((JLabel) oldTitle).setForeground(TEXT_COLOR);
            }
        }
        // 设置新的选中状态
        optionPanel.setBorder(BorderFactory.createLineBorder(PRIMARY_COLOR, 3));
        optionPanel.setBackground(PRIMARY_LIGHT);
@@ -783,9 +794,15 @@
            ((JLabel) titleObj).setForeground(PRIMARY_COLOR);
        }
        selectedOptionPanel = optionPanel;
        // 保存选择
        dikuaiData.put("drawingMethod", type);
        return true;
    }
    private boolean hasConfiguredHandheldMarker() {
        String handheldId = Setsys.getPropertyValue("handheldMarkerId");
        return handheldId != null && !handheldId.trim().isEmpty();
    }
    
    private void toggleDrawing() {
@@ -885,7 +902,7 @@
        settingsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        
        // 割草模式选择
        JPanel patternPanel = createFormGroup("割草模式", "选择割草路径的生成模式");
        JPanel patternPanel = createFormGroupWithoutHint("割草模式");
        mowingPatternCombo = new JComboBox<>(new String[]{"平行线", "螺旋式"});
        mowingPatternCombo.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        mowingPatternCombo.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
@@ -917,29 +934,27 @@
        
        patternPanel.add(mowingPatternCombo);
        settingsPanel.add(patternPanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 20)));
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        
        // 割草宽度设置
        JPanel widthPanel = createFormGroup("割草宽度", "设置割草机单次割草的宽度");
        // 割草机割刀宽度设置
        JPanel widthPanel = createFormGroupWithoutHint("割草机割刀宽度");
        JPanel widthInputPanel = new JPanel(new BorderLayout());
        widthInputPanel.setBackground(WHITE);
        widthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
        widthInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        
        SpinnerNumberModel widthModel = new SpinnerNumberModel(40, 20, 60, 1);
        mowingWidthSpinner = new JSpinner(widthModel);
        mowingWidthSpinner.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) mowingWidthSpinner.getEditor();
        editor.getTextField().setBorder(BorderFactory.createCompoundBorder(
        mowingWidthField = new JTextField("0.40");
        mowingWidthField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        mowingWidthField.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)
        ));
        
        // 添加微调器焦点效果
        mowingWidthSpinner.addFocusListener(new FocusAdapter() {
        // 添加文本框焦点效果和变化监听
        mowingWidthField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                editor.getTextField().setBorder(BorderFactory.createCompoundBorder(
                mowingWidthField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
@@ -947,27 +962,156 @@
            
            @Override
            public void focusLost(FocusEvent e) {
                editor.getTextField().setBorder(BorderFactory.createCompoundBorder(
                mowingWidthField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(BORDER_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
                updateCalculatedMowingWidth();
            }
        });
        JLabel unitLabel = new JLabel("米");
        unitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        unitLabel.setForeground(LIGHT_TEXT);
        unitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
        widthInputPanel.add(mowingWidthField, BorderLayout.CENTER);
        widthInputPanel.add(unitLabel, BorderLayout.EAST);
        widthPanel.add(widthInputPanel);
        settingsPanel.add(widthPanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        // 相邻行重叠距离设置
        JPanel overlapPanel = createFormGroupWithoutHint("相邻行重叠距离");
        JPanel overlapInputPanel = new JPanel(new BorderLayout());
        overlapInputPanel.setBackground(WHITE);
        overlapInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
        overlapInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        overlapDistanceField = new JTextField("0.06");
        overlapDistanceField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        overlapDistanceField.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)
        ));
        // 添加文本框焦点效果和变化监听
        overlapDistanceField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                overlapDistanceField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
            }
            @Override
            public void focusLost(FocusEvent e) {
                overlapDistanceField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(BORDER_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
                updateCalculatedMowingWidth();
            }
        });
        JLabel overlapUnitLabel = new JLabel("米");
        overlapUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        overlapUnitLabel.setForeground(LIGHT_TEXT);
        overlapUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
        overlapInputPanel.add(overlapDistanceField, BorderLayout.CENTER);
        overlapInputPanel.add(overlapUnitLabel, BorderLayout.EAST);
        overlapPanel.add(overlapInputPanel);
        settingsPanel.add(overlapPanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        // 割草宽度显示(自动计算,不可修改)
        JPanel calculatedWidthPanel = createFormGroup("割草宽度", "割草宽度 = 割草机割刀宽度 - 重叠距离(自动计算)");
        JPanel calculatedWidthDisplayPanel = new JPanel(new BorderLayout());
        calculatedWidthDisplayPanel.setBackground(LIGHT_GRAY);
        calculatedWidthDisplayPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
        calculatedWidthDisplayPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        calculatedWidthDisplayPanel.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)
        ));
        calculatedMowingWidthLabel = new JLabel("0.00 米");
        calculatedMowingWidthLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        calculatedMowingWidthLabel.setForeground(TEXT_COLOR);
        calculatedWidthDisplayPanel.add(calculatedMowingWidthLabel, BorderLayout.CENTER);
        calculatedWidthPanel.add(calculatedWidthDisplayPanel);
        settingsPanel.add(calculatedWidthPanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        // 割草安全距离
        JPanel safetyDistancePanel = createFormGroupWithoutHint("割草安全距离");
        JPanel safetyDistanceInputPanel = new JPanel(new BorderLayout());
        safetyDistanceInputPanel.setBackground(WHITE);
        safetyDistanceInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
        safetyDistanceInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        // 获取默认值
        String defaultSafetyDistance = "0.5"; // 默认值
        try {
            Device device = Device.getGecaoji();
            if (device != null) {
                String safetyDistance = device.getMowingSafetyDistance();
                if (safetyDistance != null && !safetyDistance.trim().isEmpty() && !"-1".equals(safetyDistance.trim())) {
                    defaultSafetyDistance = safetyDistance.trim();
                }
            }
        } catch (Exception e) {
            // 如果获取失败,使用默认值
        }
        mowingSafetyDistanceField = new JTextField(defaultSafetyDistance);
        mowingSafetyDistanceField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)
        ));
        // 添加文本框焦点效果
        mowingSafetyDistanceField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
            }
            @Override
            public void focusLost(FocusEvent e) {
                mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(BORDER_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
            }
        });
        
        JLabel unitLabel = new JLabel("厘米");
        unitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        unitLabel.setForeground(LIGHT_TEXT);
        unitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
        JLabel safetyDistanceUnitLabel = new JLabel("米");
        safetyDistanceUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        safetyDistanceUnitLabel.setForeground(LIGHT_TEXT);
        safetyDistanceUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
        
        widthInputPanel.add(mowingWidthSpinner, BorderLayout.CENTER);
        widthInputPanel.add(unitLabel, BorderLayout.EAST);
        safetyDistanceInputPanel.add(mowingSafetyDistanceField, BorderLayout.CENTER);
        safetyDistanceInputPanel.add(safetyDistanceUnitLabel, BorderLayout.EAST);
        
        widthPanel.add(widthInputPanel);
        settingsPanel.add(widthPanel);
        safetyDistancePanel.add(safetyDistanceInputPanel);
        settingsPanel.add(safetyDistancePanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 25)));
        
        stepPanel.add(settingsPanel);
        
        // 初始化计算后的割草宽度
        updateCalculatedMowingWidth();
        JButton generatePathButton = createPrimaryButton("生成割草路径", 16);
        generatePathButton.setAlignmentX(Component.LEFT_ALIGNMENT);
        generatePathButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, 55));
@@ -1003,6 +1147,41 @@
        return stepPanel;
    }
    
    /**
     * 更新计算后的割草宽度显示
     */
    private void updateCalculatedMowingWidth() {
        if (calculatedMowingWidthLabel == null || mowingWidthField == null || overlapDistanceField == null) {
            return;
        }
        try {
            String widthText = mowingWidthField.getText().trim();
            String overlapText = overlapDistanceField.getText().trim();
            if (widthText.isEmpty() || overlapText.isEmpty()) {
                calculatedMowingWidthLabel.setText("0.00 米");
                return;
            }
            double bladeWidthMeters = Double.parseDouble(widthText);
            double overlapMeters = Double.parseDouble(overlapText);
            double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
            if (calculatedWidthMeters <= 0) {
                calculatedMowingWidthLabel.setText("无效(割刀宽度需大于重叠距离)");
                calculatedMowingWidthLabel.setForeground(ERROR_COLOR);
            } else {
                calculatedMowingWidthLabel.setText(String.format(Locale.US, "%.2f 米", calculatedWidthMeters));
                calculatedMowingWidthLabel.setForeground(TEXT_COLOR);
            }
        } catch (NumberFormatException e) {
            calculatedMowingWidthLabel.setText("0.00 米");
        } catch (Exception e) {
            calculatedMowingWidthLabel.setText("0.00 米");
        }
    }
    private JPanel createInstructionPanel(String text) {
        JPanel instructionPanel = new JPanel(new BorderLayout());
        instructionPanel.setBackground(PRIMARY_LIGHT);
@@ -1053,6 +1232,23 @@
        
        return formGroup;
    }
    private JPanel createFormGroupWithoutHint(String label) {
        JPanel formGroup = new JPanel();
        formGroup.setLayout(new BoxLayout(formGroup, BoxLayout.Y_AXIS));
        formGroup.setBackground(WHITE);
        formGroup.setAlignmentX(Component.LEFT_ALIGNMENT);
        JLabel nameLabel = new JLabel(label);
        nameLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
        nameLabel.setForeground(TEXT_COLOR);
        nameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        formGroup.add(nameLabel);
        formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
        return formGroup;
    }
    private void generateMowingPath() {
        if (!dikuaiData.containsKey("boundaryDrawn")) {
@@ -1091,25 +1287,79 @@
        String patternDisplay = (String) mowingPatternCombo.getSelectedItem();
        dikuaiData.put("mowingPattern", patternDisplay);
        Object widthObj = mowingWidthSpinner.getValue();
        if (!(widthObj instanceof Number)) {
            JOptionPane.showMessageDialog(this, "割草宽度输入无效", "提示", JOptionPane.WARNING_MESSAGE);
        String widthText = mowingWidthField.getText().trim();
        if (widthText.isEmpty()) {
            JOptionPane.showMessageDialog(this, "割草机割刀宽度不能为空", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("割草宽度输入无效,请重新输入。", false);
            showPathGenerationMessage("割草机割刀宽度不能为空,请重新输入。", false);
            setPathAvailability(false);
            return;
        }
        double widthCm = ((Number) widthObj).doubleValue();
        if (widthCm <= 0) {
            JOptionPane.showMessageDialog(this, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE);
        double bladeWidthMeters;
        try {
            bladeWidthMeters = Double.parseDouble(widthText);
        } catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(this, "割草机割刀宽度输入无效", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("割草宽度必须大于0,请重新设置。", false);
            showPathGenerationMessage("割草机割刀宽度输入无效,请重新输入。", false);
            setPathAvailability(false);
            return;
        }
        dikuaiData.put("mowingWidth", widthObj.toString());
        if (bladeWidthMeters <= 0) {
            JOptionPane.showMessageDialog(this, "割草机割刀宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("割草机割刀宽度必须大于0,请重新设置。", false);
            setPathAvailability(false);
            return;
        }
        // 保存割草机割刀宽度(米)
        dikuaiData.put("mowingBladeWidth", String.format(Locale.US, "%.2f", bladeWidthMeters));
        // 获取重叠距离
        String overlapText = overlapDistanceField.getText().trim();
        if (overlapText.isEmpty()) {
            JOptionPane.showMessageDialog(this, "相邻行重叠距离不能为空", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("相邻行重叠距离不能为空,请重新输入。", false);
            setPathAvailability(false);
            return;
        }
        double overlapMeters;
        try {
            overlapMeters = Double.parseDouble(overlapText);
        } catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(this, "相邻行重叠距离输入无效", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("相邻行重叠距离输入无效,请重新输入。", false);
            setPathAvailability(false);
            return;
        }
        if (overlapMeters < 0) {
            JOptionPane.showMessageDialog(this, "相邻行重叠距离不能为负数", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("相邻行重叠距离不能为负数,请重新设置。", false);
            setPathAvailability(false);
            return;
        }
        // 保存重叠距离到地块数据
        dikuaiData.put("mowingOverlapDistance", String.format(Locale.US, "%.2f", overlapMeters));
        // 计算实际使用的割草宽度(割刀宽度 - 重叠距离)
        double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
        if (calculatedWidthMeters <= 0) {
            JOptionPane.showMessageDialog(this, "计算后的割草宽度必须大于0(割刀宽度必须大于重叠距离)", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("计算后的割草宽度必须大于0,请调整割刀宽度或重叠距离。", false);
            setPathAvailability(false);
            return;
        }
        String widthMeters = String.format(Locale.US, "%.2f", widthCm / 100.0);
        // 保存割草宽度(计算后的值,转换为厘米存储,保持与原有数据格式兼容)
        double calculatedWidthCm = calculatedWidthMeters * 100.0;
        dikuaiData.put("mowingWidth", String.format(Locale.US, "%.0f", calculatedWidthCm));
        String widthMeters = String.format(Locale.US, "%.2f", calculatedWidthMeters);
        String plannerMode = resolvePlannerMode(patternDisplay);
        try {
@@ -1232,13 +1482,51 @@
            }
        }
        if (mowingWidthSpinner != null) {
            Object widthValue = mowingWidthSpinner.getValue();
            if (widthValue instanceof Number) {
                int widthInt = ((Number) widthValue).intValue();
                dikuaiData.put("mowingWidth", Integer.toString(widthInt));
            } else if (widthValue != null) {
                dikuaiData.put("mowingWidth", widthValue.toString());
        // 保存割草机割刀宽度
        if (mowingWidthField != null) {
            String bladeWidthText = mowingWidthField.getText().trim();
            if (!bladeWidthText.isEmpty()) {
                try {
                    double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                    dikuaiData.put("mowingBladeWidth", String.format(Locale.US, "%.2f", bladeWidthMeters));
                } catch (NumberFormatException e) {
                    // 如果解析失败,直接保存文本
                    dikuaiData.put("mowingBladeWidth", bladeWidthText);
                }
            }
        }
        // 保存相邻行重叠距离
        if (overlapDistanceField != null) {
            String overlapText = overlapDistanceField.getText().trim();
            if (!overlapText.isEmpty()) {
                try {
                    double overlapMeters = Double.parseDouble(overlapText);
                    dikuaiData.put("mowingOverlapDistance", String.format(Locale.US, "%.2f", overlapMeters));
                } catch (NumberFormatException e) {
                    // 如果解析失败,直接保存文本
                    dikuaiData.put("mowingOverlapDistance", overlapText);
                }
            }
        }
        // 计算并保存割草宽度(割刀宽度 - 重叠距离)
        if (mowingWidthField != null && overlapDistanceField != null) {
            try {
                String bladeWidthText = mowingWidthField.getText().trim();
                String overlapText = overlapDistanceField.getText().trim();
                if (!bladeWidthText.isEmpty() && !overlapText.isEmpty()) {
                    double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                    double overlapMeters = Double.parseDouble(overlapText);
                    double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
                    if (calculatedWidthMeters > 0) {
                        // 转换为厘米存储,保持与原有数据格式兼容
                        int widthCm = (int) Math.round(calculatedWidthMeters * 100.0);
                        dikuaiData.put("mowingWidth", Integer.toString(widthCm));
                    }
                }
            } catch (NumberFormatException e) {
                // 如果计算失败,不保存割草宽度
            }
        }
    }
@@ -1260,18 +1548,14 @@
    }
    
    private JButton createPrimaryButton(String text, int fontSize) {
        JButton button = new JButton(text);
        JButton button = buttonset.createStyledButton(text, PRIMARY_COLOR);
        button.setFont(new Font("微软雅黑", Font.BOLD, fontSize));
        button.setBackground(PRIMARY_COLOR);
        button.setForeground(WHITE);
        button.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(PRIMARY_DARK, 2),
            BorderFactory.createEmptyBorder(12, 25, 12, 25)
        ));
        button.setFocusPainted(false);
        button.setCursor(new Cursor(Cursor.HAND_CURSOR));
        // 按钮悬停效果
        button.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e) {
@@ -1279,7 +1563,7 @@
                    button.setBackground(PRIMARY_DARK);
                }
            }
            @Override
            public void mouseExited(MouseEvent e) {
                if (button.isEnabled()) {
@@ -1287,7 +1571,7 @@
                }
            }
        });
        return button;
    }
@@ -1335,15 +1619,13 @@
        buttonPanel.setBackground(WHITE);
        buttonPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0));
        prevButton = new JButton("上一步");
        prevButton = buttonset.createStyledButton("上一步", MEDIUM_GRAY);
        prevButton.setFont(new Font("微软雅黑", Font.BOLD, 16));
        prevButton.setBackground(MEDIUM_GRAY);
        prevButton.setForeground(TEXT_COLOR);
        prevButton.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 25, 10, 25)
        ));
        prevButton.setFocusPainted(false);
        prevButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
        nextButton = createPrimaryButton("下一步", 16);
@@ -1647,10 +1929,6 @@
    private void showStep(int step) {
        currentStep = step;
        cardLayout.show(stepsPanel, "step" + step);
        if (step == 1) {
            updateObstacleSummary();
        }
        // 更新按钮状态
        updateButtonState(step);
@@ -1721,7 +1999,53 @@
            case 3:
                dikuaiData.put("mowingPattern", (String) mowingPatternCombo.getSelectedItem());
                dikuaiData.put("mowingWidth", mowingWidthSpinner.getValue().toString());
                // 保存割草机割刀宽度
                if (mowingWidthField != null) {
                    String bladeWidthText = mowingWidthField.getText().trim();
                    if (!bladeWidthText.isEmpty()) {
                        try {
                            double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                            dikuaiData.put("mowingBladeWidth", String.format(Locale.US, "%.2f", bladeWidthMeters));
                        } catch (NumberFormatException e) {
                            dikuaiData.put("mowingBladeWidth", bladeWidthText);
                        }
                    }
                }
                // 保存相邻行重叠距离
                if (overlapDistanceField != null) {
                    String overlapText = overlapDistanceField.getText().trim();
                    if (!overlapText.isEmpty()) {
                        try {
                            double overlapMeters = Double.parseDouble(overlapText);
                            dikuaiData.put("mowingOverlapDistance", String.format(Locale.US, "%.2f", overlapMeters));
                        } catch (NumberFormatException e) {
                            dikuaiData.put("mowingOverlapDistance", overlapText);
                        }
                    }
                }
                // 计算并保存割草宽度(割刀宽度 - 重叠距离)
                if (mowingWidthField != null && overlapDistanceField != null) {
                    try {
                        String bladeWidthText = mowingWidthField.getText().trim();
                        String overlapText = overlapDistanceField.getText().trim();
                        if (!bladeWidthText.isEmpty() && !overlapText.isEmpty()) {
                            double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                            double overlapMeters = Double.parseDouble(overlapText);
                            double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
                            if (calculatedWidthMeters > 0) {
                                // 转换为厘米存储,保持与原有数据格式兼容
                                int widthCm = (int) Math.round(calculatedWidthMeters * 100.0);
                                dikuaiData.put("mowingWidth", Integer.toString(widthCm));
                            }
                        }
                    } catch (NumberFormatException e) {
                        // 如果计算失败,不保存割草宽度
                    }
                }
                if (!hasGeneratedPath()) {
                    JOptionPane.showMessageDialog(this, "请先生成割草路径", "提示", JOptionPane.WARNING_MESSAGE);
                    return false;
@@ -1951,7 +2275,7 @@
        if (method != null) {
            JPanel panel = drawingOptionPanels.get(method);
            if (panel != null) {
                selectDrawingOption(panel, method);
                selectDrawingOption(panel, method, false);
            }
        }
@@ -2002,21 +2326,51 @@
            }
        }
        if (mowingWidthSpinner != null) {
            String width = data.get("mowingWidth");
            if (isMeaningfulValue(width)) {
        // 恢复割草机割刀宽度(优先从mowingBladeWidth获取)
        if (mowingWidthField != null) {
            String bladeWidth = data.get("mowingBladeWidth");
            if (isMeaningfulValue(bladeWidth)) {
                try {
                    double parsed = Double.parseDouble(width.trim());
                    SpinnerNumberModel model = (SpinnerNumberModel) mowingWidthSpinner.getModel();
                    int min = ((Number) model.getMinimum()).intValue();
                    int max = ((Number) model.getMaximum()).intValue();
                    int rounded = (int) Math.round(parsed);
                    if (rounded < min) {
                        rounded = min;
                    } else if (rounded > max) {
                        rounded = max;
                    double bladeWidthMeters = Double.parseDouble(bladeWidth.trim());
                    mowingWidthField.setText(String.format(Locale.US, "%.2f", bladeWidthMeters));
                } catch (NumberFormatException ignored) {
                    // 如果mowingBladeWidth不存在或解析失败,尝试从mowingWidth恢复
                    String width = data.get("mowingWidth");
                    if (isMeaningfulValue(width)) {
                        try {
                            // 如果存储的是厘米,转换为米显示
                            double parsed = Double.parseDouble(width.trim());
                            // 假设如果值大于10,则是厘米,需要转换为米;否则已经是米
                            double widthMeters = parsed > 10 ? parsed / 100.0 : parsed;
                            mowingWidthField.setText(String.format(Locale.US, "%.2f", widthMeters));
                        } catch (NumberFormatException ignored2) {
                            // 保持当前值
                        }
                    }
                    mowingWidthSpinner.setValue(rounded);
                }
            } else {
                // 如果mowingBladeWidth不存在,尝试从mowingWidth恢复
                String width = data.get("mowingWidth");
                if (isMeaningfulValue(width)) {
                    try {
                        // 如果存储的是厘米,转换为米显示
                        double parsed = Double.parseDouble(width.trim());
                        // 假设如果值大于10,则是厘米,需要转换为米;否则已经是米
                        double widthMeters = parsed > 10 ? parsed / 100.0 : parsed;
                        mowingWidthField.setText(String.format(Locale.US, "%.2f", widthMeters));
                    } catch (NumberFormatException ignored) {
                        // 保持当前值
                    }
                }
            }
        }
        if (overlapDistanceField != null) {
            String overlap = data.get("mowingOverlapDistance");
            if (isMeaningfulValue(overlap)) {
                try {
                    double overlapMeters = Double.parseDouble(overlap.trim());
                    overlapDistanceField.setText(String.format(Locale.US, "%.2f", overlapMeters));
                } catch (NumberFormatException ignored) {
                    // 保持当前值
                }
@@ -2125,8 +2479,60 @@
        if (dikuaiData.containsKey("mowingPattern")) {
            dikuai.setMowingPattern(dikuaiData.get("mowingPattern"));
        }
        // 保存割草机割刀宽度(优先从dikuaiData获取,否则从TextField获取)
        if (dikuaiData.containsKey("mowingBladeWidth")) {
            dikuai.setMowingBladeWidth(dikuaiData.get("mowingBladeWidth"));
        } else if (mowingWidthField != null) {
            String bladeWidthText = mowingWidthField.getText().trim();
            if (!bladeWidthText.isEmpty()) {
                try {
                    double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                    dikuai.setMowingBladeWidth(String.format(Locale.US, "%.2f", bladeWidthMeters));
                } catch (NumberFormatException e) {
                    dikuai.setMowingBladeWidth(bladeWidthText);
                }
            }
        }
        // 保存相邻行重叠距离(优先从dikuaiData获取,否则从TextField获取)
        if (dikuaiData.containsKey("mowingOverlapDistance")) {
            dikuai.setMowingOverlapDistance(dikuaiData.get("mowingOverlapDistance"));
        } else if (overlapDistanceField != null) {
            String overlapText = overlapDistanceField.getText().trim();
            if (!overlapText.isEmpty()) {
                try {
                    double overlapMeters = Double.parseDouble(overlapText);
                    dikuai.setMowingOverlapDistance(String.format(Locale.US, "%.2f", overlapMeters));
                } catch (NumberFormatException e) {
                    dikuai.setMowingOverlapDistance(overlapText);
                }
            }
        }
        // 保存割草宽度(计算后的值,优先从dikuaiData获取)
        if (dikuaiData.containsKey("mowingWidth")) {
            dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
        } else {
            // 如果没有在dikuaiData中,则从TextField计算
            if (mowingWidthField != null && overlapDistanceField != null) {
                try {
                    String bladeWidthText = mowingWidthField.getText().trim();
                    String overlapText = overlapDistanceField.getText().trim();
                    if (!bladeWidthText.isEmpty() && !overlapText.isEmpty()) {
                        double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                        double overlapMeters = Double.parseDouble(overlapText);
                        double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
                        if (calculatedWidthMeters > 0) {
                            // 转换为厘米存储,保持与原有数据格式兼容
                            int widthCm = (int) Math.round(calculatedWidthMeters * 100.0);
                            dikuai.setMowingWidth(Integer.toString(widthCm));
                        }
                    }
                } catch (NumberFormatException e) {
                    // 如果计算失败,保持原有值或使用默认值
                }
            }
        }
        String plannedPath = dikuaiData.get("plannedPath");