32c98d4855b6178554c787103dc956d161e152b3..b518f895dec5264fd25e22a68300c40ceba6f43d
3 天以前 826220679@qq.com
新增了按钮功能
b518f8 对比 | 目录
3 天以前 826220679@qq.com
优化了地块管理文字变成图标
96f963 对比 | 目录
已添加3个文件
已修改10个文件
917 ■■■■ 文件已修改
.classpath 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.project 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
dikuai.properties 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
image/look.png 补丁 | 查看 | 原始文档 | blame | 历史
set.properties 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/dikuai/Dikuai.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/dikuai/Dikuaiguanli.java 263 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/dikuai/addzhangaiwu.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lujing/MowingPathGenerationPage.java 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhangaiwu/AddDikuai.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhuye/Fuzhibutton.java 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhuye/Lookbutton.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhuye/buttonset.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.classpath
@@ -6,9 +6,9 @@
            <attribute name="module" value="true"/>
        </attributes>
    </classpathentry>
    <classpathentry kind="lib" path="lib/jSerialComm-2.10.4.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/jts-core-1.19.0.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/slf4j-api-1.7.30.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/slf4j-simple-1.7.30.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/jSerialComm-2.10.4.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/jts-core-1.19.0.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/slf4j-api-1.7.30.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/slf4j-simple-1.7.30.jar"/>
    <classpathentry kind="output" path="bin"/>
</classpath>
.project
@@ -14,4 +14,15 @@
    <natures>
        <nature>org.eclipse.jdt.core.javanature</nature>
    </natures>
    <filteredResources>
        <filter>
            <id>1766205615208</id>
            <name></name>
            <type>30</type>
            <matcher>
                <id>org.eclipse.core.resources.regexFilterMatcher</id>
                <arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
            </matcher>
        </filter>
    </filteredResources>
</projectDescription>
dikuai.properties
@@ -1,21 +1,44 @@
#Dikuai Properties
#Fri Dec 19 19:44:17 CST 2025
LAND1.angleThreshold=-1
#Sat Dec 20 14:47:09 GMT+08:00 2025
LAND2.boundaryCoordinates=5.38,-6.41;-11.15,-8.94;-12.75,-4.68;-12.23,-3.28;-11.12,-3.17;-9.29,-3.53;-7.46,-3.89;-5.62,-4.25;-3.79,-4.61;-1.96,-4.97;-0.12,-5.33;1.71,-5.69;3.54,-6.05;5.38,-6.41
LAND1.intelligentSceneAnalysis=-1
LAND1.mowingSafetyDistance=-1
LAND1.landArea=26.75
LAND1.returnPointCoordinates=-1
LAND1.landNumber=LAND1
LAND2.landName=1452
LAND2.userId=-1
LAND2.intelligentSceneAnalysis=-1
LAND2.mowingOverlapDistance=0.06
LAND2.landArea=55.11
LAND1.returnPathCoordinates=-1
LAND1.mowingPattern=平行线
LAND1.mowingOverlapDistance=0.06
LAND2.updateTime=2025-12-20 14\:47\:09
LAND2.createTime=2025-12-20 12\:24\:28
LAND1.mowingWidth=34
LAND2.returnPathCoordinates=-1
LAND2.angleThreshold=-1
LAND2.boundaryPointInterval=-1
LAND2.mowingWidth=34
LAND1.plannedPath=-5.830841,-2.644443;-12.323966,-4.153883;-12.276978,-4.492026;-6.161845,-3.070456;-6.492848,-3.496470;-12.229991,-4.830169;-12.183003,-5.168312;-6.823852,-3.922484;-7.154855,-4.348497;-12.136016,-5.506455;-12.089028,-5.844598;-7.487144,-4.774810;-7.821079,-5.201505;-12.042040,-6.182741;-11.995053,-6.520885;-8.155014,-5.628200;-8.700347,-6.104039;-11.948065,-6.859028;-11.901077,-7.197171;-10.070186,-6.771548;-11.440025,-7.439057;-11.854090,-7.535314
LAND1.updateTime=2025-12-19 19\:44\:17
LAND2.mowingSafetyDistance=0.50
LAND2.plannedPath=-12.031,-3.808;-10.774,-3.615;-9.790,-3.809;-12.167,-4.172;-12.302,-4.537;-8.807,-4.002;-7.823,-4.196;-12.280,-4.878;-12.158,-5.203;-6.838,-4.389;-5.852,-4.582;-12.036,-5.528;-11.914,-5.854;-4.868,-4.775;-3.884,-4.969;-11.792,-6.179;-11.670,-6.504;-2.901,-5.162;-1.917,-5.355;-11.547,-6.829;-11.425,-7.155;-0.931,-5.548;0.055,-5.741;-11.303,-7.480;-11.181,-7.805;1.038,-5.935;2.022,-6.128;-11.059,-8.130;-10.937,-8.456;3.005,-6.322
LAND1.baseStationCoordinates=3949.89151752,N,11616.79267501,E
LAND1.boundaryCoordinates=-10.36,-8.92;-12.10,-7.80;-12.66,-3.77;-4.96,-1.98;-6.04,-3.37;-7.12,-4.76;-8.20,-6.14;-9.28,-7.53;-10.36,-8.92
LAND1.boundaryOriginalCoordinates=39.831445,116.279757,49.24;39.831445,116.279747,49.38;39.831448,116.279741,49.27;39.831455,116.279736,49.26;39.831463,116.279733,49.26;39.831473,116.279731,49.26;39.831483,116.279729,49.25;39.831491,116.279730,49.26;39.831496,116.279735,49.22;39.831497,116.279748,49.27;39.831498,116.279762,49.27;39.831500,116.279776,49.34;39.831502,116.279791,49.32;39.831504,116.279805,49.27;39.831507,116.279820,49.28
LAND2.boundaryOriginalCoordinates=39.831468,116.279941,49.29;39.831465,116.279925,49.33;39.831463,116.279908,49.34;39.831462,116.279891,49.35;39.831461,116.279874,49.33;39.831458,116.279859,49.34;39.831456,116.279843,49.32;39.831454,116.279827,49.32;39.831452,116.279813,49.42;39.831450,116.279798,49.41;39.831448,116.279783,49.36;39.831447,116.279769,49.26;39.831445,116.279757,49.24;39.831445,116.279747,49.38;39.831448,116.279741,49.27;39.831455,116.279736,49.26;39.831463,116.279733,49.26;39.831473,116.279731,49.26;39.831483,116.279729,49.25;39.831491,116.279730,49.26;39.831496,116.279735,49.22;39.831497,116.279748,49.27
LAND1.boundaryPointInterval=-1
LAND1.createTime=2025-12-19 18\:31\:53
LAND1.intelligentSceneAnalysis=-1
LAND1.landArea=26.75
LAND1.landName=21233
LAND1.landNumber=LAND1
LAND1.mowingBladeWidth=0.40
LAND1.mowingOverlapDistance=0.06
LAND1.mowingPattern=平行线
LAND1.mowingTrack=-1
LAND1.mowingWidth=34
LAND1.plannedPath=-5.830841,-2.644443;-12.323966,-4.153883;-12.276978,-4.492026;-6.161845,-3.070456;-6.492848,-3.496470;-12.229991,-4.830169;-12.183003,-5.168312;-6.823852,-3.922484;-7.154855,-4.348497;-12.136016,-5.506455;-12.089028,-5.844598;-7.487144,-4.774810;-7.821079,-5.201505;-12.042040,-6.182741;-11.995053,-6.520885;-8.155014,-5.628200;-8.700347,-6.104039;-11.948065,-6.859028;-11.901077,-7.197171;-10.070186,-6.771548;-11.440025,-7.439057;-11.854090,-7.535314
LAND1.returnPointCoordinates=-1
LAND1.updateTime=2025-12-19 19\:44\:17
LAND2.returnPointCoordinates=-1
LAND2.mowingTrack=-1
LAND1.userId=-1
LAND1.angleThreshold=-1
LAND2.mowingBladeWidth=0.40
LAND1.landName=21233
LAND1.mowingTrack=-1
LAND2.baseStationCoordinates=3949.89151752,N,11616.79267501,E
LAND1.boundaryOriginalCoordinates=39.831445,116.279757,49.24;39.831445,116.279747,49.38;39.831448,116.279741,49.27;39.831455,116.279736,49.26;39.831463,116.279733,49.26;39.831473,116.279731,49.26;39.831483,116.279729,49.25;39.831491,116.279730,49.26;39.831496,116.279735,49.22;39.831497,116.279748,49.27;39.831498,116.279762,49.27;39.831500,116.279776,49.34;39.831502,116.279791,49.32;39.831504,116.279805,49.27;39.831507,116.279820,49.28
LAND1.boundaryCoordinates=-10.36,-8.92;-12.10,-7.80;-12.66,-3.77;-4.96,-1.98;-6.04,-3.37;-7.12,-4.76;-8.20,-6.14;-9.28,-7.53;-10.36,-8.92
LAND2.mowingPattern=平行线
LAND2.landNumber=LAND2
LAND1.mowingBladeWidth=0.40
image/look.png
set.properties
@@ -1,19 +1,19 @@
#Mower Configuration Properties - Updated
#Fri Dec 19 19:44:56 CST 2025
#Current work land selection updated
#Sat Dec 20 15:10:30 GMT+08:00 2025
appVersion=-1
boundaryLengthVisible=false
currentWorkLandNumber=LAND1
cuttingWidth=200
firmwareVersion=-1
handheldMarkerId=1872
idleTrailDurationSeconds=60
manualBoundaryDrawingMode=false
mapScale=41.56
measurementModeEnabled=false
mowerId=860
serialAutoConnect=true
serialBaudRate=115200
serialPortName=COM15
simCardNumber=-1
viewCenterX=8.76
viewCenterY=4.44
currentWorkLandNumber=LAND2
serialBaudRate=115200
boundaryLengthVisible=false
idleTrailDurationSeconds=60
handheldMarkerId=1872
viewCenterX=5.62
viewCenterY=6.22
manualBoundaryDrawingMode=false
mowerId=860
serialPortName=COM15
serialAutoConnect=true
mapScale=13.68
measurementModeEnabled=false
firmwareVersion=-1
cuttingWidth=200
src/dikuai/Dikuai.java
@@ -23,6 +23,8 @@
    private String plannedPath;
    // è¿”回点坐标
    private String returnPointCoordinates;
    // å¾€è¿”路径坐标(割草机完成割草作业返回的路径坐标,格式:X1,Y1;X2,Y2;...;XN,YN)
    private String returnPathCoordinates;
    // è¾¹ç•Œç‚¹é—´éš”
    private String boundaryPointInterval;
    // è§’度阈值
@@ -47,6 +49,8 @@
    private String mowingBladeWidth;
    // å‰²è‰é‡å è·ç¦»ï¼ˆç±³ï¼‰ï¼Œé»˜è®¤0.06ç±³
    private String mowingOverlapDistance;
    // å‰²è‰å®‰å…¨è·ç¦»ï¼ˆç±³ï¼‰
    private String mowingSafetyDistance;
    // å­˜å‚¨å¤šä¸ªåœ°å—的映射表,键为地块编号
    private static Map<String, Dikuai> dikuaiMap = new HashMap<>();
@@ -98,6 +102,7 @@
                dikuai.boundaryCoordinates = landProps.getProperty("boundaryCoordinates", "-1");
                dikuai.plannedPath = landProps.getProperty("plannedPath", "-1");
                dikuai.returnPointCoordinates = landProps.getProperty("returnPointCoordinates", "-1");
                dikuai.returnPathCoordinates = landProps.getProperty("returnPathCoordinates", "-1");
                dikuai.boundaryPointInterval = landProps.getProperty("boundaryPointInterval", "-1");
                dikuai.angleThreshold = landProps.getProperty("angleThreshold", "-1");
                dikuai.intelligentSceneAnalysis = landProps.getProperty("intelligentSceneAnalysis", "-1");
@@ -110,6 +115,7 @@
                dikuai.mowingTrack = landProps.getProperty("mowingTrack", "-1");
                dikuai.mowingBladeWidth = landProps.getProperty("mowingBladeWidth", "0.40");
                dikuai.mowingOverlapDistance = landProps.getProperty("mowingOverlapDistance", "0.06");
                dikuai.mowingSafetyDistance = landProps.getProperty("mowingSafetyDistance", "-1");
                
                dikuaiMap.put(landNum, dikuai);
            }
@@ -201,6 +207,9 @@
            case "returnPointCoordinates":
                this.returnPointCoordinates = value;
                return true;
            case "returnPathCoordinates":
                this.returnPathCoordinates = value;
                return true;
            case "boundaryPointInterval":
                this.boundaryPointInterval = value;
                return true;
@@ -237,6 +246,9 @@
            case "mowingOverlapDistance":
                this.mowingOverlapDistance = value;
                return true;
            case "mowingSafetyDistance":
                this.mowingSafetyDistance = value;
                return true;
            default:
                System.err.println("未知字段: " + fieldName);
                return false;
@@ -261,6 +273,7 @@
            if (dikuai.boundaryCoordinates != null) properties.setProperty(landNumber + ".boundaryCoordinates", dikuai.boundaryCoordinates);
            if (dikuai.plannedPath != null) properties.setProperty(landNumber + ".plannedPath", dikuai.plannedPath);
            if (dikuai.returnPointCoordinates != null) properties.setProperty(landNumber + ".returnPointCoordinates", dikuai.returnPointCoordinates);
            if (dikuai.returnPathCoordinates != null) properties.setProperty(landNumber + ".returnPathCoordinates", dikuai.returnPathCoordinates);
            if (dikuai.boundaryPointInterval != null) properties.setProperty(landNumber + ".boundaryPointInterval", dikuai.boundaryPointInterval);
            if (dikuai.angleThreshold != null) properties.setProperty(landNumber + ".angleThreshold", dikuai.angleThreshold);
            if (dikuai.intelligentSceneAnalysis != null) properties.setProperty(landNumber + ".intelligentSceneAnalysis", dikuai.intelligentSceneAnalysis);
@@ -273,6 +286,7 @@
            if (dikuai.mowingTrack != null) properties.setProperty(landNumber + ".mowingTrack", dikuai.mowingTrack);
            if (dikuai.mowingBladeWidth != null) properties.setProperty(landNumber + ".mowingBladeWidth", dikuai.mowingBladeWidth);
            if (dikuai.mowingOverlapDistance != null) properties.setProperty(landNumber + ".mowingOverlapDistance", dikuai.mowingOverlapDistance);
            if (dikuai.mowingSafetyDistance != null) properties.setProperty(landNumber + ".mowingSafetyDistance", dikuai.mowingSafetyDistance);
        }
        
        try {
@@ -344,6 +358,14 @@
        this.returnPointCoordinates = returnPointCoordinates;
    }
    public String getReturnPathCoordinates() {
        return returnPathCoordinates;
    }
    public void setReturnPathCoordinates(String returnPathCoordinates) {
        this.returnPathCoordinates = returnPathCoordinates;
    }
    public String getBoundaryPointInterval() {
        return boundaryPointInterval;
    }
@@ -440,6 +462,14 @@
        this.mowingOverlapDistance = mowingOverlapDistance;
    }
    public String getMowingSafetyDistance() {
        return mowingSafetyDistance;
    }
    public void setMowingSafetyDistance(String mowingSafetyDistance) {
        this.mowingSafetyDistance = mowingSafetyDistance;
    }
    @Override
    public String toString() {
        return "Dikuai{" +
@@ -450,6 +480,7 @@
                ", boundaryCoordinates='" + boundaryCoordinates + '\'' +
                ", plannedPath='" + plannedPath + '\'' +
                ", returnPointCoordinates='" + returnPointCoordinates + '\'' +
                ", returnPathCoordinates='" + returnPathCoordinates + '\'' +
                ", boundaryPointInterval='" + boundaryPointInterval + '\'' +
                ", angleThreshold='" + angleThreshold + '\'' +
                ", intelligentSceneAnalysis='" + intelligentSceneAnalysis + '\'' +
src/dikuai/Dikuaiguanli.java
@@ -11,8 +11,6 @@
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import ui.UIConfig;
import ui.UIUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
@@ -30,6 +28,9 @@
import zhuye.MapRenderer;
import zhuye.Shouye;
import zhuye.Coordinate;
import zhuye.buttonset;
import zhuye.Fuzhibutton;
import zhuye.Lookbutton;
import gecaoji.Device;
/**
@@ -297,30 +298,31 @@
        // å‰²è‰å®‰å…¨è·ç¦»
        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 = "未设置";
        String safetyDistanceValue = dikuai.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)));
        // å¾€è¿”点路径(带查看图标按钮)
        JPanel returnPathPanel = createCardInfoItemWithButton("往返点路径:",
            getDisplayValue(dikuai.getReturnPathCoordinates(), "未设置"),
            createViewButton(e -> editReturnPath(dikuai)));
        configureInteractiveLabel(getInfoItemTitleLabel(returnPathPanel),
            () -> editReturnPath(dikuai),
            "点击查看/编辑往返点路径");
        contentPanel.add(returnPathPanel);
    contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        ObstacleSummary obstacleSummary = getObstacleSummaryFromCache(dikuai.getLandNumber());
@@ -345,24 +347,24 @@
    contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        // è·¯å¾„坐标(带查看按钮)
        JPanel pathPanel = createCardInfoItemWithButtonOnly("路径坐标:",
            "查看", e -> editPlannedPath(dikuai));
        JPanel pathPanel = createCardInfoItemWithIconButton("路径坐标:",
            createViewButton(e -> editPlannedPath(dikuai)));
        configureInteractiveLabel(getInfoItemTitleLabel(pathPanel),
            () -> editPlannedPath(dikuai),
            "点击查看/编辑路径坐标");
        contentPanel.add(pathPanel);
    contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
    JPanel baseStationPanel = createCardInfoItemWithButtonOnly("基站坐标:",
        "查看", e -> editBaseStationCoordinates(dikuai));
    JPanel baseStationPanel = createCardInfoItemWithIconButton("基站坐标:",
        createViewButton(e -> editBaseStationCoordinates(dikuai)));
    configureInteractiveLabel(getInfoItemTitleLabel(baseStationPanel),
        () -> editBaseStationCoordinates(dikuai),
        "点击查看/编辑基站坐标");
    contentPanel.add(baseStationPanel);
    contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
    JPanel boundaryOriginalPanel = createCardInfoItemWithButtonOnly("边界原始坐标:",
        "查看", e -> editBoundaryOriginalCoordinates(dikuai));
    JPanel boundaryOriginalPanel = createCardInfoItemWithIconButton("边界原始坐标:",
        createViewButton(e -> editBoundaryOriginalCoordinates(dikuai)));
    configureInteractiveLabel(getInfoItemTitleLabel(boundaryOriginalPanel),
        () -> editBoundaryOriginalCoordinates(dikuai),
        "点击查看/编辑边界原始坐标");
@@ -371,7 +373,7 @@
        JPanel completedTrackPanel = createCardInfoItemWithButton("已完成割草路径:",
            getTruncatedValue(dikuai.getMowingTrack(), 12, "未记录"),
            "查看", e -> showCompletedMowingTrackDialog(dikuai));
            createViewButton(e -> showCompletedMowingTrackDialog(dikuai)));
        setInfoItemTooltip(completedTrackPanel, dikuai.getMowingTrack());
        configureInteractiveLabel(getInfoItemTitleLabel(completedTrackPanel),
            () -> showCompletedMowingTrackDialog(dikuai),
@@ -456,6 +458,38 @@
        return itemPanel;
    }
    private JPanel createCardInfoItemWithButton(String label, String value, JButton button) {
        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));
        JLabel valueComp = new JLabel(value);
        valueComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        valueComp.setForeground(TEXT_COLOR);
        rightPanel.add(valueComp);
        rightPanel.add(button);
        itemPanel.add(labelComp, BorderLayout.WEST);
        itemPanel.add(rightPanel, BorderLayout.CENTER);
        itemPanel.putClientProperty("valueLabel", valueComp);
        itemPanel.putClientProperty("titleLabel", labelComp);
        return itemPanel;
    }
    private JPanel createCardInfoItemWithButtonOnly(String label, String buttonText, ActionListener listener) {
        JPanel itemPanel = new JPanel(new BorderLayout());
        itemPanel.setBackground(CARD_BACKGROUND);
@@ -484,6 +518,32 @@
        return itemPanel;
    }
    private JPanel createCardInfoItemWithIconButton(String label, JButton button) {
        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));
        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);
@@ -721,9 +781,80 @@
        contentPanel.add(scrollPane, BorderLayout.CENTER);
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        JButton okButton = new JButton("确定");
        JButton cancelButton = new JButton("取消");
        JButton copyButton = new JButton("复制");
        // åˆ¤æ–­æ˜¯å¦æ˜¯å¾€è¿”点路径对话框
        boolean isReturnPathDialog = title != null && title.contains("往返点路径");
        JButton okButton;
        JButton cancelButton;
        JButton copyButton;
        if (isReturnPathDialog) {
            // å¾€è¿”点路径对话框:使用 buttonset é£Žæ ¼çš„确定按钮,图标按钮
            okButton = buttonset.createStyledButton("去绘制", new Color(70, 130, 220));
            // å–消按钮使用 closepage.png å›¾æ ‡
            cancelButton = new JButton();
            ImageIcon closeIcon = loadIcon("image/closepage.png", 25, 25);
            if (closeIcon != null) {
                cancelButton.setIcon(closeIcon);
            } else {
                cancelButton.setText("关闭");
            }
            cancelButton.setFont(new Font("微软雅黑", Font.PLAIN, 11));
            cancelButton.setForeground(PRIMARY_COLOR);
            cancelButton.setBorder(BorderFactory.createEmptyBorder());
            cancelButton.setContentAreaFilled(false);
            cancelButton.setFocusPainted(false);
            cancelButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
            cancelButton.addMouseListener(new MouseAdapter() {
                public void mouseEntered(MouseEvent e) { cancelButton.setOpaque(true); cancelButton.setBackground(new Color(255, 240, 240)); }
                public void mouseExited(MouseEvent e) { cancelButton.setOpaque(false); }
            });
            // ä½¿ç”¨ Fuzhibutton åˆ›å»ºå¤åˆ¶æŒ‰é’®
            copyButton = Fuzhibutton.createCopyButton(
                (java.util.function.Supplier<String>) () -> {
                    String text = textArea.getText();
                    if (text == null) {
                        text = "";
                    }
                    String trimmed = text.trim();
                    if (trimmed.isEmpty() || "-1".equals(trimmed)) {
                        return null; // è¿”回null会触发"未设置要复制的内容"提示
                    }
                    return text;
                },
                "复制" + title,
                new Color(230, 250, 240)
            );
        } else {
            // å…¶ä»–对话框保持原有样式
            okButton = new JButton("确定");
            cancelButton = new JButton("取消");
            copyButton = new JButton("复制");
            // å…¶ä»–对话框的复制按钮逻辑
            copyButton.addActionListener(e -> {
                String text = textArea.getText();
                if (text == null) {
                    text = "";
                }
                String trimmed = text.trim();
                if (trimmed.isEmpty() || "-1".equals(trimmed)) {
                    JOptionPane.showMessageDialog(dialog, title + " æœªè®¾ç½®", "提示", JOptionPane.INFORMATION_MESSAGE);
                    return;
                }
                try {
                    StringSelection selection = new StringSelection(text);
                    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                    clipboard.setContents(selection, selection);
                    JOptionPane.showMessageDialog(dialog, title + " å·²å¤åˆ¶åˆ°å‰ªè´´æ¿", "提示", JOptionPane.INFORMATION_MESSAGE);
                } catch (Exception ex) {
                    JOptionPane.showMessageDialog(dialog, "复制失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
                }
            });
        }
        final boolean[] confirmed = new boolean[] {false};
        final String[] resultHolder = new String[1];
@@ -736,26 +867,6 @@
        cancelButton.addActionListener(e -> dialog.dispose());
        copyButton.addActionListener(e -> {
            String text = textArea.getText();
            if (text == null) {
                text = "";
            }
            String trimmed = text.trim();
            if (trimmed.isEmpty() || "-1".equals(trimmed)) {
                JOptionPane.showMessageDialog(dialog, title + " æœªè®¾ç½®", "提示", JOptionPane.INFORMATION_MESSAGE);
                return;
            }
            try {
                StringSelection selection = new StringSelection(text);
                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                clipboard.setContents(selection, selection);
                JOptionPane.showMessageDialog(dialog, title + " å·²å¤åˆ¶åˆ°å‰ªè´´æ¿", "提示", JOptionPane.INFORMATION_MESSAGE);
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(dialog, "复制失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
            }
        });
        buttonPanel.add(okButton);
        buttonPanel.add(cancelButton);
        buttonPanel.add(copyButton);
@@ -764,6 +875,18 @@
        dialog.setContentPane(contentPanel);
        dialog.getRootPane().setDefaultButton(okButton);
        dialog.pack();
        // å¦‚果是往返点路径对话框,设置宽度为首页的90%,高度保持不变
        if (isReturnPathDialog) {
            Shouye shouye = Shouye.getInstance();
            if (shouye != null && shouye.getWidth() > 0) {
                int homeWidth = shouye.getWidth();
                int dialogWidth = (int)(homeWidth * 0.9);
                Dimension currentSize = dialog.getSize();
                dialog.setSize(dialogWidth, currentSize.height);
            }
        }
        dialog.setLocationRelativeTo(this);
        dialog.setVisible(true);
@@ -1544,15 +1667,31 @@
    }
    private JButton createDeleteButton() {
        JButton button = createStyledButton("删除", RED_COLOR, false); // è½®å»“风格
        ImageIcon deleteIcon = loadIcon("image/delete.png", 16, 16);
        JButton button = new JButton();
        ImageIcon deleteIcon = loadIcon("image/delete.png", 25, 25);
        if (deleteIcon != null) {
            button.setIcon(deleteIcon);
            button.setIconTextGap(6);
        } else {
            button.setText("删除");
        }
        button.setFont(new Font("微软雅黑", Font.PLAIN, 11));
        button.setForeground(RED_COLOR);
        button.setBorder(BorderFactory.createEmptyBorder());
        button.setContentAreaFilled(false);
        button.setFocusPainted(false);
        button.setCursor(new Cursor(Cursor.HAND_CURSOR));
        button.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) { button.setOpaque(true); button.setBackground(new Color(255, 240, 240)); }
            public void mouseExited(MouseEvent e) { button.setOpaque(false); }
        });
        return button;
    }
    private JButton createViewButton(ActionListener listener) {
        // ä½¿ç”¨ Lookbutton ç±»åˆ›å»ºæŸ¥çœ‹æŒ‰é’®
        return Lookbutton.createViewButton(listener, new Color(230, 250, 240));
    }
    private JButton createPrimaryFooterButton(String text) {
        return createStyledButton(text, PRIMARY_COLOR, true); // å®žå¿ƒé£Žæ ¼
    }
@@ -1771,13 +1910,21 @@
        });
    }
    private void editReturnPoint(Dikuai dikuai) {
        FanhuiDialog fd = new FanhuiDialog(SwingUtilities.getWindowAncestor(this), dikuai);
        fd.setVisible(true);
        // å¦‚果对话框已更新数据,刷新显示
        if (fd.isUpdated()) {
            loadDikuaiData();
    private void editReturnPath(Dikuai dikuai) {
        if (dikuai == null) {
            return;
        }
        String edited = promptCoordinateEditing("查看 / ç¼–辑往返点路径", dikuai.getReturnPathCoordinates());
        if (edited == null) {
            return;
        }
        String normalized = normalizeCoordinateInput(edited);
        if (!saveFieldAndRefresh(dikuai, "returnPathCoordinates", normalized)) {
            JOptionPane.showMessageDialog(this, "无法更新往返点路径", "错误", JOptionPane.ERROR_MESSAGE);
            return;
        }
        String message = "-1".equals(normalized) ? "往返点路径已清空" : "往返点路径已更新";
        JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE);
    }
    /**
src/dikuai/addzhangaiwu.java
@@ -1359,7 +1359,7 @@
        }
        if (!configMap.isEmpty()) {
            List<ExistingObstacle> remaining = new ArrayList<>(configMap.values());
            remaining.sort(Comparator.comparing(ExistingObstacle::getName, String.CASE_INSENSITIVE_ORDER));
            remaining.sort((a, b) -> String.CASE_INSENSITIVE_ORDER.compare(a.getName(), b.getName()));
            result.addAll(remaining);
        }
        if (result.isEmpty()) {
src/lujing/MowingPathGenerationPage.java
@@ -3,8 +3,6 @@
import javax.swing.*;
import javax.swing.SwingUtilities;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.math.BigDecimal;
@@ -23,6 +21,7 @@
import org.locationtech.jts.geom.Coordinate;
import gecaoji.Device;
import java.util.Locale;
import zhuye.Fuzhibutton;
/**
 * ç”Ÿæˆå‰²è‰è·¯å¾„页面
@@ -762,15 +761,18 @@
        titleLabel.setForeground(TEXT_COLOR);
        titlePanel.add(titleLabel, BorderLayout.WEST);
        
        // åˆ›å»ºå¤åˆ¶æŒ‰é’®
        JButton copyButton = createCopyButton(title, () -> {
            String text = textArea.getText();
            if (text == null || text.trim().isEmpty()) {
                JOptionPane.showMessageDialog(this, title + " æœªè®¾ç½®", "提示", JOptionPane.INFORMATION_MESSAGE);
                return;
            }
            copyToClipboard(text, title);
        });
        // åˆ›å»ºå¤åˆ¶æŒ‰é’®ï¼ˆä½¿ç”¨ Fuzhibutton)
        JButton copyButton = Fuzhibutton.createCopyButton(
            () -> {
                String text = textArea.getText();
                if (text == null || text.trim().isEmpty() || "-1".equals(text.trim())) {
                    return null; // è¿”回null会触发"未设置"提示
                }
                return text;
            },
            "复制" + title,
            new Color(230, 250, 240)
        );
        titlePanel.add(copyButton, BorderLayout.EAST);
        
        section.add(titlePanel, BorderLayout.NORTH);
@@ -812,15 +814,18 @@
        titleLabel.setForeground(TEXT_COLOR);
        titlePanel.add(titleLabel, BorderLayout.WEST);
        
        // åˆ›å»ºå¤åˆ¶æŒ‰é’®
        JButton copyButton = createCopyButton(title, () -> {
            String text = textField.getText();
            if (text == null || text.trim().isEmpty()) {
                JOptionPane.showMessageDialog(this, title + " æœªè®¾ç½®", "提示", JOptionPane.INFORMATION_MESSAGE);
                return;
            }
            copyToClipboard(text, title);
        });
        // åˆ›å»ºå¤åˆ¶æŒ‰é’®ï¼ˆä½¿ç”¨ Fuzhibutton)
        JButton copyButton = Fuzhibutton.createCopyButton(
            () -> {
                String text = textField.getText();
                if (text == null || text.trim().isEmpty() || "-1".equals(text.trim())) {
                    return null; // è¿”回null会触发"未设置"提示
                }
                return text;
            },
            "复制" + title,
            new Color(230, 250, 240)
        );
        titlePanel.add(copyButton, BorderLayout.EAST);
        
        section.add(titlePanel, BorderLayout.NORTH);
@@ -962,80 +967,6 @@
        return "parallel";
    }
    
    /**
     * åˆ›å»ºå¤åˆ¶æŒ‰é’®
     */
    private JButton createCopyButton(String title, Runnable copyAction) {
        JButton copyButton = new JButton();
        Font titleFont = new Font("微软雅黑", Font.BOLD, 14);
        FontMetrics metrics = getFontMetrics(titleFont);
        int iconSize = metrics.getHeight(); // ä½¿ç”¨æ ‡é¢˜å­—体高度作为图标大小
        // åŠ è½½å¤åˆ¶å›¾æ ‡
        ImageIcon copyIcon = null;
        ImageIcon successIcon = null;
        try {
            ImageIcon originalCopyIcon = new ImageIcon("image/fuzhi.png");
            Image scaledCopyImage = originalCopyIcon.getImage().getScaledInstance(iconSize, iconSize, Image.SCALE_SMOOTH);
            copyIcon = new ImageIcon(scaledCopyImage);
            // åŠ è½½æˆåŠŸå›¾æ ‡
            ImageIcon originalSuccessIcon = new ImageIcon("image/fuzhisucc.png");
            Image scaledSuccessImage = originalSuccessIcon.getImage().getScaledInstance(iconSize, iconSize, Image.SCALE_SMOOTH);
            successIcon = new ImageIcon(scaledSuccessImage);
        } catch (Exception e) {
            // å¦‚果图片加载失败,使用文本
            copyButton.setText("复制");
            copyButton.setFont(new Font("微软雅黑", Font.PLAIN, 12));
            System.err.println("无法加载复制图标: " + e.getMessage());
        }
        final ImageIcon finalCopyIcon = copyIcon;
        final ImageIcon finalSuccessIcon = successIcon;
        copyButton.setIcon(finalCopyIcon);
        copyButton.setContentAreaFilled(false);
        copyButton.setBorder(null);
        copyButton.setFocusPainted(false);
        copyButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
        copyButton.setToolTipText("复制" + title);
        // æ·»åŠ ç‚¹å‡»äº‹ä»¶
        copyButton.addActionListener(e -> {
            copyAction.run();
            // å¤åˆ¶æˆåŠŸåŽåˆ‡æ¢å›¾æ ‡
            if (finalSuccessIcon != null) {
                copyButton.setIcon(finalSuccessIcon);
                // 1秒后恢复原图标
                Timer timer = new Timer(1000, evt -> {
                    copyButton.setIcon(finalCopyIcon);
                });
                timer.setRepeats(false);
                timer.start();
            }
        });
        return copyButton;
    }
    /**
     * å¤åˆ¶æ–‡æœ¬åˆ°å‰ªè´´æ¿
     */
    private void copyToClipboard(String text, String title) {
        if (text == null || text.trim().isEmpty()) {
            JOptionPane.showMessageDialog(this, title + " æœªè®¾ç½®", "提示", JOptionPane.INFORMATION_MESSAGE);
            return;
        }
        try {
            StringSelection selection = new StringSelection(text);
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            clipboard.setContents(selection, selection);
            // åŽ»æŽ‰æˆåŠŸæç¤ºå¼¹çª—
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "复制失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
        }
    }
}
src/zhangaiwu/AddDikuai.java
@@ -2539,6 +2539,21 @@
        if (isMeaningfulValue(plannedPath)) {
            dikuai.setPlannedPath(plannedPath);
        }
        // ä¿å­˜å‰²è‰å®‰å…¨è·ç¦»ï¼ˆä¼˜å…ˆä»ŽdikuaiData获取,否则从TextField获取)
        if (dikuaiData.containsKey("mowingSafetyDistance")) {
            dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance"));
        } else if (mowingSafetyDistanceField != null) {
            String safetyDistanceText = mowingSafetyDistanceField.getText().trim();
            if (!safetyDistanceText.isEmpty()) {
                try {
                    double safetyDistanceMeters = Double.parseDouble(safetyDistanceText);
                    dikuai.setMowingSafetyDistance(String.format(Locale.US, "%.2f", safetyDistanceMeters));
                } catch (NumberFormatException e) {
                    dikuai.setMowingSafetyDistance(safetyDistanceText);
                }
            }
        }
        Dikuai.putDikuai(landNumber, dikuai);
        Dikuai.saveToProperties();
src/zhuye/Fuzhibutton.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,270 @@
package zhuye;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
 * å¤åˆ¶æŒ‰é’®å·¥å…·ç±»
 * æä¾›ç»Ÿä¸€çš„复制按钮创建方法,默认显示复制图标,复制成功后显示成功图标
 */
public final class Fuzhibutton {
    // é»˜è®¤å›¾æ ‡å¤§å°
    private static final int DEFAULT_ICON_SIZE = 18;
    // æˆåŠŸå›¾æ ‡æ˜¾ç¤ºæ—¶é•¿ï¼ˆæ¯«ç§’ï¼‰
    private static final int SUCCESS_ICON_DURATION = 1500;
    private Fuzhibutton() {
        // å·¥å…·ç±»ä¸éœ€è¦å®žä¾‹åŒ–
    }
    /**
     * åˆ›å»ºå¤åˆ¶æŒ‰é’®
     * é»˜è®¤æ˜¾ç¤º image/fuzhi.png å›¾æ ‡ï¼Œå¤åˆ¶æˆåŠŸåŽæ˜¾ç¤º image/fuzhisucc.png å›¾æ ‡
     *
     * @param textToCopy è¦å¤åˆ¶çš„æ–‡æœ¬å†…容(如果为null,则需要在点击时通过回调提供)
     * @param tooltip æŒ‰é’®æç¤ºæ–‡æœ¬
     * @param hoverColor é¼ æ ‡æ‚¬åœæ—¶çš„背景颜色(如果为null,使用默认颜色)
     * @return é…ç½®å¥½çš„复制按钮
     */
    public static JButton createCopyButton(String textToCopy, String tooltip, Color hoverColor) {
        JButton copyButton = new JButton();
        // åŠ è½½å›¾æ ‡
        ImageIcon copyIcon = loadIcon("image/fuzhi.png", DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
        ImageIcon successIcon = loadIcon("image/fuzhisucc.png", DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
        final ImageIcon finalCopyIcon = copyIcon;
        final ImageIcon finalSuccessIcon = successIcon;
        // è®¾ç½®æŒ‰é’®å›¾æ ‡
        if (copyIcon != null) {
            copyButton.setIcon(copyIcon);
        } else {
            copyButton.setText("复制");
        }
        // è®¾ç½®æŒ‰é’®æ ·å¼
        copyButton.setFont(new Font("微软雅黑", Font.PLAIN, 11));
        copyButton.setForeground(new Color(46, 139, 87)); // PRIMARY_COLOR
        copyButton.setBorder(BorderFactory.createEmptyBorder());
        copyButton.setContentAreaFilled(false);
        copyButton.setFocusPainted(false);
        copyButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
        // è®¾ç½®æç¤ºæ–‡æœ¬ä¸º"复制信息"
        copyButton.setToolTipText("复制信息");
        // è®¾ç½®æ‚¬åœæ•ˆæžœ
        final Color finalHoverColor = hoverColor != null ? hoverColor : new Color(230, 250, 240);
        copyButton.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                copyButton.setOpaque(true);
                copyButton.setBackground(finalHoverColor);
            }
            public void mouseExited(MouseEvent e) {
                copyButton.setOpaque(false);
            }
        });
        // æ·»åŠ å¤åˆ¶åŠŸèƒ½
        copyButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String text = textToCopy;
                // å¦‚æžœ textToCopy ä¸º null,尝试从父组件获取文本(如 JTextArea)
                if (text == null) {
                    // å°è¯•从父组件中查找 JTextArea
                    Container parent = copyButton.getParent();
                    while (parent != null) {
                        if (parent instanceof JDialog) {
                            Component[] components = ((JDialog) parent).getContentPane().getComponents();
                            text = findTextFromComponents(components);
                            break;
                        } else if (parent instanceof JFrame) {
                            Component[] components = ((JFrame) parent).getContentPane().getComponents();
                            text = findTextFromComponents(components);
                            break;
                        }
                        parent = parent.getParent();
                    }
                }
                // å¦‚果没有需要复制的内容,直接返回,不显示弹窗提示
                if (text == null || text.trim().isEmpty() || "-1".equals(text.trim())) {
                    return;
                }
                try {
                    StringSelection selection = new StringSelection(text);
                    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                    clipboard.setContents(selection, selection);
                    // å¤åˆ¶æˆåŠŸåŽåˆ‡æ¢ä¸ºæˆåŠŸå›¾æ ‡
                    if (finalSuccessIcon != null) {
                        copyButton.setIcon(finalSuccessIcon);
                        // 1.5秒后恢复为原始图标
                        Timer timer = new Timer(SUCCESS_ICON_DURATION, evt -> {
                            if (finalCopyIcon != null) {
                                copyButton.setIcon(finalCopyIcon);
                            }
                        });
                        timer.setRepeats(false);
                        timer.start();
                    }
                } catch (Exception ex) {
                    JOptionPane.showMessageDialog(
                        SwingUtilities.getWindowAncestor(copyButton),
                        "复制失败: " + ex.getMessage(),
                        "错误",
                        JOptionPane.ERROR_MESSAGE
                    );
                }
            }
        });
        return copyButton;
    }
    /**
     * åˆ›å»ºå¤åˆ¶æŒ‰é’®ï¼ˆä½¿ç”¨å›žè°ƒå‡½æ•°æä¾›è¦å¤åˆ¶çš„æ–‡æœ¬ï¼‰
     *
     * @param copyAction å¤åˆ¶åŠ¨ä½œå›žè°ƒï¼Œè¿”å›žè¦å¤åˆ¶çš„æ–‡æœ¬
     * @param tooltip æŒ‰é’®æç¤ºæ–‡æœ¬
     * @param hoverColor é¼ æ ‡æ‚¬åœæ—¶çš„背景颜色(如果为null,使用默认颜色)
     * @return é…ç½®å¥½çš„复制按钮
     */
    public static JButton createCopyButton(java.util.function.Supplier<String> copyAction, String tooltip, Color hoverColor) {
        JButton copyButton = new JButton();
        // åŠ è½½å›¾æ ‡
        ImageIcon copyIcon = loadIcon("image/fuzhi.png", DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
        ImageIcon successIcon = loadIcon("image/fuzhisucc.png", DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
        final ImageIcon finalCopyIcon = copyIcon;
        final ImageIcon finalSuccessIcon = successIcon;
        // è®¾ç½®æŒ‰é’®å›¾æ ‡
        if (copyIcon != null) {
            copyButton.setIcon(copyIcon);
        } else {
            copyButton.setText("复制");
        }
        // è®¾ç½®æŒ‰é’®æ ·å¼
        copyButton.setFont(new Font("微软雅黑", Font.PLAIN, 11));
        copyButton.setForeground(new Color(46, 139, 87)); // PRIMARY_COLOR
        copyButton.setBorder(BorderFactory.createEmptyBorder());
        copyButton.setContentAreaFilled(false);
        copyButton.setFocusPainted(false);
        copyButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
        // è®¾ç½®æç¤ºæ–‡æœ¬ä¸º"复制信息"
        copyButton.setToolTipText("复制信息");
        // è®¾ç½®æ‚¬åœæ•ˆæžœ
        final Color finalHoverColor = hoverColor != null ? hoverColor : new Color(230, 250, 240);
        copyButton.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                copyButton.setOpaque(true);
                copyButton.setBackground(finalHoverColor);
            }
            public void mouseExited(MouseEvent e) {
                copyButton.setOpaque(false);
            }
        });
        // æ·»åŠ å¤åˆ¶åŠŸèƒ½
        copyButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String text = copyAction != null ? copyAction.get() : null;
                // å¦‚果没有需要复制的内容,直接返回,不显示弹窗提示
                if (text == null || text.trim().isEmpty() || "-1".equals(text.trim())) {
                    return;
                }
                try {
                    StringSelection selection = new StringSelection(text);
                    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                    clipboard.setContents(selection, selection);
                    // å¤åˆ¶æˆåŠŸåŽåˆ‡æ¢ä¸ºæˆåŠŸå›¾æ ‡
                    if (finalSuccessIcon != null) {
                        copyButton.setIcon(finalSuccessIcon);
                        // 1.5秒后恢复为原始图标
                        Timer timer = new Timer(SUCCESS_ICON_DURATION, evt -> {
                            if (finalCopyIcon != null) {
                                copyButton.setIcon(finalCopyIcon);
                            }
                        });
                        timer.setRepeats(false);
                        timer.start();
                    }
                } catch (Exception ex) {
                    JOptionPane.showMessageDialog(
                        SwingUtilities.getWindowAncestor(copyButton),
                        "复制失败: " + ex.getMessage(),
                        "错误",
                        JOptionPane.ERROR_MESSAGE
                    );
                }
            }
        });
        return copyButton;
    }
    /**
     * åŠ è½½å›¾æ ‡å¹¶è°ƒæ•´å¤§å°
     *
     * @param path å›¾æ ‡è·¯å¾„
     * @param width ç›®æ ‡å®½åº¦
     * @param height ç›®æ ‡é«˜åº¦
     * @return è°ƒæ•´å¤§å°åŽçš„图标,如果加载失败返回null
     */
    private static ImageIcon loadIcon(String path, int width, int height) {
        try {
            ImageIcon rawIcon = new ImageIcon(path);
            if (rawIcon.getIconWidth() <= 0 || rawIcon.getIconHeight() <= 0) {
                return null;
            }
            Image scaled = rawIcon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
            return new ImageIcon(scaled);
        } catch (Exception ex) {
            System.err.println("无法加载图标: " + path + " - " + ex.getMessage());
            return null;
        }
    }
    /**
     * ä»Žç»„件树中查找 JTextArea å¹¶èŽ·å–å…¶æ–‡æœ¬
     *
     * @param components ç»„件数组
     * @return æ‰¾åˆ°çš„æ–‡æœ¬ï¼Œå¦‚果未找到返回null
     */
    private static String findTextFromComponents(Component[] components) {
        for (Component comp : components) {
            if (comp instanceof JTextArea) {
                return ((JTextArea) comp).getText();
            } else if (comp instanceof Container) {
                String text = findTextFromComponents(((Container) comp).getComponents());
                if (text != null) {
                    return text;
                }
            }
        }
        return null;
    }
}
src/zhuye/Lookbutton.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
package zhuye;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
 * æŸ¥çœ‹æŒ‰é’®å·¥å…·ç±»
 * æä¾›ç»Ÿä¸€çš„æŸ¥çœ‹æŒ‰é’®åˆ›å»ºæ–¹æ³•,默认显示查看图标,点击按钮执行对应的操作
 */
public final class Lookbutton {
    // é»˜è®¤å›¾æ ‡å¤§å°
    private static final int DEFAULT_ICON_SIZE =25;
    private Lookbutton() {
        // å·¥å…·ç±»ä¸éœ€è¦å®žä¾‹åŒ–
    }
    /**
     * åˆ›å»ºæŸ¥çœ‹æŒ‰é’®
     * é»˜è®¤æ˜¾ç¤º image/look.png å›¾æ ‡ï¼Œç‚¹å‡»æŒ‰é’®æ‰§è¡Œä¼ å…¥çš„æ“ä½œ
     *
     * @param listener æŒ‰é’®ç‚¹å‡»äº‹ä»¶ç›‘听器,当按钮被点击时执行
     * @param hoverColor é¼ æ ‡æ‚¬åœæ—¶çš„背景颜色(如果为null,使用默认颜色)
     * @return é…ç½®å¥½çš„æŸ¥çœ‹æŒ‰é’®
     */
    public static JButton createViewButton(ActionListener listener, Color hoverColor) {
        JButton viewButton = new JButton();
        // åŠ è½½å›¾æ ‡
        ImageIcon lookIcon = loadIcon("image/look.png", DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
        // è®¾ç½®æŒ‰é’®å›¾æ ‡
        if (lookIcon != null) {
            viewButton.setIcon(lookIcon);
        } else {
            viewButton.setText("查看");
        }
        // è®¾ç½®æŒ‰é’®æ ·å¼
        viewButton.setFont(new Font("微软雅黑", Font.PLAIN, 11));
        viewButton.setForeground(new Color(46, 139, 87)); // PRIMARY_COLOR
        viewButton.setBorder(BorderFactory.createEmptyBorder());
        viewButton.setContentAreaFilled(false);
        viewButton.setFocusPainted(false);
        viewButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
        // è®¾ç½®æç¤ºæ–‡æœ¬ä¸º"点击查看"
        viewButton.setToolTipText("点击查看");
        // è®¾ç½®æ‚¬åœæ•ˆæžœ
        final Color finalHoverColor = hoverColor != null ? hoverColor : new Color(230, 250, 240);
        viewButton.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                viewButton.setOpaque(true);
                viewButton.setBackground(finalHoverColor);
            }
            public void mouseExited(MouseEvent e) {
                viewButton.setOpaque(false);
            }
        });
        // æ·»åŠ ç‚¹å‡»äº‹ä»¶
        if (listener != null) {
            viewButton.addActionListener(listener);
        }
        return viewButton;
    }
    /**
     * åˆ›å»ºæŸ¥çœ‹æŒ‰é’®ï¼ˆä½¿ç”¨é»˜è®¤æ‚¬åœé¢œè‰²ï¼‰
     *
     * @param listener æŒ‰é’®ç‚¹å‡»äº‹ä»¶ç›‘听器,当按钮被点击时执行
     * @return é…ç½®å¥½çš„æŸ¥çœ‹æŒ‰é’®
     */
    public static JButton createViewButton(ActionListener listener) {
        return createViewButton(listener, null);
    }
    /**
     * åŠ è½½å›¾æ ‡å¹¶è°ƒæ•´å¤§å°
     *
     * @param path å›¾æ ‡è·¯å¾„
     * @param width ç›®æ ‡å®½åº¦
     * @param height ç›®æ ‡é«˜åº¦
     * @return è°ƒæ•´å¤§å°åŽçš„图标,如果加载失败返回null
     */
    private static ImageIcon loadIcon(String path, int width, int height) {
        try {
            ImageIcon rawIcon = new ImageIcon(path);
            if (rawIcon.getIconWidth() <= 0 || rawIcon.getIconHeight() <= 0) {
                return null;
            }
            Image scaled = rawIcon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
            return new ImageIcon(scaled);
        } catch (Exception ex) {
            System.err.println("无法加载图标: " + path + " - " + ex.getMessage());
            return null;
        }
    }
}
src/zhuye/buttonset.java
@@ -1,6 +1,4 @@
package zhuye;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;