Bläddra i källkod

refactor: 天地图

lxf 1 vecka sedan
förälder
incheckning
1ba0990b1d

+ 4 - 0
.env

@@ -1,2 +1,6 @@
 SERVER = "http://127.0.0.1:81/"
 PYSERVER = "http://127.0.0.1:5000/"
+# 高德 Web 服务 Key(海拔等残留能力)
+AMAP_KEY = "ffb31295caa62931c074d40c33138b81"
+# 天地图 Web 服务 Key(控制台申请)
+TIANDITU_KEY = "1312739162ff3c829be3c3015cc41d41"

+ 5 - 1
.env.dev

@@ -1,3 +1,7 @@
 SERVER = "https://feiniaotech-dev.sysuimars.cn/"
 PYSERVER = "https://birds-api-1-2.sysuimars.cn/"
-NEW_SERVER = "https://woot.feiniaotech.com/"
+NEW_SERVER = "https://woot.feiniaotech.com/"
+# 高德 Web 服务 Key(海拔等残留能力)
+AMAP_KEY = "ffb31295caa62931c074d40c33138b81"
+# 天地图 Web 服务 Key(控制台申请)
+TIANDITU_KEY = "1312739162ff3c829be3c3015cc41d41"

+ 4 - 0
.env.pro

@@ -1,3 +1,7 @@
 SERVER = "https://birdseye-api.feiniaotech.sysuimars.cn/"
 PYSERVER = "https://birds-api-1-2.sysuimars.cn/"
 NEW_SERVER = "https://woot.feiniaotech.com/"
+# 高德 Web 服务 Key(海拔等残留能力)
+AMAP_KEY = "ffb31295caa62931c074d40c33138b81"
+# 天地图 Web 服务 Key(控制台申请)
+TIANDITU_KEY = "1312739162ff3c829be3c3015cc41d41"

+ 4 - 0
src/api/config.js

@@ -12,6 +12,10 @@ module.exports = {
     base_dev_url :newServer + "mini/",
     base_new_url :newPathServer + "api/",
     base_py_url :pyServer,
+    /** 天地图 Web 服务 Key(.env 中配置 TIANDITU_KEY) */
+    tianditu_web_key: VE_ENV.TIANDITU_KEY || "",
+    /** 高德 Key:仅海拔等残留能力使用 */
+    amap_web_key: VE_ENV.AMAP_KEY || "",
     image_url:oldServer+"images/",
     mini_key:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
     base_img_url : "https://img.sysuimars.com/",

+ 5 - 3
src/api/modules/old_mini_map.js

@@ -5,20 +5,22 @@ module.exports = {
         url: config.base_dev_url + "lz_tree/listByPoint",
         type: "post",
     },
+    /** 天地图逆地理(业务请走 tiandituLocation.fetchRegeo,浏览器端 Key 需直连) */
     location: {
-        url: "/ws/geocoder/v1",
+        url: "https://api.tianditu.gov.cn/geocoder",
         type: "get"
     },
     getAltitude: {
         url: "/v3/elevation/point",
         type: "get"
     },
+    /** 天地图地名搜索 V2(业务请走 tiandituLocation.fetchPlaceSuggestion) */
     search: {
-        url: "/ws/place/v1/suggestion",
+        url: "https://api.tianditu.gov.cn/v2/search",
         type: "get"
     },
     division: {
-        url: "/ws/district/v1/list",
+        url: "https://api.tianditu.gov.cn/v2/administrative",
         type: "get"
     },
     getCtiyList: {

+ 0 - 82
src/router/globalRoutes.js

@@ -12,27 +12,6 @@ export default [
         name: "dev_login",
         component: () => import("@/views/old_mini/dev_login.vue"),
     },
-    // 创建农场
-    {
-        path: "/create_farm",
-        name: "CreateFarm",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/create_farm/index.vue"),
-    },
-    // 农事记录
-    {
-        path: "/agri_record",
-        name: "AgriRecord",
-        meta: { showTabbar: true, keepAlive: true },
-        component: () => import("@/views/old_mini/agri_record/index.vue"),
-    },
-    // 编辑地图
-    {
-        path: "/edit_map",
-        name: "EditMap",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/create_farm/editMap.vue"),
-    },
     // 长势报告
     {
         path: "/growth_report",
@@ -47,20 +26,6 @@ export default [
         meta: { keepAlive: false },
         component: () => import("@/views/old_mini/growth_report/alert_detail.vue"),
     },
-    // 农事互动列表
-    {
-        path: "/interaction_list",
-        name: "InteractionList",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/interactionList/index.vue"),
-    },
-    // 勾画发生区域
-    {
-        path: "/draw_region",
-        name: "DrawRegion",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/interactionList/drawRegion.vue"),
-    },
     // 农事详情
     {
         path: "/work_detail",
@@ -68,19 +33,6 @@ export default [
         meta: { keepAlive: true },
         component: () => import("@/views/old_mini/work_detail/index.vue"),
     },
-    // 农事详情 - 查看区域
-    {
-        path: "/view_area",
-        name: "WorkViewArea",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/work_detail/components/mapInfo.vue"),
-    },
-    {
-        path: "/select_crop",
-        name: "SelectCrop",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/create_farm/selectCrop.vue"),
-    },
     // 农事档案
     {
         path: "/agri_file",
@@ -88,27 +40,6 @@ export default [
         meta: { showTabbar: true, keepAlive: true },
         component: () => import("@/views/old_mini/agri_file/index.vue"),
     },
-    // 农事记录详情
-    {
-        path: "/record_details",
-        name: "RecordDetails",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/recordDetails/index.vue"),
-    },
-    // 地图管理
-    {
-        path: "/map_manage",
-        name: "MapManage",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/recordDetails/mapManage.vue"),
-    },
-    // 农事详情
-    {
-        path: "/agri_record_detail",
-        name: "AgriRecordDetail",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/agri_record_detail/index.vue"),
-    },
     // 农事执行
     {
         path: "/work_execute",
@@ -123,13 +54,6 @@ export default [
         meta: { keepAlive: false },
         component: () => import("@/views/old_mini/work_execute/surveyEntry.vue"),
     },
-    // 农场信息
-    {
-        path: "/farm_info",
-        name: "FarmInfo",
-        meta: { keepAlive: true },
-        component: () => import("@/views/old_mini/farm_info/index.vue"),
-    },
     // 录入信息
     {
         path: "/entry_information",
@@ -186,12 +110,6 @@ export default [
         meta: { keepAlive: true },
         component: () => import("@/views/old_mini/agri_file/pages/abnormalTrack.vue"),
     },
-    // 定位校验通过桥接(小程序写 localStorage 后回退)
-    {
-        path: "/location_check_pass",
-        name: "LocationCheckPass",
-        component: () => import("@/views/old_mini/agri_file/pages/locationCheckPass.vue"),
-    },
     // 病虫害互动选地块
     {
         path: "/pest_interact",

+ 184 - 0
src/utils/tiandituLocation.js

@@ -0,0 +1,184 @@
+/**
+ * 天地图位置服务:逆地理 / 地名搜索
+ * 浏览器端 Key 需由页面直连(不可走本地代理),并用 fetch 避免 axios 自定义头触发 CORS
+ * 返回结构兼容原腾讯调用方(result.address_component / ad_info / data[])
+ * 坐标系:CGCS2000 ≈ WGS84
+ */
+import { tianditu_web_key } from "@/api/config";
+
+export const TIANDITU_WEB_KEY = tianditu_web_key;
+
+const GEOCODER_URL = "https://api.tianditu.gov.cn/geocoder";
+const SEARCH_URL = "https://api.tianditu.gov.cn/v2/search";
+
+/** "lng,lat" → 规范化字符串 */
+export function toLocationParam(lngLatText) {
+    const parts = String(lngLatText || "")
+        .split(",")
+        .map((s) => s.trim());
+    if (parts.length < 2 || !parts[0] || !parts[1]) return "";
+    return `${parts[0]},${parts[1]}`;
+}
+
+export function lngLatToParam(lng, lat) {
+    const x = Number(lng);
+    const y = Number(lat);
+    if (!Number.isFinite(x) || !Number.isFinite(y)) return "";
+    return `${x},${y}`;
+}
+
+/** 兼容旧调用名 */
+export const toAmapLocationParam = toLocationParam;
+
+/** 天地图 9 位国标码(156xxxxxx)→ 6 位 adcode */
+export function normalizeAdcode(code) {
+    if (code == null || code === "") return "";
+    let s = String(code).trim();
+    if (s.startsWith("156") && s.length >= 9) s = s.slice(3);
+    if (/^\d{6,}$/.test(s)) return s.slice(0, 6);
+    return s;
+}
+
+function parseLngLat(location) {
+    const parts = String(location || "")
+        .split(",")
+        .map((s) => s.trim());
+    if (parts.length < 2) return null;
+    const lng = Number(parts[0]);
+    const lat = Number(parts[1]);
+    if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null;
+    return { lng, lat };
+}
+
+function buildMapBound(lng, lat, delta = 1) {
+    return `${lng - delta},${lat - delta},${lng + delta},${lat + delta}`;
+}
+
+async function requestTianditu(url, params) {
+    const qs = new URLSearchParams();
+    Object.keys(params || {}).forEach((key) => {
+        const val = params[key];
+        if (val == null) return;
+        qs.set(key, String(val));
+    });
+    const res = await fetch(`${url}?${qs.toString()}`, {
+        method: "GET",
+        mode: "cors",
+        credentials: "omit",
+    });
+    const text = await res.text();
+    let data = null;
+    try {
+        data = text ? JSON.parse(text) : null;
+    } catch {
+        throw new Error(text || `tianditu http ${res.status}`);
+    }
+    if (!res.ok) {
+        throw new Error(data?.msg || data?.resolve || `tianditu http ${res.status}`);
+    }
+    // 浏览器端 Key 权限错误等业务码
+    if (data?.code && Number(data.code) !== 0) {
+        throw new Error(data?.resolve || data?.msg || `tianditu code ${data.code}`);
+    }
+    return data;
+}
+
+export function normalizeRegeoResult(payload) {
+    const ac = payload?.addressComponent || {};
+    const address =
+        payload?.formatted_address ||
+        [ac.province, ac.city, ac.county, ac.town, ac.address || ac.road || ac.poi]
+            .filter(Boolean)
+            .join("") ||
+        "";
+    const city = ac.city || "";
+    const district = ac.county || ac.town || "";
+    return {
+        address,
+        formatted_addresses: { recommend: address },
+        address_component: {
+            province: ac.province || "",
+            city,
+            district,
+            street: ac.road || ac.address || "",
+        },
+        ad_info: {
+            adcode: normalizeAdcode(ac.county_code || ac.city_code || ac.town_code || ""),
+        },
+    };
+}
+
+/** 逆地理:入参 lng/lat 或 "lng,lat";出参 { result } */
+export async function fetchRegeo({ lng, lat, location } = {}) {
+    let lon = lng;
+    let la = lat;
+    if ((lon == null || la == null) && location) {
+        const parsed = parseLngLat(location);
+        if (parsed) {
+            lon = parsed.lng;
+            la = parsed.lat;
+        }
+    }
+    lon = Number(lon);
+    la = Number(la);
+    if (!Number.isFinite(lon) || !Number.isFinite(la)) {
+        return { result: normalizeRegeoResult(null) };
+    }
+
+    const res = await requestTianditu(GEOCODER_URL, {
+        postStr: JSON.stringify({ lon, lat: la, ver: 1 }),
+        type: "geocode",
+        tk: TIANDITU_WEB_KEY,
+    });
+    if (String(res?.status) !== "0") {
+        throw new Error(res?.msg || "tianditu regeo failed");
+    }
+    return { result: normalizeRegeoResult(res.result) };
+}
+
+function mapPoiItem(poi) {
+    const lonlat = String(poi?.lonlat || "")
+        .split(",")
+        .map((s) => Number(String(s).trim()));
+    const lng = lonlat[0];
+    const lat = lonlat[1];
+    if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null;
+    return {
+        title: poi.name || "",
+        address: poi.address || poi.name || "",
+        province: poi.province || "",
+        city: poi.city || "",
+        district: poi.county || "",
+        adcode: normalizeAdcode(poi.countyCode || poi.cityCode || poi.provinceCode || ""),
+        location: { lat, lng },
+        coordSys: "wgs84",
+    };
+}
+
+/** 普通搜索(queryType=1):出参 { data } 兼容原 suggestion 列表 */
+export async function fetchPlaceSuggestion(keyword, locationLngLat) {
+    if (!keyword) return { data: [] };
+
+    const center = parseLngLat(locationLngLat) || { lng: 113.26, lat: 23.13 };
+    const postObj = {
+        keyWord: keyword,
+        mapBound: buildMapBound(center.lng, center.lat, 1),
+        level: "18",
+        queryType: "7",
+        start: "0",
+        count: "20",
+        show: "2",
+    };
+
+    const res = await requestTianditu(SEARCH_URL, {
+        postStr: JSON.stringify(postObj),
+        type: "query",
+        tk: TIANDITU_WEB_KEY,
+    });
+
+    const pois = res?.pois || res?.result?.pois || [];
+    if (!Array.isArray(pois) || !pois.length) {
+        return { data: [] };
+    }
+    return { data: pois.map(mapPoiItem).filter(Boolean) };
+}

+ 7 - 12
src/utils/util.js

@@ -1,3 +1,4 @@
+import { fetchRegeo } from "@/utils/tiandituLocation";
 
 export function getValues(arr,key) {
     let ids = [];
@@ -105,24 +106,18 @@ export function parsePolygon(polygonString) {
     }
 }
 
-//通过经纬度查处对应的地址
-const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
+//通过经纬度查处对应的地址(天地图逆地理)
 export async function getLocation (point){
     const type = typeof point
     let arr = point
     if(type==='string'){
         arr = parsePointToArray(point)
     }
-    const locationParams = {
-        key: MAP_KEY,
-        location: `${arr[1]},${arr[0]}`,
-    };
     let obj = {}
-   await VE_API.old_mini_map.location(locationParams).then(({ result }) => {
-        obj.street = result.address_component?.street
-        obj.address = result.formatted_addresses?.recommend
-            ? result.formatted_addresses.recommend
-            : result.address + "";
-  });
+    const { result } = await fetchRegeo({ lng: arr[0], lat: arr[1] });
+    obj.street = result.address_component?.street
+    obj.address = result.formatted_addresses?.recommend
+        ? result.formatted_addresses.recommend
+        : result.address + "";
   return obj
 }

+ 4 - 7
src/views/old_mini/entry_information/components/baInformation.vue

@@ -60,6 +60,7 @@ import { useRouter } from "vue-router";
 import { useStore } from "vuex";
 import { ElMessage } from "element-plus";
 import { convertPointToArray } from "@/utils/index";
+import { fetchRegeo } from "@/utils/tiandituLocation";
 import SelectLocationMap, { POINT_ICON } from "../map/selectLocationMap.js";
 
 // ---------------------------------------------------------------------------
@@ -72,7 +73,6 @@ const emit = defineEmits(["next"]);
 // ---------------------------------------------------------------------------
 const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
 const FORM_KEY = "ENTRY_BA_FORM";
-const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 const DEFAULT_POINT = "POINT(113.6142086995688 23.585836479509055)";
 
 // ---------------------------------------------------------------------------
@@ -135,10 +135,7 @@ async function fetchLocationAddress(point) {
     if (!coordinate?.length) return;
     const reqId = ++addressReqId;
     try {
-        const { result } = await VE_API.old_mini_map.location({
-            key: MAP_KEY,
-            location: `${coordinate[1]},${coordinate[0]}`,
-        });
+        const { result } = await fetchRegeo({ lng: coordinate[0], lat: coordinate[1] });
         if (reqId !== addressReqId) return;
         const address =
             result?.formatted_addresses?.recommend ||
@@ -272,8 +269,8 @@ const handleNext = () => {
 };
 
 onMounted(() => {
-    restoreFormDraft();
-    syncLocationFromSession();
+    // restoreFormDraft();
+    // syncLocationFromSession();
 });
 
 onActivated(() => {

+ 2 - 5
src/views/old_mini/entry_information/components/selectCategory.vue

@@ -110,6 +110,7 @@ import { computed, nextTick, onMounted, ref } from "vue";
 import { ElMessage } from "element-plus";
 import { Check, Search } from "@element-plus/icons-vue";
 import { selectNewlyCreatedFarm } from "@/utils/selectedFarm";
+import { fetchRegeo } from "@/utils/tiandituLocation";
 
 // ---------------------------------------------------------------------------
 // Props / Emits
@@ -143,7 +144,6 @@ const FIRST_CROP_KEY_MAP = {
 };
 const TAB_ORDER = ["果树", "大田"];
 const DEFAULT_COUNTY_CODE = "440113";
-const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 const DEFAULT_POINT = "POINT(113.6142086995688 23.585836479509055)";
 /** 品类 chip:1 行 × 3 列 */
 const DEFAULT_CROP_VISIBLE = 3;
@@ -327,10 +327,7 @@ async function fetchAdcodeByPoint(point) {
     const match = String(point || "").match(/POINT\s*\(([\d.\-]+)\s+([\d.\-]+)\)/i);
     if (!match) return "";
     try {
-        const { result } = await VE_API.old_mini_map.location({
-            key: MAP_KEY,
-            location: `${match[2]},${match[1]}`,
-        });
+        const { result } = await fetchRegeo({ lng: match[1], lat: match[2] });
         const adcode = result?.ad_info?.adcode;
         return adcode != null && adcode !== "" ? String(adcode) : "";
     } catch {

+ 0 - 1289
src/views/old_mini/entry_information/components/selectVariety.vue

@@ -1,1289 +0,0 @@
-<template>
-    <div class="select-variety">
-        <div class="select-variety__content">
-            <div class="page-header">
-                <div class="page-title">完善种植品种</div>
-                <div class="page-title">精准管理每一块地块<span class="page-title-tip">(单选)</span></div>
-            </div>
-
-
-            <div class="variety-card">
-                <!-- 单品类标题 -->
-                <div class="category-title" v-if="currentCategory?.name">
-                    <span class="category-title__line"></span>
-                    <span class="category-title__diamond"></span>
-                    <span class="category-title__text">品类 - {{ currentCategory.name }}</span>
-                    <span class="category-title__diamond"></span>
-                    <span class="category-title__line right-line"></span>
-                </div>
-                <el-input v-model="searchKeyword" class="search-bar" placeholder="请输入品种" @keyup.enter="handleSearch">
-                    <template #prefix>
-                        <el-icon class="search-icon">
-                            <Search />
-                        </el-icon>
-                    </template>
-                    <template #suffix>
-                        <span class="search-btn" @click="handleSearch">搜索</span>
-                    </template>
-                </el-input>
-
-                <div class="variety-grid">
-                    <div v-for="item in visibleVarieties" :key="item.id" class="variety-item"
-                        :class="{ selected: isSelected(item.id) }" @click="toggleVariety(item)">
-                        {{ item.name }}
-                    </div>
-                </div>
-
-                <div v-if="!varietyLoading && !filteredVarieties.length" class="empty-tip">暂无品种</div>
-
-                <div v-if="filteredVarieties.length > DEFAULT_VISIBLE_COUNT" class="expand-trigger"
-                    @click="gridExpanded = !gridExpanded">
-                    {{ gridExpanded ? "点击收起" : "点击展开更多" }}
-                </div>
-            </div>
-
-            <!-- 默认表单卡(单选,始终一张) -->
-            <div
-                v-if="currentSelected"
-                :key="currentSelected.uid"
-                class="form-card"
-                :class="hasVariety ? 'selected-card' : 'form-card--default'"
-            >
-                <div class="form-card__header">
-                    <img class="title-icon" src="@/assets/img/home/label-icon.png" alt="" />
-                    <el-select
-                        v-model="currentSelected.id"
-                        class="variety-select"
-                        placeholder="选择品种"
-                        popper-class="variety-select-popper"
-                        @change="handleFormVarietyChange"
-                    >
-                        <el-option
-                            v-for="opt in currentVarieties"
-                            :key="opt.id"
-                            :label="opt.name"
-                            :value="opt.id"
-                        />
-                    </el-select>
-                    <!-- <span v-if="hasVariety" class="delete-btn" @click="clearVariety">删除</span> -->
-                </div>
-
-                <div
-                    class="form-card__body"
-                    :class="{ 'form-card__body--locked': !hasVariety }"
-                    @click="onFormBodyClick"
-                >
-                    <div class="form-row">
-                        <span class="form-label">当下物候期</span>
-                        <el-select
-                            v-model="currentSelected.phenologyId"
-                            class="form-select"
-                            placeholder="选择物候期"
-                            :loading="interactLoading"
-                            placement="bottom-end"
-                            popper-class="variety-select-popper"
-                        >
-                            <el-option
-                                v-for="opt in currentPhenologyOptions"
-                                :key="opt.id"
-                                :label="opt.name"
-                                :value="opt.id"
-                            />
-                        </el-select>
-                    </div>
-
-                    <div class="form-row">
-                        <span class="form-label" :class="{ muted: !hasVariety }">
-                            {{ currentStartingPointQuestion }}
-                        </span>
-                        <el-date-picker
-                            v-model="currentSelected.startTime"
-                            class="form-picker"
-                            type="date"
-                            placeholder="请选择时间"
-                            format="YYYY-MM-DD"
-                            value-format="YYYY-MM-DD"
-                            :clearable="false"
-                            :disabled-date="disableFutureDates"
-                            style="width: 118px; flex:none;"
-                            :readonly="!hasVariety"
-                        />
-                    </div>
-
-                    <div class="form-row">
-                        <span class="form-label">种植亩数</span>
-                        <el-input
-                            v-model="currentSelected.area"
-                            class="form-input"
-                            placeholder="请输入亩数"
-                            type="number"
-                            :readonly="!hasVariety"
-                        />
-                    </div>
-
-                    <div class="form-row location-row">
-                        <span class="form-label">选择种植点位</span>
-                        <div
-                            class="map"
-                            :class="{ 'map--clickable': hasVariety }"
-                            :ref="(el) => setItemMapRef(currentSelected.uid, el)"
-                            @click="goSelectItemLocation(currentSelected)"
-                        ></div>
-                    </div>
-                </div>
-            </div>
-        </div>
-
-        <div class="custom-bottom-fixed-btns">
-            <div class="bottom-btn secondary-btn" @click="handlePrev">上一步</div>
-            <div class="bottom-btn primary-btn" @click="handleConfirm">{{ varietyNextText }}</div>
-        </div>
-    </div>
-</template>
-
-<script setup>
-import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from "vue";
-import { ElMessage } from "element-plus";
-import { Search } from "@element-plus/icons-vue";
-import { useRouter } from "vue-router";
-import { useStore } from "vuex";
-import SelectLocationMap, { POINT_ICON } from "../map/selectLocationMap.js";
-
-const emit = defineEmits(["prev", "next"]);
-const props = defineProps({
-    multiple: { type: Boolean, default: false },
-    isLastStep: { type: Boolean, default: false },
-});
-const router = useRouter();
-const store = useStore();
-
-const varietyNextText = computed(() => (props.isLastStep ? "提交信息" : "下一步 (3/4)"));
-
-const DEFAULT_VISIBLE_COUNT = 12;
-const DEFAULT_POINT = "POINT(113.6142086995688 23.585836479509055)";
-const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
-const EDIT_UID_KEY = "ENTRY_VARIETY_EDIT_UID";
-const SELECTED_LIST_KEY = "ENTRY_SELECTED_VARIETY_LIST";
-const CATEGORY_SESSION_KEY = "ENTRY_SELECTED_CATEGORY";
-const MAP_PADDING = [6, 6, 6, 6];
-let selectedUid = 0;
-
-const itemPreviewMaps = new Map();
-
-function getResidentPoint() {
-    try {
-        const saved = JSON.parse(sessionStorage.getItem(LOCATION_KEY) || "null");
-        return saved?.point || "";
-    } catch {
-        return "";
-    }
-}
-
-function getDefaultPoint() {
-    return (
-        getResidentPoint() ||
-        localStorage.getItem("MINI_USER_LOCATION_POINT") ||
-        store.state.home.miniUserLocationPoint ||
-        DEFAULT_POINT
-    );
-}
-
-function utilWktToCoordinate(point) {
-    const match = String(point).match(/POINT\s*\(([\d.\-]+)\s+([\d.\-]+)\)/i);
-    if (!match) return null;
-    return [Number(match[1]), Number(match[2])];
-}
-
-function createPreviewMap(el, point, showPoint = false) {
-    if (!el || !point) return null;
-    const map = new SelectLocationMap({ iconSrc: POINT_ICON.plant });
-    map.initMap(point, el, {
-        enableClick: false,
-        padding: MAP_PADDING,
-        showPoint,
-    });
-    nextTick(() => {
-        map.kmap?.map?.updateSize?.();
-    });
-    return map;
-}
-
-function destroyPreviewMap(map) {
-    map?.clearLayer();
-}
-
-function updateItemMap(uid, point, showPoint) {
-    const map = itemPreviewMaps.get(uid);
-    if (!map?.kmap || !point) return;
-    const coordinate = utilWktToCoordinate(point);
-    if (!coordinate) return;
-    if (showPoint) {
-        map.showPoint(coordinate);
-    } else {
-        map.hidePoint();
-        map.setMapPosition(coordinate, false);
-    }
-}
-
-function setItemMapRef(uid, el) {
-    if (!el) {
-        destroyPreviewMap(itemPreviewMaps.get(uid));
-        itemPreviewMaps.delete(uid);
-        return;
-    }
-    if (itemPreviewMaps.has(uid)) {
-        nextTick(() => itemPreviewMaps.get(uid)?.kmap?.map?.updateSize?.());
-        return;
-    }
-    nextTick(() => {
-        const item = selectedList.value.find((i) => i.uid === uid);
-        const point = item?.location || getDefaultPoint();
-        const map = createPreviewMap(el, point, !!item?.location);
-        if (map) itemPreviewMaps.set(uid, map);
-    });
-}
-
-function syncItemLocationFromSession() {
-    const uid = sessionStorage.getItem(EDIT_UID_KEY);
-    if (!uid) return;
-    sessionStorage.removeItem(EDIT_UID_KEY);
-    try {
-        const raw = sessionStorage.getItem(LOCATION_KEY);
-        const saved = raw ? JSON.parse(raw) : null;
-        if (!saved?.point) return;
-        const item = selectedList.value.find((i) => i.uid === uid);
-        if (!item) return;
-        item.location = saved.point;
-        saveSelectedListDraft();
-        nextTick(() => updateItemMap(uid, saved.point, true));
-    } catch {
-        // ignore
-    }
-}
-
-function saveSelectedListDraft() {
-    try {
-        sessionStorage.setItem(
-            SELECTED_LIST_KEY,
-            JSON.stringify({
-                selectedUid,
-                activeCategoryId: activeCategoryId.value,
-                list: selectedList.value,
-            })
-        );
-    } catch {
-        // ignore
-    }
-}
-
-function restoreSelectedListDraft() {
-    try {
-        const raw = sessionStorage.getItem(SELECTED_LIST_KEY);
-        if (!raw) return;
-        const draft = JSON.parse(raw);
-        if (draft?.activeCategoryId != null) {
-            activeCategoryId.value = draft.activeCategoryId;
-        }
-        if (typeof draft?.selectedUid === "number") {
-            selectedUid = draft.selectedUid;
-        }
-        if (Array.isArray(draft?.list) && draft.list.length) {
-            selectedList.value = props.multiple ? draft.list : draft.list.slice(0, 1);
-        }
-    } catch {
-        // ignore
-    }
-}
-
-const goSelectItemLocation = (item) => {
-    if (!item?.id) {
-        ElMessage.warning("请选择品种");
-        return;
-    }
-    sessionStorage.setItem(EDIT_UID_KEY, item.uid);
-    sessionStorage.setItem("ENTRY_INFORMATION_STEP", "3");
-    saveSelectedListDraft();
-    router.push({
-        path: "/entry_select_location",
-        query: {
-            mapCenter: item.location || getDefaultPoint(),
-            pointType: "plant",
-        },
-    });
-};
-
-async function bootstrapVarietyPage() {
-    initCategoryTabsFromSession();
-    restoreSelectedListDraft();
-    ensureDefaultForm();
-    syncItemLocationFromSession();
-    await Promise.all(
-        categoryTabs.value.map((tab) => fetchVarietiesByCrop(tab.id))
-    );
-    await fetchInitialInteractOptionsWithoutZone();
-}
-
-onMounted(() => {
-    bootstrapVarietyPage();
-});
-
-/** keep-alive 返回时只同步点位/地图,避免重复拉接口 */
-onActivated(() => {
-    syncItemLocationFromSession();
-    nextTick(() => {
-        itemPreviewMaps.forEach((map) => map.kmap?.map?.updateSize?.());
-    });
-});
-
-onBeforeUnmount(() => {
-    itemPreviewMaps.forEach((map) => destroyPreviewMap(map));
-    itemPreviewMaps.clear();
-});
-
-const getSelectedCategoriesFromSession = () => {
-    try {
-        const raw = sessionStorage.getItem(CATEGORY_SESSION_KEY);
-        if (!raw) return [];
-        const list = JSON.parse(raw);
-        if (!Array.isArray(list)) return [];
-        if (!props.multiple) return list;
-        return [...list].sort((a, b) => {
-            const rank = (item) => {
-                if (item?.firstCrop === "大田" || item?.majorKey === "field") return 0;
-                if (item?.firstCrop === "果树" || item?.majorKey === "fruit") return 1;
-                return 2;
-            };
-            return rank(a) - rank(b);
-        });
-    } catch {
-        return [];
-    }
-};
-
-const initCategoryTabsFromSession = () => {
-    const selected = getSelectedCategoriesFromSession();
-    const prevLoaded = new Map(
-        categoryTabs.value.map((tab) => [String(tab.id), tab])
-    );
-    categoryTabs.value = selected.map((item) => {
-        const id = item.id;
-        const cached = prevLoaded.get(String(id));
-        if (cached?.loaded) {
-            return {
-                ...cached,
-                name: item.name || cached.name,
-                code: item.code || cached.code || "",
-                firstCrop: item.firstCrop || cached.firstCrop || "",
-            };
-        }
-        return {
-            id,
-            name: item.name || "",
-            code: item.code || "",
-            firstCrop: item.firstCrop || "",
-            varieties: [],
-            phenology: [],
-            startingPointQuestion: "起种点问题",
-            loaded: false,
-        };
-    });
-    if (!categoryTabs.value.length) {
-        activeCategoryId.value = null;
-        return;
-    }
-    const stillExists = categoryTabs.value.some(
-        (tab) => String(tab.id) === String(activeCategoryId.value)
-    );
-    if (!stillExists) {
-        activeCategoryId.value = categoryTabs.value[0].id;
-    }
-};
-
-const categoryTabs = ref([]);
-const activeCategoryId = ref(null);
-const varietyLoading = ref(false);
-const searchKeyword = ref("");
-const appliedKeyword = ref("");
-const gridExpanded = ref(false);
-const selectedList = ref([]);
-/** 无分区初始互动选项,业务侧自行对接 */
-const initialInteractOptions = ref(null);
-/** 无分区初始互动接口 loading(表单卡) */
-const interactLoading = ref(false);
-/** 进行中的品种请求,防止并发重复 */
-const varietyFetchPromises = new Map();
-/** 进行中的初始互动请求 key */
-let interactFetchKey = "";
-let interactFetchPromise = null;
-
-function readJson(key) {
-    try {
-        const raw = sessionStorage.getItem(key);
-        return raw ? JSON.parse(raw) : null;
-    } catch {
-        return null;
-    }
-}
-
-function writeJson(key, value) {
-    sessionStorage.setItem(key, JSON.stringify(value));
-}
-
-function saveCropDetailFromApi(data) {
-    if (data?.crop_id == null || data?.crop_id === "") return;
-    const cache = readJson("ENTRY_CROP_DETAIL") || {};
-    cache[String(data.crop_id)] = {
-        crop_id: data.crop_id,
-        crop_code: data.crop_code || "",
-        crop_group: data.crop_group || "",
-        crop_name: data.crop_name || "",
-        crop_type: data.crop_type || "",
-    };
-    writeJson("ENTRY_CROP_DETAIL", cache);
-}
-
-/** 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 applyInitialInteractToCurrentCategory(data) {
-    const tab = currentCategory.value;
-    if (!tab || !data) return;
-
-    const theme = data.date_interact?.interact_theme;
-    if (theme) {
-        tab.startingPointQuestion = theme;
-    }
-
-    const phenology = mapOptionsInteractToPhenology(data.options_interact?.interact);
-    if (!phenology.length) return;
-
-    tab.phenology = phenology;
-    const form = getCurrentForm();
-    if (!form?.phenologyId) return;
-    const stillExists = phenology.some((opt) => String(opt.id) === String(form.phenologyId));
-    if (!stillExists) form.phenologyId = "";
-}
-
-/** 组装并请求无分区初始互动选项 */
-async function fetchInitialInteractOptionsWithoutZone() {
-    const category = currentCategory.value;
-    if (!category) return;
-
-    const form = getCurrentForm();
-    const resident = readJson(LOCATION_KEY);
-    const cropDetail = readJson("ENTRY_CROP_DETAIL") || {};
-    const detail = cropDetail[String(category.id)] || {};
-
-    const params = {
-        crop_big_type: category.firstCrop || detail.crop_group || "",
-        crop_code: category.code || detail.crop_code || form?.cropCode || "",
-        crop_maturing: "P0",
-        location: form?.location || resident?.point || getDefaultPoint(),
-        date: form?.startTime || new Date().toISOString().split("T")[0],
-    };
-
-    const key = `${params.crop_code}|${params.location}|${params.date}`;
-    if (interactFetchPromise && interactFetchKey === key) {
-        return interactFetchPromise;
-    }
-
-    interactFetchKey = key;
-    interactLoading.value = true;
-    interactFetchPromise = (async () => {
-        try {
-            const raw = await VE_API.questionnaire.getInitialInteractionWithoutZone(params);
-            const res = Array.isArray(raw) ? raw[0] : raw;
-            if (res?.code === 200) {
-                initialInteractOptions.value = res.data ?? null;
-                applyInitialInteractToCurrentCategory(res.data);
-            }
-        } catch (error) {
-            console.error("fetchInitialInteractOptionsWithoutZone failed", error);
-        } finally {
-            if (interactFetchKey === key) {
-                interactFetchPromise = null;
-                interactFetchKey = "";
-                interactLoading.value = false;
-            }
-        }
-    })();
-
-    return interactFetchPromise;
-}
-
-const fetchVarietiesByCrop = async (cropId, force = false) => {
-    if (cropId == null || cropId === "") return;
-    const tab = categoryTabs.value.find((item) => String(item.id) === String(cropId));
-    if (!tab) return;
-    if (tab.loaded && !force) return;
-
-    const cacheKey = String(cropId);
-    if (!force && varietyFetchPromises.has(cacheKey)) {
-        return varietyFetchPromises.get(cacheKey);
-    }
-
-    const showLoading = String(cropId) === String(activeCategoryId.value);
-    if (showLoading) varietyLoading.value = true;
-
-    const request = (async () => {
-        try {
-            const res = await VE_API.entry.getVarietiesByCrop({
-                crop_id: cropId,
-            });
-            if (res.code === 200 && res.data) {
-                const data = res.data;
-                if (data.crop_code) tab.code = data.crop_code;
-                if (data.crop_group) tab.firstCrop = tab.firstCrop || data.crop_group;
-                tab.varieties = (data.varieties || [])
-                    .map((item) => {
-                        if (typeof item === "string") {
-                            return { id: item, name: item };
-                        }
-                        const name = item?.name || "";
-                        return {
-                            id: item?.id ?? name,
-                            name,
-                        };
-                    })
-                    .filter((item) => item.name);
-                // 物候期 / 起种文案仅由 getInitialInteractionWithoutZone 写入
-                tab.loaded = true;
-
-                // 优先用已缓存的 crop_code,避免重复打 farmer_crop_detail
-                if (!tab.code) {
-                    const cached = readJson("ENTRY_CROP_DETAIL") || {};
-                    const detail = cached[cacheKey];
-                    if (detail?.crop_code) {
-                        tab.code = detail.crop_code;
-                        tab.firstCrop = tab.firstCrop || detail.crop_group || "";
-                    }
-                }
-                if (!tab.code) {
-                    try {
-                        const detailRes = await VE_API.entry.getMachines({ crop_id: cropId });
-                        const detail = detailRes?.data || {};
-                        if (detailRes?.code === 200 && detail.crop_code) {
-                            tab.code = detail.crop_code;
-                            tab.firstCrop = tab.firstCrop || detail.crop_group || "";
-                            saveCropDetailFromApi(detail);
-                        }
-                    } catch {
-                        // ignore
-                    }
-                }
-            } else {
-                tab.varieties = [];
-                if (showLoading) {
-                    ElMessage.error(res.msg || "获取品种列表失败");
-                }
-            }
-        } catch {
-            tab.varieties = [];
-            if (showLoading) {
-                ElMessage.error("获取品种列表失败,请稍后再试");
-            }
-        } finally {
-            if (showLoading) varietyLoading.value = false;
-            varietyFetchPromises.delete(cacheKey);
-        }
-    })();
-
-    varietyFetchPromises.set(cacheKey, request);
-    return request;
-};
-
-watch(
-    selectedList,
-    () => {
-        saveSelectedListDraft();
-    },
-    { deep: true }
-);
-
-const currentCategory = computed(
-    () =>
-        categoryTabs.value.find((tab) => String(tab.id) === String(activeCategoryId.value)) ||
-        categoryTabs.value[0]
-);
-
-const getPhenologyOptions = (categoryId) => {
-    const tab = categoryTabs.value.find((item) => String(item.id) === String(categoryId));
-    return tab?.phenology || [];
-};
-
-const getStartingPointQuestion = (categoryId) => {
-    const tab = categoryTabs.value.find((item) => String(item.id) === String(categoryId));
-    return tab?.startingPointQuestion || "起种点问题";
-};
-
-const currentVarieties = computed(() => currentCategory.value?.varieties || []);
-
-const getVarietiesByCategory = (categoryId) => {
-    const tab = categoryTabs.value.find((item) => String(item.id) === String(categoryId));
-    return tab?.varieties || [];
-};
-
-const filteredVarieties = computed(() => {
-    const list = currentVarieties.value;
-    const kw = appliedKeyword.value.trim();
-    if (!kw) return list;
-    return list.filter((item) => item.name.includes(kw));
-});
-
-const visibleVarieties = computed(() => {
-    if (gridExpanded.value) return filteredVarieties.value;
-    return filteredVarieties.value.slice(0, DEFAULT_VISIBLE_COUNT);
-});
-
-/** 只能选今天及之前的日期 */
-const disableFutureDates = (date) => {
-    const today = new Date();
-    today.setHours(0, 0, 0, 0);
-    return date.getTime() > today.getTime();
-};
-
-const createEmptyForm = (category) => ({
-    uid: `sel-${++selectedUid}`,
-    id: "",
-    name: "",
-    categoryId: category?.id ?? null,
-    categoryName: category?.name ?? "",
-    cropCode: category?.code || "",
-    firstCrop: category?.firstCrop || "",
-    phenologyId: "",
-    startTime: "",
-    area: "",
-    location: "",
-});
-
-/** 始终保留可编辑表单卡:农户一张;农服按品类各一张 */
-const ensureDefaultForm = () => {
-    const category = currentCategory.value;
-    if (!category) {
-        if (!props.multiple) selectedList.value = [];
-        return;
-    }
-    if (props.multiple) {
-        const categoryIds = new Set(categoryTabs.value.map((tab) => String(tab.id)));
-        selectedList.value = selectedList.value.filter((item) =>
-            categoryIds.has(String(item.categoryId))
-        );
-        const exists = selectedList.value.some(
-            (item) => String(item.categoryId) === String(category.id)
-        );
-        if (!exists) {
-            selectedList.value.push(createEmptyForm(category));
-        }
-        return;
-    }
-    if (!selectedList.value.length) {
-        selectedList.value = [createEmptyForm(category)];
-        return;
-    }
-    const item = selectedList.value[0];
-    if (String(item.categoryId) !== String(category.id)) {
-        selectedList.value = [createEmptyForm(category)];
-        return;
-    }
-    item.categoryId = category.id;
-    item.categoryName = category.name;
-    item.cropCode = category.code || item.cropCode || "";
-    item.firstCrop = category.firstCrop || item.firstCrop || "";
-};
-
-const getCurrentForm = () => {
-    if (props.multiple) {
-        return (
-            selectedList.value.find(
-                (item) => String(item.categoryId) === String(activeCategoryId.value)
-            ) || null
-        );
-    }
-    return selectedList.value[0] || null;
-};
-
-const currentSelected = computed(() => getCurrentForm());
-const hasVariety = computed(() => !!currentSelected.value?.id);
-const currentPhenologyOptions = computed(() =>
-    getPhenologyOptions(currentSelected.value?.categoryId || activeCategoryId.value)
-);
-const currentStartingPointQuestion = computed(() =>
-    getStartingPointQuestion(currentSelected.value?.categoryId || activeCategoryId.value)
-);
-
-watch(activeCategoryId, async (id, prevId) => {
-    searchKeyword.value = "";
-    appliedKeyword.value = "";
-    gridExpanded.value = false;
-    ensureDefaultForm();
-    // 初始化赋值由 bootstrap 拉取,避免与 onMounted 重复请求
-    if (id == null || prevId == null) return;
-    if (String(id) === String(prevId)) return;
-    await fetchVarietiesByCrop(id);
-    fetchInitialInteractOptionsWithoutZone();
-});
-
-const isSelected = (id) =>
-    !!id && String(getCurrentForm()?.id) === String(id);
-
-const handleSearch = () => {
-    appliedKeyword.value = searchKeyword.value;
-    gridExpanded.value = false;
-};
-
-const applyVarietyToForm = (variety) => {
-    ensureDefaultForm();
-    const item = getCurrentForm();
-    if (!item || !variety || !currentCategory.value) return;
-    item.id = variety.id;
-    item.name = variety.name;
-    item.categoryId = currentCategory.value.id;
-    item.categoryName = currentCategory.value.name;
-    item.cropCode = currentCategory.value.code || item.cropCode || "";
-    item.firstCrop = currentCategory.value.firstCrop || item.firstCrop || "";
-    item.phenologyId = "";
-    saveSelectedListDraft();
-};
-
-const handleFormVarietyChange = (varietyId) => {
-    if (!varietyId) {
-        clearVariety();
-        return;
-    }
-    const found = currentVarieties.value.find((item) => String(item.id) === String(varietyId));
-    if (!found) return;
-    applyVarietyToForm(found);
-};
-
-const clearVariety = () => {
-    ensureDefaultForm();
-    const item = getCurrentForm();
-    if (!item) return;
-    item.id = "";
-    item.name = "";
-    item.phenologyId = "";
-    item.startTime = "";
-    item.area = "";
-    item.location = "";
-    nextTick(() => updateItemMap(item.uid, getDefaultPoint(), false));
-    saveSelectedListDraft();
-};
-
-const onFormBodyClick = () => {
-    if (!hasVariety.value) {
-        ElMessage.warning("请选择品种");
-    }
-};
-
-const toggleVariety = (variety) => {
-    const current = getCurrentForm();
-    if (current && String(current.id) === String(variety.id)) {
-        clearVariety();
-        return;
-    }
-    applyVarietyToForm(variety);
-};
-
-const handlePrev = () => {
-    const idx = categoryTabs.value.findIndex(
-        (tab) => String(tab.id) === String(activeCategoryId.value)
-    );
-    if (idx > 0) {
-        activeCategoryId.value = categoryTabs.value[idx - 1].id;
-        saveSelectedListDraft();
-        return;
-    }
-    emit("prev");
-};
-
-const getBaForm = () => {
-    try {
-        const raw = sessionStorage.getItem("ENTRY_BA_FORM");
-        return raw ? JSON.parse(raw) : null;
-    } catch {
-        return null;
-    }
-};
-
-const validateItem = (item, categoryName = "") => {
-    const label = item?.name || categoryName;
-    if (!item?.id) {
-        ElMessage.warning(categoryName ? `请选择「${categoryName}」的品种` : "请选择一个品种");
-        return false;
-    }
-    if (!item.phenologyId) {
-        ElMessage.warning(`请选择「${label}」的当下物候期`);
-        return false;
-    }
-    if (!item.startTime) {
-        ElMessage.warning(`请填写「${label}」的起种点时间`);
-        return false;
-    }
-    if (item.area === "" || item.area == null || Number(item.area) <= 0) {
-        ElMessage.warning(`请填写「${label}」的种植亩数`);
-        return false;
-    }
-    if (!item.location) {
-        ElMessage.warning(`请选择「${label}」的种植点位`);
-        return false;
-    }
-    return true;
-};
-
-const validateSubmitData = () => {
-    if (!categoryTabs.value.length) {
-        ElMessage.warning("请先选择种植品类");
-        return false;
-    }
-    if (props.multiple) {
-        for (const category of categoryTabs.value) {
-            const item = selectedList.value.find(
-                (row) => String(row.categoryId) === String(category.id)
-            );
-            if (!validateItem(item, category.name)) return false;
-        }
-    } else if (!validateItem(selectedList.value[0])) {
-        return false;
-    }
-    const baForm = getBaForm();
-    if (!baForm?.name || !baForm?.phone) {
-        ElMessage.warning("请先完善个人信息");
-        return false;
-    }
-    return true;
-};
-
-const handleConfirm = () => {
-    const currentCategoryName = currentCategory.value?.name || "";
-    if (props.multiple && !validateItem(getCurrentForm(), currentCategoryName)) return;
-    const idx = categoryTabs.value.findIndex(
-        (tab) => String(tab.id) === String(activeCategoryId.value)
-    );
-    if (idx > -1 && idx < categoryTabs.value.length - 1) {
-        activeCategoryId.value = categoryTabs.value[idx + 1].id;
-        saveSelectedListDraft();
-        return;
-    }
-    if (!validateSubmitData()) return;
-    selectedList.value.forEach((row) => {
-        if (!row.id) return;
-        const options = getPhenologyOptions(row.categoryId);
-        const found = options.find((opt) => String(opt.id) === String(row.phenologyId));
-        row.phenophase = found?.name || String(row.phenologyId || "");
-        row.phenophaseCode = found?.code || String(row.phenologyId || "");
-    });
-    saveSelectedListDraft();
-    emit("next");
-};
-</script>
-
-<style lang="scss" scoped>
-.select-variety {
-    flex: 1;
-    min-height: 0;
-    display: flex;
-    flex-direction: column;
-    overflow: hidden;
-
-    &__content {
-        flex: 1;
-        min-height: 0;
-        overflow-y: auto;
-        -webkit-overflow-scrolling: touch;
-        padding: 20px 10px 100px;
-
-        .page-header {
-            margin-bottom: 20px;
-
-            .page-title {
-                font-size: 26px;
-                color: #005599;
-                line-height: 30px;
-                font-family: "PangMenZhengDao";
-                .page-title-tip {
-                    font-size: 20px;
-                }
-            }
-
-            .page-subtitle {
-                color: rgba(46, 46, 46, 0.4);
-            }
-        }
-
-        .category-title {
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            gap: 1px;
-            height: 24px;
-            margin-bottom: 9px;
-            padding: 0 6px;
-            box-sizing: border-box;
-
-            &__line {
-                flex: 1;
-                height: 1px;
-                background: linear-gradient(90deg, rgba(118, 118, 118, 0) 0%, rgba(118, 118, 118, 0.4) 100%);
-                &.right-line {
-                    background: linear-gradient(270deg, rgba(118, 118, 118, 0) 0%, rgba(118, 118, 118, 0.4) 100%);
-                }
-            }
-
-            &__diamond {
-                flex-shrink: 0;
-                width: 6px;
-                height: 6px;
-                background: rgba(61, 61, 61, 0.2);
-                transform: rotate(45deg);
-            }
-
-            &__text {
-                flex-shrink: 0;
-                padding: 0 10px;
-                font-size: 16px;
-                color: #3D3D3D;
-                line-height: 24px;
-                white-space: nowrap;
-            }
-        }
-
-        .variety-card {
-            margin-bottom: 10px;
-            background: rgba(255, 255, 255, 0.5);
-            border: 1px solid #fff;
-            border-radius: 10px;
-            padding: 10px 6px;
-
-            .search-bar {
-                margin-bottom: 10px;
-
-                :deep(.el-input__wrapper) {
-                    height: 36px;
-                    padding: 0 10px;
-                    background: #fff;
-                    border-radius: 6px;
-                    box-shadow: none;
-                }
-
-                :deep(.el-input__inner) {
-                    color: #1a1a1a;
-                    line-height: 20px;
-
-                    &::placeholder {
-                        color: rgba(29, 33, 41, 0.2);
-                    }
-                }
-
-                .search-icon {
-                    font-size: 16px;
-                    color: rgba(0, 0, 0, 0.2);
-                }
-
-                .search-btn {
-                    color: #2199f8;
-                }
-            }
-
-            .variety-grid {
-                display: grid;
-                grid-template-columns: repeat(4, minmax(0, 1fr));
-                gap: 8px;
-
-                .variety-item {
-                    display: flex;
-                    align-items: center;
-                    justify-content: center;
-                    padding: 7px 0;
-                    background: #fff;
-                    border: 1px solid transparent;
-                    border-radius: 6px;
-                    color: #565656;
-                    text-align: center;
-
-                    &.selected {
-                        color: #2199f8;
-                        background: rgba(33, 153, 248, 0.1);
-                        border-color: #2199f8;
-                    }
-                }
-            }
-
-            .expand-trigger {
-                margin-top: 10px;
-                text-align: center;
-                color: rgba(0, 0, 0, 0.6);
-            }
-
-            .empty-tip {
-                padding: 24px 0 10px;
-                text-align: center;
-                color: rgba(0, 0, 0, 0.35);
-                font-size: 14px;
-            }
-        }
-
-        .form-card {
-            margin-bottom: 10px;
-            background: #fff;
-            border-radius: 10px;
-            padding: 12px;
-
-            &__header {
-                display: flex;
-                align-items: center;
-                border-bottom: .2px solid rgba(0, 0, 0, 0.05);
-                padding-bottom: 10px;
-
-                .title-icon {
-                    width: 15px;
-                    object-fit: contain;
-                    margin-right: 5px;
-                }
-
-                .variety-select {
-                    flex: 0 0 auto;
-                    width: auto !important;
-                    max-width: calc(100% - 50px);
-
-                    :deep(.el-select__wrapper) {
-                        box-shadow: none;
-                        padding: 0;
-                        display: inline-flex;
-                        width: auto !important;
-                    }
-
-                    :deep(.el-select__selection) {
-                        display: inline-flex;
-                        width: auto;
-                        flex: 0 0 auto;
-                    }
-
-                    :deep(.el-select__placeholder) {
-                        position: static;
-                        font-size: 16px;
-                        color: #000;
-                        transform: none;
-                    }
-
-                    :deep(.el-select__caret) {
-                        color: #000;
-                        font-size: 16px;
-                    }
-                }
-
-                .delete-btn {
-                    flex-shrink: 0;
-                    margin-left: auto;
-                    padding-left: 12px;
-                    font-size: 14px;
-                    color: #f53f3f;
-                    line-height: 20px;
-                    white-space: nowrap;
-                }
-            }
-
-            &--default {
-                .form-card__header {
-                    .variety-select {
-                        :deep(.el-select__selected-item),
-                        :deep(.el-select__placeholder) {
-                            color: #1d2129;
-                        }
-
-                        :deep(.el-select__placeholder.is-transparent) {
-                            color: rgba(34, 34, 34, 0.3);
-                        }
-
-                        :deep(.el-select__caret) {
-                            color: rgba(34, 34, 34, 0.3);
-                        }
-                    }
-                }
-
-                .form-card__body--locked {
-                    cursor: pointer;
-
-                    .form-row {
-                        pointer-events: none;
-                    }
-                }
-            }
-
-            .form-row {
-                display: flex;
-                align-items: center;
-                justify-content: space-between;
-                gap: 12px;
-                margin-top: 10px;
-
-                .form-label {
-                    font-size: 14px;
-                    color: #1D2129;
-
-                    &.muted {
-                        color: rgba(29, 33, 41, 0.2);
-                    }
-                }
-
-                .form-input {
-                    flex: 1;
-                    min-width: 0;
-
-                    :deep(.el-input__wrapper) {
-                        box-shadow: none;
-                        background: transparent;
-                        padding: 0;
-                        justify-content: flex-end;
-                    }
-
-                    :deep(.el-input__inner) {
-                        text-align: right;
-                        font-size: 14px;
-                        color: #1a1a1a;
-
-                        &::placeholder {
-                            color: rgba(0, 0, 0, 0.25);
-                        }
-
-                    }
-                }
-
-                .form-select {
-                    flex: 0 0 auto;
-                    width: auto;
-                    max-width: 160px;
-                    margin-left: auto;
-
-                    :deep(.el-select__wrapper) {
-                        box-shadow: none;
-                        background: transparent;
-                        padding: 0;
-                        justify-content: flex-end;
-                        min-height: auto;
-                        width: auto;
-                    }
-
-                    :deep(.el-select__selection) {
-                        justify-content: flex-end;
-                        flex: 0 1 auto;
-                    }
-
-                    :deep(.el-select__placeholder),
-                    :deep(.el-select__selected-item) {
-                        text-align: right;
-                        font-size: 14px;
-                        color: #1a1a1a;
-                    }
-
-                    :deep(.el-select__placeholder) {
-                        color: #1D2129;
-                        position: static;
-                        transform: none;
-                    }
-                    :deep(.el-select__placeholder.is-transparent) {
-                        color: rgba(0, 0, 0, 0.25);
-                    }
-
-                    :deep(.el-select__caret) {
-                        color: rgba(0, 0, 0, 0.25);
-                    }
-                }
-
-                .map {
-                    width: 98px;
-                    height: 60px;
-                    flex-shrink: 0;
-                    margin-left: auto;
-                    border-radius: 8px;
-                    overflow: hidden;
-                    background: linear-gradient(135deg, #e8eef3 0%, #d4dde6 100%);
-                    pointer-events: none;
-
-                    &--clickable {
-                        pointer-events: auto;
-                        cursor: pointer;
-                    }
-                }
-            }
-        }
-    }
-
-    .custom-bottom-fixed-btns {
-        display: flex;
-        justify-content: space-between;
-        align-items: baseline;
-        padding: 10px 12px 0 12px;
-        height: 80px;
-        background: #fff;
-        box-sizing: border-box;
-        box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.4);
-
-        .bottom-btn {
-            padding: 0 30px;
-            height: 40px;
-            line-height: 40px;
-            font-size: 14px;
-            border-radius: 25px;
-            text-align: center;
-        }
-
-        .secondary-btn {
-            color: #2199f8;
-            background: #fff;
-            border: 1px solid #2199f8;
-        }
-
-        .primary-btn {
-            background: #2199f8;
-            color: #fff;
-
-            &.disabled {
-                opacity: 0.6;
-                pointer-events: none;
-            }
-        }
-    }
-}
-</style>
-
-<style lang="scss">
-.variety-select-popper {
-    width: auto !important;
-    min-width: 100px !important;
-
-    .el-select-dropdown__item.is-variety-selected {
-        color: #2199f8;
-        background: transparent;
-        font-weight: 500;
-
-        &.is-hovering,
-        &:hover {
-            color: #2199f8;
-            background: transparent;
-        }
-    }
-}
-</style>

+ 4 - 7
src/views/old_mini/entry_information/components/serviceInformation.vue

@@ -85,10 +85,10 @@
 import { nextTick, onActivated, onMounted, reactive, ref } from "vue";
 import { useRouter } from "vue-router";
 import { convertPointToArray } from "@/utils/index";
+import { fetchRegeo } from "@/utils/tiandituLocation";
 
 const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
 const FORM_KEY = "ENTRY_BA_FORM";
-const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 
 const TEAM_TYPES = [
     { value: "machinery_coop", label: "农机专业合作社", lines: ["农机专业", "合作社"] },
@@ -170,11 +170,7 @@ function restoreFormDraft() {
 function fetchLocationAddress(point) {
     const coordinate = convertPointToArray(point);
     if (!coordinate?.length) return;
-    VE_API.old_mini_map
-        .location({
-            key: MAP_KEY,
-            location: `${coordinate[1]},${coordinate[0]}`,
-        })
+    fetchRegeo({ lng: coordinate[0], lat: coordinate[1] })
         .then(({ result }) => {
             locationAddress.value =
                 result?.formatted_addresses?.recommend ||
@@ -182,7 +178,8 @@ function fetchLocationAddress(point) {
                 (result?.address_component
                     ? result.address_component.city + (result.address_component.district || "")
                     : "");
-        });
+        })
+        .catch(() => {});
 }
 
 function syncLocationFromSession() {

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

@@ -213,7 +213,7 @@ function syncEntryState() {
     clampStep();
 }
 
-onMounted(syncEntryState);
+// onMounted(syncEntryState);
 onActivated(syncEntryState);
 
 watch(

+ 2 - 5
src/views/old_mini/entry_information/selectLocation.vue

@@ -29,11 +29,11 @@ import customHeader from "@/components/customHeader.vue";
 import locationSearch from "@/components/pageComponents/locationSearch.vue";
 import SelectLocationMap, { mapLocation, POINT_ICON } from "./map/selectLocationMap.js";
 import { convertPointToArray } from "@/utils/index";
+import { fetchRegeo } from "@/utils/tiandituLocation";
 
 const SESSION_KEY_RESIDENT = "ENTRY_RESIDENT_LOCATION";
 const SESSION_KEY_GROWTH = "GROWTH_REPORT_LOCATION";
 const DEFAULT_POINT = "POINT(113.6142086995688 23.585836479509055)";
-const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 
 const router = useRouter();
 const route = useRoute();
@@ -85,10 +85,7 @@ function toPointWkt(coordinate) {
 async function fetchLocationInfoByCoordinate(coordinate) {
     if (!coordinate?.length) return { adcode: "", address: "" };
     try {
-        const { result } = await VE_API.old_mini_map.location({
-            key: MAP_KEY,
-            location: `${coordinate[1]},${coordinate[0]}`,
-        });
+        const { result } = await fetchRegeo({ lng: coordinate[0], lat: coordinate[1] });
         const adcode = result?.ad_info?.adcode;
         const address =
             result?.formatted_addresses?.recommend ||

+ 6 - 18
src/views/old_mini/growth_report/index.vue

@@ -107,6 +107,7 @@ import { useStore } from "vuex";
 import { ElMessage } from "element-plus";
 import { ArrowRight, Close, LocationFilled } from "@element-plus/icons-vue";
 import { convertPointToArray } from "@/utils/index";
+import { fetchRegeo, toLocationParam } from "@/utils/tiandituLocation";
 import GrowthReportMap from "./growthReportMap.js";
 import CropAlertPanel from "./components/CropAlertPanel.vue";
 import inviteEntryPopup from "@/views/old_mini/entry_information/components/inviteEntryPopup.vue";
@@ -119,7 +120,6 @@ const router = useRouter();
 const route = useRoute();
 
 const DEFAULT_MAP_POINT = "POINT(113.6142086995688 23.585836479509055)";
-const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 const LOCATION_SESSION_KEY = "GROWTH_REPORT_LOCATION";
 const MAP_POINT_STORAGE_KEY = "GROWTH_REPORT_MAP_POINT";
 /** 无农场时逆地理得到的区县码,供底图 / 点位预警共用 */
@@ -194,15 +194,6 @@ const getMiniUserLocationPoint = () => {
     return DEFAULT_MAP_POINT;
 };
 
-/** 腾讯逆地理 location 为 lat,lng;MINI_USER_LOCATION 存的是 lng,lat */
-const toTencentLocationParam = (lngLatText) => {
-    const parts = String(lngLatText || "")
-        .split(",")
-        .map((s) => s.trim());
-    if (parts.length < 2 || !parts[0] || !parts[1]) return "";
-    return `${parts[1]},${parts[0]}`;
-};
-
 const hasSelectedFarm = () => {
     const farm = getSelectedFarm();
     const id =
@@ -605,9 +596,9 @@ const handleZoneChange = (zoneId) => {
 
 async function getLocationName(point = currentMapPoint.value) {
     let locationParam = "";
-    // 无农场:逆地理用登录点 MINI_USER_LOCATION(lng,lat → lat,lng)
+    // 无农场:逆地理用登录点 MINI_USER_LOCATION(lng,lat)
     if (!hasSelectedFarm()) {
-        locationParam = toTencentLocationParam(getMiniUserLocationText());
+        locationParam = toLocationParam(getMiniUserLocationText());
     }
     if (!locationParam) {
         const locationPoint =
@@ -617,18 +608,15 @@ async function getLocationName(point = currentMapPoint.value) {
         if (!locationPoint) return;
         const farmLocation = convertPointToArray(locationPoint);
         if (Array.isArray(farmLocation) && farmLocation.length >= 2) {
-            locationParam = `${farmLocation[1]},${farmLocation[0]}`;
+            locationParam = `${farmLocation[0]},${farmLocation[1]}`;
         } else if (typeof farmLocation === "string" && farmLocation.includes(",")) {
-            locationParam = toTencentLocationParam(farmLocation);
+            locationParam = toLocationParam(farmLocation);
         } else {
             return;
         }
     }
     try {
-        const { result } = await VE_API.old_mini_map.location({
-            key: MAP_KEY,
-            location: locationParam,
-        });
+        const { result } = await fetchRegeo({ location: locationParam });
         locationName.value = result?.address_component
             ? result.address_component.city + result.address_component.district
             : (result?.address || "") + "";

+ 8 - 5
vue.config.js

@@ -32,12 +32,13 @@ module.exports = {
             return middlewares;
         },
         proxy: {
-            '/ws': {
-                target: 'https://apis.map.qq.com',
+            '/tianditu-api': {
+                target: 'https://api.tianditu.gov.cn',
                 changeOrigin: true,
                 ws: false,
+                pathRewrite: { '^/tianditu-api': '' },
             },
-            '/v3/elevation': {
+            '/v3': {
                 target: 'https://restapi.amap.com',
                 changeOrigin: true,
                 ws: false,
@@ -49,8 +50,8 @@ module.exports = {
             }
         },
         //这里的ip和端口是前端项目的;下面为需要跨域访问后端项目
-        host:"127.0.0.1",
-        port: '8081',
+        host:"0.0.0.0",
+        port: '8080',
         https: false,
         open: false // 配置自动启动浏览器
     },
@@ -71,6 +72,8 @@ module.exports = {
                     SERVER: JSON.stringify(process.env.SERVER),
                     PYSERVER: JSON.stringify(process.env.PYSERVER),
                     NEW_SERVER: JSON.stringify(process.env.NEW_SERVER),
+                    AMAP_KEY: JSON.stringify(process.env.AMAP_KEY || ""),
+                    TIANDITU_KEY: JSON.stringify(process.env.TIANDITU_KEY || ""),
                 },
             },
         ]);