Quellcode durchsuchen

feat:修改病虫害地图逻辑

wangsisi vor 4 Tagen
Ursprung
Commit
86ae3b11ef

+ 5 - 1
src/components/pageComponents/PhenologyTrackTimelineItem.vue

@@ -58,7 +58,11 @@ const getFarmImages = async () => {
 
 onActivated(() => {
     getFarmImages();
-})
+});
+
+defineExpose({
+    refresh: getFarmImages,
+});
 </script>
 
 <style scoped lang="scss">

+ 24 - 3
src/store/modules/recordDetails/index.js

@@ -1,8 +1,22 @@
 const emptyMapConfirmPayload = () => ({
     zone_name: '',
-    coordinates: [],
+    coordinates: '',
+    gridMultipolygon: '',
+    selectedGridIds: [],
 });
 
+/** coordinates 可能是 WKT 数组,也可能是 merge 后的单条 MULTIPOLYGON 字符串 */
+export function normalizeMapCoordinates(coordinates) {
+    if (Array.isArray(coordinates)) {
+        return coordinates.filter((item) => item && String(item).trim().length > 10);
+    }
+    if (typeof coordinates === 'string') {
+        const trimmed = coordinates.trim();
+        return trimmed.length > 10 ? [trimmed] : [];
+    }
+    return [];
+}
+
 export default {
     namespaced: true,
     state() {
@@ -13,14 +27,21 @@ export default {
     getters: {
         hasMapConfirmPayload(state) {
             const { zone_name, coordinates } = state.mapConfirmPayload;
-            return Boolean(zone_name) || (Array.isArray(coordinates) && coordinates.length > 0);
+            return Boolean(zone_name) || normalizeMapCoordinates(coordinates).length > 0;
+        },
+        mapConfirmCoordinatesArray(state) {
+            return normalizeMapCoordinates(state.mapConfirmPayload.coordinates);
         },
     },
     mutations: {
         SET_MAP_CONFIRM_PAYLOAD(state, payload) {
             state.mapConfirmPayload = {
                 zone_name: payload?.zone_name || '',
-                coordinates: Array.isArray(payload?.coordinates) ? payload.coordinates : [],
+                coordinates: payload?.coordinates ?? state.mapConfirmPayload.coordinates ?? '',
+                gridMultipolygon: payload?.gridMultipolygon ?? state.mapConfirmPayload.gridMultipolygon ?? '',
+                selectedGridIds: Array.isArray(payload?.selectedGridIds)
+                    ? payload.selectedGridIds
+                    : (state.mapConfirmPayload.selectedGridIds || []),
             };
         },
         CLEAR_MAP_CONFIRM_PAYLOAD(state) {

+ 9 - 11
src/views/old_mini/recordDetails/index.vue

@@ -154,7 +154,7 @@
                         </div>
                     </div>
                     <div class="phenology-track-section">
-                        <PhenologyTrackTimelineItem :abnormalType="recordTypeObj[recordType]" />
+                        <PhenologyTrackTimelineItem ref="phenologyTrackTimelineRef" :abnormalType="recordTypeObj[recordType]" />
                     </div>
                 </div>
             </div>
@@ -413,10 +413,7 @@ function getFarmMapLocation() {
 
 const mapConfirmPayload = computed(() => store.state.recordDetails.mapConfirmPayload);
 
-const hasMapConfirmGeometry = computed(() => {
-    const coordinates = mapConfirmPayload.value?.coordinates;
-    return Array.isArray(coordinates) && coordinates.length > 0;
-});
+const hasMapConfirmGeometry = computed(() => store.getters['recordDetails/mapConfirmCoordinatesArray'].length > 0);
 
 const formData = ref({
     ratio: '',
@@ -444,13 +441,13 @@ function resizeUploadMap() {
 
 /** 弹窗动画结束后再初始化,避免容器尺寸为 0 导致白屏 */
 function syncUploadMapView() {
-    const { coordinates } = mapConfirmPayload.value;
+    const geometryArr = store.getters['recordDetails/mapConfirmCoordinatesArray'];
     nextTick(() => {
         setTimeout(() => {
             if (!ensureUploadMap()) return;
             resizeUploadMap();
-            if (Array.isArray(coordinates) && coordinates.length) {
-                indexMap.setAreaGeometry(coordinates);
+            if (geometryArr.length) {
+                indexMap.setAreaGeometry(geometryArr);
                 resizeUploadMap();
             }
         }, 250);
@@ -488,6 +485,7 @@ watch(showUploadProgressPopup, (show) => {
 
 const initImgArr = ref([]);
 const uploadProgressPopupRef = ref(null);
+const phenologyTrackTimelineRef = ref(null);
 const popupImageUploadLoading = ref(false);
 const uploadFileObj = new UploadFile();
 const miniUserId = localStorage.getItem("MINI_USER_ID");
@@ -587,8 +585,7 @@ const handleConfirmUpload = (imgArr) => {
     if (!validateAbnormalRatio()) return;
 
     if (recordType.value === 'pest') {
-        const coordinates = mapConfirmPayload.value?.coordinates;
-        const hasDrawnMap = Array.isArray(coordinates) && coordinates.length > 0;
+        const hasDrawnMap = store.getters['recordDetails/mapConfirmCoordinatesArray'].length > 0;
         if (!hasDrawnMap) {
             handleMapClick();
             return;
@@ -603,7 +600,7 @@ const handleConfirmUpload = (imgArr) => {
         type: recordTypeObj[recordType.value],
         time: new Date().toISOString().slice(0, 10),
         zone_name: mapConfirmPayload.value.zone_name,
-        coordinates: mapConfirmPayload.value.coordinates[0],
+        coordinates: mapConfirmPayload.value.coordinates,
         "recog_type": "20",
         image_urls: imgArr,
         ...formData.value,
@@ -612,6 +609,7 @@ const handleConfirmUpload = (imgArr) => {
         if (res.code === 200) {
             showUploadProgressPopup.value = false;
             store.commit('recordDetails/CLEAR_MAP_CONFIRM_PAYLOAD');
+            phenologyTrackTimelineRef.value?.refresh?.();
             ElMessage.success(t('recordDetails.confirmSuccess'));
         } else {
             ElMessage.error(res.msg);

+ 36 - 6
src/views/old_mini/recordDetails/map/index.js

@@ -3,9 +3,12 @@ import * as util from "@/common/ol_common.js";
 import config from "@/api/config.js";
 import Style from "ol/style/Style";
 import Icon from "ol/style/Icon";
+import Fill from "ol/style/Fill";
+import Stroke from "ol/style/Stroke";
 import { Point } from 'ol/geom';
 import Feature from "ol/Feature";
 import { reactive } from "vue";
+import WKT from "ol/format/WKT.js";
 
 export let mapLocation = reactive({
   data: null,
@@ -32,6 +35,19 @@ class IndexMap {
         });
       },
     });
+    this.editable = true;
+  }
+
+  createReadonlyPolygonStyle() {
+    return new Style({
+      fill: new Fill({
+        color: "rgba(100, 0, 0, 0.5)",
+      }),
+      stroke: new Stroke({
+        color: "#E03131",
+        width: 2,
+      }),
+    });
   }
 
   /**
@@ -43,14 +59,15 @@ class IndexMap {
    */
   initMap(location, target, options = {}) {
     const { editable = true, movable = true } = options;
+    this.editable = editable;
     let level = 16;
     let coordinate = util.wktCastGeom(location).getFirstCoordinate();
     this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 8, 22);
     let xyz2 = config.base_img_url3 + "map/lby/{z}/{x}/{y}.png";
     this.kmap.addXYZLayer(xyz2, { minZoom: 8, maxZoom: 22 }, 2);
-    this.kmap.addLayer(this.clickPointLayer.layer);
-    this.setMapPoint(coordinate);
     if (editable) {
+      this.kmap.addLayer(this.clickPointLayer.layer);
+      this.setMapPoint(coordinate);
       this.addMapSingerClick();
     }
     if (!movable && this.kmap?.setStates) {
@@ -98,10 +115,23 @@ class IndexMap {
 
   setAreaGeometry(geometryArr) {
     this.clearLayer()
-    let that = this
-    geometryArr.map(item => {
-      that.kmap.setLayerWkt(item)
-    })
+    if (!this.editable) {
+      const format = new WKT();
+      const mapProjection = this.kmap.map.getView().getProjection();
+      geometryArr.forEach((item) => {
+        const geometry = format.readGeometry(item, {
+          dataProjection: "EPSG:4326",
+          featureProjection: mapProjection,
+        });
+        const feature = new Feature({ geometry });
+        feature.setStyle(this.createReadonlyPolygonStyle());
+        this.kmap.polygonLayer.source.addFeature(feature);
+      });
+    } else {
+      geometryArr.map(item => {
+        this.kmap.setLayerWkt(item)
+      });
+    }
     this.fitView()
   }
 

+ 333 - 31
src/views/old_mini/recordDetails/map/mapManage.js

@@ -5,7 +5,7 @@ import Style from "ol/style/Style";
 import Icon from "ol/style/Icon";
 import Fill from "ol/style/Fill";
 import Stroke from "ol/style/Stroke";
-import { Point, Polygon } from "ol/geom";
+import { Point, Polygon, MultiPolygon } from "ol/geom";
 import Feature from "ol/Feature";
 import DragPan from "ol/interaction/DragPan";
 import MouseWheelZoom from "ol/interaction/MouseWheelZoom";
@@ -15,6 +15,8 @@ import DoubleClickZoom from "ol/interaction/DoubleClickZoom";
 import KeyboardPan from "ol/interaction/KeyboardPan";
 import KeyboardZoom from "ol/interaction/KeyboardZoom";
 import DragRotateAndZoom from "ol/interaction/DragRotateAndZoom";
+import Select from "ol/interaction/Select";
+import { singleClick } from "ol/events/condition";
 import { reactive } from "vue";
 import WKT from "ol/format/WKT.js";
 import * as proj from "ol/proj";
@@ -62,35 +64,55 @@ class MapManage {
         });
       },
     });
-    this.gridLayer = new KMap.VectorLayer("terrainGridLayer", 900, {
-      style: () => {
+    this.gridLayer = new KMap.VectorLayer("terrainGridLayer", 1100, {
+      style: (feature) => {
+        const selected = !!feature.get("selected");
         return new Style({
-          // fill: new Fill({
-          //   color: "rgba(0, 0, 0, 0.25)",
-          // }),
+          fill: new Fill({
+            color: selected ? "rgba(100, 0, 0, 0.5)" : "rgba(255, 255, 255, 0.01)",
+          }),
           stroke: new Stroke({
-            color: "rgba(0, 0, 0, 0.57)",
-            width: 1.2,
+            color: selected ? "#E03131" : "#fff",
+            width: selected ? 1.8 : 1.2,
           }),
         });
       },
     });
+    this.gridToggleSelect = null;
+    this.selectedGridIds = new Set();
+    this.terrainGridItems = [];
     this.wktFormat = new WKT();
+    this.editable = true;
   }
 
-  initMap(location, target) {
+  createReadonlyPolygonStyle() {
+    return new Style({
+      fill: new Fill({
+        color: "rgba(124, 124, 124, 0.5)",
+      }),
+      stroke: new Stroke({
+        color: "rgba(255, 255, 255, 0.55)",
+        width: 2,
+      }),
+    });
+  }
+
+  initMap(location, target, options = {}) {
+    const { editable = true } = options;
+    this.editable = editable;
     let level = 16;
     let coordinate = util.wktCastGeom(location).getFirstCoordinate();
     this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 8, 22);
     let xyz2 = config.base_img_url3 + "map/lby/{z}/{x}/{y}.png";
     this.kmap.addXYZLayer(xyz2, { minZoom: 8, maxZoom: 22 }, 2);
-    this.kmap.addLayer(this.clickPointLayer.layer);
+    // this.kmap.addLayer(this.clickPointLayer.layer);
     this.kmap.addLayer(this.gridLayer.layer);
 
-    this.kmap.initDraw(() => {});
-    this.kmap.modifyDraw();
-
-    this.setRegionDrawingActive(false);
+    if (this.editable) {
+      this.kmap.initDraw(() => {});
+      this.kmap.modifyDraw();
+      this.setRegionDrawingActive(false);
+    }
   }
 
   /**
@@ -110,7 +132,7 @@ class MapManage {
       const c = this.kmap.getView().getCenter();
       this.setMapPoint(c);
     } else {
-      this.clickPointLayer.source.clear();
+      // this.clickPointLayer.source.clear();
     }
   }
 
@@ -118,10 +140,64 @@ class MapManage {
     this.setRegionDrawingActive(true);
   }
 
+  enableMapInteraction() {
+    if (!this.kmap) return;
+    setViewportInteractionsActive(this.kmap.map, true);
+  }
+
+  /**
+   * 根据中心点和亩数生成正方形 WKT
+   * @param {number[]} center [lng, lat]
+   * @param {number} mu 面积(亩)
+   */
+  generateSquareWktByMu(center, mu = 60) {
+    const lng = parseFloat(center[0]);
+    const lat = parseFloat(center[1]);
+    const halfSide = Math.sqrt(mu * 666.67) / 2;
+    const latDelta = halfSide / 111000;
+    const lngDelta = halfSide / (111000 * Math.cos((lat * Math.PI) / 180));
+    const ring = [
+      [lng - lngDelta, lat + latDelta],
+      [lng + lngDelta, lat + latDelta],
+      [lng + lngDelta, lat - latDelta],
+      [lng - lngDelta, lat - latDelta],
+      [lng - lngDelta, lat + latDelta],
+    ];
+    const coordinates = ring.map((point) => `${point[0]} ${point[1]}`).join(", ");
+    return `MULTIPOLYGON (((${coordinates})))`;
+  }
+
+  /**
+   * 以当前地图中心生成指定亩数的正方形区域
+   * @param {number} mu 面积(亩)
+   * @returns {string|null} WKT
+   */
+  setDefaultSquareAtCenter(mu = 60) {
+    if (!this.kmap) return null;
+    const center = this.kmap.getView().getCenter();
+    const wkt = this.generateSquareWktByMu(center, mu);
+    this.setAreaGeometry([wkt]);
+    this.setMapPoint(center);
+    return wkt;
+  }
+
+  setCenterAndSquare(center, mu = 60) {
+    if (!this.kmap) return null;
+    this.kmap.getView().animate({
+      center,
+      zoom: 16,
+      duration: 0,
+    });
+    this.setMapPoint(center);
+    const wkt = this.generateSquareWktByMu(center, mu);
+    this.setAreaGeometry([wkt]);
+    return wkt;
+  }
+
   setMapPoint(coordinate) {
-    this.clickPointLayer.source.clear();
+    // this.clickPointLayer.source.clear();
     let point = new Feature(new Point(coordinate));
-    this.clickPointLayer.addFeature(point);
+    // this.clickPointLayer.addFeature(point);
   }
 
   setMapPosition(center) {
@@ -145,25 +221,175 @@ class MapManage {
   }
 
   clearGridLayer() {
+    this.unbindGridClick();
     this.gridLayer?.source?.clear();
+    this.selectedGridIds?.clear();
+    this.terrainGridItems = [];
+  }
+
+  unbindGridClick() {
+    if (this.gridToggleSelect && this.kmap?.map) {
+      this.kmap.map.removeInteraction(this.gridToggleSelect);
+    }
+    this.gridToggleSelect = null;
+  }
+
+  bindGridClick() {
+    if (!this.kmap?.map || !this.gridLayer?.layer) return;
+    this.unbindGridClick();
+
+    const selectedStyle = new Style({
+      fill: new Fill({
+        color: "rgba(100, 0, 0, 0.5)",
+      }),
+      stroke: new Stroke({
+        color: "#E03131",
+        width: 1.8,
+      }),
+    });
+
+    this.gridToggleSelect = new Select({
+      condition: singleClick,
+      toggleCondition: singleClick,
+      layers: [this.gridLayer.layer],
+      multi: true,
+      hitTolerance: 8,
+      style: selectedStyle,
+    });
+
+    this.gridToggleSelect.on("select", (e) => {
+      e.selected.forEach((feature) => {
+        feature.set("selected", true);
+        this.selectedGridIds.add(feature.get("gridId"));
+        feature.changed();
+      });
+      e.deselected.forEach((feature) => {
+        feature.set("selected", false);
+        this.selectedGridIds.delete(feature.get("gridId"));
+        feature.changed();
+      });
+      this.gridLayer.layer.changed();
+    });
+
+    this.kmap.map.addInteraction(this.gridToggleSelect);
+  }
+
+  getSelectedGrids() {
+    const empty = {
+      gridIds: [],
+      geometryArr: [],
+      parcels: [],
+      mianji: "0.00",
+      mergedGeometry: "",
+    };
+    if (!this.kmap || !this.gridLayer?.source) return empty;
+
+    const projection = this.kmap.map.getView().getProjection();
+    const gridIds = [];
+    const geometryArr = [];
+    const parcels = [];
+    let totalMu = 0;
+
+    this.gridLayer.source.getFeatures().forEach((feature) => {
+      if (!feature.get("selected")) return;
+      const geometry = feature.getGeometry();
+      if (!geometry) return;
+
+      const gridId = feature.get("gridId");
+      gridIds.push(gridId);
+      const wkt = this.wktFormat.writeGeometry(geometry, {
+        dataProjection: "EPSG:4326",
+        featureProjection: projection,
+      });
+      geometryArr.push(wkt);
+
+      let geom = geometry.clone();
+      geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"));
+      let mu = getArea(geom);
+      mu = (mu + mu / 2) / 1000;
+      totalMu += mu;
+      parcels.push({ gridId, wkt, mianji: Number(mu.toFixed(2)) });
+    });
+
+    return {
+      gridIds,
+      geometryArr,
+      parcels,
+      mianji: totalMu.toFixed(2),
+      mergedGeometry: this.mergeGeometryWkts(geometryArr),
+    };
+  }
+
+  polygonCoordSetsFromGeometry(geometry) {
+    if (!geometry || typeof geometry.getType !== "function") return [];
+    const type = geometry.getType();
+    if (type === "Polygon") return [geometry.getCoordinates()];
+    if (type === "MultiPolygon") return geometry.getCoordinates();
+    return [];
+  }
+
+  /**
+   * 将多个 WKT 面合并为一个(单块返回 POLYGON,多块返回 MULTIPOLYGON)
+   * @param {string[]} wktArr
+   * @returns {string}
+   */
+  mergeGeometryWkts(wktArr) {
+    if (!Array.isArray(wktArr) || wktArr.length === 0) return "";
+    const trimmed = wktArr
+      .map((item) => String(item).trim())
+      .filter((item) => item.length > 10);
+    if (trimmed.length === 0) return "";
+    if (trimmed.length === 1) return trimmed[0];
+
+    const wktOpts = {
+      dataProjection: "EPSG:4326",
+      featureProjection: "EPSG:4326",
+    };
+    const coordSets = [];
+    trimmed.forEach((wkt) => {
+      try {
+        const geometry = this.wktFormat.readGeometry(wkt, wktOpts);
+        coordSets.push(...this.polygonCoordSetsFromGeometry(geometry));
+      } catch {
+        /* 单条解析失败则跳过 */
+      }
+    });
+
+    if (coordSets.length === 0) return trimmed[0];
+    if (coordSets.length === 1) {
+      return this.wktFormat.writeGeometry(new Polygon(coordSets[0]), wktOpts);
+    }
+    return this.wktFormat.writeGeometry(new MultiPolygon(coordSets), wktOpts);
+  }
+
+  getSelectedGridIds() {
+    return this.getSelectedGrids().gridIds;
   }
 
   /**
-   * MULTIPOINT WKT 顶点转 Polygon(接口网格为多点围成的面)
+   * 接口网格 geometry 转 Polygon(支持 MULTIPOINT / POLYGON
    */
-  multipointWktToPolygon(multipointWkt) {
-    if (!this.kmap || !multipointWkt) return null;
+  gridGeometryToPolygon(geometryWkt) {
+    if (!this.kmap || !geometryWkt) return null;
     const projection = this.kmap.map.getView().getProjection();
     let geom;
     try {
-      geom = this.wktFormat.readGeometry(String(multipointWkt).trim(), {
+      geom = this.wktFormat.readGeometry(String(geometryWkt).trim(), {
         dataProjection: "EPSG:4326",
         featureProjection: projection,
       });
     } catch {
       return null;
     }
-    if (geom.getType() !== "MultiPoint") return null;
+
+    const type = geom.getType();
+    if (type === "Polygon") return geom;
+    if (type === "MultiPolygon") {
+      const polygons = geom.getPolygons();
+      return polygons.length ? polygons[0] : null;
+    }
+    if (type !== "MultiPoint") return null;
+
     const coords = geom.getCoordinates();
     if (!coords || coords.length < 3) return null;
     const ring = coords.map((c) => [...c]);
@@ -184,30 +410,95 @@ class MapManage {
     this.clearGridLayer();
     if (!Array.isArray(gridItems) || !gridItems.length) return;
 
+    this.terrainGridItems = gridItems.map((item) => ({
+      id: item.id,
+      geometry: item.geometry,
+      area_m2: item.area_m2,
+    }));
+
     gridItems.forEach((item) => {
-      const polygon = this.multipointWktToPolygon(item?.geometry);
+      const polygon = this.gridGeometryToPolygon(item?.geometry);
       if (!polygon) return;
       const feature = new Feature({ geometry: polygon });
       feature.set("gridId", item.id);
       feature.set("area_m2", item.area_m2);
+      feature.set("selected", false);
       this.gridLayer.addFeature(feature);
     });
+    this.bindGridClick();
   }
 
   fitGridView() {
     if (!this.kmap || !this.gridLayer?.source) return;
     const extent = this.gridLayer.source.getExtent();
     if (!extent || extent.some((v) => !Number.isFinite(v))) return;
-    this.kmap.getView().fit(extent, { duration: 500, padding: [80, 80, 80, 80] });
+    this.kmap.getView().fit(extent, { duration: 500, padding: [40, 40, 40, 40] });
+  }
+
+  getDisplayAreaWkt() {
+    if (!this.kmap?.polygonLayer?.source) return "";
+    const projection = this.kmap.map.getView().getProjection();
+    const geometryArr = [];
+    this.kmap.polygonLayer.source.getFeatures().forEach((feature) => {
+      const geometry = feature.getGeometry();
+      if (!geometry) return;
+      geometryArr.push(
+        this.wktFormat.writeGeometry(geometry, {
+          dataProjection: "EPSG:4326",
+          featureProjection: projection,
+        })
+      );
+    });
+    return this.mergeGeometryWkts(geometryArr);
+  }
+
+  getTerrainGridItems() {
+    if (this.terrainGridItems.length) {
+      return this.terrainGridItems.map((item) => ({ ...item }));
+    }
+    if (!this.kmap || !this.gridLayer?.source) return [];
+
+    const projection = this.kmap.map.getView().getProjection();
+    return this.gridLayer.source.getFeatures().map((feature) => {
+      const geometry = feature.getGeometry();
+      return {
+        id: feature.get("gridId"),
+        geometry: geometry
+          ? this.wktFormat.writeGeometry(geometry, {
+              dataProjection: "EPSG:4326",
+              featureProjection: projection,
+            })
+          : "",
+        area_m2: feature.get("area_m2"),
+      };
+    });
+  }
+
+  restoreSelectedGrids(gridIds) {
+    if (!this.gridLayer?.source || !Array.isArray(gridIds)) return;
+    const idSet = new Set(gridIds.map((id) => String(id)));
+    this.selectedGridIds.clear();
+    this.gridLayer.source.getFeatures().forEach((feature) => {
+      const gridId = feature.get("gridId");
+      const selected = idSet.has(String(gridId));
+      feature.set("selected", selected);
+      if (selected) {
+        this.selectedGridIds.add(gridId);
+      }
+      feature.changed();
+    });
+    this.gridLayer.layer.changed();
   }
 
   destroyMap() {
+    this.unbindGridClick();
     this.clearLayer();
     if (this.kmap && typeof this.kmap.destroy === "function") {
       this.kmap.destroy();
     }
     this.kmap = null;
     this.regionDrawingActive = false;
+    this.selectedGridIds?.clear();
   }
 
   /**
@@ -243,18 +534,29 @@ class MapManage {
   }
 
   setAreaGeometry(geometryArr) {
-    this.clearLayer()
-    let that = this
-    geometryArr.map(item => {
-      that.kmap.setLayerWkt(item)
-    })
-    this.fitView()
+    this.clearLayer();
+    if (!this.kmap) return;
+
+    const format = new WKT();
+    const mapProjection = this.kmap.map.getView().getProjection();
+    geometryArr.forEach((item) => {
+      const geometry = format.readGeometry(item, {
+        dataProjection: "EPSG:4326",
+        featureProjection: mapProjection,
+      });
+      const feature = new Feature({ geometry });
+      if (!this.editable) {
+        feature.setStyle(this.createReadonlyPolygonStyle());
+      }
+      this.kmap.polygonLayer.source.addFeature(feature);
+    });
+    this.fitView();
   }
 
   fitView(){
     let extent = this.kmap.polygonLayer.source.getExtent()
     // 地图自适应到区域可视范围
-    this.kmap.getView().fit(extent, { duration: 500, padding: [100, 100, 100, 100] });
+    this.kmap.getView().fit(extent, { duration: 500, padding: [40, 40, 40, 40] });
   }
 }
 

+ 118 - 47
src/views/old_mini/recordDetails/mapManage.vue

@@ -2,11 +2,12 @@
     <div class="map-manage">
         <custom-header :name="$t('勾画区域')" />
         <div class="map-manage-content">
-            <locationSearch class="location-search" @change="handleLocationChange"></locationSearch>
+            <!-- <locationSearch class="location-search" @change="handleLocationChange"></locationSearch> -->
             <div class="map-container" ref="mapContainer"></div>
             <div class="map-tip" v-if="recordType === 'pest'">{{ $t('建立您的专属病虫害地图档案,生成分区靶向治疗方案') }}</div>
-            <div class="new-region-btn" v-show="!drawingEnabled" @click="onStartRegionDrawing">{{ $t('新建管理分区') }}</div>
-            <div class="map-icon" v-show="drawingEnabled" @click="handleMapIconClick">
+            <div class="new-region-btn" v-show="!drawingEnabled && recordType !== 'pest'" @click="onStartRegionDrawing">
+                {{ $t('新建管理分区') }}</div>
+            <div class="map-icon" v-show="drawingEnabled && recordType !== 'pest'" @click="handleMapIconClick">
                 <img src="@/assets/img/map/map-icon.png" alt="">
             </div>
             <div class="map-legend" v-if="recordType === 'pest'">
@@ -25,12 +26,13 @@
                 <div class="bottom-btn secondary-btn" @click="handleClearDraw">{{ $t('取消勾选') }}</div>
                 <div class="bottom-btn primary-btn" @click="handleConfirmDraw">{{ $t('确认区域') }}</div>
             </template>
+            <div v-else-if="recordType === 'pest'" class="bottom-btn primary-btn" @click="handleConfirmDraw">{{
+                $t('确认区域') }}</div>
             <div v-else class="bottom-btn secondary-btn">{{ $t('邀请勾画') }}</div>
         </div>
 
         <UploadProgressPopup ref="uploadProgressPopupRef" v-model:show="showUploadProgressPopup"
-            :confirm-params="{ regionName: formData.regionName }"
-            :confirm-text="$t('点击上传')"
+            :confirm-params="{ regionName: formData.regionName }" :confirm-text="$t('点击上传')"
             @cancel="handleCancelUploadPopup" @confirm="handleConfirmUpload">
             <template #header>
                 <div class="upload-form">
@@ -69,6 +71,7 @@ import { ref, onActivated, onDeactivated, computed, nextTick } from "vue";
 import { ElMessageBox, ElMessage } from "element-plus";
 import { useRouter, useRoute } from "vue-router";
 import { useStore } from "vuex";
+import { normalizeMapCoordinates } from "@/store/modules/recordDetails/index.js";
 
 const router = useRouter();
 const route = useRoute();
@@ -153,14 +156,52 @@ const handleConfirmUpload = () => {
 
 const confirmPayload = ref({
     zone_name: '',
-    coordinates: [],
+    coordinates: '',
+    gridMultipolygon: '',
+    selectedGridIds: [],
 });
 
+function buildMapConfirmPayload(overrides = {}) {
+    const selectedGrids = mapManage.getSelectedGrids();
+    const hasGridLayer = Boolean(mapManage.kmap && mapManage.gridLayer?.source);
+    return {
+        zone_name: confirmPayload.value.zone_name,
+        coordinates: selectedGrids.gridIds.length
+            ? mapManage.mergeGeometryWkts(selectedGrids.geometryArr)
+            : confirmPayload.value.coordinates,
+        gridMultipolygon: mapManage.getDisplayAreaWkt() || confirmPayload.value.gridMultipolygon,
+        selectedGridIds: hasGridLayer ? selectedGrids.gridIds : confirmPayload.value.selectedGridIds,
+        ...overrides,
+    };
+}
+
+function loadPestTerrainGrids(gridMultipolygon, selectedGridIds = []) {
+    const wkt = String(gridMultipolygon || '').trim();
+    if (!wkt) return;
+
+    const baseWktArr = normalizeMapCoordinates(wkt);
+    if (baseWktArr.length) {
+        mapManage.setAreaGeometry(baseWktArr);
+    }
+
+    VE_API.record.generateGrid({ multipolygon: wkt }).then((res) => {
+        if (res.code === 200 && Array.isArray(res.data) && res.data.length) {
+            mapManage.setTerrainGrids(res.data);
+            if (selectedGridIds.length) {
+                mapManage.restoreSelectedGrids(selectedGridIds);
+            }
+            mapManage.fitGridView();
+        } else if (res.code === 200) {
+            ElMessage.warning("未生成网格数据");
+        }
+    });
+}
+
 const handleRegionNameConfirm = (regionName) => {
-    store.commit('recordDetails/SET_MAP_CONFIRM_PAYLOAD', {
+    confirmPayload.value.zone_name = regionName;
+    store.commit('recordDetails/SET_MAP_CONFIRM_PAYLOAD', buildMapConfirmPayload({
         zone_name: regionName,
-        coordinates: confirmPayload.value.coordinates,
-    });
+    }));
     router.back();
 };
 
@@ -170,7 +211,16 @@ const onStartRegionDrawing = () => {
 };
 
 const handleLocationChange = (location) => {
-    // console.log(location);
+    if (recordType.value === 'pest') {
+        const wkt = mapManage.setCenterAndSquare(location.coordinateArray, 60);
+        if (wkt) {
+            confirmPayload.value.coordinates = [wkt];
+            confirmPayload.value.gridMultipolygon = wkt;
+            confirmPayload.value.selectedGridIds = [];
+            loadPestTerrainGrids(wkt);
+        }
+        return;
+    }
     mapManage.setMapPosition(location.coordinateArray);
 };
 
@@ -192,27 +242,25 @@ const handleClearDraw = () => {
 
 const handleConfirmDraw = () => {
     if (recordType.value === 'pest') {
-        const payload = mapManage.getAreaGeometry();
-        if (!payload.geometryArr.length) {
-            ElMessage.warning("请先勾画地块");
+        const selectedGrids = mapManage.getSelectedGrids();
+        if (!selectedGrids.gridIds.length) {
+            ElMessage.warning("至少要选中一个区域");
             return;
         }
-        // VE_API.record.generateGrid({
-        //     multipolygon: payload.geometryArr[0],
-        // }).then(res => {
-        //     if (res.code === 200 && Array.isArray(res.data) && res.data.length) {
-        //         mapManage.setTerrainGrids(res.data);
-        //         mapManage.fitGridView();
-        //     } else if (res.code === 200) {
-        //         ElMessage.warning("未生成网格数据");
-        //     }
-        // });
-        // console.log("地块数据", payload);
-        // console.log("WKT 列表", payload.geometryArr);
-        // console.log("合计亩数", payload.mianji);
-        // console.log("分块明细", payload.parcels);
-        confirmPayload.value.coordinates = payload.geometryArr;
+        const mergedGeometry = mapManage.mergeGeometryWkts(selectedGrids.geometryArr);
+        confirmPayload.value.coordinates = mergedGeometry;
         showRegionNamePopup.value = true;
+        // const payload = mapManage.getAreaGeometry();
+        // if (!payload.geometryArr.length) {
+        //     ElMessage.warning("请先勾画地块");
+        //     return;
+        // }
+        // // console.log("地块数据", payload);
+        // // console.log("WKT 列表", payload.geometryArr);
+        // // console.log("合计亩数", payload.mianji);
+        // // console.log("分块明细", payload.parcels);
+        // confirmPayload.value.coordinates = payload.geometryArr;
+        // showRegionNamePopup.value = true;
     } else {
         uploadProgressPopupRef.value?.uploadReset?.();
         showUploadProgressPopup.value = true;
@@ -222,28 +270,55 @@ const handleConfirmDraw = () => {
 function initMapManageView() {
     if (!mapContainer.value) return;
     mapManage.destroyMap();
-    mapManage.initMap(getFarmMapLocation(), mapContainer.value);
+    mapManage.initMap(getFarmMapLocation(), mapContainer.value, {
+        editable: recordType.value !== 'pest',
+    });
 
     const stored = store.state.recordDetails.mapConfirmPayload;
     if (store.getters['recordDetails/hasMapConfirmPayload']) {
-        const coordinates = Array.isArray(stored.coordinates) ? [...stored.coordinates] : [];
         confirmPayload.value = {
             zone_name: stored.zone_name || '',
-            coordinates,
+            coordinates: stored.coordinates,
+            gridMultipolygon: stored.gridMultipolygon || '',
+            selectedGridIds: Array.isArray(stored.selectedGridIds) ? [...stored.selectedGridIds] : [],
         };
-        if (coordinates.length) {
-            mapManage.setAreaGeometry(coordinates);
+        if (recordType.value !== 'pest') {
+            const geometryArr = store.getters['recordDetails/mapConfirmCoordinatesArray'];
+            if (geometryArr.length) {
+                mapManage.setAreaGeometry(geometryArr);
+            }
         }
     } else {
         mapManage.clearLayer();
         confirmPayload.value = {
             zone_name: '',
-            coordinates: [],
+            coordinates: '',
+            gridMultipolygon: '',
+            selectedGridIds: [],
         };
     }
 
     if (recordType.value === 'pest') {
-        onStartRegionDrawing();
+        mapManage.enableMapInteraction();
+        if (confirmPayload.value.gridMultipolygon) {
+            loadPestTerrainGrids(
+                confirmPayload.value.gridMultipolygon,
+                confirmPayload.value.selectedGridIds,
+            );
+        } else if (!confirmPayload.value.coordinates) {
+            const wkt = mapManage.setDefaultSquareAtCenter(60);
+            if (wkt) {
+                confirmPayload.value.coordinates = [wkt];
+                confirmPayload.value.gridMultipolygon = wkt;
+                loadPestTerrainGrids(wkt);
+            }
+        } else {
+            const coords = normalizeMapCoordinates(confirmPayload.value.coordinates);
+            if (coords.length === 1) {
+                confirmPayload.value.gridMultipolygon = coords[0];
+                loadPestTerrainGrids(coords[0], confirmPayload.value.selectedGridIds);
+            }
+        }
     }
 }
 
@@ -253,16 +328,12 @@ onActivated(() => {
 
 onDeactivated(() => {
     if (recordType.value === 'pest' && mapManage.kmap) {
-        const payload = mapManage.getAreaGeometry();
-        if (payload.geometryArr?.length) {
-            const zoneName = confirmPayload.value.zone_name
-                || store.state.recordDetails.mapConfirmPayload.zone_name;
-            store.commit('recordDetails/SET_MAP_CONFIRM_PAYLOAD', {
-                zone_name: zoneName,
-                coordinates: payload.geometryArr,
-            });
-            confirmPayload.value.coordinates = payload.geometryArr;
-        }
+        const payload = buildMapConfirmPayload();
+        confirmPayload.value = {
+            ...confirmPayload.value,
+            ...payload,
+        };
+        store.commit('recordDetails/SET_MAP_CONFIRM_PAYLOAD', payload);
     }
     mapManage.destroyMap();
     drawingEnabled.value = false;
@@ -284,7 +355,7 @@ onDeactivated(() => {
             top: 12px;
             left: 12px;
             z-index: 2;
-        }
+        }  
 
         .map-container {
             width: 100%;
@@ -293,7 +364,7 @@ onDeactivated(() => {
 
         .map-tip {
             position: absolute;
-            top: 80px;
+            top: 30px;
             left: 50%;
             transform: translate(-50%, -50%);
             z-index: 2;