فهرست منبع

feat:修改bug,对接分区管理接口

wangsisi 4 روز پیش
والد
کامیت
9e00ccd9c6

+ 15 - 0
src/api/modules/basic_farm.js

@@ -52,4 +52,19 @@ module.exports = {
         url: config.base_dev_url + "region/updateLastViewTime",
         type: "get",
     },
+    //根据农场主体ID查询其下所有农场的分区信息(品种分区 + 问题分区)
+    fetchRegionInfo: {
+        url: config.base_dev_url + "farm_subject/listRegionAndProblemZonesBySubjectId",
+        type: "get",
+    },
+    //编辑农场主体问题分区(两层菜单结构提交)
+    ediRegionZone: {
+        url: config.base_dev_url + "farm_subject/editRegionAndProblemZonesBySubjectId",
+        type: "post",
+    },
+    //保存 / 重置问题分区图片
+    saveProblemZoneImage: {
+        url: config.base_dev_url + "farm_problem_zone_image/saveByProblemZoneId",
+        type: "post",
+    }
 }

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

@@ -1510,28 +1510,6 @@ watch(
                     }
                 }
 
-                .arrange-card.status-warning-bg {
-                    border-color: #FF943D;
-
-                    .card-left {
-                        .left-info {
-                            .left-date {
-                                color: #FF943D;
-                                border-color: #FF943D;
-                            }
-                        }
-
-                        .title-text {
-                            color: #fff;
-                            background: #FF943D;;
-                        }
-                    }
-
-                    &::before {
-                        border-right-color: #FF943D;
-                    }
-                }
-
                 .arrange-card.status-act {
                     border-color: #FF953D;
 
@@ -1602,6 +1580,32 @@ watch(
                         border-right-color: #e4e4e4;
                     }
                 }
+
+                .arrange-card.status-warning-bg {
+                    border-color: #FF943D;
+
+                    .card-content {
+                        color: #000;
+                    }
+
+                    .card-left {
+                        .left-info {
+                            .left-date {
+                                color: #FF943D;
+                                border-color: #FF943D;
+                            }
+                        }
+
+                        .title-text {
+                            color: #fff;
+                            background: #FF943D;;
+                        }
+                    }
+
+                    &::before {
+                        border-right-color: #FF943D;
+                    }
+                }
             }
         }
     }

+ 242 - 14
src/components/popup/confirmDrawTypePopup.vue

@@ -17,37 +17,151 @@
         <div class="category-list">
             <div
                 v-for="(item, index) in currentCategoryOptions"
-                :key="index"
+                :key="`${selectedType}-${index}`"
                 class="category-item"
-                :class="{ 'category-item--active': selectedCategory === index }"
+                :class="{ 'category-item--active': selectedCategory === item }"
                 @click="selectedCategory = item"
             >
-                {{ item.regionName }}
+                {{ item.regionName || item.problemZoneTypeName }}
             </div>
         </div>
 
-        <textarea v-model="remark" class="remark-input" maxlength="200" placeholder="添加备注"></textarea>
+        <textarea v-if="selectedType !== 'abnormal'" v-model="remark" class="remark-input" maxlength="200"
+            placeholder="添加备注"></textarea>
 
         <div class="confirm-btn" @click="handleConfirm">确认</div>
     </popup>
+
+    <!-- 异常问题区:点击确认后弹出的照片上传进度弹窗 -->
+    <popup v-model:show="showUploadProgressPopup" round class="upload-progress-popup" teleport="body">
+        <div class="upload-progress-title">
+            <span>{{ uploadPromptText }}</span>
+            <el-progress class="upload-progress" :percentage="uploadPercentage" :stroke-width="10" :format="format" />
+        </div>
+        <div class="upload-box">
+            <upload :maxCount="10" :initImgArr="initImgArr" @handleUpload="handleUploadSuccess" />
+        </div>
+        <textarea v-model="remark" class="remark-input" maxlength="200" placeholder="添加备注"></textarea>
+        <div class="confirm-btn" :class="{ 'confirm-btn-loading': confirmUploadLoading }" @click="handleConfirmUpload">
+            {{ confirmUploadLoading ? '上传中...' : '确认上传' }}
+        </div>
+    </popup>
 </template>
 
 <script setup>
 import { Popup } from "vant";
 import { computed, ref } from "vue";
 import { ElMessage } from "element-plus";
+import upload from "@/components/upload.vue";
 
 const emit = defineEmits(["confirm"]);
 
 const showValue = ref(false);
 
+const normalizeCategoryItems = (arr) => {
+    const list = Array.isArray(arr)
+        ? arr
+        : arr && typeof arr === "object"
+          ? Object.values(arr)
+          : [];
+    return list.map((t) => ({
+        ...t,
+        regionName:
+            t.regionName ||
+            t.problemZoneTypeName ||
+            t.name ||
+            t.title ||
+            t.label ||
+            t.typeName ||
+            t.zoneName ||
+            "",
+    }));
+};
+
+const TAB_TYPE_VALUES = new Set(["variety", "abnormal", "environment", "sleep"]);
+
+const PROBLEM_TYPE_KEYS = ["abnormal", "environment", "sleep"];
+
+/**
+ * 接口 problemZone 分组的 code 常与前端 Tab(sleep/abnormal…)不一致,按名称与常见别名归一化
+ */
+const resolveCanonicalProblemZoneCode = (g) => {
+    if (!g) return "";
+    const code = (g.code ?? "").toString().trim();
+    const lower = code.toLowerCase();
+    const name = (g.name ?? "").toString();
+    if (name.includes("异常") || lower === "abnormal" || lower.includes("abnormal")) {
+        return "abnormal";
+    }
+    if (name.includes("环境") || lower === "environment" || lower.includes("environment")) {
+        return "environment";
+    }
+    if (
+        name.includes("休眠") ||
+        lower === "sleep" ||
+        lower.includes("sleep") ||
+        lower.includes("dormant") ||
+        lower.includes("dormancy") ||
+        lower.includes("hibern")
+    ) {
+        return "sleep";
+    }
+    return "";
+};
+
+/** 地图页选中的大类 code → 弹窗内四选一 Tab 的 value */
+const normalizeDrawerRegionType = (activeCode, problemZoneList) => {
+    if (!activeCode || activeCode === "variety") return "variety";
+    if (TAB_TYPE_VALUES.has(activeCode)) return activeCode;
+    const hit = (problemZoneList || []).find((x) => x && String(x.code) === String(activeCode));
+    if (hit) {
+        const c = resolveCanonicalProblemZoneCode(hit);
+        if (c) return c;
+    }
+    return activeCode;
+};
+
+const buildCategoryMap = (payload) => {
+    const map = {
+        variety: normalizeCategoryItems(payload.regionList),
+        abnormal: [],
+        environment: [],
+        sleep: [],
+    };
+    (payload.problemZoneList || []).forEach((g) => {
+        const canonical = resolveCanonicalProblemZoneCode(g);
+        if (!canonical || !PROBLEM_TYPE_KEYS.includes(canonical)) return;
+        const rawChildren = g.children ?? g.childList ?? g.problemZoneItemList ?? [];
+        const normalized = normalizeCategoryItems(rawChildren);
+        map[canonical] = [...map[canonical], ...normalized];
+    });
+    if (Array.isArray(payload.varietyTabs) && payload.activeRegionType) {
+        const nt = normalizeDrawerRegionType(payload.activeRegionType, payload.problemZoneList);
+        if (TAB_TYPE_VALUES.has(nt)) {
+            map[nt] = normalizeCategoryItems(payload.varietyTabs);
+        }
+    }
+    const fall = payload.fallbackProblemCategories || {};
+    PROBLEM_TYPE_KEYS.forEach((key) => {
+        if (!Array.isArray(map[key]) || map[key].length === 0) {
+            map[key] = normalizeCategoryItems(fall[key]);
+        }
+    });
+    return map;
+};
+
 const openPopup = (payload) => {
     remark.value = "";
-    categoryMap.value.variety = payload.varietyTabs;
-    categoryMap.value.environment = payload.varietyTabs;
-    categoryMap.value.sleep = payload.varietyTabs;
-    selectedType.value = payload.activeRegionType;
-    selectedCategory.value = payload.activeVarietyIndex;
+    categoryMap.value = buildCategoryMap(payload);
+    const drawerType = normalizeDrawerRegionType(
+        payload.activeRegionType,
+        payload.problemZoneList
+    );
+    selectedType.value = TAB_TYPE_VALUES.has(drawerType) ? drawerType : "variety";
+    const list = categoryMap.value[selectedType.value] || [];
+    const idx = Number(payload.activeVarietyIndex);
+    selectedCategory.value =
+        list[Number.isFinite(idx) ? idx : 0] ?? list[0] ?? null;
     showValue.value = true;
 };
 
@@ -69,7 +183,7 @@ const typeOptions = [
 
 const categoryMap = ref({
     variety: [],
-    abnormal: [{regionName: "病害"}, {regionName: "虫害"}, {regionName: "长势过快"}, {regionName: "长势过慢"}],
+    abnormal: [],
     environment: [],
     sleep: [],
 });
@@ -83,21 +197,91 @@ const currentCategoryOptions = computed(() => categoryMap.value[selectedType.val
 const handleTypeClick = (value) => {
     selectedType.value = value;
     const nextList = categoryMap.value[value] || [];
-    selectedCategory.value = nextList[0] || "";
+    selectedCategory.value = nextList[0] ?? null;
 };
 
 const handleConfirm = () => {
-    if (!selectedType.value || !selectedCategory.value) {
+    if (!selectedType.value || selectedCategory.value == null) {
         ElMessage.warning("请选择勾画类型和类别");
         return;
     }
-    emit("confirm", {
+
+    const payload = {
         type: selectedType.value,
         category: selectedCategory.value,
         remark: remark.value.trim(),
-    });
+    };
+
+    // 异常问题区:先打开上传弹窗,再发出 confirm
+    if (selectedType.value === "abnormal") {
+        pendingConfirmPayload.value = payload;
+        initImgArr.value = [];
+        uploadData.value = [];
+        totalUploadCount.value = 0;
+        uploadedSuccessCount.value = 0;
+        showUploadProgressPopup.value = true;
+        close();
+        return;
+    }
+
+    emit("confirm", payload);
     close();
 };
+
+// ---------------- 上传进度弹窗逻辑(只用于异常问题区) ----------------
+const showUploadProgressPopup = ref(false);
+const pendingConfirmPayload = ref(null);
+
+const uploadPromptText = computed(() => {
+    const c = pendingConfirmPayload.value?.category;
+    const name = c?.regionName || c?.problemZoneTypeName || c?.name || c?.title || c?.label || "";
+    return name ? `请上传 ${name} 照片` : "请上传照片";
+});
+
+const totalUploadCount = ref(0);
+const uploadedSuccessCount = ref(0);
+const uploadPercentage = computed(() => {
+    if (!totalUploadCount.value) return 0;
+    return Math.round((uploadedSuccessCount.value / totalUploadCount.value) * 100);
+});
+const format = () => {
+    if (!totalUploadCount.value) return "0/0";
+    return `${uploadedSuccessCount.value}/${totalUploadCount.value}`;
+};
+
+const initImgArr = ref([]);
+const uploadData = ref([]);
+const confirmUploadLoading = ref(false);
+
+const handleUploadSuccess = (data) => {
+    uploadData.value = data?.imgArr || [];
+    const len = (data?.imgArr && data.imgArr.length) || 0;
+    totalUploadCount.value = len;
+    uploadedSuccessCount.value = len;
+};
+
+const handleConfirmUpload = async () => {
+    if (confirmUploadLoading.value) return;
+    if (!uploadData.value || uploadData.value.length === 0) {
+        ElMessage.warning("请先上传照片");
+        return;
+    }
+    confirmUploadLoading.value = true;
+    try {
+        const payload = pendingConfirmPayload.value;
+        if (pendingConfirmPayload.value) {
+            pendingConfirmPayload.value.remark = remark.value.trim();
+        }
+        showUploadProgressPopup.value = false;
+        pendingConfirmPayload.value = null;
+        emit("confirm", {
+            ...(payload || {}),
+            images: uploadData.value, // 预留字段,父组件当前逻辑不受影响
+        });
+    } finally {
+        confirmUploadLoading.value = false;
+    }
+};
 </script>
 
 <style scoped lang="scss">
@@ -167,4 +351,48 @@ const handleConfirm = () => {
         font-size: 16px;
     }
 }
+
+.upload-progress-popup {
+    width: 100%;
+    padding: 16px 10px;
+
+    .upload-progress-title {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 12px;
+    }
+
+    .upload-box {
+        margin: 10px 0 16px;
+    }
+
+    // 上传弹窗里的备注输入框样式:保持与主弹窗输入框一致
+    .remark-input {
+        margin-bottom: 20px;
+        width: 100%;
+        height: 72px;
+        resize: none;
+        border: 1px solid #e6e6e6;
+        border-radius: 8px;
+        padding: 10px 12px;
+        box-sizing: border-box;
+        font-size: 14px;
+        color: #333;
+    }
+
+    .remark-input::placeholder {
+        color: #c9c9c9;
+    }
+
+    .confirm-btn {
+        width: 100%;
+        text-align: center;
+        color: #fff;
+        background: #2199f8;
+        border-radius: 25px;
+        padding: 10px 0;
+        font-size: 16px;
+    }
+}
 </style>

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

@@ -168,14 +168,16 @@ class DrawRegionMap {
             let f = new Feature({ geometry: geometry })
             // 只读模式下,为多边形单独设置样式:仅填充+边框 + 面积文本,不显示可拖动的顶点小圆点
             if (!this.editable) {
-                // 查看模式下单块区域展示:统一品种区样式
+                // 查看模式下单块区域展示:按当前大类颜色展示(颜色由页面侧 applyRegionStyles 写入 Map.drawStyleColors)
+                const fillColor = KMap.Map?.drawStyleColors?.fill ?? "rgba(0, 57, 44, 0.5)";
+                const strokeColor = KMap.Map?.drawStyleColors?.stroke ?? "#18AA8B";
                 const styles = [
                     new Style({
                         fill: new Fill({
-                            color: "rgba(0, 57, 44, 0.5)",
+                            color: fillColor,
                         }),
                         stroke: new Stroke({
-                            color: "#18AA8B",
+                            color: strokeColor,
                             width: 2,
                         }),
                     }),

+ 13 - 6
src/views/old_mini/monitor/subPages/agriculturalDetail.vue

@@ -18,10 +18,10 @@
                     <div class="ratio-tip">{{ route.query.content }}</div>
                     <div class="photo-grid">
                         <div
-                            v-for="photo in imgInfo.imageList"
+                            v-for="(photo, index) in imgInfo.imageList"
                             :key="photo.id"
                             class="photo-item"
-                            @click="handlePhotoClick(photo.url)"
+                            @click="handlePhotoClick(index)"
                         >
                             <img :src="photo.url" alt="农情照片" />
                         </div>
@@ -34,16 +34,23 @@
 
 <script setup>
 import { useRoute } from "vue-router";
-import { ref, onMounted } from "vue";
+import { ref, onMounted, computed } from "vue";
 import customHeader from "@/components/customHeader.vue";
 import { showImagePreview } from 'vant';
 
 const route = useRoute();
 
-const handlePhotoClick = (photo) => {
+const previewImages = computed(() => {
+    const list = imgInfo.value?.imageList || [];
+    return list.map((item) => item?.url).filter(Boolean);
+});
+
+const handlePhotoClick = (index) => {
+    const images = previewImages.value;
+    const safeIndex = Math.min(Math.max(Number(index) || 0, 0), Math.max(images.length - 1, 0));
     showImagePreview({
-        images: [photo],
-        startPosition: 0,
+        images,
+        startPosition: safeIndex,
         closeable: true,
         showIndex: true,
     });

+ 403 - 106
src/views/old_mini/monitor/subPages/darwArea.vue

@@ -2,20 +2,16 @@
     <div class="edit-map">
         <custom-header @goback="goBack" :isGoBack="true" :name="viewOnly ? '查看区域' : '勾选地块'"></custom-header>
         <div class="region-type-tabs">
-            <div
-                v-for="item in regionTypeTabs"
-                :key="item.value"
-                class="region-type-tab"
-                :class="{ 'region-type-tab--active': activeRegionType === item.value }"
-                @click="handleRegionTypeClick(item)"
-            >
-                {{ item.label }}
+            <div v-for="item in regionTypeTabs" :key="item.code" class="region-type-tab"
+                :class="{ 'region-type-tab--active': activeRegionType === item.code }"
+                @click="handleRegionTypeClick(item)">
+                {{ item.name }}
             </div>
         </div>
         <div class="variety-tabs" v-if="varietyTabs.length > 0">
             <div v-for="(v, index) in varietyTabs" :key="index" class="variety-tab"
                 :class="{ 'variety-tab--active': activeVariety === index }" @click="handleVarietyClick(v, index)">
-                {{ v.regionName }}
+                {{ v.regionName || v.problemZoneTypeName }}
             </div>
         </div>
         <div class="edit-map-content">
@@ -28,19 +24,16 @@
                 <div class="edit-map-footer-btn" :class="{ 'confirm-btn-box': viewOnly }">
                     <template v-if="!viewOnly">
                         <div class="btn-reset" @click="resetPolygon">重置区域</div>
-                        <div class="btn-confirm" @click="openConfirmDrawTypePopup">确认</div>
+                        <div class="btn-confirm" @click="openConfirmDrawTypePopup">确认区域</div>
                     </template>
                     <div v-else class="btn-confirm" @click="handleEditRegion">编辑区域</div>
                 </div>
             </div>
         </div>
         <tip-popup v-model:show="showTipPopup" type="success" text="您的农情报告已生成" text2="请查看" buttonText="点击查看"
-        @confirm="handleTipConfirm" />
+            @confirm="handleTipConfirm" />
 
-        <confirm-draw-type-popup
-            ref="confirmDrawTypePopupRef"
-            @confirm="handleConfirmDrawType"
-        />
+        <confirm-draw-type-popup ref="confirmDrawTypePopupRef" @confirm="handleConfirmDrawType" />
     </div>
 </template>
 
@@ -73,8 +66,10 @@ const selectedDrawTypeMeta = ref({
     category: "",
     remark: "",
 });
-// 每个品种(tab)对应的地块数据草稿:key 为 tab index,value 为 { geomArr, geom }
+// 每个品种(tab)对应的地块数据草稿:key 为 tab index,value 为 { geomArr, geom }(与当前大类 Tab 一致时同步)
 const regionsDraftByIndex = ref({});
+/** 按弹窗确认的「大类 + 小类」存几何与备注,避免与地图当前选中 Tab 不一致 */
+const regionsDraftByKey = ref({});
 const submitting = ref(false);
 // 仅在首次进入页面时根据路由 varietyId 定位一次,避免后续刷新覆盖当前流程进度
 const hasAppliedInitialVariety = ref(false);
@@ -84,30 +79,22 @@ const varietyTabs = ref([]);
 const activeVariety = ref(0);
 const regionGeom = ref(null);
 
-const regionTypeTabs = [
-    { label: "品种区", value: "variety" },
-    { label: "异常问题区", value: "abnormal" },
-    { label: "环境问题区", value: "environment" },
-    { label: "休眠区", value: "sleep" },
-];
-const activeRegionType = ref("variety");
-
 const categoryMap = {
     variety: [],
-    abnormal: [{regionName: "病害"}, {regionName: "虫害"}, {regionName: "长势过快"}, {regionName: "长势过慢"}],
-    environment: [{regionName: "渍水不畅"}, {regionName: "密不透风"}, {regionName: "高温灼伤"}, {regionName: "低温冻害"}],
+    abnormal: [{ regionName: "病害" }, { regionName: "虫害" }, { regionName: "长势过快" }, { regionName: "长势过慢" }],
+    environment: [{ regionName: "渍水不畅" }, { regionName: "密不透风" }, { regionName: "高温灼伤" }, { regionName: "低温冻害" }],
     sleep: [],
 };
 
 const handleRegionTypeClick = (item) => {
-    activeRegionType.value = item.value;
+    activeRegionType.value = item.code;
     activeVariety.value = 0;
-    if (item.value === "variety") {
-        varietyTabs.value = farmVarietyList.value;
+    if (item.code === "variety") {
+        varietyTabs.value = regionInfo.value.regionList || [];
     } else {
-        varietyTabs.value = categoryMap[item.value];
+        varietyTabs.value = item.children;
     }
-    // handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
+    handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
     if (drawRegionMap.kmap) {
         applyRegionStyles();
     }
@@ -192,23 +179,26 @@ const applyRegionStyles = () => {
         vertexColor = "#E03131";
         fillColor = [100, 0, 0, 0.5];
         strokeColor = "#E03131";
-    }else if (activeRegionType.value === "environment") {
+    } else if (activeRegionType.value === "environment") {
         lineColor = "#FDCF7F";
         vertexColor = "#FDCF7F";
         fillColor = [151, 96, 0, 0.5];
         strokeColor = "#FDCF7F";
-    }else{
+    } else {
         lineColor = "#A6A6A6";
         vertexColor = "#A6A6A6";
         fillColor = [166, 166, 166, 0.25];
         strokeColor = "#A6A6A6";
     }
-    
+
 
     // 勾画进行中:通过 Map.drawStyleColors 影响底层 drawStyleFunc 颜色
     KMapMap.drawStyleColors = {
         line: lineColor,
         vertex: vertexColor,
+        // 只读回显时也复用当前大类颜色(避免查看态全部变绿)
+        fill: fillColor,
+        stroke: strokeColor,
     };
 
     kmap.polygonStyle = createPolygonStyleFunc(fillColor, strokeColor);
@@ -225,6 +215,35 @@ const isValidGeom = (geom) => {
     return true;
 };
 
+// 从 tab.geomItems[{geomWkt}] 中提取 WKT 数组(兼容单块/多块)
+const getGeomArrFromGeomItems = (tab) => {
+    const items = tab?.geomItems;
+    if (!Array.isArray(items) || items.length === 0) return [];
+    return items
+        .map((x) => x?.geomWkt)
+        .filter((x) => x !== undefined && x !== null)
+        .map((x) => String(x).trim())
+        .filter((x) => isValidGeom(x));
+};
+
+const draftKeyFromParts = (majorType, tabLike) => {
+    if (!majorType || !tabLike || typeof tabLike !== "object") return "";
+    const t = tabLike;
+    const id = t.regionId ?? t.problemZoneTypeId ?? t.typeId ?? t.id;
+    if (id != null && String(id).trim() !== "") {
+        return `${majorType}:${String(id).trim()}`;
+    }
+    const nm = (t.regionName || t.problemZoneTypeName || "").toString().trim();
+    return nm ? `${majorType}:name:${nm}` : "";
+};
+
+const getDraftGeomForTab = (majorType, tab, index) => {
+    const k = draftKeyFromParts(majorType, tab);
+    const byKey = k ? regionsDraftByKey.value[k] : null;
+    if (byKey?.geom) return byKey.geom;
+    return regionsDraftByIndex.value[index]?.geom;
+};
+
 const resolveInitialVarietyIndex = (regionList) => {
     const queryVarietyId = route.query?.varietyId;
     if (!queryVarietyId || !Array.isArray(regionList) || regionList.length === 0) return 0;
@@ -245,7 +264,7 @@ const getReadonlyVarietyRegions = (activeIndex) => {
         if (i === activeIndex) continue;
         const tab = varietyTabs.value[i];
         if (!tab) continue;
-        const draftGeom = regionsDraftByIndex.value[i]?.geom;
+        const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, i);
         const geometry = draftGeom || tab.geom;
         if (!isValidGeom(geometry)) continue;
         readonlyRegions.push({
@@ -263,22 +282,72 @@ const renderReadonlyVarietyRegions = (activeIndex) => {
     }
 };
 
+/**
+ * 仅渲染「品种区」所有已有地块(只读),用于在其它问题区查看/编辑时作为底图参考。
+ * 需求:只显示品种区地块,其它问题区地块不显示,且不可编辑。
+ */
+const renderAllVarietyRegionsReadonlyOnly = () => {
+    if (!drawRegionMap || typeof drawRegionMap.setStatusRegions !== "function") return;
+    const list = regionInfo.value?.regionList || [];
+    if (!Array.isArray(list) || list.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);
+};
+
 const handleVarietyClick = (tab, index) => {
     activeVariety.value = index;
-    const currentGeom = regionsDraftByIndex.value[index]?.geom || tab.geom;
-    regionGeom.value = currentGeom;
+    // 取值优先级:
+    // 1) 草稿(regionsDraftByKey / regionsDraftByIndex)
+    // 2) 接口返回的 tab.geomItems[].geomWkt(你给的结构)
+    // 3) 兜底 tab.geom(兼容旧结构)
+    const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, index);
+    const geomArr =
+        isValidGeom(draftGeom)
+            ? [String(draftGeom).trim()]
+            : (() => {
+                  const fromItems = getGeomArrFromGeomItems(tab);
+                  if (fromItems.length) return fromItems;
+                  return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
+              })();
+    // 保留一份“当前选中地块”的原始字符串形态给其它逻辑使用
+    regionGeom.value = geomArr.length > 1 ? JSON.stringify(geomArr) : geomArr[0] || "";
     // 地图尚未初始化时,仅更新状态,不做图层绘制,避免首次进入重复叠加
     if (!drawRegionMap.kmap) return;
 
-    // // 每次切换/重绘地块前先清空“当前可编辑图层”
-    // if (drawRegionMap.kmap?.polygonLayer?.source) {
-    //     drawRegionMap.kmap.polygonLayer.source.clear(); 
-    // }
-    // 渲染当前品种之前的已勾画地块(灰色只读 + 品种名)
-    renderReadonlyVarietyRegions(index);
-    if (isValidGeom(currentGeom)) {
+    // 确保当前大类颜色样式生效(非品种区:黄色/红色/灰色;品种区:绿色)
+    applyRegionStyles();
+
+    // 非品种区:底图显示品种区(只读绿色)+ 当前小类(按当前大类颜色渲染)
+    // 品种区:显示其他品种只读 + 当前品种可编辑
+    if (activeRegionType.value !== "variety") {
+        renderAllVarietyRegionsReadonlyOnly();
+    } else {
+        renderReadonlyVarietyRegions(index);
+    }
+
+    // 切换时先清空当前可编辑图层,避免叠加
+    drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
+    if (geomArr.length > 0) {
         setTimeout(() => {
-            drawRegionMap.setAreaGeometry([currentGeom], true);
+            // 切换小类仅负责“显示”,不主动缩放;缩放留到点击“编辑”时触发
+            drawRegionMap.setAreaGeometry(geomArr, false);
+            // 需要 fit 时,确保视野包含当前渲染的所有地块(只读层 + 当前层)
+            drawRegionMap.fitAllRegions?.();
         }, 50);
     }
 };
@@ -293,7 +362,7 @@ async function fetchFarmSubjectDetail() {
 
     const { data } = await VE_API.basic_farm.fetchFarmSubjectDetail({ subjectId });
 
-    
+
     if (data?.regionList?.length) {
         if (!hasAppliedInitialVariety.value && route.query?.varietyId) {
             activeVariety.value = resolveInitialVarietyIndex(data?.regionList);
@@ -301,12 +370,147 @@ async function fetchFarmSubjectDetail() {
         }
 
         point.value = data.farmLocation;
-        farmVarietyList.value = data.regionList;
-        varietyTabs.value = data.regionList;
-        handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
+        // farmVarietyList.value = data.regionList;
+        // varietyTabs.value = data.regionList;
+        // handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
     }
 }
 
+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 });
+    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 || [];
+        }
+    }
+}
+
+const resolveProblemZoneGroupKind = (g) => {
+    if (!g) return "";
+    const code = (g.code ?? "").toString().trim().toLowerCase();
+    const name = (g.name ?? "").toString();
+    if (name.includes("异常") || code === "abnormal" || code.includes("abnormal")) return "abnormal";
+    if (name.includes("环境") || code === "environment" || code.includes("environment")) return "environment";
+    if (
+        name.includes("休眠") ||
+        code === "sleep" ||
+        code.includes("sleep") ||
+        code.includes("dormant") ||
+        code.includes("dormancy") ||
+        code.includes("hibern")
+    ) {
+        return "sleep";
+    }
+    return "";
+};
+
+const findProblemZoneGroupByDrawType = (majorType) => {
+    if (!majorType || majorType === "variety") return null;
+    const list = regionInfo.value?.problemZoneList || [];
+    return (
+        list.find((x) => resolveProblemZoneGroupKind(x) === majorType) ||
+        list.find((x) => String(x?.code) === String(majorType))
+    );
+};
+
+const getTabsForMajorType = (majorType) => {
+    if (majorType === "variety") {
+        return regionInfo.value?.regionList || [];
+    }
+    const g = findProblemZoneGroupByDrawType(majorType);
+    return g?.children || [];
+};
+
+const findTabIndexForCategory = (tabs, category) => {
+    if (!Array.isArray(tabs) || !category || typeof category !== "object") return -1;
+    const fields = ["regionId", "problemZoneTypeId", "typeId", "id"];
+    for (let i = 0; i < tabs.length; i++) {
+        const t = tabs[i];
+        for (const f of fields) {
+            const cv = category[f];
+            const tv = t[f];
+            if (cv != null && cv !== "" && tv != null && String(cv) === String(tv)) return i;
+        }
+    }
+    const cname = (category.regionName || category.problemZoneTypeName || "").toString().trim();
+    if (cname) {
+        const idx = tabs.findIndex((t) => {
+            const tn = (t.regionName || t.problemZoneTypeName || "").toString().trim();
+            return tn === cname;
+        });
+        if (idx >= 0) return idx;
+    }
+    return -1;
+};
+
+/** 仅提交本次有勾画草稿(regionsDraftByKey)的品种区,不带未勾画项 */
+const buildRegionListForSubmit = () => {
+    const list = regionInfo.value?.regionList || [];
+    const out = [];
+    list.forEach((tab) => {
+        const k = draftKeyFromParts("variety", tab);
+        const d = k ? regionsDraftByKey.value[k] : null;
+        const geom = d?.geom != null ? String(d.geom).trim() : "";
+        if (!isValidGeom(geom)) return;
+        out.push({
+            regionId: tab.regionId,
+            typeId: tab.typeId,
+            regionName: tab.regionName,
+            geom,
+        });
+    });
+    return out;
+};
+
+/** 仅包含有勾画草稿的问题分区子项;无任何子项勾画的分组整组不传 */
+const buildProblemZoneListForSubmit = () => {
+    const raw = regionInfo.value?.problemZoneList || [];
+    const subjectId = route.query.subjectId;
+    const groups = [];
+    raw.forEach((group) => {
+        const canonical = resolveProblemZoneGroupKind(group);
+        const children = (group.children || [])
+            .map((child) => {
+                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) : "";
+                return {
+                    farmSubjectId: subjectId,
+                    problemZoneTypeId: child.problemZoneTypeId ?? child.typeId ?? "",
+                    problemZoneTypeName: child.problemZoneTypeName ?? child.regionName ?? "",
+                    parentName: group.name ?? "",
+                    parentCode: group.code ?? "",
+                    geomItems: [{ geomWkt, remark }],
+                };
+            })
+            .filter(Boolean);
+        if (children.length === 0) return;
+        groups.push({
+            name: group.name,
+            code: group.code,
+            children,
+        });
+    });
+    return groups;
+};
+
 onActivated(async () => {
     activeVariety.value = 0;
     hasAppliedInitialVariety.value = false;
@@ -318,8 +522,8 @@ onActivated(async () => {
 
     type.value = route.query.type;
     const editable = !viewOnly.value;
-    await fetchFarmSubjectDetail();
-
+    // await fetchFarmSubjectDetail();
+    await fetchRegionInfo();
     drawRegionMap.initMap(point.value, mapContainer.value, editable, true, true);
     applyRegionStyles();
 
@@ -407,35 +611,22 @@ const resetPolygon = () => {
         });
 };
 
-const buildRegionsPayload = () => {
-    const regions = [];
-    varietyTabs.value.forEach((tab, index) => {
-        const draft = regionsDraftByIndex.value[index];
-        if (!draft?.geom) return;
-        regions.push({
-            regionId: tab.regionId,
-            typeId: tab.typeId,
-            regionName: tab.regionName,
-            geom: draft.geom,
-        });
-    });
-    return regions;
-};
-
 const submitRegions = async () => {
     if (submitting.value) return;
-    const regions = buildRegionsPayload();
-    if (regions.length === 0) {
+    const regionList = buildRegionListForSubmit();
+    const problemZoneList = buildProblemZoneListForSubmit();
+    if (regionList.length === 0 && problemZoneList.length === 0) {
         ElMessage.warning("请先勾画地块后再确认");
-        return;
+        return false;
     }
     submitting.value = true;
     try {
-        const res = await VE_API.basic_farm.saveBasicFarmInfoByExpertV3({
-            id: route.query.subjectId,
-            expertMiniUserId: '81881',
-            regions,
-        });
+        const params = {
+            subjectId: route.query.subjectId,
+            regionList,
+            problemZoneList,
+        };
+        const res = await VE_API.basic_farm.ediRegionZone(params);
         if (res?.code === 0) {
             return true;
         }
@@ -449,35 +640,89 @@ const submitRegions = async () => {
     }
 };
 
-const confirmArea = async () => {
-    // 先把当前品种的地块保存到草稿
+/** 以弹窗 confirm 传入的 drawMeta(大类 type + 小类 category + remark)为准写入草稿并提交 */
+const confirmArea = async (drawMeta) => {
+    const meta =
+        drawMeta && drawMeta.type != null
+            ? drawMeta
+            : selectedDrawTypeMeta.value;
+    if (!meta?.type || meta.category == null) {
+        ElMessage.warning("请先在弹窗中选择勾画类型与类别");
+        return;
+    }
     const polygonData = drawRegionMap.getAreaGeometry?.();
     const geometryArr = polygonData?.geometryArr;
     if (!Array.isArray(geometryArr) || geometryArr.length === 0) {
         ElMessage.warning("请先勾画地块后再确认");
         return;
     }
-    // geom 后端一般期望 string;若勾画多个,则序列化为 JSON 数组
     const geom = geometryArr.length === 1 ? geometryArr[0] : JSON.stringify(geometryArr);
-    regionsDraftByIndex.value[activeVariety.value] = { geomArr: geometryArr, geom };
+    const key = draftKeyFromParts(meta.type, meta.category);
+    if (!key) {
+        ElMessage.warning("无法识别勾画类别,请重新选择");
+        return;
+    }
+    regionsDraftByKey.value[key] = {
+        geomArr: geometryArr,
+        geom,
+        remark: meta.remark != null ? String(meta.remark) : "",
+    };
+    const tabList = getTabsForMajorType(meta.type);
+    const idx = findTabIndexForCategory(tabList, meta.category);
+    if (activeRegionType.value === meta.type && idx >= 0) {
+        regionsDraftByIndex.value[idx] = { geomArr: geometryArr, geom };
+    }
 
-    // 每次确认都提交接口(携带目前已确认过的所有品种 regions)
     const ok = await submitRegions();
-    await VE_API.basic_farm.updateLastViewTime({
-        regionId: varietyTabs.value[activeVariety.value].regionId,
-    });
-    fetchFarmSubjectDetail()
+    const rid = meta.category?.regionId;
+    if (rid != null && rid !== "") {
+        try {
+            await VE_API.basic_farm.updateLastViewTime({ regionId: rid });
+        } catch (_) {
+            /* 非品种区可能无 regionId,忽略 */
+        }
+    }
     if (!ok) return;
 
+    // 保存成功后:不跳走,刷新当前页并回显最新数据,并切换为查看态
+    try {
+        await fetchRegionInfo();
+
+        // 恢复当前大类下的子类 tab(fetchRegionInfo 默认会填充品种区)
+        if (activeRegionType.value === "variety") {
+            varietyTabs.value = regionInfo.value?.regionList || [];
+        } else {
+            const group = (regionTypeTabs.value || []).find(
+                (x) => x?.code === activeRegionType.value
+            );
+            varietyTabs.value = group?.children || [];
+        }
+
+        // 更新路由为查看态,确保按钮/头部状态同步
+        try {
+            router.replace({ query: { ...route.query, type: "viewOnly" } });
+        } catch (_) {
+            // 路由更新失败不影响回显
+        }
+
+        // 重新初始化地图(非编辑/查看模式),确保回显形状正确
+        if (drawRegionMap.kmap) {
+            drawRegionMap.destroyMap?.();
+        }
+        drawRegionMap.initMap(point.value, mapContainer.value, false, true, false);
+        applyRegionStyles();
+        if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
+            handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
+        }
+        drawRegionMap.fitAllRegions?.();
+    } catch (_) {
+        // 刷新失败不阻断用户继续操作,保持当前页面状态
+    }
+
     if (route.query.showTipPopup) {
         showTipPopup.value = true;
         return;
     }
-    if (route.query.targetUrl) {
-        router.replace(route.query.targetUrl);
-        return;
-    }
-    router.go(-1);
 };
 
 const openConfirmDrawTypePopup = () => {
@@ -487,22 +732,28 @@ const openConfirmDrawTypePopup = () => {
         ElMessage.warning("请先勾画地块后再确认");
         return;
     }
-    confirmDrawTypePopupRef.value.openPopup(
-        {
-            varietyTabs: farmVarietyList.value,
-            activeRegionType: activeRegionType.value,
-            activeVarietyIndex: activeVariety.value,
-        }
-    );
+    confirmDrawTypePopupRef.value.openPopup({
+        varietyTabs: varietyTabs.value,
+        regionList: regionInfo.value?.regionList ?? [],
+        problemZoneList: regionInfo.value?.problemZoneList ?? [],
+        /** 接口未返回子类时与地图页本地 categoryMap 一致,避免弹窗内切换大类无选项 */
+        fallbackProblemCategories: {
+            abnormal: categoryMap.abnormal,
+            environment: categoryMap.environment,
+            sleep: categoryMap.sleep,
+        },
+        activeRegionType: activeRegionType.value,
+        activeVarietyIndex: activeVariety.value,
+    });
 };
 
 const handleConfirmDrawType = async (payload) => {
     selectedDrawTypeMeta.value = payload;
-    await confirmArea();
+    await confirmArea(payload);
 };
 
 const handleEditRegion = () => {
-    // 从查看态切换到可勾画编辑态:移除查看标记并重建地图(editable=true
+    // 从查看态切换到可勾画编辑态:移除查看标记(所有大类共用
     const nextQuery = { ...route.query };
     delete nextQuery.type;
     delete nextQuery.viewOnly;
@@ -513,19 +764,65 @@ const handleEditRegion = () => {
             drawRegionMap.destroyMap();
         }
 
+        // 若当前不在品种区(如环境/异常/休眠),点击“编辑”进入当前问题区的编辑态:
+        // - 地图可编辑(仅编辑当前选中的问题区子项)
+        // - 只显示「品种区」地块作为底图参考(只读)
+        // - 不显示其它问题区地块
+        if (activeRegionType.value !== "variety") {
+            drawRegionMap.initMap(point.value, mapContainer.value, true, true, true);
+            applyRegionStyles();
+            // 清空当前可编辑图层,避免残留
+            drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
+            renderAllVarietyRegionsReadonlyOnly();
+
+            const tab = varietyTabs.value?.[activeVariety.value];
+            // 进入编辑时:缩放到当前要编辑的地块
+            const currentGeomStr = tab
+                ? (getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value) || tab.geom)
+                : "";
+            const currentGeomArr = isValidGeom(currentGeomStr)
+                ? [String(currentGeomStr).trim()]
+                : getGeomArrFromGeomItems(tab);
+            regionGeom.value =
+                currentGeomArr.length > 1 ? JSON.stringify(currentGeomArr) : currentGeomArr[0] || "";
+            if (currentGeomArr.length > 0) {
+                drawRegionMap.setAreaGeometry(currentGeomArr, true);
+            } else {
+                // 仍自适应到品种区底图
+                drawRegionMap.fitAllRegions?.();
+            }
+            return;
+        }
+
         drawRegionMap.initMap(point.value, mapContainer.value, true, true, true);
         applyRegionStyles();
 
-        // 切到编辑态后立即补渲“其他品种只读地块”,避免首次点击编辑时不显示
-        renderReadonlyVarietyRegions(activeVariety.value);
-
-        if (varietyTabs.value.length && regionGeom.value?.length) {
-            drawRegionMap.setAreaGeometry([regionGeom.value],true);
-        }
-        // 切到编辑态后,统一自适应到所有区域,避免画面偏移
-        if (drawRegionMap.fitAllRegions) {
-            drawRegionMap.fitAllRegions();
+        // 切到编辑态后:统一走一遍当前 tab 的点击逻辑,确保所有有地块的品种都能显示
+        if (varietyTabs.value.length && varietyTabs.value[activeVariety.value]) {
+            // 进入编辑时:让当前品种地块显示且缩放到当前编辑地块
+            handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
+            const tab = varietyTabs.value[activeVariety.value];
+            const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value);
+            const geomArr =
+                isValidGeom(draftGeom)
+                    ? [String(draftGeom).trim()]
+                    : (() => {
+                          const fromItems = getGeomArrFromGeomItems(tab);
+                          if (fromItems.length) return fromItems;
+                          return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
+                      })();
+            drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
+            if (geomArr.length > 0) {
+                drawRegionMap.setAreaGeometry(geomArr, true);
+            }
+        } else {
+            renderReadonlyVarietyRegions(activeVariety.value);
+            const fallbackGeom = regionGeom.value;
+            if (isValidGeom(fallbackGeom)) {
+                drawRegionMap.setAreaGeometry([fallbackGeom], true);
+            }
         }
+        // 编辑态下:保持缩放在当前要编辑地块,不再强制 fitAllRegions 覆盖
     });
 };