Prechádzať zdrojové kódy

fix: 优化聚合代码,对接接口

lxf 1 týždeň pred
rodič
commit
a53dcc1fa0

+ 4 - 0
src/api/modules/manage_interface.js

@@ -25,4 +25,8 @@ module.exports = {
         url: config.base_url + "adm/batchOpenOrCloseSample",
         type: "post",
     },
+    speciesList: {
+        url: config.base_url + "species_item/list",
+        type: "get",
+    },
 }

+ 47 - 8
src/views/home/album_compoents/albumCarousel.vue

@@ -67,12 +67,19 @@
                                             style="width: 240px;"
                                             value-format="YYYY-MM-DD"
                                             placeholder="请选择栽种时间"
+                                            :disabled-date="disabledDate"
                                         />
                                     </el-form-item>
-                                    <el-form-item label="守护人" prop="user">
-                                        <el-select v-model="ruleForm.user" style="width: 240px;" placeholder="请选择守护人">
-                                            <el-option label="张三" value="张三" />
-                                            <el-option label="张四" value="张四" />
+
+                                    <el-form-item v-if="treeItemData?.miniUserId" label="守护人" prop="user">
+                                        <div class="avatar-content">
+                                            <el-avatar :size="36" :src="treeItemData.icon" />
+                                            <div class="avatar-name">{{ treeItemData.userNickName || treeItemData.miniUserId }}</div>
+                                        </div>
+                                    </el-form-item>
+                                    <el-form-item v-else label="预留守护人" prop="user">
+                                        <el-select v-model="ruleForm.offlineTakeSelected" filterable style="width: 240px;" placeholder="请选择守护人">
+                                            <el-option v-for="(user, index) in userList" :key="index" :label="user.name" :value="{value: user.tel, ...user}" />
                                         </el-select>
                                     </el-form-item>
                                 </el-form>
@@ -117,6 +124,7 @@ import "./cacheImg.js";
 import AlbumCarouselItem from "./albumCarouselItem";
 import { dateFormat } from "@/utils/date_util.js";
 import eventBus from "@/api/eventBus";
+import { ElMessage } from "element-plus";
 
 const lock = ref(false);
 const farmId = ref(766);
@@ -155,9 +163,11 @@ eventBus.off("click:point", handleClickPoint);
 eventBus.on("click:point", handleClickPoint);
 
 const sampleIdVal = ref(null)
+const treeItemData = ref({})
 function handleClickPoint({ farmId, sampleId, data }) {
     // sampleId = data.id;
     sampleIdVal.value = sampleId
+    treeItemData.value = data
     ruleForm.pz = data.pz
     ruleForm.age = data.age
     ruleForm.plantDate = data.plantDate
@@ -166,6 +176,9 @@ function handleClickPoint({ farmId, sampleId, data }) {
     isLoadingImg.value = true;
     let startDate = new Date(currentDate);
     startDate.setMonth(currentDate.getMonth() - 2);
+    if (!data.miniUserId && userList.value.length === 0) {
+        getUserList()
+    }
 
     // 格式化日期
     let params = { sampleId, farmId, startDate: dateFormat(startDate, "YY-mm-dd") };
@@ -179,6 +192,13 @@ function handleClickPoint({ farmId, sampleId, data }) {
     showTag.value = data.nonghu == 1 ? true : false;
 }
 
+const userList = ref([])
+function getUserList() {
+    VE_API.manage_interface.offlineTakeList({farmId: 766}).then(({data}) => {
+        userList.value = data
+    })
+}
+
 // eventBus.off("albumCarousel", toggleActiveImg);
 // eventBus.on("albumCarousel", toggleActiveImg);
 
@@ -244,7 +264,7 @@ const ruleForm = reactive({
     pz: "",
     age: null,
     plantDate: "",
-    offlineTakeSelected: [],
+    offlineTakeSelected: {},
 });
 
 const rules = reactive({
@@ -260,15 +280,27 @@ const rules = reactive({
     ],
 });
 
+// 禁用当前日期之后的日期
+const disabledDate = (time) => {
+  // 获取当前时间(去掉时分秒,只比较日期部分)
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  
+  // 禁用今天之后的日期
+  return time.getTime() > today.getTime();
+};
+
 async function saveEdit(isToSave) {
     if (isToSave) {
         // 保存
         await ruleFormRef.value.validate((valid, fields) => {
             if (valid) {
                 console.log('ruleForm', ruleForm);
-                console.log('submit!')
-                VE_API.manage_interface.editFosterSample({...ruleForm, sampleId: sampleIdVal.value}).then(() => {
-                    console.log('dadata');
+                VE_API.manage_interface.editFosterSample({...ruleForm, sampleId: sampleIdVal.value}).then((res) => {
+                    if (res.code === 0) {
+                        ElMessage.success("修改成功")
+                        eventBus.emit("refreshMapData")
+                    }
                 })
             } else {
                 console.log('error submit!', fields)
@@ -442,6 +474,13 @@ async function saveEdit(isToSave) {
                 flex: 1;
             }
         }
+        .avatar-content {
+            display: flex;
+            align-items: center;
+            .avatar-name {
+                padding-left: 10px;
+            }
+        }
         .box-wrap {
             display: flex;
             .box-item {

+ 51 - 11
src/views/home/components/adoptList.vue

@@ -57,10 +57,10 @@
                 <el-date-picker
                     v-model="batchTotal"
                     type="date"
-                    format="YYYY.MM.DD"
+                    format="YYYY-MM-DD"
                     style="width: 190px"
-                    value-format="YYYY.MM.DD"
-                    placeholder="栽种时间"
+                    value-format="YYYY-MM-DD"
+                    placeholder="选择时间"
                 />
             </div>
         </div>
@@ -103,7 +103,12 @@
                         
                         <div class="center-item p-t-2 has-input edit-item" v-show="(isManySetting && settingType === 'price') || item.settingPrice">
                             <span class="edit-label">品种:</span>
-                            <el-input @change="settingSinglePrice" style="width: 140px" class="number-input" v-model="item.pz" />
+                            
+
+                            <el-select v-model="item.pz" filterable style="width: 140px;" placeholder="请选择品种">
+                                <el-option v-for="(species, index) in speciesList" :key="index" :label="species.name" :value="species.name" />
+                            </el-select>
+                            <!-- <el-input @change="settingSinglePrice" style="width: 140px" class="number-input" v-model="item.pz" /> -->
                         </div>
 
                         <div class="center-item" v-show="(isManySetting && settingType !== 'total')"><span class="edit-label">栽种时间:</span><span class="unit">{{item.time}}</span></div>
@@ -112,18 +117,22 @@
                             <span class="edit-label">栽种时间:</span>
                             <!-- <el-input-number @change="settingSinglePrice" class="number-input" :controls="false" v-model="item.total" :min="0" /> -->
                             <el-date-picker
-                                v-model="item.time"
+                                v-model="item.plantDate"
                                 type="date"
-                                format="YYYY.MM.DD"
+                                format="YYYY-MM-DD"
                                 style="width: 140px"
-                                value-format="YYYY.MM.DD"
-                                placeholder="栽种时间"
+                                value-format="YYYY-MM-DD"
+                                placeholder="选择时间"
                             />
                         </div>
 
                         <div class="center-item p-t-2 user-wrap" v-show="item.status === 0 && !isManySetting && !item.settingPrice">
                             <img src="@/assets/images/foster-home/user.png" alt="">
                             选择守护人
+
+                            <el-select v-model="offlineTakeSelected" filterable style="width: 240px;" placeholder="请选择守护人">
+                                <el-option v-for="(user, index) in userList" :key="index" :label="user.name" :value="{value: user.tel, ...user}" />
+                            </el-select>
                         </div>
                         <div class="center-item p-t-2 progress-wrap" v-show="item.status === 1 && !isManySetting && !item.settingPrice">
                             守护人:
@@ -144,7 +153,7 @@
                 <div v-show="item.settingPrice" class="btn-group edit-one">
                     <!-- 渐变主色按钮 -->
                     <div class="btn cancel-btn" @click="toSettingSinglePrice(index, false)">取消</div>
-                    <div class="btn edit-btn" @click="toSettingSinglePrice(index, false)">确认修改</div>
+                    <div class="btn edit-btn" @click="toSettingSinglePrice(index, false, true)">确认修改</div>
                 </div>
             </div>
             <div class="pagination-wrap">
@@ -163,6 +172,7 @@
 </template>
 
 <script setup>
+import { ElMessage } from "element-plus";
 import { onMounted, ref } from "vue";
 import { useStore } from "vuex";
 let store = useStore();
@@ -210,8 +220,21 @@ const adoptList = ref([]);
 
 const owners = ref([{userName: "王丽丽", value: 50}])
 
+
+const offlineTakeSelected = ref(null)
+const userList = ref([])
+const speciesList = ref([])
+function getUserList() {
+    VE_API.manage_interface.offlineTakeList({farmId: 766}).then(({data}) => {
+        userList.value = data
+    })
+    VE_API.manage_interface.speciesList({farmId: 766}).then(({data}) => {
+        speciesList.value = data
+    })
+}
 onMounted(() => {
     getSamplePage()
+    getUserList()
 });
 
 const currentPage = ref(1)
@@ -251,9 +274,26 @@ function setManyPrice(v) {
 
 // 设置单棵树单价
 
-function toSettingSinglePrice(i, val) {
+function toSettingSinglePrice(i, val, toSave = false) {
   console.log('tototot');
-  adoptList.value[i].settingPrice = val
+  const data = adoptList.value[i]
+  if (toSave) {
+    const params = {
+        sampleId: data.sampleId,
+        pz: data.pz,
+        age: data.age,
+        plantDate: data.plantDate,
+        offlineTakeSelected: {...offlineTakeSelected.value}
+    }
+    VE_API.manage_interface.editFosterSample(params).then((res) => {
+        if (res.code === 0) {
+            ElMessage.success("修改成功")
+            adoptList.value[i].settingPrice = val
+        }
+    })
+  } else {
+    adoptList.value[i].settingPrice = val
+  }
 }
 function settingSinglePrice() {
   console.log('sss');

+ 0 - 311
src/views/home/homeMap-amap.vue

@@ -1,311 +0,0 @@
-<template>
-    <div id="map-container"></div>
-</template>
-
-<script setup>
-import { ref, onMounted, onUnmounted } from "vue";
-import AMapLoader from "@amap/amap-jsapi-loader";
-import eventBus from "@/api/eventBus";
-import { convertPointToArray } from "@/utils/index";
-import { deepClone } from "@/common/commonFun";
-import wellknown from 'wellknown';
-import {base_img_url,base_img_url3} from "@/api/config";
-
-const map = ref(null);
-const mouseTool = ref(null);
-const selectedArea = ref(null);
-const selectedPoints = ref([]);
-const allPoints = ref([]);
-const defalutAllPoints = ref([])
-const pointList = ref([])
-
-const mapEventType = ref('rectangle')
-
-// 自定义圆点样式
-const createMarkerContent = (color = 'rgba(0, 0, 0, 0.4)') => {
-    return `
-        <div style="
-            width: 15px;
-            height: 15px;
-            background-color: ${color};
-            border: 1px solid #fff;
-            border-radius: 50%;
-        ">
-        </div>
-    `;
-}
-
-//高亮样式
-const createMarkerImg = () => {
-    return `
-        <img style="width: 38px;height: 38px;" src="${require('@/assets/images/map/status/active-icon.png')}">
-    `;
-}
-// 初始化地图
-const initMap = async () => {
-    try {
-        const AMap = await AMapLoader.load({
-            key: "41769169f0f157e13a197e7eb0dd7b5b", // 替换为你的高德地图 Key
-            version: "2.0", // SDK 版本
-            plugins: ["AMap.MouseTool","AMap.MarkerCluster"], // 加载 MouseTool 插件
-        }).then(async (AMap) => {
-            // 创建地图实例
-            map.value = new AMap.Map("map-container", {
-                zoom: 18, // 初始缩放级别
-                center: [111.01055729572, 21.7743543],
-                layers: [
-                    // 天地图卫星
-                    new AMap.TileLayer({
-                        tileSize: 256, // 瓦片大小
-                        getTileUrl: function(x, y, z) {
-                            // 天地图影像 WMTS 地址
-                            const s = Math.floor(Math.random() * 8); // 随机选择服务器节点
-                            // return `https://t{0-7}.tianditu.gov.cn/img_c/w mts?tk=e95115c454a663cd052d96019fd83840`
-                            return `https://t${s}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX=${z}&TILEROW=${y}&TILECOL=${x}&tk=e95115c454a663cd052d96019fd83840`;
-                        },
-                    }),
-                    //天地图路网
-                    new AMap.TileLayer({
-                        tileSize: 256, // 瓦片大小
-                        getTileUrl: function(x, y, z) {
-                            // 天地图影像 WMTS 地址
-                            const s = Math.floor(Math.random() * 8); // 随机选择服务器节点
-                            return `https://t${s}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX=${z}&TILEROW=${y}&TILECOL=${x}&tk=e95115c454a663cd052d96019fd83840`;
-                        },
-                    }),
-                    //正射影像
-                    new AMap.TileLayer({
-                        tileUrl: base_img_url + 'map/lby/[z]/[x]/[y].png',
-                        zIndex:2
-                    }),
-                    new AMap.TileLayer({
-                        tileUrl: base_img_url3 + 'map/lby/[z]/[x]/[y].png',
-                        zIndex:2
-                    })
-                ],
-            });
-
-            // // 初始化点聚合
-            // const cluster = new AMap.MarkerCluster(map, markers, {
-            //     gridSize: 60,          // 聚合网格像素大小
-            //     maxZoom: 15,           // 最大聚合级别
-            //     renderClusterMarker: customizeCluster // 自定义聚合样式
-            // });
-
-            // 初始化 MouseTool
-            mouseTool.value = new AMap.MouseTool(map.value);
-            
-            // startRectangleSelection()
-
-            // 监听框选完成事件
-            mouseTool.value.on("draw", (event) => {
-                if(mapEventType.value==='click') return
-                const { obj} = event;
-                if (obj instanceof AMap.Rectangle) {
-                    selectedArea.value = obj;
-                    map.value.remove(selectedArea.value);
-                    checkPointsInArea(); // 检查框选区域内的点位
-                }
-            });
-
-        });
-    } catch (error) {
-        console.error("地图加载失败:", error);
-    }
-};
-
-//重置数据
-const resetData = () =>{
-    allPoints.value = defalutAllPoints.value
-    allPoints.value.forEach(item =>{
-        item.icon.setContent(createMarkerContent(item.color))
-        item.icon.setOffset(new window.AMap.Pixel(-7, -7))
-        item.type = 'defalutIcon'
-    })
-}
-
-//初始化数据
-const initData = (data) =>{
-    allPoints.value = data
-    defalutAllPoints.value = []
-    totalList.value.forEach(item => map.value.remove(item.icon))
-    totalList.value = []
-    allPoints.value.forEach(item =>{
-        item.icon = new window.AMap.Marker({
-            position: item.lnglat,
-            map: map.value,
-            content: createMarkerContent(item.color),
-            offset: new window.AMap.Pixel(-7, -7),
-            extData: { id: item.id, priority: item.status > item.noImg > item.wys } // 标记优先级
-        });
-        item.type = 'defalutIcon'
-
-        // 绑定点击事件
-        item.icon.on('click', () => {
-            mapEventType.value = 'click'
-            if(item.type === 'defalutIcon'){
-                if (startDraw.value) {
-                    item.icon.setContent(createMarkerImg()); 
-                    item.icon.setOffset(new window.AMap.Pixel(-18, -18))
-                    item.type = 'activeIcon'
-                    const arr = pointList.value.filter(ele => ele.id === item.id)
-                    if(arr.length===0){
-                        pointList.value.push(item)
-                    }
-                }
-            }else{
-                item.icon.setContent(createMarkerContent(item.color)); 
-                item.icon.setOffset(new window.AMap.Pixel(-7, -7))
-                item.type = 'defalutIcon'
-            }
-
-            console.log('item', item);
-            eventBus.emit("clickMapPoint", item)
-            setTimeout(()=>{
-                mapEventType.value = 'draw'
-            },100)
-        });
-        defalutAllPoints.value.push(item)
-    })
-    // 初始化点聚合
-    // console.log('data',data);
-    // const cluster = new window.AMap.MarkerCluster(map.value, data, {
-    //     gridSize: 30,          // 聚合网格像素大小
-    //     // maxZoom: 15,           // 最大聚合级别
-    //     // renderClusterMarker: customizeCluster // 自定义聚合样式
-    // });
-    setTimeout(()=>{
-        map.value.setFitView(null, false, [0, 0, 0, 0]);
-    },100)
-    // 范围
-    // const p1 = [
-    //     [111.00745562137854, 21.77564355219471],
-    //     [111.00982318507914, 21.77557930674749],
-    //     [111.01133652227907, 21.776531091149934],
-    //     [111.0125762214629, 21.775945743742568],
-    //     [111.01290458708189, 21.775241423284797],
-    //     [111.01341617119812, 21.773759019078113],
-    //     [111.01320915809049, 21.77329502418189],
-    //     [111.01209794980082, 21.772538355582128],
-    //     [111.01117233946951, 21.772414623609905],
-    //     [111.00909744947245, 21.772878618505956],
-    //     [111.00744610353439, 21.774094523079953],
-    //     [111.00745562137854, 21.77564355219471]
-    //     ]
-    // addPolygon(p1)
-    
-}
-
-const startDraw = ref(false)
-// 开始框选
-const startRectangleSelection = () => {
-    if (mouseTool.value) {
-        startDraw.value = true
-        mouseTool.value.rectangle({
-            strokeColor: "#ffffff", // 框选区域边框颜色
-            strokeOpacity: 1, // 边框透明度
-            strokeWeight: 2, // 边框宽度
-            fillColor: "#000000", // 填充颜色
-            fillOpacity: 0.5, // 填充透明度
-        }); // 启用矩形框选工具
-        map.value.setDefaultCursor("crosshair");
-    }
-};
-
- // 停止绘制矩形
-const stopDrawRectangle = () => {
-    mouseTool.value.close(); // 关闭 mouseTool
-    map.value.setDefaultCursor("default");
-};
-
-// 清除框选
-const clearSelection = () => {
-    if (selectedArea.value) {
-        map.value.remove(selectedArea.value); // 移除框选区域
-        selectedArea.value = null;
-    }
-    resetData()                                                                                                                                                                                                                                                                                                                                      
-    selectedPoints.value = []; // 清空选中的点位
-};
-
-// 检查框选区域内的点位
-const checkPointsInArea = () => {
-    if (!selectedArea.value) return;
-    const bounds = selectedArea.value.getBounds(); // 获取框选区域的边界
-    selectedPoints.value = allPoints.value.filter((point) => {
-        return bounds.contains(point.lnglat); // 判断点位是否在框选区域内
-    });
-    
-    selectedPoints.value.forEach(item =>{
-        if(item.type==='defalutIcon'){
-            item.icon.setContent(createMarkerImg())
-            item.icon.setOffset(new window.AMap.Pixel(-18, -18))
-            item.type = 'activeIcon'
-        }else{
-            item.icon.setContent(createMarkerContent(item.color))
-            item.icon.setOffset(new window.AMap.Pixel(-7, -7))
-            item.type = 'defalutIcon'
-        }
-    })
-};
-
-const totalList = ref([])
-
-function getSelectPoint(){
-    totalList.value = selectedPoints.value.concat(pointList.value)
-    const arr = totalList.value.filter(item =>item.type==='activeIcon')
-    return arr
-}
-
-function getRegionList(farmId) {
-    VE_API.region.list({farmId}).then(({data}) =>{
-        data.map(item => {
-            addPolygon(wktToAmapPolygon(item.wkt))
-        })
-    })
-}
-
-function addPolygon(data) {
-    console.log('dddddd', data);
-    let polygon = new window.AMap.Polygon({
-      path: data,
-      fillColor: '#FFE17E',
-      strokeOpacity: 1,
-      fillOpacity: 0.1,
-      strokeColor: 'rgba(255, 255, 255, 0.3)',
-      strokeWeight: 1,
-      strokeStyle: 'solid',
-    //   strokeDasharray: [5, 5],
-    });
-    map.value.add(polygon)
-}
-
-defineExpose({getSelectPoint,initData,startRectangleSelection,stopDrawRectangle,clearSelection, getRegionList})
-
-// 组件挂载时初始化地图
-onMounted(() => {
-    initMap()
-});
-
-onUnmounted(()=>{
-    map.value.destroy()// 销毁地图实例以释放资源
-})
-
-function wktToAmapPolygon(wkt) {
-    const geoJSON = wellknown.parse(wkt);
-    if (!geoJSON || geoJSON.type !== 'MultiPolygon') {
-        console.error('Invalid WKT or not a MULTIPOLYGON');
-        return [];
-    }
-
-    // MULTIPOLYGON 的坐标结构:[[[ [环1], [环2], ... ]]],取第一个环
-    return geoJSON.coordinates[0][0].map(coord => [coord[0], coord[1]]);
-}
-</script>
-
-<style scoped>
-#map-container {
-    width: 100%;
-    height: 100%;
-}
-</style>

+ 322 - 295
src/views/home/homeMap.vue

@@ -19,54 +19,118 @@ import { boundingExtent, getCenter } from "ol/extent.js";
 import RegionLayer from "./map/regionLayer";
 import eventBus from "@/api/eventBus";
 import albumCarousel from "./album_compoents/albumCarousel.vue";
-// import MockFarmLayer from "./map/mockFarmLayer";
-
 import { onMounted, ref } from "vue";
 import { useStore } from "vuex";
 import { unByKey } from "ol/Observable";
-let store = useStore();
-
-// 选中样式(高亮)
-// const selectedStyle = new Style({
-//     image: new Icon({
-//         src: require("@/assets/images/map/status/active-icon.png"),
-//         scale: 0.6,
-//     }),
-// });
 
-let kmap = null;
+const store = useStore();
 const areaRef = ref(null);
-// let mockFarmLayer = null;
-let dragBox;
+const currentTree = ref(null);
+const isDrawing = ref(false);
+const mapPoint = ref([]);
+let kmap = null;
+let treeClusterLayer = null;
+let dragBox = null;
+let listenKey = null;
+let regionLayer = null;
+let clusterSource = null;
+
+// 样式缓存优化
+// 样式缓存优化 - 确保所有样式都正确初始化
+const styleCache = {
+    default: new Style({
+        image: new Circle({
+            radius: 10,
+            fill: new Fill({ color: "#ffffff00" }),
+            stroke: new Stroke({ color: "#fff", width: 1 }),
+        }),
+    }),
+    selected: new Style({
+        image: new Icon({
+            src: require("@/assets/images/map/status/active-icon.png"),
+            scale: 0.6,
+            crossOrigin: 'anonymous' // 添加crossOrigin属性
+        }),
+    }),
+    current: new Style({
+        image: new Icon({
+            src: require("@/assets/images/map/status/current-icon.png"),
+            scale: 0.8,
+            crossOrigin: 'anonymous'
+        }),
+    }),
+    renYang1: new Style({
+        image: new Circle({
+            radius: 12,
+            fill: new Fill({ color: "#EEEEEE" }),
+            stroke: new Stroke({ color: "#fff", width: 1 }),
+        }),
+    }),
+    renYang2: new Style({
+        image: new Circle({
+            radius: 12,
+            fill: new Fill({ color: "#F0AC37" }),
+            stroke: new Stroke({ color: "#fff", width: 1 }),
+        }),
+    }),
+    clusterText: (size, isRenyang) => new Style({
+        text: new Text({
+            text: size.toString(),
+            font: "bold 10px sans-serif",
+            fill: new Fill({ color: isRenyang === 1 ? "#000" : "#fff" }),
+        }),
+        zIndex: 3,
+    }),
+    photoStyle: (src) => {
+        try {
+            return new Style({
+                image: new Photo({
+                    src: src,
+                    kind: "circle",
+                    radius: 22,
+                    shadow: 0,
+                    crop: false,
+                    displacement: [0, 0],
+                    stroke: new Stroke({ width: 2, color: "#fff" }),
+                }),
+                zIndex: 4
+            });
+        } catch (e) {
+            console.warn('Failed to create photo style:', e);
+            return new Style({
+                image: new Circle({
+                    radius: 12,
+                    fill: new Fill({ color: "#F0AC37" }),
+                    stroke: new Stroke({ color: "#fff", width: 1 }),
+                }),
+            });
+        }
+    }
+};
 
-const currentTree = ref(null)
+// 初始化地图
+onMounted(() => {
+    const level = 16;
+    const coordinate = util.wktCastGeom(store.getters.userinfo.location).getFirstCoordinate();
+    kmap = new KMap.Map(areaRef.value, level, coordinate[0], coordinate[1], null, 1, 22, "img", true, true);
+    regionLayer = new RegionLayer(kmap);
+});
 
-const isDrawing = ref(false);
+// 启用框选
 const enableBoxSelect = () => {
-    const data = mapPoint.value.filter((item) => !item.isRenyang);
-    addCluster(data, 1);
+    const data = mapPoint.value.filter(item => !item.isRenyang);
+    updateClusterData(data, 1);
     isDrawing.value = true;
+    
     dragBox = new DragBox({
-        condition: platformModifierKeyOnly, // 按住 Ctrl 才可框选(可去掉)
+        condition: platformModifierKeyOnly,
     });
 
-    // 添加键盘事件监听
     window.addEventListener("keydown", handleKeyDown);
     window.addEventListener("keyup", handleKeyUp);
 
     kmap.map.addInteraction(dragBox);
-    // areaRef.value.style.cursor = 'crosshair';
-    dragBox.on("boxend", () => {
-        const extent = dragBox.getGeometry().getExtent();
-
-        treeClusterLayer.layer.getSource().getSource().getFeatures().forEach((feature) => {
-            const isInside = feature.getGeometry().intersectsExtent(extent);
-            if (isInside) {
-                // feature.setStyle(selectedStyle);
-                feature.set("highlight", true);
-            }
-        });
-    });
+    dragBox.on("boxend", handleBoxEnd);
 };
 
 const handleKeyDown = (e) => {
@@ -81,309 +145,272 @@ const handleKeyUp = (e) => {
     }
 };
 
+const handleBoxEnd = () => {
+    const extent = dragBox.getGeometry().getExtent();
+    treeClusterLayer.layer.getSource().getSource().getFeatures().forEach(feature => {
+        // 只对当前框选范围内且未被认养的树进行操作
+        if (feature.getGeometry().intersectsExtent(extent)) {
+            // 累加选中状态(不会取消之前选中的)
+            if (!feature.get("isRenyang")) {
+                feature.set("highlight", true);
+            }
+        }
+    });
+    refreshClusterStyle();
+};
+
+// 停止框选
 const stopBoxSelect = () => {
-    addCluster(mapPoint.value);
+    updateClusterData(mapPoint.value);
     kmap.map.removeInteraction(dragBox);
-    isDrawing.value = false
-    // 移除事件监听
+    isDrawing.value = false;
     window.removeEventListener("keydown", handleKeyDown);
     window.removeEventListener("keyup", handleKeyUp);
 };
 
-function saveSelect(callback) {
-    let sampleIds = []
-    treeClusterLayer.layer.getSource().getSource().getFeatures().forEach((feature) => {
+// 保存选择
+const saveSelect = (callback) => {
+    const sampleIds = [];
+    treeClusterLayer.layer.getSource().getSource().getFeatures().forEach(feature => {
         if (feature.get("highlight")) {
-            sampleIds.push(feature.get("sampleId"))
+            sampleIds.push(feature.get("sampleId"));
         }
-    })
+    });
+    
     const params = {
         sampleIds,
         isRenyang: 1,
         farmId: Number(sessionStorage.getItem("currentFarmId"))
-    }
-    VE_API.manage_interface.batchOpenOrCloseSample(params).then(() => {
-        callback && callback()
-    })
-    console.log('sampleIds', sampleIds);
-}
-
-let treeClusterLayer;
-onMounted(() => {
-    let level = 16;
-    let coordinate = util.wktCastGeom(store.getters.userinfo.location).getFirstCoordinate();
-    kmap = new KMap.Map(areaRef.value, level, coordinate[0], coordinate[1], null, 1, 22, "img", true, true);
-    regionLayer = new RegionLayer(kmap);
-    // mockFarmLayer = new MockFarmLayer(kmap)
-    // VE_API.info.contactsList().then(res => {
-    //   if(res.code ===0){
-    //     mockFarmLayer.setData(res.data)
-    //   }
-    // })
+    };
+    
+    VE_API.manage_interface.batchOpenOrCloseSample(params).then(() => {// 保存成功后刷新数据
+        callback?.();
+    });
+};
 
-});
-let styleCache = {};
-let listenKey;
-const mapPoint = ref([]);
-let clusterSource;
-// --------聚合-----------
-function addCluster(treeListData, distanceVal) {
-    if (!distanceVal) {
+// 更新集群数据而不重新渲染整个图层
+const updateClusterData = (treeListData, distanceVal = 20) => {
+    if (!treeListData || !Array.isArray(treeListData)) {
+        console.error('Invalid data provided to updateClusterData');
+        return;
+    }
+    if (distanceVal === 20) {
         mapPoint.value = treeListData;
     }
-    clearCluster();
-    let features = [];
-    // 根据状态加上对应的图标
-    for (let item of treeListData) {
+    const features = treeListData.map(item => {
         try {
-            item.wkt = item.geom
-            let point = newPoint(item);
-            features.push(point);
+            item.wkt = item.geom;
+            return newPoint(item);
         } catch (err) {
-            console.error('err', err)
+            console.error('Error creating point:', err);
+            return null;
+        }
+    }).filter(Boolean);
+
+    if (!treeClusterLayer) {
+        createClusterLayer(features, distanceVal);
+    } else {
+        // 只更新数据源而不重新创建图层
+        clusterSource.getSource().clear();
+        clusterSource.getSource().addFeatures(features);
+        clusterSource.setDistance(distanceVal);
+        
+        if (!distanceVal) {
+            zoomToFeatures(features);
         }
     }
-    const source = new VectorSource({
-        features: features,
-    });
-    clusterSource = new Cluster({
-        distance: distanceVal ? distanceVal : 20, // 集合范围
-        // minDistance: 60,
-        source: source,
-    });
+};
+
+// 创建集群图层
+const createClusterLayer = (features, distance) => {
+    const source = new VectorSource({ features });
+    clusterSource = new Cluster({ distance, source });
 
     treeClusterLayer = new KMap.VectorLayer("forest-cluster", 1000, {
         minZoom: 15,
         source: clusterSource,
-        style: (feature) => {
-            const size = feature.get("features").length;
-            let testStyle;
-            // 只有一个数据,不需要聚合,直接使用第一项数据的图标
-            if (size == 1) {
-               let featureOne = feature.get("features")[0];
-                const key = featureOne.get('isRenyang')+"status"
-                let style = styleCache[key];
-                // let style = false;
-                if (!style) {
-                    const highlight = featureOne.get("highlight");
-                    const currentTree = featureOne.get("currentTree");
-                    if (highlight) {
-                        style = new Style({
-                            image: new Icon({
-                                src: require("@/assets/images/map/status/active-icon.png"),
-                                scale: 0.6,
-                            }),
-                        });
-                    } else if (currentTree) {
-                        style = new Style({
-                            image: new Icon({
-                                src: require("@/assets/images/map/status/current-icon.png"),
-                                scale: 0.8,
-                            }),
-                        });
-                    } else {
-                        style = new Style({
-                            image: new Circle({
-                                radius: featureOne.get("isRenyang") === 0 || !featureOne.get("isRenyang") ? 10 : 12,
-                                fill: new Fill({
-                                    color:
-                                        featureOne.get("isRenyang") === 0
-                                            ? "#ffffff00"
-                                            : featureOne.get("isRenyang") === 1
-                                            ? "#EEEEEE"
-                                            : featureOne.get("isRenyang") === 2
-                                            ? "#F0AC37"
-                                            : "#ffffff00",
-                                }),
-                                stroke: new Stroke({
-                                    color: "#fff",
-                                    width: 1,
-                                }),
-                            }),
-                        });
-                    }
-                    // styleCache[key] = style;
-                }
-                const imgIcon = featureOne.get('icon')
-                if (imgIcon) {
-                    style = new Style({
-                        // image: new Icon({
-                        //     src: imgIcon,
-                        //     scale: 1,
-                        // }),
-                        image: new Photo({
-                            src: imgIcon,
-                            kind: "circle",
-                            radius: 22,
-                            shadow: 0,
-                            crop: false,
-                            displacement: [0, 0],
-                            stroke: new Stroke({
-                                width: 2,
-                                color: "#fff",
-                            }),
-                        }),
-                        zIndex: 4
-                    });
-                }
-                return style;
-            }
-            // 多个点位聚合,循环处理得到图标
-            const featureObj = feature.get("features")[0];
-            // let pointId = featureObj.get('isRenyang')
-            // let style = styleCache[pointId];
-                const imgIcon = featureObj.get('icon')
-            let style = false;
-            if (!style) {
-                testStyle = new Style({
-                    text: new Text({
-                        text: size + "",
-                        offsetX: 0,
-                        offsetY: 0,
-                        font: "bold 10px sans-serif",
-                        fill: new Fill({ color: featureObj.get("isRenyang") === 1 ? "#000" : "#fff" }), // 字体颜色
-                        // stroke: new Stroke({ color: "green" }), // 字体颜色
-                    }),
-                    zIndex: 3,
-                });
-
-                const highlight = featureObj.get("highlight");
-                const currentTree = featureObj.get("currentTree");
-                if (highlight) {
-                    style = new Style({
-                        image: new Icon({
-                            src: require("@/assets/images/map/status/active-icon.png"),
-                            scale: 0.6,
-                        }),
-                    });
-                } else if (currentTree) {
-                    style = new Style({
-                        image: new Icon({
-                            src: require("@/assets/images/map/status/current-icon.png"),
-                            scale: 0.8,
-                        }),
-                    });
-                } else {
-                    // 已认养--显示图标
-                    if (imgIcon) {
-                        style = new Style({
-                            image: new Photo({
-                                src: imgIcon,
-                                kind: "circle",
-                                radius: 22,
-                                shadow: 0,
-                                crop: false,
-                                displacement: [0, 0],
-                                stroke: new Stroke({
-                                    width: 2,
-                                    color: "#fff",
-                                }),
-                            }),
-                            zIndex: 4
-                        });
-                    } else {
-                        style = new Style({
-                            image: new Circle({
-                                radius: featureObj.get("isRenyang") === 0 || !featureObj.get("isRenyang") ? 10 : 12,
-                                fill: new Fill({
-                                    color:
-                                        featureObj.get("isRenyang") === 0
-                                            ? "#ffffff00"
-                                            : featureObj.get("isRenyang") === 1
-                                            ? "#EEEEEE"
-                                            : featureObj.get("isRenyang") === 2
-                                            ? "#F0AC37"
-                                            : "#ffffff00",
-                                }),
-                                stroke: new Stroke({
-                                    color: "#fff",
-                                    width: 1,
-                                }),
-                            }),
-                        });
-                    }
-                }
-                // styleCache[pointId] = style;
-            }
-            return [style, testStyle];
-        },
+        style: clusterStyleFunction
     });
 
     kmap.addLayer(treeClusterLayer.layer);
+    // if (!distance) {
+    // }
+    zoomToFeatures(features);
+
+    setupMapClickHandler();
+};
 
-    if(!distanceVal) {
-        zoomToFeatures(features)
+// 更新后的集群样式函数
+const clusterStyleFunction = (feature) => {
+    const size = feature.get("features").length;
+    const features = feature.get("features");
+    
+    if (!features || features.length === 0) {
+        return new Style({}); // 返回空样式作为fallback
     }
-    // kmap.getView().fit(this.treeClusterLayer.layer.getSource().getSource().getExtent(), { duration: 1000, padding: [120, 120, 200, 120] });
-    // 监听聚合点位的点击,点击后缩放到范围内
-    listenKey = kmap.on("click", (e) => {
-        if (treeClusterLayer) {
-            treeClusterLayer.layer.getFeatures(e.pixel).then((clickedFeatures, layer) => {
-                let hasFeatures = false
-                if (clickedFeatures.length) {
-                    const features = clickedFeatures[0].get("features");
-                    if (features.length > 1) {
-                        hasFeatures = true
-                        const extent = boundingExtent(features.map((r) => r.getGeometry().getCoordinates()));
-                        kmap.getView().fit(extent, { duration: 1000, padding: [250, 250, 250, 250] });
-
-                        const currentZoom = kmap.getView().getZoom();
-                        if (currentZoom > 17) {
-                            // this.kmap.getView().setZoom(16);
-                            // kmap.getView().animate({
-                            //     zoom: 14,
-                            //     duration: 0 // 动画持续时间,单位为毫秒
-                            // });
-                            kmap.getView().setZoom(17)
-                        }
-                    }
-                    if (isDrawing.value) {
-                        features[0].set("highlight", true);
-                        // features[0].setStyle(selectedStyle)
-                    } else if (!hasFeatures) {
-                        resetCurrentTree()
-                        features[0].set("currentTree", true)
-                        const fs = features[0]
-                        currentTree.value = features[0]
-                        console.log('hasFeatures', fs);
-                        eventBus.emit("click:point", { farmId: Number(sessionStorage.getItem("currentFarmId")), sampleId: fs.get("sampleId"), data: fs.getProperties() })
-                        // eventBus.emit("clickMapPoint", features[0])
-                    }
-                }
-            });
+
+    if (size === 1) {
+        return getSingleFeatureStyle(features[0]);
+    }
+    
+    return getClusterFeatureStyle(features[0], size);
+};
+
+// 更新后的获取单个要素样式函数
+const getSingleFeatureStyle = (feature) => {
+    if (!feature) return new Style({}); // 防御性编程
+
+    // 有图标的使用照片样式优先
+    const imgIcon = feature.get('icon');
+    if (imgIcon) {
+        try {
+            return styleCache.photoStyle(imgIcon);
+        } catch (e) {
+            console.warn('Failed to create photo style:', e);
+            return styleCache.default;
+        }
+    }
+    
+        
+    // 高亮样式其次
+    if (feature.get("highlight")) {
+        return styleCache.selected;
+    }
+    
+    // 当前树样式其次
+    if (feature.get("currentTree")) {
+        return styleCache.current;
+    }
+    
+
+    // 根据认养状态返回不同样式
+    const isRenyang = feature.get("isRenyang");
+    switch (isRenyang) {
+        case 1: return styleCache.renYang1;
+        case 2: return styleCache.renYang2;
+        default: return styleCache.default;
+    }
+};
+
+// 更新后的获取集群要素样式函数
+const getClusterFeatureStyle = (feature, size) => {
+    if (!feature) return [styleCache.default];
+    
+    const styles = [];
+    const baseStyle = getSingleFeatureStyle(feature);
+    
+    // 确保基础样式有效
+    if (baseStyle) {
+        styles.push(baseStyle);
+    }
+    
+    // 添加集群文字样式
+    if (size > 1) {
+        const textStyle = styleCache.clusterText(size, feature.get("isRenyang"));
+        if (textStyle) {
+            styles.push(textStyle);
         }
+    }
+    
+    return styles.length > 0 ? styles : [styleCache.default];
+};
+
+// 刷新集群样式(不重新创建图层)
+const refreshClusterStyle = () => {
+    if (treeClusterLayer) {
+        treeClusterLayer.layer.getSource().refresh();
+    }
+};
+
+// 设置地图点击处理
+const setupMapClickHandler = () => {
+    if (listenKey) {
+        unByKey(listenKey);
+    }
+    
+    listenKey = kmap.on("click", (e) => {
+        if (!treeClusterLayer) return;
+        
+        treeClusterLayer.layer.getFeatures(e.pixel).then((clickedFeatures) => {
+            if (!clickedFeatures.length) return;
+            
+            const features = clickedFeatures[0].get("features");
+            if (features.length > 1) {
+                handleClusterClick(features);
+            } else {
+                handleSingleFeatureClick(features[0]);
+            }
+        });
     });
-}
+};
+
+// 处理集群点击
+const handleClusterClick = (features) => {
+    const extent = boundingExtent(features.map(r => r.getGeometry().getCoordinates()));
+    kmap.getView().fit(extent, { duration: 1000, padding: [250, 250, 250, 250] });
+
+    const currentZoom = kmap.getView().getZoom();
+    if (currentZoom > 17) {
+        kmap.getView().setZoom(17);
+    }
+};
+
+// 处理单个要素点击
+const handleSingleFeatureClick = (feature) => {
+    if (isDrawing.value) {
+        feature.set("highlight", true);
+        refreshClusterStyle();
+    } else {
+        resetCurrentTree();
+        feature.set("currentTree", true);
+        currentTree.value = feature;
+        
+        eventBus.emit("click:point", { 
+            farmId: Number(sessionStorage.getItem("currentFarmId")), 
+            sampleId: feature.get("sampleId"), 
+            data: feature.getProperties() 
+        });
+    }
+};
 
-// 清除聚合图层,解除绑定
-function clearCluster() {
+// 清除集群图层
+const clearCluster = () => {
     if (treeClusterLayer) {
         treeClusterLayer.layer.getSource().getSource().clear();
-        treeClusterLayer.layer.getSource().clear();
+        kmap.removeLayer(treeClusterLayer.layer);
         treeClusterLayer = null;
         unByKey(listenKey);
     }
-}
+};
 
-function zoomToFeatures(features) {
-    const extent = boundingExtent(features.map((r) => r.getGeometry().getCoordinates()));
+// 缩放到要素范围
+const zoomToFeatures = (features) => {
+    const extent = boundingExtent(features.map(r => r.getGeometry().getCoordinates()));
     kmap.getView().fit(extent, { duration: 500, padding: [80, 250, 100, 250] });
+};
 
-    // const center = getCenter(extent);
-    // kmap.setCenter2(center);
-}
-
-function resetCurrentTree() {
-    currentTree.value && currentTree.value.set("currentTree", false)
-}
+// 重置当前树
+const resetCurrentTree = () => {
+    if (currentTree.value) {
+        currentTree.value.set("currentTree", false);
+        refreshClusterStyle();
+    }
+};
 
-defineExpose({ addCluster, enableBoxSelect, stopBoxSelect, initAreaMap, resetCurrentTree, saveSelect });
+// 初始化区域地图
+const initAreaMap = (arr) => {
+    regionLayer?.initData(arr);
+};
 
-// 分区
-let regionLayer = null;
-function initAreaMap(arr) {
-    regionLayer.initData(arr);
-}
+defineExpose({ 
+    addCluster: updateClusterData, 
+    enableBoxSelect, 
+    stopBoxSelect, 
+    initAreaMap, 
+    resetCurrentTree, 
+    saveSelect,
+    refreshClusterStyle // 暴露刷新方法
+});
 </script>
 
 <style lang="less" scoped>
@@ -391,4 +418,4 @@ function initAreaMap(arr) {
     width: 100%;
     height: 100%;
 }
-</style>
+</style>

+ 5 - 0
src/views/home/index.vue

@@ -120,6 +120,10 @@ onMounted(() => {
     // 柏桥村
     sessionStorage.setItem("currentFarmId", 766)
     areaId({ areaId: 0, farmId: 766 });
+
+    // 修改单棵树信息后刷新地图数据
+    eventBus.off("refreshMapData", getPointList);
+    eventBus.on("refreshMapData", getPointList);
 });
 
 onUnmounted(() => {
@@ -131,6 +135,7 @@ onUnmounted(() => {
     eventBus.off("clickMapPoint", toggleLeftComponet);
 });
 
+
 const getPointList = () => {
     VE_API.manage_interface.fetchSampleList({ farmId: organId.value }).then(({ data }) => {
         if (mapRef.value) {