Prechádzať zdrojové kódy

feat:对接新增农场功能接口

wangsisi 4 hodín pred
rodič
commit
77c938af9d

+ 1 - 0
.env.dev

@@ -1,2 +1,3 @@
 SERVER = "https://feiniaotech-dev.sysuimars.cn/"
 PYSERVER = "https://birds-api-1-2.sysuimars.cn/"
+NEW_SERVER = "http://119.29.14.99:39306/"

+ 1 - 0
.env.pro

@@ -1,2 +1,3 @@
 SERVER = "https://birdseye-api.feiniaotech.sysuimars.cn/"
 PYSERVER = "https://birds-api-1-2.sysuimars.cn/"
+NEW_SERVER = "http://119.29.14.99:39306/"

+ 2 - 0
src/api/config.js

@@ -1,5 +1,6 @@
 let newServer = VE_ENV.SERVER
 let pyServer = VE_ENV.PYSERVER
+let newPathServer = VE_ENV.NEW_SERVER
 let oldServer = "https://birdseye-api.sysuimars.com/"
 let fosterServer = "https://foster-api.sysuimars.com/"
 
@@ -9,6 +10,7 @@ module.exports = {
     base_foster_url: fosterServer + 'app/',
     base_mini_url :oldServer + "mini/",
     base_dev_url :newServer + "mini/",
+    base_new_url :newPathServer + "api/",
     base_py_url :pyServer,
     image_url:oldServer+"images/",
     mini_key:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",

+ 10 - 49
src/api/modules/farm.js

@@ -5,18 +5,10 @@ module.exports = {
         url: config.base_dev_url + "container/specieList",
         type: "get",
     },
-    fetchPhenologyList: {
-        url: config.base_dev_url + "container/phenology/select/{containerId}",
-        type: "get",
-    },
     saveFarm: {
         url: config.base_dev_url + "v2/farm/createFarm",
         type: "post",
     },
-    updateFarm: {
-        url: config.base_dev_url + "v2/farm/updateFarm",
-        type: "post",
-    },
     userFarmSelectOption: {
         url: config.base_dev_url + "v2/farm/userFarmSelectOption",
         type: "get",
@@ -33,48 +25,15 @@ module.exports = {
         url: config.base_dev_url + "v2/farm/getFarmDetail",
         type: "get",
     },
-    listByIds: {
-        url: config.base_dev_url + "image/listByIds",
-        type: "post",
-    },
     getFarmWorkLib: {
         url: config.base_dev_url + "z_farm_work_lib/get",
         type: "get",
     },
-    //删除农场
-    deleteFarm: {
-        url: config.base_dev_url + "v2/farm/deleteFarm",
-        type: "get",
-    },
     //查询单个农事编排信息
     getFarmWorkArrangeDetail: {
         url: config.base_dev_url + "container_farm_work_arrange/get",
         type: "get",
     },
-    // 查有图片日期
-    findHasImagesDate: {
-        url: config.base_dev_url + "image_v2/findHasImagesDate",
-        type: "get",
-    },
-    // 获取图片列表
-    getImageInfo: {
-        url: config.base_dev_url + "image_v2/images/queryByDate",
-        type: "get",
-    },
-    // 获取农资店列表
-    getStoreList: {
-        url: config.base_dev_url + "z_agricultural_store/page/{page}/{limit}",
-        type: "get",
-    },
-    // 报价
-    getPriceList: {
-        url: config.base_dev_url + "agricultural_store_pesticide_fertilizer/findBySchemeAndPesticides",
-        type: "post",
-    },
-    updateBatchByScheme: {
-        url: config.base_dev_url + "agricultural_store_pesticide_fertilizer/updateBatchByScheme",
-        type: "post",
-    },
     // 生育期
     listByPhenologyId: {
         url: config.base_dev_url + "container_reproductive/listByPhenologyId",
@@ -85,17 +44,19 @@ module.exports = {
         url: config.base_dev_url + "v2/farm/farmBaseTypeOptions",
         type: "get",
     },
-    // 长势报告
-    growthReport: {
-        url: config.base_dev_url + "container/growthReport/generate/result",
+    //获取作物品类列表
+    findCategoryList: {
+        url: config.base_new_url + "find_categoly",
         type: "get",
     },
-    growthReportBySubject: {
-        url: config.base_dev_url + "container/growthReport/listBySubjectId",
-        type: "get",
+    //新建农场
+    createFarm: {
+        url: config.base_new_url + "farm_information_create",
+        type: "post",
     },
-    readReportByFarm: {
-        url: config.base_dev_url + "container/growthReport/read",
+    //获取农场列表
+    getFarmList: {
+        url: config.base_new_url + "find_farm_information",
         type: "get",
     },
 }

+ 1 - 0
src/components/gardenList.vue

@@ -146,6 +146,7 @@ function normalizeFarmList(data) {
 
 function getFarmList() {
     VE_API.farm.listByUserId().then(({ data }) => {
+    // VE_API.farm.getFarmList().then(({ data }) => {
         const fullData = normalizeFarmList(data);
         gardenList.value = fullData;
         if (!fullData.length) {

+ 0 - 1
src/router/globalRoutes.js

@@ -160,7 +160,6 @@ export default [
         meta: { keepAlive: true },
         component: () => import("@/views/old_mini/work_detail/index.vue"),
     },
-    // 农事详情
     {
         path: "/select_crop",
         name: "SelectCrop",

+ 152 - 132
src/views/old_mini/create_farm/index.vue

@@ -29,32 +29,31 @@
                         <div class="create-content">
                             <div class="create-from">
                                 <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" class="demo-ruleForm">
-                                    <el-form-item label="农场位置" prop="address">
+                                    <el-form-item label="农场位置" prop="farm_address">
                                         <div class="position-wrap">
-                                            <el-input placeholder="农场位置" readonly v-model="ruleForm.address"
+                                            <el-input placeholder="农场位置" readonly v-model="ruleForm.farm_address"
                                                 autocomplete="off" />
                                         </div>
                                     </el-form-item>
-                                    <el-form-item label="种植作物">
+                                    <el-form-item label="种植作物" required>
                                         <div class="select-wrap">
                                             <div class="species-entry" @click="handleGoSelectCrop">
-                                                <span class="species-entry__text">点击选择作物</span>
-                                                <el-icon><ArrowRight /></el-icon>
+                                                <span class="species-entry__text">{{ selectedCropSummaryText || "点击选择作物" }}</span>
                                             </div>
                                         </div>
                                     </el-form-item>
-                                    <el-form-item label="农场面积" prop="mu">
+                                    <el-form-item label="农场面积" prop="farm_area">
                                         <div class="area-box">
                                             <el-input :placeholder="isFromEditMap ? '勾选地块获得农场面积' : '请输入农场的真实面积'
-                                                " v-model="ruleForm.mu" :readonly="isFromEditMap" type="text"
+                                                " v-model="ruleForm.farm_area" :readonly="isFromEditMap" type="text"
                                                 autocomplete="off" style="width: fit-content" @input="handleMianjiInput"
                                                 @keypress="handleMianjiKeypress" />
                                             <div class="unit">亩</div>
                                         </div>
                                     </el-form-item>
-                                    <el-form-item label="农场名称" prop="name">
-                                        <el-input placeholder="请输入您的农场名称" v-model="ruleForm.name" autocomplete="off"
-                                            @input="handleFarmNameInput" />
+                                    <el-form-item label="农场名称" prop="farm_name">
+                                        <el-input placeholder="请输入您的农场名称" v-model="ruleForm.farm_name"
+                                            autocomplete="off" @input="handleFarmNameInput" />
                                     </el-form-item>
                                 </el-form>
                             </div>
@@ -75,10 +74,23 @@
                 </div>
             </div>
         </div>
+
+        <!-- 四大种植报告生成成功 -->
+        <Popup v-model:show="showPlantingReportPopup" round class="planting-report-popup"
+            :close-on-click-overlay="false">
+            <div class="planting-report-popup__inner">
+                <img class="icon" src="@/assets/img/home/right.png" alt="" />
+                <div class="title">您的四大种植报告<br />已全部生成</div>
+                <div class="links">
+                    <span class="link">(作物长势报告)</span>
+                    <span class="link">(历史风险报告)</span>
+                    <span class="link">(土壤改良报告)</span>
+                    <span class="link">(种植建议报告)</span>
+                </div>
+                <div class="btn" @click="handlePlantingReportView">点击查看</div>
+            </div>
+        </Popup>
     </div>
-    <!-- 农情采集成功弹窗 -->
-    <tip-popup v-model:show="showSuccessPopup" type="success" text="农情采集成功" @confirm="handlePopupConfirm"
-        @handleClickOverlay="handlePopupConfirm" />
 </template>
 
 <script setup>
@@ -86,13 +98,50 @@ import customHeader from "@/components/customHeader.vue";
 import IndexMap from "./map/index.js";
 import { useRoute, useRouter } from "vue-router";
 import { mapLocation } from "./map/index.js";
-import { onMounted, ref, reactive, watch, onActivated, nextTick, onDeactivated, computed } from "vue";
+import { onMounted, ref, reactive, watch, onActivated, nextTick, onDeactivated } from "vue";
 import { useStore } from "vuex";
 import { convertPointToArray } from "@/utils/index";
-import tipPopup from "@/components/popup/tipPopup.vue";
 import wx from "weixin-js-sdk";
+import { Popup } from "vant";
+import { ElMessage } from "element-plus";
 import { transformFromGCJToWGS, transformFromWGSToGCJ } from "@/utils/WSCoordinate.js";
 
+/** 与 selectCrop.vue 写入的 sessionStorage 键一致 */
+const SESSION_SELECT_CROP_KEY = "selectedCrop";
+
+/** @returns {Array<object>|null} 已选作物品类列表,未选择或数据无效时为 null */
+function parseSelectedCropItems() {
+    try {
+        const raw = sessionStorage.getItem(SESSION_SELECT_CROP_KEY);
+        if (!raw || raw === "null") return null;
+        const data = JSON.parse(raw);
+        if (!Array.isArray(data) || data.length === 0) return null;
+        return data;
+    } catch {
+        return null;
+    }
+}
+
+/** 入口展示:无选中为「点击选择作物」,有则 variety_name 去重后用顿号连接 */
+const selectedCropSummaryText = ref("");
+
+function refreshSelectedCropSummary() {
+    const items = parseSelectedCropItems();
+    if (!items?.length) {
+        selectedCropSummaryText.value = "";
+        return;
+    }
+    const seen = new Set();
+    const ordered = [];
+    for (const item of items) {
+        const name = String(item.variety_name ?? "").trim();
+        if (!name || seen.has(name)) continue;
+        seen.add(name);
+        ordered.push(name);
+    }
+    selectedCropSummaryText.value = ordered.join("、");
+}
+
 const route = useRoute();
 const router = useRouter();
 const store = useStore();
@@ -100,13 +149,13 @@ const store = useStore();
 const indexMap = new IndexMap();
 const mapContainer = ref(null);
 
-// 农情采集成功弹窗
-const showSuccessPopup = ref(false);
+/** 创建成功后「四大种植报告」提示弹窗 */
+const showPlantingReportPopup = ref(false);
 
-const handlePopupConfirm = () => {
-    showSuccessPopup.value = false;
-    router.replace(`/home`);
-};
+function handlePlantingReportView() {
+    showPlantingReportPopup.value = false;
+    backgToHome();
+}
 
 // 标记是否从编辑地图页面确认返回
 const isFromEditMap = ref(false);
@@ -165,12 +214,12 @@ onMounted(() => {
     // 不再初始化时绘制默认地块,等待用户点击"新增地块"按钮
     // 清空地块和面积数据
     polygonArr.value = null;
-    ruleForm.mu = "";
+    ruleForm.farm_area = "";
 
     if (route.query.type === "edit" && store.state.home.editFarmData) {
         populateEditData();
     }
-    getBaseTypeList();
+    refreshSelectedCropSummary();
 });
 
 const polygonArr = ref(null);
@@ -213,6 +262,7 @@ onActivated(() => {
         }
         handleMapUpdate();
     });
+    refreshSelectedCropSummary();
 });
 
 onDeactivated(() => {
@@ -236,8 +286,7 @@ function handleMapUpdate() {
 
         // 不再自动生成默认地块,等待用户点击"新增地块"
         polygonArr.value = null;
-        ruleForm.mu = "";
-        ruleForm.defaultFarm = 0;
+        ruleForm.farm_area = "";
 
         return; // 直接返回,不执行下面的逻辑
     }
@@ -258,7 +307,7 @@ function handleMapUpdate() {
             if (polygonData.isConfirmed) {
                 // 用户点击了"确认"按钮,锁定输入框并显示精确面积
                 isFromEditMap.value = true;
-                ruleForm.mu = polygonData.mianji;
+                ruleForm.farm_area = polygonData.mianji;
             } else {
                 // 用户点击了"取消",不锁定输入框,面积保持原样
                 isFromEditMap.value = false;
@@ -273,9 +322,8 @@ function handleMapUpdate() {
             // 用户在编辑页面删除了所有地块
             // 重置所有状态
             polygonArr.value = null;
-            ruleForm.mu = "";
+            ruleForm.farm_area = "";
             isFromEditMap.value = false;
-            ruleForm.defaultFarm = 0;
         }
     } else if (route.query.type === "edit" && store.state.home.editFarmData) {
         // 如果是编辑模式且没有从编辑地图返回的数据,回填原始编辑数据
@@ -334,7 +382,7 @@ const handleSearchRes = (v) => {
     const coordinateArray = [longitude, latitude];
     indexMap.setMapPosition(coordinateArray);
     centerPoint.value = `POINT (${coordinateArray[0]} ${coordinateArray[1]})`;
-    ruleForm.address = v.item?.title || v.item?.address;
+    ruleForm.farm_address = v.item?.title || v.item?.address;
     pointAddress.value = v.item?.province + v.item?.city + (v.item?.district || '');
     // 更新farmCity以便后续更新农场名称
     farmCity.value = v.item?.city + (v.item?.district || "");
@@ -345,15 +393,9 @@ const handleSearchRes = (v) => {
 // 表单
 const ruleFormRef = ref(null);
 const ruleForm = reactive({
-    address: "",
-    mu: "",
-    // 多选品类:数组
-    speciesItem: [],
-    name: "",
-    fzr: "",
-    tel: "",
-    baseType: null,
-    defaultFarm: 0, // 0:否 1:是
+    farm_address: "",
+    farm_area: "",
+    farm_name: "",
 });
 // 自定义验证规则:验证面积必须是大于0的数字
 const validateMianji = (rule, value, callback) => {
@@ -372,47 +414,35 @@ const validateMianji = (rule, value, callback) => {
 };
 
 const rules = reactive({
-    address: [{ required: true, message: "请选择农场位置", trigger: "blur" }],
-    mu: [
+    farm_address: [{ required: true, message: "请选择农场位置", trigger: "blur" }],
+    farm_area: [
         { required: true, message: "请输入农场面积", trigger: "blur" },
         { validator: validateMianji, trigger: ["blur", "change"] },
     ],
-    name: [{ required: true, message: "请输入您的农场名称", trigger: ["blur", "change"] }],
-    fzr: [{ required: true, message: "请输入联系人姓名", trigger: ["blur", "change"] }],
-    tel: [
-        { required: true, message: "请输入联系人电话", trigger: ["blur"] },
-        {
-            pattern: /^1[3-9]\d{9}$/,
-            message: "请输入正确的手机号码",
-            trigger: ["blur"],
-        },
-    ],
-    defaultFarm: [{ required: true, message: "请选择是否为默认农场", trigger: "blur" }],
-    baseType: [{ required: true, message: "请选择基地类别", trigger: "blur" }],
+    farm_name: [{ required: true, message: "请输入您的农场名称", trigger: ["blur", "change"] }],
 });
 
 const submitForm = (formEl) => {
     if (!formEl) return;
     formEl.validate((valid) => {
         if (valid) {
-            const speciesList = Array.isArray(ruleForm.speciesItem) ? ruleForm.speciesItem : (ruleForm.speciesItem ? [ruleForm.speciesItem] : []);
-            const mainSpecies = speciesList[0];
-            const speciesContainer = speciesList.map((item) => ({
-                speciesId: item?.id ?? null,
-                containerId: item?.defaultContainerId ?? null,
-            }));
+            const cropItems = parseSelectedCropItems();
+            if (!cropItems) {
+                ElMessage.warning("请先点击「种植作物」选择作物品类");
+                return;
+            }
             const params = {
                 ...ruleForm,
-                wkt: centerPoint.value,
-                speciesContainer,
-                agriculturalCreate: route.query.type === "client" ? 1 : 0,
+                farm_location: centerPoint.value,
+                farm_variety: cropItems.map((item) => item.variety_code).join(","),
+                farm_category: cropItems.map((item) => item.categorycode).join(","),
                 // 编辑时geom不是数组,新增时是数组
-                geom:
-                    route.query.type === "edit"
-                        ? polygonArr.value && polygonArr.value.length > 0
-                            ? polygonArr.value[0]
-                            : null
-                        : polygonArr.value,
+                // geom:
+                //     route.query.type === "edit"
+                //         ? polygonArr.value && polygonArr.value.length > 0
+                //             ? polygonArr.value[0]
+                //             : null
+                //         : polygonArr.value,
             };
 
             // 如果是编辑模式,添加农场ID
@@ -424,24 +454,18 @@ const submitForm = (formEl) => {
                 // 处理 geom 参数,如果是数组需要序列化
                 const queryParams = {
                     ...params,
-                    ...route.query,
                 };
                 // 如果 geom 是数组,需要序列化为 JSON 字符串
                 if (Array.isArray(queryParams.geom)) {
                     queryParams.geom = JSON.stringify(queryParams.geom);
                 }
-                // speciesContainer 为对象时需序列化,否则 query 传递后无法正确解析
-                if (queryParams.speciesContainer && typeof queryParams.speciesContainer === 'object') {
-                    queryParams.speciesContainer = JSON.stringify(queryParams.speciesContainer);
-                }
-                delete queryParams.speciesItem;
-                queryParams.speciesName = mainSpecies?.name;
 
-                router.push({
-                    path: "/prescription",
-                    query: queryParams,
+                VE_API.farm.createFarm(queryParams).then(({ data, code }) => {
+                    if (code === 200) {
+                        showPlantingReportPopup.value = true; 
+                        sessionStorage.removeItem(SESSION_SELECT_CROP_KEY);
+                    }
                 });
-                return;
             }
         }
     });
@@ -478,7 +502,7 @@ function getLocationName(location) {
     };
     VE_API.old_mini_map.location(params).then(({ result }) => {
         const add = result.formatted_addresses?.recommend ? result.formatted_addresses.recommend : result.address + "";
-        ruleForm.address = add;
+        ruleForm.farm_address = add;
         pointAddress.value = result.address;
         farmCity.value = result.address_component?.city + (result.address_component?.district || "");
         // 地址修改后,如果满足条件则自动更新农场名称
@@ -494,16 +518,9 @@ watch(() => mapLocation.data, (newValue) => {
     }
 });
 
-const baseTypeList = ref([]);
-function getBaseTypeList() {
-    return VE_API.farm.fetchBaseTypeList().then(({ data }) => {
-        baseTypeList.value = data || [];
-        return data;
-    });
-}
-
 function backgToHome() {
     ruleFormRef.value?.resetFields();
+    sessionStorage.removeItem(SESSION_SELECT_CROP_KEY);
 
     // 根据来源页面决定返回目标
     const fromPage = route.query?.from;
@@ -556,7 +573,7 @@ function handleMianjiKeypress(event) {
     }
 
     // 如果已经有小数点,不允许再输入小数点
-    const currentValue = ruleForm.mu || "";
+    const currentValue = ruleForm.farm_area || "";
     if (char === "." && currentValue.includes(".")) {
         event.preventDefault();
         return;
@@ -578,8 +595,8 @@ function handleFarmNameInput() {
 // 根据地址更新农场名称(如果满足条件)
 function updateFarmNameIfNeeded() {
     // 只有在创建模式下且用户没有手动修改过农场名称时,才自动更新农场名称
-    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && !ruleForm.name) {
-        ruleForm.name = farmCity.value + "农场";
+    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && !ruleForm.farm_name) {
+        ruleForm.farm_name = farmCity.value + "农场";
     }
 }
 
@@ -591,13 +608,9 @@ function populateEditData() {
     }
 
     // 回填基本信息
-    ruleForm.name = editData.name || "";
-    ruleForm.fzr = editData.fzr || "";
-    ruleForm.tel = editData.tel || "";
-    ruleForm.baseType = editData.baseType || null;
-    ruleForm.defaultFarm = editData.defaultOption || 0;
-    ruleForm.mu = editData.mianji || "";
-    ruleForm.address = editData.address || "";
+    ruleForm.farm_name = editData.name || "";
+    ruleForm.farm_area = editData.mianji || "";
+    ruleForm.farm_address = editData.address || "";
 
     // 设置地图中心点
     if (editData.pointWkt) {
@@ -613,38 +626,6 @@ function populateEditData() {
         isFromEditMap.value = true; // 编辑模式下锁定面积输入
     }
 
-    // 处理作物数据(兼容多选)
-    if (Array.isArray(editData.regionList) && editData.regionList.length > 0) {
-        const selected = [];
-        const speciesIdSet = new Set();
-        editData.regionList.forEach((region) => {
-            const targetId = region.speciesId;
-            if (targetId == null) return;
-            // 去重:同一个 speciesId 只保留一条
-            const speciesKey = String(targetId);
-            if (speciesIdSet.has(speciesKey)) return;
-            speciesIdSet.add(speciesKey);
-            selected.push({
-                value: targetId,
-                id: targetId,
-                name: region.regionName || region.speciesName || "",
-                defaultContainerId: region.containerId ?? null,
-            });
-        });
-        if (selected.length > 0) {
-            ruleForm.speciesItem = selected;
-        }
-    } else if (editData.speciesId && editData.speciesName) {
-        // 兼容旧数据:只有单个品类字段时,按原逻辑回显一个
-        const species = {
-            value: editData.speciesId,
-            id: editData.speciesId,
-            name: editData.speciesName,
-            defaultContainerId: editData.containerId,
-        };
-        ruleForm.speciesItem = [species];
-    }
-
     // 设置地址信息
     if (editData.district) {
         try {
@@ -680,7 +661,7 @@ function handleMianjiInput(value) {
 
     // 更新输入框的值(如果被过滤了)
     if (filteredValue !== value) {
-        ruleForm.mu = filteredValue;
+        ruleForm.farm_area = filteredValue;
         return;
     }
 
@@ -885,8 +866,7 @@ function handleMianjiInput(value) {
                     .species-entry {
                         display: inline-flex;
                         align-items: center;
-                        gap: 2px;
-                        color: #2199f8;
+                        color: #000;
                         cursor: pointer;
                     }
                 }
@@ -972,6 +952,46 @@ function handleMianjiInput(value) {
         }
     }
 }
+
+.planting-report-popup {
+    width: 82%;
+    padding: 28px 20px 20px;
+
+    .planting-report-popup__inner {
+        text-align: center;
+
+        .icon {
+            width: 68px;
+            height: 68px;
+            margin-bottom: 16px;
+        }
+
+        .title {
+            font-size: 24px;
+        }
+
+        .links {
+            display: flex;
+            flex-wrap: wrap;
+            justify-content: center;
+            gap: 10px 16px;
+            margin: 18px 0 22px;
+
+            .link {
+                font-size: 18px;
+                color: #2199f8;
+            }
+        }
+
+        .btn {
+            padding: 8px;
+            border-radius: 25px;
+            font-size: 16px;
+            color: #fff;
+            background: #2199F8;
+        }
+    }
+}
 </style>
 
 <style lang="scss">

+ 393 - 175
src/views/old_mini/create_farm/selectCrop.vue

@@ -3,41 +3,44 @@
         <custom-header name="选择作物"></custom-header>
         <div class="page-body">
             <div class="top-tabs">
-                <div v-for="tab in topTabs" :key="tab.id" class="tab-item" :class="{ active: activeTopTab === tab.id }"
-                    @click="scrollToTab(tab.id)">
-                    {{ tab.name }}
+                <div v-for="(m, mi) in majorTabList" :key="m.key" class="tab-item"
+                    :class="{ active: activeMajorKey === m.key }" @click="scrollToMajor(mi)">
+                    {{ m.label }}
                 </div>
             </div>
 
             <div ref="categoryScrollEl" class="category-sections">
-                <div v-for="tab in topTabs" :key="tab.id" :id="tabAnchorId(tab.id)" class="tab-anchor-block">
-                    <template v-for="section in cropSource[tab.id] || []" :key="sectionKey(tab.id, section.id)">
-                        <div class="category-section">
-                            <div class="section-header">
-                                <div class="section-title">
-                                    <span class="title-bar"></span>
-                                    <span>{{ section.name }}</span>
-                                </div>
-                                <el-input v-model="searchMap[sectionKey(tab.id, section.id)]" class="search-wrap"
-                                    placeholder="搜索品类" :prefix-icon="Search" />
+                <div v-for="(m, mi) in majorTabList" :key="m.key" :id="majorAnchorId(mi)" class="major-anchor-block">
+                    <div class="category-section">
+                        <div class="section-header">
+                            <div class="section-title">
+                                <span class="title-bar"></span>
+                                <span>{{ m.label }}</span>
                             </div>
+                            <el-input v-model="searchMap[majorBlockKey(m.key)]" class="search-wrap" placeholder="搜索品类">
+                                <template #prefix>
+                                    <el-icon>
+                                        <Search />
+                                    </el-icon>
+                                </template>
+                            </el-input>
+                        </div>
 
-                            <div class="crop-grid">
-                                <div v-for="crop in getVisibleCrops(tab.id, section)" :key="crop.id" class="crop-item"
-                                    :class="{ selected: isSelected(crop.id) }"
-                                    @click="handleCropClick(tab.id, section, crop.id)">
-                                    <span>{{ crop.name }}</span>
-                                    <span v-if="isSelected(crop.id)" class="selected-mark">
-                                        <el-icon><Select /></el-icon>
-                                    </span>
-                                </div>
+                        <div class="crop-grid">
+                            <div class="crop-item" v-for="tab in getVisibleTabsForMajor(m.key)" :key="blockKey(tab)"
+                                :class="{ selected: isTabVarietySelected(tab) }" @click="handleVarietyTileClick(tab)">
+                                <span>{{ tab.variety_name }}</span>
+                                <span v-if="isTabVarietySelected(tab)" class="selected-mark">
+                                    <el-icon><Select /></el-icon>
+                                </span>
                             </div>
+                        </div>
 
-                            <div class="expand-trigger" @click="toggleExpand(tab.id, section.id)">
-                                {{ expandedSections[sectionKey(tab.id, section.id)] ? "收起" : "点击展开更多" }}
-                            </div>
+                        <div v-if="shouldShowExpandForMajor(m.key)" class="expand-trigger"
+                            @click="toggleExpandMajor(m.key)">
+                            {{ expandedSections[majorBlockKey(m.key)] ? "收起" : "点击展开更多" }}
                         </div>
-                    </template>
+                    </div>
                 </div>
             </div>
         </div>
@@ -49,27 +52,34 @@
     <popup v-model:show="showPopup" class="crop-popup" round>
         <div class="popup-title">请选择具体品种</div>
         <div class="popup-body">
-            <div v-if="popupSection" class="category-section">
+            <div v-if="popupVarietyDetail" class="category-section">
                 <div class="section-header">
                     <div class="section-title">
                         <span class="title-bar"></span>
-                        <span>{{ popupSection.name }}</span>
+                        <span>{{ popupVarietyDetail.varietyName }}</span>
                     </div>
-                    <el-input v-model="popupSearchKeyword" class="search-wrap" placeholder="搜索品种" :prefix-icon="Search" />
+                    <el-input v-model="popupSearchKeyword" class="search-wrap" placeholder="搜索品种">
+                        <template #prefix>
+                            <el-icon>
+                                <Search />
+                            </el-icon>
+                        </template>
+                    </el-input>
                 </div>
                 <div class="crop-grid">
-                    <div v-for="crop in popupVisibleCrops" :key="crop.id" class="crop-item"
-                        :class="{ selected: isSelected(crop.id) }" @click="toggleCrop(crop.id)">
-                        <span>{{ crop.name }}</span>
-                        <span v-if="isSelected(crop.id)" class="selected-mark">
+                    <div v-for="(cat, catIdx) in popupVisibleCategories" :key="categoryRowKey(cat, catIdx)"
+                        class="crop-item" :class="{ selected: isPopupSelected(cat) }"
+                        @click="togglePopupCrop(cat)">
+                        <span>{{ cat.category_name }}</span>
+                        <span v-if="isPopupSelected(cat)" class="selected-mark">
                             <el-icon><Select /></el-icon>
                         </span>
                     </div>
                 </div>
             </div>
-            <div class="switch-btn" @click="handleSwitch">换一换</div>
+            <div v-if="shouldShowPopupSwitch" class="switch-btn" @click="handleSwitch">换一换</div>
             <div class="popup-actions">
-                <div class="action-btn cancel-btn" @click="clearSelection">取消选中</div>
+                <div class="action-btn cancel-btn" @click="clearPopupSelection">取消选中</div>
                 <div class="action-btn confirm-btn" @click="handlePopupConfirm">确认</div>
             </div>
         </div>
@@ -78,9 +88,12 @@
 
 <script setup>
 import CustomHeader from "@/components/customHeader.vue";
-import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
+import { ElMessage } from "element-plus";
+import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref } from "vue";
 import { Popup } from "vant";
-import { Search, Select } from "@element-plus/icons-vue";
+import { useRouter } from "vue-router";
+
+const router = useRouter();
 
 const SECTION_VISIBLE_COUNT = 9;
 const POPUP_PAGE_SIZE = 12;
@@ -88,74 +101,17 @@ const TAB_SCROLL_MARGIN = 8;
 const TAB_SCROLL_LEAD = 16;
 const PROGRAMMATIC_SCROLL_UNLOCK_MS = 750;
 
-const topTabs = [
-    { id: "tree", name: "类别" },
-    { id: "grain", name: "类别" },
-    { id: "bean", name: "类别" },
-    { id: "other", name: "类别" },
-];
-
-const cropSource = {
-    tree: [
-        {
-            id: "s1",
-            name: "类别",
-            crops: Array.from({ length: 10 }, (_, idx) => ({
-                id: `tree-s1-${idx + 1}`,
-                name: "品类",
-            })),
-        },
-        {
-            id: "s2",
-            name: "类别",
-            crops: Array.from({ length: 10 }, (_, idx) => ({
-                id: `tree-s2-${idx + 1}`,
-                name: "品类",
-            })),
-        },
-        {
-            id: "s3",
-            name: "类别",
-            crops: Array.from({ length: 10 }, (_, idx) => ({
-                id: `tree-s3-${idx + 1}`,
-                name: "品类",
-            })),
-        },
-    ],
-    grain: [
-        {
-            id: "s1",
-            name: "类别",
-            crops: Array.from({ length: 9 }, (_, idx) => ({
-                id: `grain-s1-${idx + 1}`,
-                name: "品类",
-            })),
-        },
-    ],
-    bean: [
-        {
-            id: "s1",
-            name: "类别",
-            crops: Array.from({ length: 9 }, (_, idx) => ({
-                id: `bean-s1-${idx + 1}`,
-                name: "品类",
-            })),
-        },
-    ],
-    other: [
-        {
-            id: "s1",
-            name: "类别",
-            crops: Array.from({ length: 9 }, (_, idx) => ({
-                id: `other-s1-${idx + 1}`,
-                name: "品类",
-            })),
-        },
-    ],
-};
+// 接口 data 下按「大田作物 / 常绿果树」等大类的分组;顶部 Tab 展示这一层
+const majorTabList = ref([]);
+const activeMajorKey = ref(null);
+
+// 扁平品种列表(含 majorKey),供弹窗查找、一大类下筛选用
+const categoryList = ref([]);
 
-const activeTopTab = ref(topTabs[0].id);
+/** 页面最终选中(底部确认带回):仅弹窗点「确认」后写入 */
 const selectedCropIds = ref(new Set());
+/** 弹窗内临时勾选,与外面列表互不影响 */
+const popupSelectedIds = ref(new Set());
 const expandedSections = ref({});
 const searchMap = ref({});
 const showPopup = ref(false);
@@ -181,17 +137,25 @@ const clearProgrammaticScrollWatchers = (root) => {
     /** 取消未完成的点击滚动时释放锁,避免连点清掉定时器后无法跟手滚动 */
     tabScrollProgrammatic.value = false;
 };
-/** 弹窗展示的数据:来自用户点击的 tab + 类别块 */
-const popupContext = ref({ tabId: null, sectionId: null });
-
-const sectionKey = (tabId, sectionId) => `${tabId}-${sectionId}`;
-const tabAnchorId = (tabId) => `crop-tab-anchor-${tabId}`;
-const getTabAnchorEl = (root, tabId) => root?.querySelector(`#${tabAnchorId(tabId)}`);
-const filterByKeyword = (list, keyword) => {
-    const normalizedKeyword = (keyword || "").trim();
-    if (!normalizedKeyword) return list;
-    return list.filter((item) => item.name.includes(normalizedKeyword));
+/** 弹窗:当前选中的品种 tab(variety_code + majorKey 定位 categoryList 中的一项) */
+const popupContext = ref({ tabId: null, majorKey: null });
+
+/** 同一页内搜索/展开区块 key;含一大类时避免不同大类下 variety_code 冲突 */
+const blockKey = (tab) => {
+    if (tab.majorKey != null && tab.majorKey !== "") {
+        return `${tab.majorKey}:${tab.variety_code}`;
+    }
+    return String(tab.variety_code);
 };
+
+const majorAnchorId = (majorIndex) => `major-anchor-${majorIndex}`;
+
+/** 一大类整块卡片:搜索 / 展开 共用同一 key */
+const majorBlockKey = (majorKey) => String(majorKey);
+
+const tabsUnderMajor = (majorKey) =>
+    categoryList.value.filter((t) => t.majorKey === majorKey);
+
 const sliceByPage = (list, page, pageSize) => {
     const start = page * pageSize;
     return list.slice(start, start + pageSize);
@@ -206,13 +170,40 @@ const toggleSetItem = (sourceSet, item) => {
     return next;
 };
 
-const popupSection = computed(() => {
-    const { tabId, sectionId } = popupContext.value;
-    if (!tabId || !sectionId) return null;
-    const sections = cropSource[tabId] || [];
-    return sections.find((s) => s.id === sectionId) || null;
+/** 弹窗标题为点击的品种名;列表为该品种下 categories(category_name / categorycode) */
+const popupVarietyDetail = computed(() => {
+    const { tabId, majorKey } = popupContext.value;
+    if (tabId == null) return null;
+    const tab = categoryList.value.find((c) => {
+        if (String(c.variety_code) !== String(tabId)) return false;
+        if (majorKey != null && majorKey !== "") return c.majorKey === majorKey;
+        return c.majorKey == null || c.majorKey === "";
+    });
+    if (!tab) return null;
+    return {
+        varietyName: tab.variety_name ?? "",
+        categories: Array.isArray(tab.categories) ? tab.categories : [],
+    };
+});
+
+const popupFilteredCategories = computed(() => {
+    const list = popupVarietyDetail.value?.categories ?? [];
+    const kw = (popupSearchKeyword.value || "").trim();
+    if (!kw) return list;
+    return list.filter((c) =>
+        String(c.category_name ?? c.name ?? "").includes(kw)
+    );
 });
 
+const popupVisibleCategories = computed(() =>
+    sliceByPage(popupFilteredCategories.value, popupPage.value, POPUP_PAGE_SIZE)
+);
+
+/** 品类超过 9 条才需要「换一换」分页切换 */
+const shouldShowPopupSwitch = computed(
+    () => popupFilteredCategories.value.length > SECTION_VISIBLE_COUNT
+);
+
 /** 锚点顶部相对滚动容器内容区的纵向位置(与 scrollTop 同坐标系) */
 const anchorContentTop = (root, el) => {
     const elRect = el.getBoundingClientRect();
@@ -220,21 +211,24 @@ const anchorContentTop = (root, el) => {
     return root.scrollTop + (elRect.top - rootRect.top);
 };
 
-/** 根据滚动位置更新顶部 Tab 高亮(与点击 scrollToTab 共用 activeTopTab) */
-const syncActiveTopTabFromScroll = () => {
+/** 一大类 Tab:按滚动位置高亮当前大类(定位而非切换) */
+const syncActiveMajorFromScroll = () => {
     const root = categoryScrollEl.value;
     if (!root) return;
+    const majors = majorTabList.value;
+    if (!majors.length) return;
     const st = root.scrollTop;
-    let nextId = topTabs[0].id;
-    for (const tab of topTabs) {
-        const el = getTabAnchorEl(root, tab.id);
+    let nextIdx = 0;
+    for (let i = 0; i < majors.length; i++) {
+        const el = root.querySelector(`#${majorAnchorId(i)}`);
         if (!el) continue;
         if (anchorContentTop(root, el) <= st + TAB_SCROLL_LEAD) {
-            nextId = tab.id;
+            nextIdx = i;
         }
     }
-    if (activeTopTab.value !== nextId) {
-        activeTopTab.value = nextId;
+    const nextKey = majors[nextIdx]?.key ?? null;
+    if (activeMajorKey.value !== nextKey) {
+        activeMajorKey.value = nextKey;
     }
 };
 
@@ -243,15 +237,18 @@ const scheduleSyncFromScroll = () => {
     if (scrollRaf) return;
     scrollRaf = requestAnimationFrame(() => {
         scrollRaf = 0;
-        syncActiveTopTabFromScroll();
+        syncActiveMajorFromScroll();
     });
 };
 
-const scrollToTab = (tabId) => {
-    activeTopTab.value = tabId;
+const scrollToMajor = (majorIndex) => {
+    const majors = majorTabList.value;
+    const key = majors[majorIndex]?.key;
+    if (key == null) return;
+    activeMajorKey.value = key;
     nextTick(() => {
         const root = categoryScrollEl.value;
-        const target = getTabAnchorEl(root, tabId);
+        const target = root?.querySelector(`#${majorAnchorId(majorIndex)}`);
         if (!root || !target) return;
         clearProgrammaticScrollWatchers(root);
         tabScrollProgrammatic.value = true;
@@ -260,12 +257,10 @@ const scrollToTab = (tabId) => {
             if (unlocked) return;
             unlocked = true;
             clearProgrammaticScrollWatchers(root);
-            /** 以点击目标为准,避免动画结束瞬间用 scroll 反算出现中间 Tab */
-            activeTopTab.value = tabId;
+            activeMajorKey.value = key;
         };
         progScrollEndHandler = () => unlock();
         root.addEventListener("scrollend", progScrollEndHandler);
-        /** 只滚内容区,避免 scrollIntoView 带动外层滚动;与 .tab-anchor-block 的 scroll-margin-top 一致 */
         const top = anchorContentTop(root, target);
         root.scrollTo({ top: Math.max(0, top - TAB_SCROLL_MARGIN), behavior: "smooth" });
         progUnlockTimer = window.setTimeout(unlock, PROGRAMMATIC_SCROLL_UNLOCK_MS);
@@ -288,72 +283,278 @@ const unbindCategoryScroll = () => {
 
 onMounted(() => {
     bindCategoryScroll();
-    nextTick(() => syncActiveTopTabFromScroll());
+    nextTick(() => syncActiveMajorFromScroll());
+    getCategoryList();
+});
+
+/** keep-alive 再次进入:若无已保存的选中数据,清空页面勾选与弹窗状态 */
+const resetCropSelectionState = () => {
+    selectedCropIds.value = new Set();
+    popupSelectedIds.value = new Set();
+    showPopup.value = false;
+    popupSearchKeyword.value = "";
+    popupPage.value = 0;
+    popupContext.value = { tabId: null, majorKey: null };
+};
+
+onActivated(() => {
+    try {
+        const raw = sessionStorage.getItem("selectedCrop");
+        if (raw == null || String(raw).trim() === "") {
+            resetCropSelectionState();
+        }
+    } catch {
+        resetCropSelectionState();
+    }
 });
 
+/** 从 data 对象里取出「值为数组」的字段作为顶层大类 Tab(排除 total_majors 等元数据) */
+const META_MAJOR_KEYS = new Set(["total_majors", "totalMajors"]);
+
+const extractMajorTabGroups = (raw) => {
+    if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return [];
+    const out = [];
+    for (const [key, val] of Object.entries(raw)) {
+        if (META_MAJOR_KEYS.has(key)) continue;
+        if (Array.isArray(val)) {
+            out.push({ key, label: key });
+        }
+    }
+    return out;
+};
+
+/** 兼容后端字段命名;保证 categories 一定有数组 */
+const normalizeCategoryTab = (item, index) => {
+    const categories =
+        item.categories ??
+        item.category_list ??
+        item.categoryList ??
+        item.children ??
+        item.subList ??
+        [];
+    return {
+        ...item,
+        variety_code: item.variety_code ?? item.varietyCode ?? item.code ?? `tab-${index}`,
+        variety_name: item.variety_name ?? item.varietyName ?? item.name ?? "",
+        categories: Array.isArray(categories) ? categories : [],
+    };
+};
+
+/** 0/1:项目内成功码;200:常见后端;无 code 且带 data:一并兼容 */
+const isListResponseOk = (res) => {
+    const c = res?.code;
+    if (c === 0 || c === 1 || c === 200) return true;
+    if (c == null && res && Object.prototype.hasOwnProperty.call(res, "data")) return true;
+    return false;
+};
+
+const getCategoryList = () => {
+    VE_API.farm.findCategoryList().then((res) => {
+        if (!isListResponseOk(res)) return;
+        const raw = res?.data !== undefined ? res.data : res;
+        const majors = extractMajorTabGroups(raw);
+        if (majors.length) {
+            majorTabList.value = majors;
+            const flat = [];
+            for (const m of majors) {
+                const arr = Array.isArray(raw[m.key]) ? raw[m.key] : [];
+                arr.forEach((item, i) => {
+                    flat.push({
+                        ...normalizeCategoryTab(item, i),
+                        majorKey: m.key,
+                    });
+                });
+            }
+            categoryList.value = flat;
+            activeMajorKey.value = majors[0]?.key ?? null;
+            nextTick(() => syncActiveMajorFromScroll());
+            return;
+        }
+        majorTabList.value = [];
+        categoryList.value = [];
+        activeMajorKey.value = null;
+    }).catch(() => { });
+};
+
 onBeforeUnmount(() => {
     clearProgrammaticScrollWatchers(categoryScrollEl.value);
     unbindCategoryScroll();
 });
 
-const getFilteredCrops = (tabId, section) => {
-    const key = sectionKey(tabId, section.id);
-    return filterByKeyword(section.crops, searchMap.value[key]);
+/** 一大类下:按搜索过滤品种 tile,展开前最多 SECTION_VISIBLE_COUNT 个 */
+const filterTabsByMajorSearch = (tabs, keyword) => {
+    const k = (keyword || "").trim();
+    if (!k) return tabs || [];
+    return (tabs || []).filter((tab) => {
+        if (String(tab.variety_name || "").includes(k)) return true;
+        return (tab.categories || []).some((c) =>
+            String(c.category_name ?? c.name ?? "").includes(k)
+        );
+    });
 };
 
-const getVisibleCrops = (tabId, section) => {
-    const list = getFilteredCrops(tabId, section);
-    const key = sectionKey(tabId, section.id);
-    return expandedSections.value[key] ? list : list.slice(0, SECTION_VISIBLE_COUNT);
+/** 当前搜索条件下该大类下的全部品种(不分页) */
+const getMajorFilteredTabList = (majorKey) => {
+    const tabs = tabsUnderMajor(majorKey);
+    const kw = searchMap.value[majorBlockKey(majorKey)];
+    return filterTabsByMajorSearch(tabs, kw);
 };
 
-const toggleExpand = (tabId, sectionId) => {
-    const key = sectionKey(tabId, sectionId);
+const getVisibleTabsForMajor = (majorKey) => {
+    const list = getMajorFilteredTabList(majorKey);
+    const bk = majorBlockKey(majorKey);
+    return expandedSections.value[bk] ? list : list.slice(0, SECTION_VISIBLE_COUNT);
+};
+
+const shouldShowExpandForMajor = (majorKey) =>
+    getMajorFilteredTabList(majorKey).length > SECTION_VISIBLE_COUNT;
+
+const toggleExpandMajor = (majorKey) => {
+    const key = majorBlockKey(majorKey);
     expandedSections.value[key] = !expandedSections.value[key];
 };
 
-const isSelected = (cropId) => selectedCropIds.value.has(cropId);
+/** 品类唯一标识:接口字段 categorycode(兼容 category_code / 旧数据 id) */
+const categoryCodeOf = (cat) =>
+    cat?.categorycode ??
+    cat?.categoryCode ??
+    cat?.category_code ??
+    cat?.id;
+
+/** 选中存储键:品种父级(majorKey + variety_code)+ categorycode,避免不同品种下重复 categorycode 串台 */
+const cropSelectionKey = (tab, cat) => {
+    if (!tab) return null;
+    const code = categoryCodeOf(cat);
+    if (code == null || code === "") return null;
+    return `${blockKey(tab)}\x1f${String(code)}`;
+};
+
+/** 弹窗当前对应的品种行(与 popupVarietyDetail 同源) */
+const getPopupTab = () => {
+    const { tabId, majorKey } = popupContext.value;
+    if (tabId == null) return null;
+    return (
+        categoryList.value.find((c) => {
+            if (String(c.variety_code) !== String(tabId)) return false;
+            if (majorKey != null && majorKey !== "") return c.majorKey === majorKey;
+            return c.majorKey == null || c.majorKey === "";
+        }) ?? null
+    );
+};
 
-const toggleCrop = (cropId) => {
-    selectedCropIds.value = toggleSetItem(selectedCropIds.value, cropId);
+const categoryRowKey = (cat, index) => {
+    const tab = getPopupTab();
+    const ck = tab ? cropSelectionKey(tab, cat) : null;
+    if (ck) return ck;
+    const code = categoryCodeOf(cat);
+    return code != null && code !== "" ? String(code) : `category-${index}`;
 };
 
-/** 主列表选品类:选中并打开弹窗(带当前 tab / 类别上下文) */
-const handleCropClick = (tabId, section, cropId) => {
-    const alreadySelected = isSelected(cropId);
-    selectedCropIds.value = toggleSetItem(selectedCropIds.value, cropId);
-    if (!alreadySelected) {
-        popupContext.value = { tabId, sectionId: section.id };
-        popupSearchKeyword.value = "";
-        popupPage.value = 0;
-        showPopup.value = true;
+/** 品种格:仅反映已确认选中(selectedCropIds),不受弹窗内临时勾选影响 */
+const isTabVarietySelected = (tab) =>
+    (tab.categories || []).some((s) => {
+        const k = cropSelectionKey(tab, s);
+        return Boolean(k && selectedCropIds.value.has(k));
+    });
+
+/** 点击品种格:弹窗标题为 variety_name,内容为 categories */
+const handleVarietyTileClick = (tab) => {
+    const list = tab.categories || [];
+    if (!list.length) return;
+    popupContext.value = {
+        tabId: tab.variety_code,
+        majorKey: tab.majorKey ?? null,
+    };
+    popupSearchKeyword.value = "";
+    popupPage.value = 0;
+    const nextPopup = new Set();
+    for (const cat of list) {
+        const k = cropSelectionKey(tab, cat);
+        if (k && selectedCropIds.value.has(k)) nextPopup.add(k);
     }
+    popupSelectedIds.value = nextPopup;
+    showPopup.value = true;
 };
 
-const handleConfirm = () => {
-    history.back();
+const isPopupSelected = (cat) => {
+    const tab = getPopupTab();
+    const k = cropSelectionKey(tab, cat);
+    return Boolean(k && popupSelectedIds.value.has(k));
 };
 
-const popupFilteredCrops = computed(() => {
-    if (!popupSection.value) return [];
-    return filterByKeyword(popupSection.value.crops, popupSearchKeyword.value);
-});
+const togglePopupCrop = (cat) => {
+    const tab = getPopupTab();
+    const k = cropSelectionKey(tab, cat);
+    if (!k) return;
+    popupSelectedIds.value = toggleSetItem(popupSelectedIds.value, k);
+};
 
-const popupVisibleCrops = computed(() => {
-    return sliceByPage(popupFilteredCrops.value, popupPage.value, POPUP_PAGE_SIZE);
-});
+/** 根据已确认的 categorycode 拼出可序列化的选项列表 */
+const buildSelectedCropPayload = () => {
+    const items = [];
+    for (const tab of categoryList.value) {
+        for (const cat of tab.categories || []) {
+            const k = cropSelectionKey(tab, cat);
+            if (!k || !selectedCropIds.value.has(k)) continue;
+            const code = categoryCodeOf(cat);
+            items.push({
+                categorycode: code,
+                category_name: cat.category_name ?? cat.name ?? "",
+                variety_code: tab.variety_code,
+                variety_name: tab.variety_name,
+                majorKey: tab.majorKey ?? null,
+            });
+        }
+    }
+    return items;
+};
+
+const handleConfirm = () => {
+    if (!selectedCropIds.value.size) {
+        ElMessage.warning("请至少选择一个品类");
+        return;
+    }
+    try {
+        sessionStorage.removeItem("selectedCrop");
+        sessionStorage.setItem(
+            "selectedCrop",
+            JSON.stringify(buildSelectedCropPayload())
+        );
+    } catch {
+        ElMessage.warning("保存选中结果失败,请重试");
+        return;
+    }
+    router.back();
+};
 
 const handleSwitch = () => {
-    if (!popupFilteredCrops.value.length) return;
-    const totalPage = Math.ceil(popupFilteredCrops.value.length / POPUP_PAGE_SIZE);
+    if (!popupFilteredCategories.value.length) return;
+    const totalPage = Math.ceil(popupFilteredCategories.value.length / POPUP_PAGE_SIZE);
     popupPage.value = (popupPage.value + 1) % totalPage;
 };
 
-const clearSelection = () => {
-    selectedCropIds.value = new Set();
+const clearPopupSelection = () => {
+    popupSelectedIds.value = new Set();
 };
 
 const handlePopupConfirm = () => {
+    const detail = popupVarietyDetail.value;
+    const tab = getPopupTab();
+    if (detail?.categories?.length && tab) {
+        const codesInTab = new Set(
+            detail.categories
+                .map((c) => cropSelectionKey(tab, c))
+                .filter(Boolean)
+        );
+        const next = new Set(selectedCropIds.value);
+        for (const k of next) {
+            if (codesInTab.has(k)) next.delete(k);
+        }
+        for (const k of popupSelectedIds.value) {
+            next.add(k);
+        }
+        selectedCropIds.value = next;
+    }
     showPopup.value = false;
 };
 </script>
@@ -451,18 +652,27 @@ const handlePopupConfirm = () => {
         height: calc(100% - 100px);
 
         .top-tabs {
-            display: grid;
-            grid-template-columns: repeat(4, 1fr);
+            display: flex;
+            flex-wrap: nowrap;
             gap: 10px;
             margin-bottom: 10px;
+            overflow-x: auto;
+            -webkit-overflow-scrolling: touch;
+            scrollbar-width: none;
+
+            &::-webkit-scrollbar {
+                display: none;
+            }
 
             .tab-item {
+                flex: 0 0 auto;
                 border-radius: 2px;
-                padding: 6px;
+                padding: 6px 14px;
                 background: #fff;
                 border: 1px solid transparent;
                 color: #858585;
                 text-align: center;
+                white-space: nowrap;
 
                 &.active {
                     background: #2f9cf4;
@@ -475,10 +685,14 @@ const handlePopupConfirm = () => {
             overflow: auto;
             height: 100%;
 
+            .major-anchor-block {
+                scroll-margin-top: 8px;
+            }
+
             .tab-anchor-block {
                 scroll-margin-top: 8px;
             }
-    
+
             .expand-trigger {
                 text-align: center;
                 color: rgba(0, 0, 0, 0.4);
@@ -500,12 +714,15 @@ const handlePopupConfirm = () => {
     width: 100%;
     background: linear-gradient(0deg, #FFFFFF 79.37%, #93CEFD 108.08%);
     padding: 20px 10px;
+
     .popup-title {
         text-align: center;
         font-size: 20px;
     }
+
     .popup-body {
         margin-top: 12px;
+
         .switch-btn {
             border-radius: 25px;
             background: rgba(238, 238, 238, 0.6);
@@ -515,10 +732,12 @@ const handlePopupConfirm = () => {
             text-align: center;
             margin: 0 auto;
         }
+
         .popup-actions {
             display: flex;
             gap: 10px;
             margin-top: 18px;
+
             .action-btn {
                 flex: 1;
                 border-radius: 25px;
@@ -528,11 +747,11 @@ const handlePopupConfirm = () => {
                 padding: 8px;
                 background: #fff;
             }
-            
+
             .cancel-btn {
                 border: 1px solid rgba(0, 0, 0, 0.2);
             }
-            
+
             .confirm-btn {
                 background: #2f9cf4;
                 color: #fff;
@@ -540,5 +759,4 @@ const handlePopupConfirm = () => {
         }
     }
 }
-
 </style>

+ 2 - 8
src/views/old_mini/growth_report/index.vue

@@ -70,15 +70,12 @@
                     </div>
                 </div>
 
-                <div class="report-box warning-bg">
+                <!-- <div class="report-box warning-bg">
                     <div class="box-title warning">今日巡园重点</div>
                     <div class="box-text w-100">
                         <div class="row">
                             <div v-for="(card, cardI) in todayPatrolFocus" :key="cardI" class="status-card"
                                 :class="'today-' + card.color" @click="handleTodayPatrolFocusClick(card)">
-                                <!-- <badge class="status-badge" dot
-                                            :offset="[80, -10]">
-                                        </badge> -->
                                 <div class="status-title"
                                     :class="{ 'status-title-small': (card.title || '').length > 6 }">
                                     {{ card.title }}
@@ -98,9 +95,6 @@
                             <div v-for="(card, cardI) in pendingFarmWork" :key="cardI" class="status-card pending-card"
                                 :style="{ background: card.purposeColor, color: card.purposeColor === '#FFFFFF' ? '#000' : '#fff' }"
                                 :class="card.type" @click="handlePendingFarmWorkClick(card)">
-                                <!-- <badge class="status-badge" dot
-                                            :offset="[80, -10]">
-                                        </badge> -->
                                 <div v-if="card.executionLimitDays || card.executionLimitDays === 0" class="tag-name"
                                     :style="{ borderColor: card.purposeColor, color: card.purposeColor }">限时 {{
                                         card.executionLimitDays }} 天</div>
@@ -115,7 +109,7 @@
                             </div>
                         </div>
                     </div>
-                </div>
+                </div> -->
 
                 <div class="report-box">
                     <div class="box-title">气象风险</div>

+ 3 - 1
src/views/old_mini/monitor/subPages/agriculturalDetail.vue

@@ -84,7 +84,9 @@ const handlePhotoClick = (index) => {
 
 onActivated(() => {
     // getFarmImagePage();
-    indexMap.initMap(imgInfo.value.pointWkt || 'POINT(113.6142086995688 23.585836479509055)', mapContainer.value);
+    if(route.query.title !== '气象风险') {
+        indexMap.initMap('POINT(113.6142086995688 23.585836479509055)', mapContainer.value);
+    } 
 });
 
 const imgInfo = ref({});

+ 2 - 1
vue.config.js

@@ -64,7 +64,8 @@ module.exports = {
                 VE_ENV: {
                     MODE: JSON.stringify(process.env.NODE_ENV),
                     SERVER: JSON.stringify(process.env.SERVER),
-                    PYSERVER: JSON.stringify(process.env.PYSERVER)
+                    PYSERVER: JSON.stringify(process.env.PYSERVER),
+                    NEW_SERVER: JSON.stringify(process.env.NEW_SERVER),
                 },
             },
         ]);