Przeglądaj źródła

feat:对接农场分布接口,添加农场聚合

wangsisi 5 dni temu
rodzic
commit
7bcb4e803e

+ 10 - 0
src/api/modules/warning.js

@@ -54,5 +54,15 @@ export default {
         url: config.one_map_url + "agri_farm_crop/list",
         type: "post",
     },
+    //列表查询农场物候记录
+    fetchFarmPhenologyRecord: {
+        url: config.one_map_url + "agri_farm_phenology_record/list",
+        type: "get",
+    },
+    //列表查询设备感知记录
+    fetchFarmDeviceRecord: {
+        url: config.one_map_url + "agri_farm_device_record/list",
+        type: "get",
+    },
 }
 

+ 8 - 1
src/components/charts/oneLineChart.vue

@@ -36,6 +36,10 @@ const props = defineProps({
     customVal: {
         type: Object,
         default: () => {}
+    },
+    yAxisUnit: {
+        type: String,
+        default: "%"
     }
 });
 
@@ -69,7 +73,10 @@ const initData = () =>{
         lineOption.yAxis[0].max = props.customVal.max
         lineOption.grid.right = 20
         lineOption.yAxis[0].axisLabel.align = "left"
-        lineOption.yAxis[0].axisLabel.formatter = "{value}"
+        lineOption.yAxis[0].axisLabel.formatter = `{value} ${props.yAxisUnit}`
+    } else {
+        // 设置 y 轴单位
+        lineOption.yAxis[0].axisLabel.formatter = `{value} ${props.yAxisUnit}`
     }
 
     options.value = lineOption;

+ 2 - 4
src/components/charts/options/oneLineOption.js

@@ -43,8 +43,8 @@ export const oneLine = {
             show: false,
         },
         axisLabel: {
-            // interval: 8,
-            rotate: 15,
+            interval: 0,
+            rotate: 30,
             margin: 14,
             color: "#9F9F9F",
             fontSize: 10
@@ -59,8 +59,6 @@ export const oneLine = {
     yAxis: [{
         type: 'value',
         offset: 10,
-        // interval: 10,
-        max: 100,
         axisTick: {
             show: false,
         },

+ 40 - 73
src/views/warningHome/components/equipment.vue

@@ -1,29 +1,19 @@
 <template>
     <div class="chart-list">
-        <!-- <div class="chart-item weather-item">
-            <chart-box name="干热风险">
-                <div class="base-wrap">
-                    <div class="base-item" v-for="(item, index) in baseData.labels" :key="index">
-                        <div class="label">{{ item }}风险</div>
-                        <div class="value">{{ baseData.valueMaxList[index].toFixed(0) }}<span>%</span></div>
-                    </div>
-                </div>
-                <weatherChart class="line-chart"></weatherChart>
-            </chart-box>
-        </div> -->
         <div class="chart-item">
             <chart-box name="温度">
                 <one-line-chart
                     class="line-chart"
-                    key="tr"
+                    key="temperature"
                     name="温度"
-                    :yData="[22, 20, 22, 30, 32, 22, 25]"
+                    :yData="temperatureData"
                     :minData="[]"
-                    :chartDate="chartDate1"
+                    :chartDate="chartDate"
+                    yAxisUnit="°"
                 ></one-line-chart>
                 <div class="box-bg tips">
                     <div class="text">
-                        这里是预警提示的内容,有点长,先用这些文字占位,后续开发会把内容不上的。这里是预警提示的内容,有点长,先用这些文字占位,后续开发会把内容不上的。
+                        暂无数据
                     </div>
                 </div>
             </chart-box>
@@ -32,15 +22,16 @@
             <chart-box name="湿度">
                 <one-line-chart
                     class="line-chart"
-                    key="tr"
+                    key="humidity"
                     name="湿度"
-                    :yData="[22, 20, 22, 30, 32, 22, 25]"
+                    :yData="humidityData"
                     :minData="[]"
-                    :chartDate="chartDate1"
+                    :chartDate="chartDate"
+                    yAxisUnit="%"
                 ></one-line-chart>
                 <div class="box-bg tips">
                     <div class="text">
-                        这里是预警提示的内容,有点长,先用这些文字占位,后续开发会把内容不上的。这里是预警提示的内容,有点长,先用这些文字占位,后续开发会把内容不上的。
+                        暂无数据
                     </div>
                 </div>
             </chart-box>
@@ -50,14 +41,15 @@
                 <one-line-chart
                     class="line-chart"
                     name="光照"
-                    :key="1"
-                    :yData="[22, 20, 22, 30, 32, 22, 25]"
+                    key="light"
+                    :yData="lightData"
                     :minData="[]"
                     :chartDate="chartDate"
+                    yAxisUnit="ml"
                 ></one-line-chart>
                 <div class="box-bg tips">
                     <div class="text">
-                        这里是预警提示的内容,有点长,先用这些文字占位,后续开发会把内容不上的。这里是预警提示的内容,有点长,先用这些文字占位,后续开发会把内容不上的。
+                        暂无数据
                     </div>
                 </div>
             </chart-box>
@@ -71,26 +63,13 @@ import oneLineChart from "@/components/charts/oneLineChart.vue";
 import { onMounted, ref, onUnmounted } from "vue";
 import eventBus from "@/api/eventBus";
 
-const riskKeys = ref([]);
-
-const yData = ref([]);
-const yData1 = ref([]);
-
-const chartDate = ref([]);
-const chartDate1 = ref([]);
-
-const text = ref(null);
-const text1 = ref(null);
-
-const baseData = ref({});
-
 const organId = ref(sessionStorage.getItem("farmId"));
 
 onMounted(() => {
-    // getList();
-    //   getBaseData()
+    getList();
     //   eventBus.on("area:id", changeAreaId);
 });
+
 onUnmounted(() => {
     eventBus.off("area:id", changeAreaId);
 });
@@ -98,44 +77,36 @@ onUnmounted(() => {
 function changeAreaId({ farmId }) {
     organId.value = farmId;
     getList();
-    getBaseData();
 }
 
+const temperatureData = ref([]);
+const humidityData = ref([]);
+const lightData = ref([]);
+const chartDate = ref([]);
 function getList() {
-    riskKeys.value = [];
-    yData.value.value = [];
-    yData1.value.value = [];
-    chartDate.value.value = [];
-    chartDate1.value.value = [];
-    // VE_API.farm.getFarmReport({ farmId: organId.value, type: "气象风险" }).then((res) => {
-    //     // if (res.data["物候进程"]) {}
-    //     // 使用 Object.keys() 提取对象中的键
-    //     riskKeys.value = Object.keys(res.data);
-    //     yData.value = res.data[riskKeys.value[0]].list.map((item) => parseFloat(item.value.toFixed(1)))
-    //     yData1.value = res.data[riskKeys.value[1]].list.map((item) => parseFloat(item.value.toFixed(1)))
-
-    //     chartDate.value = formattedDates(res.data[riskKeys.value[0]].list.map((item) => item.name))
-    //     chartDate1.value = formattedDates(res.data[riskKeys.value[1]].list.map((item) => item.name))
-
-    //     text.value = res.data[riskKeys.value[0]].text
-    //     text1.value = res.data[riskKeys.value[1]].text
-    // })
-}
-
-function formattedDates(dates) {
-    return dates.map((date) => {
-        const [year, month, day] = date.split("-");
-        return `${month}-${day}`; // 使用模板字符串格式化为 MM/DD
+    VE_API.warning.fetchFarmDeviceRecord({ farmId: 1 }).then((res) => {
+        if(res.code === 0 && res.data && res.data.length > 0) { 
+            temperatureData.value = res.data.map(item => item.temperature.toFixed(2));
+            humidityData.value = res.data.map(item => item.humidity.toFixed(2));
+            lightData.value = res.data.map(item => item.illumination.toFixed(2));
+            chartDate.value = res.data.map(item => formattedDates(item.recordTime));
+        }else{
+            temperatureData.value = [];
+            humidityData.value = [];
+            lightData.value = [];
+            chartDate.value = [];
+        }
     });
 }
 
-const getBaseData = () => {
-    const point = sessionStorage.getItem("point");
-    // 获取气象图表数据
-    VE_API.mini_farm.weather_warning_land_check({ farmId: organId.value, point }).then((res) => {
-        baseData.value = res.data || {};
-    });
-};
+function formattedDates(date) {
+    if (!date) return '';
+    // 处理 ISO 8601 格式:2025-10-01T00:00:00
+    // 先分割 T 获取日期部分,再分割 - 获取年月日
+    const datePart = date.split('T')[0]; // 获取日期部分:2025-10-01
+    const [year, month, day] = datePart.split("-");
+    return `${month}-${day}`; // 返回 MM-DD 格式
+}
 </script>
 
 <style lang="scss" scoped>
@@ -158,10 +129,6 @@ const getBaseData = () => {
             height: calc(100% - 70px);
         }
 
-        &.weather-item {
-            height: 266px;
-        }
-
         .base-wrap {
             width: 100%;
             height: 56px;

+ 49 - 31
src/views/warningHome/components/farmInfoGroup.vue

@@ -2,15 +2,19 @@
     <div class="farm-info-group">
         <!-- 顶部标题栏 -->
         <div class="info-header">
-            <div class="back-btn" v-if="!farmList.length" @click="handleBack">返回</div>
+            <div class="back-btn" v-show="isBack" @click="handleBack">返回</div>
             <div class="title">
                 <img class="title-icon" src="@/assets/images/common/chart-icon.png" alt="" />
-                <span class="title-text">{{ farmList.length ? "农场列表" : "荔博园" }}</span>
+                <span class="title-text">{{ !isBack ? "农场列表" : farmList[0].farmName }}</span>
             </div>
         </div>
 
         <!-- 主要内容区域 -->
-        <div class="info-content" :style="{ height: !farmList.length ? 'calc(100% - 168px)' : 'calc(100% - 50px)' }">
+        <div class="info-content" :style="{ overflowY: isBack ? 'hidden' : 'auto' }">
+            <!-- 农场列表为空时显示暂无数据 -->
+            <div v-if="!isBack && farmList.length === 0" class="empty-data">
+                <div class="empty-text">暂无数据</div>
+            </div>
             <div class="farm-info" v-for="item in farmList" :key="item" @click="handleFarmInfo(item)">
                 <div class="lz-icon">
                     <img src="@/assets/images/warningHome/lz-icon.png" alt="" />
@@ -18,27 +22,27 @@
                 <!-- 右侧信息区域 -->
                 <div class="info-right">
                     <div class="info-item farm-code">
-                        <span class="info-label">{{ !farmList.length ? '农场编号:GZ86770' : item.farmName }}</span>
+                        <span class="info-label">{{ isBack ? `农场编号:${item.farmCode}` : item.farmName }}</span>
                         <div class="tags">
-                            <span class="tag">荔枝</span>
-                            <span class="tag">井岗红糯</span>
+                            <span class="tag">{{ item.speciesName }}</span>
+                            <span class="tag">{{ item.varietyName }}</span>
                         </div>
                     </div>
                     <div class="info-item">
                         <span class="info-label">农场面积:</span>
-                        <span class="info-value">{{ item.plantArea && item.plantArea.toFixed(2) || 0 }}亩</span>
+                        <span class="info-value">{{ item.area && item.area.toFixed(2) || 0 }}亩</span>
                     </div>
                     <div class="info-item">
                         <span class="info-label">农场位置:</span>
-                        <span class="info-value">广东省广州市***</span>
+                        <span class="info-value">{{ item.location }}</span>
                     </div>
                     <div class="info-item">
                         <span class="info-label">权属人:</span>
-                        <span class="info-value">张扬</span>
+                        <span class="info-value">{{ item.owner }}</span>
                     </div>
                 </div>
             </div>
-            <template v-if="!farmList.length">
+            <template v-if="isBack">
                 <div class="tabs">
                     <div
                         v-for="tab in tabs"
@@ -52,14 +56,18 @@
                 </div>
                 <div class="tab-content">
                     <div class="timeline" v-if="activeTab === 'crop'">
+                        <!-- 时间轴列表为空时显示暂无数据 -->
+                        <div v-if="timelineList.length === 0" class="empty-data">
+                            <div class="empty-text">暂无数据</div>
+                        </div>
                         <div class="timeline-item" v-for="item in timelineList" :key="item.id">
                             <div class="timeline-left">
                                 <div class="dot"></div>
                                 <div class="line"></div>
                             </div>
                             <div class="timeline-right">
-                                <div class="date">{{ item.date }}</div>
-                                <div class="text">{{ item.text }}</div>
+                                <div class="date">{{ item.recordTime && item.recordTime.slice(0, 10) || '' }}</div>
+                                <div class="text">{{ item.description }}</div>
                             </div>
                         </div>
                     </div>
@@ -97,16 +105,28 @@ onMounted(() => {
     fetchFarmList();
 });
 
+//获取农场列表
+const defaultFarmList = ref([]);
 const fetchFarmList = () => {
     VE_API.warning.fetchFarmList().then(res => {
         if (res.code === 0 && res.data && res.data.length > 0) {
             farmList.value = res.data;
+            defaultFarmList.value = res.data;
         } else {
             farmList.value = [];
+            defaultFarmList.value = [];
         }
     });
 };
 
+//查询农场物候记录
+const timelineList = ref([]);
+const fetchFarmPhenologyRecord = (farmId) => {
+    VE_API.warning.fetchFarmPhenologyRecord({ farmId }).then(res => {
+        timelineList.value = res.data || [];
+    });
+};
+
 // 底部 tabs 配置
 const tabs = [
     { key: "crop", label: "作物档案" },
@@ -122,24 +142,10 @@ const perceptionTabs = [
 const activeTab = ref("crop"); // 默认选中感知记录
 const perceptionActive = ref("device"); // 默认选中设备感知
 
-// 时间轴示例数据(后续可替换为接口数据)
-const timelineList = ref([
-    { id: 1, date: "8/1", text: "水稻进入拔节期,水稻进入拔节期" },
-    { id: 2, date: "8/1", text: "水稻进入拔节期,水稻进入拔节期" },
-    { id: 3, date: "8/1", text: "水稻进入拔节期,水稻进入拔节期" },
-    { id: 4, date: "8/1", text: "水稻进入拔节期,水稻进入拔节期" },
-    { id: 5, date: "8/1", text: "水稻进入拔节期,水稻进入拔节期" },
-]);
-
 const farmList = ref([]);
 const handleBack = () => {
-    farmList.value = [
-        { id: 1, name: "荔博园", area: "58亩", position: "广东省广州市***", owner: "张扬" },
-        { id: 2, name: "荔博园", area: "58亩", position: "广东省广州市***", owner: "张扬" },
-        { id: 3, name: "荔博园", area: "58亩", position: "广东省广州市***", owner: "张扬" },
-        { id: 4, name: "荔博园", area: "58亩", position: "广东省广州市***", owner: "张扬" },
-        { id: 5, name: "荔博园", area: "58亩", position: "广东省广州市***", owner: "张扬" },
-    ];
+    farmList.value = defaultFarmList.value;
+    isBack.value = false;
 };
 
 const handleCropArchive = () => {
@@ -150,8 +156,11 @@ const handlePerceptionRecord = () => {
     // 感知记录逻辑
 };
 
+const isBack = ref(false);
 const handleFarmInfo = (item) => {
     farmList.value = [item];
+    isBack.value = true;
+    fetchFarmPhenologyRecord(item.id);
 };
 </script>
 <style lang="scss" scoped>
@@ -192,8 +201,7 @@ const handleFarmInfo = (item) => {
 
     .info-content {
         padding: 5px 10px;
-        height: calc(100% - 168px);
-        overflow-y: auto;
+        height: calc(100% - 50px);
 
         .farm-info + .farm-info {
             margin-top: 12px;
@@ -266,7 +274,7 @@ const handleFarmInfo = (item) => {
         }
         .tab-content {
             margin: 12px 0;
-            height: calc(100% - 24px);
+            height: calc(100% - 145px);
             overflow-y: auto;
             .timeline {
                 padding-right: 12px;
@@ -341,6 +349,16 @@ const handleFarmInfo = (item) => {
                 }
             }
         }
+        .empty-data {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            height: 200px;
+            .empty-text {
+                color: rgba(255, 255, 255, 0.4);
+                font-size: 14px;
+            }
+        }
     }
 }
 </style>

+ 143 - 81
src/views/warningHome/map/distributionLayer.js

@@ -1,5 +1,5 @@
 import * as KMap from "@/utils/ol-map/KMap";
-import { Vector as VectorSource } from "ol/source.js";
+import { Vector as VectorSource, Cluster } from "ol/source.js";
 import { Feature } from "ol";
 import { WKT } from "ol/format";
 import { Circle, Fill, Stroke, Style, Text, Icon, RegularShape } from "ol/style.js";
@@ -22,87 +22,137 @@ class DistributionLayer {
         });
         this.kmap.addLayer(this.distributionLayer.layer)
 
-        this.distributionPointLayer = new KMap.VectorLayer("distributionPointLayer", 99, {
-            source: new VectorSource({}),
-            style: function (f) {
-                const label = f.get("label") || "";
-                const baseColor = f.get("color") || "#2199F8";
-                const farmName = f.get("farmName") || "";
-                const imgName = f.get("imgName") || "";
-                const speciesIcon = f.get("speciesIcon");
-
-                // 方块填充色背景(在白色描边 PNG 底图下方,参考 UI)
-                const squareBgStyle = new Style({
-                    image: new RegularShape({
-                        points: 4,
-                        radius: 34, // 控制方块大小
-                        angle: Math.PI / 4, // 旋转 45°,让正方形对齐
+        // 创建聚合数据源
+        this.clusterSource = new Cluster({
+            distance: 80, // 聚合距离(像素)
+            minDistance: 50, // 最小聚合距离
+        });
+
+        // 提取单个点样式函数
+        const getPointStyle = (feature, isCluster = false, clusterSize = 1) => {
+            const label = feature.get("label") || "";
+            const baseColor = feature.get("color") || "#2199F8";
+            const farmName = feature.get("farmName") || "";
+            const imgName = feature.get("imgName") || "";
+            const speciesIcon = feature.get("speciesIcon");
+
+            // 方块填充色背景(在白色描边 PNG 底图下方,参考 UI)
+            const squareBgStyle = new Style({
+                image: new RegularShape({
+                    points: 4,
+                    radius: 34, // 控制方块大小
+                    angle: Math.PI / 4, // 旋转 45°,让正方形对齐
+                    fill: new Fill({
+                        color: baseColor,
+                    }),
+                    imgSize: [34, 34],
+                    // 向上平移一点,让方块主要位于指针上方
+                    displacement: [0, 4],
+                }),
+            });
+
+            const iconAndTextStyle = new Style({
+                image: new Icon({
+                    src: pointBgImg,
+                    scale: 0.45,
+                }),
+                // 文字 + 背景(参考 UI:白色圆角矩形 + 彩色文字)
+                text: new Text({
+                    text: label,
+                    font: "normal 14px sans-serif",
+                    offsetY: -46, // 文字整体上移,避免和图片重叠
+                    textAlign: "center",
+                    fill: new Fill({
+                        color: baseColor,
+                    }),
+                    backgroundFill: new Fill({
+                        color: "rgba(255,255,255,1)",
+                    }),
+                    backgroundStroke: new Stroke({
+                        color: baseColor,
+                        width: 1,
+                    }),
+                    padding: [2, 14, 2, 14],
+                }),
+            });
+
+            // 聚合点数量徽章(红色圆形,白色文字,图片右上角)
+            // 图片 scale 0.45,假设原始图片约 100x100,实际显示约 45x45
+            // 右上角位置:offsetX 约 22-25,offsetY 约 -22-25(相对于图片中心)
+            const badgeStyle = isCluster && clusterSize > 1
+                ? new Style({
+                    image: new Circle({
+                        radius: 10,
                         fill: new Fill({
-                            color: baseColor,
+                            color: "#FF0000", // 红色
                         }),
-                        imgSize: [34, 34],
-                        // 向上平移一点,让方块主要位于指针上方
-                        displacement: [0, 4],
+                        // 定位到图片右上角
+                        displacement: [22, 25], // 相对于图片中心,向右上偏移
                     }),
-                });
-
-                const iconAndTextStyle = new Style({
-                    image: new Icon({
-                        src: pointBgImg,
-                        scale: 0.45,
+                    text: new Text({
+                        text: clusterSize.toString(),
+                        font: "bold 11px sans-serif",
+                        fill: new Fill({
+                            color: "#FFFFFF", // 白色文字
+                        }),
+                        // 文字位置与圆形位置一致
+                        offsetX: 22,
+                        offsetY: -25,
+                        textAlign: "center",
+                        textBaseline: "middle",
                     }),
-                    // 文字 + 背景(参考 UI:白色圆角矩形 + 彩色文字)
+                })
+                : null;
+
+            // 农场名称(显示在点位下方,白色文字)
+            // 聚合点显示第一个点的农场名称
+            const farmNameStyle = farmName
+                ? new Style({
                     text: new Text({
-                        text: label,
-                        font: "normal 14px sans-serif",
-                        offsetY: -46, // 文字整体上移,避免和图片重叠
+                        text: farmName,
+                        font: "normal 18px sans-serif",
+                        offsetY: 45, // 向下偏移,位于点位下方
                         textAlign: "center",
                         fill: new Fill({
-                            color: baseColor,
-                        }),
-                        backgroundFill: new Fill({
-                            color: "rgba(255,255,255,1)",
+                            color: "#FFFFFF",
                         }),
-                        backgroundStroke: new Stroke({
-                            color: baseColor,
-                            width: 1,
+                        stroke: new Stroke({
+                            color: "rgba(0,0,0,0.6)",
+                            width: 2,
                         }),
-                        padding: [2, 14, 2, 14],
                     }),
-                });
+                })
+                : null;
+            const typeImgStyle = new Style({
+                image: new Icon({
+                    // src: imgUrl,
+                    src: speciesIcon,
+                    scale: 0.8,
+                    displacement: [0, 4],
+                }),
+            });
 
-                // 农场名称(显示在点位下方,白色文字)
-                const farmNameStyle = farmName
-                    ? new Style({
-                        text: new Text({
-                            text: farmName,
-                            font: "normal 19px sans-serif",
-                            offsetY: 45, // 向下偏移,位于点位下方
-                            textAlign: "center",
-                            fill: new Fill({
-                                color: "#FFFFFF",
-                            }),
-                            stroke: new Stroke({
-                                color: "rgba(0,0,0,0.6)",
-                                width: 2,
-                            }),
-                        }),
-                    })
-                    : null;
-                const typeImgStyle = new Style({
-                    image: new Icon({
-                        // src: imgUrl,
-                        src: speciesIcon,
-                        scale: 0.8,
-                        displacement: [0, 4],
-                    }),
-                });
+            // 先画纯色方块,再画白色描边 PNG 和文字,然后画徽章,最后画农场名称
+            const styles = [squareBgStyle, iconAndTextStyle];
+            if (speciesIcon) styles.push(typeImgStyle);
+            if (badgeStyle) styles.push(badgeStyle);
+            if (farmNameStyle) styles.push(farmNameStyle);
+            return styles;
+        };
 
-                // 先画纯色方块,再画白色描边 PNG 和文字,最后画农场名称
-                const styles = [squareBgStyle, iconAndTextStyle];
-                if (speciesIcon) styles.push(typeImgStyle);
-                if (farmNameStyle) styles.push(farmNameStyle);
-                return styles;
+        this.distributionPointLayer = new KMap.VectorLayer("distributionPointLayer", 99, {
+            source: this.clusterSource,
+            style: (f) => {
+                // 判断是否为聚合点
+                const features = f.get('features');
+                if (features && features.length > 1) {
+                    // 聚合点:使用第一个点的样式,但添加数量标识
+                    const firstFeature = features[0];
+                    return getPointStyle(firstFeature, true, features.length);
+                }
+                // 单个点样式(原有逻辑)
+                const singleFeature = features && features.length === 1 ? features[0] : f;
+                return getPointStyle(singleFeature, false, 1);
             },
         });
         this.kmap.addLayer(this.distributionPointLayer.layer)
@@ -133,8 +183,8 @@ class DistributionLayer {
         if (this.distributionLayer && this.distributionLayer.source) {
             this.distributionLayer.source.clear();
         }
-        if (this.distributionPointLayer && this.distributionPointLayer.source) {
-            this.distributionPointLayer.source.clear();
+        if (this.clusterSource && this.clusterSource.source) {
+            this.clusterSource.source.clear();
         }
         if (this.facilityPointLayer && this.facilityPointLayer.source) {
             this.facilityPointLayer.source.clear();
@@ -142,10 +192,13 @@ class DistributionLayer {
     }
 
     initData(data, field = 'speciesName') {
-        // 每次加载前先清空旧数据(多用于“作物分布 / 物候期分布 / 农场分布”
+        // 每次加载前先清空旧数据(多用于"作物分布 / 物候期分布 / 农场分布"
         this.clear();
         if(!data || data.length === 0) return;
 
+        // 创建临时 VectorSource 用于存储点数据
+        const pointSource = new VectorSource({});
+
         for (let item of data) {
             // 面数据(区域多边形)
             if (item.geom) {
@@ -156,9 +209,14 @@ class DistributionLayer {
                 item.color = item.speciesColor || "#2199F8";
                 item.wkt = item.centerPoint;
                 item.label = item[field] || "";
-                this.distributionPointLayer.source.addFeature(newPoint(item));
+                pointSource.addFeature(newPoint(item));
             }
         }
+
+        // 将点数据源设置到聚合源
+        if (pointSource.getFeatures().length > 0) {
+            this.clusterSource.setSource(pointSource);
+        }
         // const extent = this.distributionLayer.source.getExtent();
         // if (extent && !isNaN(extent[0])) {
         //     this.kmap.map.getView().fit(extent, {
@@ -167,13 +225,17 @@ class DistributionLayer {
         //     });
         // }
 
-        // 所有点位添加完成后,地图范围自适应到包含 distributionPointLayer 的所有点
-        const pointExtent = this.distributionPointLayer.source.getExtent();
-        if (pointExtent && !isNaN(pointExtent[0])) {
-            this.kmap.map.getView().fit(pointExtent, {
-                padding: [280, 400, 200, 150],
-                duration: 500,
-            });
+        // 所有点位添加完成后,地图范围自适应到包含所有点
+        if (pointSource.getFeatures().length > 0) {
+            setTimeout(() => {
+                const pointExtent = this.clusterSource.source.getExtent();
+                if (pointExtent && !isNaN(pointExtent[0])) {
+                    this.kmap.map.getView().fit(pointExtent, {
+                        padding: [280, 400, 200, 150],
+                        duration: 500,
+                    });
+                }
+            }, 100);
         }
     }