Browse Source

feat:添加勾画地图逻辑和状态

wangsisi 5 days ago
parent
commit
c358e75874

+ 2 - 2
src/components/upload.vue

@@ -111,9 +111,9 @@ const afterRead = async (files) => {
     let ext = getFileExt(fileVal.name);
     let key = `birdseye-look-mini/${miniUserId}/${new Date().getTime()}.${ext}`;
     let resFilename = await uploadFileObj.put(key, fileVal)
-    file.status = "done";
-    file.message = "";
     if (resFilename) {
+      file.status = "done";
+      file.message = "";
       fileArr.value.push(props.fullPath ? base_img_url2 + resFilename : resFilename)
       imgArr.value.push(resFilename)
       eventBus.emit('upload:change', fileArr.value)

+ 108 - 13
src/views/old_mini/interactionList/drawRegion copy.vue → src/views/old_mini/interactionList/drawRegion2.vue

@@ -2,8 +2,8 @@
     <div class="edit-map">
         <custom-header :name="viewOnly ? '查看区域' : '勾画区域'"></custom-header>
         <div class="region-type-tabs">
-            <div v-for="(item,index) in regionTypeTabs" :key="item.code" class="region-type-tab"
-                :class="{ 'region-type-tab--active': activeRegionType === index }">
+            <div v-for="item in regionTypeTabs" :key="item.code" class="region-type-tab"
+                :class="{ 'region-type-tab--active': activeRegionType === item.code }">
                 {{ item.name }}
             </div>
         </div>
@@ -22,17 +22,30 @@
                 </div>
                 <div class="edit-map-footer-btn" v-if="!viewOnly">
                     <div class="btn-delete" @click="deletePolygon">删除地块</div>
-                    <div class="btn-cancel" @click="goBack">取消</div>
+                    <!-- <div class="btn-cancel" @click="goBack">取消</div> -->
                     <div class="btn-confirm" @click="confirm">确认</div>
                 </div>
             </div>
         </div>
+
+        <popup v-model:show="showAbnormalTypePopup" round closeable class="abnormal-popup"
+            :close-on-click-overlay="true">
+            <div class="abnormal-popup-content">
+                <div class="abnormal-popup-title">请选择异常类型</div>
+                <el-select v-model="selectedAbnormalType" class="abnormal-type-select" placeholder="请选择类型" size="large">
+                    <el-option v-for="item in abnormalTypeOptions" :key="item.value" :label="item.name"
+                        :value="item.value" />
+                </el-select>
+                <div class="abnormal-popup-confirm" @click="handleConfirmUpload">确认上传</div>
+            </div>
+        </popup>
     </div>
 </template>
 
 <script setup>
 import customHeader from "@/components/customHeader.vue";
 import { ref, computed, onMounted, onActivated, onDeactivated } from "vue";
+import { Popup } from "vant";
 import DrawRegionMap from "./map/drawRegionMap.js";
 import { Map as KMapMap } from "@/utils/ol-map/KMap";
 import { useRouter, useRoute } from "vue-router";
@@ -51,6 +64,18 @@ const drawRegionMap = new DrawRegionMap();
 
 const type = ref(null);
 const viewOnly = computed(() => route.query.viewOnly === "1" || route.query.viewOnly === "true");
+const showAbnormalTypePopup = ref(false);
+const selectedAbnormalType = ref("");
+const abnormalTypeOptions = [{
+    name: "病害",
+    value: "DISEASE"
+}, {
+    name: "虫害",
+    value: "PEST"
+}, {
+    name: "不确定",
+    value: "UNCERTAIN"
+}];
 
 onMounted(() => {
     type.value = route.query.type;
@@ -148,19 +173,28 @@ const regionTypeTabs = ref([]);
 const activeRegionType = ref("variety");
 const regionInfo = ref([]);
 async function fetchRegionInfo() {
-    const { data } = await VE_API.basic_farm.fetchRegionInfo({ subjectId: route.query.subjectId || 109 });
+    const { data } = await VE_API.basic_farm.fetchRegionInfo({ subjectId: localStorage.getItem('selectedFarmId') });
     if (data && data.length > 0) {
         regionInfo.value = data[0] || [];
         regionTypeTabs.value = regionInfo.value.problemZoneList || [];
         regionTypeTabs.value.unshift({ name: "品种区", code: "variety" });
-        if (data[0]?.regionList?.length) {
-            // if (!hasAppliedInitialVariety.value && route.query?.varietyId) {
-            //     activeVariety.value = resolveInitialVarietyIndex(data[0]?.regionList);
-            //     hasAppliedInitialVariety.value = true;
-            // }
-
-            // point.value = data[0].point;
-            varietyTabs.value = regionInfo.value.regionList || [];
+        // if (data[0]?.regionList?.length) {
+        //     // if (!hasAppliedInitialVariety.value && route.query?.varietyId) {
+        //         // activeVariety.value = resolveInitialVarietyIndex(data[0]?.regionList);
+        //     //     hasAppliedInitialVariety.value = true;
+        //     // }
+
+        //     // point.value = data[0].point;
+        //     varietyTabs.value = regionInfo.value.regionList || [];
+        // }
+
+        if (route.query.firstAct) {
+            activeRegionType.value = route.query.firstAct;
+            const index = regionTypeTabs.value.findIndex(item => item.code === route.query.firstAct);
+            if (index !== -1) {
+                varietyTabs.value = data[0].problemZoneList[index].children || [];
+                activeVariety.value = 0;
+            }
         }
     }
 }
@@ -376,11 +410,33 @@ const deletePolygon = () => {
     });
 };
 
-const confirm = () => {
+const saveAndBack = () => {
     const polygonData = drawRegionMap.getAreaGeometry();
     sessionStorage.setItem("drawRegionPolygonData", JSON.stringify(polygonData));
+    if (selectedAbnormalType.value) {
+        sessionStorage.setItem("drawRegionAbnormalType", selectedAbnormalType.value);
+    } else {
+        sessionStorage.removeItem("drawRegionAbnormalType");
+    }
     router.back();
 };
+
+const handleConfirmUpload = () => {
+    if (!selectedAbnormalType.value) {
+        ElMessage.warning("请选择异常类型");
+        return;
+    }
+    showAbnormalTypePopup.value = false;
+    saveAndBack();
+};
+
+const confirm = () => {
+    if (getCanonicalRegionTypeForStyles() === "ABNORMAL") {
+        showAbnormalTypePopup.value = true;
+        return;
+    }
+    saveAndBack();
+};
 </script>
 
 <style lang="scss" scoped>
@@ -514,4 +570,43 @@ const confirm = () => {
         }
     }
 }
+
+.abnormal-popup {
+    width: 100%;
+
+    ::v-deep {
+        .van-popup__close-icon {
+            color: #000;
+        }
+    }
+
+    .abnormal-popup-content {
+        background: linear-gradient(180deg, #d9ecff 0%, #ffffff 60%);
+        border-radius: 18px;
+        padding: 24px 16px;
+        box-sizing: border-box;
+        position: relative;
+
+        .abnormal-popup-title {
+            font-size: 16px;
+            margin-bottom: 12px;
+        }
+
+        .abnormal-type-select {
+            width: 100%;
+        }
+
+        .abnormal-popup-confirm {
+            margin-top: 24px;
+            border-radius: 25px;
+            padding: 8px;
+            background: #2199F8;
+            color: #fff;
+            font-size: 16px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+    }
+}
 </style>

+ 49 - 26
src/views/old_mini/interactionList/index.vue

@@ -98,7 +98,7 @@
 
                     <!-- 上传按钮 -->
                     <uploader v-if="item.imagePaths.length === 0 && item.questionStatus === 3"
-                        @click="handleUploadClick(item)" class="upload-wrap" multiple :max-count="10"
+                        @click="handleUploadClick(item)" :before-read="beforeReadUpload" class="upload-wrap" multiple :max-count="10"
                         :after-read="afterReadUpload">
                         <div class="upload-btn">
                             <el-icon>
@@ -214,9 +214,13 @@
             <span>照片上传进度</span>
             <el-progress class="upload-progress" :percentage="uploadPercentage" :stroke-width="10" :format="format" />
         </div>
-        <div class="upload-box">
+        <div
+            class="upload-box"
+            v-loading="popupImageUploadLoading"
+            element-loading-text="上传中..."
+        >
             <!-- 把已经上传成功的图片传给 upload 组件做回显,同时保持原有上传事件不变 -->
-            <upload :maxCount="10" :initImgArr="initImgArr" @handleUpload="handleUploadSuccess">
+            <upload ref="uploadRef" :maxCount="10" :initImgArr="initImgArr" @handleUpload="handleUploadSuccess">
             </upload>
         </div>
         <div class="input-box">
@@ -441,6 +445,19 @@ const urgentType = {
 const uploadFileObj = new UploadFile();
 const initImgArr = ref([]);
 const miniUserId = localStorage.getItem("MINI_USER_ID");
+const uploadRef = ref(null);
+/** 列表选择照片后,在弹窗内直传 OSS 期间,遮挡图片区域避免误操作 */
+const popupImageUploadLoading = ref(false);
+const beforeReadUpload = (file) => {
+    showUploadProgressPopup.value = true;
+    totalUploadCount.value = 0;
+    uploadedSuccessCount.value = 0;
+    initImgArr.value = [];
+    popupImageUploadLoading.value = false;
+    uploadRef.value && uploadRef.value.uploadReset();
+    return true;
+};
+
 //弹窗问题
 const afterReadUpload = async (data) => {
     // 继续上传:回显已有图片再追加新图;首次上传:直接清空,只显示本次新传
@@ -462,28 +479,32 @@ const afterReadUpload = async (data) => {
         sessionStorage.removeItem("drawRegionInteractionId");
     }
 
-    for (let file of data) {
-        // 将文件上传至服务器
-        let fileVal = file.file;
-        file.status = "uploading";
-        file.message = "上传中...";
-        let ext = getFileExt(fileVal.name);
-        let key = `birdseye-look-mini/${miniUserId}/${new Date().getTime()}.${ext}`;
-        let resFilename = await uploadFileObj.put(key, fileVal)
-        if (resFilename) {
-            file.status = "done";
-            file.message = "";
-            // 记录成功数量,用于进度条“当前成功数”
-            uploadedSuccessCount.value += 1;
-            // 记录已上传成功的图片路径,用于回显
-            initImgArr.value.push(resFilename)
-        } else {
-            file.status = 'failed';
-            file.message = '上传失败';
-            ElMessage.error('图片上传失败,请稍后再试!')
+    popupImageUploadLoading.value = true;
+    try {
+        for (let file of data) {
+            // 将文件上传至服务器
+            let fileVal = file.file;
+            file.status = "uploading";
+            file.message = "上传中...";
+            let ext = getFileExt(fileVal.name);
+            let key = `birdseye-look-mini/${miniUserId}/${new Date().getTime()}.${ext}`;
+            let resFilename = await uploadFileObj.put(key, fileVal)
+            if (resFilename) {
+                file.status = "done";
+                file.message = "";
+                // 记录成功数量,用于进度条“当前成功数”
+                uploadedSuccessCount.value += 1;
+                // 记录已上传成功的图片路径,用于回显
+                initImgArr.value.push(resFilename)
+            } else {
+                file.status = 'failed';
+                file.message = '上传失败';
+                ElMessage.error('图片上传失败,请稍后再试!')
+            }
         }
+    } finally {
+        popupImageUploadLoading.value = false;
     }
-    showUploadProgressPopup.value = true;
 
     // 所有文件上传结束后再打开进度弹窗,此时 imgArr 已包含全部图片
     if (initImgArr.value.length > 0 && currentItem.value.interactionTypeId != 1 && currentItem.value.questionStatus === 3) {
@@ -784,12 +805,12 @@ const handleDrawRegion = (item) => {
     }
 
     if (item.rangeWkt && item.rangeWkt.length > 10) {
-        router.push(`/draw_region?polygonData=${polygonData}&rangeWkt=${item.rangeWkt}&updatedTime=${item.updatedTime.slice(0, 10)}&reproductiveName=${item.reproductiveName}&mapCenter=${localStorage.getItem('selectedFarmPoint') || 'POINT (113.6142086995688 23.585836479509055)'}&firstAct=0&secondAct=0`);
+        router.push(`/draw_region?polygonData=${polygonData}&rangeWkt=${item.rangeWkt}&updatedTime=${item.updatedTime.slice(0, 10)}&reproductiveName=${item.reproductiveName}&mapCenter=${localStorage.getItem('selectedFarmPoint') || 'POINT (113.6142086995688 23.585836479509055)'}&firstAct=ABNORMAL&secondAct=0`);
     } else {
         if (polygonData) {
-            router.push(`/draw_region?polygonData=${polygonData}&mapCenter=${localStorage.getItem('selectedFarmPoint') || 'POINT (113.6142086995688 23.585836479509055)'}&firstAct=0&secondAct=0`);
+            router.push(`/draw_region?polygonData=${polygonData}&mapCenter=${localStorage.getItem('selectedFarmPoint') || 'POINT (113.6142086995688 23.585836479509055)'}&firstAct=ABNORMAL&secondAct=0`);
         } else {
-            router.push(`/draw_region?mapCenter=${localStorage.getItem('selectedFarmPoint') || 'POINT (113.6142086995688 23.585836479509055)'}&firstAct=0&secondAct=0`);
+            router.push(`/draw_region?mapCenter=${localStorage.getItem('selectedFarmPoint') || 'POINT (113.6142086995688 23.585836479509055)'}&firstAct=ABNORMAL&secondAct=0`);
         }
     }
 };
@@ -1355,6 +1376,8 @@ const handleSubmitAll = () => {
 
     .upload-box {
         margin-bottom: 12px;
+        position: relative;
+        min-height: 88px;
     }
 
     .input-box {

+ 119 - 17
src/views/old_mini/interactionList/map/drawRegionMap.js

@@ -59,6 +59,79 @@ class DrawRegionMap {
                         }),
                     });
                 }
+                if (displayMode === "lockedDisease") {
+                    const status = String(f.get("handleStatus") || "");
+                    const lockStyleType = String(f.get("lockStyleType") || "");
+                    const isControlled = lockStyleType === "controlled" || status === "3";
+                    const isGrowthTreating = lockStyleType === "growthTreating";
+                    const label = f.get("label") || (isControlled ? "病害已控制" : "病害治疗中");
+                    const updatedTime = f.get("updatedTime") || "";
+                    let areaText = "";
+                    try {
+                        let geom = f.getGeometry().clone();
+                        geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"));
+                        let area = getArea(geom);
+                        area = (area + area / 2) / 1000;
+                        areaText = `${Number(area).toFixed(2)}亩`;
+                    } catch (_) {
+                        areaText = "";
+                    }
+                    const fillColor = isControlled
+                        ? "rgba(166, 166, 166, 0.25)"
+                        : isGrowthTreating
+                            ? "rgba(255, 159, 102, 0.3)"
+                            : "rgba(191, 91, 91, 0.36)";
+                    const strokeColor = isControlled
+                        ? "#A6A6A6"
+                        : isGrowthTreating
+                            ? "rgba(255, 94, 0, 0.3)"
+                            : "rgba(224, 49, 49, 0.3)";
+                    const badgeTextColor = isControlled ? "#ffffff" : isGrowthTreating ? "#F76F00" : "#E32A28";
+                    const badgeBgColor = isControlled ? "#A6A6A6" : "#ffffff";
+                    const badgeBorderColor = isControlled ? "" : isGrowthTreating ? "#F76F00" : "#E32A28";
+                    const styles = [
+                        new Style({
+                            fill: new Fill({ color: fillColor }),
+                            stroke: new Stroke({ color: strokeColor, width: 1.5 }),
+                            text: new Text({
+                                text: label,
+                                font: "bold 13px sans-serif",
+                                fill: new Fill({ color: badgeTextColor }),
+                                backgroundFill: new Fill({ color: badgeBgColor }),
+                                backgroundStroke: badgeBorderColor
+                                    ? new Stroke({ color: badgeBorderColor, width: 1 })
+                                    : undefined,
+                                padding: [4, 10, 4, 10],
+                                offsetY: -40,
+                            }),
+                        }),
+                    ];
+                    if (updatedTime) {
+                        styles.push(
+                            new Style({
+                                text: new Text({
+                                    text: `发现时间:${updatedTime}`,
+                                    font: "12px sans-serif",
+                                    fill: new Fill({ color: "#ffffff" }),
+                                    offsetY: -16,
+                                }),
+                            })
+                        );
+                    }
+                    if (areaText) {
+                        styles.push(
+                            new Style({
+                                text: new Text({
+                                    text: areaText,
+                                    font: "16px sans-serif",
+                                    fill: new Fill({ color: "#ffffff" }),
+                                    offsetY: 14,
+                                }),
+                            })
+                        );
+                    }
+                    return styles;
+                }
 
                 const status = f.get("status"); // 'resolved' | 'unresolved'
                 const reproductiveName = f.get("reproductiveName") || "";
@@ -162,7 +235,7 @@ class DrawRegionMap {
      * @param {boolean} needFitView 是否自动缩放视图
      * @param {string|number} areaText 显示的面积(单位:亩),可选
      * @param {{ fill?: string, stroke?: string }} [readonlyAreaStyle] 只读模式下覆盖填充/描边色;不传则仍用 Map.drawStyleColors 或默认
-     * @param {{ badgeText: string, discoveryDate: string, badgeBackground?: string }} [growthOverlay] 只读模式下异常区标签(配色与勾画页一致
+     * @param {{ badgeText: string, discoveryDate: string, badgeBackground?: string }|function} [growthOverlay] 只读模式下异常区标签(可传函数按地块动态返回
      * @param {string} [polygonCenterLabel] 只读模式下覆盖多边形中心文案(原样展示,不加「亩」);用于品种查看态显示品种名等
      */
     setAreaGeometry(geometryArr, needFitView = false, areaText, readonlyAreaStyle, growthOverlay, polygonCenterLabel) {
@@ -172,7 +245,7 @@ class DrawRegionMap {
         if (!this.kmap || !this.kmap.polygonLayer || !this.kmap.polygonLayer.source) return;
 
         let that = this;
-        geometryArr.map(item => {
+        geometryArr.map((item, index) => {
             // 不使用 setLayerWkt,而是手动添加要素,避免自动缩放视图
             const format = new WKT()
             const mapProjection = that.kmap.map.getView().getProjection()
@@ -183,15 +256,39 @@ class DrawRegionMap {
             let f = new Feature({ geometry: geometry })
             // 只读模式下,为多边形单独设置样式:仅填充+边框 + 面积文本,不显示可拖动的顶点小圆点
             if (!this.editable) {
+                const resolvedGrowthOverlay =
+                    typeof growthOverlay === "function"
+                        ? growthOverlay(f, item, index)
+                        : growthOverlay;
+                const overlayStatus = String(resolvedGrowthOverlay?.handleStatus || "");
+                const overlayBadgeText = String(resolvedGrowthOverlay?.badgeText || "");
+                const overlayLockStyleType = String(resolvedGrowthOverlay?.lockStyleType || "");
+                // 只读查看态颜色与编辑态保持一致:
+                // 1) 已控制(3) 灰色;2) 长势治疗中(2) 橙色;3) 病虫害治疗中(2) 红色
+                const isControlledDisease = overlayStatus === "3";
+                const isTreatingGrowth =
+                    overlayStatus === "2" &&
+                    (overlayLockStyleType === "growthTreating" || overlayBadgeText.includes("长势"));
+                const isTreatingDisease = overlayStatus === "2" && !isTreatingGrowth;
                 // 查看模式下单块区域展示:优先 readonlyAreaStyle,其次 Map.drawStyleColors,再默认
-                const fillColor =
-                    readonlyAreaStyle?.fill ??
-                    KMap.Map?.drawStyleColors?.fill ??
-                    "rgba(0, 57, 44, 0.5)";
-                const strokeColor =
-                    readonlyAreaStyle?.stroke ??
-                    KMap.Map?.drawStyleColors?.stroke ??
-                    "#18AA8B";
+                const fillColor = isControlledDisease
+                    ? "rgba(166, 166, 166, 0.25)"
+                    : isTreatingGrowth
+                        ? "rgba(255, 159, 102, 0.3)"
+                    : isTreatingDisease
+                        ? "rgba(191, 91, 91, 0.36)"
+                    : (readonlyAreaStyle?.fill ??
+                        KMap.Map?.drawStyleColors?.fill ??
+                        "rgba(0, 57, 44, 0.5)");
+                const strokeColor = isControlledDisease
+                    ? "#A6A6A6"
+                    : isTreatingGrowth
+                        ? "rgba(255, 94, 0, 0.3)"
+                    : isTreatingDisease
+                        ? "rgba(224, 49, 49, 0.3)"
+                    : (readonlyAreaStyle?.stroke ??
+                        KMap.Map?.drawStyleColors?.stroke ??
+                        "#18AA8B");
                 const styles = [
                     new Style({
                         fill: new Fill({
@@ -233,9 +330,9 @@ class DrawRegionMap {
                     }
                 }
                 const hasGrowth =
-                    growthOverlay &&
-                    growthOverlay.badgeText &&
-                    growthOverlay.discoveryDate;
+                    resolvedGrowthOverlay &&
+                    resolvedGrowthOverlay.badgeText &&
+                    resolvedGrowthOverlay.discoveryDate;
                 if (textValue) {
                     // 品种名与 staticRegionLayer.readonlyVariety 标签视觉一致(12px + 深绿底)
                     const isVarietyNameLabel = !!trimmedCenterLabel;
@@ -265,19 +362,22 @@ class DrawRegionMap {
                     styles.push(
                         new Style({
                             text: new Text({
-                                text: growthOverlay.badgeText,
+                                text: resolvedGrowthOverlay.badgeText,
                                 font: "bold 13px sans-serif",
-                                fill: new Fill({ color: "#ffffff" }),
+                                fill: new Fill({ color: resolvedGrowthOverlay.badgeTextColor || "#ffffff" }),
                                 backgroundFill: new Fill({
-                                    color: growthOverlay.badgeBackground || "#FF7F00",
+                                    color: resolvedGrowthOverlay.badgeBackground || "#FF7F00",
                                 }),
+                                backgroundStroke: resolvedGrowthOverlay.badgeBorderColor
+                                    ? new Stroke({ color: resolvedGrowthOverlay.badgeBorderColor, width: 1 })
+                                    : undefined,
                                 padding: [4, 10, 4, 10],
                                 offsetY: -40,
                             }),
                         }),
                         new Style({
                             text: new Text({
-                                text: `发现时间:${growthOverlay.discoveryDate}`,
+                                text: `发现时间:${resolvedGrowthOverlay.discoveryDate}`,
                                 font: "12px sans-serif",
                                 fill: new Fill({ color: "#ffffff" }),
                                 offsetY: -16,
@@ -418,6 +518,8 @@ class DrawRegionMap {
                 feature.set("updatedTime", region.updatedTime);
                 feature.set("label", region.label || "");
                 feature.set("displayMode", region.displayMode || "");
+                feature.set("handleStatus", region.handleStatus);
+                feature.set("lockStyleType", region.lockStyleType || "");
                 feature.set("unresolvedBlueFill", region.unresolvedBlueFill === true);
                 this.staticRegionLayer.addFeature(feature);
             } catch (e) {

+ 4 - 6
src/views/old_mini/monitor/index.vue

@@ -106,12 +106,10 @@ const getVarietyTabs = async (isShowPopup = true) => {
         const res = await VE_API.basic_farm.fetchProblemZoneList({
             subjectId: gardenId.value,
         });
-        // const res = await VE_API.monitor.listRegionsBySubjectId({
-        //     subjectId: gardenId.value,
-        // });
-        if(res.data && res.data.regionList && res.data.problemZones){
-            varietyTabs.value = res.data.regionList.concat(res.data.problemZones)
-        }
+        // if(res.data && res.data.regionList && res.data.problemZones){
+        //     varietyTabs.value = res.data.regionList.concat(res.data.problemZones)
+        // }
+        varietyTabs.value = res.data.regionList
         if (varietyTabs.value.length > 0) {
             handleVarietyClick(varietyTabs.value[activeVariety.value || 0], activeVariety.value || 0)
             if (isShowPopup && !showSelectRegionPopup.value && agriExecutePopupRef.value) {

+ 484 - 68
src/views/old_mini/monitor/subPages/darwArea.vue

@@ -178,6 +178,13 @@ const highlightDeleteSelectedFeature = (feature) => {
     );
 };
 
+const isFeatureLockedForDelete = (feature) => {
+    if (activeRegionType.value === "variety") return false;
+    const growth = getAbnormalGrowthOverlayMeta(feature);
+    const status = String(growth?.handleStatus || "");
+    return status === "2" || status === "3";
+};
+
 const enableDeleteSelectionMode = () => {
     const map = drawRegionMap.kmap?.map;
     const polygonLayer = drawRegionMap.kmap?.polygonLayer?.layer;
@@ -195,6 +202,10 @@ const enableDeleteSelectionMode = () => {
             }
         );
         if (hitFeature) {
+            if (isFeatureLockedForDelete(hitFeature)) {
+                ElMessage.warning("该地块不可删除");
+                return;
+            }
             highlightDeleteSelectedFeature(hitFeature);
         }
     });
@@ -222,13 +233,24 @@ const handleRegionTypeClick = (item) => {
 // 删除当前地块
 const deletePolygon = () => {
     const features = drawRegionMap.kmap?.polygonLayer?.source?.getFeatures?.() || [];
+    const deletableFeatures = features.filter((f) => !isFeatureLockedForDelete(f));
     let matchedFeature = null;
     if (features.length === 0) {
         clearDeleteSelectionState();
         ElMessage.warning("当前没有可删除的地块");
         return;
     }
+    if (features.length === 1 && deletableFeatures.length === 0) {
+        clearDeleteSelectionState();
+        ElMessage.warning("当前地块不可删除");
+        return;
+    }
     if (features.length > 1) {
+        if (deletableFeatures.length === 0) {
+            clearDeleteSelectionState();
+            ElMessage.warning("当前地块不可删除");
+            return;
+        }
         if (!selectedDeleteFeature.value) {
             enableDeleteSelectionMode();
             ElMessage.warning("当前有多个地块,请先选择地块");
@@ -245,6 +267,11 @@ const deletePolygon = () => {
             ElMessage.warning("请选择地块后再删除");
             return;
         }
+        if (isFeatureLockedForDelete(matchedFeature)) {
+            clearDeleteSelectionState();
+            ElMessage.warning("该地块不可删除");
+            return;
+        }
     } else {
         clearDeleteSelectionState();
     }
@@ -267,10 +294,8 @@ const deletePolygon = () => {
 
         const polygonData = drawRegionMap.getAreaGeometry?.();
         const geometryArr = polygonData?.geometryArr || [];
-        const mergedGeom = mergePolygonWktsForApi(geometryArr);
-
         try {
-            const ok = await submitCurrentTabGeometryChange(mergedGeom);
+            const ok = await submitCurrentTabGeometryChange(geometryArr);
             if (!ok) {
                 ElMessage.error("删除后保存失败");
                 return;
@@ -313,6 +338,26 @@ const getOuterRingCoordinates = (geometry) => {
 
 const createPolygonStyleFunc = (fillColor, strokeColor) => {
     return (feature) => {
+        const growth = getAbnormalGrowthOverlayMeta(feature);
+        const isTreatingDisease =
+            String(growth?.handleStatus || "") === "2" && !String(growth?.badgeText || "").includes("长势");
+        const isTreatingGrowth =
+            String(growth?.handleStatus || "") === "2" && String(growth?.badgeText || "").includes("长势");
+        const isControlledDisease = String(growth?.handleStatus || "") === "3";
+        const currentFillColor = isControlledDisease
+            ? SLEEP_ZONE_FILL_COLOR
+            : isTreatingGrowth
+                ? "rgba(255, 159, 102, 0.3)"
+            : isTreatingDisease
+                ? DISEASE_TREATING_FILL_COLOR
+                : fillColor;
+        const currentStrokeColor = isControlledDisease
+            ? SLEEP_ZONE_STROKE_COLOR
+            : isTreatingGrowth
+                ? "rgba(255, 94, 0, 0.3)"
+            : isTreatingDisease
+                ? DISEASE_TREATING_STROKE_COLOR
+                : strokeColor;
         const styles = [];
         const ring = getOuterRingCoordinates(feature.getGeometry());
         const ringLen = ring.length;
@@ -325,7 +370,7 @@ const createPolygonStyleFunc = (fillColor, strokeColor) => {
                         image: new Circle({
                             radius: 4,
                             fill: new Fill({
-                                color: strokeColor,
+                                color: currentStrokeColor,
                             }),
                             stroke: new Stroke({
                                 color: "#fff",
@@ -350,10 +395,10 @@ const createPolygonStyleFunc = (fillColor, strokeColor) => {
         }
         const fillStyle = new Style({
             fill: new Fill({
-                color: fillColor,
+                color: currentFillColor,
             }),
             stroke: new Stroke({
-                color: strokeColor,
+                color: currentStrokeColor,
                 width: 2,
             }),
         });
@@ -361,15 +406,17 @@ const createPolygonStyleFunc = (fillColor, strokeColor) => {
         geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"));
         let area = getArea(geom);
         area = (area + area / 2) / 1000;
-        const growth = getAbnormalGrowthOverlayMeta();
         if (growth) {
             styles.push(
                 new Style({
                     text: new Text({
                         text: growth.badgeText,
                         font: "bold 13px sans-serif",
-                        fill: new Fill({ color: "#ffffff" }),
+                        fill: new Fill({ color: growth.badgeTextColor || "#ffffff" }),
                         backgroundFill: new Fill({ color: growth.badgeBackground || ABNORMAL_BADGE_BG_GROWTH }),
+                        backgroundStroke: growth.badgeBorderColor
+                            ? new Stroke({ color: growth.badgeBorderColor, width: 1 })
+                            : undefined,
                         padding: [4, 10, 4, 10],
                         offsetY: -40,
                     }),
@@ -558,6 +605,204 @@ const getGeomArrFromGeomItems = (tab) => {
         .filter((x) => isValidGeom(x));
 };
 
+const isDiseaseAbnormalTab = (tab) => {
+    const name = (tab?.problemZoneTypeName || tab?.regionName || "").toString();
+    return name.includes("病害") || name.includes("虫害");
+};
+
+const isGrowthAbnormalTab = (tab) => {
+    const name = (tab?.problemZoneTypeName || tab?.regionName || "").toString();
+    return name.includes("过慢") || name.includes("过快");
+};
+
+const hasLockedStatusItems = (tab) => {
+    const items = Array.isArray(tab?.geomItems) ? tab.geomItems : [];
+    return items.some((item) => {
+        const status = String(item?.handleStatus ?? "");
+        return status === "2" || status === "3";
+    });
+};
+
+const isLockedAbnormalTab = (tab) =>
+    isDiseaseAbnormalTab(tab) || isGrowthAbnormalTab(tab) || hasLockedStatusItems(tab);
+
+const isLockedAbnormalItem = (tab, item) => {
+    const status = String(item?.handleStatus ?? "");
+    if (!isLockedAbnormalTab(tab)) return false;
+    return status === "2" || status === "3";
+};
+
+const formatDateToYmdDot = (raw) => {
+    const sourceDate = raw ? new Date(raw) : new Date();
+    const resolvedDate = Number.isNaN(sourceDate.getTime()) ? new Date() : sourceDate;
+    const y = resolvedDate.getFullYear();
+    const m = String(resolvedDate.getMonth() + 1).padStart(2, "0");
+    const d = String(resolvedDate.getDate()).padStart(2, "0");
+    return `${y}.${m}.${d}`;
+};
+
+const formatDateToYmdDash = (raw) => {
+    const sourceDate = raw ? new Date(raw) : new Date();
+    const resolvedDate = Number.isNaN(sourceDate.getTime()) ? new Date() : sourceDate;
+    const y = resolvedDate.getFullYear();
+    const m = String(resolvedDate.getMonth() + 1).padStart(2, "0");
+    const d = String(resolvedDate.getDate()).padStart(2, "0");
+    return `${y}-${m}-${d}`;
+};
+
+const getLockedDiseaseRegions = (tab) => {
+    if (!isLockedAbnormalTab(tab)) return [];
+    const items = Array.isArray(tab?.geomItems) ? tab.geomItems : [];
+    const out = [];
+    items.forEach((item) => {
+        if (!isLockedAbnormalItem(tab, item)) return;
+        const status = String(item?.handleStatus ?? "");
+        const polygons = flattenWktToPolygonWktArray(item?.geomWkt).filter(isValidGeom);
+        polygons.forEach((geometry) => {
+            const name = (tab?.problemZoneTypeName || tab?.regionName || "").toString();
+            const isGrowth = name.includes("过慢") || name.includes("过快");
+            const prefix = name.includes("虫害")
+                ? "虫害"
+                : name.includes("病害")
+                    ? "病害"
+                    : name.includes("过快")
+                        ? "长势过快"
+                        : "长势过慢";
+            out.push({
+                geometry,
+                displayMode: "lockedDisease",
+                handleStatus: status,
+                lockStyleType: isGrowth ? "growthTreating" : status === "3" ? "controlled" : "diseaseTreating",
+                label: status === "3" ? `${prefix}已控制` : `${prefix}治疗中`,
+                updatedTime: formatDateToYmdDot(item?.createTime),
+            });
+        });
+    });
+    return out;
+};
+
+const getEditableGeomArrForCurrentTab = (tab, rawGeomArr) => {
+    if (
+        !tab ||
+        !isLockedAbnormalTab(tab) ||
+        viewOnly.value ||
+        activeRegionType.value === "variety"
+    ) {
+        return flattenGeomWktListForMap(rawGeomArr);
+    }
+    const key = draftKeyFromParts(activeRegionType.value, tab);
+    const draft = key ? regionsDraftByKey.value[key] : null;
+    const draftItems = Array.isArray(draft?.geomItems) ? draft.geomItems : [];
+    const items = draftItems.length > 0 ? draftItems : Array.isArray(tab?.geomItems) ? tab.geomItems : [];
+    const editable = [];
+    items.forEach((item) => {
+        if (isLockedAbnormalItem(tab, item)) return;
+        editable.push(...flattenWktToPolygonWktArray(item?.geomWkt));
+    });
+    const editableGeomArr = flattenGeomWktListForMap(editable);
+    if (editableGeomArr.length > 0) return editableGeomArr;
+    // 当前小类存在 2/3 锁定地块时,编辑图层保持空,避免把锁定地块误加到可编辑层
+    const hasLockedItems = items.some((item) => isLockedAbnormalItem(tab, item));
+    if (hasLockedItems) return [];
+    // 仅当无锁定地块且没有 geomItems 时,兜底使用纯几何草稿(例如新勾画未落库)
+    const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value);
+    if (isValidGeom(draftGeom)) {
+        return flattenGeomWktListForMap([String(draftGeom).trim()]);
+    }
+    return editableGeomArr;
+};
+
+// 兜底防线:若锁定地块意外进入可编辑图层,按 handleStatus(2/3) 强制移除,避免只读层与可编辑层叠加
+const purgeLockedFeaturesFromEditableLayer = (tab) => {
+    if (!isLockedAbnormalTab(tab)) return;
+    const source = drawRegionMap.kmap?.polygonLayer?.source;
+    if (!source) return;
+    const features = source.getFeatures?.() || [];
+    features.forEach((feature) => {
+        const geomItem = getGeomItemByFeature(tab, feature);
+        const status = String(geomItem?.handleStatus ?? "");
+        if (status === "2" || status === "3") {
+            source.removeFeature?.(feature);
+        }
+    });
+};
+
+/** 非品种区提交:按当前地块顺序生成 geomItems(已有项更新,新项追加) */
+const buildGeomItemsFromGeometryArr = (tab, geometryArr, remarkOverride) => {
+    const geomArr = flattenGeomWktListForMap(geometryArr || []);
+    const existingItems = Array.isArray(tab?.geomItems) ? tab.geomItems : [];
+    const nowDate = new Date();
+    const y = nowDate.getFullYear();
+    const m = String(nowDate.getMonth() + 1).padStart(2, "0");
+    const d = String(nowDate.getDate()).padStart(2, "0");
+    const defaultCreateTime = `${y}-${m}-${d}`;
+    return geomArr.map((geomWkt, idx) => {
+        const base = existingItems[idx] && typeof existingItems[idx] === "object" ? existingItems[idx] : {};
+        const baseRemark = base?.remark != null ? String(base.remark) : "";
+        const finalRemark = remarkOverride != null ? String(remarkOverride) : baseRemark;
+        const createTime =
+            base?.createTime != null && String(base.createTime).trim() !== ""
+                ? String(base.createTime)
+                : defaultCreateTime;
+        const handleStatus =
+            base?.handleStatus != null && String(base.handleStatus).trim() !== ""
+                ? base.handleStatus
+                : 1;
+        return {
+            ...base,
+            geomWkt,
+            remark: finalRemark,
+            createTime,
+            handleStatus,
+        };
+    });
+};
+
+/** 病害编辑态提交:2/3 保持只读原样,1 与新勾画项按当前可编辑地块更新/追加 */
+const buildDiseaseGeomItemsForSubmit = (tab, editableGeometryArr, remarkOverride) => {
+    const items = Array.isArray(tab?.geomItems) ? tab.geomItems : [];
+    const lockedItems = items
+        .filter((item) => {
+            const status = String(item?.handleStatus ?? "");
+            return status === "2" || status === "3";
+        })
+        .map((item) => ({
+            ...item,
+            geomWkt: isValidGeom(item?.geomWkt) ? String(item.geomWkt).trim() : "",
+            remark: item?.remark != null ? String(item.remark) : "",
+        }))
+        .filter((item) => isValidGeom(item.geomWkt));
+    const editableBaseItems = items.filter((item) => {
+        const status = String(item?.handleStatus ?? "");
+        return status === "1" || status === "";
+    });
+    const geomArr = flattenGeomWktListForMap(editableGeometryArr || []);
+    const editableItems = geomArr.map((geomWkt, idx) => {
+        const base =
+            editableBaseItems[idx] && typeof editableBaseItems[idx] === "object"
+                ? editableBaseItems[idx]
+                : {};
+        const baseRemark = base?.remark != null ? String(base.remark) : "";
+        const finalRemark = remarkOverride != null ? String(remarkOverride) : baseRemark;
+        const createTime =
+            base?.createTime != null && String(base.createTime).trim() !== ""
+                ? String(base.createTime)
+                : formatDateToYmdDot();
+        const handleStatus =
+            base?.handleStatus != null && String(base.handleStatus).trim() !== ""
+                ? String(base.handleStatus)
+                : 1;
+        return {
+            ...base,
+            geomWkt,
+            remark: finalRemark,
+            createTime: formatDateToYmdDash(createTime),
+            handleStatus,
+        };
+    });
+    return [...lockedItems, ...editableItems];
+};
+
 const draftKeyFromParts = (majorType, tabLike) => {
     if (!majorType || !tabLike || typeof tabLike !== "object") return "";
     const t = tabLike;
@@ -608,6 +853,25 @@ const getReadonlyVarietyRegions = (activeIndex) => {
     return readonlyRegions;
 };
 
+const getAllVarietyReadonlyRegions = () => {
+    const list = regionInfo.value?.regionList || [];
+    if (!Array.isArray(list) || list.length === 0) return [];
+    const regions = [];
+    for (let i = 0; i < list.length; i++) {
+        const tab = list[i];
+        if (!tab) continue;
+        const draftGeom = getDraftGeomForTab("variety", tab, i);
+        const geometry = draftGeom || tab.geom;
+        if (!isValidGeom(geometry)) continue;
+        regions.push({
+            geometry,
+            label: tab.regionName || "",
+            displayMode: "readonlyVariety",
+        });
+    }
+    return regions;
+};
+
 const renderReadonlyVarietyRegions = (activeIndex) => {
     if (drawRegionMap && typeof drawRegionMap.setStatusRegions === "function") {
         drawRegionMap.setStatusRegions(getReadonlyVarietyRegions(activeIndex));
@@ -620,24 +884,11 @@ const renderReadonlyVarietyRegions = (activeIndex) => {
  */
 const renderAllVarietyRegionsReadonlyOnly = () => {
     if (!drawRegionMap || typeof drawRegionMap.setStatusRegions !== "function") return;
-    const list = regionInfo.value?.regionList || [];
-    if (!Array.isArray(list) || list.length === 0) {
+    const regions = getAllVarietyReadonlyRegions();
+    if (regions.length === 0) {
         drawRegionMap.setStatusRegions([]);
         return;
     }
-    const regions = [];
-    for (let i = 0; i < list.length; i++) {
-        const tab = list[i];
-        if (!tab) continue;
-        const draftGeom = getDraftGeomForTab("variety", tab, i);
-        const geometry = draftGeom || tab.geom;
-        if (!isValidGeom(geometry)) continue;
-        regions.push({
-            geometry,
-            label: tab.regionName || "",
-            displayMode: "readonlyVariety",
-        });
-    }
     drawRegionMap.setStatusRegions(regions);
 };
 
@@ -693,7 +944,7 @@ const handleVarietyClick = (tab, index) => {
                 if (fromItems.length) return fromItems;
                 return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
             })();
-    const geomArr = flattenGeomWktListForMap(rawGeomArr);
+    const geomArr = getEditableGeomArrForCurrentTab(tab, rawGeomArr);
     // 保留一份“当前选中地块”的原始字符串形态给其它逻辑使用
     regionGeom.value = geomArr.length > 1 ? JSON.stringify(geomArr) : geomArr[0] || "";
     // 地图尚未初始化时,仅更新状态,不做图层绘制,避免首次进入重复叠加
@@ -705,7 +956,23 @@ const handleVarietyClick = (tab, index) => {
     // 非品种区:底图显示品种区(只读绿色)+ 当前小类(按当前大类颜色渲染)
     // 品种区:显示其他品种只读 + 当前品种可编辑
     if (activeRegionType.value !== "variety") {
-        renderAllVarietyRegionsReadonlyOnly();
+        if (isLockedAbnormalTab(tab)) {
+            drawRegionMap.setStatusRegions([
+                ...getAllVarietyReadonlyRegions(),
+                ...getLockedDiseaseRegions(tab),
+            ]);
+            // 查看态下锁定异常区只展示只读层,避免与 polygonLayer 回显叠层
+            if (viewOnly.value) {
+                drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
+                pendingGeomRenderTimer.value = setTimeout(() => {
+                    drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
+                    drawRegionMap.fitAllRegions?.();
+                }, 50);
+                return;
+            }
+        } else {
+            renderAllVarietyRegionsReadonlyOnly();
+        }
     } else {
         renderReadonlyVarietyRegions(index);
     }
@@ -730,9 +997,12 @@ const handleVarietyClick = (tab, index) => {
                 false,
                 undefined,
                 undefined,
-                getAbnormalGrowthOverlayMeta(),
+                getAbnormalGrowthOverlayMeta,
                 viewOnlySubCategoryLabelWithArea || undefined
             );
+            if (!viewOnly.value && activeRegionType.value !== "variety") {
+                purgeLockedFeaturesFromEditableLayer(tab);
+            }
             // 有当前小类地块:仅 fit 当前可编辑层,避免被其它只读参考地块拉远
             drawRegionMap.fitView?.();
         }, 50);
@@ -786,33 +1056,124 @@ const getCanonicalRegionTypeForStyles = () => {
 
 const ABNORMAL_BADGE_BG_DISEASE_PEST = "#E32A28";
 const ABNORMAL_BADGE_BG_GROWTH = "#F76F00";
+const DISEASE_TREATING_FILL_COLOR = "rgba(191, 91, 91, 0.36)";
+const DISEASE_TREATING_STROKE_COLOR = "rgba(224, 49, 49, 0.3)";
+const SLEEP_ZONE_STROKE_COLOR = "#A6A6A6";
+const SLEEP_ZONE_FILL_COLOR = [166, 166, 166, 0.25];
+
+const getGeomItemByFeature = (tab, feature) => {
+    if (!tab || !feature) return null;
+    const items = Array.isArray(tab.geomItems) ? tab.geomItems : [];
+    if (!items.length) return null;
+    let featureWkt = "";
+    try {
+        featureWkt = wktGeomFormat.writeGeometry(feature.getGeometry(), WGS84_WKT_OPTS);
+    } catch (_) {
+        featureWkt = "";
+    }
+    if (!isValidGeom(featureWkt)) return null;
+    const normalizeWktKey = (wkt) => String(wkt || "").replace(/\s+/g, "").trim();
+    const normalizedFeatureWkt = normalizeWktKey(featureWkt);
+    for (const item of items) {
+        const polygons = flattenWktToPolygonWktArray(item?.geomWkt);
+        if (polygons.some((w) => normalizeWktKey(w) === normalizedFeatureWkt)) {
+            return item;
+        }
+    }
+    // 兜底:仅在要素数量与 geomItems 数量一致时按索引映射,避免病害锁定态错位
+    const features = drawRegionMap.kmap?.polygonLayer?.source?.getFeatures?.() || [];
+    if (features.length === items.length) {
+        const featureIndex = features.findIndex((f) => f === feature);
+        if (featureIndex >= 0 && featureIndex < items.length) {
+            return items[featureIndex];
+        }
+    }
+    return null;
+};
 
 /** 异常区小类(长势/病害/虫害等)闭合地块后在多边形内展示标签与发现日期(查看态 setAreaGeometry 同步使用) */
-const getAbnormalGrowthOverlayMeta = () => {
+const getAbnormalGrowthOverlayMeta = (feature) => {
     if (getCanonicalRegionTypeForStyles() !== "ABNORMAL") return null;
     const tab = varietyTabs.value?.[activeVariety.value];
     if (!tab) return null;
+    const geomItem = getGeomItemByFeature(tab, feature);
     const name = (tab.problemZoneTypeName || tab.regionName || "").toString();
+    const handleStatus = String(geomItem?.handleStatus || "");
     let badgeText = "";
     let badgeBackground = ABNORMAL_BADGE_BG_GROWTH;
-    if (name.includes("病害")) {
-        badgeText = "新增病害";
-        badgeBackground = ABNORMAL_BADGE_BG_DISEASE_PEST;
-    } else if (name.includes("虫害")) {
-        badgeText = "新增虫害";
-        badgeBackground = ABNORMAL_BADGE_BG_DISEASE_PEST;
+    let badgeTextColor = "#ffffff";
+    let badgeBorderColor = "";
+    if (name.includes("病害") || name.includes("虫害")) {
+        // 病害/虫害:严格按当前地块对应 geomItem;未命中(新勾画)按“新增”展示,避免误用旧地块状态
+        const currentGeomItem = geomItem || null;
+        const currentHandleStatus = String(currentGeomItem?.handleStatus || "");
+        const currentCreateTime = currentGeomItem?.createTime;
+        const prefix = name.includes("虫害") ? "虫害" : "病害";
+        if (!currentGeomItem) {
+            badgeText = `新增${prefix}`;
+            badgeBackground = ABNORMAL_BADGE_BG_DISEASE_PEST;
+        } else if (currentHandleStatus === "2") {
+            badgeText = `${prefix}治疗中`;
+            badgeTextColor = ABNORMAL_BADGE_BG_DISEASE_PEST;
+            badgeBackground = "#fff";
+            badgeBorderColor = ABNORMAL_BADGE_BG_DISEASE_PEST;
+        } else if (currentHandleStatus == "3") {
+            badgeText = `${prefix}已控制`;
+            // 已控制态与休眠区视觉保持一致
+            badgeBackground = SLEEP_ZONE_STROKE_COLOR;
+        } else {
+            badgeText = `新增${prefix}`;
+            badgeBackground = ABNORMAL_BADGE_BG_DISEASE_PEST;
+        }
+        const rawCreateTime = currentCreateTime || tab?.createTime;
+        const sourceDate = rawCreateTime ? new Date(rawCreateTime) : new Date();
+        const resolvedDate = Number.isNaN(sourceDate.getTime()) ? new Date() : sourceDate;
+        const y = resolvedDate.getFullYear();
+        const m = String(resolvedDate.getMonth() + 1).padStart(2, "0");
+        const d = String(resolvedDate.getDate()).padStart(2, "0");
+        return {
+            badgeText,
+            discoveryDate: `${y}.${m}.${d}`,
+            badgeBackground,
+            badgeTextColor,
+            badgeBorderColor,
+            handleStatus: currentHandleStatus,
+        };
     } else if (name.includes("过慢")) {
-        badgeText = "新增长势过慢";
+        if (handleStatus === "2") {
+            badgeText = "长势过慢治疗中";
+            badgeTextColor = ABNORMAL_BADGE_BG_GROWTH;
+            badgeBackground = "#fff";
+            badgeBorderColor = ABNORMAL_BADGE_BG_GROWTH;
+        } else {
+            badgeText = "新增长势过慢";
+        }
     } else if (name.includes("过快")) {
-        badgeText = "新增长势过快";
+        if (handleStatus === "2") {
+            badgeText = "长势过快治疗中";
+            badgeTextColor = ABNORMAL_BADGE_BG_GROWTH;
+            badgeBackground = "#fff";
+            badgeBorderColor = ABNORMAL_BADGE_BG_GROWTH;
+        } else {
+            badgeText = "新增长势过快";
+        }
     } else {
         return null;
     }
-    const now = new Date();
-    const y = now.getFullYear();
-    const m = String(now.getMonth() + 1).padStart(2, "0");
-    const d = String(now.getDate()).padStart(2, "0");
-    return { badgeText, discoveryDate: `${y}.${m}.${d}`, badgeBackground };
+    const rawCreateTime = geomItem?.createTime || tab?.createTime;
+    const sourceDate = rawCreateTime ? new Date(rawCreateTime) : new Date();
+    const resolvedDate = Number.isNaN(sourceDate.getTime()) ? new Date() : sourceDate;
+    const y = resolvedDate.getFullYear();
+    const m = String(resolvedDate.getMonth() + 1).padStart(2, "0");
+    const d = String(resolvedDate.getDate()).padStart(2, "0");
+    return {
+        badgeText,
+        discoveryDate: `${y}.${m}.${d}`,
+        badgeBackground,
+        badgeTextColor,
+        badgeBorderColor,
+        handleStatus,
+    };
 };
 
 const findProblemZoneGroupByDrawType = (majorType) => {
@@ -885,17 +1246,34 @@ const buildProblemZoneListForSubmit = () => {
                 if (!canonical) return null;
                 const k = draftKeyFromParts(canonical, child);
                 const d = k ? regionsDraftByKey.value[k] : null;
-                const geomWkt = d?.geom != null ? String(d.geom).trim() : "";
-                if (!isValidGeom(geomWkt)) return null;
-                const remark =
-                    d && "remark" in d && d.remark != null ? String(d.remark) : "";
+                let geomItems = Array.isArray(d?.geomItems)
+                    ? d.geomItems
+                    : [];
+                geomItems = geomItems
+                    .map((x) => {
+                        const geomWkt = x?.geomWkt != null ? String(x.geomWkt).trim() : "";
+                        if (!isValidGeom(geomWkt)) return null;
+                        return {
+                            ...x,
+                            geomWkt,
+                            remark: x?.remark != null ? String(x.remark) : "",
+                        };
+                    })
+                    .filter(Boolean);
+                if (geomItems.length === 0) {
+                    const geomWkt = d?.geom != null ? String(d.geom).trim() : "";
+                    if (!isValidGeom(geomWkt)) return null;
+                    const remark =
+                        d && "remark" in d && d.remark != null ? String(d.remark) : "";
+                    geomItems = [{ geomWkt, remark }];
+                }
                 return {
                     farmSubjectId: subjectId,
                     problemZoneTypeId: child.problemZoneTypeId ?? child.typeId ?? "",
                     problemZoneTypeName: child.problemZoneTypeName ?? child.regionName ?? "",
                     parentName: group.name ?? "",
                     parentCode: group.code ?? "",
-                    geomItems: [{ geomWkt, remark }],
+                    geomItems,
                 };
             })
             .filter(Boolean);
@@ -1019,14 +1397,24 @@ const resetPolygon = () => {
                 if (fromItems.length) return fromItems;
                 return isValidGeom(currentTab?.geom) ? [String(currentTab.geom).trim()] : [];
             })();
-            const initialGeomArr = flattenGeomWktListForMap(initialGeomArrRaw);
+            const initialGeomArr = getEditableGeomArrForCurrentTab(currentTab, initialGeomArrRaw);
+            if (
+                !viewOnly.value &&
+                activeRegionType.value !== "variety" &&
+                isLockedAbnormalTab(currentTab)
+            ) {
+                drawRegionMap.setStatusRegions([
+                    ...getAllVarietyReadonlyRegions(),
+                    ...getLockedDiseaseRegions(currentTab),
+                ]);
+            }
             if (initialGeomArr.length > 0) {
                 drawRegionMap.setAreaGeometry(
                     initialGeomArr,
                     false,
                     undefined,
                     undefined,
-                    getAbnormalGrowthOverlayMeta()
+                    getAbnormalGrowthOverlayMeta
                 );
                 regionGeom.value =
                     initialGeomArr.length > 1 ? JSON.stringify(initialGeomArr) : initialGeomArr[0];
@@ -1088,10 +1476,15 @@ const submitRegions = async () => {
     }
 };
 
-const submitCurrentTabGeometryChange = async (geomWkt) => {
+const submitCurrentTabGeometryChange = async (geomWktOrArr) => {
     const tab = varietyTabs.value?.[activeVariety.value];
     if (!tab) return false;
-    const normalizedGeom = isValidGeom(geomWkt) ? String(geomWkt).trim() : "";
+    const geometryArr = Array.isArray(geomWktOrArr)
+        ? geomWktOrArr
+        : isValidGeom(geomWktOrArr)
+            ? [String(geomWktOrArr).trim()]
+            : [];
+    const normalizedGeom = mergePolygonWktsForApi(geometryArr);
     const params = {
         subjectId: route.query.subjectId,
         regionList: [],
@@ -1110,7 +1503,9 @@ const submitCurrentTabGeometryChange = async (geomWkt) => {
     } else {
         const group = findProblemZoneGroupByDrawType(activeRegionType.value);
         if (!group) return false;
-        const remark = tab?.geomItems?.[0]?.remark || "";
+        const geomItems = isLockedAbnormalTab(tab)
+            ? buildDiseaseGeomItemsForSubmit(tab, geometryArr)
+            : buildGeomItemsFromGeometryArr(tab, geometryArr);
         params.problemZoneList = [
             {
                 name: group.name,
@@ -1122,7 +1517,7 @@ const submitCurrentTabGeometryChange = async (geomWkt) => {
                         problemZoneTypeName: tab.problemZoneTypeName ?? tab.regionName ?? "",
                         parentName: group.name ?? "",
                         parentCode: group.code ?? "",
-                        geomItems: normalizedGeom ? [{ geomWkt: normalizedGeom, remark: String(remark) }] : [],
+                        geomItems,
                     },
                 ],
             },
@@ -1149,8 +1544,8 @@ const confirmArea = async (drawMeta) => {
         ElMessage.warning("请先勾画地块后再确认");
         return;
     }
-    const geom = mergePolygonWktsForApi(geometryArr);
-    if (!isValidGeom(geom)) {
+    const normalizedGeomArr = flattenGeomWktListForMap(geometryArr);
+    if (!Array.isArray(normalizedGeomArr) || normalizedGeomArr.length === 0) {
         ElMessage.warning("地块几何无效,请重新勾画");
         return;
     }
@@ -1159,15 +1554,23 @@ const confirmArea = async (drawMeta) => {
         ElMessage.warning("无法识别勾画类别,请重新选择");
         return;
     }
-    regionsDraftByKey.value[key] = {
-        geomArr: geometryArr,
+    const geom = mergePolygonWktsForApi(normalizedGeomArr);
+    const currentTab = varietyTabs.value?.[activeVariety.value];
+    const nextDraft = {
+        geomArr: normalizedGeomArr,
         geom,
         remark: meta.remark != null ? String(meta.remark) : "",
     };
+    if (meta.type !== "variety") {
+        nextDraft.geomItems = isLockedAbnormalTab(currentTab)
+            ? buildDiseaseGeomItemsForSubmit(currentTab, normalizedGeomArr, meta.remark)
+            : buildGeomItemsFromGeometryArr(currentTab, normalizedGeomArr, meta.remark);
+    }
+    regionsDraftByKey.value[key] = nextDraft;
     const tabList = getTabsForMajorType(meta.type);
     const idx = findTabIndexForCategory(tabList, meta.category);
     if (activeRegionType.value === meta.type && idx >= 0) {
-        regionsDraftByIndex.value[idx] = { geomArr: geometryArr, geom };
+        regionsDraftByIndex.value[idx] = { geomArr: normalizedGeomArr, geom };
     }
 
     const ok = await submitRegions();
@@ -1270,17 +1673,29 @@ const handleEditRegion = async () => {
             applyRegionStyles();
             // 清空当前可编辑图层,避免残留
             drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
-            renderAllVarietyRegionsReadonlyOnly();
-
             const tab = varietyTabs.value?.[activeVariety.value];
+            if (isLockedAbnormalTab(tab)) {
+                drawRegionMap.setStatusRegions([
+                    ...getAllVarietyReadonlyRegions(),
+                    ...getLockedDiseaseRegions(tab),
+                ]);
+            } else {
+                renderAllVarietyRegionsReadonlyOnly();
+            }
+
             // 进入编辑时:缩放到当前要编辑的地块
-            const currentGeomStr = tab
-                ? (getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value) || tab.geom)
-                : "";
-            const currentGeomArrRaw = isValidGeom(currentGeomStr)
-                ? [String(currentGeomStr).trim()]
-                : getGeomArrFromGeomItems(tab);
-            const currentGeomArr = flattenGeomWktListForMap(currentGeomArrRaw);
+            // 异常锁定小类(2/3)进入编辑态时,禁止从 tab.geom / draftGeom 回填到可编辑层,避免与只读层重叠
+            const currentGeomArrRaw = isLockedAbnormalTab(tab)
+                ? getGeomArrFromGeomItems(tab)
+                : (() => {
+                    const currentGeomStr = tab
+                        ? (getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value) || tab.geom)
+                        : "";
+                    return isValidGeom(currentGeomStr)
+                        ? [String(currentGeomStr).trim()]
+                        : getGeomArrFromGeomItems(tab);
+                })();
+            const currentGeomArr = getEditableGeomArrForCurrentTab(tab, currentGeomArrRaw);
             regionGeom.value =
                 currentGeomArr.length > 1 ? JSON.stringify(currentGeomArr) : currentGeomArr[0] || "";
             if (currentGeomArr.length > 0) {
@@ -1290,8 +1705,9 @@ const handleEditRegion = async () => {
                     true,
                     undefined,
                     undefined,
-                    getAbnormalGrowthOverlayMeta()
+                    getAbnormalGrowthOverlayMeta
                 );
+                purgeLockedFeaturesFromEditableLayer(tab);
             } else {
                 // 仍自适应到品种区底图
                 drawRegionMap.fitAllRegions?.();
@@ -1324,7 +1740,7 @@ const handleEditRegion = async () => {
                     true,
                     undefined,
                     undefined,
-                    getAbnormalGrowthOverlayMeta()
+                    getAbnormalGrowthOverlayMeta
                 );
             }
         } else {
@@ -1337,7 +1753,7 @@ const handleEditRegion = async () => {
                     true,
                     undefined,
                     undefined,
-                    getAbnormalGrowthOverlayMeta()
+                    getAbnormalGrowthOverlayMeta
                 );
             }
         }