Procházet zdrojové kódy

feat:修改农情档案页面

wangsisi před 16 hodinami
rodič
revize
77b8e20ecb

binární
src/assets/img/report/bh-icon.png


binární
src/assets/img/report/wh-icon.png


binární
src/assets/img/report/yc-icon.png


+ 389 - 264
src/components/weatherInfo copy.vue

@@ -1,283 +1,202 @@
 <template>
-    <div class="weather-info is-garden" :class="{ expanded: isExpanded}">
+    <div class="weather-info is-garden"
+        :class="{ expanded: isExpanded, 'no-farm': !hasFarm, 'no-weather': !hasWeather, 'farm-list': activeGarden === 'list' }">
         <div class="header flex-center">
             <div class="header-left">
                 <div class="address-select flex-center" v-if="hasFarm">
-                    <el-dropdown class="select-garden" trigger="click" popper-class="select-garden-popper">
-                        <div class="el-dropdown-link flex-center">
-                            <span class="ellipsis-l1">{{ farmName }}</span>
-                            <div class="default-text" v-show="isDefaultFarm">默认</div>
-                            <el-icon class="el-icon--right"><arrow-down /></el-icon>
+                    <div class="garden-tabs">
+                        <div class="garden-item left-item" @click="handleGardenClick('current')"
+                            :class="{ 'active': activeGarden === 'current' }">
+                            <img class="current-icon"
+                                :src="activeGarden === 'current' ? require('@/assets/img/common/farm-active.png') : require('@/assets/img/common/farm.png')"
+                                alt="">
+                            <span class="current-name van-ellipsis">{{ farmName }}</span>
                         </div>
-                        <template #dropdown>
-                            <el-dropdown-menu>
-                                <el-dropdown-item
-                                    @click="handleCommand(item)"
-                                    v-for="item in farmList"
-                                    :key="item.id"
-                                    :class="{ 'selected-active-garden': farmId === item.id }"
-                                >
-                                    <span>{{ item.name }}</span>
-                                    <span v-if="item.defaultOption" class="dropdown-default-text">默认</span>
-                                </el-dropdown-item>
-                            </el-dropdown-menu>
-                        </template>
-                    </el-dropdown>
-                    <div class="btn-wrap">
-                        <div class="add-garden" @click="handleFarmInfo">农场信息</div>
-                        <div class="add-garden gray-btn" @click="handleAddFarm">
-                            <el-icon><Plus /></el-icon>
-                            <span>新建农场</span>
+                        <img class="title-block" v-show="activeGarden === 'current'"
+                            src="@/assets/img/common/title-block.png" alt="">
+                        <img class="title-block" v-show="activeGarden !== 'current'"
+                            src="@/assets/img/common/title-block-active.png" alt="">
+                        <div class="garden-item right-item" @click="handleGardenClick('list')"
+                            :class="{ 'active': activeGarden === 'list' }">
+                            <img class="current-icon"
+                                :src="activeGarden === 'list' ? require('@/assets/img/common/menu-active.png') : require('@/assets/img/common/menu.png')"
+                                alt="">
+                            <span class="current-name">{{ t("weather.farmList") }}</span>
+                        </div>
+
+                        <div class="mask-wrap" v-if="props.showTabMask" @click="emit('closeTabMask')">
+                            <div class="mask-content">
+                                <div class="mask-text">
+                                    {{ t("weather.farmListGuide") }}
+                                </div>
+                            </div>
                         </div>
                     </div>
                 </div>
 
                 <div class="address-select flex-center farm-name" v-else>
-                    示范农场
+                    {{ t("common.demoFarm") }}
                 </div>
-                <div class="temperature flex-center">
-                    <div class="temperature-number">{{ currentWeather.temp || '--' }}</div>
-                    <div class="temperature-text">
-                        <span>{{ locationName || '--' }}</span>
-                        <div class="temperature-text-time">
-                            <span>{{ currentWeather.text }}-</span>
-                            <span>{{ currentDateText }}</span>
-                            <span v-show="!isExpanded" class="temperature-text-more" @click="toggleExpand">
-                                展开更多天气
-                            </span>
+                <div class="farm-l" v-show="activeGarden === 'current' && hasWeather">
+                    <div class="temperature flex-center">
+
+                        <div class="weather-icon" v-if="currentWeather.iconDay">
+                            <i :class="'qi-' + currentWeather.iconDay + '-fill'"></i>
                         </div>
+                        <div class="temperature-number">{{ currentWeather.temp || '--' }}</div>
+                        <div class="temperature-text">
+                            <span>{{ currentWeather.text }}</span>
+                            <div class="temperature-text-time">
+                                <span>{{ currentDateText }}</span>
+                                <span class="temperature-text-date">{{ currentWeekText }}</span>
+                                <span v-show="!isExpanded" class="temperature-text-more" @click="toggleExpand">
+                                    {{ t("weather.expandMore") }}
+                                </span>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="button-wrap" v-if="hasFarm">
+                        <!-- <div class="button-item" @click="handleFarmInfo">
+                            <img class="button-pre" src="@/assets/img/common/info.png" alt="">
+                            {{ t("weather.farmInfoMaintain") }}
+                        </div> -->
+                        <!-- <div class="button-item">
+                            <img class="button-pre" src="@/assets/img/common/idea.png" alt="">
+                            种植档案管理
+                        </div> -->
                     </div>
                 </div>
-            </div>
-            <div class="weather-icon" v-if="currentWeather.iconDay">
-                <i :class="'qi-'+currentWeather.iconDay + '-fill'"></i>
+
+                <slot v-if="!hasWeather" name="types-content"></slot>
             </div>
             <!-- <div class="weather-icon" v-else>
                 <img :src="`https://birdseye-img.sysuimars.com/weather/${currentWeather.iconDay}.svg`" alt="" />
             </div> -->
         </div>
-        <div class="weather-chart-container">
+        <div class="weather-chart-container" v-show="activeGarden === 'current' && hasWeather">
             <div class="weather-chart-title">
-                <span>未来七天天气</span>
-                <div class="weather-chart-title-more" @click="toggleExpand">收起</div>
+                <span>{{ t("weather.future7Days") }}</span>
+                <div class="weather-chart-title-more" @click="toggleExpand">{{ t("weather.collapse") }}</div>
             </div>
             <weather-chart class="weather-chart" :weather-data="weatherData"></weather-chart>
         </div>
-        <!-- 农场信息 -->
-        <farm-info-popup
-            ref="myFarmInfoRef"
-            :showEditBtn="false"
-            :showBtn="true"
-            :farmId="farmId"
-            @success="onFarmBasicInfoSaved"
-        ></farm-info-popup>
+        <!-- 农场筛选 -->
+        <div class="farm-filter" v-show="activeGarden === 'list'">
+            <div class="filter-l">
+                <el-input v-model="searchFarm" style="width: 96px" :placeholder="t('weather.searchFarm')" :prefix-icon="Search" />
+            </div>
+            <div class="filter-r">
+                <el-select v-model="regionVal" :placeholder="t('weather.selectRegion')" style="width: 92px">
+                    <el-option v-for="item in regionOptions" :key="item.value" :label="item.label"
+                        :value="item.value" />
+                </el-select>
+                <el-select v-model="typeVal" :placeholder="t('weather.selectCategory')" style="width: 92px">
+                    <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+            </div>
+        </div>
     </div>
 
 </template>
 
 <script setup>
-import { ref, onActivated, computed, watch, onMounted } from "vue";
+import { ref, onActivated, computed } from "vue";
 import weatherChart from "./weatherChart.vue";
 import { useRouter } from "vue-router";
 import { useStore } from "vuex";
-import farmInfoPopup from "@/views/old_mini/home/components/farmInfoPopup.vue";
+import { Search } from '@element-plus/icons-vue';
 import { convertPointToArray } from "@/utils/index";
+import { useI18n } from "@/i18n";
+
+const { t } = useI18n();
 const store = useStore();
 
 const props = defineProps({
+    showTabMask: {
+        type: Boolean,
+        default: false
+    },
     gardenId: {
         type: [Number, String],
         default: null
     },
+    hasWeather: {
+        type: Boolean,
+        default: true
+    },
     from: {
         type: String,
         default: null
     }
 });
 
+const regionVal = ref('');
+const typeVal = ref('');
+const searchFarm = ref('');
+const regionOptions = ref([{
+    label: '地区1',
+    value: '1'
+}, {
+    label: '地区2',
+    value: '2'
+}]);
+const typeOptions = ref([{
+    label: '品类1',
+    value: '1'
+}]);
+
 // 定义emit事件
-const emit = defineEmits(['weatherExpanded','changeGarden']);
+const emit = defineEmits(['weatherExpanded', 'changeGarden', 'changeGardenTab', 'closeTabMask']);
 const router = useRouter();
-const handleCommand = ({id, name}) => {
-    farmName.value = name;
-    farmId.value = id;
-    // 更新默认农场标识
-    const selectedFarm = farmList.value.find(farm => farm.id === id);
-    isDefaultFarm.value = selectedFarm ? selectedFarm.defaultOption || false : false;
-    // 保存用户选择的农场到 localStorage
-    localStorage.setItem('selectedFarmId', id);
-    localStorage.setItem('selectedFarmName', name);
-    localStorage.setItem('selectedFarmPoint', selectedFarm.wkt);
-    getLocationName();
-    getWeatherData();
-    emit('changeGarden',{id, name});
-};
 
 const isExpanded = ref(false);
 const toggleExpand = () => {
     isExpanded.value = !isExpanded.value;
-    emit('weatherExpanded',isExpanded.value);
+    emit('weatherExpanded', isExpanded.value);
 };
 
 const farmId = ref(null);
 const farmName = ref("");
-const farmList = ref([]);
-const hasFarm = ref(true)
-const isDefaultFarm = ref(false); // 添加默认农场标识
-
-// 根据传入的gardenId设置农场(先刷新列表再设置)
-async function setFarmByGardenId(gardenIdValue) {
-    if (!gardenIdValue) {
-        return false;
-    }
-
-    // 先刷新农场列表,确保数据是最新的
-    return new Promise((resolve) => {
-        VE_API.farm.listByUserId().then(({data}) => {
-            // const fullData = data.filter(item => item.userType === 2);
-            const fullData = data;
-            farmList.value = fullData || [];
-            if (fullData && fullData.length > 0) {
-                hasFarm.value = true;
-                const targetFarm = fullData.find(farm => farm.id == gardenIdValue);
-                if (targetFarm) {
-                    farmName.value = targetFarm.name;
-                    farmId.value = Number(gardenIdValue);
-                    isDefaultFarm.value = targetFarm.defaultOption || false;
-                    // 保存到 localStorage
-                    localStorage.setItem('selectedFarmId', farmId.value);
-                    localStorage.setItem('selectedFarmName', farmName.value);
-                    localStorage.setItem('selectedFarmPoint', targetFarm.wkt);
-
-                    getLocationName();
-                    getWeatherData();
-                    emit('changeGarden', { id: farmId.value, name: farmName.value });
-                    resolve(true);
-                } else {
-                    resolve(false);
-                }
-            } else {
-                farmList.value = [];
-                hasFarm.value = false;
-                getLocationName();
-                getWeatherData();
-                resolve(false);
-            }
-        }).catch(() => {
-            resolve(false);
-        });
-    });
-}
-
-// 获取农场列表
-function getFarmList() {
-    // 如果传入了 gardenId,优先使用 setFarmByGardenId(它会刷新列表并设置)
-    if (props.gardenId) {
-        setFarmByGardenId(props.gardenId).then((setSuccess) => {
-            // 如果设置失败,使用已获取的列表数据执行默认逻辑(避免重复请求)
-            if (!setSuccess && farmList.value && farmList.value.length > 0) {
-                selectFarmFromList(farmList.value);
-            } else if (!setSuccess) {
-                // 如果列表为空,再次获取列表
-                getFarmListWithoutGardenId();
-            }
-        });
-        return;
-    }
+const hasFarm = ref(false)
+const activeGarden = ref('current');
 
-    // 如果没有传入 gardenId,执行正常逻辑
-    getFarmListWithoutGardenId();
-}
+onActivated(() => {
+    handleGardenClick('current');
+});
 
-// 从列表中选择农场(使用已有列表数据)
-function selectFarmFromList(data) {
-    // 使用 localStorage 中保存的农场选择
-    const savedFarmId = localStorage.getItem('selectedFarmId');
-    const savedFarmName = localStorage.getItem('selectedFarmName');
-    if (savedFarmId && savedFarmName) {
-        // 检查保存的农场是否还在当前列表中(名称以接口列表为准,避免改名后仍显示 localStorage 旧值)
-        const savedFarm = data.find(farm => farm.id == savedFarmId);
-        if (savedFarm) {
-            farmName.value = savedFarm.name;
-            farmId.value = Number(savedFarmId);
-            isDefaultFarm.value = savedFarm.defaultOption || false;
-            localStorage.setItem('selectedFarmPoint', savedFarm.wkt);
-            localStorage.setItem('selectedFarmName', savedFarm.name);
-        } else {
-            // 如果保存的农场不在列表中,按优先级选择
-            selectDefaultFarm(data);
-        }
-    } else {
-        // 如果没有保存的选择,按优先级选择
-        selectDefaultFarm(data);
+function setGardenLoaded(hasFarmData) {
+    hasFarm.value = !!hasFarmData;
+    if (!hasFarm.value) {
+        farmId.value = null;
+        farmName.value = "";
     }
-    getLocationName();
-    getWeatherData();
-    emit('changeGarden',{id: farmId.value, name: farmName.value});
-}
-
-// 获取农场列表(不处理传入的gardenId)
-function getFarmListWithoutGardenId() {
-    VE_API.farm.listByUserId().then(({data}) => {
-        // const fullData = data.filter(item => item.userType === 2);
-        const fullData = data;
-        farmList.value = fullData || [];
-        if (fullData && fullData.length > 0) {
-            hasFarm.value = true;
-            selectFarmFromList(fullData);
-        } else {
-            farmList.value = [];
-            hasFarm.value = false;
-            getLocationName();
-            getWeatherData();
-        }
-    })
 }
 
-// 监听父组件传入的gardenId变化
-watch(() => props.gardenId, (newGardenId) => {
-    if (newGardenId) {
-        // 直接调用 setFarmByGardenId,它会刷新列表并设置
-        setFarmByGardenId(newGardenId);
-    }
-}, { immediate: false });
-
-// 选择默认农场的逻辑
-function selectDefaultFarm(data) {
-    // 首先查找 defaultOption 为 true 的农场
-    const defaultFarm = data.find(farm => farm.defaultOption === true);
-
-    if (defaultFarm) {
-        // 如果有默认农场,选择它
-        farmName.value = defaultFarm.name;
-        farmId.value = defaultFarm.id;
-        isDefaultFarm.value = true;
-        localStorage.setItem('selectedFarmPoint', defaultFarm.wkt);
-    } else {
-        // 如果没有默认农场,选择第一个
-        farmName.value = data[0].name;
-        farmId.value = data[0].id;
-        isDefaultFarm.value = data[0].defaultOption || false;
-        localStorage.setItem('selectedFarmPoint', data[0].wkt);
+function setSelectedGarden(payload) {
+    if (!payload?.id) {
+        setGardenLoaded(false);
+        getLocationName();
+        getWeatherData();
+        return;
     }
 
-    // 保存到 localStorage
-    localStorage.setItem('selectedFarmId', farmId.value);
-    localStorage.setItem('selectedFarmName', farmName.value);
-    localStorage.setItem('selectedFarmPoint', data[0].wkt);
+    hasFarm.value = true;
+    farmId.value = Number(payload.id);
+    farmName.value = payload.name || "";
     getLocationName();
     getWeatherData();
+    emit('changeGarden', payload);
 }
 
-onActivated(() => {
-    getFarmList();
-});
+const handleFarmInfo = () => {
+    router.push(`/farm_info?subjectId=${farmId.value}`);
+}
 
-// 暴露刷新方法供父组件调用
-defineExpose({
-    refreshFarmList: getFarmList,
-    toggleExpand
-});
+const handleGardenClick = (type) => {
+    activeGarden.value = type;
+    emit("changeGardenTab", type);
+};
 
 const locationName = ref("");
 const weatherData = ref(null);
@@ -288,39 +207,15 @@ function getLocationName() {
     const farmLocation = convertPointToArray(locationPoint);
     let formattedLocation = `${farmLocation[1]},${farmLocation[0]}`;
     const params = {
-            key: MAP_KEY,
-            location: formattedLocation,
-        };
-        VE_API.old_mini_map.location(params).then(({ result }) => {
-            // locationVal.value = result.formatted_addresses.recommend;
-            locationName.value = result?.address_component
-                ? result.address_component.city + result.address_component.district
-                : result?.address + "";
-        });
-}
-
-const myFarmInfoRef = ref(null);
-
-/** 弹窗内修改农场名称等成功后,同步头部展示与下拉列表、本地缓存 */
-function onFarmBasicInfoSaved(payload) {
-    if (!payload?.id || payload.name == null) {
-        return;
-    }
-    const savedId = Number(payload.id);
-    const idx = farmList.value.findIndex((f) => f.id == savedId);
-    if (idx !== -1) {
-        farmList.value[idx] = { ...farmList.value[idx], name: payload.name };
-    }
-    if (farmId.value == savedId) {
-        farmName.value = payload.name;
-        localStorage.setItem('selectedFarmName', payload.name);
-        emit('changeGarden', { id: farmId.value, name: farmName.value });
-    }
-}
-
-const handleFarmInfo = () => {
-    // myFarmInfoRef.value.handleShow();
-    router.push(`/farm_info?subjectId=${farmId.value}`);
+        key: MAP_KEY,
+        location: formattedLocation,
+    };
+    VE_API.old_mini_map.location(params).then(({ result }) => {
+        // locationVal.value = result.formatted_addresses.recommend;
+        locationName.value = result?.address_component
+            ? result.address_component.city + result.address_component.district
+            : result?.address + "";
+    });
 }
 const handleAddFarm = () => {
     router.push(`/create_farm?from=${props.from}&isReload=true`);
@@ -339,8 +234,9 @@ function getWeatherData() {
             const today = data.daily[0];
             currentWeather.value = {
                 temp: today.tempMax || today.tempMin || 26,
-                text: today.textDay || "晴天",
-                iconDay: today.iconDay
+                text: today.textDay || t("weather.sunny"),
+                iconDay: today.iconDay,
+                fxDate: today.fxDate
             };
         }
     }).catch(() => {
@@ -355,56 +251,140 @@ const currentDateText = computed(() => {
     const day = String(now.getDate()).padStart(2, '0');
     return `${month}/${day}`;
 });
+
+const currentWeekText = computed(() => {
+    if (!currentWeather.value?.fxDate) {
+        return "";
+    }
+
+    // 兼容 "YYYY-MM-DD" 格式,避免部分环境下直接 new Date() 解析异常
+    const date = new Date(currentWeather.value.fxDate.replace(/-/g, "/"));
+    if (Number.isNaN(date.getTime())) {
+        return "";
+    }
+
+    return t(`week.${date.getDay()}`);
+});
+
+
+// 暴露方法供父页面(如长势报告)同步农场列表状态
+defineExpose({
+    setGardenLoaded,
+    setSelectedGarden,
+    toggleExpand,
+    handleGardenClick
+});
 </script>
 
 <style lang="scss" scoped>
+.mask-wrap {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: calc(100% - 300px);
+    background-color: rgba(0, 0, 0, 0.52);
+    z-index: 99999;
+    color: rgba(0, 0, 0, 0.9);
+    .mask-content {
+        position: absolute;
+        right: 51px;
+        top: 58px;
+        .mask-text {
+            background: #fff;
+            padding: 6px 16px;
+            border-radius: 10px;
+            font-size: 14px;
+            line-height: 22px;
+            text-align: center;
+            width: fit-content;
+            box-sizing: border-box;
+            border: 1px solid #DCDCDC;
+            position: relative;
+            z-index: 10px;
+            &::after {
+                content: '';
+                position: absolute;
+                right: 24px;
+                top: -10px;
+                width: 0;
+                height: 0;
+                border-right: 5px solid transparent;
+                border-top: 5px solid transparent;
+                border-left: 5px solid transparent;
+                border-bottom: 5px solid #fff;
+            }
+        }
+    }
+}
 .weather-info {
     width: 100%;
     // height: 58px;
-    height: 70px;
+    height: 80px;
     border-radius: 14px;
-    background-color: #ffffff;
-    padding: 10px 12px;
+    border: 0.5px solid #ffffff;
+    background: linear-gradient(180deg, #89CBFF 0%, #2199F8 100%);
     box-sizing: border-box;
     transition: height 0.3s ease-in-out;
     overflow: hidden;
 
     &.is-garden {
-        height: 85px;
-        box-shadow: 0px 1px 5.5px 0px #00000005;
         border-radius: 8px;
-        padding: 10px 12px;
+        box-shadow: 0px -2px 4px 0px #0000000D;
+        // height: 130px;
+        height: 96px;
+    }
+
+    &.no-weather {
+        height: auto;
+    }
+
+    &.no-farm {
+        height: 80px;
+
+        &.expanded {
+            height: 312px;
+        }
     }
 
     &.expanded {
-        height: 312px;
-        background-image: linear-gradient(90deg, #e2f1fe 0%, #ffffff 80%);
+        height: 362px;
     }
 
-    &.bg-white{
+    &.bg-white {
         border-radius: 14px;
         background-image: linear-gradient(90deg, #e2f1fe 0%, #ffffff 80%);
     }
 
+    &.farm-list {
+        height: 94px;
+    }
+
     .flex-center {
         display: flex;
         align-items: center;
     }
+
     .header {
         display: flex;
         align-items: flex-end;
         justify-content: space-between;
+
         .header-left {
+            width: 100%;
+
             .address-select {
                 .select-garden {
                     width: fit-content;
                     max-width: 170px;
                     margin-right: 8px;
                     margin-bottom: 10px;
+
                     .el-dropdown-link {
                         font-size: 15px;
                         font-weight: 500;
                         color: #000000;
+
                         span {
                             width: fit-content;
                             max-width: 95%;
@@ -424,13 +404,15 @@ const currentDateText = computed(() => {
                         }
                     }
                 }
-                .btn-wrap{
+
+                .btn-wrap {
                     position: absolute;
                     right: 10px;
                     top: 8px;
                     display: flex;
                     gap: 8px;
                 }
+
                 .add-garden {
                     font-size: 12px;
                     color: #2199f8;
@@ -440,34 +422,47 @@ const currentDateText = computed(() => {
                     display: flex;
                     align-items: center;
                 }
+
                 .gray-btn {
                     color: #919191;
                     border: 1px solid rgba(145, 145, 145, 0.5);
                 }
             }
+
             .farm-name {
                 font-size: 16px;
                 color: #1D2129;
+                background: #fff;
+                padding: 2px 0 4px 12px;
             }
+
             .temperature {
+                padding-left: 10px;
+                width: 100%;
+                box-sizing: border-box;
+                position: relative;
+                background: #fff;
+
                 .temperature-number {
-                    font-size: 40px;
+                    font-size: 38px;
                     position: relative;
-                    margin-right: 14px;
+                    margin: 0 8px;
+
                     &::after {
                         content: "°";
-                        font-size: 18px;
+                        font-size: 10px;
                         position: absolute;
                         right: -6px;
                         top: 2px;
                     }
                 }
+
                 .temperature-text {
-                    font-weight: 500;
                     .temperature-text-time {
                         font-size: 12px;
                         font-weight: 400;
                     }
+
                     .temperature-text-more {
                         font-size: 12px;
                         color: #2199f8;
@@ -475,13 +470,20 @@ const currentDateText = computed(() => {
                         font-weight: 500;
                         cursor: pointer;
                     }
+
+                    .temperature-text-date {
+                        color: #1D2129;
+                        margin-left: 4px;
+                    }
                 }
             }
         }
+
         .header-right {
             width: 84px;
             height: 73px;
         }
+
         .weather-icon {
             i {
                 font-size: 40px;
@@ -489,17 +491,135 @@ const currentDateText = computed(() => {
             }
         }
     }
+
+    .garden-tabs {
+        width: 100%;
+        display: flex;
+        align-items: center;
+
+        .garden-item {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 6px;
+            height: 40px;
+            width: calc(50% - 17px);
+
+            // width:100px;
+            // background: linear-gradient(180deg, #89CBFF 0%, #2199F8 100%);
+            &.left-item {
+                padding-left: 10px;
+            }
+
+            &.right-item {
+                padding-right: 10px;
+                z-index: 9999999;
+            }
+
+            &.active {
+                background: #fff;
+
+                // background: linear-gradient(360deg, #FFFFFF 30.18%, #FFFFFF 82.87%, #D3ECFF 106.69%);
+                .current-name {
+                    color: #2199F8;
+                }
+            }
+
+            .current-name {
+                font-size: 18px;
+                color: #fff;
+                font-family: "PangMenZhengDao";
+            }
+
+            .current-icon {
+                width: 16px;
+            }
+        }
+
+        .title-block {
+            width: 34px;
+            height: 40px;
+        }
+    }
+
+    .button-wrap {
+        background: #fff;
+        display: flex;
+        justify-content: space-between;
+        gap: 10px;
+        color: #2199F8;
+        padding: 8px 10px;
+
+        .button-item {
+            border: 0.5px solid rgba(33, 153, 248, 0.5);
+            display: flex;
+            border-radius: 4px;
+            align-items: center;
+            height: 28px;
+            line-height: 28px;
+            box-sizing: border-box;
+            gap: 4px;
+            font-size: 12px;
+            flex: auto;
+            justify-content: center;
+        }
+
+        .button-pre {
+            width: 14px;
+            height: 14px;
+        }
+    }
+
+    .farm-filter {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 12px 7px 8px 7px;
+        height: 53px;
+        box-sizing: border-box;
+        background: #ffffff;
+
+        .filter-l {
+            ::v-deep {
+                .el-input__wrapper {
+                    border-radius: 20px;
+                }
+
+                .el-input .el-input__icon {
+                    margin-right: 3px;
+                }
+            }
+        }
+
+        .filter-r {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+
+            ::v-deep {
+                .el-select__wrapper {
+                    border-radius: 20px;
+                    padding: 4px 6px 4px 10px;
+                }
+            }
+        }
+    }
+
     .weather-chart-container {
         width: 100%;
         height: 100%;
         overflow: hidden;
-        margin-top: 8px;
+        padding: 8px 10px 0 10px;
+        box-sizing: border-box;
+        background: #ffffff;
+
         .weather-chart-title {
             display: flex;
             justify-content: space-between;
             align-items: center;
             font-size: 12px;
             font-weight: 500;
+
             .weather-chart-title-more {
                 color: #828282;
                 cursor: pointer;
@@ -509,6 +629,7 @@ const currentDateText = computed(() => {
                 border: 1px solid rgba(130, 130, 130, 0.5);
             }
         }
+
         .weather-chart {
             margin-top: 5px;
             width: 100%;
@@ -521,20 +642,24 @@ const currentDateText = computed(() => {
 .select-garden-popper {
     max-height: calc(100vh - 200px);
     overflow-y: auto;
+
     &.el-dropdown__popper {
         .el-dropdown__list {
             max-width: 250px;
         }
-        .el-dropdown-menu__item{
+
+        .el-dropdown-menu__item {
             background-color: transparent !important;
             color: #606266 !important;
         }
+
         .selected-active-garden {
             color: #2199f8 !important;
             background-color: rgba(33, 153, 248, 0.1) !important;
             font-weight: 500;
         }
     }
+
     .dropdown-default-text {
         font-size: 11px;
         color: #2199f8;

+ 75 - 45
src/components/weatherInfo.vue

@@ -55,20 +55,16 @@
                             </div>
                         </div>
                     </div>
-
-                    <div class="button-wrap" v-if="hasFarm">
-                        <!-- <div class="button-item" @click="handleFarmInfo">
-                            <img class="button-pre" src="@/assets/img/common/info.png" alt="">
-                            {{ t("weather.farmInfoMaintain") }}
-                        </div> -->
-                        <!-- <div class="button-item">
-                            <img class="button-pre" src="@/assets/img/common/idea.png" alt="">
-                            种植档案管理
-                        </div> -->
-                    </div>
                 </div>
 
-                <slot v-if="!hasWeather" name="types-content"></slot>
+                <div v-if="!hasWeather" class="report-tabs">
+                    <div
+                        v-for="item in reportTabs"
+                        :key="item.key"
+                        class="report-tab-item"
+                        @click="emit('reportTabClick', item)"
+                    >{{ item.label }}</div>
+                </div>
             </div>
             <!-- <div class="weather-icon" v-else>
                 <img :src="`https://birdseye-img.sysuimars.com/weather/${currentWeather.iconDay}.svg`" alt="" />
@@ -81,6 +77,14 @@
             </div>
             <weather-chart class="weather-chart" :weather-data="weatherData"></weather-chart>
         </div>
+        <div
+            v-if="!hasWeather && activeGarden === 'current'"
+            class="report-maintain-btn"
+            @click="handleFarmInfoMaintain"
+        >
+            <img class="report-maintain-icon" src="@/assets/img/common/info.png" alt="" />
+            <span>{{ t("weather.farmInfoMaintain") }}</span>
+        </div>
         <!-- 农场筛选 -->
         <div class="farm-filter" v-show="activeGarden === 'list'">
             <div class="filter-l">
@@ -125,6 +129,14 @@ const props = defineProps({
         type: Boolean,
         default: true
     },
+    reportTabs: {
+        type: Array,
+        default: () => [
+            { key: "historyRisk", label: "历史风险报告" },
+            { key: "soilImprovement", label: "土壤改良" },
+            { key: "rotationAdvice", label: "轮作建议" },
+        ],
+    },
     from: {
         type: String,
         default: null
@@ -147,7 +159,7 @@ const typeOptions = ref([{
 }]);
 
 // 定义emit事件
-const emit = defineEmits(['weatherExpanded', 'changeGarden', 'changeGardenTab', 'closeTabMask']);
+const emit = defineEmits(['weatherExpanded', 'changeGarden', 'changeGardenTab', 'closeTabMask', 'reportTabClick', 'farmInfoMaintain']);
 const router = useRouter();
 
 const isExpanded = ref(false);
@@ -189,15 +201,16 @@ function setSelectedGarden(payload) {
     emit('changeGarden', payload);
 }
 
-const handleFarmInfo = () => {
-    router.push(`/farm_info?subjectId=${farmId.value}`);
-}
-
 const handleGardenClick = (type) => {
     activeGarden.value = type;
     emit("changeGardenTab", type);
 };
 
+const handleFarmInfoMaintain = () => {
+    if (!farmId.value) return;
+    emit('farmInfoMaintain', farmId.value);
+};
+
 const locationName = ref("");
 const weatherData = ref(null);
 const currentWeather = ref({ temp: "--", text: "--", iconDay: "" });
@@ -337,6 +350,31 @@ defineExpose({
 
     &.no-weather {
         height: auto;
+        position: relative;
+
+        .report-maintain-btn {
+            position: fixed;
+            right: -5px;
+            bottom: 10px;
+            z-index: 13;
+            display: flex;
+            align-items: center;
+            gap: 4px;
+            padding: 7px 10px;
+            background: #2199F8;
+            color: #fff;
+            border-radius: 20px 0 0 20px;
+            font-size: 11px;
+            line-height: 1;
+            white-space: nowrap;
+            box-sizing: border-box;
+
+            .report-maintain-icon {
+                width: 14px;
+                height: 14px;
+                filter: brightness(0) invert(1);
+            }
+        }
     }
 
     &.no-farm {
@@ -477,6 +515,26 @@ defineExpose({
                     }
                 }
             }
+
+            .report-tabs {
+                width: 100%;
+                display: flex;
+                align-items: center;
+                gap: 4px;
+                padding: 10px 9px 6px;
+                background: #fff;
+                box-sizing: border-box;
+
+                .report-tab-item {
+                    padding: 5px 7px;
+                    text-align: center;
+                    font-size: 12px;
+                    color: #777777;
+                    background: rgba(255, 255, 255, 0.1);
+                    border: 0.5px solid rgba(180, 180, 180, 0.4);
+                    border-radius: 2px;
+                }
+            }
         }
 
         .header-right {
@@ -542,34 +600,6 @@ defineExpose({
         }
     }
 
-    .button-wrap {
-        background: #fff;
-        display: flex;
-        justify-content: space-between;
-        gap: 10px;
-        color: #2199F8;
-        padding: 8px 10px;
-
-        .button-item {
-            border: 0.5px solid rgba(33, 153, 248, 0.5);
-            display: flex;
-            border-radius: 4px;
-            align-items: center;
-            height: 28px;
-            line-height: 28px;
-            box-sizing: border-box;
-            gap: 4px;
-            font-size: 12px;
-            flex: auto;
-            justify-content: center;
-        }
-
-        .button-pre {
-            width: 14px;
-            height: 14px;
-        }
-    }
-
     .farm-filter {
         display: flex;
         justify-content: space-between;

+ 431 - 0
src/views/old_mini/agri_file/index copy.vue

@@ -0,0 +1,431 @@
+<template>
+    <div class="agri-file" :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 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>
+                <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>
+            </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 { useStore } from "vuex";
+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 store = useStore();
+const route = useRoute();
+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 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 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;
+    }
+};
+
+watch(activeRecordSubTab, syncFarmRecordMap);
+watch(activeGardenTab, syncFarmRecordMap);
+
+const weatherExpanded = (isExpandedValue) => {
+    isExpanded.value = isExpandedValue;
+};
+
+const handleMaskClick = () => {
+    if (weatherInfoRef.value?.toggleExpand) {
+        weatherInfoRef.value.toggleExpand();
+    }
+};
+
+const changeGardenTab = (tab) => {
+    activeGardenTab.value = tab;
+};
+const changeType = (type) => {
+    activeType.value = type;
+};
+
+const handleGardenLoaded = ({ hasFarm }) => {
+    weatherInfoRef.value?.setGardenLoaded?.(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;
+};
+
+const mapContainer = ref(null);
+onActivated(async () => {
+    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();
+});
+</script>
+
+<style lang="scss" scoped>
+.agri-file {
+    width: 100%;
+    height: 100%;
+    background: #F5F7FB;
+    box-sizing: border-box;
+
+    .weather-mask {
+        position: fixed;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background-color: rgba(0, 0, 0, 0.52);
+        z-index: 11;
+    }
+
+    .weather-info {
+        width: calc(100% - 20px);
+        position: absolute;
+        z-index: 12;
+        left: 10px;
+        top: 12px;
+    }
+
+    .file-content {
+        position: relative;
+        height: 100%;
+        box-sizing: border-box;
+
+        $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%
+        );
+
+        @mixin map-legend-panel-base {
+            position: absolute;
+            z-index: 15;
+            background: $map-legend-panel-bg;
+            backdrop-filter: blur(4px);
+            box-sizing: border-box;
+        }
+
+        .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;
+            }
+
+            &__pill {
+                width: 16px;
+                height: 5px;
+                border-radius: 10px;
+
+                &--zone {
+                    background: #1c9e80;
+                }
+
+                &--growth {
+                    background: #ff953d;
+                }
+
+                &--pest {
+                    background: #e03131;
+                }
+            }
+
+            &__text {
+                font-size: $map-legend-text-size;
+                color: $map-legend-text-color;
+            }
+        }
+
+        .map-legend-box {
+            @include map-legend-panel-base;
+            top: 150px;
+            right: 10px;
+            width: 222px;
+            padding: 5px 10px;
+            border-radius: 5px;
+
+            &__labels {
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                margin-bottom: 8px;
+                font-size: $map-legend-text-size;
+                color: $map-legend-text-color;
+            }
+
+            &__bar {
+                height: 8px;
+                border-radius: 4px;
+                background: $map-legend-scale-gradient;
+            }
+        }
+
+        .map-tool-bar {
+            position: absolute;
+            left: 12px;
+            top: 50%;
+            transform: translateY(-78%);
+            z-index: 15;
+            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;
+            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;
+
+                    .map-tool-bar__icon {
+                        filter: grayscale(0);
+                    }
+                }
+            }
+
+            &__label {
+                font-size: 12px;
+            }
+        }
+
+        .map-container {
+            width: 100%;
+            height: 100%;
+        }
+    }
+
+    .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;
+
+            &.type-item-active {
+                background: #2199F8;
+                color: #fff;
+            }
+        }
+    }
+}
+</style>

+ 297 - 305
src/views/old_mini/agri_file/index.vue

@@ -1,89 +1,82 @@
 <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 v-show="activeGardenTab === 'current'" class="agri-file-header" :style="headerMotionStyle">
+            <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}`"
+            >
+                <div class="tracking-item__header">
+                    <div class="tracking-item__header-row">
+                        <div class="tracking-item__header-left">
+                            <span class="tracking-item__level">{{ item.level }}</span>
+                            <span class="tracking-item__title">{{ item.title }}</span>
+                        </div>
+                        <div class="tracking-item__tags">
+                            <span
+                                v-for="(tag, tagIndex) in item.tags"
+                                :key="tagIndex"
+                                class="tracking-item__tag"
+                            >{{ 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.reason }}</div>
+                            </div>
+                            <div class="tracking-item__info">
+                                <div class="tracking-item__issue">{{ item.issue }}</div>
+                            </div>
+                        </div>
+                        <div class="tracking-item__btn">{{ item.recordText }}</div>
+                    </div>
+                    <div class="tracking-item__history">
+                        <div class="tracking-item__history-text">{{ item.historyText }}</div>
+                        <div class="tracking-item__images">
+                            <img
+                                v-for="(image, imageIndex) in item.images.slice(0, 5)"
+                                :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 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 store = useStore();
 const route = useRoute();
+const router = useRouter();
 const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 
 const isExpanded = ref(false);
@@ -92,99 +85,80 @@ 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([
+    {
+        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 +171,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 +183,32 @@ 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;
 };
 
-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();
 });
 </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 +220,204 @@ 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: 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;
         }
 
-        .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);
+        }
 
-            &__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);
-                }
+        &__icon {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+            img {
+                width: 18px;
+                height: 16px;
+            }
+        }
 
-                &--active {
-                    color: #2199f8;
+        &__reason {
+            color: var(--tracking-primary);
+            font-weight: 500;
+        }
 
-                    .map-tool-bar__icon {
-                        filter: grayscale(0);
-                    }
-                }
-            }
+        &__issue {
+            margin-top: 3px;
+            color: var(--tracking-primary);
+            font-size: 12px;
+        }
 
-            &__label {
-                font-size: 12px;
-            }
+        &__btn {
+            padding: 5px 7px;
+            border-radius: 5px;
+            background: var(--tracking-primary);
+            color: #fff;
+            font-size: 12px;
         }
 
-        .map-container {
-            width: 100%;
-            height: 100%;
+        &__history {
+            margin-top: 10px;
+            padding: 6px 8px;
+            border-radius: 6px;
+            background: rgba(168, 168, 168, 0.1);
         }
-    }
 
-    .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;
+        &__history-text {
+            color: rgba(31, 31, 31, 0.5);
+            font-size: 12px;
+        }
 
-            &.type-item-active {
-                background: #2199F8;
-                color: #fff;
-            }
+        &__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>

+ 1 - 73
src/views/old_mini/growth_report/index.vue

@@ -10,22 +10,7 @@
         >
             <weather-info ref="weatherInfoRef" :hasWeather="false" from="growth_report" class="weather-info"
                 @weatherExpanded="weatherExpanded" @changeGarden="changeGarden" @changeGardenTab="changeGardenTab"
-                :isGarden="true" :gardenId="defaultGardenId">
-                <template #types-content>
-                    <div class="report-tabs">
-                        <div
-                            v-for="item in reportTabs"
-                            :key="item.key"
-                            class="report-tab-item"
-                            @click="handleReportTabClick(item)"
-                        >{{ item.label }}</div>
-                    </div>
-                </template>
-            </weather-info>
-            <div class="report-maintain-btn" @click="handleFarmInfoMaintain">
-                <img class="report-maintain-icon" src="@/assets/img/common/info.png" alt="" />
-                <span>农场信息维护</span>
-            </div>
+                @reportTabClick="handleReportTabClick" :isGarden="true" :gardenId="defaultGardenId" />
         </div>
         <!-- 农场列表 -->
         <div v-show="activeGardenTab === 'list'">
@@ -152,12 +137,6 @@ const mapLegendItems = [
     { key: "pest", label: "病虫害异常", pillClass: "map-legend__pill--pest" },
 ];
 
-const reportTabs = [
-    { key: "historyRisk", label: "历史风险报告" },
-    { key: "soilImprovement", label: "土壤改良" },
-    { key: "rotationAdvice", label: "轮作建议" },
-];
-
 const handleReportTabClick = (item) => {
     if (item.key === "historyRisk") {
         router.push(
@@ -169,13 +148,6 @@ const handleReportTabClick = (item) => {
     panelViewType.value = "plot";
 };
 
-const handleFarmInfoMaintain = () => {
-    if (!selectedGardenId.value) return;
-    console.log(selectedGardenId.value);
-    // router.push(`/farm_info?subjectId=${selectedGardenId.value}`);
-    // router.push(`/farm_info?subjectId=766`);
-};
-
 const weatherExpanded = (isExpandedValue) => {
     isExpanded.value = isExpandedValue;
 };
@@ -263,50 +235,6 @@ onActivated(async () => {
                 }
             }
         }
-
-        .report-tabs {
-            width: 100%;
-            display: flex;
-            align-items: center;
-            gap: 4px;
-            padding: 10px 9px 6px;
-            background: #fff;
-            box-sizing: border-box;
-
-            .report-tab-item {
-                padding: 5px 7px;
-                text-align: center;
-                font-size: 12px;
-                color: #777777;
-                background: rgba(255, 255, 255, 0.1);
-                border: 0.5px solid rgba(180, 180, 180, 0.4);
-                border-radius: 2px;
-            }
-        }
-
-        .report-maintain-btn {
-            position: absolute;
-            right: 0;
-            bottom: 10px;
-            z-index: 13;
-            display: flex;
-            align-items: center;
-            gap: 4px;
-            padding: 7px 10px;
-            background: #2199F8;
-            color: #fff;
-            border-radius: 20px;
-            font-size: 11px;
-            line-height: 1;
-            white-space: nowrap;
-            box-sizing: border-box;
-
-            .report-maintain-icon {
-                width: 14px;
-                height: 14px;
-                filter: brightness(0) invert(1);
-            }
-        }
     }
 
     .report-content {