2 Commits af57a04f82 ... 5b15c51f3b

Autor SHA1 Mensaje Fecha
  wangsisi 5b15c51f3b Merge branch 'agriculture' of http://www.sysuimars.cn:3000/feiniao/feiniao-farm-h5 into agriculture hace 1 semana
  wangsisi 8afe3a8caa feat:添加农情相册页面 hace 1 semana

+ 1 - 1
scripts/build-flat-en.js

@@ -11,7 +11,7 @@ const messages = new Function(`${fs.readFileSync(messagesPath, "utf8").replace("
 
 const PHRASES = [
     ["长势报告", "Growth Report"],
-    ["农档案", "Crop Archives"],
+    ["农档案", "Crop Archives"],
     ["农事规划", "Farm Planning"],
     ["有味溯源", "Traceability"],
     ["荔枝长势报告", "Lychee Growth Report"],

+ 1 - 1
src/App.vue

@@ -23,7 +23,7 @@
 
 
     <Tabbar class="tabbar" route fixed v-show="showTab" active-color="#2199F8" inactive-color="#898989">
-        <!-- 托管农户:首页、农档案、农事记录 -->
+        <!-- 托管农户:首页、农档案、农事记录 -->
         <!-- <template v-if="userType == 2"> -->
             <tabbar-item replace to="/growth_report">
                 <span>{{ t("tabbar.growthReport") }}</span>

BIN
src/assets/img/agricultural/icon-1.png


BIN
src/assets/img/agricultural/icon-2.png


BIN
src/assets/img/agricultural/icon-3.png


+ 2 - 2
src/i18n/flat-en.js

@@ -77,7 +77,7 @@ export default {
   '农场面积\'': '[Farm面积\']',
   '农场面积:\'': '[Farm面积:\']',
   '农情互动\'': '[农情 Interaction\']',
-  '农档案\'': 'Crop Archives\'',
+  '农档案\'': 'Crop Archives\'',
   '农情照片"': '[农情照片"]',
   '农情照片\'': '[农情照片\']',
   '农情研判:\'': '[农情研判:\']',
@@ -196,7 +196,7 @@ export default {
   '托管农事 会触发农情互动,并且为您推送农事,取消关注 则不会为您推送任何与该农事相关的内容"': '托管农事 会触发农情互动,并且为您推送农事,Cancel关注 则不会为您推送任何与该农事相关的内容"',
   '托管农事"': '[托管Farm Work"]',
   '托管农事\'': '[托管Farm Work\']',
-  '托管农户:首页、农档案、农事记录 --': '托管农户:首页、Crop Archives、农事记录 --',
+  '托管农户:首页、农档案、农事记录 --': '托管农户:首页、Crop Archives、农事记录 --',
   '执行区域': '[执行区域]',
   '执行区域 --': '[执行区域 --]',
   '执行区域\'': '[执行区域\']',

+ 23 - 1
src/i18n/messages.js

@@ -4,7 +4,7 @@ export default {
     zh: {
         tabbar: {
             growthReport: "长势报告",
-            agriFile: "农档案",
+            agriFile: "农档案",
             agriRecord: "农事规划",
             trace: "有味溯源",
             execute: "农事执行",
@@ -129,6 +129,17 @@ export default {
             selectReproductiveToast: "请选择生育期",
         },
         agriFile: {
+            farmName: "农场名称",
+            farmLocation: "位置位置位置位置",
+            moreFarms: "更多农场",
+            patrolRecord: "巡园记录",
+            agriAlbum: "农情相册",
+            diagnosisReport: "诊断报告",
+            addZone: "新增分区",
+            addAlbum: "新增相册",
+            photoCount: "{count}张",
+            zoneLabel: "分区{n}",
+            zoneOne: "分区一",
             legendZone: "管理分区",
             legendGrowth: "长势异常",
             legendPest: "病虫害异常",
@@ -445,6 +456,17 @@ export default {
             selectReproductiveToast: "Please select reproductive stage",
         },
         agriFile: {
+            farmName: "Farm Name",
+            farmLocation: "Location",
+            moreFarms: "More farms",
+            patrolRecord: "Patrol records",
+            agriAlbum: "Agri album",
+            diagnosisReport: "Diagnosis report",
+            addZone: "Add zone",
+            addAlbum: "Add album",
+            photoCount: "{count} photos",
+            zoneLabel: "Zone {n}",
+            zoneOne: "Zone 1",
             legendZone: "Management zone",
             legendGrowth: "Growth Abnormality",
             legendPest: "Pest &Disease",

+ 332 - 305
src/views/old_mini/agri_file/index copy.vue

@@ -1,89 +1,75 @@
 <template>
-    <div class="agri-file" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
+    <div class="agri-file-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
         <!-- 天气遮罩 -->
         <div class="weather-mask" v-show="isExpanded" @click="handleMaskClick"></div>
-        <!-- 天气 -->
-        <weather-info ref="weatherInfoRef" :hasWeather="false" from="monitor" class="weather-info"
-            @weatherExpanded="weatherExpanded" @changeGarden="changeGarden" @changeGardenTab="changeGardenTab"
-            :isGarden="true" :gardenId="defaultGardenId">
-            <template #types-content>
-                <div class="type-tabs">
-                    <div class="type-item" @click="changeType(farmVarietyName)"
-                        :class="{ 'type-item-active': activeType === farmVarietyName }">{{ farmVarietyName }}</div>
-                    <!-- <div class="type-item" @click="changeType('水稻')" :class="{ 'type-item-active': activeType === '水稻' }">{{ $t('水稻') }}</div> -->
-                    <!-- <div class="type-item" @click="changeType('柑橘')" :class="{ 'type-item-active': activeType === '柑橘' }">{{ $t('柑橘') }}</div> -->
-                    <!-- <div class="type-item" @click="changeType('小麦')" :class="{ 'type-item-active': activeType === '小麦' }">{{ $t('小麦') }}</div> -->
-                </div>
-            </template>
-        </weather-info>
+        <!-- 头部 -->
+        <div class="agri-file-header" :style="activeGardenTab === 'current' ? headerMotionStyle : undefined">
+            <weather-info ref="weatherInfoRef" :hasWeather="false" from="agri_file" class="weather-info"
+                @weatherExpanded="weatherExpanded" @changeGarden="changeGarden" @changeGardenTab="changeGardenTab"
+                @reportTabClick="handleReportTabClick" :isGarden="true"
+                :gardenId="defaultGardenId" />
+        </div>
         <!-- 农场列表 -->
         <div v-show="activeGardenTab === 'list'">
             <garden-list ref="gardenListRef" :garden-id="selectedGardenId" @loaded="handleGardenLoaded"
                 @selectGarden="handleGardenSelected" />
         </div>
 
-        <div class="file-content" v-show="activeGardenTab === 'current'">
-            <!-- 地图图例:分类标识 + 长势等级 -->
-            <div class="map-legend">
-                <div
-                    v-for="item in mapLegendItems"
-                    :key="item.key"
-                    class="map-legend__item"
-                >
-                    <span class="map-legend__pill" :class="item.pillClass"></span>
-                    <span class="map-legend__text">{{ item.label }}</span>
-                </div>
-            </div>
-            <!-- <div class="map-legend-box">
-                <div class="map-legend-box__labels">
-                    <span
-                        v-for="(label, index) in growthScaleLabels"
-                        :key="index"
-                    >{{ label }}</span>
+        <div v-show="activeGardenTab === 'current'" class="tracking-list">
+            <div v-for="item in trackingList" :key="item.id" class="tracking-item"
+                :class="`tracking-item--${item.theme}`" @click="handleTrackingItemClick(item)">
+                <div class="tracking-item__header">
+                    <div class="tracking-item__header-row">
+                        <div class="tracking-item__header-left">
+                            <span class="tracking-item__level" v-show="item.risk_level">{{
+                                formatRiskLevel(item.risk_level) }}</span>
+                            <span class="tracking-item__title">{{ item.first_work?.work_name }}</span>
+                        </div>
+                        <div class="tracking-item__tags">
+                            <span v-for="(tag, tagIndex) in item.list" :key="tagIndex" class="tracking-item__tag">{{
+                                tag?.category_name || tag }}</span>
+                        </div>
+                    </div>
                 </div>
-                <div class="map-legend-box__bar"></div>
-            </div> -->
-            <div class="map-tool-bar">
-                <div
-                    v-for="(item, index) in mapToolItems"
-                    :key="item.key"
-                    class="map-tool-bar__item"
-                    :class="{ 'map-tool-bar__item--active': activeMapTool === index }"
-                    @click="changeMapTool(index)"
-                >
-                    <img
-                        class="map-tool-bar__icon"
-                        :src="require(`@/assets/img/map/tool-${index + 1}.png`)"
-                        :alt="item.label"
-                    />
-                    <span class="map-tool-bar__label">{{ item.label }}</span>
+                <div class="tracking-item__body">
+                    <div class="tracking-item__action">
+                        <div class="tracking-item__action-main">
+                            <div class="tracking-item__icon">
+                                <img :src="item.icon" alt="" />
+                                <div class="tracking-item__reason">{{ item.first_work?.work_reason_short }}</div>
+                            </div>
+                            <div class="tracking-item__info">
+                                <div class="tracking-item__issue">{{ item.first_work?.interaction_issue }}</div>
+                            </div>
+                        </div>
+                        <div class="tracking-item__btn">{{ $t('agriFile.recordNow') }}</div>
+                    </div>
+                    <div class="tracking-item__history" v-show="false">
+                        <div class="tracking-item__history-text">{{ item.historyText }}</div>
+                        <div class="tracking-item__images">
+                            <img v-for="(image, imageIndex) in item.images" :key="imageIndex"
+                                class="tracking-item__thumb" :src="image" alt="" />
+                        </div>
+                    </div>
                 </div>
             </div>
-            <div class="map-container" ref="mapContainer"></div>
-            <file-float
-                v-model:active-tab="activeRecordTab"
-                v-model:active-sub-tab="activeRecordSubTab"
-                :farm-record-data="farmRecordData"
-                :loading="farmRecordLoading"
-                :crop-variety="farmVarietyName"
-            />
         </div>
     </div>
 </template>
 
 <script setup>
-import { computed, nextTick, onActivated, ref, watch } from "vue";
-import { useRoute } from "vue-router";
+import { computed, onActivated, ref } from "vue";
+import { useRoute, useRouter } from "vue-router";
 import { useStore } from "vuex";
+import { useI18n } from "@/i18n";
 import weatherInfo from "@/components/weatherInfo.vue";
 import gardenList from "@/components/gardenList.vue";
-import fileFloat from "./components/fileFloat.vue";
-import FileMap, { RECORD_TAB_KEYS, recordsToCenterPoint, hasFarmBaseImage } from "./fileMap.js";
-import { useI18n } from "@/i18n";
 
-const { t, locale } = useI18n();
+const { t } = useI18n();
+
 const store = useStore();
 const route = useRoute();
+const router = useRouter();
 const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 
 const isExpanded = ref(false);
@@ -92,99 +78,81 @@ const defaultGardenId = ref(null);
 const selectedGardenId = ref(null);
 const gardenListRef = ref(null);
 const activeGardenTab = ref("current");
-const activeType = ref(null);
-
-const MAP_LEGEND_CONFIG = [
-    { key: "zone", labelKey: "agriFile.legendZone", type: "zone" },
-    { key: "growth", labelKey: "agriFile.legendGrowth", type: "growth" },
-    { key: "pest", labelKey: "agriFile.legendPest", type: "pest" },
-];
-
-const GROWTH_SCALE_LABEL_KEYS = [
-    "agriFile.scaleNormal",
-    "agriFile.scaleGood",
-    "agriFile.scaleExcellent",
-];
-
-const mapLegendItems = computed(() =>
-    MAP_LEGEND_CONFIG.map(({ key, labelKey, type }) => ({
-        key,
-        label: t(labelKey),
-        pillClass: `map-legend__pill--${type}`,
-    }))
-);
-
-const growthScaleLabels = computed(() => GROWTH_SCALE_LABEL_KEYS.map((key) => t(key)));
-
-const MAP_TOOL_ITEMS = [
-    { key: "all", label: "底图" },
-    { key: "habitat", label: "植被" },
-    { key: "light", label: "水体" },
-    { key: "water", label: "降水" },
-    // { key: "fengshui", label: "风水" },
-    // { key: "soil", label: "土壤" },
-];
-
-const mapToolItems = MAP_TOOL_ITEMS;
-const activeMapTool = ref(0);
-
-const changeMapTool = (index) => {
-    activeMapTool.value = index;
-    fileMap.toggleBaseImageLayers(index === 1 && hasFarmBaseImage(selectedGardenId.value));
-};
-
-const fileMap = new FileMap();
-
-const farmRecordData = ref({});
-const farmRecordLoading = ref(false);
-
-const activeRecordTab = ref(0);
-const activeRecordSubTab = ref(0);
-
-const getActiveTabKey = () =>
-    RECORD_TAB_KEYS[activeRecordSubTab.value] || RECORD_TAB_KEYS[0];
-
-const getActiveRecords = () => farmRecordData.value[getActiveTabKey()] || [];
-
-const syncFarmRecordMap = () => {
-    if (activeGardenTab.value !== "current") return;
-    nextTick(() => {
-        fileMap.setRecordPolygons(getActiveRecords(), getActiveTabKey());
-    });
-};
-
-const initAgriFileMap = async () => {
-    await nextTick();
-    if (!mapContainer.value) return;
-    fileMap.initMap(recordsToCenterPoint(getActiveRecords()), mapContainer.value);
-    fileMap.showBaseImageByFarmId(selectedGardenId.value);
-    fileMap.toggleBaseImageLayers(activeMapTool.value === 1 && hasFarmBaseImage(selectedGardenId.value));
-};
+const panelExpandProgress = ref(0);
+const panelViewType = ref("risk");
+
+const HEADER_FADE_START = 0.68;
+
+const headerMotionStyle = computed(() => {
+    const progress = panelExpandProgress.value;
+    const fade = progress <= HEADER_FADE_START
+        ? 0
+        : (progress - HEADER_FADE_START) / (1 - HEADER_FADE_START);
+    const opacity = 1 - fade;
+    return {
+        opacity,
+        transform: `translateY(${-14 * fade}px) scale(${1 - fade * 0.04})`,
+        pointerEvents: opacity < 0.15 ? "none" : "auto",
+    };
+});
 
-const getFarmRecord = async () => {
-    farmRecordLoading.value = true;
-    try {
-        const farmData = JSON.parse(localStorage.getItem("selectedFarmData"));
-        const res = await VE_API.monitor.getFarmRecord({
-            farm_id: selectedGardenId.value,
-            variety_code: farmData.farm_variety,
-        });
-        if (res.code === 200) {
-            farmRecordData.value = {
-                phenology: res.data?.phenology ?? [],
-                abnormal: res.data?.abnormal ?? [],
-                farming: res.data?.farming ?? [],
-            };
-            syncFarmRecordMap();
-        }
-    } finally {
-        farmRecordLoading.value = false;
+const currentFarmName = ref("");
+const currentFarmVariety = ref(null);
+
+const defaultThumb = require("@/assets/img/home/banner.png");
+const trackingList = ref([]);
+// const trackingList = ref([
+//     {
+//         id: "phenology",
+//         theme: "blue",
+//         level: "二级",
+//         title: "物候跟踪记录",
+//         tags: ["物候", "物候", "物候"],
+//         icon: require("@/assets/img/report/wh-icon.png"),
+//         reason: "某某原因",
+//         issue: "互动问题互动问题互动问题互动问题互",
+//         recordText: "立即记录",
+//         historyText: "2026.06.07 某某区发生的事情",
+//         images: Array.from({ length: 5 }, () => defaultThumb),
+//     },
+//     {
+//         id: "pest",
+//         theme: "red",
+//         level: "二级",
+//         title: "病虫害态势监控",
+//         tags: ["真菌类", "真菌类", "真菌类"],
+//         icon: require("@/assets/img/report/bh-icon.png"),
+//         reason: "某某原因",
+//         issue: "互动问题互动问题互动问题互动问题互",
+//         recordText: "立即记录",
+//         historyText: "2026.06.07 某某区发生的事情",
+//         images: Array.from({ length: 5 }, () => defaultThumb),
+//     },
+//     {
+//         id: "growth",
+//         theme: "orange",
+//         level: "二级",
+//         title: "长势异常态势跟踪",
+//         tags: ["真菌类", "真菌类", "真菌类"],
+//         icon: require("@/assets/img/report/yc-icon.png"),
+//         reason: "某某原因",
+//         issue: "互动问题互动问题互动问题互动问题互",
+//         recordText: "立即记录",
+//         historyText: "2026.06.07 某某区发生的事情",
+//         images: Array.from({ length: 5 }, () => defaultThumb),
+//     },
+// ]);
+
+const handleReportTabClick = (item) => {
+    if (item.key === "historyRisk") {
+        router.push(
+            `/history_risk_report?farmVariety=${currentFarmVariety.value ?? ""}&currentFarmName=${currentFarmName.value ?? ""}`
+        );
+        return;
     }
+    panelViewType.value = "plot";
 };
 
-watch(activeRecordSubTab, syncFarmRecordMap);
-watch(activeGardenTab, syncFarmRecordMap);
-
 const weatherExpanded = (isExpandedValue) => {
     isExpanded.value = isExpandedValue;
 };
@@ -197,9 +165,10 @@ const handleMaskClick = () => {
 
 const changeGardenTab = (tab) => {
     activeGardenTab.value = tab;
-};
-const changeType = (type) => {
-    activeType.value = type;
+    if (tab !== "current") {
+        panelExpandProgress.value = 0;
+        panelViewType.value = "risk";
+    }
 };
 
 const handleGardenLoaded = ({ hasFarm }) => {
@@ -208,40 +177,68 @@ const handleGardenLoaded = ({ hasFarm }) => {
 
 const handleGardenSelected = (garden) => {
     selectedGardenId.value = garden?.id ?? null;
-    fileMap.showBaseImageByFarmId(selectedGardenId.value);
     weatherInfoRef.value?.setSelectedGarden?.(garden);
 };
 
-const farmVarietyName = ref(null);
 const changeGarden = (data) => {
     if (!data?.id) return;
     store.commit("home/SET_GARDEN_ID", data.id);
     selectedGardenId.value = data.id;
-    fileMap.showBaseImageByFarmId(data.id);
-    getFarmRecord();
-    activeType.value = data.variety_name;
-    farmVarietyName.value = data.variety_name;
+    currentFarmName.value = data.name ?? "";
+    currentFarmVariety.value = data.farm_variety ?? null;
+    getFarmRiskAndTracking();
 };
 
-const mapContainer = ref(null);
-onActivated(async () => {
+onActivated(() => {
     if (route.query?.farmId) {
         defaultGardenId.value = route.query.farmId;
-        await getFarmRecord();
     }
     const savedFarmId = localStorage.getItem("selectedFarmId");
     selectedGardenId.value = savedFarmId ? Number(savedFarmId) : null;
     gardenListRef.value?.refreshFarmList?.();
-    await initAgriFileMap();
 });
+
+const RISK_LEVEL_KEYS = { 1: "agriFile.riskLevel1", 2: "agriFile.riskLevel2", 3: "agriFile.riskLevel3" };
+const formatRiskLevel = (level) => {
+    const key = RISK_LEVEL_KEYS[Number(level)];
+    return key ? t(key) : "";
+};
+
+const currentPheCode = ref('');
+let fetchRequestId = 0;
+const getFarmRiskAndTracking = async () => {
+    const requestId = ++fetchRequestId;
+    trackingList.value = [];
+    const selectedFarmData = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
+    const res = await VE_API.monitor.getFarmRiskAndTracking({
+        farm_id: selectedGardenId.value,
+        crop_type: selectedFarmData.farm_variety,
+        category_code: selectedFarmData.farm_category
+    });
+    if (requestId !== fetchRequestId) return;
+    if (res.code === 200) {
+        currentPheCode.value = res.data.current_phe_code;
+        trackingList.value = [
+            { ...res.data.phenology_tracking, theme: "blue", icon: require("@/assets/img/report/wh-icon.png"), workType: 'phenology' },
+            { ...res.data.growth_abnormal_tracking, theme: "orange", icon: require("@/assets/img/report/yc-icon.png"), workType: 'growth' },
+            { ...res.data.pest_risk_assessment, theme: "red", icon: require("@/assets/img/report/bh-icon.png"), workType: 'pest' },
+        ];
+    }
+};
+
+const handleTrackingItemClick = (item) => {
+    router.push({
+        path: "/record_details",
+        query: { workId: item?.first_work?.work_id, type: item?.workType, curCode: currentPheCode.value },
+    });
+};
 </script>
 
 <style lang="scss" scoped>
-.agri-file {
+.agri-file-page {
     width: 100%;
     height: 100%;
-    background: #F5F7FB;
-    box-sizing: border-box;
+    background: #D0E6FC;
 
     .weather-mask {
         position: fixed;
@@ -253,179 +250,209 @@ onActivated(async () => {
         z-index: 11;
     }
 
-    .weather-info {
-        width: calc(100% - 20px);
+    .agri-file-header {
         position: absolute;
         z-index: 12;
         left: 10px;
         top: 12px;
+        width: calc(100% - 20px);
+        will-change: transform, opacity;
+        transform-origin: center top;
+
+        .weather-info {
+            width: 100%;
+            position: relative;
+            left: auto;
+            top: auto;
+
+            :deep(.garden-tabs) {
+                .garden-item.left-item.active .current-name {
+                    color: #2199F8;
+                    font-weight: 600;
+                }
+            }
+        }
     }
 
-    .file-content {
-        position: relative;
+    .tracking-list {
         height: 100%;
+        padding: 65px 10px 12px;
+        // padding: 105px 10px 12px;
         box-sizing: border-box;
+        overflow-y: auto;
+        display: flex;
+        flex-direction: column;
+        gap: 12px;
 
-        $map-legend-panel-bg: rgba(0, 0, 0, 0.46);
-        $map-legend-text-color: #ffffff;
-        $map-legend-text-size: 12px;
-        $map-legend-scale-gradient: linear-gradient(
-            90deg,
-            #007aff 0%,
-            #00d4aa 25%,
-            #ffe600 50%,
-            #ff9500 75%,
-            #ff3b30 100%
-        );
+        &::-webkit-scrollbar {
+            display: none;
+        }
+    }
 
-        @mixin map-legend-panel-base {
-            position: absolute;
-            z-index: 15;
-            background: $map-legend-panel-bg;
-            backdrop-filter: blur(4px);
-            box-sizing: border-box;
+    .tracking-item {
+        width: 100%;
+        box-shadow: 0px 4px 4px 0px var(--tracking-shadow);
+        border: 1px solid #fff;
+        border-radius: 10px;
+
+        &--blue {
+            --tracking-header-bg: linear-gradient(0deg, #A6D8FF 0%, #39A7FF 100%);
+            --tracking-primary: #2199f8;
+            --tracking-action-border: rgba(33, 153, 248, 0.2);
+            --tracking-shadow: #2199f81a;
         }
 
-        .map-legend {
-            @include map-legend-panel-base;
-            top: 110px;
-            right: 12px;
-            display: flex;
-            align-items: center;
-            justify-content: space-around;
-            gap: 10px;
-            padding: 8px 10px;
-            border-radius: 4px;
-
-            &__item {
-                display: flex;
-                align-items: center;
-                gap: 5px;
-            }
+        &--red {
+            --tracking-header-bg: linear-gradient(0deg, #FD9D9D 0%, #FF6A6A 100%);
+            --tracking-primary: #FF6A6A;
+            --tracking-action-border: rgba(255, 173, 173, 0.2);
+            --tracking-shadow: #ff6b6b1a;
+        }
 
-            &__pill {
-                width: 16px;
-                height: 5px;
-                border-radius: 10px;
+        &--orange {
+            --tracking-header-bg: linear-gradient(0deg, #FCB981 0%, #FF953D 100%);
+            --tracking-primary: #FA8D39;
+            --tracking-action-border: rgba(255, 173, 173, 0.2);
+            --tracking-shadow: #f593421a;
+        }
 
-                &--zone {
-                    background: #1c9e80;
-                }
+        &__header {
+            position: relative;
+            padding: 10px 10px 20px;
+            border-radius: 10px 10px 0 0;
+            background: var(--tracking-header-bg);
+        }
 
-                &--growth {
-                    background: #ff953d;
-                }
+        &__header-row {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+        }
 
-                &--pest {
-                    background: #e03131;
-                }
-            }
+        &__header-left {
+            display: flex;
+            align-items: center;
+            gap: 6px;
+        }
 
-            &__text {
-                font-size: $map-legend-text-size;
-                color: $map-legend-text-color;
-            }
+        &__level {
+            padding: 1px 6px;
+            border-radius: 2px;
+            background: #fff;
+            color: #FF6A6A;
+            font-size: 12px;
+            min-width: fit-content;
         }
 
-        .map-legend-box {
-            @include map-legend-panel-base;
-            top: 150px;
-            right: 10px;
-            width: 222px;
-            padding: 5px 10px;
-            border-radius: 5px;
+        &__title {
+            color: #fff;
+            font-size: 16px;
+            font-weight: 500;
+        }
 
-            &__labels {
-                display: flex;
-                justify-content: space-between;
-                align-items: center;
-                margin-bottom: 8px;
-                font-size: $map-legend-text-size;
-                color: $map-legend-text-color;
-            }
+        &__tags {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+        }
+
+        &__tag {
+            padding: 1px 5px;
+            border-radius: 2px;
+            background: #fff;
+            color: var(--tracking-primary);
+            font-size: 12px;
+            border: .5px solid var(--tracking-primary);
+            min-width: max-content;
+        }
 
-            &__bar {
-                height: 8px;
-                border-radius: 4px;
-                background: $map-legend-scale-gradient;
+        &__body {
+            position: relative;
+            padding: 10px;
+            background: #fff;
+            border-radius: 10px;
+            margin-top: -8px;
+
+            &::before {
+                content: "";
+                position: absolute;
+                top: -7px;
+                left: 60px;
+                width: 0;
+                height: 0;
+                border-left: 7px solid transparent;
+                border-right: 7px solid transparent;
+                border-bottom: 7px solid #fff;
             }
         }
 
-        .map-tool-bar {
-            position: absolute;
-            left: 12px;
-            top: 50%;
-            transform: translateY(-78%);
-            z-index: 15;
+        &__action {
             display: flex;
-            flex-direction: column;
             align-items: center;
-            padding: 10px 6px;
-            background: #ffffff;
-            border-radius: 7px;
-            box-shadow: 0px 2.3px 2.3px 0px #0000001A;
+            justify-content: space-between;
+            gap: 8px;
+            padding: 10px 8px;
+            border: 1px solid var(--tracking-action-border);
+            border-radius: 6px;
             box-sizing: border-box;
+        }
 
-            &__item {
-                display: flex;
-                flex-direction: column;
-                align-items: center;
-                justify-content: center;
-                padding: 10px 5px;
-                color: #C1C1C1;
-
-                .map-tool-bar__icon {
-                    width: 18px;
-                    height: 16px;
-                    margin-bottom: 3px;
-                    filter: grayscale(1);
-                }
-
-                &--active {
-                    color: #2199f8;
+        &__icon {
+            display: flex;
+            align-items: center;
+            gap: 4px;
 
-                    .map-tool-bar__icon {
-                        filter: grayscale(0);
-                    }
-                }
+            img {
+                width: 18px;
+                height: 16px;
             }
+        }
 
-            &__label {
-                font-size: 12px;
-            }
+        &__reason {
+            color: var(--tracking-primary);
+            font-weight: 500;
         }
 
-        .map-container {
-            width: 100%;
-            height: 100%;
+        &__issue {
+            margin-top: 3px;
+            color: var(--tracking-primary);
+            font-size: 12px;
         }
-    }
 
-    .type-tabs {
-        width: 100%;
-        background: #FFF;
-        display: flex;
-        align-items: center;
-        flex-wrap: wrap;
-        gap: 8px;
-        padding: 8px;
-
-        .type-item {
-            height: 28px;
-            line-height: 28px;
-            text-align: center;
-            padding: 0 6px;
-            min-width: 78px;
-            color: #9A9A9A;
-            background: #EFEFEF;
-            box-sizing: border-box;
-            border-radius: 2px;
+        &__btn {
+            padding: 5px 7px;
+            border-radius: 5px;
+            background: var(--tracking-primary);
+            color: #fff;
+            font-size: 12px;
+            min-width: fit-content;
+        }
 
-            &.type-item-active {
-                background: #2199F8;
-                color: #fff;
-            }
+        &__history {
+            margin-top: 10px;
+            padding: 6px 8px;
+            border-radius: 6px;
+            background: rgba(168, 168, 168, 0.1);
+        }
+
+        &__history-text {
+            color: rgba(31, 31, 31, 0.5);
+            font-size: 12px;
+        }
+
+        &__images {
+            display: grid;
+            grid-template-columns: repeat(5, 1fr);
+            gap: 6px;
+            margin-top: 6px;
+        }
+
+        &__thumb {
+            width: 100%;
+            aspect-ratio: 1;
+            border-radius: 8px;
+            object-fit: cover;
         }
     }
 }
-</style>
+</style>

+ 389 - 366
src/views/old_mini/agri_file/index.vue

@@ -1,458 +1,481 @@
 <template>
     <div class="agri-file-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
-        <!-- 天气遮罩 -->
-        <div class="weather-mask" v-show="isExpanded" @click="handleMaskClick"></div>
-        <!-- 头部 -->
-        <div class="agri-file-header" :style="activeGardenTab === 'current' ? headerMotionStyle : undefined">
-            <weather-info ref="weatherInfoRef" :hasWeather="false" from="agri_file" class="weather-info"
-                @weatherExpanded="weatherExpanded" @changeGarden="changeGarden" @changeGardenTab="changeGardenTab"
-                @reportTabClick="handleReportTabClick" :isGarden="true"
-                :gardenId="defaultGardenId" />
+        <div class="farm-header">
+            <div class="farm-header__info">
+                <el-avatar class="farm-header__avatar" :size="46" :src="circleUrl" />
+                <div class="farm-header__text">
+                    <div class="farm-header__name van-ellipsis">{{ farmName }}</div>
+                    <div class="farm-header__location van-ellipsis">{{ farmLocation }}</div>
+                </div>
+            </div>
+            <div class="farm-header__more">
+                <el-icon class="farm-header__more-icon">
+                    <Switch />
+                </el-icon>
+                <span>{{ t("agriFile.moreFarms") }}</span>
+            </div>
         </div>
-        <!-- 农场列表 -->
-        <div v-show="activeGardenTab === 'list'">
-            <garden-list ref="gardenListRef" :garden-id="selectedGardenId" @loaded="handleGardenLoaded"
-                @selectGarden="handleGardenSelected" />
+        <div class="nav-cards">
+            <div v-for="item in navCards" :key="item.key" class="nav-card"
+                :class="{ 'is-active': activeNav === item.key }" @click="activeNav = item.key">
+                <img class="nav-card__icon" :src="item.icon" alt="" />
+                <span class="nav-card__label">{{ t(item.labelKey) }}</span>
+            </div>
         </div>
-
-        <div v-show="activeGardenTab === 'current'" class="tracking-list">
-            <div v-for="item in trackingList" :key="item.id" class="tracking-item"
-                :class="`tracking-item--${item.theme}`" @click="handleTrackingItemClick(item)">
-                <div class="tracking-item__header">
-                    <div class="tracking-item__header-row">
-                        <div class="tracking-item__header-left">
-                            <span class="tracking-item__level" v-show="item.risk_level">{{
-                                formatRiskLevel(item.risk_level) }}</span>
-                            <span class="tracking-item__title">{{ item.first_work?.work_name }}</span>
-                        </div>
-                        <div class="tracking-item__tags">
-                            <span v-for="(tag, tagIndex) in item.list" :key="tagIndex" class="tracking-item__tag">{{
-                                tag?.category_name || tag }}</span>
-                        </div>
-                    </div>
+        <div v-show="activeNav === 'album'" class="content-panel">
+            <div class="map-card">
+                <div ref="mapContainer" class="map-container"></div>
+                <div class="add-zone-btn">
+                    <el-icon>
+                        <Plus />
+                    </el-icon>
+                    <span>{{ t("agriFile.addZone") }}</span>
                 </div>
-                <div class="tracking-item__body">
-                    <div class="tracking-item__action">
-                        <div class="tracking-item__action-main">
-                            <div class="tracking-item__icon">
-                                <img :src="item.icon" alt="" />
-                                <div class="tracking-item__reason">{{ item.first_work?.work_reason_short }}</div>
-                            </div>
-                            <div class="tracking-item__info">
-                                <div class="tracking-item__issue">{{ item.first_work?.interaction_issue }}</div>
-                            </div>
-                        </div>
-                        <div class="tracking-item__btn">{{ $t('agriFile.recordNow') }}</div>
+                <div v-for="item in zoneList" :key="item.id" :ref="(el) => setMarkerEl(item.id, el)"
+                    class="zone-marker">
+                    <div class="zone-marker__label">{{ item.name }}</div>
+                    <div class="zone-marker__thumb">
+                        <img :src="item.cover" alt="" />
+                        <span class="zone-marker__badge">{{ item.count }}</span>
                     </div>
-                    <div class="tracking-item__history" v-show="false">
-                        <div class="tracking-item__history-text">{{ item.historyText }}</div>
-                        <div class="tracking-item__images">
-                            <img v-for="(image, imageIndex) in item.images" :key="imageIndex"
-                                class="tracking-item__thumb" :src="image" alt="" />
-                        </div>
+                    <div class="zone-marker__arrow"></div>
+                </div>
+            </div>
+            <div class="album-grid">
+                <div class="album-add">
+                    <el-icon class="album-add__icon">
+                        <Plus />
+                    </el-icon>
+                    <span>{{ t("agriFile.addAlbum") }}</span>
+                </div>
+                <div v-for="item in albumList" :key="item.id" class="album-card">
+                    <div class="album-card__cover">
+                        <img :src="item.cover" alt="" />
+                        <span class="album-card__count">{{ t("agriFile.photoCount", { count: item.count }) }}</span>
                     </div>
+                    <div class="album-card__name">{{ item.name }}</div>
                 </div>
             </div>
         </div>
+        <div class="add-album-btn">
+            <el-icon>
+                <Plus />
+            </el-icon>
+        </div>
     </div>
 </template>
 
 <script setup>
-import { computed, onActivated, ref } from "vue";
-import { useRoute, useRouter } from "vue-router";
+import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from "vue";
 import { useStore } from "vuex";
+import { Switch, Plus } from "@element-plus/icons-vue";
+import Overlay from "ol/Overlay";
 import { useI18n } from "@/i18n";
-import weatherInfo from "@/components/weatherInfo.vue";
-import gardenList from "@/components/gardenList.vue";
+import FileMap from "./fileMap";
+import * as util from "@/common/ol_common.js";
 
 const { t } = useI18n();
-
 const store = useStore();
-const route = useRoute();
-const router = useRouter();
 const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 
-const isExpanded = ref(false);
-const weatherInfoRef = ref(null);
-const defaultGardenId = ref(null);
-const selectedGardenId = ref(null);
-const gardenListRef = ref(null);
-const activeGardenTab = ref("current");
-const panelExpandProgress = ref(0);
-const panelViewType = ref("risk");
-
-const HEADER_FADE_START = 0.68;
-
-const headerMotionStyle = computed(() => {
-    const progress = panelExpandProgress.value;
-    const fade = progress <= HEADER_FADE_START
-        ? 0
-        : (progress - HEADER_FADE_START) / (1 - HEADER_FADE_START);
-    const opacity = 1 - fade;
-    return {
-        opacity,
-        transform: `translateY(${-14 * fade}px) scale(${1 - fade * 0.04})`,
-        pointerEvents: opacity < 0.15 ? "none" : "auto",
-    };
-});
-
-const currentFarmName = ref("");
-const currentFarmVariety = ref(null);
-
-const defaultThumb = require("@/assets/img/home/banner.png");
-const trackingList = ref([]);
-// const trackingList = ref([
-//     {
-//         id: "phenology",
-//         theme: "blue",
-//         level: "二级",
-//         title: "物候跟踪记录",
-//         tags: ["物候", "物候", "物候"],
-//         icon: require("@/assets/img/report/wh-icon.png"),
-//         reason: "某某原因",
-//         issue: "互动问题互动问题互动问题互动问题互",
-//         recordText: "立即记录",
-//         historyText: "2026.06.07 某某区发生的事情",
-//         images: Array.from({ length: 5 }, () => defaultThumb),
-//     },
-//     {
-//         id: "pest",
-//         theme: "red",
-//         level: "二级",
-//         title: "病虫害态势监控",
-//         tags: ["真菌类", "真菌类", "真菌类"],
-//         icon: require("@/assets/img/report/bh-icon.png"),
-//         reason: "某某原因",
-//         issue: "互动问题互动问题互动问题互动问题互",
-//         recordText: "立即记录",
-//         historyText: "2026.06.07 某某区发生的事情",
-//         images: Array.from({ length: 5 }, () => defaultThumb),
-//     },
-//     {
-//         id: "growth",
-//         theme: "orange",
-//         level: "二级",
-//         title: "长势异常态势跟踪",
-//         tags: ["真菌类", "真菌类", "真菌类"],
-//         icon: require("@/assets/img/report/yc-icon.png"),
-//         reason: "某某原因",
-//         issue: "互动问题互动问题互动问题互动问题互",
-//         recordText: "立即记录",
-//         historyText: "2026.06.07 某某区发生的事情",
-//         images: Array.from({ length: 5 }, () => defaultThumb),
-//     },
-// ]);
-
-const handleReportTabClick = (item) => {
-    if (item.key === "historyRisk") {
-        router.push(
-            `/history_risk_report?farmVariety=${currentFarmVariety.value ?? ""}&currentFarmName=${currentFarmName.value ?? ""}`
-        );
-        return;
-    }
-    panelViewType.value = "plot";
+const defaultCover = require("@/assets/img/home/banner.png");
+const DEFAULT_CENTER = [113.6142086995688, 23.585836479509055];
+
+const defaultAvatar = require("@/assets/img/home/banner.png");
+const farmName = ref("");
+const farmLocation = ref("");
+const circleUrl = ref(defaultAvatar);
+const activeNav = ref("album");
+const mapContainer = ref(null);
+const fileMap = new FileMap();
+const markerEls = {};
+const mapOverlays = [];
+
+const navCards = [
+    {
+        key: "patrol",
+        labelKey: "agriFile.patrolRecord",
+        icon: require("@/assets/img/agricultural/icon-1.png"),
+    },
+    {
+        key: "album",
+        labelKey: "agriFile.agriAlbum",
+        icon: require("@/assets/img/agricultural/icon-2.png"),
+    },
+    {
+        key: "report",
+        labelKey: "agriFile.diagnosisReport",
+        icon: require("@/assets/img/agricultural/icon-3.png"),
+    },
+];
+
+const zoneList = ref([
+    { id: 1, name: "", count: 2, cover: defaultCover, longitude: 113.612409, latitude: 23.587036 },
+    { id: 2, name: "", count: 2, cover: defaultCover, longitude: 113.615809, latitude: 23.586636 },
+    { id: 3, name: "", count: 2, cover: defaultCover, longitude: 113.613609, latitude: 23.584436 },
+    { id: 4, name: "", count: 2, cover: defaultCover, longitude: 113.615609, latitude: 23.585036 },
+]);
+
+const albumList = ref([
+    { id: 1, name: "", count: 125, cover: defaultCover },
+    { id: 2, name: "", count: 125, cover: defaultCover },
+    { id: 3, name: "", count: 125, cover: defaultCover },
+]);
+
+const setMarkerEl = (id, el) => {
+    if (el) markerEls[id] = el;
+    else delete markerEls[id];
 };
 
-const weatherExpanded = (isExpandedValue) => {
-    isExpanded.value = isExpandedValue;
+const getFarmCenter = () => {
+    try {
+        const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
+        const wkt = farm.wkt || farm.geom_wkt || farm.farm_location;
+        if (typeof wkt === "string" && /^POINT\s*\(/i.test(wkt.trim())) {
+            return util.wktCastGeom(wkt).getFirstCoordinate();
+        }
+    } catch {
+        // ignore
+    }
+    return DEFAULT_CENTER;
 };
 
-const handleMaskClick = () => {
-    if (weatherInfoRef.value?.toggleExpand) {
-        weatherInfoRef.value.toggleExpand();
+const clearMapOverlays = () => {
+    if (!fileMap.kmap?.map) {
+        mapOverlays.length = 0;
+        return;
     }
+    mapOverlays.forEach((overlay) => fileMap.kmap.map.removeOverlay(overlay));
+    mapOverlays.length = 0;
 };
 
-const changeGardenTab = (tab) => {
-    activeGardenTab.value = tab;
-    if (tab !== "current") {
-        panelExpandProgress.value = 0;
-        panelViewType.value = "risk";
-    }
+const bindZoneOverlays = () => {
+    if (!fileMap.kmap?.map) return;
+    clearMapOverlays();
+    zoneList.value.forEach((zone) => {
+        const el = markerEls[zone.id];
+        if (!el || zone.longitude == null || zone.latitude == null) return;
+        const overlay = new Overlay({
+            element: el,
+            position: [zone.longitude, zone.latitude],
+            positioning: "bottom-center",
+            stopEvent: false,
+        });
+        fileMap.kmap.map.addOverlay(overlay);
+        mapOverlays.push(overlay);
+    });
 };
 
-const handleGardenLoaded = ({ hasFarm }) => {
-    weatherInfoRef.value?.setGardenLoaded?.(hasFarm);
+const initAlbumMap = async () => {
+    await nextTick();
+    if (!mapContainer.value) return;
+    const center = getFarmCenter();
+    fileMap.initMap(`POINT(${center[0]} ${center[1]})`, mapContainer.value);
+    await nextTick();
+    bindZoneOverlays();
+    fileMap.kmap?.map?.updateSize?.();
 };
 
-const handleGardenSelected = (garden) => {
-    selectedGardenId.value = garden?.id ?? null;
-    weatherInfoRef.value?.setSelectedGarden?.(garden);
+const fillMockLabels = () => {
+    const zoneName = t("agriFile.zoneLabel", { n: 1 });
+    const albumName = t("agriFile.zoneOne");
+    zoneList.value = zoneList.value.map((item) => ({ ...item, name: zoneName }));
+    albumList.value = albumList.value.map((item) => ({ ...item, name: albumName }));
 };
 
-const changeGarden = (data) => {
-    if (!data?.id) return;
-    store.commit("home/SET_GARDEN_ID", data.id);
-    selectedGardenId.value = data.id;
-    currentFarmName.value = data.name ?? "";
-    currentFarmVariety.value = data.farm_variety ?? null;
-    getFarmRiskAndTracking();
+const loadFarmInfo = () => {
+    try {
+        const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
+        farmName.value = farm.farm_name || farm.name || t("agriFile.farmName");
+        farmLocation.value = farm.farm_address || farm.rawAddress || farm.city || t("agriFile.farmLocation");
+        circleUrl.value = farm.cover || farm.image || farm.avatar || defaultAvatar;
+    } catch {
+        farmName.value = t("agriFile.farmName");
+        farmLocation.value = t("agriFile.farmLocation");
+        circleUrl.value = defaultAvatar;
+    }
 };
 
+onMounted(() => {
+    fillMockLabels();
+    loadFarmInfo();
+    initAlbumMap();
+});
 onActivated(() => {
-    if (route.query?.farmId) {
-        defaultGardenId.value = route.query.farmId;
-    }
-    const savedFarmId = localStorage.getItem("selectedFarmId");
-    selectedGardenId.value = savedFarmId ? Number(savedFarmId) : null;
-    gardenListRef.value?.refreshFarmList?.();
+    fillMockLabels();
+    loadFarmInfo();
+    initAlbumMap();
 });
+onBeforeUnmount(clearMapOverlays);
 
-const RISK_LEVEL_KEYS = { 1: "agriFile.riskLevel1", 2: "agriFile.riskLevel2", 3: "agriFile.riskLevel3" };
-const formatRiskLevel = (level) => {
-    const key = RISK_LEVEL_KEYS[Number(level)];
-    return key ? t(key) : "";
-};
-
-const currentPheCode = ref('');
-let fetchRequestId = 0;
-const getFarmRiskAndTracking = async () => {
-    const requestId = ++fetchRequestId;
-    trackingList.value = [];
-    const selectedFarmData = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
-    const res = await VE_API.monitor.getFarmRiskAndTracking({
-        farm_id: selectedGardenId.value,
-        crop_type: selectedFarmData.farm_variety,
-        category_code: selectedFarmData.farm_category
-    });
-    if (requestId !== fetchRequestId) return;
-    if (res.code === 200) {
-        currentPheCode.value = res.data.current_phe_code;
-        trackingList.value = [
-            { ...res.data.phenology_tracking, theme: "blue", icon: require("@/assets/img/report/wh-icon.png"), workType: 'phenology' },
-            { ...res.data.growth_abnormal_tracking, theme: "orange", icon: require("@/assets/img/report/yc-icon.png"), workType: 'growth' },
-            { ...res.data.pest_risk_assessment, theme: "red", icon: require("@/assets/img/report/bh-icon.png"), workType: 'pest' },
-        ];
+watch(activeNav, (key) => {
+    if (key === "album") {
+        initAlbumMap();
     }
-};
-
-const handleTrackingItemClick = (item) => {
-    router.push({
-        path: "/record_details",
-        query: { workId: item?.first_work?.work_id, type: item?.workType, curCode: currentPheCode.value },
-    });
-};
+});
 </script>
 
 <style lang="scss" scoped>
 .agri-file-page {
+    position: relative;
+    display: flex;
+    flex-direction: column;
     width: 100%;
     height: 100%;
-    background: #D0E6FC;
-
-    .weather-mask {
-        position: fixed;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        background-color: rgba(0, 0, 0, 0.52);
-        z-index: 11;
-    }
+    overflow: hidden;
+    background: linear-gradient(180deg, #D1EBFF 0%, #FFFFFF 100%);
 
-    .agri-file-header {
-        position: absolute;
-        z-index: 12;
-        left: 10px;
-        top: 12px;
-        width: calc(100% - 20px);
-        will-change: transform, opacity;
-        transform-origin: center top;
-
-        .weather-info {
-            width: 100%;
-            position: relative;
-            left: auto;
-            top: auto;
+    .farm-header {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 10px;
 
-            :deep(.garden-tabs) {
-                .garden-item.left-item.active .current-name {
-                    color: #2199F8;
-                    font-weight: 600;
-                }
+        .farm-header__info {
+            display: flex;
+            align-items: center;
+            gap: 6px;
+
+            .farm-header__name {
+                font-size: 16px;
+                font-weight: 500;
             }
+
+            .farm-header__location {
+                margin-top: 2px;
+                color: rgba(0, 0, 0, 0.5);
+                font-size: 12px;
+            }
+        }
+
+        .farm-header__more {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+            padding: 6px 10px;
+            border-radius: 16px;
+            background: #fff;
+            color: #2199F8;
         }
     }
 
-    .tracking-list {
-        height: 100%;
-        padding: 65px 10px 12px;
-        // padding: 105px 10px 12px;
-        box-sizing: border-box;
-        overflow-y: auto;
+    .nav-cards {
+        position: relative;
+        z-index: 2;
         display: flex;
-        flex-direction: column;
-        gap: 12px;
+        justify-content: space-around;
+        padding: 10px 10px;
 
-        &::-webkit-scrollbar {
-            display: none;
-        }
-    }
+        .nav-card {
+            position: relative;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            border-radius: 8px;
+            background: #fff;
+            width: 90px;
+            height: 64px;
+            box-shadow: 0px 4px 4px 0px #0000000D;
+
+            .nav-card__icon {
+                width: 26px;
+                height: 26px;
+                margin-bottom: 4px;
+            }
 
-    .tracking-item {
-        width: 100%;
-        box-shadow: 0px 4px 4px 0px var(--tracking-shadow);
-        border: 1px solid #fff;
-        border-radius: 10px;
-
-        &--blue {
-            --tracking-header-bg: linear-gradient(0deg, #A6D8FF 0%, #39A7FF 100%);
-            --tracking-primary: #2199f8;
-            --tracking-action-border: rgba(33, 153, 248, 0.2);
-            --tracking-shadow: #2199f81a;
-        }
+            &.is-active {
+                background: linear-gradient(180deg, #5BB8FF 0%, #2199F8 100%);
+                box-shadow: 0 4px 10px rgba(33, 153, 248, 0.28);
 
-        &--red {
-            --tracking-header-bg: linear-gradient(0deg, #FD9D9D 0%, #FF6A6A 100%);
-            --tracking-primary: #FF6A6A;
-            --tracking-action-border: rgba(255, 173, 173, 0.2);
-            --tracking-shadow: #ff6b6b1a;
-        }
+                .nav-card__icon {
+                    filter: brightness(0) invert(1);
+                }
 
-        &--orange {
-            --tracking-header-bg: linear-gradient(0deg, #FCB981 0%, #FF953D 100%);
-            --tracking-primary: #FA8D39;
-            --tracking-action-border: rgba(255, 173, 173, 0.2);
-            --tracking-shadow: #f593421a;
-        }
+                .nav-card__label {
+                    color: #fff;
+                }
 
-        &__header {
-            position: relative;
-            padding: 10px 10px 20px;
-            border-radius: 10px 10px 0 0;
-            background: var(--tracking-header-bg);
+                &::after {
+                    content: "";
+                    position: absolute;
+                    left: 50%;
+                    bottom: -6px;
+                    transform: translateX(-50%);
+                    border-left: 6px solid transparent;
+                    border-right: 6px solid transparent;
+                    border-top: 6px solid #2199F8;
+                }
+            }
         }
+    }
 
-        &__header-row {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-        }
+    .content-panel {
+        flex: 1;
+        overflow-y: auto;
+        background: linear-gradient(360deg, rgba(229, 243, 255, 0.55) 0%, rgba(255, 255, 255, 0.55) 100%);
+        border-radius: 20px 20px 0 0;
+        border: 1px solid #FFFFFF;
+        padding: 18px 10px;
 
-        &__header-left {
-            display: flex;
-            align-items: center;
-            gap: 6px;
+        &::-webkit-scrollbar {
+            display: none;
         }
 
-        &__level {
-            padding: 1px 6px;
-            border-radius: 2px;
-            background: #fff;
-            color: #FF6A6A;
-            font-size: 12px;
-            min-width: fit-content;
+        .map-card {
+            position: relative;
+            height: 210px;
         }
 
-        &__title {
-            color: #fff;
-            font-size: 16px;
-            font-weight: 500;
+        .map-container {
+            width: 100%;
+            height: 100%;
+            clip-path: inset(0px round 8px);
         }
 
-        &__tags {
+        .zone-marker {
             display: flex;
+            flex-direction: column;
             align-items: center;
-            gap: 4px;
-        }
-
-        &__tag {
-            padding: 1px 5px;
-            border-radius: 2px;
-            background: #fff;
-            color: var(--tracking-primary);
-            font-size: 12px;
-            border: .5px solid var(--tracking-primary);
-            min-width: max-content;
-        }
+            pointer-events: none;
+
+            &__label {
+                padding: 3px 14px;
+                border-radius: 25px;
+                background: #fff;
+                color: #0A0A0A;
+                font-size: 12px;
+            }
 
-        &__body {
-            position: relative;
-            padding: 10px;
-            background: #fff;
-            border-radius: 10px;
-            margin-top: -8px;
+            &__thumb {
+                position: relative;
+                width: 42px;
+                height: 42px;
+                margin-top: 8px;
+
+                img {
+                    width: 100%;
+                    height: 100%;
+                    object-fit: cover;
+                    border: 2px solid #fff;
+                    border-radius: 6px;
+                    box-sizing: border-box;
+                }
+            }
 
-            &::before {
-                content: "";
+            &__badge {
                 position: absolute;
-                top: -7px;
-                left: 60px;
+                top: -6px;
+                right: -6px;
+                min-width: 18px;
+                height: 18px;
+                line-height: 16px;
+                border-radius: 50%;
+                background: #fff;
+                color: #0A0A0A;
+                font-size: 12px;
+                text-align: center;
+            }
+
+            &__arrow {
                 width: 0;
                 height: 0;
-                border-left: 7px solid transparent;
-                border-right: 7px solid transparent;
-                border-bottom: 7px solid #fff;
+                border-left: 5px solid transparent;
+                border-right: 5px solid transparent;
+                border-top: 6px solid #fff;
             }
         }
+    }
 
-        &__action {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            gap: 8px;
-            padding: 10px 8px;
-            border: 1px solid var(--tracking-action-border);
-            border-radius: 6px;
-            box-sizing: border-box;
-        }
+    .add-zone-btn {
+        position: absolute;
+        right: 5px;
+        bottom: 10px;
+        z-index: 5;
+        display: flex;
+        align-items: center;
+        gap: 2px;
+        padding: 5px 10px;
+        border-radius: 4px;
+        background: #fff;
+        color: #2199F8;
+        font-weight: 500;
+    }
+
+    .album-grid {
+        display: grid;
+        grid-template-columns: 1fr 1fr;
+        gap: 16px;
+        margin-top: 16px;
+        box-sizing: border-box;
 
-        &__icon {
+        .album-add {
+            align-self: start;
             display: flex;
+            flex-direction: column;
             align-items: center;
-            gap: 4px;
+            justify-content: center;
+            width: 100%;
+            aspect-ratio: 1;
+            border-radius: 5px;
+            background: #F5F5F7;
+            color: #969494;
+            font-size: 16px;
 
-            img {
-                width: 18px;
-                height: 16px;
+            &__icon {
+                margin-bottom: 6px;
+                font-size: 20px;
             }
         }
 
-        &__reason {
-            color: var(--tracking-primary);
-            font-weight: 500;
-        }
-
-        &__issue {
-            margin-top: 3px;
-            color: var(--tracking-primary);
-            font-size: 12px;
-        }
-
-        &__btn {
-            padding: 5px 7px;
-            border-radius: 5px;
-            background: var(--tracking-primary);
-            color: #fff;
-            font-size: 12px;
-            min-width: fit-content;
-        }
+        .album-card {
+            width: 100%;
 
-        &__history {
-            margin-top: 10px;
-            padding: 6px 8px;
-            border-radius: 6px;
-            background: rgba(168, 168, 168, 0.1);
-        }
+            &__cover {
+                position: relative;
+                width: 100%;
+                aspect-ratio: 1;
+                img {
+                    width: 100%;
+                    height: 100%;
+                    object-fit: cover;
+                    border-radius: 6px;
+                }
+            }
 
-        &__history-text {
-            color: rgba(31, 31, 31, 0.5);
-            font-size: 12px;
-        }
+            &__count {
+                position: absolute;
+                top: 1px;
+                right: 0;
+                padding: 1px 5px;
+                border-radius: 0 5px 0 5px;
+                background: rgba(0, 0, 0, 0.4);
+                backdrop-filter: blur(4px);
+                color: #fff;
+            }
 
-        &__images {
-            display: grid;
-            grid-template-columns: repeat(5, 1fr);
-            gap: 6px;
-            margin-top: 6px;
+            &__name {
+                margin-top: 10px;
+                color: #1F1F1F;
+            }
         }
+    }
 
-        &__thumb {
-            width: 100%;
-            aspect-ratio: 1;
-            border-radius: 8px;
-            object-fit: cover;
-        }
+    .add-album-btn {
+        position: absolute;
+        bottom: 78px;
+        right: 16px;
+        z-index: 10;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 44px;
+        height: 44px;
+        border-radius: 50%;
+        background: #2199F8;
+        color: #fff;
+        font-size: 22px;
+        box-shadow: 0 4px 10px rgba(33, 153, 248, 0.35);
     }
 }
 </style>

+ 1 - 1
src/views/old_mini/dev_login.vue

@@ -16,7 +16,7 @@ let userId = route.query.userId;
 
 onMounted(async () => {
     const token = route.query.token
-    let targetUrl = route.query.targetUrl ? route.query.targetUrl : '/agri_record';
+    let targetUrl = route.query.targetUrl ? route.query.targetUrl : '/growth_report';
     
     // 先从 session 获取保存的角色
     let savedRole = null;