فهرست منبع

fix: 对接接口

lxf 6 روز پیش
والد
کامیت
3599639c0a

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

@@ -101,4 +101,9 @@ module.exports = {
         url: config.base_new_url + "questionnaire/weather_report_list",
         type: "get",
     },
+    // 新建分区
+    createZone: {
+        url: config.base_new_url + "questionnaire/add_zone_info",
+        type: "post",
+    },
 }

+ 47 - 7
src/components/popup/restorePlantingPopup.vue

@@ -103,7 +103,7 @@
 </template>
 
 <script setup>
-import { computed, onMounted, ref } from "vue";
+import { computed, onActivated, onDeactivated, ref } from "vue";
 import { Popup } from "vant";
 import { Check } from "@element-plus/icons-vue";
 import { ElMessage } from "element-plus";
@@ -173,6 +173,17 @@ const interactKeys = computed(() =>
     Object.keys(interactMap.value).sort((a, b) => Number(a) - Number(b))
 );
 
+const getSelectedFarmId = () => {
+    try {
+        const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
+        return String(
+            localStorage.getItem("selectedFarmId") || farm.farm_id || farm.id || ""
+        );
+    } catch {
+        return String(localStorage.getItem("selectedFarmId") || "");
+    }
+};
+
 const getSelectedZoneId = () => {
     try {
         const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
@@ -182,7 +193,16 @@ const getSelectedZoneId = () => {
     }
 };
 
-const resolvedZoneId = computed(() => props.zoneId ?? getSelectedZoneId());
+/** 每次实时读缓存,避免 computed 缓存旧 zone_id */
+const resolveZoneId = () => props.zoneId ?? getSelectedZoneId();
+
+/** 上次成功请求对应的农场/分区 */
+let lastFetchedFarmKey = "";
+/** 递增后旧请求的返回不再打开弹窗 */
+let requestSeq = 0;
+/** 当前页是否处于前台,离开后迟到的响应不展示 */
+let pageActive = false;
+const getFarmFetchKey = () => `${getSelectedFarmId()}|${resolveZoneId() ?? ""}`;
 
 // ---------------------------------------------------------------------------
 // 工具函数
@@ -291,22 +311,30 @@ const applyQuestionData = (data) => {
 
 /** 请求接口判断是否展示,并填充问题内容 */
 const checkShouldShow = async () => {
-    if (resolvedZoneId.value == null || resolvedZoneId.value === "") {
+    const seq = ++requestSeq;
+    const zoneId = resolveZoneId();
+    const fetchKey = getFarmFetchKey();
+    if (!pageActive) return;
+    if (zoneId == null || zoneId === "") {
         visible.value = false;
+        lastFetchedFarmKey = fetchKey;
         return;
     }
     try {
         const res = await VE_API.questionnaire.getInitialInteraction({
-            zone_id: resolvedZoneId.value,
+            zone_id: zoneId,
             date: formatToday(),
-            // date: '2025-07-10',
         });
+        // 已切页或已有更新的请求时,丢弃这次结果,避免弹窗叠加
+        if (seq !== requestSeq || !pageActive) return;
         if (res?.code === 200 && res.data) {
             applyQuestionData(res.data);
         } else {
             visible.value = false;
         }
+        lastFetchedFarmKey = fetchKey;
     } catch (e) {
+        if (seq !== requestSeq || !pageActive) return;
         visible.value = false;
     }
 };
@@ -314,10 +342,22 @@ const checkShouldShow = async () => {
 // ---------------------------------------------------------------------------
 // 事件处理 / 生命周期
 // ---------------------------------------------------------------------------
-onMounted(() => {
+// 切换农场返回后重新请求初始互动
+onActivated(() => {
+    pageActive = true;
+    const fetchKey = getFarmFetchKey();
+    if (fetchKey === lastFetchedFarmKey) return;
+    visible.value = false;
+    resetForm();
     checkShouldShow();
 });
 
+onDeactivated(() => {
+    pageActive = false;
+    requestSeq += 1;
+    visible.value = false;
+});
+
 /**
  * 暂未到达 / 已过:立刻切到相邻物候期;
  * 已到达:选中并记下当前 period_code,供提交使用
@@ -342,7 +382,7 @@ const handlePhenologySelect = (value) => {
  */
 const buildSubmitPayload = (type = "time") => {
     const payload = {
-        zone_id: Number(resolvedZoneId.value) || resolvedZoneId.value,
+        zone_id: Number(resolveZoneId()) || resolveZoneId(),
         date: toApiDate(selectedDate.value) || formatToday(),
     };
     if (type === "time") {

+ 1 - 1
src/i18n/messages.js

@@ -145,7 +145,7 @@ export default {
             farmLocation: "--",
             moreFarms: "更多农场",
             patrolRecord: "巡园记录",
-            patrolGrowthTitle: "长势跟踪巡园要点",
+            patrolGrowthTitle: "物候跟踪巡园要点",
             patrolAbnormalTitle: "异常态势巡园要点",
             patrolSubject: "--",
             patrolIssue: "暂无数据",

+ 172 - 8
src/views/old_mini/agri_file/components/addZoneInfoPopup.vue

@@ -48,6 +48,25 @@
             </div>
 
             <div class="form-block">
+                <div class="form-block__title">当前物候期</div>
+                <el-select
+                    v-model="form.phenologyId"
+                    class="form-select"
+                    placeholder="选择物候期"
+                    placement="bottom-end"
+                    popper-class="variety-select-popper"
+                    :disabled="!form.categoryId"
+                    clearable
+                >
+                    <el-option
+                        v-for="opt in phenologyOptions"
+                        :key="opt.id"
+                        :label="opt.name"
+                        :value="opt.id"
+                    />
+                </el-select>
+            </div>
+            <div class="form-block">
                 <div class="form-block__title">{{ startingPointQuestion }}</div>
                 <div class="form-date-wrap">
                     <el-date-picker
@@ -92,6 +111,7 @@ const { t } = useI18n();
 
 const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
 const DEFAULT_COUNTY_CODE = "440113";
+const DEFAULT_POINT = "POINT(113.6142086995688 23.585836479509055)";
 const META_KEYS = new Set(["city", "county", "county_code", "province"]);
 
 const props = defineProps({
@@ -111,15 +131,25 @@ const emit = defineEmits(["update:show", "confirm"]);
 const form = reactive({
     categoryId: "",
     varietyId: "",
+    phenologyId: "",
     plantDate: "",
     zoneName: "",
 });
 
+/** getVarietiesByCrop 返回的作物编码,供无分区互动接口使用 */
+const cropMeta = reactive({
+    crop_code: "",
+    crop_group: "",
+});
+
 const categoryLoading = ref(false);
 const varietyLoading = ref(false);
 const categoryOptions = ref([]);
 const varietyOptions = ref([]);
+const phenologyOptions = ref([]);
 const startingPointQuestion = ref(t("agriFile.plantStartPoint"));
+let interactFetchKey = "";
+let interactFetchPromise = null;
 
 const showValue = computed({
     get: () => props.show,
@@ -144,20 +174,126 @@ const resolveCountyCode = () => {
     return DEFAULT_COUNTY_CODE;
 };
 
+function getDefaultPoint() {
+    try {
+        const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
+        const wkt = farm.wkt || farm.geom_wkt || farm.farm_location;
+        if (typeof wkt === "string" && /^POINT\s*\(/i.test(wkt.trim())) return wkt.trim();
+    } catch {
+        // ignore
+    }
+    try {
+        const saved = JSON.parse(sessionStorage.getItem(LOCATION_KEY) || "null");
+        if (saved?.point) return saved.point;
+    } catch {
+        // ignore
+    }
+    return localStorage.getItem("MINI_USER_LOCATION_POINT") || DEFAULT_POINT;
+}
+
+/** options_interact.interact → 物候期下拉(展示 period_name,提交 period_code) */
+function mapOptionsInteractToPhenology(interact) {
+    if (!interact) return [];
+    const list = Array.isArray(interact)
+        ? interact
+        : Object.keys(interact)
+              .sort((a, b) => Number(a) - Number(b))
+              .map((key) => interact[key]);
+
+    return list
+        .map((item) => {
+            if (!item || typeof item !== "object") return null;
+            const code = item.period_code ?? item.code ?? item.id;
+            const name = item.period_name ?? item.name ?? "";
+            if (!name) return null;
+            return {
+                id: code != null && code !== "" ? code : name,
+                name,
+                code: code != null && code !== "" ? String(code) : "",
+                url: item.url || "",
+            };
+        })
+        .filter(Boolean);
+}
+
+/** 无分区初始互动:起种问题文案 + 物候期选项 */
+function applyInitialInteract(data) {
+    if (!data) return;
+
+    const theme = data.date_interact?.interact_theme;
+    if (theme) {
+        startingPointQuestion.value = theme;
+    }
+
+    const phenology = mapOptionsInteractToPhenology(data.options_interact?.interact);
+    if (!phenology.length) return;
+
+    phenologyOptions.value = phenology;
+    if (!form.phenologyId) return;
+    const stillExists = phenology.some((opt) => String(opt.id) === String(form.phenologyId));
+    if (!stillExists) form.phenologyId = "";
+}
+
+async function fetchInitialInteractOptionsWithoutZone() {
+    const category = categoryOptions.value.find(
+        (item) => String(item.id) === String(form.categoryId)
+    );
+    if (!category) return;
+
+    const params = {
+        crop_big_type: cropMeta.crop_group || category.firstCrop || "",
+        crop_code: cropMeta.crop_code || category.code || "",
+        crop_maturing: "P0",
+        location: getDefaultPoint(),
+        date: form.plantDate || new Date().toISOString().split("T")[0],
+    };
+    if (!params.crop_code) return;
+
+    const key = `${params.crop_code}|${params.location}|${params.date}`;
+    if (interactFetchPromise && interactFetchKey === key) {
+        return interactFetchPromise;
+    }
+
+    interactFetchKey = key;
+    interactFetchPromise = (async () => {
+        try {
+            const raw = await VE_API.questionnaire.getInitialInteractionWithoutZone(params);
+            const res = Array.isArray(raw) ? raw[0] : raw;
+            if (res?.code === 200) {
+                applyInitialInteract(res.data);
+            }
+        } catch (error) {
+            console.warn("[addZoneInfoPopup] getInitialInteractionWithoutZone failed", error);
+        } finally {
+            if (interactFetchKey === key) {
+                interactFetchPromise = null;
+                interactFetchKey = "";
+            }
+        }
+    })();
+
+    return interactFetchPromise;
+}
+
 /** getCrops 可能返回对象(果树/大田分组)或数组,统一展平为下拉选项 */
 const flattenCropsToOptions = (data) => {
     const map = new Map();
-    const pushCrop = (crop) => {
+    const pushCrop = (crop, firstCrop = "") => {
         const id = crop?.crop_id ?? crop?.id;
         const name = crop?.crop_name ?? crop?.name;
         if (id == null || !name) return;
-        map.set(String(id), { id, name, code: crop.crop_code || "" });
+        map.set(String(id), {
+            id,
+            name,
+            code: crop.crop_code || "",
+            firstCrop: firstCrop || crop.crop_group || "",
+        });
     };
 
     if (Array.isArray(data)) {
         data.forEach((item) => {
             if (Array.isArray(item?.items)) {
-                item.items.forEach(pushCrop);
+                item.items.forEach((crop) => pushCrop(crop, item.firstCrop || ""));
                 return;
             }
             pushCrop(item);
@@ -171,10 +307,10 @@ const flattenCropsToOptions = (data) => {
         .forEach((key) => {
             data[key].forEach((group) => {
                 if (Array.isArray(group?.items)) {
-                    group.items.forEach(pushCrop);
+                    group.items.forEach((crop) => pushCrop(crop, key));
                     return;
                 }
-                pushCrop(group);
+                pushCrop(group, key);
             });
         });
     return Array.from(map.values());
@@ -204,6 +340,9 @@ const fetchCategoryOptions = async () => {
 const fetchVarietyOptions = async (cropId) => {
     if (cropId == null || cropId === "") {
         varietyOptions.value = [];
+        phenologyOptions.value = [];
+        cropMeta.crop_code = "";
+        cropMeta.crop_group = "";
         startingPointQuestion.value = t("agriFile.plantStartPoint");
         return;
     }
@@ -214,15 +353,22 @@ const fetchVarietyOptions = async (cropId) => {
         });
         if (res.code === 200 && res.data) {
             varietyOptions.value = res.data.varieties;
-            startingPointQuestion.value =
-                res.data.starting_point_question || t("agriFile.plantStartPoint");
+            cropMeta.crop_code = res.data.crop_code || "";
+            cropMeta.crop_group = res.data.crop_group || "";
+            await fetchInitialInteractOptionsWithoutZone();
         } else {
             varietyOptions.value = [];
+            phenologyOptions.value = [];
+            cropMeta.crop_code = "";
+            cropMeta.crop_group = "";
             startingPointQuestion.value = t("agriFile.plantStartPoint");
             ElMessage.error(res.msg || "获取品种列表失败");
         }
     } catch {
         varietyOptions.value = [];
+        phenologyOptions.value = [];
+        cropMeta.crop_code = "";
+        cropMeta.crop_group = "";
         startingPointQuestion.value = t("agriFile.plantStartPoint");
         ElMessage.error("获取品种列表失败,请稍后再试");
     } finally {
@@ -243,10 +389,14 @@ const applyAutoZoneName = (name) => {
 const resetForm = () => {
     form.categoryId = "";
     form.varietyId = "";
+    form.phenologyId = "";
     form.plantDate = "";
     form.zoneName = "";
     lastAutoZoneName.value = "";
     varietyOptions.value = [];
+    phenologyOptions.value = [];
+    cropMeta.crop_code = "";
+    cropMeta.crop_group = "";
     startingPointQuestion.value = t("agriFile.plantStartPoint");
 };
 
@@ -261,6 +411,9 @@ watch(
 
 const handleCategoryChange = (cropId) => {
     form.varietyId = "";
+    form.phenologyId = "";
+    phenologyOptions.value = [];
+    startingPointQuestion.value = t("agriFile.plantStartPoint");
     fetchVarietyOptions(cropId);
     const category = categoryOptions.value.find(
         (item) => String(item.id) === String(cropId)
@@ -282,6 +435,10 @@ const handleConfirm = () => {
         ElMessage.warning(t("agriFile.pleaseSelectVariety"));
         return;
     }
+    if (!form.phenologyId) {
+        ElMessage.warning("请选择物候期");
+        return;
+    }
     if (!form.plantDate) {
         ElMessage.warning(t("agriFile.pleaseSelectPlantDate"));
         return;
@@ -298,13 +455,19 @@ const handleConfirm = () => {
     const variety = varietyOptions.value.find(
         (item) => String(item.id) === String(form.varietyId)
     );
+    const phenology = phenologyOptions.value.find(
+        (item) => String(item.id) === String(form.phenologyId)
+    );
 
     emit("confirm", {
         zone_name: zoneName,
         category_id: form.categoryId,
         category_name: category?.name || "",
         variety_id: form.varietyId,
-        variety_name: variety?.name || "",
+        variety_name: variety?.name || form.varietyId || "",
+        phenology_id: form.phenologyId,
+        phenology_name: phenology?.name || "",
+        phenology_code: phenology?.code || String(form.phenologyId || ""),
         plant_date: form.plantDate,
     });
     emit("update:show", false);
@@ -348,6 +511,7 @@ const handleConfirm = () => {
     .form-select {
         flex: 1;
         min-width: 0;
+        width: 100%;
     }
 
     :deep(.el-select__wrapper),

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

@@ -177,8 +177,8 @@ const fetchCropArchive = async () => {
     const farm = getSelectedFarm();
     const params = {
         farm_id: farm.farm_id,
-        zone_id: 46,
-        crop_code: "LCH",
+        zone_id: farm.zone_id,
+        crop_code: farm.category_code,
     };
     if (!params) {
         cropArchiveList.value = [];

+ 6 - 0
src/views/old_mini/agri_file/fileMap.js

@@ -368,6 +368,12 @@ class FileMap {
         if (this.kmap?.map) {
             this.kmap.map.setTarget(target);
             this.kmap.map.updateSize();
+            // 切换农场后复用实例时同步中心点
+            this.kmap.getView().animate({
+                center,
+                zoom: 16,
+                duration: 0,
+            });
             this.flushPending();
             return;
         }

+ 4 - 4
src/views/old_mini/agri_file/pages/growthTrack.vue

@@ -82,7 +82,7 @@
                     <div class="situation-card__guide">
                         <div class="guide-title">{{ t("agriFile.patrolGuide") }}</div>
                         <div class="situation-card__guide-box">
-                            <img src="@/assets/img/agricultural/patrol-guide.png" alt="" />
+                            <img :src="guideImage" alt="" />
                         </div>
                     </div>
                 </div>
@@ -188,8 +188,8 @@ const interactProblem = computed(
     () => currentContent.value.interact_problem || t("agriFile.interactQuestionText")
 );
 const guideImage = computed(
-    // () => resolveImageUrl(currentContent.value.guide_url) || DEFAULT_GUIDE_IMAGE
-    () => resolveImageUrl('感知_玉米_4_32.png') || DEFAULT_GUIDE_IMAGE
+    () => resolveImageUrl(currentContent.value.guide_url) || DEFAULT_GUIDE_IMAGE
+    // () => resolveImageUrl('感知_玉米_4_32.png') || DEFAULT_GUIDE_IMAGE
 );
 
 const interactOptions = computed(() => {
@@ -223,7 +223,7 @@ const showSuccessPopup = ref(false);
 const showLeavePopup = ref(false);
 const situationList = [
     { nameKey: "agriFile.growthAbnormal" },
-    { nameKey: "agriFile.rootAbnormal" },
+    // { nameKey: "agriFile.rootAbnormal" },
 ];
 
 const handleShare = () => {

+ 91 - 44
src/views/old_mini/dev_login.vue

@@ -5,7 +5,7 @@
 <script setup>
 import { useRoute, useRouter } from "vue-router";
 import { useStore } from "vuex";
-import { SET_TOKEN,SET_USER_ROLES,SET_USER_CUR_ROLE } from "@/store/modules/app/type";
+import { SET_TOKEN, SET_USER_ROLES, SET_USER_CUR_ROLE } from "@/store/modules/app/type";
 import { onMounted } from "vue";
 
 const router = useRouter();
@@ -14,93 +14,140 @@ const store = useStore();
 
 let userId = route.query.userId;
 
+const resolveFarmPointWkt = (farm) => {
+    if (farm.geom_wkt && /^POINT\s*\(/i.test(String(farm.geom_wkt).trim())) {
+        return farm.geom_wkt;
+    }
+    if (farm.farm_location && /^POINT\s*\(/i.test(String(farm.farm_location).trim())) {
+        return farm.farm_location;
+    }
+    return farm.geom_wkt || farm.wkt || "";
+};
+
+const normalizeFarm = (farm) => {
+    const id = farm.farm_id ?? farm.id;
+    return {
+        ...farm,
+        id,
+        name: farm.farm_name || farm.name,
+        wkt: resolveFarmPointWkt(farm),
+        rawAddress: farm.farm_address || farm.city || "",
+    };
+};
+
+const hasSelectedFarm = (farm) => {
+    if (!farm || typeof farm !== "object") return false;
+    return !!(farm.farm_id || farm.id || farm.farm_name || farm.name);
+};
+
+const saveSelectedFarm = (farm) => {
+    localStorage.setItem("selectedFarmId", farm.id);
+    localStorage.setItem("selectedFarmName", farm.name || "");
+    localStorage.setItem("selectedFarmPoint", farm.wkt || "");
+    localStorage.setItem("selectedFarmData", JSON.stringify(farm));
+};
+
+/** 拉取农场列表:无数据去录入;有数据则恢复缓存选中或默认第一项 */
+const resolveFarmsAfterLogin = async () => {
+    const id = userId || localStorage.getItem("MINI_USER_ID");
+    if (!id) {
+        router.replace({ path: "/entry_information", query: { isClose: "1" } });
+        return false;
+    }
+    try {
+        const res = await VE_API.questionnaire.getFarms({ id });
+        const list = Array.isArray(res?.data) ? res.data : [];
+        if (!list.length) {
+            router.replace({ path: "/entry_information", query: { isClose: "1" } });
+            return false;
+        }
+
+        let cached = null;
+        try {
+            cached = JSON.parse(localStorage.getItem("selectedFarmData") || "null");
+        } catch {
+            cached = null;
+        }
+
+        const farm = hasSelectedFarm(cached) ? normalizeFarm(cached) : normalizeFarm(list[0]);
+        saveSelectedFarm(farm);
+        return true;
+    } catch (e) {
+        console.warn("[dev_login] getFarms failed", e);
+        return true;
+    }
+};
+
 onMounted(async () => {
-    const token = route.query.token
-    let targetUrl = route.query.targetUrl ? route.query.targetUrl : '/growth_report';
-    
+    const token = route.query.token;
+    let targetUrl = route.query.targetUrl ? route.query.targetUrl : "/growth_report";
+
     // 先从 session 获取保存的角色
     let savedRole = null;
-    
+
     if (!token) {
         const { data } = await VE_API.system.devLogin({ userId: userId });
         store.dispatch(`app/${SET_TOKEN}`, data.token);
         store.dispatch(`app/${SET_USER_ROLES}`, data.roles);
-        
+
         const sessionRes = await VE_API.mine.getSessionStore({ key: "cur_role" });
         if (sessionRes && sessionRes.data) {
             savedRole = sessionRes.data.val;
         }
-        // 优先使用保存的角色,如果保存的角色在 roles 中,则使用保存的角色,否则如果 roles 中包含 2,赋值 2,否则赋值 0
-        // let curRole = 0;
-        // if (savedRole !== null && Array.isArray(data.roles) && data.roles.includes(savedRole)) {
-        //     curRole = savedRole;
-        // }
-        // store.dispatch(`app/${SET_USER_CUR_ROLE}`, curRole);
         store.dispatch(`app/${SET_USER_CUR_ROLE}`, data.roles[data.roles.length - 1]);
         localStorage.setItem("localUserInfo", JSON.stringify(data));
     }
     // 存userId
-    let pointXy = route.query.point.split(",")
+    let pointXy = route.query.point.split(",");
     // 刷新后仍保留id和point
-    localStorage.setItem("MINI_USER_ID", userId)
+    localStorage.setItem("MINI_USER_ID", userId);
     route.query?.userInfo && localStorage.setItem("localUserInfo", route.query.userInfo);
-    if(route.query.roles){
+    if (route.query.roles) {
         const roles = JSON.parse(route.query.roles);
         store.dispatch(`app/${SET_USER_ROLES}`, roles);
-        
+
         const sessionRes = await VE_API.mine.getSessionStore({ key: "cur_role" });
         if (sessionRes && sessionRes.data) {
             savedRole = sessionRes.data.val;
         }
-        // 优先使用保存的角色,如果保存的角色在 roles 中,则使用保存的角色,否则如果 roles 中包含 2,赋值 2,否则赋值 0
-        // let curRole = 0;
-        // if (savedRole !== null && Array.isArray(roles) && roles.includes(savedRole)) {
-        //     curRole = savedRole;
-        // }
-        // store.dispatch(`app/${SET_USER_CUR_ROLE}`, curRole);
         store.dispatch(`app/${SET_USER_CUR_ROLE}`, roles[roles.length - 1]);
     }
-    // 进入首页时请求接口,确定是否为托管农户
-    // await fetchUserType();
-    // await getFarmList(() => {
-    //     targetUrl = '/create_farm?type=farmer&expertMiniUserId=81881&isReload=true';
-    // });
-    
-    localStorage.setItem("MINI_USER_LOCATION", route.query.point)
-    localStorage.setItem("MINI_USER_LOCATION_POINT", `POINT(${pointXy[0]} ${pointXy[1]})`)
+
+    localStorage.setItem("MINI_USER_LOCATION", route.query.point);
+    localStorage.setItem("MINI_USER_LOCATION_POINT", `POINT(${pointXy[0]} ${pointXy[1]})`);
     store.commit("home/SET_MINI_USER_LOCATION", route.query.point);
     store.commit("home/SET_MINI_USER_ID", userId);
     store.commit("home/SET_MINI_USER_LOCATION_POINT", `POINT(${pointXy[0]} ${pointXy[1]})`);
+
+    const hasFarms = await resolveFarmsAfterLogin();
+    if (!hasFarms) return;
+
     router.push(`${targetUrl}?miniJson=${JSON.stringify(route.query)}`);
-})
+});
 
 const getFarmList = async (callback) => {
-    localStorage.removeItem('selectedFarmId');
-    localStorage.removeItem('selectedFarmName');
+    localStorage.removeItem("selectedFarmId");
+    localStorage.removeItem("selectedFarmName");
     const { data } = await VE_API.farm.userFarmSelectOption();
-    if(data && data.length > 0) {
-        const defalutFarm = data[0]
-        localStorage.setItem('selectedFarmId', defalutFarm.id);
-        localStorage.setItem('selectedFarmName', defalutFarm.name);
-        localStorage.setItem('selectedFarmPoint', defalutFarm.point);
+    if (data && data.length > 0) {
+        const defalutFarm = data[0];
+        localStorage.setItem("selectedFarmId", defalutFarm.id);
+        localStorage.setItem("selectedFarmName", defalutFarm.name);
+        localStorage.setItem("selectedFarmPoint", defalutFarm.point);
     } else {
         callback();
     }
-}
-
+};
 
 // 获取用户是否为托管农户,并缓存 USER_TYPE
 const fetchUserType = async () => {
-    const { data } = await VE_API.farm
-        .userFarmSelectOption({ userType: 2 })
+    const { data } = await VE_API.farm.userFarmSelectOption({ userType: 2 });
     if (Array.isArray(data) && data.length > 0) {
         localStorage.setItem("USER_TYPE", "2");
     } else {
         localStorage.setItem("USER_TYPE", "1");
     }
 };
-
-
 </script>
 
 <style scoped></style>

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

@@ -99,7 +99,7 @@
                             format="YYYY-MM-DD"
                             value-format="YYYY-MM-DD"
                             :clearable="false"
-                            style="width: 130px"
+                            style="width: 118px; flex:none;"
                             :readonly="!hasVariety"
                         />
                     </div>
@@ -354,20 +354,6 @@ const getSelectedCategoriesFromSession = () => {
     }
 };
 
-const mapPhenologyList = (list) => {
-    if (!Array.isArray(list)) return [];
-    return list
-        .map((item, index) => {
-            if (typeof item === "string") {
-                return { id: item, name: item };
-            }
-            const name = item?.name || item?.phenology || String(item?.id ?? index);
-            const id = item?.id ?? name;
-            return { id, name };
-        })
-        .filter((item) => item.name);
-};
-
 const initCategoryTabsFromSession = () => {
     const selected = getSelectedCategoriesFromSession();
     const prevLoaded = new Map(
@@ -573,9 +559,7 @@ const fetchVarietiesByCrop = async (cropId, force = false) => {
                         };
                     })
                     .filter((item) => item.name);
-                tab.phenology = mapPhenologyList(data.phenology);
-                tab.startingPointQuestion =
-                    data.starting_point_question || "起种点问题";
+                // 物候期 / 起种文案仅由 getInitialInteractionWithoutZone 写入
                 tab.loaded = true;
 
                 // 优先用已缓存的 crop_code,避免重复打 farmer_crop_detail
@@ -602,16 +586,12 @@ const fetchVarietiesByCrop = async (cropId, force = false) => {
                 }
             } else {
                 tab.varieties = [];
-                tab.phenology = [];
-                tab.startingPointQuestion = "起种点问题";
                 if (showLoading) {
                     ElMessage.error(res.msg || "获取品种列表失败");
                 }
             }
         } catch {
             tab.varieties = [];
-            tab.phenology = [];
-            tab.startingPointQuestion = "起种点问题";
             if (showLoading) {
                 ElMessage.error("获取品种列表失败,请稍后再试");
             }

+ 11 - 1
src/views/old_mini/entry_information/index.vue

@@ -1,6 +1,11 @@
 <template>
     <div class="entry-information-page">
-        <custom-header name="录入信息" bgColor="#fff"></custom-header>
+        <custom-header
+            name="录入信息"
+            bgColor="#fff"
+            :isClose="hideBack"
+            :showClose="false"
+        ></custom-header>
         <div class="entry-information-body">
             <service-information v-if="currentStep === 1 && isServiceEntry" @next="handleNext" />
             <ba-information v-else-if="currentStep === 1" @next="handleNext" />
@@ -53,6 +58,11 @@ const ADMIN_ID_KEY = "ENTRY_ADMIN_ID";
 const router = useRouter();
 const route = useRoute();
 
+/** 无农场从登录页进入时隐藏返回箭头 */
+const hideBack = computed(
+    () => route.query.isClose === "1" || route.query.isClose === "true"
+);
+
 function readStep() {
     const step = Number(sessionStorage.getItem(STEP_KEY));
     return step >= 1 && step <= 5 ? step : 1;

+ 94 - 48
src/views/old_mini/growth_report/components/CropAlertPanel.vue

@@ -30,11 +30,11 @@
                                 <template #content>
                                     <div class="crop-option-list">
                                         <div
-                                            v-for="item in cropOptions"
-                                            :key="item"
+                                            v-for="(item, index) in cropOptions"
+                                            :key="index"
                                             class="crop-option-item"
-                                            :class="{ active: item === currentCropName }"
-                                            @click="handleSelectCrop(item)"
+                                            :class="{ active: index === activeIndex }"
+                                            @click="handleSelectCrop(index)"
                                         >
                                             {{ item }}
                                         </div>
@@ -100,7 +100,7 @@
 <script setup>
 import { FloatingPanel } from "vant";
 import { CaretBottom, Link } from "@element-plus/icons-vue";
-import { nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
+import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from "vue";
 import { useStore } from "vuex";
 import { useI18n } from "@/i18n";
 import { useRouter } from "vue-router";
@@ -110,32 +110,34 @@ const store = useStore();
 const { t } = useI18n();
 
 const GUIDE_STORAGE_KEY = "GROWTH_REPORT_CROP_SWITCH_GUIDE";
-const FALLBACK_QUERY_PARAMS = {
-    farm_id: "321",
-    zone_id: "46",
-    date: "2026-08-11",
-};
 const CARD_ID = {
     WARNING: "warning",
     STRESS: 2,
 };
 
 const props = defineProps({
+    /** 当前选中农场 id,切换农场后触发分区 / 预警重拉 */
+    farmId: {
+        type: [String, Number],
+        default: "",
+    },
     cropName: {
         type: String,
-        default: "水稻",
+        default: "",
     },
     cropAvatar: {
         type: String,
-        default: require("@/assets/img/home/banner.png"),
-    },
-    cropOptions: {
-        type: Array,
-        default: () => ["水稻", "荔枝", "香蕉"],
+        default: "https://birdseye-img.sysuimars.com/dinggou-mini/defalut-icon.png",
     },
 });
 
-const emit = defineEmits(["switchCategory", "viewDetail", "heightChange"]);
+/** 分区列表(getZones),选项文案用 zone_name */
+const zoneList = ref([]);
+const cropOptions = ref([]);
+const selectedZoneId = ref(null);
+const activeIndex = ref(0);
+
+const emit = defineEmits(["viewDetail", "heightChange"]);
 
 const weatherIcon = require("@/assets/img/report/weather.png");
 
@@ -149,14 +151,18 @@ const resolveTabBarHeight = () => {
 
 const tabBarHeight = ref(resolveTabBarHeight());
 
-const currentCropName = ref(props.cropName);
+const currentCropName = ref(props.cropName || "");
 const cropMenuVisible = ref(false);
 const guideVisible = ref(false);
 
 watch(
     () => props.cropName,
     (name) => {
-        if (name) currentCropName.value = name;
+        if (name) {
+            currentCropName.value = name;
+            fetchAlertCards();
+            fetchZones();
+        }
     }
 );
 
@@ -182,15 +188,23 @@ watch(cropMenuVisible, (visible) => {
     if (visible) dismissGuide();
 });
 
-const handleSelectCrop = (name) => {
-    dismissGuide();
-    if (!name || name === currentCropName.value) {
-        cropMenuVisible.value = false;
+const applySelectedZone = (index) => {
+    const zone = zoneList.value[index];
+    if (!zone) {
+        activeIndex.value = 0;
+        selectedZoneId.value = null;
         return;
     }
-    currentCropName.value = name;
+    activeIndex.value = index;
+    selectedZoneId.value = zone.zone_id ?? null;
+};
+
+const handleSelectCrop = (index) => {
+    dismissGuide();
     cropMenuVisible.value = false;
-    emit("switchCategory", name);
+    if (index === activeIndex.value) return;
+    applySelectedZone(index);
+    fetchAlertCards();
 };
 
 const PANEL_HEADER_BAR = 14; // van-floating-panel 原生 header 高度
@@ -293,21 +307,40 @@ const formatLocalDate = (date) => {
     return `${y}-${m}-${d}`;
 };
 
-/** 有选中农场用缓存 farm_id/zone_id + 当天日期,否则用默认参数 */
+/** 用当前选中农场 + 分区 + 当天日期 */
 const getQueryParams = () => {
     const farm = getSelectedFarm();
-    const farmId = farm.farm_id ?? farm.id;
-    const zoneId = farm.zone_id ?? farm.zoneId;
-    if (farmId == null || farmId === "" || zoneId == null || zoneId === "") {
-        return { ...FALLBACK_QUERY_PARAMS };
-    }
     return {
-        farm_id: farmId,
-        zone_id: zoneId,
+        farm_id: farm.farm_id ?? farm.id,
+        zone_id: selectedZoneId.value ?? farm.zone_id ?? farm.zoneId,
         date: formatLocalDate(new Date()),
     };
 };
 
+/** 拉取分区列表,填充切换作物下拉 */
+const fetchZones = async () => {
+    const farm = getSelectedFarm();
+    const farmId = props.farmId || farm.farm_id || farm.id;
+    if (farmId == null || farmId === "") {
+        zoneList.value = [];
+        cropOptions.value = [];
+        applySelectedZone(-1);
+        return;
+    }
+    try {
+        const res = await VE_API.questionnaire.getZones({ farm_id: farmId });
+        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) {
+        console.warn("[CropAlertPanel] getZones failed", e);
+        zoneList.value = [];
+        cropOptions.value = [];
+        applySelectedZone(-1);
+    }
+};
+
 /** 接口可能返回对象或数组,统一取第一条 */
 const pickFirstRecord = (res) => {
     const data = res?.data ?? res;
@@ -323,41 +356,47 @@ const patchAlertCard = (cardId, patch) => {
 };
 
 const applyWeatherRisk = (record) => {
-    if (!record) return;
     patchAlertCard(CARD_ID.WARNING, {
-        title: record.risk_name || "",
-        content: record.explain_simple || "",
+        title: record?.risk_name || "",
+        content: record?.explain_simple || "",
     });
 };
 
 const applyWeatherStress = (record) => {
-    if (!record) return;
-    if (record.crop_name) currentCropName.value = record.crop_name;
     patchAlertCard(CARD_ID.STRESS, {
-        title: record.stress_name || "",
-        content: record.explain_simple || "",
+        title: record?.stress_name || "",
+        content: record?.explain_simple || "",
     });
 };
 
 const fetchAlertCards = async () => {
     const queryParams = getQueryParams();
+    if (queryParams.farm_id == null || queryParams.farm_id === "") {
+        applyWeatherRisk(null);
+        applyWeatherStress(null);
+        nextTick(() => syncMidAnchor());
+        return;
+    }
     const [riskRes, stressRes] = await Promise.allSettled([
         VE_API.questionnaire.getWeatherRisk(queryParams),
         VE_API.questionnaire.getWeatherStress(queryParams),
     ]);
 
-    if (riskRes.status === "fulfilled") {
-        applyWeatherRisk(pickFirstRecord(riskRes.value));
-    }
-    if (stressRes.status === "fulfilled") {
-        applyWeatherStress(pickFirstRecord(stressRes.value));
-    }
+    // 成功但无数据时清空旧卡片,避免切换农场后残留
+    applyWeatherRisk(riskRes.status === "fulfilled" ? pickFirstRecord(riskRes.value) : null);
+    applyWeatherStress(stressRes.status === "fulfilled" ? pickFirstRecord(stressRes.value) : null);
 
     nextTick(() => syncMidAnchor());
 };
 
 const handleViewDetail = (item) => {
-    emit("viewDetail", item);
+    router.push({
+        path: "/alert_detail",
+        query: {
+            title: item?.title || "具体预警",
+            type: item?.source || "warning",
+        },
+    });
 };
 
 const handlePatrolTip = (item) => {
@@ -376,7 +415,6 @@ const handleHeightChange = ({ height: nextHeight }) => {
 };
 
 onMounted(() => {
-    fetchAlertCards();
     nextTick(() => {
         tabBarHeight.value = resolveTabBarHeight();
         // 等布局完成后再量一次,避免首屏高度偏小;小程序里 tabBar 可能稍晚才量到
@@ -403,11 +441,19 @@ onMounted(() => {
     document.addEventListener("click", handleOutsideClick, true);
 });
 
+/** 进入 / 从切换农场返回时:先拉分区,再拉预警 */
+onActivated(async () => {
+    await fetchZones();
+    await fetchAlertCards();
+});
+
 onBeforeUnmount(() => {
     document.removeEventListener("click", handleOutsideClick, true);
     resizeObserver?.disconnect?.();
     resizeObserver = null;
 });
+
+defineExpose({ refreshAlerts: fetchAlertCards, refreshZones: fetchZones });
 </script>
 
 <style lang="scss" scoped>

+ 50 - 30
src/views/old_mini/growth_report/index.vue

@@ -38,7 +38,7 @@
         <div v-if="!showInteractCard" class="location-bar">
             <el-icon class="location-bar__icon"><LocationFilled /></el-icon>
             <span class="location-bar__name van-ellipsis">{{ locationName }}</span>
-            <span class="location-bar__action" @click="handleSwitchLocation">切换位置</span>
+            <span class="location-bar__action" @click="handleSwitchFarm">切换农场</span>
         </div>
 
         <!-- 左侧:胁迫图层菜单 -->
@@ -97,9 +97,7 @@
 
         <crop-alert-panel
             :crop-name="currentCropName"
-            @switch-category="handleSwitchCategory"
-            @view-detail="handleViewDetail"
-            @patrol-tip="handlePatrolTip"
+            :farm-id="selectedFarmId"
             @height-change="handlePanelHeightChange"
         />
     </div>
@@ -142,13 +140,13 @@ const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 const mapContainer = ref(null);
 const growthReportMap = new GrowthReportMap();
 const locationName = ref("");
-const currentMapPoint = ref(
-    localStorage.getItem(MAP_POINT_STORAGE_KEY) ||
-        localStorage.getItem("selectedFarmPoint") ||
-        store.state.home.miniUserLocationPoint ||
-        DEFAULT_MAP_POINT
-);
-const currentCropName = ref("水稻");
+const selectedFarmId = ref(localStorage.getItem("selectedFarmId") || "");
+const currentCropName = computed(() => {
+    // 依赖 selectedFarmId,切换农场后重新读缓存
+    void selectedFarmId.value;
+    const farm = getSelectedFarm();
+    return farm.categoryLabel || farm.variety_name || farm.crop_name || "";
+});
 const activeMenuKey = ref("hot-drought-2");
 const panelHeight = ref(280);
 const inviteBottom = computed(() => panelHeight.value);
@@ -162,11 +160,30 @@ const getSelectedFarm = () => {
     }
 };
 
+/** 地图中心优先用农场 farm_location */
+const resolveFarmLocationWkt = (farm = getSelectedFarm()) => {
+    const candidates = [farm.farm_location, farm.wkt, farm.geom_wkt, localStorage.getItem("selectedFarmPoint")];
+    for (const item of candidates) {
+        if (typeof item === "string" && /^POINT\s*\(/i.test(item.trim())) {
+            return item.trim();
+        }
+    }
+    return store.state.home.miniUserLocationPoint || DEFAULT_MAP_POINT;
+};
+
+const currentMapPoint = ref(resolveFarmLocationWkt());
+
 const getSelectedZoneId = () => {
     const farm = getSelectedFarm();
     return farm.zone_id ?? farm.zoneId ?? "";
 };
 
+const syncSelectedFarmId = () => {
+    selectedFarmId.value = String(
+        localStorage.getItem("selectedFarmId") || getSelectedFarm().farm_id || getSelectedFarm().id || ""
+    );
+};
+
 const showInteractCard = ref(false);
 const interactQuestionText = ref("");
 const interactOptions = ref([]);
@@ -332,6 +349,24 @@ function applySelectedLocation() {
     return true;
 }
 
+/** 切换农场后:地图中心落到当前农场 farm_location */
+function syncMapFromSelectedFarm() {
+    const farm = getSelectedFarm();
+    const point = resolveFarmLocationWkt(farm);
+    if (!point) return false;
+    saveCurrentMapPoint(point);
+    const coordinate = convertPointToArray(point)?.map(Number);
+    if (growthReportMap.kmap && coordinate?.length === 2) {
+        growthReportMap.setMapPosition(coordinate);
+    }
+    getLocationName(point);
+    return true;
+}
+
+const handleSwitchFarm = () => {
+    router.push("/all_garden");
+};
+
 const handleSwitchLocation = () => {
     // 以已确认的 currentMapPoint 为准,避免地图临时中心覆盖已选位置
     const mapCenter =
@@ -465,37 +500,22 @@ function tryOpenInviteEntryPopup() {
     });
 }
 
-const handleSwitchCategory = (cropName) => {
-    if (!cropName) return;
-    currentCropName.value = cropName;
-};
-
-const handleViewDetail = (item) => {
-    router.push({
-        path: "/alert_detail",
-        query: {
-            title: item?.title || "具体预警",
-            type: item?.source || "warning",
-        },
-    });
-};
-
-const handlePatrolTip = () => {
-};
-
 const handlePanelHeightChange = (height) => {
     panelHeight.value = Number(height) || 0;
 };
 
 onMounted(() => {
+    syncSelectedFarmId();
+    syncMapFromSelectedFarm();
     getLocationName();
     initMap();
     fetchInteractTask();
     tryOpenInviteEntryPopup();
 });
 onActivated(() => {
+    syncSelectedFarmId();
     const applied = applySelectedLocation();
-    if (!applied) getLocationName();
+    if (!applied) syncMapFromSelectedFarm();
     initMap();
     fetchInteractTask();
     tryOpenInviteEntryPopup();