Ver Fonte

feat:修改四川拓土平台页面逻辑

wangsisi há 2 semanas atrás
pai
commit
1796021072

BIN
src/assets/images/map/location.png


+ 3 - 10
src/router/mainRoutes.js

@@ -11,18 +11,11 @@ export default [
         path: "/warningHome",
         name: "warningHome",
         component: () => import(/* @vite-ignore */ "@/views/warningHome/index.vue"),
-        // component: () => import("@/views/authentic/index.vue"),
     },
     {
-        path: "/home",
-        name: "Home",
-        component: () => import(/* @vite-ignore */ "@/views/home/index.vue"),
-        // component: () => import("@/views/authentic/index.vue"),
-    },
-    {
-        path: "/garden-file",
-        name: "GardenFile",
-        component: () => import(/* @vite-ignore */ "@/views/file/index.vue"),
+        path: "/landRecognition",
+        name: "LandRecognition",
+        component: () => import(/* @vite-ignore */ "@/views/landRecognition/index.vue"),
     },
     //新增农事
     {

Diff do ficheiro suprimidas por serem muito extensas
+ 232 - 299
src/utils/ol-map/Map.js


+ 0 - 28
src/views/file/fileMap.js

@@ -1,28 +0,0 @@
-import config from "@/api/config.js";
-import * as KMap from "@/utils/ol-map/KMap";
-import * as util from "@/common/ol_common.js";
-import Point from "ol/geom/Point.js";
-import Feature from "ol/Feature";
-import VectorLayer from "ol/layer/Vector.js";
-import WKT from "ol/format/WKT.js";
-import ScaleLine from "ol/control/ScaleLine";
-import { useRouter } from "vue-router";
-
-/**
- * @description 地图层对象
- */
-class FileMap {
-  constructor() {
-    let that = this;
-    let vectorStyle = new KMap.VectorStyle();
-    this.vectorStyle = vectorStyle;
-  }
-
-  initMap(location, target) {
-    let level = 16;
-    let coordinate = util.wktCastGeom(location).getFirstCoordinate();
-    this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 6, 22);
-  }
-}
-
-export default FileMap;

+ 0 - 365
src/views/file/index.vue

@@ -1,365 +0,0 @@
-<template>
-  <div class="base-container no-events">
-    <fnHeader></fnHeader>
-    <div class="content">
-      <!-- <div class="top"></div> -->
-      <div class="left yes-events">
-        <div class="chart-list">
-          <div class="chart-item">
-            <chart-box name="光照效能" arrow="left">
-              <!-- <template #title-right>
-                <div>
-                  <el-select
-                    v-model="topographicValue"
-                    placeholder="请选择"
-                    style="width: 86px"
-                    size="small"
-                  >
-                    <el-option
-                      v-for="item in topographicOptions"
-                      :key="item.value"
-                      :label="item.label"
-                      :value="item.value"
-                    />
-                  </el-select>
-                </div>
-              </template> -->
-              <div class="topographic-content">
-                <bar-chart
-                  styleName="styleName1"
-                  :xData="evaluateXData"
-                  :yData="evaluateYData"
-                ></bar-chart>
-                <!-- <div class="text-list box-bg">
-                    <div class="text-item" v-for="item in 3" :key="item">
-                    <div class="circle"></div>
-                    <div class="txt">
-                        高温Ⅰ级<span>26</span>棵
-                    </div>
-                    </div>
-                </div> -->
-              </div>
-            </chart-box>
-          </div>
-          <div class="chart-item">
-            <chart-box name="气象预警" arrow="left">
-              <div class="topographic-content">
-                <bar-chart
-                  styleName="styleName1"
-                  :xData="evaluateXData"
-                  :yData="evaluateYData"
-                ></bar-chart>
-                <div class="text-list box-bg">
-                  <div class="text-item" v-for="item in 3" :key="item">
-                    <div class="circle"></div>
-                    <div class="txt">桂味<span>15626</span>棵(树龄3.5年)</div>
-                  </div>
-                </div>
-              </div>
-            </chart-box>
-          </div>
-          <div class="chart-item">
-            <chart-box name="气象预警" arrow="left"></chart-box>
-          </div>
-          <div class="chart-item">
-            <chart-box name="气象预警" arrow="left"></chart-box>
-          </div>
-        </div>
-      </div>
-      <div class="home-bottom">
-        <div class="file-box yes-events">
-          <chart-box name="果园档案">
-            <template #title-right>
-              <el-icon class="arrow-icon cursor-pointer" color="#141414"
-                ><DArrowLeft
-              /></el-icon>
-              <div class="edit-btn cursor-pointer" @click="goBack">返回</div>
-            </template>
-            <file-bar>
-              <div class="btn-list">
-                <div class="btn-item">新增</div>
-                <div class="btn-item">筛选</div>
-                <div class="btn-item">撤销</div>
-                <div class="btn-item">保存</div>
-              </div>
-            </file-bar>
-          </chart-box>
-        </div>
-      </div>
-      <div class="right yes-events">
-        <div class="chart-list">
-          <div class="chart-item">
-            <chart-box name="病害历史" arrow="arrow-left">
-              <template #title-right>
-                <el-select
-                  v-model="pestHistoryValue"
-                  placeholder="请选择"
-                  size="small"
-                  class="common-select"
-                  popper-class="common-select-popper"
-                >
-                  <el-option
-                    v-for="item in pestHistoryOptions"
-                    :key="item.value"
-                    :label="item.label"
-                    :value="item.value"
-                  />
-                </el-select>
-              </template>
-              <div class="pest-history">
-                <bar-chart
-                  styleName="styleName1"
-                  :xData="evaluateXData"
-                  :yData="evaluateYData"
-                ></bar-chart>
-              </div>
-            </chart-box>
-          </div>
-          <div class="chart-item">
-            <chart-box name="虫害历史" arrow="arrow-left">
-              <template #title-right>
-                <el-select
-                  v-model="pestHistoryValue"
-                  placeholder="请选择"
-                  size="small"
-                  class="common-select"
-                  popper-class="common-select-popper"
-                >
-                  <el-option
-                    v-for="item in pestHistoryOptions"
-                    :key="item.value"
-                    :label="item.label"
-                    :value="item.value"
-                  />
-                </el-select>
-              </template>
-              <div class="topographic-content">
-                <bar-chart
-                  styleName="styleName1"
-                  :xData="evaluateXData"
-                  :yData="evaluateYData"
-                ></bar-chart>
-                <div class="text-list box-bg">
-                  <div class="text-item" v-for="item in 3" :key="item">
-                    <div class="circle"></div>
-                    <div class="txt">桂味<span>15626</span>棵(树龄3.5年)</div>
-                  </div>
-                </div>
-              </div>
-            </chart-box>
-          </div>
-          <div class="chart-item">
-            <chart-box name="气象预警" arrow="arrow-left"></chart-box>
-          </div>
-          <div class="chart-item">
-            <chart-box name="气象预警" arrow="arrow-left"></chart-box>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-  <div ref="mapRef" class="bottom-map"></div>
-</template>
-
-<script setup>
-import { onMounted, ref } from "vue";
-import fnHeader from "@/components/fnHeader.vue";
-import chartBox from "@/components/chartBox.vue";
-import fileBar from "@/components/fileBar.vue";
-import barChart from "@/components/charts/barChart.vue";
-import FileMap from "./fileMap";
-import router from "@/router";
-
-let fileMap = new FileMap();
-const mapRef = ref();
-const topographicValue = ref("高温风险");
-const topographicOptions = ref([
-  { label: "高温风险", value: "高温风险" },
-  { label: "病害风险", value: "病害风险" },
-  { label: "虫害风险", value: "虫害风险" },
-  { label: "阴雨寡照", value: "阴雨寡照" },
-]);
-
-// 右1-病害历史
-const pestHistoryValue = ref("品种");
-const pestHistoryOptions = ref([
-  { label: "品种", value: "品种" },
-  { label: "品种1", value: "品种1" },
-  { label: "品种2", value: "品种2" },
-  { label: "品种3", value: "品种3" },
-]);
-
-// 地形风险
-const evaluateXData = ["区域1", "区域2", "区域3"];
-const evaluateYData = [33, 41, 43];
-
-onMounted(() => {
-  let location = "POINT (113.78049350268851 23.419886891845312)";
-  fileMap.initMap(location, mapRef.value);
-});
-
-// 返回
-const goBack = () => {
-  router.replace("/home");
-};
-</script>
-
-<style lang="scss" scoped>
-.base-container {
-  width: 100%;
-  height: 100vh;
-  color: #fff;
-  position: absolute;
-  box-sizing: border-box;
-  z-index: 1;
-
-  .content {
-    width: 100%;
-    height: calc(100% - 74px - 48px);
-    display: flex;
-    justify-content: space-between;
-    box-sizing: border-box;
-    .left,
-    .right {
-      width: 375px;
-      height: 100%;
-      padding-top: 10px;
-      box-sizing: border-box;
-      display: flex;
-      .chart-list {
-        width: 100%;
-        height: 100%;
-        display: flex;
-        flex-direction: column;
-        justify-content: space-between;
-        .chart-item {
-          width: 100%;
-          height: calc(100% / 4);
-          box-sizing: border-box;
-          margin-bottom: 10px;
-          &.chart-item:last-child {
-            margin: 0;
-          }
-        }
-      }
-      .topographic-content {
-        width: 100%;
-        height: calc(100% - 10px);
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        margin-bottom: 10px;
-        .text {
-          font-weight: 400;
-          padding: 8px 0 4px 5px;
-          text-indent: 2em;
-          margin-left: 8px;
-          span {
-            color: #69bdff;
-          }
-        }
-        .box-bg {
-          border-radius: 2px 2px 0 0;
-          font-size: 12px;
-          padding: 8px 6px;
-          box-sizing: border-box;
-          background: linear-gradient(
-            180deg,
-            rgb(85, 85, 85, 0.4) 0%,
-            rgb(35, 35, 35, 1) 100%
-          );
-        }
-      }
-      .text-list {
-        flex: none;
-        margin: 0 16px;
-        .text-item {
-          display: flex;
-          align-items: center;
-          margin-bottom: 5px;
-          .circle {
-            width: 4px;
-            height: 4px;
-            background: rgba(255, 255, 255, 0.44);
-            border-radius: 50%;
-            margin-right: 6px;
-          }
-          .txt {
-            font-size: 12px;
-            span {
-              margin: 0 2px 0 6px;
-              color: #e8ba52;
-            }
-          }
-        }
-      }
-    }
-    .left {
-      padding-left: 20px;
-    }
-    .right {
-      padding-right: 20px;
-      .list {
-        width: 100%;
-        height: 100%;
-      }
-      .pest-history {
-        width: 100%;
-        height: 100%;
-        padding: 6px 8px 20px 20px;
-        box-sizing: border-box;
-      }
-    }
-    .home-bottom {
-      display: flex;
-      align-items: flex-end;
-      width: calc(100% - 375px - 375px - 72px);
-      height: 100%;
-      justify-content: flex-end;
-      .file-box {
-        height: 25%;
-        min-height: 210px;
-        width: 422px;
-        position: relative;
-        box-shadow: 0 0 3px 3px #2199f8;
-        .arrow-icon {
-          top: -32px;
-          left: 50%;
-          position: absolute;
-          background: #fff;
-          width: 16px;
-          height: 80px;
-          line-height: 80px;
-          border-radius: 5px 0 0 5px;
-          text-align: center;
-          transform: translateX(-50%) rotate(270deg);
-        }
-        .edit-btn {
-          padding: 2px 24px;
-          background: #2199f8;
-          border-radius: 4px;
-        }
-        .btn-list {
-          position: absolute;
-          right: 4px;
-          .btn-item {
-            background: #4f4f4f;
-            border-radius: 4px;
-            padding: 2px 24px;
-            margin-bottom: 16px;
-            &:last-child {
-              margin-bottom: 0;
-            }
-          }
-        }
-      }
-    }
-  }
-}
-.bottom-map {
-  width: 100%;
-  height: 100vh;
-  position: absolute;
-  z-index: 0;
-}
-</style>

+ 375 - 0
src/views/landRecognition/index.vue

@@ -0,0 +1,375 @@
+<template>
+    <div class="base-container no-events">
+        <fnHeader showDate :autoGo="true" hideSwitch></fnHeader>
+        <div class="content">
+            <div class="warning-top yes-events">
+                <div class="btn-box" @click="handleClick">返回</div>
+                <el-date-picker
+                    v-model="selectedDate"
+                    type="date"
+                    size="large"
+                    placeholder="选择日期"
+                    format="YYYY-MM-DD"
+                    value-format="YYYY-MM-DD"
+                    class="date-picker-custom"
+                    @change="handleDateChange"
+                />
+            </div>
+            <div class="top-btn yes-events">
+                <div class="btn-item" v-show="!isEditing" @click="handleEdit">编辑地块</div>
+                <template v-if="isEditing">
+                    <div class="btn-item" :class="{ disabled: !canUndo }" @click="handleUndo">撤销</div>
+                    <div class="btn-item" :class="{ disabled: !hasSelectedPoint }" @click="handleDeletePoint">
+                        删除点
+                    </div>
+                </template>
+            </div>
+            <div class="bottom-btn yes-events">
+                <div class="btn-item" @click="handleCancel">取消</div>
+                <div class="btn-item primary-btn" @click="handleConfirm">{{ confirmButtonText }}</div>
+            </div>
+        </div>
+    </div>
+    <div ref="mapRef" class="map"></div>
+</template>
+
+<script setup>
+import { onMounted, ref, nextTick, computed } from "vue";
+import fnHeader from "@/components/fnHeader.vue";
+import IndexMap from "./indexMap.js";
+import { useStore } from "vuex";
+import { useRouter } from "vue-router";
+import { convertPointToArray } from "@/utils/index";
+import { ElLoading, ElDatePicker, ElMessage } from "element-plus";
+
+const mapRef = ref(null);
+const store = useStore();
+const router = useRouter();
+const indexMap = new IndexMap();
+
+onMounted(() => {
+    indexMap.initMap("POINT(104.65757459845925 30.33392788321249)", mapRef.value);
+    nextTick(() => {
+        initLandRecognition();
+    });
+});
+
+const isEditing = ref(false);
+const canUndoState = ref(false);
+const hasSelectedPointState = ref(false);
+const confirmButtonText = ref("确认地块");
+const selectedDate = ref(null);
+
+const canUndo = computed(() => {
+    return canUndoState.value;
+});
+
+const hasSelectedPoint = computed(() => {
+    return hasSelectedPointState.value;
+});
+
+// 判断是否可以确认地块(需要先选择日期)
+const canConfirm = computed(() => {
+    return selectedDate.value !== null && selectedDate.value !== "";
+});
+
+const handleEdit = () => {
+    isEditing.value = true;
+    indexMap.startEdit(() => {
+        // 编辑回调,更新撤销按钮状态和选中点状态
+        canUndoState.value = indexMap.canUndo();
+        hasSelectedPointState.value = indexMap.hasSelectedPoint();
+    });
+    // 初始化按钮状态(编辑模式刚启动时不能撤销,没有选中点)
+    canUndoState.value = false;
+    hasSelectedPointState.value = false;
+};
+
+const handleUndo = () => {
+    if (canUndo.value) {
+        indexMap.undoEdit();
+        // 更新撤销按钮状态和选中点状态
+        canUndoState.value = indexMap.canUndo();
+        hasSelectedPointState.value = indexMap.hasSelectedPoint();
+    }
+};
+
+const handleDeletePoint = () => {
+    if (hasSelectedPoint.value) {
+        const success = indexMap.deleteSelectedPoint();
+        if (success) {
+            // 删除成功后更新状态
+            hasSelectedPointState.value = indexMap.hasSelectedPoint();
+            canUndoState.value = indexMap.canUndo();
+        }
+    }
+};
+
+const handleCancel = () => {
+    if (isEditing.value) {
+        indexMap.endEdit();
+        isEditing.value = false;
+        canUndoState.value = false;
+        hasSelectedPointState.value = false;
+    } else {
+        // 恢复到初始值
+        initLandRecognition();
+    }
+};
+
+const handleConfirm = () => {
+    // 如果没有选择日期,提示用户
+    if (!canConfirm.value) {
+        ElMessage.warning("请先选择日期");
+        return;
+    }
+    
+    if (confirmButtonText.value === "确认地块") {
+        // 点击"确认地块"
+        if (isEditing.value) {
+            indexMap.saveEdit();
+            isEditing.value = false;
+            canUndoState.value = false;
+            hasSelectedPointState.value = false;
+        }
+        // 切换按钮文字为"确认识别"
+        confirmButtonText.value = "确认识别";
+    } else if (confirmButtonText.value === "确认识别") {
+        // 点击"确认识别"
+        console.log("点击确认识别");
+
+        // 获取地块WKT数据
+        const areaWKT = indexMap.getAreaWKT();
+        if (!areaWKT) {
+            console.error("无法获取地块数据");
+            return;
+        }
+
+        // 显示全局loading
+        const loadingInstance = ElLoading.service({
+            lock: true,
+            text: "识别中...",
+            background: "rgba(0, 0, 0, 0.7)",
+        });
+
+        // 3秒后关闭loading并跳转
+        setTimeout(() => {
+            // 关闭loading
+            loadingInstance.close();
+
+            // 跳转到warningHome页面,带参数
+            // 使用encodeURIComponent编码WKT数据,因为URL有长度限制
+            router.push({
+                path: "/warningHome",
+                query: {
+                    wkt: encodeURIComponent(areaWKT),
+                },
+            });
+        }, 3000);
+    }
+};
+
+const handleClick = () => {
+    router.replace("/warningHome");
+};
+
+const handleDateChange = (date) => {
+    // 日期变化时的处理逻辑
+    if (date) {
+        console.log("选择的日期:", date);
+    }
+};
+
+const initLandRecognition = () => {
+    // 清除旧地块
+    indexMap.clearLayer();
+    // 生成正方形地块
+    const arr = convertPointToArray("POINT(104.65757459845925 30.33392788321249)");
+    const geometryData = generateSquarePolygonByMu(arr, 1500);
+    // 设置地块
+    indexMap.setAreaGeometry([geometryData.wkt]);
+};
+
+/**
+ * 根据中心点和亩数生成正方形地块WKT
+ * @param {Array} center - 中心点坐标 [lng, lat]
+ * @param {Number} mu - 面积(亩)
+ * @returns {Object} 包含WKT字符串和面积(亩)的对象
+ */
+function generateSquarePolygonByMu(center, mu) {
+    // 确保输入是数字类型
+    const lng = parseFloat(center[0]);
+    const lat = parseFloat(center[1]);
+
+    // 1亩 ≈ 666.67平方米
+    const areaInSquareMeters = mu * 666.67;
+    // 正方形边长(米)
+    const sideLength = Math.sqrt(areaInSquareMeters);
+    // 半边长(米)
+    const halfSide = sideLength / 2;
+
+    // 纬度方向:1度约等于111000米
+    const latDelta = halfSide / 111000;
+
+    // 经度方向:需要根据纬度调整,1度约等于111000 * cos(lat)米
+    const lngDelta = halfSide / (111000 * Math.cos((lat * Math.PI) / 180));
+
+    // 计算四个顶点(逆时针顺序)
+    const topLeft = [lng - lngDelta, lat + latDelta];
+    const topRight = [lng + lngDelta, lat + latDelta];
+    const bottomRight = [lng + lngDelta, lat - latDelta];
+    const bottomLeft = [lng - lngDelta, lat - latDelta];
+
+    // 生成MULTIPOLYGON格式的WKT(需要闭合,所以第一个点要重复)
+    const formatPoint = (point) => `${point[0]} ${point[1]}`;
+    const coordinates = [topLeft, topRight, bottomRight, bottomLeft, topLeft].map(formatPoint).join(", ");
+
+    // 注意:MULTIPOLYGON 和括号之间需要有一个空格
+    const wkt = `MULTIPOLYGON (((${coordinates})))`;
+
+    return { wkt, area: parseFloat(mu).toFixed(2) };
+}
+</script>
+
+<style lang="scss" scoped>
+.base-container {
+    width: 100%;
+    height: 100vh;
+    color: #fff;
+    position: absolute;
+    box-sizing: border-box;
+    z-index: 2;
+    overflow: hidden;
+    .content {
+        width: 100%;
+        height: calc(100% - 74px - 48px);
+        padding: 16px 20px 0 27px;
+        display: flex;
+        justify-content: space-between;
+        box-sizing: border-box;
+        position: relative;
+        .warning-top {
+            display: flex;
+            width: max-content;
+            align-items: center;
+            position: absolute;
+            .btn-box {
+                cursor: pointer;
+                color: #f7be5a;
+                font-size: 20px;
+                font-family: "PangMenZhengDao";
+                border: 2px solid #f7be5a;
+                border-radius: 4px;
+                padding: 8px 14px 11px;
+                background: rgba(29, 29, 29, 0.54);
+            }
+            .btn-box + .btn-box,
+            .btn-box + :deep(.date-picker-custom) {
+                margin-left: 15px;
+            }
+            :deep(.date-picker-custom) {
+                .el-input__wrapper {
+                    background: rgba(29, 29, 29, 0.54);
+                    border: 2px solid #f7be5a;
+                    border-radius: 4px;
+                    padding: 8px 14px 11px;
+                    box-shadow: none;
+                    height: 28px;
+                    margin-top: -5px;
+                }
+                .el-input__inner {
+                    color: #f7be5a;
+                    font-size: 20px;
+                    font-family: "PangMenZhengDao";
+                }
+                .el-input__suffix {
+                    .el-input__suffix-inner {
+                        .el-icon {
+                            color: #f7be5a !important;
+                        }
+                        svg {
+                            color: #f7be5a !important;
+                            fill: #f7be5a !important;
+                        }
+                    }
+                }
+                // 针对日期选择器的图标
+                .el-input__icon {
+                    color: #f7be5a !important;
+                }
+                // 针对 SVG 图标
+                svg {
+                    color: #f7be5a !important;
+                    fill: #f7be5a !important;
+                }
+
+                &:hover .el-input__wrapper {
+                    border-color: #f7be5a;
+                }
+                &.is-focus .el-input__wrapper {
+                    border-color: #f7be5a;
+                    box-shadow: 0 0 0 1px #f7be5a inset;
+                }
+            }
+        }
+        .top-btn {
+            display: flex;
+            align-items: center;
+            position: absolute;
+            top: 38px;
+            right: 38px;
+            gap: 20px;
+            .btn-item {
+                background: #ffffff;
+                color: #1d1d1d;
+                padding: 11px 27px;
+                border-radius: 4px;
+                font-size: 16px;
+                font-weight: 500;
+                cursor: pointer;
+                &.disabled {
+                    opacity: 0.5;
+                    cursor: not-allowed;
+                }
+            }
+        }
+        .bottom-btn {
+            display: flex;
+            align-items: center;
+            position: absolute;
+            bottom: 100px;
+            left: calc(50% - 280px);
+            gap: 20px;
+            .btn-item {
+                background: #ffffff;
+                color: #1d1d1d;
+                width: 248px;
+                padding: 18px 0;
+                text-align: center;
+                border-radius: 12px;
+                font-size: 20px;
+                font-family: "PangMenZhengDao";
+                cursor: pointer;
+                &.disabled {
+                    opacity: 0.5;
+                    cursor: not-allowed;
+                }
+            }
+            .btn-item.primary-btn {
+                background: linear-gradient(180deg, #ffd887, #ed9e1e);
+                color: #1d1d1d;
+                &.disabled {
+                    opacity: 0.5;
+                    cursor: not-allowed;
+                }
+            }
+        }
+    }
+}
+.map {
+    width: 100%;
+    height: 100vh;
+    position: absolute;
+    z-index: 1;
+}
+</style>

+ 849 - 0
src/views/landRecognition/indexMap.js

@@ -0,0 +1,849 @@
+import config from "@/api/config.js";
+import * as KMap from "@/utils/ol-map/KMap";
+import * as util from "@/common/ol_common.js";
+import Point from "ol/geom/Point.js";
+import Feature from "ol/Feature";
+import VectorLayer from "ol/layer/Vector.js";
+import VectorSource from "ol/source/Vector";
+import WKT from "ol/format/WKT.js";
+import ScaleLine from "ol/control/ScaleLine";
+import { useRouter } from "vue-router";
+import locationIcon from "@/assets/images/map/location.png";
+import OLPolygon from "ol/geom/Polygon.js";
+import * as proj from "ol/proj";
+import Fill from "ol/style/Fill";
+import Stroke from "ol/style/Stroke";
+import Style from "ol/style/Style";
+import Icon from "ol/style/Icon";
+import Circle from "ol/style/Circle";
+import Text from "ol/style/Text";
+import Modify from "ol/interaction/Modify";
+import Translate from "ol/interaction/Translate";
+import { getArea } from "ol/sphere";
+
+/**
+ * @description 地图层对象
+ */
+class IndexMap {
+    constructor() {
+        let that = this;
+        let vectorStyle = new KMap.VectorStyle();
+        this.vectorStyle = vectorStyle;
+
+        // 位置图标
+        this.clickPointLayer = new KMap.VectorLayer("clickPointLayer", 9999, {
+            style: (f) => {
+                return new Style({
+                    image: new Icon({
+                        src: locationIcon,
+                        scale: 0.4,
+                        anchor: [0.5, 1],
+                    }),
+                });
+            },
+        });
+
+        // 地块图层:默认不显示编辑点
+        this.areaLayer = new KMap.VectorLayer("areaLayer", 100, {
+            minZoom: 2,
+            style: (f) => {
+                const styles = [];
+                const baseStyle = new Style({
+                    fill: new Fill({
+                        color: "rgba(59, 45, 2, 0.31)",
+                    }),
+                    stroke: new Stroke({
+                        color: "#FFD489",
+                        width: 2,
+                    }),
+                });
+                styles.push(baseStyle);
+
+                // 计算并显示面积(亩数)
+                try {
+                    const geometry = f.getGeometry();
+                    const geom = geometry.clone();
+                    // 转换坐标系用于计算面积
+                    geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:3857"));
+                    let area = getArea(geom);
+                    // 转换为亩:1平方米 = 0.0015亩,但根据代码中的公式是 (area + area / 2) / 1000
+                    area = (area + area / 2) / 1000;
+                    const areaText = area.toFixed(2) + "亩";
+
+                    // 添加面积文字标签
+                    const areaTextStyle = new Style({
+                        text: new Text({
+                            text: areaText,
+                            font: "26px PangMenZhengDao",
+                            fill: new Fill({
+                                color: "#FFFFFF",
+                            }),
+                            stroke: new Stroke({
+                                color: "#000",
+                                width: 2,
+                            }),
+                            textAlign: "center",
+                            textBaseline: "middle",
+                            overflow: true,
+                            placement: "point",
+                            offsetY: 20,
+                        }),
+                    });
+                    styles.push(areaTextStyle);
+
+                    // 计算多边形的中心点并添加中心点图标
+                    try {
+                        const extent = geometry.getExtent();
+                        const centerX = (extent[0] + extent[2]) / 2;
+                        const centerY = (extent[1] + extent[3]) / 2;
+                        const centerCoord = [centerX, centerY];
+                        
+                        const centerPointStyle = new Style({
+                            geometry: new Point(centerCoord),
+                            image: new Icon({
+                                src: locationIcon,
+                                scale: 0.4,
+                                anchor: [0.5, 1],
+                            }),
+                        });
+                        styles.push(centerPointStyle);
+                    } catch (error) {
+                        console.error("计算中心点失败:", error);
+                    }
+                } catch (error) {
+                    console.error("计算面积失败:", error);
+                }
+
+                // 如果处于编辑模式,显示编辑点
+                if (f.get("icon") === "point-act") {
+                    const geometry = f.getGeometry();
+                    const type = geometry.getType();
+                    let rings = [];
+                    if (type === "Polygon") {
+                        rings = geometry.getCoordinates();
+                    } else if (type === "MultiPolygon") {
+                        const polys = geometry.getCoordinates();
+                        polys.forEach((poly) => {
+                            rings = rings.concat(poly);
+                        });
+                    }
+                    rings.forEach((ring, ringIndex) => {
+                        if (!ring || ring.length < 2) return;
+                        for (let i = 0; i < ring.length - 1; i++) {
+                            // 检查是否是选中的点
+                            const isSelected =
+                                this.selectedPointFeature === f &&
+                                this.selectedRingIndex === ringIndex &&
+                                this.selectedPointIndex === i;
+
+                            styles.push(
+                                new Style({
+                                    geometry: new Point(ring[i]),
+                                    image: new Circle({
+                                        radius: 6,
+                                        fill: new Fill({
+                                            // 选中的点显示为红色
+                                            color: isSelected ? "#FF0000" : i % 2 ? "#FFD489" : "#FFFFFF",
+                                        }),
+                                        stroke: new Stroke({
+                                            color: isSelected ? "#FF0000" : "#FFD489",
+                                            width: isSelected ? 2 : 1,
+                                        }),
+                                    }),
+                                    zIndex: 101,
+                                })
+                            );
+                        }
+                    });
+                }
+                return styles;
+            },
+        });
+
+        // 编辑相关状态
+        this.isEditMode = false;
+        this.editModify = null;
+        this.originalGeometries = null;
+        this.editHistory = [];
+        this.editHistoryIndex = -1;
+        this.hasInitialState = false;
+        this.isUndoing = false;
+
+        // 点选择相关状态
+        this.selectedPointFeature = null;
+        this.selectedPointIndex = -1;
+        this.selectedRingIndex = 0;
+        this.pointClickHandler = null;
+
+        // 拖拽交互(用于非编辑模式下拖拽多边形)
+        this.translateInteraction = null;
+    }
+
+    initMap(location, target) {
+        let level = 16;
+        let coordinate = util.wktCastGeom(location).getFirstCoordinate();
+        this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 6, 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.areaLayer.layer);
+        // this.setMapPoint(coordinate);
+    }
+
+    setMapPoint(coordinate) {
+        this.clickPointLayer.source.clear();
+        let point = new Feature(new Point(coordinate));
+        this.clickPointLayer.addFeature(point);
+    }
+
+    clearLayer() {
+        // 清除图层时,移除拖拽交互
+        this.disableTranslate();
+
+        if (this.areaLayer && this.areaLayer.layer) {
+            this.areaLayer.layer.getSource().clear();
+        }
+    }
+
+    setAreaGeometry(geometryArr) {
+        this.clearLayer();
+        let that = this;
+        const source = new VectorSource();
+
+        geometryArr.forEach((item) => {
+            const format = new WKT();
+            const mapProjection = that.kmap.map.getView().getProjection();
+            let geometry = format.readGeometry(item, {
+                dataProjection: "EPSG:4326",
+                featureProjection: mapProjection,
+            });
+            let f = new Feature({ geometry: geometry });
+            source.addFeature(f);
+        });
+
+        this.areaLayer.layer.setSource(source);
+        this.fitView();
+
+        // 启用拖拽交互(非编辑模式下可以拖拽多边形)
+        this.enableTranslate();
+    }
+
+    // 启用拖拽交互
+    enableTranslate() {
+        if (!this.areaLayer || !this.areaLayer.layer) {
+            return;
+        }
+        const source = this.areaLayer.layer.getSource();
+        if (!source || source.getFeatures().length === 0) {
+            return;
+        }
+
+        // 如果已经存在拖拽交互,先移除
+        if (this.translateInteraction) {
+            this.kmap.map.removeInteraction(this.translateInteraction);
+        }
+
+        // 创建拖拽交互
+        this.translateInteraction = new Translate({
+            layers: [this.areaLayer.layer],
+        });
+
+        // 添加拖拽交互到地图
+        this.kmap.map.addInteraction(this.translateInteraction);
+    }
+
+    // 禁用拖拽交互
+    disableTranslate() {
+        if (this.translateInteraction) {
+            this.kmap.map.removeInteraction(this.translateInteraction);
+            this.translateInteraction = null;
+        }
+    }
+
+    fitView() {
+        const source = this.areaLayer.layer.getSource();
+        if (!source || source.getFeatures().length === 0) {
+            return;
+        }
+        let extent = source.getExtent();
+        // 地图自适应到区域可视范围
+        this.kmap.getView().fit(extent, { duration: 500, padding: [100, 100, 100, 100] });
+    }
+
+    // 启用编辑模式
+    startEdit(onModifyCallback) {
+        if (!this.areaLayer || !this.areaLayer.layer) {
+            return;
+        }
+        const source = this.areaLayer.layer.getSource();
+        if (!source || source.getFeatures().length === 0) {
+            return;
+        }
+        // 保存原始几何数据,用于退出编辑时还原
+        const features = source.getFeatures();
+        this.originalGeometries = features.map((feature) => {
+            return {
+                feature: feature,
+                geometry: feature.getGeometry().clone(), // 克隆几何数据
+            };
+        });
+
+        // 初始化历史记录(不在进入编辑模式时保存初始状态,在第一次修改时保存)
+        this.editHistory = [];
+        this.editHistoryIndex = -1;
+        // 标记是否已经保存过初始状态
+        this.hasInitialState = false;
+        // 标记是否正在执行撤销操作(用于防止撤销时触发保存)
+        this.isUndoing = false;
+
+        // 为所有 feature 设置编辑标记,用于显示编辑点
+        features.forEach((feature) => {
+            feature.set("icon", "point-act");
+        });
+        // 刷新图层样式
+        this.areaLayer.layer.changed();
+
+        // 禁用拖拽交互(编辑模式下使用 Modify 交互)
+        this.disableTranslate();
+
+        // 设置修改交互的源为area图层
+        if (this.kmap.modify) {
+            this.kmap.modify.setActive(false);
+        }
+        // 重新初始化修改交互,使用area图层的源
+        if (this.editModify) {
+            this.kmap.map.removeInteraction(this.editModify);
+        }
+        this.editModify = new Modify({
+            source: source,
+            pixelTolerance: 10,
+        });
+        // 保存回调函数引用,用于在事件中调用
+        this.onModifyCallback = onModifyCallback;
+
+        // 监听修改事件,更新样式和保存历史
+        // 只有在用户真正编辑后(modifyend)才开始记录历史状态
+        this.editModify.on("modifystart", () => {
+            // 只有在第一次修改开始时,才保存当前状态作为初始状态
+            if (!this.hasInitialState) {
+                this.saveHistoryState();
+                // 第一次保存后,通知外部更新状态
+                if (this.onModifyCallback && typeof this.onModifyCallback === "function") {
+                    setTimeout(() => {
+                        this.onModifyCallback();
+                    }, 0);
+                }
+            }
+            // 后续修改时不在 modifystart 保存,只在 modifyend 保存
+        });
+        this.editModify.on("modifyend", () => {
+            // 修改完成后保存修改后的状态(只有真正编辑后才保存)
+            if (this.hasInitialState) {
+                this.saveHistoryState();
+            }
+            this.areaLayer.layer.changed();
+            // 调用回调通知外部有修改,使用 setTimeout 确保状态已经更新
+            if (this.onModifyCallback && typeof this.onModifyCallback === "function") {
+                setTimeout(() => {
+                    this.onModifyCallback();
+                }, 0);
+            }
+        });
+        this.kmap.map.addInteraction(this.editModify);
+        this.editModify.setActive(true);
+        this.isEditMode = true;
+
+        // 启用点选择功能
+        this.enablePointSelection();
+    }
+
+    // 启用点选择功能
+    enablePointSelection() {
+        if (!this.areaLayer || !this.areaLayer.layer) {
+            return;
+        }
+        const source = this.areaLayer.layer.getSource();
+        if (!source || source.getFeatures().length === 0) {
+            return;
+        }
+
+        // 清除之前的点击处理器
+        if (this.pointClickHandler) {
+            this.kmap.map.un("singleclick", this.pointClickHandler);
+        }
+
+        const that = this;
+        this.pointClickHandler = (evt) => {
+            if (!that.isEditMode) {
+                return;
+            }
+
+            const pixel = evt.pixel;
+
+            // 获取所有 feature
+            const features = source.getFeatures();
+            if (features.length === 0) {
+                return;
+            }
+
+            // 在所有 feature 中查找最近的点
+            let minDistance = Infinity;
+            let closestPoint = null;
+
+            // 遍历所有 feature 查找点击的点
+            for (let feature of features) {
+                if (feature.get("icon") !== "point-act") {
+                    continue;
+                }
+
+                const geometry = feature.getGeometry();
+                const type = geometry.getType();
+                let rings = [];
+
+                if (type === "Polygon") {
+                    rings = geometry.getCoordinates();
+                } else if (type === "MultiPolygon") {
+                    const polys = geometry.getCoordinates();
+                    polys.forEach((poly) => {
+                        rings = rings.concat(poly);
+                    });
+                }
+
+                // 遍历所有环查找最近的点
+                rings.forEach((ring, ringIndex) => {
+                    if (!ring || ring.length < 2) return;
+
+                    for (let i = 0; i < ring.length - 1; i++) {
+                        const pointCoord = ring[i];
+                        // 计算点击位置与点的距离(像素)
+                        const pointPixel = that.kmap.map.getPixelFromCoordinate(pointCoord);
+                        const distance = Math.sqrt(
+                            Math.pow(pixel[0] - pointPixel[0], 2) + Math.pow(pixel[1] - pointPixel[1], 2)
+                        );
+
+                        // 记录最近的点
+                        if (distance < minDistance) {
+                            minDistance = distance;
+                            closestPoint = {
+                                feature: feature,
+                                ringIndex: ringIndex,
+                                pointIndex: i,
+                                distance: distance,
+                            };
+                        }
+                    }
+                });
+            }
+
+            // 如果找到最近的点且距离小于 15 像素,选中它
+            if (closestPoint && closestPoint.distance < 15) {
+                that.selectedPointFeature = closestPoint.feature;
+                that.selectedPointIndex = closestPoint.pointIndex;
+                that.selectedRingIndex = closestPoint.ringIndex;
+
+                // 刷新图层样式
+                that.areaLayer.layer.changed();
+
+                // 通知外部更新
+                if (that.onModifyCallback && typeof that.onModifyCallback === "function") {
+                    setTimeout(() => {
+                        that.onModifyCallback();
+                    }, 0);
+                }
+            } else {
+                // 如果没有选中点,清除选择
+                that.clearPointSelection();
+                if (that.onModifyCallback && typeof that.onModifyCallback === "function") {
+                    setTimeout(() => {
+                        that.onModifyCallback();
+                    }, 0);
+                }
+            }
+        };
+
+        // 使用 priority 确保点选择事件在 Modify 交互之前处理
+        // 通过使用 once 和重新绑定,或者使用更高的优先级
+        this.kmap.map.on("singleclick", this.pointClickHandler);
+
+        // 暂时禁用 Modify 交互的点选择功能,避免冲突
+        // Modify 交互仍然可以用于拖拽编辑,但点选择由我们自己的处理器处理
+    }
+
+    // 清除点选择
+    clearPointSelection() {
+        this.selectedPointFeature = null;
+        this.selectedPointIndex = -1;
+        this.selectedRingIndex = 0;
+        if (this.areaLayer && this.areaLayer.layer) {
+            this.areaLayer.layer.changed();
+        }
+    }
+
+    // 检查是否有选中的点
+    hasSelectedPoint() {
+        return this.selectedPointFeature !== null && this.selectedPointIndex >= 0;
+    }
+
+    // 删除选中的点
+    deleteSelectedPoint() {
+        if (!this.hasSelectedPoint()) {
+            console.log("没有选中的点");
+            return false;
+        }
+
+        const feature = this.selectedPointFeature;
+        if (!feature) {
+            console.log("选中的 feature 不存在");
+            return false;
+        }
+
+        const geometry = feature.getGeometry();
+        if (!geometry) {
+            console.log("几何对象不存在");
+            return false;
+        }
+
+        const type = geometry.getType();
+        let coordinates = geometry.getCoordinates();
+        let updated = false;
+
+        try {
+            // 保存选中的索引,因为删除后需要清除选择
+            const selectedPointIndex = this.selectedPointIndex;
+            const selectedRingIndex = this.selectedRingIndex;
+
+            // 在删除点之前,先保存当前状态(包含要删除的点)
+            // 这样撤销时可以恢复到删除前的状态
+            if (!this.hasInitialState) {
+                this.saveHistoryState();
+            } else {
+                // 如果已经有初始状态,也需要保存删除前的状态
+                this.saveHistoryState();
+            }
+
+            if (type === "Polygon") {
+                // 单多边形
+                let ring = coordinates[0];
+                // 删除选中的点(不包括最后一个闭合点)
+                if (selectedPointIndex < ring.length - 1 && ring.length > 1) {
+                    // 创建新的坐标数组,避免直接修改原数组
+                    const newRing = [...ring];
+                    newRing.splice(selectedPointIndex, 1);
+                    // 确保多边形闭合(最后一个点等于第一个点)
+                    if (
+                        newRing.length > 0 &&
+                        (newRing[newRing.length - 1][0] !== newRing[0][0] ||
+                            newRing[newRing.length - 1][1] !== newRing[0][1])
+                    ) {
+                        newRing[newRing.length - 1] = [newRing[0][0], newRing[0][1]];
+                    }
+                    // 更新几何坐标
+                    geometry.setCoordinates([newRing]);
+                    updated = true;
+                }
+            } else if (type === "MultiPolygon") {
+                // 多多边形
+                if (selectedRingIndex < coordinates.length) {
+                    let ring = coordinates[selectedRingIndex][0];
+                    if (selectedPointIndex < ring.length - 1 && ring.length > 1) {
+                        // 创建新的坐标数组
+                        const newRing = [...ring];
+                        newRing.splice(selectedPointIndex, 1);
+                        // 确保多边形闭合
+                        if (
+                            newRing.length > 0 &&
+                            (newRing[newRing.length - 1][0] !== newRing[0][0] ||
+                                newRing[newRing.length - 1][1] !== newRing[0][1])
+                        ) {
+                            newRing[newRing.length - 1] = [newRing[0][0], newRing[0][1]];
+                        }
+                        // 更新对应环的坐标
+                        const newCoordinates = JSON.parse(JSON.stringify(coordinates));
+                        newCoordinates[selectedRingIndex][0] = newRing;
+                        geometry.setCoordinates(newCoordinates);
+                        updated = true;
+                    }
+                }
+            }
+
+            if (!updated) {
+                console.log("删除点失败:未更新");
+                return false;
+            }
+
+            // 触发几何变化事件,确保图层知道几何已更新
+            geometry.changed();
+            // 触发 feature 变化事件
+            feature.changed();
+
+            // 保存删除点后的状态
+            this.saveHistoryState();
+
+            // 清除选择(必须在更新几何之后)
+            this.clearPointSelection();
+
+            // 刷新图层(这会触发样式函数重新计算面积)
+            this.areaLayer.layer.changed();
+
+            // 确保 Modify 交互仍然激活
+            if (this.editModify) {
+                this.editModify.setActive(true);
+            }
+
+            // 通知外部更新(确保撤销按钮状态更新)
+            // 使用 setTimeout 确保历史状态已经保存完成
+            if (this.onModifyCallback && typeof this.onModifyCallback === "function") {
+                setTimeout(() => {
+                    this.onModifyCallback();
+                }, 10);
+            }
+
+            console.log(
+                "删除点成功,历史记录索引:",
+                this.editHistoryIndex,
+                "历史记录长度:",
+                this.editHistory ? this.editHistory.length : 0,
+                "可以撤销:",
+                this.canUndo()
+            );
+            return true;
+        } catch (error) {
+            console.error("删除点失败:", error);
+            return false;
+        }
+    }
+
+    // 保存当前状态到历史记录
+    saveHistoryState() {
+        // 如果正在执行撤销操作,不保存状态(避免撤销时触发保存)
+        if (this.isUndoing) {
+            return;
+        }
+        if (!this.areaLayer || !this.areaLayer.layer) {
+            return;
+        }
+        const source = this.areaLayer.layer.getSource();
+        if (!source) {
+            return;
+        }
+        const features = source.getFeatures();
+        if (features.length === 0) {
+            return;
+        }
+        // 初始化历史记录数组
+        if (!this.editHistory) {
+            this.editHistory = [];
+            this.editHistoryIndex = -1;
+        }
+        // 保存当前所有feature的几何数据
+        const state = features.map((feature) => {
+            return {
+                feature: feature,
+                geometry: feature.getGeometry().clone(),
+            };
+        });
+
+        // 如果是第一次保存(还没有历史记录),直接保存初始状态
+        if (this.editHistory.length === 0) {
+            this.hasInitialState = true;
+            // 直接添加到历史记录,不需要检查
+            this.editHistory.push(state);
+            this.editHistoryIndex = 0;
+            return;
+        }
+
+        // 检查当前状态是否与上一个状态相同(避免重复保存)
+        if (this.editHistoryIndex >= 0) {
+            const lastState = this.editHistory[this.editHistoryIndex];
+            if (lastState && lastState.length === state.length) {
+                const wkt = new WKT();
+                let isSame = true;
+                for (let i = 0; i < state.length; i++) {
+                    const currentWkt = wkt.writeGeometry(state[i].geometry);
+                    const lastWkt = wkt.writeGeometry(lastState[i].geometry);
+                    if (currentWkt !== lastWkt) {
+                        isSame = false;
+                        break;
+                    }
+                }
+                // 如果状态相同,不保存
+                if (isSame) {
+                    return;
+                }
+            }
+        }
+
+        // 移除当前位置之后的历史记录(如果有新的修改)
+        if (this.editHistoryIndex < this.editHistory.length - 1) {
+            this.editHistory = this.editHistory.slice(0, this.editHistoryIndex + 1);
+        }
+        // 添加到历史记录
+        this.editHistory.push(state);
+        this.editHistoryIndex = this.editHistory.length - 1;
+        // 限制历史记录数量,避免内存占用过大
+        if (this.editHistory.length > 50) {
+            this.editHistory.shift();
+            this.editHistoryIndex--;
+        }
+    }
+
+    // 撤销编辑(恢复到上一步)
+    undoEdit() {
+        if (!this.isEditMode) {
+            return false;
+        }
+        if (!this.editHistory || this.editHistory.length === 0) {
+            return false;
+        }
+        // 如果已经在第一个状态(index 0),无法撤销
+        if (this.editHistoryIndex <= 0) {
+            return false;
+        }
+
+        // 设置撤销标志,防止恢复几何数据时触发保存
+        this.isUndoing = true;
+        try {
+            // 回退到上一个状态
+            this.editHistoryIndex--;
+            const previousState = this.editHistory[this.editHistoryIndex];
+
+            // 恢复几何数据
+            if (previousState && previousState.length > 0) {
+                previousState.forEach(({ feature, geometry }) => {
+                    feature.setGeometry(geometry.clone());
+                });
+                // 刷新图层样式
+                this.areaLayer.layer.changed();
+                return true;
+            }
+            return false;
+        } finally {
+            // 确保无论成功还是失败,都要重置标志位
+            this.isUndoing = false;
+        }
+    }
+
+    // 检查是否可以撤销
+    canUndo() {
+        if (!this.isEditMode) {
+            return false;
+        }
+        if (!this.editHistory || this.editHistory.length === 0) {
+            return false;
+        }
+        // 如果当前索引大于0,说明可以撤销
+        return this.editHistoryIndex > 0;
+    }
+
+    // 禁用编辑模式(还原到编辑前的状态)
+    endEdit() {
+        if (this.editModify) {
+            this.editModify.setActive(false);
+            this.kmap.map.removeInteraction(this.editModify);
+            this.editModify = null;
+        }
+
+        // 清除点选择
+        if (this.pointClickHandler) {
+            this.kmap.map.un("singleclick", this.pointClickHandler);
+            this.pointClickHandler = null;
+        }
+        this.clearPointSelection();
+
+        // 清除历史记录
+        this.editHistory = null;
+        this.editHistoryIndex = -1;
+        this.hasInitialState = false;
+        this.isUndoing = false;
+        // 还原地块到编辑前的状态
+        if (this.areaLayer && this.areaLayer.layer) {
+            const source = this.areaLayer.layer.getSource();
+            if (source && this.originalGeometries) {
+                // 恢复原始几何数据
+                this.originalGeometries.forEach(({ feature, geometry }) => {
+                    feature.setGeometry(geometry.clone());
+                    feature.unset("icon");
+                });
+                // 清除保存的原始几何数据
+                this.originalGeometries = null;
+                // 刷新图层样式
+                this.areaLayer.layer.changed();
+            } else {
+                // 如果没有保存的原始数据,只清除编辑标记
+                const features = source.getFeatures();
+                features.forEach((feature) => {
+                    feature.unset("icon");
+                });
+                this.areaLayer.layer.changed();
+            }
+        }
+        this.isEditMode = false;
+
+        // 重新启用拖拽交互(退出编辑模式后可以拖拽)
+        this.enableTranslate();
+    }
+
+    // 保存编辑(保留当前编辑状态)
+    saveEdit() {
+        if (this.editModify) {
+            this.editModify.setActive(false);
+            this.kmap.map.removeInteraction(this.editModify);
+            this.editModify = null;
+        }
+
+        // 清除点选择
+        if (this.pointClickHandler) {
+            this.kmap.map.un("singleclick", this.pointClickHandler);
+            this.pointClickHandler = null;
+        }
+        this.clearPointSelection();
+
+        // 清除保存的原始几何数据(因为已经保存了当前状态)
+        if (this.areaLayer && this.areaLayer.layer) {
+            const source = this.areaLayer.layer.getSource();
+            if (source) {
+                const features = source.getFeatures();
+                features.forEach((feature) => {
+                    feature.unset("icon");
+                });
+                // 刷新图层样式
+                this.areaLayer.layer.changed();
+            }
+        }
+        this.originalGeometries = null;
+        this.isEditMode = false;
+
+        // 重新启用拖拽交互(保存编辑后可以拖拽)
+        this.enableTranslate();
+    }
+
+    // 获取地块的WKT数据
+    getAreaWKT() {
+        if (!this.areaLayer || !this.areaLayer.layer) {
+            return null;
+        }
+        const source = this.areaLayer.layer.getSource();
+        if (!source || source.getFeatures().length === 0) {
+            return null;
+        }
+        const features = source.getFeatures();
+        const wktFormat = new WKT();
+        const wktStrings = features
+            .map((feature) => {
+                const geometry = feature.getGeometry();
+                if (!geometry) {
+                    return null;
+                }
+                // 转换为WGS84坐标系(EPSG:4326)的WKT
+                const geom = geometry.clone();
+                const mapProjection = this.kmap.map.getView().getProjection();
+                geom.transform(mapProjection, "EPSG:4326");
+                return wktFormat.writeGeometry(geom);
+            })
+            .filter(Boolean);
+
+        return wktStrings.length > 0 ? wktStrings[0] : null;
+    }
+}
+
+export default IndexMap;

+ 1 - 2
src/views/warningHome/components/chart_components/chartList.vue

@@ -134,8 +134,6 @@ const yAxisFormatter = "{value}";
 onMounted(() => {
     // 监听图例组件的变化事件
     eventBus.on("landUseLegend:change", handleLandUseLegendChange);
-    // 初始化时请求用地面积统计数据(statType=1)
-    fetchYieldChartData();
 });
 
 onUnmounted(() => {
@@ -157,6 +155,7 @@ const handleLandUseLegendChange = (data) => {
     // 使用新的 nonGrain 重新获取饼图数据
     fetchStatRegionAreaRatio(data.nonGrain);
     fetchStatPhenologyAreaRatioForGrain();
+    fetchYieldChartData();
 };
 
 const options = ref([]);

+ 56 - 5
src/views/warningHome/index.vue

@@ -16,11 +16,12 @@
                             />
                         </div>
                     </div> -->
-                    <div class="address-box">四川省-简阳市-平泉街道</div>
-                    <div class="btn-box" @click="handleClick">智慧果园</div>
+                    <div class="address-box" @click="backToHome">{{ isLandRecognition ? "返回" : "四川省-简阳市-平泉街道" }}</div>
+                    <div class="btn-box" v-if="!isLandRecognition" @click="handleClick">智慧果园</div>
+                    <div class="btn-box" @click="handleLandRecognition">{{ isLandRecognition ? "重新识别" : "地块识别" }}</div>
                 </div>
             </div>
-            <div class="warning-r right chart-wrap yes-events">
+            <div class="warning-r right chart-wrap yes-events" v-show="isLandRecognition">
                 <chart-list
                     :activeBaseTab="activeBaseTab"
                     :areaCode="selectedAreaCode"
@@ -29,7 +30,7 @@
             </div>
             <!-- 地图图例 -->
             <!-- <map-legend></map-legend> -->
-            <land-use-legend @change="handleLegendChange"></land-use-legend>
+            <land-use-legend @change="handleLegendChange" v-show="isLandRecognition"></land-use-legend>
             <!-- 地图搜索 -->
             <div class="warning-search yes-events">
                 <el-select
@@ -67,6 +68,7 @@
 
 <script setup>
 import "./map/mockFarmLayer";
+import { convertPointToArray } from "@/utils/index";
 import StaticMapLayers from "@/components/static_map_change/Layers.js";
 import StaticMapPointLayers from "@/components/static_map_change/pointLayer.js";
 import { onMounted, onUnmounted, ref, reactive, nextTick } from "vue";
@@ -80,11 +82,12 @@ import BoundaryLayer from "./map/boundaryLayer";
 import trackDialog from "./components/trackDialog.vue";
 import eventBus from "@/api/eventBus";
 import { useStore } from "vuex";
-import { useRouter } from "vue-router";
+import { useRouter, useRoute } from "vue-router";
 import chartList from "./components/chart_components/chartList.vue";
 
 let store = useStore();
 const router = useRouter();
+const route = useRoute();
 let warningMap = new WarningMap();
 let alarmLayer = null;
 let staticMapLayers = null;
@@ -106,6 +109,8 @@ const activePhenologySecondId = ref(null);
 const currentYear = ref(2025);
 const currentQuarter = ref(1);
 
+const isLandRecognition = ref(false);
+
 // 顶部基础 tabs
 const activeBaseTab = ref("作物分布");
 
@@ -145,6 +150,35 @@ onMounted(async () => {
     getVillageBoundary()
 
     eventBus.emit("warningMap:init", warningMap.kmap);
+    
+    // 检查是否从地块识别页面跳转过来
+    if (route.query.wkt) {
+        // 使用nextTick确保所有图层都已初始化
+        await nextTick();
+        
+        // 解码WKT数据
+        const wkt = decodeURIComponent(route.query.wkt);
+        if (wkt) {
+            // 显示地块识别模式
+            isLandRecognition.value = true;
+            
+            // 将WKT转换为distributionLayer需要的格式
+            const plotData = {
+                geom: wkt,
+                color: "#00A342", // 绿色,与地块识别页面的颜色一致
+                label: "识别地块",
+            };
+            
+            // 在地图上显示地块
+            if (distributionLayer) {
+                try{
+                    distributionLayer.setAreaGeometry([plotData.geom]);
+                } catch (error) {
+                    console.error("初始化地块数据失败:", error);
+                }
+            }
+        }
+    }
 
     // 图例数据
     eventBus.on("alarmList:warningLayers", (data) => {
@@ -248,6 +282,20 @@ const handleTimeChange = (index, year, quarter) => {
     }
 };
 
+const handleLandRecognition = () => {
+    if (isLandRecognition.value) {
+        router.go(-1);
+    } else {
+        router.push("/landRecognition");
+    }
+};
+
+const backToHome = () => {
+    if (isLandRecognition.value) {
+        router.replace("/warningHome");
+    }
+};
+
 // 初始化区域选择器的默认值
 const initAreaDefaultValue = async () => {
     try {
@@ -711,6 +759,9 @@ const handleLegendChange = (data) => {
                 padding: 8px 14px 11px;
                 background: rgba(29, 29, 29, 0.54);
             }
+            .btn-box + .btn-box {
+                margin-left: 15px;
+            }
             .top-l {
                 display: flex;
                 flex-direction: column;

+ 150 - 60
src/views/warningHome/map/distributionLayer.js

@@ -5,23 +5,26 @@ import { WKT } from "ol/format";
 import { Circle, Fill, Stroke, Style, Text, Icon, RegularShape } from "ol/style.js";
 import Photo from "ol-ext/style/Photo";
 import { newPoint, newPolymerFeature } from "@/utils/map.js";
-
+import locationIcon from "@/assets/images/map/location.png";
 import pointBgImg from "@/assets/images/map/point_bg.png";
 import eventBus from "@/api/eventBus";
+import Point from "ol/geom/Point.js";
 
 class DistributionLayer {
     constructor(kmap) {
         let that = this;
-        this.kmap = kmap
-        let vectorStyle = new KMap.VectorStyle()
+        this.kmap = kmap;
+        // 标志:是否锁定地图中心(当有地块识别数据时,锁定中心位置)
+        this.lockViewCenter = false;
+        let vectorStyle = new KMap.VectorStyle();
         this.distributionLayer = new KMap.VectorLayer("distributionLayer", 99, {
             source: new VectorSource({}),
             style: function (f) {
-                let style2 = vectorStyle.getPolygonStyle(f.get("color")+'4d', f.get("color"), 2)
-                return [style2]
-            }
+                let style2 = vectorStyle.getPolygonStyle(f.get("color") + "4d", f.get("color"), 2);
+                return [style2];
+            },
         });
-        this.kmap.addLayer(this.distributionLayer.layer)
+        this.kmap.addLayer(this.distributionLayer.layer);
 
         // 创建聚合数据源(必须提供一个非空的 VectorSource,避免内部调用 loadFeatures 时 source 为空报错)
         this.clusterSource = new Cluster({
@@ -81,49 +84,50 @@ class DistributionLayer {
             // 聚合点数量徽章(红色圆形,白色文字,图片右上角)
             // 图片 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: "#FF0000", // 红色
-                        }),
-                        // 定位到图片右上角
-                        displacement: [22, 25], // 相对于图片中心,向右上偏移
-                    }),
-                    text: new Text({
-                        text: clusterSize > 99 ? "99+" : clusterSize.toString(),
-                        font: "bold 11px sans-serif",
-                        fill: new Fill({
-                            color: "#FFFFFF", // 白色文字
-                        }),
-                        // 文字位置与圆形位置一致
-                        offsetX: 22,
-                        offsetY: -25,
-                        textAlign: "center",
-                        textBaseline: "middle",
-                    }),
-                })
-                : null;
+            const badgeStyle =
+                isCluster && clusterSize > 1
+                    ? new Style({
+                          image: new Circle({
+                              radius: 10,
+                              fill: new Fill({
+                                  color: "#FF0000", // 红色
+                              }),
+                              // 定位到图片右上角
+                              displacement: [22, 25], // 相对于图片中心,向右上偏移
+                          }),
+                          text: new Text({
+                              text: clusterSize > 99 ? "99+" : clusterSize.toString(),
+                              font: "bold 11px sans-serif",
+                              fill: new Fill({
+                                  color: "#FFFFFF", // 白色文字
+                              }),
+                              // 文字位置与圆形位置一致
+                              offsetX: 22,
+                              offsetY: -25,
+                              textAlign: "center",
+                              textBaseline: "middle",
+                          }),
+                      })
+                    : null;
 
             // 农场名称(显示在点位下方,白色文字)
             // 聚合点显示第一个点的农场名称
             const farmNameStyle = farmName
                 ? new Style({
-                    text: new Text({
-                        text: farmName,
-                        font: "normal 18px sans-serif",
-                        offsetY: 45, // 向下偏移,位于点位下方
-                        textAlign: "center",
-                        fill: new Fill({
-                            color: "#FFFFFF",
-                        }),
-                        stroke: new Stroke({
-                            color: "rgba(0,0,0,0.6)",
-                            width: 2,
-                        }),
-                    }),
-                })
+                      text: new Text({
+                          text: farmName,
+                          font: "normal 18px 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({
@@ -146,7 +150,7 @@ class DistributionLayer {
             source: this.clusterSource,
             style: (f) => {
                 // 判断是否为聚合点
-                const features = f.get('features');
+                const features = f.get("features");
                 if (features && features.length > 1) {
                     // 聚合点:使用第一个点的样式,但添加数量标识
                     const firstFeature = features[0];
@@ -157,7 +161,46 @@ class DistributionLayer {
                 return getPointStyle(singleFeature, false, 1);
             },
         });
-        this.kmap.addLayer(this.distributionPointLayer.layer)
+        this.kmap.addLayer(this.distributionPointLayer.layer);
+
+        this.areaLayer = new KMap.VectorLayer("areaLayer", 9999, {
+            minZoom: 2,
+            style: (f) => {
+                const styles = [];
+                const baseStyle = new Style({
+                    fill: new Fill({
+                        color: "rgba(59, 45, 2, 0.31)",
+                    }),
+                    stroke: new Stroke({
+                        color: "#FFD489",
+                        width: 2,
+                    }),
+                });
+                styles.push(baseStyle);
+                // 计算多边形的中心点并添加中心点图标
+                try {
+                    const geometry = f.getGeometry();
+                    const extent = geometry.getExtent();
+                    const centerX = (extent[0] + extent[2]) / 2;
+                    const centerY = (extent[1] + extent[3]) / 2;
+                    const centerCoord = [centerX, centerY];
+
+                    const centerPointStyle = new Style({
+                        geometry: new Point(centerCoord),
+                        image: new Icon({
+                            src: locationIcon,
+                            scale: 0.4,
+                            anchor: [0.5, 1],
+                        }),
+                    });
+                    styles.push(centerPointStyle);
+                } catch (error) {
+                    console.error("计算中心点失败:", error);
+                }
+                return styles;
+            },
+        });
+        this.kmap.addLayer(this.areaLayer.layer);
 
         // 设施图层:只显示图标(冷链冷库 / 加工厂等),不带边框和文字
         this.facilityPointLayer = new KMap.VectorLayer("facilityPointLayer", 100, {
@@ -175,7 +218,7 @@ class DistributionLayer {
                 });
             },
         });
-        this.kmap.addLayer(this.facilityPointLayer.layer)
+        this.kmap.addLayer(this.facilityPointLayer.layer);
 
         // 标志:是否启用农场分布点击处理
         this.enableFarmClick = false;
@@ -186,7 +229,7 @@ class DistributionLayer {
                 this.handlePointClick(event);
             }
         };
-        this.kmap.on('singleclick', this.clickHandler);
+        this.kmap.on("singleclick", this.clickHandler);
     }
 
     /**
@@ -197,6 +240,12 @@ class DistributionLayer {
         this.enableFarmClick = enable;
     }
 
+    setMapPoint(coordinate) {
+        this.clickPointLayer.source.clear();
+        let point = new Feature(new Point(coordinate));
+        this.clickPointLayer.addFeature(point);
+    }
+
     /**
      * 处理点位点击事件
      * 如果是聚合点,放大地图;如果是单个农场,打印提示
@@ -204,7 +253,7 @@ class DistributionLayer {
     handlePointClick(event) {
         const pixel = event.pixel;
         const coordinate = event.coordinate;
-        
+
         // 检查是否点击到了 distributionPointLayer
         const clickedFeature = this.kmap.map.forEachFeatureAtPixel(pixel, (feature, layer) => {
             if (layer === this.distributionPointLayer.layer) {
@@ -218,25 +267,25 @@ class DistributionLayer {
         }
 
         // 获取聚合点内的所有 features
-        const features = clickedFeature.get('features');
-        
+        const features = clickedFeature.get("features");
+
         if (features && features.length > 1) {
             // 是聚合点,放大地图
             const view = this.kmap.map.getView();
             const currentZoom = view.getZoom();
             const newZoom = Math.min(currentZoom + 2, view.getMaxZoom() || 22);
-            
+
             view.animate({
                 center: coordinate,
                 zoom: newZoom,
-                duration: 300
+                duration: 300,
             });
         } else if (features && features.length === 1) {
             // 是单个农场,通过事件总线发送事件
             const farmFeature = features[0];
             const farmData = farmFeature.getProperties();
             // 通过事件总线发送农场点击事件
-            eventBus.emit('distributionLayer:farmClick', farmData);
+            eventBus.emit("distributionLayer:farmClick", farmData);
         }
     }
 
@@ -258,10 +307,10 @@ class DistributionLayer {
         }
     }
 
-    initData(data, field = 'speciesName') {
+    initData(data, field = "speciesName") {
         // 每次加载前先清空旧数据(多用于"作物分布 / 物候期分布 / 农场分布")
         this.clear();
-        if(!data || data.length === 0) return;
+        if (!data || data.length === 0) return;
 
         for (let item of data) {
             // 面数据(区域多边形)
@@ -271,6 +320,11 @@ class DistributionLayer {
             }
         }
 
+        // 如果锁定了视图中心(有地块识别数据),则不自动适应范围,保持地块为中心
+        if (this.lockViewCenter) {
+            return;
+        }
+
         // 地块面渲染完成后,按照面要素自适应地图范围(不再根据点位来缩放)
         const extent = this.distributionLayer.source.getExtent();
         if (extent && !isNaN(extent[0])) {
@@ -281,6 +335,44 @@ class DistributionLayer {
         }
     }
 
+    clearLayer() {
+        if (this.areaLayer && this.areaLayer.layer) {
+            this.areaLayer.layer.getSource().clear();
+        }
+    }
+
+    setAreaGeometry(geometryArr) {
+        this.clearLayer();
+        let that = this;
+        const source = new VectorSource();
+
+        geometryArr.forEach((item) => {
+            const format = new WKT();
+            const mapProjection = that.kmap.map.getView().getProjection();
+            let geometry = format.readGeometry(item, {
+                dataProjection: "EPSG:4326",
+                featureProjection: mapProjection,
+            });
+            let f = new Feature({ geometry: geometry });
+            source.addFeature(f);
+        });
+
+        this.areaLayer.layer.setSource(source);
+        this.fitView();
+        // 设置地块后,锁定地图中心,防止后续数据加载改变视图
+        this.lockViewCenter = true;
+    }
+
+    fitView() {
+        const source = this.areaLayer.layer.getSource();
+        if (!source || source.getFeatures().length === 0) {
+            return;
+        }
+        let extent = source.getExtent();
+        // 地图自适应到区域可视范围
+        this.kmap.getView().fit(extent, { duration: 500, padding: [100, 100, 100, 100] });
+    }
+
     /**
      * 设施数据加载(冷链冷库 / 加工厂)
      * 只操作 facilityPointLayer,不影响作物分布图层
@@ -313,6 +405,4 @@ class DistributionLayer {
     }
 }
 
-
-
-export default DistributionLayer;
+export default DistributionLayer;

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff