wangsisi 1 день назад
Родитель
Сommit
06c8f99c69

+ 30 - 22
src/components/pageComponents/ArchivesFarmTimeLine.vue

@@ -18,7 +18,7 @@
                     </div>
                     <div v-for="(r, rIdx) in Array.isArray(p.reproductiveList) ? p.reproductiveList : []"
                         :key="`reproductive-${uniqueTimestamp}-${idx}-${rIdx}`" class="reproductive-item">
-                        <div class="arranges">
+                        <div class="arranges" :class="{'arranges-min':!showPhenologyName}">
                             <div v-for="(fw, aIdx) in Array.isArray(r.farmWorkArrangeList) ? r.farmWorkArrangeList : []"
                                 :key="`arrange-${uniqueTimestamp}-${idx}-${rIdx}-${aIdx}`" class="arrange-card" :class="[
                                     getArrangeStatusClass(fw),
@@ -79,31 +79,33 @@
                                 </div>
                             </div>
                         </div>
-                        <template v-if="r.name === p.phenologyName">
-                            <div class="phenology-name text-blue"
-                                :class="{ 'phenology-red': !shouldShowBlueLeft(p, r), 'phenology-blue': shouldShowBlueLeft(p, r) }"
-                                :style="r.phenologyName === getNextPhenologyName(idx, rIdx) ? 'padding: 6px 0;' : ''">
-                                {{ r.name }}
-                            </div>
-                        </template>
-                        <template v-else>
-                            <template v-if="p.phenologyName === getNextPhenologyName(idx, rIdx)">
-                                <div class="phenology-name"
-                                    :class="{ 'text-red': !shouldShowBlueLeft(p, r), 'text-blue': shouldShowBlueLeft(p, r) }">
+                        <template v-if="showPhenologyName">
+                            <template v-if="r.name === p.phenologyName">
+                                <div class="phenology-name text-blue"
+                                    :class="{ 'phenology-red': !shouldShowBlueLeft(p, r), 'phenology-blue': shouldShowBlueLeft(p, r) }"
+                                    :style="r.phenologyName === getNextPhenologyName(idx, rIdx) ? 'padding: 6px 0;' : ''">
                                     {{ r.name }}
                                 </div>
                             </template>
                             <template v-else>
-                                <div class="phenology-name"
-                                    :class="{ 'text-red': !shouldShowBlueLeft(p, r), 'text-blue': shouldShowBlueLeft(p, r) }">
-                                    {{ r.name }}
-                                </div>
-                                <div class="phenology-name mr" :class="{
-                                    'phenology-red': !shouldShowBlueLeft(p, r),
-                                    'phenology-blue': shouldShowBlueLeft(p, r),
-                                }">
-                                    {{ p.phenologyName }}
-                                </div>
+                                <template v-if="p.phenologyName === getNextPhenologyName(idx, rIdx)">
+                                    <div class="phenology-name"
+                                        :class="{ 'text-red': !shouldShowBlueLeft(p, r), 'text-blue': shouldShowBlueLeft(p, r) }">
+                                        {{ r.name }}
+                                    </div>
+                                </template>
+                                <template v-else>
+                                    <div class="phenology-name"
+                                        :class="{ 'text-red': !shouldShowBlueLeft(p, r), 'text-blue': shouldShowBlueLeft(p, r) }">
+                                        {{ r.name }}
+                                    </div>
+                                    <div class="phenology-name mr" :class="{
+                                        'phenology-red': !shouldShowBlueLeft(p, r),
+                                        'phenology-blue': shouldShowBlueLeft(p, r),
+                                    }">
+                                        {{ p.phenologyName }}
+                                    </div>
+                                </template>
                             </template>
                         </template>
                     </div>
@@ -833,6 +835,9 @@ watch(
     { immediate: true }
 );
 
+const showPhenologyName = computed(() => {
+    return !props.problemZoneId;
+});
 watch(
     () => props.problemZoneId,
     (val, oldVal) => {
@@ -1326,6 +1331,9 @@ watch(
                 min-width: calc(100vw - 78px);
                 gap: 5px;
                 letter-spacing: 0px;
+                &.arranges-min{
+                    max-width: calc(100vw - 58px);
+                }
 
                 // min-height: 90px;
                 .arrange-card {

+ 45 - 9
src/views/old_mini/agri_record/index.vue

@@ -10,7 +10,12 @@
         <div class="archives-time-line">
             <div class="archives-time-line-header">
                 <div class="line-title" @click="handleFarmInfoClick">农事记录</div>
-                <!-- <el-date-picker style="width: 110px" v-model="date" type="year" placeholder="全部日期" /> -->
+                <div class="header-right">
+                    <div class="add-variety-btn" v-if="varietyTabs.length > 0" @click="handleAddVariety">
+                        <span>分区管理</span>
+                    </div>
+                    <el-date-picker style="width: 110px" v-model="date" type="year" placeholder="全部日期" />
+                </div>
             </div>
             <!-- 品种选择 -->
             <div class="variety-tabs" v-if="varietyTabs.length > 0">
@@ -76,6 +81,11 @@ const handleFarmInfoClick = () => {
     // });
 }
 
+const handleAddVariety = () => {
+    // router.push("/interaction?addVariety=true&subjectId=" + gardenId.value);
+    router.push(`/draw_area?subjectId=${gardenId.value}&type=viewOnly`);
+};
+
 // 品种选择(作物档案内)- 根据主体ID动态获取分区列表
 const varietyTabs = ref([]);
 const activeVariety = ref(0);
@@ -297,8 +307,12 @@ const changeGarden = ({ id }) => {
         position: relative;
         margin-top: 96px;
         height: calc(100% - 90px);
+        display: flex;
+        flex-direction: column;
+        min-height: 0;
 
         .archives-time-line-header {
+            flex-shrink: 0;
             display: flex;
             align-items: center;
             justify-content: space-between;
@@ -320,22 +334,42 @@ const changeGarden = ({ id }) => {
                     border-radius: 20px;
                 }
             }
+            .header-right {
+                display: flex;
+                align-items: center;
+                gap: 12px;
+
+                .add-variety-btn {
+                    display: flex;
+                    align-items: center;
+                    gap: 3px;
+                    padding: 4px 10px;
+                    border: 1px solid #2199F8;
+                    border-radius: 2px;
+                    color: #2199F8;
+                    background: rgba(33, 153, 248, 0.1);
+                }
+            }
         }
 
         .variety-tabs {
-            display: flex;
-            flex-wrap: wrap;
-            align-items: center;
-            align-content: flex-start;
+            display: grid;
+            grid-template-columns: repeat(4, 1fr);
             gap: 8px;
             margin: 10px 0;
 
             .variety-tab {
-                padding: 4px 12px;
+                box-sizing: border-box;
+                padding: 4px;
                 border-radius: 2px;
                 color: #767676;
                 background: #fff;
-                white-space: nowrap;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                text-align: center;
+                word-break: break-word;
+                overflow-wrap: anywhere;
             }
 
             .variety-tab--active {
@@ -344,12 +378,14 @@ const changeGarden = ({ id }) => {
             }
         }
         .archives-time-line-content {
-            margin-top: 10px;
-            height: calc(100% - 70px);
+            flex: 1;
+            min-height: 0;
             background: #fff;
             border-radius: 8px;
             padding: 10px;
             box-sizing: border-box;
+            display: flex;
+            flex-direction: column;
         }
     }
 }

+ 32 - 5
src/views/old_mini/interactionList/map/drawRegionMap.js

@@ -163,8 +163,9 @@ class DrawRegionMap {
      * @param {string|number} areaText 显示的面积(单位:亩),可选
      * @param {{ fill?: string, stroke?: string }} [readonlyAreaStyle] 只读模式下覆盖填充/描边色;不传则仍用 Map.drawStyleColors 或默认
      * @param {{ badgeText: string, discoveryDate: string, badgeBackground?: string }} [growthOverlay] 只读模式下异常区标签(配色与勾画页一致)
+     * @param {string} [polygonCenterLabel] 只读模式下覆盖多边形中心文案(原样展示,不加「亩」);用于品种查看态显示品种名等
      */
-    setAreaGeometry(geometryArr, needFitView = false, areaText, readonlyAreaStyle, growthOverlay) {
+    setAreaGeometry(geometryArr, needFitView = false, areaText, readonlyAreaStyle, growthOverlay, polygonCenterLabel) {
         // 兜底保护:geometryArr 可能为 undefined/null 或空数组
         if (!Array.isArray(geometryArr) || geometryArr.length === 0) return;
         // 地图实例或图层尚未初始化时也直接返回,避免报错
@@ -202,6 +203,11 @@ class DrawRegionMap {
                         }),
                     }),
                 ];
+                // 中心文本:优先 polygonCenterLabel(品种查看态显示名称);否则按面积规则
+                const trimmedCenterLabel =
+                    typeof polygonCenterLabel === "string" && polygonCenterLabel.trim() !== ""
+                        ? polygonCenterLabel.trim()
+                        : "";
                 // 面积文本显示规则:
                 // 1) 传空字符串:不显示
                 // 2) 传了值:优先显示传入值
@@ -209,7 +215,9 @@ class DrawRegionMap {
                 const isExplicitEmptyText = typeof areaText === "string" && areaText.trim() === "";
                 const hasProvidedAreaText = areaText !== undefined && areaText !== null;
                 let textValue = "";
-                if (!isExplicitEmptyText) {
+                if (trimmedCenterLabel) {
+                    textValue = trimmedCenterLabel;
+                } else if (!isExplicitEmptyText) {
                     if (hasProvidedAreaText) {
                         textValue = `${areaText}亩`;
                     } else {
@@ -229,12 +237,22 @@ class DrawRegionMap {
                     growthOverlay.badgeText &&
                     growthOverlay.discoveryDate;
                 if (textValue) {
+                    // 品种名与 staticRegionLayer.readonlyVariety 标签视觉一致(12px + 深绿底)
+                    const isVarietyNameLabel = !!trimmedCenterLabel;
                     styles.push(
                         new Style({
                             text: new Text({
                                 text: textValue,
-                                font: "15px sans-serif",
+                                font: isVarietyNameLabel ? "12px sans-serif" : "15px sans-serif",
                                 fill: new Fill({ color: "#ffffff" }),
+                                ...(isVarietyNameLabel
+                                    ? {
+                                          backgroundFill: new Fill({
+                                              color: "rgba(0, 57, 44, 0.85)",
+                                          }),
+                                          padding: [2, 6, 2, 6],
+                                      }
+                                    : {}),
                                 offsetY: hasGrowth ? 14 : 0,
                             }),
                         })
@@ -276,8 +294,17 @@ class DrawRegionMap {
 
 
     fitView() {
-        let extent = this.kmap.polygonLayer.source.getExtent()
-        // 地图自适应到区域可视范围
+        if (!this.kmap?.polygonLayer?.source) return;
+        const extent = this.kmap.polygonLayer.source.getExtent();
+        if (
+            !extent ||
+            !isFinite(extent[0]) ||
+            !isFinite(extent[1]) ||
+            !isFinite(extent[2]) ||
+            !isFinite(extent[3])
+        ) {
+            return;
+        }
         this.kmap.getView().fit(extent, { duration: 500, padding: [10, 10, 10, 10] });
     }
 

+ 22 - 10
src/views/old_mini/monitor/index.vue

@@ -81,7 +81,7 @@ import startInteractPopup from "@/components/popup/startInteractPopup.vue";
 
 const startInteractPopupRef = ref(null);
 const handlePage = () => {
-    router.push("/interaction_list");
+    // router.push("/interaction_list");
 }
 
 const agriExecutePopupRef = ref(null);
@@ -319,8 +319,12 @@ function handleReportClick() {
         position: relative;
         margin-top: 96px;
         height: calc(100% - 90px);
+        display: flex;
+        flex-direction: column;
+        min-height: 0;
 
         .archives-time-line-header {
+            flex-shrink: 0;
             display: flex;
             align-items: center;
             justify-content: space-between;
@@ -362,19 +366,24 @@ function handleReportClick() {
         }
 
         .variety-tabs {
-            display: flex;
-            flex-wrap: wrap;
-            align-items: center;
-            align-content: flex-start;
+            flex-shrink: 0;
+            display: grid;
+            grid-template-columns: repeat(4, 1fr);
             gap: 8px;
             margin: 10px 0;
 
             .variety-tab {
-                padding: 4px 12px;
+                box-sizing: border-box;
+                padding: 4px;
                 border-radius: 2px;
                 color: #767676;
                 background: #fff;
-                white-space: nowrap;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                text-align: center;
+                word-break: break-word;
+                overflow-wrap: anywhere;
             }
 
             .variety-tab--active {
@@ -384,12 +393,14 @@ function handleReportClick() {
         }
 
         .archives-time-line-content {
-            margin-top: 10px;
-            height: calc(100% - 70px);
+            flex: 1;
+            min-height: 0;
             background: #fff;
             border-radius: 8px;
             padding: 10px;
             box-sizing: border-box;
+            display: flex;
+            flex-direction: column;
 
             .report-box {
                 background: linear-gradient(120deg, #eef8ff, #bbe3ff);
@@ -422,7 +433,8 @@ function handleReportClick() {
             }
 
             .time-line {
-                height: 100%;
+                flex: 1;
+                min-height: 0;
             }
         }
     }

+ 156 - 19
src/views/old_mini/monitor/subPages/darwArea.vue

@@ -17,7 +17,7 @@
         <div class="edit-map-content">
             <div class="edit-map-tip" v-if="!viewOnly">操作提示:拖动圆点,即可调整地块边界</div>
             <div class="map-container" ref="mapContainer"></div>
-            <div class="edit-map-footer" :style="{ 'bottom': varietyTabs.length > 0 ? '75px' : '35px' }">
+            <div class="edit-map-footer">
                 <div class="footer-back" @click="goBack">
                     <img class="back-icon" src="@/assets/img/home/go-back.png" alt="" />
                 </div>
@@ -39,7 +39,7 @@
 
 <script setup>
 import customHeader from "@/components/customHeader.vue";
-import { ref, computed, onActivated, onDeactivated, nextTick } from "vue";
+import { ref, computed, watch, onActivated, onDeactivated, nextTick } from "vue";
 import DrawRegionMap from "../../interactionList/map/drawRegionMap.js";
 import { Map as KMapMap } from "@/utils/ol-map/KMap";
 import { useRouter, useRoute } from "vue-router";
@@ -49,7 +49,8 @@ import TipPopup from "@/components/popup/tipPopup.vue";
 import ConfirmDrawTypePopup from "@/components/popup/confirmDrawTypePopup.vue";
 import Style from "ol/style/Style";
 import { Fill, Stroke, Circle, Text } from "ol/style.js";
-import { Point } from "ol/geom";
+import { Point, Polygon, MultiPolygon } from "ol/geom";
+import WKT from "ol/format/WKT.js";
 import * as proj from "ol/proj";
 import { getArea } from "ol/sphere.js";
 
@@ -58,6 +59,15 @@ const route = useRoute();
 const mapContainer = ref(null);
 const drawRegionMap = new DrawRegionMap();
 
+/** OpenLayers 地图在容器尺寸变化后不会自动重算 canvas,需手动 updateSize */
+const resizeMapToContainer = () => {
+    nextTick(() => {
+        requestAnimationFrame(() => {
+            drawRegionMap.kmap?.map?.updateSize?.();
+        });
+    });
+};
+
 // handleVarietyClick 内部有延迟渲染(setTimeout),切换查看/编辑或重建地图时必须取消,避免旧回调在新实例上再次 addFeature 造成叠加
 const pendingGeomRenderTimer = ref(null);
 const clearPendingGeomRenderTimer = () => {
@@ -115,15 +125,28 @@ const handleRegionTypeClick = (item) => {
     }
 };
 
+/** 取 Polygon / MultiPolygon 的外环坐标(地图投影),用于顶点样式 */
+const getOuterRingCoordinates = (geometry) => {
+    if (!geometry || typeof geometry.getType !== "function") return [];
+    const type = geometry.getType();
+    const c = geometry.getCoordinates();
+    if (!c || !c.length) return [];
+    if (type === "Polygon") return c[0] || [];
+    if (type === "MultiPolygon") return c[0]?.[0] || [];
+    return [];
+};
+
 const createPolygonStyleFunc = (fillColor, strokeColor) => {
     return (feature) => {
         const styles = [];
-        const coord = feature.getGeometry().getCoordinates()[0];
-        for (let i = 0; i < coord[0].length - 1; i++) {
+        const ring = getOuterRingCoordinates(feature.getGeometry());
+        const ringLen = ring.length;
+        const vertexCount = ringLen > 1 ? ringLen - 1 : ringLen;
+        for (let i = 0; i < vertexCount; i++) {
             if (i % 2) {
                 styles.push(
                     new Style({
-                        geometry: new Point(coord[0][i]),
+                        geometry: new Point(ring[i]),
                         image: new Circle({
                             radius: 4,
                             fill: new Fill({
@@ -139,7 +162,7 @@ const createPolygonStyleFunc = (fillColor, strokeColor) => {
             } else {
                 styles.push(
                     new Style({
-                        geometry: new Point(coord[0][i]),
+                        geometry: new Point(ring[i]),
                         image: new Circle({
                             radius: 6,
                             fill: new Fill({
@@ -262,6 +285,75 @@ const isValidGeom = (geom) => {
     return true;
 };
 
+/** 与 drawRegionMap / setAreaGeometry 一致:WKT 按 WGS84 解析 */
+const WGS84_WKT_OPTS = Object.freeze({
+    dataProjection: "EPSG:4326",
+    featureProjection: "EPSG:4326",
+});
+
+const wktGeomFormat = new WKT();
+
+/** Polygon / MultiPolygon 各自对应一组环坐标(MultiPolygon 为多块) */
+const 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 [];
+};
+
+const mergePolygonWktsForApi = (wktArr) => {
+    if (!Array.isArray(wktArr) || wktArr.length === 0) return "";
+    const trimmed = wktArr.map((x) => String(x).trim()).filter(isValidGeom);
+    if (trimmed.length === 0) return "";
+    if (trimmed.length === 1) return trimmed[0];
+
+    const coordSets = [];
+    for (const w of trimmed) {
+        try {
+            const g = wktGeomFormat.readGeometry(w, WGS84_WKT_OPTS);
+            coordSets.push(...polygonCoordSetsFromGeometry(g));
+        } catch (_) {
+            /* 单条解析失败则跳过 */
+        }
+    }
+    if (coordSets.length === 0) return trimmed[0];
+    if (coordSets.length === 1) {
+        try {
+            return wktGeomFormat.writeGeometry(new Polygon(coordSets[0]), WGS84_WKT_OPTS);
+        } catch (_) {
+            return trimmed[0];
+        }
+    }
+    return wktGeomFormat.writeGeometry(new MultiPolygon(coordSets), WGS84_WKT_OPTS);
+};
+
+const flattenWktToPolygonWktArray = (wkt) => {
+    if (!isValidGeom(wkt)) return [];
+    const s = String(wkt).trim();
+    try {
+        const g = wktGeomFormat.readGeometry(s, WGS84_WKT_OPTS);
+        const sets = polygonCoordSetsFromGeometry(g);
+        if (sets.length === 0) return [s];
+        return sets.map((rings) => wktGeomFormat.writeGeometry(new Polygon(rings), WGS84_WKT_OPTS));
+    } catch (_) {
+        return [s];
+    }
+};
+
+const flattenGeomWktList = (arr) => {
+    if (!Array.isArray(arr)) return [];
+    const out = [];
+    for (const item of arr) {
+        if (!isValidGeom(item)) continue;
+        out.push(...flattenWktToPolygonWktArray(String(item).trim()));
+    }
+    return out;
+};
+
+/** 地图回显:展开 MULTIPOLYGON 为多个 POLYGON WKT,并去掉无效项 */
+const flattenGeomWktListForMap = (arr) => flattenGeomWktList(arr).filter(isValidGeom);
+
 // 从 tab.geomItems[{geomWkt}] 中提取 WKT 数组(兼容单块/多块)
 const getGeomArrFromGeomItems = (tab) => {
     const items = tab?.geomItems;
@@ -399,7 +491,7 @@ const handleVarietyClick = (tab, index) => {
     // 2) 接口返回的 tab.geomItems[].geomWkt(你给的结构)
     // 3) 兜底 tab.geom(兼容旧结构)
     const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, index);
-    const geomArr =
+    const rawGeomArr =
         isValidGeom(draftGeom)
             ? [String(draftGeom).trim()]
             : (() => {
@@ -407,6 +499,7 @@ const handleVarietyClick = (tab, index) => {
                   if (fromItems.length) return fromItems;
                   return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
               })();
+    const geomArr = flattenGeomWktListForMap(rawGeomArr);
     // 保留一份“当前选中地块”的原始字符串形态给其它逻辑使用
     regionGeom.value = geomArr.length > 1 ? JSON.stringify(geomArr) : geomArr[0] || "";
     // 地图尚未初始化时,仅更新状态,不做图层绘制,避免首次进入重复叠加
@@ -429,9 +522,25 @@ const handleVarietyClick = (tab, index) => {
         pendingGeomRenderTimer.value = setTimeout(() => {
             // 再兜底清一次,避免异步回调在 destroy/init 后仍执行导致重复追加
             drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
-            // 切换小类仅负责“显示”,不主动缩放;缩放留到点击“编辑”时触发
-            drawRegionMap.setAreaGeometry(geomArr, false, undefined, undefined, getAbnormalGrowthOverlayMeta());
-            // 需要 fit 时,确保视野包含当前渲染的所有地块(只读层 + 当前层)
+            const viewOnlyVarietyLabel =
+                route.query.type === "viewOnly" && activeRegionType.value === "variety"
+                    ? (tab.regionName || tab.problemZoneTypeName || "").toString().trim()
+                    : "";
+            drawRegionMap.setAreaGeometry(
+                geomArr,
+                false,
+                undefined,
+                undefined,
+                getAbnormalGrowthOverlayMeta(),
+                viewOnlyVarietyLabel || undefined
+            );
+            // 有当前小类地块:仅 fit 当前可编辑层,避免被其它只读参考地块拉远
+            drawRegionMap.fitView?.();
+        }, 50);
+    } else {
+        // 当前小类无地块:视野包含品种区只读底图(与其它参考地块联合范围)
+        pendingGeomRenderTimer.value = setTimeout(() => {
+            drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
             drawRegionMap.fitAllRegions?.();
         }, 50);
     }
@@ -648,10 +757,20 @@ onActivated(async () => {
     if (!viewOnly.value) {
         drawRegionMap.setMapPosition(convertPointToArray(point.value));
     }
+
+    resizeMapToContainer();
 });
 
+watch(
+    () => [varietyTabs.value.length, activeRegionType.value, activeVariety.value],
+    () => {
+        if (drawRegionMap.kmap?.map) resizeMapToContainer();
+    }
+);
+
 onDeactivated(() => {
     activeVariety.value = 0;
+    activeRegionType.value = "variety";
     clearPendingGeomRenderTimer();
     // 离开页面时中止未完成绘制并销毁地图,确保下次进入是干净实例
     drawRegionMap.abortOngoingDrawSketch?.();
@@ -689,8 +808,9 @@ const resetPolygon = () => {
                 try {
                     const parsed = JSON.parse(polygonDataStr);
                     if (parsed && Array.isArray(parsed.geometryArr) && parsed.geometryArr.length) {
+                        const resetGeomArr = flattenGeomWktListForMap(parsed.geometryArr);
                         drawRegionMap.setAreaGeometry(
-                            parsed.geometryArr,
+                            resetGeomArr.length ? resetGeomArr : parsed.geometryArr,
                             false,
                             undefined,
                             undefined,
@@ -766,13 +886,16 @@ const confirmArea = async (drawMeta) => {
         return;
     }
     const polygonData = drawRegionMap.getAreaGeometry?.();
-    console.log(polygonData, 'polygonData');
     const geometryArr = polygonData?.geometryArr;
     if (!Array.isArray(geometryArr) || geometryArr.length === 0) {
         ElMessage.warning("请先勾画地块后再确认");
         return;
     }
-    const geom = geometryArr.length === 1 ? geometryArr[0] : JSON.stringify(geometryArr);
+    const geom = mergePolygonWktsForApi(geometryArr);
+    if (!isValidGeom(geom)) {
+        ElMessage.warning("地块几何无效,请重新勾画");
+        return;
+    }
     const key = draftKeyFromParts(meta.type, meta.category);
     if (!key) {
         ElMessage.warning("无法识别勾画类别,请重新选择");
@@ -895,9 +1018,10 @@ const handleEditRegion = async () => {
             const currentGeomStr = tab
                 ? (getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value) || tab.geom)
                 : "";
-            const currentGeomArr = isValidGeom(currentGeomStr)
+            const currentGeomArrRaw = isValidGeom(currentGeomStr)
                 ? [String(currentGeomStr).trim()]
                 : getGeomArrFromGeomItems(tab);
+            const currentGeomArr = flattenGeomWktListForMap(currentGeomArrRaw);
             regionGeom.value =
                 currentGeomArr.length > 1 ? JSON.stringify(currentGeomArr) : currentGeomArr[0] || "";
             if (currentGeomArr.length > 0) {
@@ -925,7 +1049,7 @@ const handleEditRegion = async () => {
             handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
             const tab = varietyTabs.value[activeVariety.value];
             const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value);
-            const geomArr =
+            const rawGeomArr =
                 isValidGeom(draftGeom)
                     ? [String(draftGeom).trim()]
                     : (() => {
@@ -933,6 +1057,7 @@ const handleEditRegion = async () => {
                           if (fromItems.length) return fromItems;
                           return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
                       })();
+            const geomArr = flattenGeomWktListForMap(rawGeomArr);
             drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
             if (geomArr.length > 0) {
                 drawRegionMap.setAreaGeometry(
@@ -978,8 +1103,16 @@ const handleTipConfirm = () => {
     width: 100%;
     height: 100vh;
     overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    box-sizing: border-box;
+
+    & > :first-child {
+        flex-shrink: 0;
+    }
 
     .region-type-tabs {
+        flex-shrink: 0;
         display: flex;
         align-items: center;
         background: #f4f4f4;
@@ -1003,6 +1136,7 @@ const handleTipConfirm = () => {
     }
 
     .variety-tabs {
+        flex-shrink: 0;
         display: flex;
         align-items: center;
         gap: 8px;
@@ -1031,9 +1165,12 @@ const handleTipConfirm = () => {
     .edit-map-content {
         width: 100%;
         margin-top: 10px;
-        height: calc(100% - 162px);
+        flex: 1;
+        min-height: 0;
         position: relative;
-
+        box-sizing: border-box;
+        /* 底部「重置 / 确认」等为 position:fixed,占高约 68px,地图区域需预留避免被盖住 */
+        padding-bottom: 68px;
         .edit-map-tip {
             position: absolute;
             top: 23px;
@@ -1053,7 +1190,7 @@ const handleTipConfirm = () => {
 
         .edit-map-footer {
             position: absolute;
-            bottom: 35px;
+            bottom: 85px;
             left: 12px;
             width: calc(100% - 24px);
             display: flex;