Просмотр исходного кода

feat: 对接接口,新增分区

lxf 2 дней назад
Родитель
Сommit
7a4d066e69

+ 10 - 0
src/api/modules/questionnaire.js

@@ -106,4 +106,14 @@ module.exports = {
         url: config.base_new_url + "questionnaire/add_zone_info",
         type: "post",
     },
+    // 查询分区是否有范围
+    checkZoneRange: {
+        url: config.base_new_url + "questionnaire/check_zone_polygon",
+        type: "get",
+    },
+    // 分区画范围
+    drawZoneRange: {
+        url: config.base_new_url + "questionnaire/update_zone_polygon",
+        type: "post",
+    },
 }

+ 5 - 3
src/components/popup/restorePlantingPopup.vue

@@ -112,6 +112,8 @@ import { base_img_url2 } from "@/api/config";
 
 const { t } = useI18n();
 
+const emit = defineEmits(["success"]);
+
 // ---------------------------------------------------------------------------
 // Props
 // ---------------------------------------------------------------------------
@@ -342,11 +344,9 @@ const checkShouldShow = async () => {
 // ---------------------------------------------------------------------------
 // 事件处理 / 生命周期
 // ---------------------------------------------------------------------------
-// 切换农场返回后重新请求初始互动
+// 进入 / 从其他页返回时重新请求;离开时已关弹窗,不能因「农场未变」跳过
 onActivated(() => {
     pageActive = true;
-    const fetchKey = getFarmFetchKey();
-    if (fetchKey === lastFetchedFarmKey) return;
     visible.value = false;
     resetForm();
     checkShouldShow();
@@ -356,6 +356,7 @@ onDeactivated(() => {
     pageActive = false;
     requestSeq += 1;
     visible.value = false;
+    lastFetchedFarmKey = "";
 });
 
 /**
@@ -418,6 +419,7 @@ const submitInteractResult = async (type = "time", { closeOnSuccess = true } = {
                 showMessage(res.msg || "提交成功", "success");
                 visible.value = false;
                 resetForm();
+                emit("success");
             }
             return true;
         }

+ 4 - 4
src/views/old_mini/agri_file/components/addZoneInfoPopup.vue

@@ -207,10 +207,10 @@ function saveSelectedFarm(farm) {
         farm_name: name,
         wkt,
     };
-    localStorage.setItem("selectedFarmId", String(id));
-    localStorage.setItem("selectedFarmName", name);
-    localStorage.setItem("selectedFarmPoint", wkt);
-    localStorage.setItem("selectedFarmData", JSON.stringify(normalized));
+    // localStorage.setItem("selectedFarmId", String(id));
+    // localStorage.setItem("selectedFarmName", name);
+    // localStorage.setItem("selectedFarmPoint", wkt);
+    // localStorage.setItem("selectedFarmData", JSON.stringify(normalized));
 }
 
 /** 用创建结果刷新「当前选中农场」缓存 */

+ 8 - 21
src/views/old_mini/agri_file/components/albumPanel.vue

@@ -52,7 +52,7 @@
                     <span class="album-card__count">
                         {{ t("agriFile.photoCount", { count: item.count }) }}
                     </span>
-                    <div class="album-card__add" @click.stop="handleUploadClick(item.zone_id)">
+                    <div class="album-card__add" @click.stop="handleUploadClick(item.zone_id, item.geom)">
                         <el-icon>
                             <Plus />
                         </el-icon>
@@ -250,28 +250,13 @@ const initAlbumMap = async () => {
     fitZonesOnMap();
 };
 
-const getFarmMapCenter = () => {
-    try {
-        const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
-        const wkt = farm.wkt || farm.geom_wkt || farm.farm_location;
-        if (typeof wkt === "string" && wkt.trim()) return wkt.trim();
-    } catch {
-        // ignore
-    }
-    return "";
-};
-
 // ---------- 事件处理 ----------
 const goAddZone = (type) => {
-    const mapCenter = getFarmMapCenter();
     router.push({
-        path: "/entry_select_location",
+        path: "/add_zone",
         query: {
-            ...(mapCenter ? { mapCenter } : {}),
-            iconType: "plant",
-            pointType: "plant",
-            from: "agri_album",
-            zoneType: type || "region",
+            type,
+            isSuccessPopup: type === "farm" ? "1" : "0",
         },
     });
 };
@@ -291,9 +276,11 @@ const goAlbumDetail = (item) => {
     });
 };
 
-const handleUploadClick = (zoneId) => {
+const handleUploadClick = (zoneId, geom) => {
+    const needDraw = geom ? false : true;
+    console.log('needDraw, ',geom,  needDraw);
     // 上传照片
-    emit('open-upload', zoneId)
+    emit('open-upload', zoneId, needDraw)
 };
 
 // ---------- 生命周期 ----------

+ 1 - 0
src/views/old_mini/agri_file/components/albumUploadPopup.vue

@@ -3,6 +3,7 @@
         v-model:show="showValue"
         round
         :close-on-click-overlay="false"
+        :z-index="20000"
         class="album-upload-popup"
         @closed="handleClosed"
     >

+ 33 - 2
src/views/old_mini/agri_file/components/patrolPanel.vue

@@ -150,15 +150,40 @@ const getSelectedZoneId = () => {
     return farm.zone_id ?? farm.zoneId ?? null;
 };
 
+/** 清空第一条上的感知任务残留,回退为 mock 文案 */
+const clearFirstPatrolInteract = () => {
+    if (!patrolList.value.length) return;
+    const item = patrolList.value[0];
+    patrolList.value[0] = {
+        ...item,
+        id: 1,
+        level: t("agriFile.riskLevel2"),
+        title: t("agriFile.patrolGrowthTitle"),
+        zone: t("agriFile.zoneOne"),
+        subject: t("agriFile.patrolSubject"),
+        issue: t("agriFile.patrolIssue"),
+        desc: "",
+        zoneId: undefined,
+        interactCode: undefined,
+        raw: undefined,
+    };
+};
+
 // ---------- 业务逻辑:加载 ----------
 const fetchPatrolInteractTask = async () => {
     const zoneId = getSelectedZoneId();
-    if (zoneId == null || zoneId === "") return;
+    if (zoneId == null || zoneId === "") {
+        clearFirstPatrolInteract();
+        return;
+    }
     try {
         const res = await VE_API.questionnaire.getExternalInteractTasks({ zone_id: zoneId });
         const tasks = res?.data?.interact_tasks;
         const first = Array.isArray(tasks) ? tasks[0] : null;
-        if (!first) return;
+        if (!first) {
+            clearFirstPatrolInteract();
+            return;
+        }
         const mapped = mapInteractTaskToPatrol(first);
         if (patrolList.value.length) {
             patrolList.value[0] = {
@@ -219,6 +244,8 @@ const fillMockLabels = () => {
 };
 
 const loadPatrolData = () => {
+    // 先清掉上一农场感知任务,避免 fillMockLabels 因 interactCode 保留旧数据
+    clearFirstPatrolInteract();
     fillMockLabels();
     fetchPatrolInteractTask();
     fetchCropArchive();
@@ -294,6 +321,10 @@ const handleComplete = () => {
 // ---------- 生命周期 ----------
 onMounted(loadPatrolData);
 onActivated(loadPatrolData);
+
+defineExpose({
+    fetchPatrolInteractTask,
+});
 </script>
 
 <style lang="scss" scoped>

+ 43 - 4
src/views/old_mini/agri_file/index.vue

@@ -29,7 +29,7 @@
             </div>
         </div>
         <div class="content-panel">
-            <patrol-panel v-show="activeNav === 'patrol'" @open-upload="openUploadPopup" />
+            <patrol-panel ref="patrolPanelRef" v-show="activeNav === 'patrol'" @open-upload="openUploadPopup" />
             <album-panel
                 v-show="activeNav === 'album'"
                 :active="activeNav === 'album'"
@@ -51,7 +51,7 @@
         <diagnosis-report-popup />
 
         <!-- 恢复种植弹窗 -->
-        <restore-planting-popup />
+        <restore-planting-popup @success="handleRestorePlantingSuccess" />
 
         <!-- 代管弹窗:后续由接口控制是否展示 -->
         <proxy-auth-popup
@@ -76,7 +76,7 @@
 </template>
 
 <script setup>
-import { computed, onActivated, onBeforeUnmount, onMounted, ref } from "vue";
+import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref } from "vue";
 import { useStore } from "vuex";
 import { useRouter } from "vue-router";
 import { ElMessage } from "element-plus";
@@ -100,6 +100,8 @@ const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 
 // ---------- 常量 ----------
 const DIAGNOSIS_GUIDE_STORAGE_KEY = "AGRI_FILE_DIAGNOSIS_REPORT_GUIDE";
+/** 勾画范围提交成功后,回农事档案继续上传的标记 */
+const CONTINUE_UPLOAD_KEY = "AGRI_FILE_CONTINUE_UPLOAD";
 
 const navCards = [
     {
@@ -125,6 +127,7 @@ const guideVisible = ref(false);
 const showUploadPopup = ref(false);
 const showSuccessPopup = ref(false);
 const showCompleteFarmPopup = ref(false);
+const patrolPanelRef = ref(null);
 
 /** 后续由接口控制是否展示代管授权弹窗 */
 const showProxyAuthPopup = ref(false);
@@ -186,12 +189,42 @@ const getSelectedFarm = () => {
 };
 
 // ---------- 事件处理 ----------
-const openUploadPopup = (zoneId) => {
+const openUploadPopup = (zoneId, needDraw) => {
     if (!zoneId) return;
+    if (needDraw) {
+        // 勾画完成后带 continueUpload 返回,继续打开上传弹窗
+        router.push({
+            path: "/add_zone",
+            query: {
+                type: "region",
+                zoneId,
+                continueUpload: "1",
+            },
+        });
+        return;
+    }
     uploadZoneId.value = zoneId;
     showUploadPopup.value = true;
 };
 
+/** 从勾画范围页提交成功后返回:继续打开上传弹窗 */
+const tryContinueUpload = () => {
+    const raw = sessionStorage.getItem(CONTINUE_UPLOAD_KEY);
+    if (!raw) return;
+    sessionStorage.removeItem(CONTINUE_UPLOAD_KEY);
+    let zoneId = "";
+    try {
+        zoneId = JSON.parse(raw)?.zoneId || "";
+    } catch {
+        return;
+    }
+    if (!zoneId) return;
+    uploadZoneId.value = zoneId;
+    nextTick(() => {
+        showUploadPopup.value = true;
+    });
+};
+
 const handleUploadConfirm = async ({ images, date } = {}) => {
     const cloud_url = Array.isArray(images) ? images : [];
     if (!cloud_url.length) {
@@ -222,6 +255,11 @@ const handleComplete = () => {
     showSuccessPopup.value = false;
 };
 
+/** 恢复种植提交成功后刷新巡园感知任务 */
+const handleRestorePlantingSuccess = () => {
+    patrolPanelRef.value?.fetchPatrolInteractTask?.();
+};
+
 const handleNavClick = (item) => {
     if (item.key === "report") dismissGuide();
     activeNav.value = item.key;
@@ -268,6 +306,7 @@ onMounted(() => {
 onActivated(() => {
     checkCompleteFarmPopup();
     tryShowGuide();
+    tryContinueUpload();
 });
 onBeforeUnmount(() => {
     document.removeEventListener("click", handleOutsideClick, true);

+ 73 - 2
src/views/old_mini/agri_file/pages/addZone.vue

@@ -281,14 +281,76 @@ const leaveAfterConfirm = () => {
     }, 800);
 };
 
-const handleFarmNameConfirm = (regionName) => {
+/** POLYGON → MULTIPOLYGON,与 update_zone_polygon 入参一致 */
+function toMultiPolygonWkt(wkt) {
+    const text = String(wkt || "").trim();
+    if (!text) return "";
+    if (/^MULTIPOLYGON/i.test(text)) return text;
+    if (/^POLYGON\s*\(/i.test(text)) {
+        const body = text.replace(/^POLYGON\s*/i, "").trim();
+        return `MULTIPOLYGON (${body})`;
+    }
+    return text;
+}
+
+function resolveSubmitFarmId() {
+    const farm = getFarmData();
+    return farm.farm_id ?? farm.id ?? "";
+}
+
+function resolveSubmitZoneId() {
+    const farm = getFarmData();
+    return route.query.trackId || farm.zone_id || farm.zoneId || "";
+}
+
+const submittingZoneRange = ref(false);
+
+/** 巡园要点 needDraw 进入:填名称后提交分区范围 */
+const handleFarmNameConfirm = async (regionName) => {
+    if (submittingZoneRange.value) return;
     const payload = mapManage.getAreaGeometry();
+    if (!payload.geometryArr.length) {
+        ElMessage.warning(t("agriFile.pleaseDrawZone"));
+        return;
+    }
+
+    const farmId = resolveSubmitFarmId();
+    const zoneId = resolveSubmitZoneId();
+    const polygon = toMultiPolygonWkt(mapManage.mergeGeometryWkts(payload.geometryArr));
+
+    if (!farmId || !zoneId || !polygon) {
+        ElMessage.warning("缺少农场或分区信息,请返回重试");
+        return;
+    }
+
+    submittingZoneRange.value = true;
+    try {
+        const res = await VE_API.questionnaire.drawZoneRange({
+            farm_id: farmId,
+            zone_id: zoneId,
+            zone_name: regionName,
+            polygon,
+        });
+        if (res?.code !== 200) {
+            ElMessage.error(res?.msg || "提交失败,请稍后再试");
+            return;
+        }
+    } catch (e) {
+        console.warn("[addZone] drawZoneRange failed", e);
+        ElMessage.error("提交失败,请稍后再试");
+        return;
+    } finally {
+        submittingZoneRange.value = false;
+    }
+
     const geometryData = {
         zone_name: regionName,
         geometryArr: payload.geometryArr,
         mianji: payload.mianji,
+        polygon,
     };
     sessionStorage.setItem("FARM_RANGE_GEOMETRY", JSON.stringify(geometryData));
+
     if (isSuccessPopupParam.value) {
         showDrawSuccessPopup.value = true;
         return;
@@ -316,7 +378,6 @@ const handleZoneInfoConfirm = (formData) => {
     };
     sessionStorage.setItem("ADD_ZONE_GEOMETRY", JSON.stringify(geometryData));
     showExecuteSuccessPopup.value = true;
-    
 };
 
 const handleDrawSuccessClosed = () => {
@@ -327,6 +388,16 @@ const handleDrawSuccessClosed = () => {
 const handleComplete = () => {
     showExecuteSuccessPopup.value = false;
 
+    // 农事档案上传入口 needDraw 勾画:返回并继续打开上传弹窗
+    if (isRegionDrawMode.value && route.query.continueUpload === "1") {
+        sessionStorage.setItem(
+            "AGRI_FILE_CONTINUE_UPLOAD",
+            JSON.stringify({ zoneId: route.query.zoneId || "" })
+        );
+        router.back();
+        return;
+    }
+
     leaveAfterConfirm();
 }
 

+ 50 - 9
src/views/old_mini/agri_file/pages/growthTrack.vue

@@ -259,9 +259,57 @@ const handleSubmit = () => {
 
 const needDraw = ref(true);
 
+const getSelectedFarm = () => {
+    try {
+        return JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
+    } catch {
+        return {};
+    }
+};
+
+/** status=1 有范围无需勾画;status=0 无范围需要勾画 */
+const fetchZoneRangeStatus = async () => {
+    const farm = getSelectedFarm();
+    const farmId = farm.farm_id ?? farm.id;
+    const zoneId = id.value || farm.zone_id || farm.zoneId;
+    if (farmId == null || farmId === "" || zoneId == null || zoneId === "") {
+        needDraw.value = true;
+        return;
+    }
+    try {
+        const res = await VE_API.questionnaire.checkZoneRange({
+            farm_id: farmId,
+            zone_id: zoneId,
+        });
+        const status = Number(res?.data?.status);
+        needDraw.value = status !== 1;
+    } catch (e) {
+        console.warn("[growthTrack] checkZoneRange failed", e);
+        needDraw.value = true;
+    }
+};
+
 /** 先跳小程序校验定位;通过后由小程序直接打开勾画/病虫害/上传目标页 */
 const handleUploadClick = () => {
-    
+    if (needDraw.value) {
+        router.push({
+            path: "/add_zone",
+            query: {
+                type: "farm",
+                targetUrl: isAbnormal.value ? "/pest_interact" : "/growth_track",
+                ...(isAbnormal.value ? { isAbnormal: "true" } : {}),
+                ...(route.query.id ? { trackId: route.query.id } : {}),
+                ...(route.query.type ? { trackType: route.query.type } : {}),
+            },
+        });
+        return;
+    }
+    if (isAbnormal.value) {
+        router.push({
+            path: "/pest_interact",
+        });
+        return;
+    }
     showUploadPopup.value = true;
     // const query = [
     //     `needDraw=${needDraw.value ? 1 : 0}`,
@@ -276,14 +324,6 @@ const handleUploadClick = () => {
     // });
 };
 
-const getSelectedFarm = () => {
-    try {
-        return JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
-    } catch {
-        return {};
-    }
-};
-
 const handleUploadConfirm = async ({ images, date } = {}) => {
     const cloud_url = Array.isArray(images) ? images : [];
     if (!cloud_url.length) {
@@ -340,6 +380,7 @@ const confirmLeave = () => {
 // });
 
 onActivated(() => {
+    fetchZoneRangeStatus();
     if (!isAbnormal.value) {
         fetchInternalInteractTasks();
     }

+ 11 - 2
src/views/old_mini/entry_information/components/selectVariety.vue

@@ -43,8 +43,13 @@
             </div>
 
             <!-- 默认表单卡(单选,始终一张) -->
-            <div v-if="currentSelected" :key="currentSelected.uid" class="form-card"
-                :class="hasVariety ? 'selected-card' : 'form-card--default'">
+            <div
+                v-if="currentSelected"
+                :key="currentSelected.uid"
+                v-loading="interactLoading"
+                class="form-card"
+                :class="hasVariety ? 'selected-card' : 'form-card--default'"
+            >
                 <div class="form-card__header">
                     <img class="title-icon" src="@/assets/img/home/label-icon.png" alt="" />
                     <el-select
@@ -402,6 +407,8 @@ const gridExpanded = ref(false);
 const selectedList = ref([]);
 /** 无分区初始互动选项,业务侧自行对接 */
 const initialInteractOptions = ref(null);
+/** 无分区初始互动接口 loading(表单卡) */
+const interactLoading = ref(false);
 /** 进行中的品种请求,防止并发重复 */
 const varietyFetchPromises = new Map();
 /** 进行中的初始互动请求 key */
@@ -503,6 +510,7 @@ async function fetchInitialInteractOptionsWithoutZone() {
     }
 
     interactFetchKey = key;
+    interactLoading.value = true;
     interactFetchPromise = (async () => {
         try {
             const raw = await VE_API.questionnaire.getInitialInteractionWithoutZone(params);
@@ -517,6 +525,7 @@ async function fetchInitialInteractOptionsWithoutZone() {
             if (interactFetchKey === key) {
                 interactFetchPromise = null;
                 interactFetchKey = "";
+                interactLoading.value = false;
             }
         }
     })();

+ 0 - 22
src/views/old_mini/entry_information/selectLocation.vue

@@ -17,13 +17,6 @@
         <div class="custom-bottom-fixed-btns">
             <div class="bottom-btn primary-btn" @click="handleSubmit">确认点位</div>
         </div>
-        <add-zone-info-popup
-            v-if="fromAgriAlbum"
-            v-model:show="showZoneInfoPopup"
-            :county-code="pendingZoneLocation?.adcode"
-            :point-location="pendingZoneLocation?.point || ''"
-            @confirm="handleZoneInfoConfirm"
-        />
     </div>
 </template>
 
@@ -36,7 +29,6 @@ import customHeader from "@/components/customHeader.vue";
 import locationSearch from "@/components/pageComponents/locationSearch.vue";
 import SelectLocationMap, { mapLocation, POINT_ICON } from "./map/selectLocationMap.js";
 import { convertPointToArray } from "@/utils/index";
-import addZoneInfoPopup from "@/views/old_mini/agri_file/components/addZoneInfoPopup.vue";
 
 const SESSION_KEY_RESIDENT = "ENTRY_RESIDENT_LOCATION";
 const SESSION_KEY_GROWTH = "GROWTH_REPORT_LOCATION";
@@ -50,7 +42,6 @@ const mapContainer = ref(null);
 const isPlantPoint = route.query.pointType === "plant";
 const fromGrowthReport = route.query.from === "growth_report";
 const fromService = route.query.from === "entry_service";
-const fromAgriAlbum = route.query.from === "agri_album";
 const headerName = fromGrowthReport ? "切换位置" : fromService ? "选择位置" : "选择种植点位";
 const sessionKey = fromGrowthReport ? SESSION_KEY_GROWTH : SESSION_KEY_RESIDENT;
 const iconSrc =
@@ -67,8 +58,6 @@ const userLocation = ref(
 const selectedAdcode = ref("");
 /** 搜索选中时带出的地址文案 */
 const selectedAddress = ref("");
-const showZoneInfoPopup = ref(false);
-const pendingZoneLocation = ref(null);
 
 const selectLocationMap = new SelectLocationMap({
     iconSrc,
@@ -169,20 +158,9 @@ const handleSubmit = async () => {
         adcode: adcode || "",
         address: address || "",
     };
-    if (fromAgriAlbum) {
-        payload.zoneType = route.query.zoneType || "region";
-        pendingZoneLocation.value = payload;
-        showZoneInfoPopup.value = true;
-        return;
-    }
     sessionStorage.setItem(sessionKey, JSON.stringify(payload));
     router.back();
 };
-
-const handleZoneInfoConfirm = () => {
-    pendingZoneLocation.value = null;
-    router.back();
-};
 </script>
 
 <style lang="scss" scoped>

+ 45 - 18
src/views/old_mini/growth_report/components/CropAlertPanel.vue

@@ -155,14 +155,13 @@ const currentCropName = ref(props.cropName || "");
 const cropMenuVisible = ref(false);
 const guideVisible = ref(false);
 
+/** 防止切换农场时旧请求回写覆盖新数据 */
+let reloadToken = 0;
+
 watch(
     () => props.cropName,
     (name) => {
-        if (name) {
-            currentCropName.value = name;
-            fetchAlertCards();
-            fetchZones();
-        }
+        if (name) currentCropName.value = name;
     }
 );
 
@@ -300,6 +299,17 @@ const getSelectedFarm = () => {
     }
 };
 
+/** 以缓存为准,避免 onActivated 时 props.farmId 尚未跟上父组件同步 */
+const resolveFarmId = () => {
+    const farm = getSelectedFarm();
+    const id =
+        localStorage.getItem("selectedFarmId") ||
+        farm.farm_id ||
+        farm.id ||
+        props.farmId;
+    return id != null && id !== "" ? id : "";
+};
+
 const formatLocalDate = (date) => {
     const y = date.getFullYear();
     const m = String(date.getMonth() + 1).padStart(2, "0");
@@ -307,20 +317,18 @@ const formatLocalDate = (date) => {
     return `${y}-${m}-${d}`;
 };
 
-/** 用当前选中农场 + 分区 + 当天日期 */
+/** 用当前选中农场 + 分区 + 当天日期(zone 只信面板选中态,避免缓存滞后) */
 const getQueryParams = () => {
-    const farm = getSelectedFarm();
     return {
-        farm_id: farm.farm_id ?? farm.id,
-        zone_id: selectedZoneId.value ?? farm.zone_id ?? farm.zoneId,
+        farm_id: resolveFarmId(),
+        zone_id: selectedZoneId.value,
         date: formatLocalDate(new Date()),
     };
 };
 
 /** 拉取分区列表,填充切换作物下拉 */
-const fetchZones = async () => {
-    const farm = getSelectedFarm();
-    const farmId = props.farmId || farm.farm_id || farm.id;
+const fetchZones = async (token = reloadToken) => {
+    const farmId = resolveFarmId();
     if (farmId == null || farmId === "") {
         zoneList.value = [];
         cropOptions.value = [];
@@ -329,11 +337,14 @@ const fetchZones = async () => {
     }
     try {
         const res = await VE_API.questionnaire.getZones({ farm_id: farmId });
+        // 旧请求回包时不覆盖当前选中分区
+        if (token !== reloadToken) return;
         const zones = Array.isArray(res?.data?.zones) ? res.data.zones : [];
         zoneList.value = zones;
         cropOptions.value = zones.map((item) => item.zone_name || "");
         applySelectedZone(zones.length ? 0 : -1);
     } catch (e) {
+        if (token !== reloadToken) return;
         console.warn("[CropAlertPanel] getZones failed", e);
         zoneList.value = [];
         cropOptions.value = [];
@@ -341,6 +352,18 @@ const fetchZones = async () => {
     }
 };
 
+/** 切换农场 / 回页:先清旧 zone,再拉分区,最后拉预警 */
+const reloadFarmAlerts = async () => {
+    const token = ++reloadToken;
+    selectedZoneId.value = null;
+    activeIndex.value = 0;
+    zoneList.value = [];
+    cropOptions.value = [];
+    await fetchZones(token);
+    if (token !== reloadToken) return;
+    await fetchAlertCards(token);
+};
+
 /** 接口可能返回对象或数组,统一取第一条 */
 const pickFirstRecord = (res) => {
     const data = res?.data ?? res;
@@ -369,9 +392,10 @@ const applyWeatherStress = (record) => {
     });
 };
 
-const fetchAlertCards = async () => {
+const fetchAlertCards = async (token = reloadToken) => {
     const queryParams = getQueryParams();
     if (queryParams.farm_id == null || queryParams.farm_id === "") {
+        if (token !== reloadToken) return;
         applyWeatherRisk(null);
         applyWeatherStress(null);
         nextTick(() => syncMidAnchor());
@@ -381,6 +405,8 @@ const fetchAlertCards = async () => {
         VE_API.questionnaire.getWeatherRisk(queryParams),
         VE_API.questionnaire.getWeatherStress(queryParams),
     ]);
+    // 切换农场期间若有更新请求,丢弃旧回包
+    if (token !== reloadToken) return;
 
     // 成功但无数据时清空旧卡片,避免切换农场后残留
     applyWeatherRisk(riskRes.status === "fulfilled" ? pickFirstRecord(riskRes.value) : null);
@@ -441,10 +467,11 @@ onMounted(() => {
     document.addEventListener("click", handleOutsideClick, true);
 });
 
-/** 进入 / 从切换农场返回时:先拉分区,再拉预警 */
-onActivated(async () => {
-    await fetchZones();
-    await fetchAlertCards();
+/** 进入 / 从切换农场返回:nextTick 等父组件 syncSelectedFarmId 落盘后再拉 */
+onActivated(() => {
+    nextTick(() => {
+        reloadFarmAlerts();
+    });
 });
 
 onBeforeUnmount(() => {
@@ -453,7 +480,7 @@ onBeforeUnmount(() => {
     resizeObserver = null;
 });
 
-defineExpose({ refreshAlerts: fetchAlertCards, refreshZones: fetchZones });
+defineExpose({ refreshAlerts: fetchAlertCards, refreshZones: fetchZones, reloadFarmAlerts });
 </script>
 
 <style lang="scss" scoped>