Parcourir la source

feat:修改长势档案页面UI

wangsisi il y a 1 jour
Parent
commit
4be44ccd21

BIN
src/assets/img/report/area.png


BIN
src/assets/img/report/category.png


BIN
src/assets/img/report/harvest.png


BIN
src/assets/img/report/time.png


BIN
src/assets/img/report/weather-icon.png


+ 409 - 0
src/views/old_mini/growth_report/components/PlotDetailContent.vue

@@ -0,0 +1,409 @@
+<template>
+    <div class="plot-detail-content" :class="{ 'plot-detail-content--fullscreen': fullscreen }">
+        <div class="plot-detail-content__card plot-detail-content__card--plot">
+            <div class="plot-detail-content__header">
+                <div class="plot-detail-content__title">{{ detail.name || "地块名称" }}</div>
+                <el-icon
+                    v-if="!fullscreen"
+                    class="plot-detail-content__close"
+                    color="#5A5A68"
+                    size="20"
+                    @click="emit('close')"
+                >
+                    <Close />
+                </el-icon>
+            </div>
+
+            <div class="plot-detail-content__grid">
+                <div
+                    v-for="item in infoItems"
+                    :key="item.key"
+                    class="plot-detail-content__grid-item"
+                >
+                    <img class="plot-detail-content__grid-icon" :src="item.icon" alt="" />
+                    <span class="plot-detail-content__grid-label">{{ item.label }}</span>
+                    <span class="plot-detail-content__grid-value">{{ item.value }}</span>
+                </div>
+            </div>
+
+            <div class="plot-detail-content__status">
+                <p class="plot-detail-content__status-text">{{ detail.description }}</p>
+                <div v-if="detail.images?.length" class="plot-detail-content__gallery">
+                    <img
+                        v-for="(image, index) in detail.images"
+                        :key="index"
+                        class="plot-detail-content__gallery-item"
+                        :src="image"
+                        alt=""
+                    />
+                </div>
+            </div>
+        </div>
+
+        <div class="plot-detail-content__card plot-detail-content__card--remote">
+            <div class="plot-detail-content__section-title">
+                <span class="plot-detail-content__section-bar"></span>
+                <span class="plot-detail-content__section-text">时序遥感曲线</span>
+            </div>
+
+            <div class="plot-detail-content__legend">
+                <div
+                    v-for="item in legendItems"
+                    :key="item.key"
+                    class="plot-detail-content__legend-item"
+                >
+                    <span
+                        class="plot-detail-content__legend-line"
+                        :style="{ background: item.color }"
+                    ></span>
+                    <span class="plot-detail-content__legend-text">{{ item.label }}</span>
+                </div>
+            </div>
+
+            <div class="plot-detail-content__chart-placeholder">
+                <span class="plot-detail-content__chart-placeholder-text">图表区域</span>
+            </div>
+
+            <div
+                class="plot-detail-content__summary"
+                v-html="remoteSensingSummaryHtml"
+            ></div>
+        </div>
+
+        <div class="plot-detail-content__history">
+            <div class="plot-detail-content__history-tabs">
+                <div
+                    v-for="tab in historyTabs"
+                    :key="tab.key"
+                    class="plot-detail-content__history-tab"
+                    :class="{ 'plot-detail-content__history-tab--active': activeHistoryTab === tab.key }"
+                    @click="activeHistoryTab = tab.key"
+                >
+                    {{ tab.label }}
+                </div>
+            </div>
+
+            <div class="plot-detail-content__history-panel">
+                <div class="plot-detail-content__history-panel-header">
+                    {{ currentHistoryTab.summaryTitle }}11
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+
+const props = defineProps({
+    detail: {
+        type: Object,
+        default: () => ({}),
+    },
+    fullscreen: {
+        type: Boolean,
+        default: false,
+    },
+});
+
+const emit = defineEmits(["close"]);
+
+const legendItems = [
+    { key: "phenology", label: "物候曲线", color: "#2199f8" },
+    { key: "pest", label: "已发病虫害", color: "#e62e2d" },
+    { key: "growth", label: "已发生生长异常", color: "#ff9138" },
+    { key: "ndvi", label: "NVDI曲线", color: "#c6c6c6" },
+];
+
+const defaultRemoteSensingSummaryHtml =
+    '近一个月降水量较往年同期<span class="plot-detail-content__summary-highlight">增加**mm/减少**mm</span>,当前地块<span class="plot-detail-content__summary-highlight">长势良好/长势一般</span>,较往年同期绿度<span class="plot-detail-content__summary-highlight">减少**%</span>,湿度<span class="plot-detail-content__summary-highlight">减少**%</span>。根据当前长势和未来气象预测,如不进行农事干预,未来该地块长势将进一步<span class="plot-detail-content__summary-highlight">削弱**%</span>。';
+
+const remoteSensingSummaryHtml = computed(
+    () => props.detail.remoteSensingSummaryHtml || defaultRemoteSensingSummaryHtml
+);
+
+const historyTabs = [
+    { key: "pest", label: "过往病虫害", summaryTitle: "过往病虫害总结:" },
+    { key: "growth", label: "过往生长异常", summaryTitle: "过往生长异常总结:" },
+    { key: "weather", label: "过往气象风险", summaryTitle: "过往气象风险总结:" },
+];
+
+const activeHistoryTab = ref("pest");
+
+const currentHistoryTab = computed(
+    () => historyTabs.find((tab) => tab.key === activeHistoryTab.value) || historyTabs[0]
+);
+
+const defaultHistorySummaryMap = {
+    pest: "暂无过往病虫害总结数据",
+    growth: "暂无过往生长异常总结数据",
+    weather: "暂无过往气象风险总结数据",
+};
+
+const currentHistorySummary = computed(() => {
+    const customSummary = props.detail.historySummary?.[activeHistoryTab.value];
+    return customSummary || defaultHistorySummaryMap[activeHistoryTab.value];
+});
+
+const infoItems = computed(() => [
+    {
+        key: "area",
+        label: "种植面积",
+        value: props.detail.area || "--",
+        icon: require("@/assets/img/report/area.png"),
+    },
+    {
+        key: "category",
+        label: "种植类别",
+        value: props.detail.categories || "--",
+        icon: require("@/assets/img/report/category.png"),
+    },
+    {
+        key: "startTime",
+        label: "起始种植时间",
+        value: props.detail.startTime || "--",
+        icon: require("@/assets/img/report/time.png"),
+    },
+    {
+        key: "harvestTime",
+        label: "预测收获时间",
+        value: props.detail.harvestTime || "--",
+        icon: require("@/assets/img/report/harvest.png"),
+    },
+]);
+</script>
+
+<style lang="scss" scoped>
+.plot-detail-content {
+    padding: 4px 10px;
+    background: #fff;
+    box-sizing: border-box;
+
+    &--fullscreen {
+        min-height: 100%;
+        padding: 10px 10px 60px;
+        background: #f3f5f6;
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+        box-sizing: border-box;
+
+        .plot-detail-content__card {
+            background: #fff;
+            border-radius: 12px;
+            padding: 12px 10px;
+            box-sizing: border-box;
+        }
+
+        .plot-detail-content__title {
+            font-size: 18px;
+            font-weight: 600;
+        }
+
+        .plot-detail-content__grid {
+            gap: 10px 12px;
+            margin-bottom: 12px;
+        }
+
+        .plot-detail-content__grid-label {
+            color: #c6c6c6;
+        }
+
+        .plot-detail-content__grid-icon {
+            opacity: 0.65;
+        }
+    }
+
+    &__card {
+        box-sizing: border-box;
+    }
+
+    &__header {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 6px;
+    }
+
+    &__title {
+        font-size: 18px;
+        font-weight: 500;
+    }
+
+    &__close {
+        flex-shrink: 0;
+    }
+
+    &__grid {
+        display: grid;
+        grid-template-columns: repeat(2, minmax(0, 1fr));
+        gap: 6px;
+        margin-bottom: 10px;
+    }
+
+    &__grid-item {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+        font-size: 12px;
+        min-width: 0;
+    }
+
+    &__grid-icon {
+        width: 14px;
+        height: 14px;
+        flex-shrink: 0;
+    }
+
+    &__grid-label {
+        color: rgba(0, 0, 0, 0.4);
+        font-size: 12px;
+        margin-right: 2px;
+        white-space: nowrap;
+    }
+
+    &__grid-value {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+    }
+
+    &__status {
+        padding: 8px;
+        background: linear-gradient(180deg, rgba(33, 153, 248, 0.1) 0%, rgba(255, 255, 255, 0.1) 100%);
+        border-radius: 8px;
+    }
+
+    &__status-text {
+        margin: 0 0 8px;
+        font-size: 12px;
+        line-height: 1.6;
+        color: #2199f8;
+    }
+
+    &__gallery {
+        display: grid;
+        grid-template-columns: repeat(4, minmax(0, 1fr));
+        gap: 6px;
+    }
+
+    &__gallery-item {
+        width: 100%;
+        aspect-ratio: 1;
+        border-radius: 4px;
+        object-fit: cover;
+    }
+
+    &__section-title {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        margin-bottom: 10px;
+    }
+
+    &__section-bar {
+        width: 4px;
+        height: 16px;
+        border-radius: 2px;
+        background: #2199f8;
+        flex-shrink: 0;
+    }
+
+    &__section-text {
+        font-size: 16px;
+        font-weight: 600;
+        color: #1d2129;
+    }
+
+    &__legend {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 10px;
+    }
+
+    &__legend-item {
+        display: flex;
+        align-items: center;
+        gap: 5px;
+    }
+
+    &__legend-line {
+        width: 10px;
+        height: 3px;
+        border-radius: 5px;
+    }
+
+    &__legend-text {
+        font-size: 12px;
+        color: rgba(0, 0, 0, 0.5);
+    }
+
+    &__chart-placeholder {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 165px;
+        margin-bottom: 10px;
+        background: #f7f8fa;
+    }
+
+    &__chart-placeholder-text {
+        font-size: 13px;
+        color: #999;
+    }
+
+    &__summary {
+        padding: 8px 10px;
+        background: rgba(178, 178, 178, 0.1);
+        border-radius: 5px;
+        font-size: 12px;
+        color: rgba(0, 0, 0, 0.4);
+
+        :deep(.plot-detail-content__summary-highlight) {
+            color: rgba(0, 0, 0, 0.8);
+        }
+    }
+
+    &__history {
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+    }
+
+    &__history-tabs {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+    }
+
+    &__history-tab {
+        flex: 1;
+        padding: 4px;
+        text-align: center;
+        color: #9A9A9A;
+        background: #fff;
+        border: 0.5px solid transparent;
+        border-radius: 2px;
+
+        &--active {
+            color: #2199f8;
+            background: rgba(33, 153, 248, 0.15);
+            border-color: #2199f8;
+        }
+    }
+
+    &__history-panel {
+        background: #fff;
+        border-radius: 10px;
+        padding: 10px;
+    }
+
+    &__history-panel-header {
+        padding: 8px 10px;
+        font-size: 12px;
+        color: #2199f8;
+        background: rgba(33, 153, 248, 0.1);
+    }
+}
+</style>

+ 92 - 0
src/views/old_mini/growth_report/components/RiskReportContent.vue

@@ -0,0 +1,92 @@
+<template>
+    <div class="risk-report-content">
+        <div v-if="loading" class="panel-state">加载中...</div>
+        <div v-else-if="!riskList.length" class="panel-state">暂无气象风险数据</div>
+        <div
+            v-else
+            v-for="(item, index) in riskList"
+            :key="index"
+            class="risk-card"
+        >
+            <div class="risk-card__head">
+                <img class="risk-card__icon" src="@/assets/img/report/weather-icon.png" alt="" />
+                <span class="risk-card__label">{{ item.title || "气象风险" }}</span>
+            </div>
+            <div class="risk-card__content">
+                <img class="risk-card__thumb" :src="item.image || defaultThumb" alt="" />
+                <span class="risk-card__desc">{{ item.description }}</span>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+defineProps({
+    loading: {
+        type: Boolean,
+        default: false,
+    },
+    riskList: {
+        type: Array,
+        default: () => [],
+    },
+});
+
+const defaultThumb = require("@/assets/img/home/banner.png");
+</script>
+
+<style lang="scss" scoped>
+.risk-report-content {
+    .panel-state {
+        text-align: center;
+        color: #9a9a9a;
+        font-size: 13px;
+        padding: 20px 0;
+    }
+
+    .risk-card + .risk-card {
+        margin-top: 10px;
+    }
+
+    .risk-card {
+        &__head {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+            margin-bottom: 10px;
+        }
+
+        &__icon {
+            width: 18px;
+            height: 16px;
+        }
+
+        &__label {
+            font-weight: 500;
+            color: #474747;
+        }
+
+        &__content {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+        }
+
+        &__thumb {
+            width: 63px;
+            height: 63px;
+            border-radius: 4px;
+            object-fit: cover;
+        }
+
+        &__desc {
+            color: rgba(6, 6, 6, 0.5);
+            display: -webkit-box;
+            -webkit-box-orient: vertical;
+            -webkit-line-clamp: 3;
+            line-clamp: 3;
+            overflow: hidden;
+        }
+    }
+}
+</style>

+ 294 - 0
src/views/old_mini/growth_report/components/RiskReportPanel.vue

@@ -0,0 +1,294 @@
+<template>
+    <floating-panel
+        class="risk-report-panel"
+        :class="{
+            'risk-report-panel--dragging': isDragging,
+            'risk-report-panel--plot': viewType === 'plot',
+            'risk-report-panel--plot-fullscreen': isPlotFullscreen,
+        }"
+        :style="panelMotionStyle"
+        v-model:height="height"
+        :anchors="anchors"
+        :duration="0.36"
+        :content-draggable="true"
+        @height-change="handleHeightChange"
+    >
+        <template v-if="viewType === 'risk'">
+            <div class="panel-header-zone">
+                <div class="panel-header">
+                    <div class="panel-header__left">
+                        <span class="panel-header__bar"></span>
+                        <span class="panel-header__title">气象风险报告</span>
+                        <span class="panel-header__date">{{ reportDate }}</span>
+                    </div>
+                    <div class="panel-header__switch" @click="handleSwitchCategory">
+                        <span>切换品类</span>
+                        <icon name="arrow-down" class="panel-header__switch-icon" />
+                    </div>
+                </div>
+            </div>
+
+            <div class="panel-body">
+                <risk-report-content :loading="loading" :risk-list="riskList" />
+            </div>
+        </template>
+
+        <plot-detail-content
+            v-else
+            :detail="plotDetail"
+            :fullscreen="isPlotFullscreen"
+            @close="emit('closePlotDetail')"
+        />
+    </floating-panel>
+</template>
+
+<script setup>
+import { FloatingPanel, Icon } from "vant";
+import { computed, ref, watch } from "vue";
+import { useStore } from "vuex";
+import PlotDetailContent from "./PlotDetailContent.vue";
+import RiskReportContent from "./RiskReportContent.vue";
+
+const store = useStore();
+const tabBarHeight = computed(() => store.state.home.tabBarHeight);
+
+const props = defineProps({
+    viewType: {
+        type: String,
+        default: "risk",
+    },
+    plotDetail: {
+        type: Object,
+        default: () => ({}),
+    },
+});
+
+const emit = defineEmits(["switchCategory", "expandProgress", "closePlotDetail"]);
+
+const formatReportDate = (date = new Date()) => {
+    const year = date.getFullYear();
+    const month = String(date.getMonth() + 1).padStart(2, "0");
+    const day = String(date.getDate()).padStart(2, "0");
+    return `${year}.${month}.${day}`;
+};
+
+const reportDate = ref(formatReportDate());
+const loading = ref(false);
+const riskList = ref([
+    {
+        title: "气象风险",
+        description: "花期短暂,果期集中,成熟迅速花期短暂,花期短暂,果期集中,成熟迅速花期短暂",
+    },
+    {
+        title: "气象风险",
+        description: "花期短暂,果期集中,成熟迅速花期短暂,花期短暂,果期集中,成熟迅速花期短暂",
+    },
+]);
+
+const handleSwitchCategory = () => {
+    emit("switchCategory");
+};
+
+const anchors = computed(() => {
+    const viewportHeight = window.innerHeight - tabBarHeight.value;
+    return [
+        Math.round(viewportHeight * 0.4),
+        window.innerHeight,
+    ];
+});
+
+const height = ref(anchors.value[0]);
+const isDragging = ref(false);
+let dragEndTimer = null;
+
+const expandProgress = computed(() => {
+    const min = anchors.value[0];
+    const max = anchors.value[anchors.value.length - 1];
+    if (max <= min) return 0;
+    return Math.min(1, Math.max(0, (height.value - min) / (max - min)));
+});
+
+const isPlotFullscreen = computed(() => props.viewType === "plot" && expandProgress.value > 0.85);
+
+const panelMotionStyle = computed(() => {
+    const progress = expandProgress.value;
+    const margin = 10 * (1 - progress);
+    const radius = 16 * (1 - progress);
+    const headerScale = 1 + progress * 0.12;
+
+    return {
+        "--panel-radius": `${radius}px`,
+        "--panel-progress": progress,
+        left: `${margin}px`,
+        width: `calc(100% - ${margin * 2}px)`,
+        zIndex: progress > 0.82 ? 20 : 10,
+        paddingTop: progress > 0.9 ? "env(safe-area-inset-top, 0px)" : "0px",
+        "--header-bar-scale": headerScale,
+    };
+});
+
+const syncExpandProgress = () => {
+    emit("expandProgress", expandProgress.value);
+};
+
+watch(expandProgress, syncExpandProgress, { immediate: true });
+
+watch(height, () => {
+    isDragging.value = true;
+    if (dragEndTimer) {
+        clearTimeout(dragEndTimer);
+    }
+    dragEndTimer = setTimeout(() => {
+        isDragging.value = false;
+    }, 380);
+});
+
+watch(anchors, (nextAnchors) => {
+    if (height.value > nextAnchors[nextAnchors.length - 1]) {
+        height.value = nextAnchors[0];
+    }
+});
+
+watch(
+    () => props.viewType,
+    (type) => {
+        if (type === "plot") {
+            height.value = anchors.value[0];
+        }
+    }
+);
+
+const handleHeightChange = () => {
+    syncExpandProgress();
+};
+</script>
+
+<style lang="scss" scoped>
+.risk-report-panel {
+    z-index: 10;
+    will-change: left, width;
+
+    &.risk-report-panel--dragging {
+        ::v-deep .van-floating-panel__header-bar {
+            transform: scaleX(calc(var(--header-bar-scale, 1)));
+        }
+    }
+
+    &.risk-report-panel--plot-fullscreen {
+        ::v-deep {
+            .van-floating-panel__header {
+                background: #f5f7fb;
+            }
+
+            .van-floating-panel__content {
+                background: #f5f7fb;
+            }
+        }
+    }
+
+    &.risk-report-panel--plot {
+        ::v-deep {
+            .van-floating-panel__header {
+                background: #fff;
+            }
+
+            .van-floating-panel__header-bar {
+                background: #c6c6c6;
+            }
+
+            .van-floating-panel__content {
+                background: #fff;
+            }
+        }
+    }
+
+    ::v-deep {
+        .van-floating-panel__header {
+            height: 18px;
+            background: linear-gradient(180deg, #acd5fb 0%, #bcddfc 100%);
+            border-radius: var(--panel-radius, 16px) var(--panel-radius, 16px) 0 0;
+        }
+
+        .van-floating-panel__header-bar {
+            width: 36px;
+            height: 4px;
+            background: rgba(255, 255, 255, 0.95);
+            border-radius: 4px;
+            transform: scaleX(var(--header-bar-scale, 1));
+            transition: transform 0.2s ease;
+        }
+
+        .van-floating-panel__content {
+            overflow-y: auto;
+            padding-bottom: env(safe-area-inset-bottom);
+            background: linear-gradient(180deg, #d2ebff 38%, #e3f2fd 68%, #ffffff 100%);
+        }
+    }
+}
+
+.panel-header-zone {
+    padding-bottom: 4px;
+    background: linear-gradient(180deg, #bcddfc 0%, #d9ebfd 100%);
+}
+
+.panel-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 8px;
+    padding: 2px 12px 8px;
+    box-sizing: border-box;
+
+    &__left {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+    }
+
+    &__bar {
+        width: 4px;
+        height: 16px;
+        border-radius: 2px;
+        background: #2199f8;
+    }
+
+    &__title {
+        font-size: 16px;
+        font-weight: 500;
+    }
+
+    &__date {
+        padding: 2px 6px;
+        font-size: 12px;
+        color: #313131;
+        background: rgba(255, 255, 255, 0.5);
+        border-radius: 2px;
+    }
+
+    &__switch {
+        display: flex;
+        align-items: center;
+        gap: 2px;
+        padding: 5px 10px;
+        font-size: 12px;
+        color: #2199f8;
+        background: #fff;
+        border: 1px solid rgba(33, 153, 248, 0.65);
+        border-radius: 6px;
+        white-space: nowrap;
+        flex-shrink: 0;
+    }
+
+    &__switch-icon {
+        font-size: 12px;
+        color: #2199f8;
+    }
+}
+
+.panel-body {
+    margin: 0 10px;
+    background: #fff;
+    border-radius: 8px;
+    padding: 10px;
+}
+</style>

+ 97 - 0
src/views/old_mini/growth_report/growthReportMap.js

@@ -0,0 +1,97 @@
+import * as KMap from "@/utils/ol-map/KMap";
+import * as util from "@/common/ol_common.js";
+import config from "@/api/config.js";
+import Style from "ol/style/Style";
+import Icon from "ol/style/Icon";
+import { Point } from 'ol/geom';
+import Feature from "ol/Feature";
+import { reactive } from "vue";
+
+export let mapLocation = reactive({
+  data: null,
+});
+
+/**
+ * @description 地图层对象
+ */
+class IndexMap {
+  constructor() {
+    let that = this;
+    let vectorStyle = new KMap.VectorStyle();
+    this.vectorStyle = vectorStyle;
+
+    // 位置图标
+    this.clickPointLayer = new KMap.VectorLayer("clickPointLayer", 9999, {
+      style: (f) => {
+        return new Style({
+          image: new Icon({
+            src: require("@/assets/img/map/map_point.png"),
+            scale: 0.5,
+          }),
+        });
+      },
+    });
+  }
+
+  initMap(location, target) {
+    let level = 16;
+    let coordinate = util.wktCastGeom(location).getFirstCoordinate();
+    this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 8, 22);
+    let xyz2 = config.base_img_url3 + "map/lby/{z}/{x}/{y}.png";
+    this.kmap.addXYZLayer(xyz2, { minZoom: 8, maxZoom: 22 }, 2);
+    this.kmap.addLayer(this.clickPointLayer.layer);
+    this.setMapPoint(coordinate)
+    this.addMapSingerClick()
+  }
+
+  setMapPosition(center) {
+    this.kmap.getView().animate({
+      center,
+      zoom: 16,
+      duration: 0,
+    });
+    this.setMapPoint(center)
+  }
+
+  setMapPoint(coordinate) {
+    this.clickPointLayer.source.clear()
+    let point = new Feature(new Point(coordinate))
+    this.clickPointLayer.addFeature(point)
+  }
+
+  // 地图点击事件
+  addMapSingerClick() {
+    let that = this;
+    that.kmap.on("singleclick", (evt) => {
+      // that.kmap.map.forEachFeatureAtPixel(evt.pixel, function (feature, layer) {
+      //   if ( layer instanceof VectorLayer && layer.get("name") === "reportPolygonLayer" ) {
+      //     areaId.data = feature.get("id")
+      //   }
+      // });
+      that.setMapPoint(evt.coordinate)
+      mapLocation.data = evt.coordinate
+    });
+  }
+
+  clearLayer() {
+    // this.kmap.removeLayer(this.clickPointLayer.layer)
+    this.kmap.polygonLayer.source.clear();
+  }
+
+  setAreaGeometry(geometryArr) {
+    this.clearLayer()
+    let that = this
+    geometryArr.map(item => {
+      that.kmap.setLayerWkt(item)
+    })
+    this.fitView()
+  }
+
+  fitView(){
+    let extent = this.kmap.polygonLayer.source.getExtent()
+    // 地图自适应到区域可视范围
+    this.kmap.getView().fit(extent, { duration: 500, padding: [100, 100, 100, 100] });
+  }
+}
+
+export default IndexMap;

+ 1608 - 0
src/views/old_mini/growth_report/index copy.vue

@@ -0,0 +1,1608 @@
+<template>
+    <div class="achievement-report-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
+        <!-- 天气遮罩 -->
+        <div class="weather-mask" v-show="isExpanded" @click="handleMaskClick"></div>
+        <!-- 组件:天气 -->
+        <div class="weather-info-wrap">
+            <weather-info ref="weatherInfoRef" from="growth_report" class="weather-info" :showTabMask="showTabMask"
+                @weatherExpanded="weatherExpanded" @changeGarden="changeGarden" @changeGardenTab="changeGardenTab"
+                @closeTabMask="closeTabMask">
+            </weather-info>
+
+            <!-- 邀请关注 -->
+            <!-- <div class="invite-follow" v-if="currentFarmName && activeGardenTab === 'current'">
+                <div class="invite-content">
+                    <icon name="share" />
+                    <span>{{ t('邀请关注') }}</span>
+                </div>
+            </div> -->
+        </div>
+
+        <!-- 农场列表 -->
+        <div v-show="activeGardenTab === 'list'">
+            <garden-list ref="gardenListRef" :garden-id="selectedGardenId" @loaded="handleGardenLoaded"
+                @selectGarden="handleGardenSelected" />
+        </div>
+
+        <div class="report-content-wrap" v-if="hasReport && activeGardenTab === 'current'" v-loading="loading"
+            element-loading-background="rgba(0, 0, 0, 0.1)">
+            <div class="history-risk-report-btn" @click="handleHistoryRiskReportClick">
+                <span class="risk-report-icon">
+                    <i></i>
+                </span>
+                <span class="risk-report-text">{{ t('历史风险报告') }}</span>
+            </div>
+
+            <div class="report-content has-report" :style="{ minHeight: `calc(100vh - ${tabBarHeight}px)` }">
+                <!-- <img src="@/assets/img/home/qrcode.png" alt="" class="code-icon" /> -->
+                <img class="header-img" src="@/assets/img/home/report.png" alt="" />
+
+                <div class="report-header">
+                    <!-- <div class="type-tabs report-tabs">
+                        <div class="type-item" :class="{ 'type-item-active': activeReportIndex === 0 }">{{ t('作物长势') }}</div>
+                        <div class="type-item" :class="{ 'type-item-active': activeReportIndex === 1 }">{{ t('历史风险') }}</div>
+                        <div class="type-item" :class="{ 'type-item-active': activeReportIndex === 2 }">{{ t('土壤改良') }}</div>
+                        <div class="type-item" :class="{ 'type-item-active': activeReportIndex === 3 }">{{ t('种植建议') }}</div>
+                    </div> -->
+                    <div class="type-tabs" v-if="subjectData.length > 1">
+                        <div
+                            @click="handleTypeTabClick(item, index)"
+                            class="type-item"
+                            v-for="(item, index) in visibleSubjectData"
+                            :class="{ 'type-item-active': activeSubjectIndex === index }"
+                            :key="index">{{ item.speciesName }}</div>
+                    <div
+                        v-if="showSubjectToggle"
+                        class="subject-toggle"
+                        @click="typeTabsExpanded = !typeTabsExpanded"
+                    >
+                        {{ typeTabsExpanded ? t("common.collapse") : t("common.expandMore") }}
+                    </div>
+                    </div>
+
+                    <div class="time-tag">{{ workItems?.[0]?.reportDate || new Date().toISOString().split('T')[0] }}</div>
+                    <div
+                        class="report-title"
+                        :class="{ 'report-title-toggle': localeToggleEnabled }"
+                        @click="onReportTitleClick"
+                    >{{ varietyName }}{{ t("growthReport.title") }}</div>
+                    <div class="report-info">
+                        <div class="info-item">
+                            <img class="info-icon" src="@/assets/img/home/farm.png" alt="" />
+                            <span class="info-text">{{ currentFarmName }}</span>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="report-box">
+                    <div class="box-title">{{ t("growthReport.weatherRisk") }}</div>
+                    <div class="box-text">
+                        <div class="box-bg">
+                            <!-- <div class="types-info">
+                                当前 <span class="text-bold">{{ t('水稻') }}</span>{{ t('处于为  分蘖初期') }}<span class="text-link" @click="handleAdjustPopup">{{ t('(校准物候期)') }}</span>
+                            </div> -->
+                            <div class="types-info">
+                                {{ t("common.current") }}
+                                <span class="text-bold">{{ varietyName }}</span>
+                                {{ t("growthReport.inStage", { stage: currentPhenologyStage }) }}
+                            </div>
+                            <div class="tp-img" v-if="currentFarmVariety === 1">
+                                <img src="@/assets/img/common/tp-1.png" alt="">
+                                <img src="@/assets/img/common/tp-2.png" alt="">
+                                <img src="@/assets/img/common/tp-3.png" alt="">
+                                <img src="@/assets/img/common/tp-4.png" alt="">
+                                <img src="@/assets/img/common/tp-5.png" alt="">
+                                <img src="@/assets/img/common/tp-6.png" alt="">
+                            </div>
+                            <div class="tp-img" v-else>
+                                <img src="@/assets/img/common/sd-1.jpg" alt="">
+                                <img src="@/assets/img/common/sd-2.jpg" alt="">
+                                <img src="@/assets/img/common/sd-3.jpg" alt="">
+                                <img src="@/assets/img/common/sd-4.jpg" alt="">
+                                <img src="@/assets/img/common/sd-5.jpg" alt="">
+                                <img src="@/assets/img/common/sd-6.jpg" alt="">
+                            </div>
+                        </div>
+                        <div class="warning-part">
+                            <div class="warning-title">
+                                <div class="title-l">
+                                    <div class="title-line"></div>
+                                    <div class="title-block"></div>
+                                </div>
+                                <div>{{ t("growthReport.futureWeatherRisk") }}</div>
+                                <div class="title-l">
+                                    <div class="title-block"></div>
+                                    <div class="title-line title-line-right"></div>
+                                </div>
+                            </div>
+
+                            <div class="report-part" v-for="(part, partI) in riskList" :key="partI">
+                                <div class="part-title">{{ part.title }}</div>
+                                <div class="part-text" v-html="boldKeywordsInText(part.description, part.boldKeywords)"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="report-box">
+                    <div class="box-title">{{ t("growthReport.farmAdvice") }}</div>
+                    <div class="box-text">
+                        <div class="warning-part" v-for="(part, partI) in adviceList" :key="partI">
+                            <div class="report-part">
+                                <div class="part-top">
+                                    <div class="part-title">{{ part.title }}</div>
+                                    <!-- <div class="part-link">
+                                        <el-icon class="part-link-icon"><Link /></el-icon>
+                                        <div class="text-link">{{ t('查看农事') }}</div>
+                                    </div> -->
+                                </div>
+                                <div class="part-text" v-html="boldKeywordsInText(part.description, part.boldKeywords)"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="report-box">
+                    <div class="box-title">{{ t("growthReport.patrolFocus") }}</div>
+                    <div class="box-text">
+                        <div class="warning-part" v-for="(part, partI) in patrolList" :key="partI">
+                            <div class="report-part">
+                                <div class="part-top">
+                                    <div class="part-title">{{ part.title }}</div>
+                                    <!-- <div class="part-link">
+                                        <el-icon class="part-link-icon"><Link /></el-icon>
+                                        <div class="text-link">{{ t('查看互动') }}</div>
+                                    </div> -->
+                                </div>
+                                <div class="part-text" v-html="boldKeywordsInText(part.description, part.boldKeywords)"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="report-box" v-for="(work, workI) in workItems" :key="workI">
+                    <div class="box-title">{{ work?.title }}</div>
+                    <div class="box-text">
+                        <div class="box-bg" v-show="work?.backgroundDesc">
+                            <span class="box-subtitle">{{ t("common.backgroundDesc") }}</span>
+                            <div class="pre-text">{{ work?.backgroundDesc }}</div>
+                        </div>
+                        <div class="box-advice" v-show="work?.suggestion">
+                            <span class="box-subtitle">{{ t("common.suggestion") }}</span>
+                            <div class="pre-text">{{ work?.suggestion }}</div>
+                        </div>
+                        <div class="box-sum pre-text" v-show="work?.summary">{{ work?.summary }}</div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- <swipe ref="swipeRef" class="my-swipe" :loop="false" indicator-color="white" @change="handleSwipeChange">
+                <swipe-item v-for="(item, index) in regionsData" :key="index">
+                    
+                </swipe-item>
+            </swipe> -->
+        </div>
+
+        <div v-else-if="activeGardenTab === 'current'" class="fake-report-wrap report-content-wrap">
+            <div class="report-content">
+
+                <img class="header-img" src="@/assets/img/home/report.png" alt="" />
+                <div class="report-header" :class="{ 'no-farm': !currentFarmName }">
+                    <!-- <img class="header-book" src="@/assets/img/home/book.png" alt="" /> -->
+                    <div class="time-tag">{{ new Date().toISOString().split('T')[0] }}</div>
+                    <div
+                        class="report-title"
+                        :class="{ 'report-title-toggle': localeToggleEnabled }"
+                        @click="onReportTitleClick"
+                    >{{ t("growthReport.cropTitle") }}</div>
+                    <div class="report-info pb-4">
+                        <div class="info-item">
+                            <img class="info-icon" src="@/assets/img/home/farm.png" alt="" />
+                            <span class="info-text">{{ t("common.demoFarm") }}</span>
+                        </div>
+                    </div>
+                </div>
+                <div class="fake-img">
+                    <img src="@/assets/img/home/fake.png" alt="" class="fake-img-item" />
+                </div>
+
+                <div class="lock-img">
+                    <img @click="handleLockClick" src="@/assets/img/home/lock-blue.png" alt=""
+                        class="has-click lock-img-item" />
+                    <div class="lock-text">
+                        {{ t("growthReport.lockTitle") }}
+                        <div>{{ t("growthReport.lockSub") }}</div>
+                    </div>
+
+                    <div @click="handleLockClick" class="lock-btn has-click">{{ t("common.unlock") }}</div>
+                </div>
+
+                <div class="lock-bg"></div>
+            </div>
+        </div>
+
+        <!-- 农场列表引导 -->
+        <div class="mask-wrap" @click="closeTabMask" v-if="showTabMask"></div>
+
+        <tip-popup v-model:show="showBindSuccess" type="success" :text="t('growthReport.bindSuccess')" hideBtn />
+
+        <start-interact-popup ref="startInteractPopupRef" />
+
+        <agri-execute-popup ref="agriExecutePopupRef" />
+
+        <!-- 校准物候期 -->
+        <adjust-popup ref="adjustPopupRef" />
+    </div>
+</template>
+
+<script setup>
+import wx from "weixin-js-sdk";
+import weatherInfo from "@/components/weatherInfo.vue";
+import { ref, onActivated, onDeactivated, onUnmounted, computed, nextTick } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { useStore } from "vuex";
+import { Swipe, SwipeItem, Badge, Icon } from 'vant';
+import tipPopup from "@/components/popup/tipPopup.vue";
+import startInteractPopup from "@/components/popup/startInteractPopup.vue";
+import agriExecutePopup from "@/components/popup/agriExecutePopup.vue";
+import gardenList from "@/components/gardenList.vue";
+import adjustPopup from "./adjustPopup.vue";
+import { useI18n } from "@/i18n";
+import { boldKeywordsInText } from "@/utils/boldKeywords";
+
+const { t, toggleLocale: dispatchToggleLocale } = useI18n();
+const store = useStore();
+
+/** 暂时隐藏标题中英切换,恢复时改为 true */
+const localeToggleEnabled = false;
+const tabBarHeight = computed(() => store.state.home.tabBarHeight);
+
+const route = useRoute();
+const router = useRouter();
+const loading = ref(false);
+const hasReport = ref(true);
+const workItems = ref([]);
+const swipeRef = ref(null);
+
+//
+const riskList = computed(() => [
+    {
+        title:
+            currentFarmVariety.value == 1
+                ? t("growthReport.risk.pest.title")
+                : t("growthReport.risk.pest.titleRice"),
+        description:
+            currentFarmVariety.value == 1
+                ? t("growthReport.risk.pest.desc")
+                : t("growthReport.risk.pest.descRice"),
+        boldKeywords:
+            currentFarmVariety.value == 1
+                ? ["高温干旱", "中等水平", "果皮灼伤"]
+                : ["中等水平", "做好排水工作"],
+    },
+    // {
+    //     title: t("growthReport.risk.rain.title"),
+    //     description: t("growthReport.risk.rain.desc"),
+    // },
+]);
+
+const adviceList = computed(() => [
+    {
+        title:
+            currentFarmVariety.value == 1
+                ? t("growthReport.advice.foliar.title")
+                : t("growthReport.advice.foliar.titleRice"),
+        description:
+            currentFarmVariety.value == 1
+                ? t("growthReport.advice.foliar.desc")
+                : t("growthReport.advice.foliar.descRice"),
+        boldKeywords:
+            currentFarmVariety.value == 1 ? ["需及时喷撒清水"] : [],
+    },
+    // {
+    //     title: t("growthReport.advice.pestControl.title"),
+    //     description: t("growthReport.advice.pestControl.desc"),
+    // },
+]);
+
+const patrolList = computed(() => {
+    const isLychee = currentFarmVariety.value == 1;
+    return [
+        {
+            title: t("growthReport.patrol.process.title"),
+            description: isLychee
+                ? t("growthReport.patrol.process.desc")
+                : t("growthReport.patrol.process.descRice"),
+            boldKeywords: isLychee ? ["5%", "果实转色期"] : ["60%", "拔节期"],
+        },
+        {
+            title: t("growthReport.patrol.growth.title"),
+            description: isLychee
+                ? t("growthReport.patrol.growth.desc")
+                : t("growthReport.patrol.growth.descRice"),
+            boldKeywords: isLychee ? ["10%", "抽生新梢"] : ["10%", "干旱缺素"],
+        },
+    ];
+    // {
+    //     title: t("growthReport.patrol.pest.title"),
+    //     description: t("growthReport.patrol.pest.desc"),
+    // },
+});
+//
+const paramsPage = ref({});
+const showBindSuccess = ref(false);
+const startInteractPopupRef = ref(null);
+const agriExecutePopupRef = ref(null);
+const adjustPopupRef = ref(null);
+
+const handleAdjustPopup = () => {
+    adjustPopupRef.value.open();
+}
+// 天气组件相关
+const isExpanded = ref(false);
+const weatherInfoRef = ref(null);
+
+const showTabMask = ref(false);
+const TAB_GUIDE_SHOWN_KEY = "GROWTH_REPORT_TAB_GUIDE_SHOWN";
+const weatherExpanded = (isExpandedValue) => {
+    isExpanded.value = isExpandedValue;
+};
+
+// 点击遮罩时收起天气
+const handleMaskClick = () => {
+    if (weatherInfoRef.value && weatherInfoRef.value.toggleExpand) {
+        weatherInfoRef.value.toggleExpand();
+    }
+};
+
+const currentFarmName = ref('');
+const selectedGardenId = ref(null);
+const gardenListRef = ref(null);
+const activeGardenTab = ref('current');
+const changeGardenTab = (tab) => {
+    activeGardenTab.value = tab;
+}
+
+const handleGardenLoaded = ({ hasFarm }) => {
+    weatherInfoRef.value?.setGardenLoaded?.(hasFarm);
+};
+
+const handleGardenSelected = (garden) => {
+    selectedGardenId.value = garden?.id ?? null;
+    weatherInfoRef.value?.setSelectedGarden?.(garden);
+};
+
+const currentFarmVariety = ref(null);
+const varietyName = ref(null);
+const currentPhenologyStage = computed(() =>
+    currentFarmVariety.value == 1
+        ? t("growthReport.phenologyFruitExpansion")
+        : t("growthReport.phenologyLateTillering")
+);
+// 切换农场时,更新报告数据
+const changeGarden = async ({ id, name,farm_variety,variety_name }) => {
+    currentFarmVariety.value = farm_variety;
+    varietyName.value = variety_name;
+    if (!id) return;
+    currentFarmName.value = name;
+    if (sessionStorage.getItem('activeSwipeIndex')) {
+        currentIndex.value = Number(sessionStorage.getItem('activeSwipeIndex'));
+    } else {
+        currentIndex.value = 0;
+        swipeRef.value && swipeRef.value.swipeTo(0, { immediate: true });
+    }
+    paramsPage.value = {
+        ...(paramsPage.value || {}),
+        subjectId: id,
+    };
+    // 初始化品种/大物候期转换
+    startInteractPopupRef.value.getPhenologyInitOrConfirmStatus();
+    await getSubjectData(id);
+    hasReport.value = true;
+    // await getRegions();
+};
+
+onActivated(() => {
+    if (!localeToggleEnabled) {
+        store.dispatch("locale/setLocale", "zh");
+    }
+    window.scrollTo(0, 0);
+    // 从新增农场页返回时,优先用缓存中的最新选中农场
+    const savedFarmId = localStorage.getItem("selectedFarmId");
+    selectedGardenId.value = savedFarmId ? Number(savedFarmId) : null;
+    gardenListRef.value?.refreshFarmList?.();
+
+    // 如果路由中带有 miniJson,并且其中有 showBind,则展示绑定成功弹窗
+    const { miniJson } = route.query || {};
+    if (miniJson) {
+        try {
+            const parsed = typeof miniJson === "string" ? JSON.parse(miniJson) : miniJson;
+            if (parsed && parsed.showBind) {
+                showBindSuccess.value = true;
+                // 处理完后清空路由中的 miniJson 参数,避免重复弹出
+                const newQuery = { ...(route.query || {}) };
+                delete newQuery.miniJson;
+                router.replace({ path: route.path, query: newQuery });
+            }
+        } catch (e) {
+            // miniJson 解析失败时忽略,不影响正常流程
+        }
+    }
+    // getResultReport();
+});
+
+const closeTabMask = () => {
+    showTabMask.value = false;
+};
+
+const userInfo = localStorage.getItem("localUserInfo");
+const userInfoObj = userInfo ? JSON.parse(userInfo) : {};
+
+const handleLockClick = () => {
+    if (currentFarmName.value) {
+        // router.push("/interaction?subjectId=" + localStorage.getItem("selectedFarmId"));
+        router.push(`/create_farm?from=growth_report&isReload=true`);
+        return;
+    }
+    if (userInfoObj?.tel) {
+        router.push(`/create_farm?from=growth_report&isReload=true`);
+        return;
+    }
+    wx.miniProgram.navigateTo({
+        url: '/pages/subPages/phone_auth/index',
+    });
+}
+
+const handleAddFarm = () => {
+    router.push(`/create_farm?from=growth_report&isReload=true`);
+}
+
+const handleHistoryRiskReportClick = () => {
+    router.push("/history_risk_report?farmVariety=" + currentFarmVariety.value + "&currentFarmName=" + currentFarmName.value);
+}
+
+const todayPatrolFocus = ref([]);
+const pendingFarmWork = ref([]);
+const handlePendingFarmWorkClick = (card) => {
+    router.push({
+        path: "/work_detail",
+        query: {
+            miniJson: JSON.stringify({
+                paramsPage: JSON.stringify({
+                    farmId: paramsPage.value.farmId,
+                    farmWorkLibId: card?.farmWorkLibId,
+                    recordId: card?.recordId,
+                    typeId: regionsData.value[currentIndex.value].typeId
+                }),
+            }),
+        },
+    });
+}
+// 点击今日巡园重点
+const handleTodayPatrolFocusClick = (card) => {
+    if (!card.interactionTypeId) return;
+    router.push(`/interaction_list?farmId=${paramsPage.value.farmId}&regionId=${paramsPage.value.regionId}&interactionTypeId=${card.interactionTypeId}`);
+}
+
+const getTodayPatrolFocus = () => {
+    VE_API.report.todayPatrolFocus({ farmId: paramsPage.value.farmId }).then(({ data }) => {
+        todayPatrolFocus.value = data || [];
+    });
+}
+
+const getPendingFarmWork = () => {
+    VE_API.report.pendingFarmWork({ farmId: paramsPage.value.farmId, regionId: paramsPage.value.regionId }).then(({ data }) => {
+        pendingFarmWork.value = data || [];
+    });
+}
+
+const currentIndex = ref(0);
+const handleSwipeChange = (index) => {
+    currentIndex.value = index;
+    if (paramsPage.value.regionId !== regionsData.value[index].regionId) {
+        paramsPage.value = {
+            ...(paramsPage.value || {}),
+            farmId: regionsData.value[index].farmId,
+            regionId: regionsData.value[index].regionId,
+        };
+        getTodayPatrolFocus();
+        getPendingFarmWork();
+        getDetail();
+    }
+}
+
+const getDetail = () => {
+    if (!paramsPage.value.farmId) return;
+    loading.value = true;
+    VE_API.report
+        .reproductiveReport({ farmId: paramsPage.value.farmId, regionId: paramsPage.value.regionId })
+        .then(({ data }) => {
+            workItems.value = data || [];
+        })
+        .finally(() => {
+            loading.value = false;
+        });
+};
+
+const subjectData = ref([])
+const typeTabsExpanded = ref(false);
+const visibleSubjectData = computed(() => {
+    if (typeTabsExpanded.value) {
+        return subjectData.value;
+    }
+    return subjectData.value.slice(0, 4);
+});
+const showSubjectToggle = computed(() => subjectData.value.length > 4);
+
+const getSubjectData = async (id) => {
+    const res = await VE_API.monitor.listFarmsBySubjectId({ subjectId: id });
+    // subjectData.value = res.data || [];
+    subjectData.value = [...res.data, ...res.data, ...res.data, ...res.data];
+    typeTabsExpanded.value = false;
+}
+
+const activeReportIndex = ref(0);
+const activeSubjectIndex = ref(0);
+const handleTypeTabClick = (item, index) => {
+    activeSubjectIndex.value = index;
+    paramsPage.value = {
+            ...(paramsPage.value || {}),
+            farmId: item.farmId,
+    };
+    getTodayPatrolFocus();
+}
+
+const regionsData = ref([]);
+const getRegions = async () => {
+    VE_API.monitor.listRegionsBySubjectId({
+        subjectId: paramsPage.value.subjectId,
+    }).then(({ data }) => {
+        console.log(data);
+        regionsData.value = data || [];
+        if (regionsData.value.length > 0) {
+            hasReport.value = true;
+            const hasShownTabGuide = localStorage.getItem(TAB_GUIDE_SHOWN_KEY) === "1";
+
+            // 首次进入且有分区数据:显示农场列表引导
+            if (!hasShownTabGuide) {
+                showTabMask.value = true;
+                localStorage.setItem(TAB_GUIDE_SHOWN_KEY, "1");
+            } else {
+                showTabMask.value = false;
+            }
+
+            // 切换农场tab回到当前农场tab
+            weatherInfoRef.value && weatherInfoRef.value.handleGardenClick('current');
+
+            // 如果不是点击农情报告已生成弹窗过来的,则显示农情互动弹窗
+            if (!route.query.hideInteraction) {
+                agriExecutePopupRef.value.showPopup(regionsData.value[currentIndex.value].farmId);
+            }
+
+            paramsPage.value = {
+                ...(paramsPage.value || {}),
+                farmId: regionsData.value[currentIndex.value].farmId,
+                regionId: regionsData.value[currentIndex.value].regionId,
+            };
+            getTodayPatrolFocus();
+            getPendingFarmWork();
+            getDetail();
+            // 如果是新增品种后跳转过来的,等待 Swipe 实例挂载后再定位
+            if (route.query.addVarietyCount) {
+                const targetIndex = Number(route.query.addVarietyCount);
+                if (!Number.isNaN(targetIndex) && targetIndex >= 0) {
+                    const safeIndex = Math.min(targetIndex, regionsData.value.length - 1);
+                    const reverseIndex = Math.min(
+                        regionsData.value.length - 1,
+                        Math.max(0, regionsData.value.length - safeIndex)
+                    );
+                    nextTick(() => {
+                        swipeRef.value?.swipeTo?.(reverseIndex, { immediate: true });
+                    });
+                }
+            }
+
+            if (sessionStorage.getItem('activeSwipeIndex')) {
+                nextTick(() => {
+                    swipeRef.value?.swipeTo?.(currentIndex.value, { immediate: true });
+                });
+                sessionStorage.removeItem('activeSwipeIndex');
+            }
+        } else {
+            // 切换农场tab回到当前农场tab
+            // weatherInfoRef.value && weatherInfoRef.value.handleGardenClick('current');
+            showTabMask.value = false;
+            hasReport.value = false;
+        }
+    });
+}
+
+/** 切换语言后,用新 lang 参数重新请求页面数据 */
+const reloadPageDataAfterLocaleChange = () => {
+    gardenListRef.value?.refreshFarmList?.();
+
+    const subjectId = paramsPage.value.subjectId || localStorage.getItem("selectedFarmId");
+    if (subjectId) {
+        getSubjectData(subjectId);
+    }
+
+    if (paramsPage.value.farmId) {
+        getTodayPatrolFocus();
+        getPendingFarmWork();
+        getDetail();
+    }
+
+    startInteractPopupRef.value?.getPhenologyInitOrConfirmStatus?.();
+};
+
+const toggleLocale = async () => {
+    await dispatchToggleLocale();
+    reloadPageDataAfterLocaleChange();
+};
+
+const onReportTitleClick = () => {
+    if (!localeToggleEnabled) return;
+    toggleLocale();
+};
+
+// 清理数据的函数
+const clearData = () => {
+    workItems.value = [];
+    paramsPage.value = {};
+    loading.value = false;
+};
+
+onDeactivated(() => {
+    sessionStorage.setItem('activeSwipeIndex', currentIndex.value);
+    clearData();
+});
+
+onUnmounted(() => {
+    clearData();
+});
+</script>
+
+<style lang="scss" scoped>
+.mask-wrap {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 300px;
+    background-color: rgba(0, 0, 0, 0.52);
+    z-index: 99999;
+}
+
+.achievement-report-page {
+    width: 100%;
+    height: 100vh;
+    background: linear-gradient(195.35deg, #d4e4ff 16.34%, rgba(93, 189, 255, 0) 50.3%),
+        linear-gradient(156.64deg, rgba(255, 255, 255, 0.16) 27.7%, rgba(255, 255, 255, 0) 72.82%);
+
+    .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-wrap {
+        width: calc(100% - 20px);
+        position: absolute;
+        z-index: 12;
+        left: 10px;
+        top: 10px;
+
+        .weather-info {
+            width: 100%;
+        }
+
+        .invite-follow {
+            position: absolute;
+            right: -6px;
+            top: 50px;
+
+            .invite-content {
+                display: flex;
+                align-items: center;
+                gap: 4px;
+                font-size: 14px;
+                font-family: "PangMenZhengDao";
+                color: #fff;
+                line-height: 30px;
+                padding: 0 12px;
+                border-radius: 20px 0 0 20px;
+                height: 30px;
+                cursor: pointer;
+                background: #2199F8;
+                position: relative;
+
+                &::after {
+                    content: '';
+                    position: absolute;
+                    bottom: -6px;
+                    right: 0;
+                    width: 0px;
+                    height: 0px;
+                    border-right: 3px solid transparent;
+                    border-top: 3px solid #75a8cd;
+                    border-left: 3px solid #75a8cd;
+                    border-bottom: 3px solid transparent;
+                }
+            }
+        }
+    }
+
+
+    .fake-report-wrap {
+        width: 100%;
+
+        .no-report-img {
+            width: 100%;
+        }
+
+        .fake-img {
+            position: relative;
+
+            .fake-img-item {
+                width: 100%;
+            }
+        }
+    }
+
+    .report-content-wrap {
+        height: 100%;
+        // padding-bottom: 60px;
+        overflow: auto;
+        box-sizing: border-box;
+        position: relative;
+
+        .history-risk-report-btn {
+            position: absolute;
+            right: 0px;
+            // top: 155px;
+            top: 120px;
+            z-index: 13;
+            height: 26px;
+            padding: 0 10px 0 8px;
+            display: inline-flex;
+            align-items: center;
+            gap: 4px;
+            color: #ffffff;
+            font-size: 14px;
+            border-radius: 13px 0 0 13px;
+            background: linear-gradient(180deg, #60c2ff 0%, #2199f8 100%);
+            box-shadow: 0 2px 6px rgba(33, 153, 248, 0.3);
+            cursor: pointer;
+
+            .risk-report-icon {
+                width: 14px;
+                height: 14px;
+                border-radius: 2px;
+                background: #ffffff;
+                position: relative;
+                display: inline-flex;
+                align-items: center;
+                justify-content: center;
+                transform: rotate(-12deg);
+
+                i {
+                    width: 8px;
+                    height: 2px;
+                    border-radius: 2px;
+                    background: #42a7ff;
+                    box-shadow: 0 3px 0 #42a7ff;
+                }
+            }
+
+            .risk-report-text {
+                line-height: 1;
+                white-space: nowrap;
+            }
+        }
+
+        .bottom-btn {
+            z-index: 2;
+            position: fixed;
+            bottom: 0;
+            left: 0;
+            width: 100%;
+            background: #fff;
+            height: 60px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 0 12px;
+            box-sizing: border-box;
+            box-shadow: 2px 2px 4.5px 0px rgba(0, 0, 0, 0.4);
+
+            .btn-item {
+                height: 40px;
+                line-height: 40px;
+                padding: 0 24px;
+                border-radius: 20px;
+                font-size: 14px;
+
+                &.second {
+                    color: #666666;
+                    border: 1px solid rgba(153, 153, 153, 0.5);
+                }
+
+                &.primay {
+                    padding: 0 34px;
+                    background: linear-gradient(180deg, #76c3ff, #2199f8);
+                    color: #fff;
+                }
+            }
+        }
+    }
+
+    .code-icon {
+        position: absolute;
+        right: 10px;
+        top: 12px;
+        width: 48px;
+    }
+
+    .report-content {
+        // background: url("@/assets/img/home/report_bg.png") no-repeat center center;
+        // background: linear-gradient(0deg, #9BCCFF, #9BCCFF),
+        //     linear-gradient(160deg, rgba(255, 255, 255, 0.16) 30%, rgba(255, 255, 255, 0) 72%);
+        background: #abd4ff;
+
+        background-size: 100% auto;
+        background-position: top center;
+        padding: 0 10px 26px 10px;
+        box-sizing: border-box;
+        position: relative;
+
+        &.has-report {
+            min-height: 100%;
+            background: linear-gradient(0deg, #9BCCFF, #9BCCFF),
+                linear-gradient(156.64deg, rgba(255, 255, 255, 0.16) 27.7%, rgba(255, 255, 255, 0) 72.82%);
+        }
+
+        .lock-bg {
+            position: absolute;
+            top: 230px;
+            left: 0;
+            width: 100%;
+            height: calc(100% - 230px);
+            background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.38) 50%, rgba(255, 255, 255, 0) 100%),
+                linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2));
+        }
+
+        .lock-img {
+            pointer-events: none;
+            position: fixed;
+            z-index: 10;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -20%);
+            width: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex-direction: column;
+            gap: 16px;
+
+            .lock-img-item {
+                width: 57px;
+            }
+
+            .has-click {
+                pointer-events: auto;
+            }
+
+            .lock-text {
+                font-size: 14px;
+                color: #000;
+                padding: 5px 64px;
+                line-height: 21px;
+                background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 50%, rgba(255, 255, 255, 0) 100%);
+            }
+
+            .lock-btn {
+                width: 140px;
+                height: 40px;
+                line-height: 40px;
+                text-align: center;
+                background: linear-gradient(180deg, #76C3FF 0%, #2199F8 100%);
+                border-radius: 25px;
+                color: #fff;
+                font-size: 16px;
+            }
+        }
+
+        .header-img {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+        }
+
+        .type-tabs {
+            background: rgba(255, 255, 255, 0.8);
+            display: flex;
+            align-items: center;
+            flex-wrap: wrap;
+            gap: 8px;
+            width: fit-content;
+            border-radius: 5px;
+            padding: 5px;
+            margin-bottom: 22px;
+            &.report-tabs {
+                padding: 0 5px;
+                height: 38px;
+                background: none;
+                margin-bottom: 6px;
+                .type-item {
+                    height: 34px;
+                    line-height: 34px;
+                    font-size: 14px;
+                    color: #858585;
+                }
+            }
+            .type-item {
+                height: 28px;
+                line-height: 28px;
+                text-align: center;
+                padding: 0 6px;
+                min-width: 80px;
+                color: #9A9A9A;
+                background: #FFFFFF;
+                box-sizing: border-box;
+                border-radius: 2px;
+                &.type-item-active {
+                    background: #2199F8;
+                    color: #fff;
+                }
+            }
+        }
+
+        .subject-toggle {
+            margin-top: 4px;
+            margin-bottom: 5px;
+            font-size: 14px;
+            color: rgba(0, 0, 0, 0.6);
+            width: 100%;
+            cursor: pointer;
+            text-align: center;
+        }
+
+        .report-header {
+            position: relative;
+            // padding-top: 148px;
+            padding-top: 120px;
+
+            &.no-farm {
+                padding-top: 102px;
+            }
+
+            .header-book {
+                position: absolute;
+                right: 0;
+                bottom: -6px;
+                height: 88px;
+                z-index: 10;
+            }
+
+            .time-tag {
+                background: #2199F8;
+                border-radius: 5px 0 5px 0;
+                height: 23px;
+                line-height: 23px;
+                font-size: 13px;
+                font-weight: 500;
+                color: #fff;
+                padding: 0 9px;
+                width: fit-content;
+                margin-bottom: 4px;
+            }
+
+            .report-title {
+                font-family: "PangMenZhengDao";
+                font-size: 34px;
+                line-height: 38px;
+
+                &.report-title-toggle {
+                    cursor: pointer;
+                    user-select: none;
+                }
+                color: #000000;
+            }
+
+            .report-info {
+                padding: 12px 0 28px 0;
+
+                &.pb-4 {
+                    padding-bottom: 4px;
+                }
+
+                .info-item {
+                    width: fit-content;
+                    display: flex;
+                    height: 33px;
+                    align-items: center;
+                    padding: 0 18px 0 6px;
+                    background: rgba(255, 255, 255, 0.58);
+                    backdrop-filter: blur(5px);
+                    border-radius: 20px;
+                    gap: 6px;
+
+                    .info-icon {
+                        width: 26px;
+                        height: 26px;
+                        object-fit: cover;
+                        border-radius: 50%;
+                    }
+
+                    .info-text {
+                        font-size: 14px;
+                        color: #000;
+                    }
+                }
+
+                .info-item+.info-item {
+                    margin-top: 5px;
+                }
+            }
+
+            // 左滑查看更多标签
+            .swipe-more-tag {
+                position: absolute;
+                bottom: 10px;
+                right: -16px;
+                box-sizing: border-box;
+                width: 36px;
+                height: 86px;
+                // padding: 0px 10px 2px 0;
+                background: rgba(0, 0, 0, 0.7);
+                border-radius: 10px 0 0 10px;
+                letter-spacing: 2px;
+                color: #ffffff;
+                font-size: 12px;
+                text-align: center;
+                line-height: 14px;
+                writing-mode: vertical-rl;
+                text-orientation: mixed;
+                padding-right: 5px;
+            }
+        }
+
+        .report-box {
+            display: flex;
+            align-items: center;
+            padding: 8px 12px;
+            // background: linear-gradient(0deg, #ffffff 86%, #2199f8 136%);
+            background: #fff;
+            border: 1px solid #ffffff;
+            border-radius: 8px;
+            gap: 5px;
+            position: relative;
+            &.warning-bg {
+                background: linear-gradient(0deg, #FFFFFF 86%, #FF9B48 136%);
+            }
+
+            .report-box-item {
+                flex: 1;
+                background: rgba(33, 153, 248, 0.1);
+                border-radius: 8px;
+                min-height: 62px;
+                box-sizing: border-box;
+                padding: 2px 4px;
+                display: flex;
+                flex-direction: column;
+                justify-content: center;
+
+                .item-content {
+                    color: #2199f8;
+                    font-size: 14px;
+                    text-align: center;
+                }
+
+                .item-title {
+                    color: #000000;
+                    font-size: 10px;
+                    text-align: center;
+                    padding-top: 5px;
+                }
+            }
+
+            .box-title {
+                position: absolute;
+                top: -8px;
+                left: -1px;
+                height: 32px;
+                line-height: 26px;
+                font-family: "PangMenZhengDao";
+                font-size: 14px;
+                padding: 0 10px;
+                color: #ffffff;
+                background: url("@/assets/img/home/title-bg.png") no-repeat center center / 100% 100%;
+
+                &.warning {
+                    background: url("@/assets/img/home/title-bg-warning.png") no-repeat center center / 100% 100%;
+                }
+            }
+
+            .w-100 {
+                width: 100%;
+            }
+
+            .box-text {
+                padding: 22px 0 8px 0;
+                font-weight: 350;
+                line-height: 21px;
+                width: 100%;
+                box-sizing: border-box;
+
+                .pre-text {
+                    white-space: pre-line;
+                    word-break: break-word;
+                }
+
+                .box-subtitle {
+                    color: #000;
+                }
+
+                .box-bg {
+                    font-weight: 400;
+                    color: rgba(0, 0, 0, 0.5);
+                    margin-bottom: 8px;
+                }
+
+                .types-info {
+                    background: rgba(33, 153, 248, 0.1);
+                    color: #000000;
+                    padding: 6px;
+                    border-radius: 5px;
+                    .text-bold {
+                        font-weight: bold;
+                    }
+                }
+                .tp-img {
+                    display: grid;
+                    grid-template-columns: repeat(3, 1fr);
+                    gap: 6px;
+                    width: 100%;
+                    margin-top: 8px;
+                    box-sizing: border-box;
+                    img {
+                        width: 100%;
+                        height: 78px;
+                        object-fit: contain;
+                    }
+                }
+                .text-link {
+                    color: #2199F8;
+                    text-decoration: underline;
+                }
+
+                .report-part {
+                    color: rgba(0, 0, 0, 0.5);
+                    .part-title {
+                        background: #2199F8;
+                        height: 24px;
+                        line-height: 24px;
+                        padding: 0 8px;
+                        color: #fff;
+                        border-radius: 2px;
+                        width: fit-content;
+                    }
+                    .part-text {
+                        padding-top: 6px;
+                        :deep(.text-bold) {
+                            font-weight: bold;
+                        }
+                    }
+                    .part-top {
+                        display: flex;
+                        align-items: center;
+                        justify-content: space-between;
+                        .part-link {
+                            display: inline-flex;
+                            align-items: center;
+                            gap: 4px;
+                            color: #2199F8;
+                            .part-link-icon {
+                                transform: rotate(270deg);
+                            }
+                        }
+                    }
+                }
+                .warning-part + .warning-part {
+                    margin-top: 8px;
+                }
+                .report-part + .report-part {
+                    margin-top: 8px;
+                }
+
+                .warning-part {
+                    background: rgba(178, 178, 178, 0.08);
+                    border-radius: 5px;
+                    padding: 11px 6px 6px;
+                    color: rgba(0, 0, 0, 0.5);
+                    width: 100%;
+                    box-sizing: border-box;
+                    .warning-title {
+                        display: flex;
+                        align-items: center;
+                        justify-content: center;
+                        gap: 10px;
+                        padding-bottom: 13px;
+                        .title-l {
+                            display: flex;
+                            align-items: center;
+                            .title-line {
+                                width: 68px;
+                                height: 1px;
+                                background: linear-gradient(90deg, rgba(118, 118, 118, 0) 0%, rgba(118, 118, 118, 0.4) 100%);
+                                &.title-line-right {
+                                    background: linear-gradient(270deg, rgba(118, 118, 118, 0) 0%, rgba(118, 118, 118, 0.4) 100%);
+                                }
+                            }
+                            .title-block {
+                                width: 6px;
+                                height: 6px;
+                                background: rgba(61, 61, 61, 0.2);
+                                transform: rotate(45deg);
+                            }
+                        }
+                    }
+                }
+
+                .box-advice {
+                    color: rgba(0, 0, 0, 0.5);
+                    padding-top: 10px;
+                }
+
+                .box-sum {
+                    margin-top: 10px;
+                    background: rgba(33, 153, 248, 0.1);
+                    border-radius: 5px;
+                    padding: 10px;
+                    line-height: 20px;
+                    color: #2199F8;
+                }
+
+                &.next-info {
+                    padding: 8px 0 8px 0;
+                }
+            }
+
+            .row {
+                display: grid;
+                grid-template-columns: repeat(3, 1fr);
+                gap: 6px;
+
+
+
+                .status-card {
+                    border-radius: 2px;
+                    padding: 7px 0;
+                    background: #ffffff;
+                    border: 0.5px solid #e5e6eb;
+                    color: #000;
+                    display: flex;
+                    flex-direction: column;
+                    align-items: center;
+                    justify-content: center;
+
+                    &.today-red {
+                        background: #FF6A6A;
+                        color: #fff;
+
+                        .status-sub {
+                            color: #fff;
+                        }
+                    }
+
+                    &.pending-card {
+                        color: #fff;
+                        position: relative;
+                        padding: 9px 0 7px 0;
+
+                        .tag-name {
+                            position: absolute;
+                            top: -8px;
+                            right: 0;
+                            background: #fff;
+                            color: #FF6A6A;
+                            font-size: 10px;
+                            height: 17px;
+                            line-height: 17px;
+                            padding: 0 3px;
+                            border-radius: 2px;
+                            box-sizing: border-box;
+                            border: 0.5px solid #FF6A6A;
+                        }
+                    }
+
+                    .status-badge {
+                        // position: absolute;
+                        // top: 0;
+                        // right: 0;
+                    }
+
+                    .status-title {
+                        font-size: 16px;
+                        line-height: 24px;
+
+                        &.status-title-small {
+                            font-size: 13px;
+                            line-height: 18px;
+                        }
+                    }
+
+                    .status-sub {
+                        font-size: 10px;
+                        color: rgba(32, 32, 32, 0.4);
+                        line-height: 15px;
+
+                        &.pending-sub {
+                            color: #fff;
+                            line-height: 13px;
+                        }
+                    }
+
+                    &.risk-strong {
+                        background: #FF6A6A;
+                        border-color: #FF6A6A;
+
+                        .status-title,
+                        .status-sub {
+                            color: #ffffff;
+                        }
+                    }
+
+                    &.danger {
+                        background: #FFE9E9;
+                        border-color: #ff8e8e;
+
+                        .status-sub {
+                            color: #FF6A6A;
+                        }
+                    }
+                }
+            }
+        }
+
+        .report-box+.report-box {
+            margin-top: 20px;
+        }
+
+        .report-excute {
+            position: relative;
+            margin-top: 12px;
+
+            .tag-label {
+                position: absolute;
+                top: 0;
+                left: 0;
+                padding: 4px 10px;
+                background: rgba(54, 52, 52, 0.8);
+                color: #fff;
+                font-size: 12px;
+                border-radius: 8px 0 8px 0;
+                z-index: 1;
+            }
+
+            ::v-deep {
+                .carousel-container .carousel-wrapper .carousel-img {
+                    min-width: calc(100vw - 32px);
+                    width: calc(100vw - 32px);
+                }
+            }
+        }
+    }
+
+    .download-btn {
+        position: fixed;
+        bottom: 20px;
+        left: 50%;
+        // background: #fff;
+        // box-shadow: 2px 2px 4.5px 0px #00000066;
+        // width: 100%;
+        transform: translateX(-50%);
+    }
+
+    .review-hide-box {
+        position: absolute;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        z-index: -1;
+        bottom: 0;
+    }
+
+    .review-image {
+        position: relative;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: 8px;
+        margin: 12px;
+        background: #fff;
+        border-radius: 8px;
+
+        .review-mask {
+            z-index: 1;
+            pointer-events: none;
+            position: absolute;
+            left: 0;
+            top: 0;
+            width: 100%;
+            height: 100%;
+            border-radius: 8px;
+            background: linear-gradient(360deg,
+                    rgba(0, 0, 0, 0.78) 0%,
+                    rgba(0, 0, 0, 0.437208) 19.87%,
+                    rgba(0, 0, 0, 0) 33.99%);
+            display: flex;
+            flex-direction: column;
+            align-items: baseline;
+            justify-content: end;
+            padding: 12px;
+            box-sizing: border-box;
+            color: #fff;
+
+            .review-text {
+                font-family: "PangMenZhengDao";
+                font-size: 16px;
+                margin-bottom: 1px;
+            }
+
+            .review-content {
+                font-size: 10px;
+                line-height: 15px;
+            }
+        }
+
+        .vs-wrap {
+            position: absolute;
+            left: 50%;
+            top: 50%;
+            transform: translate(-50%, -50%);
+            width: 40px;
+            height: 40px;
+            z-index: 10;
+
+            img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+            }
+        }
+
+        .review-image-item {
+            position: relative;
+            flex: 1;
+
+            .review-image-item-title {
+                position: absolute;
+                top: 0;
+                left: 0;
+                background: rgba(54, 52, 52, 0.6);
+                padding: 4px 10px;
+                border-radius: 8px 0 8px 0;
+                backdrop-filter: 4px;
+                font-size: 12px;
+                color: #fff;
+            }
+
+            // .review-image-item-img {
+            //     width: 100%;
+            //     height: 250px;
+            //     object-fit: cover;
+            // }
+            .review-image-item-img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+                object-position: center;
+            }
+
+            .left-img {
+                border-radius: 8px 0 0 8px;
+            }
+
+            .right-img {
+                border-radius: 0 8px 8px 0;
+            }
+        }
+    }
+}
+
+
+.cavans-popup {
+    width: 100%;
+    max-width: 100%;
+    max-height: 92vh;
+    background: none;
+    border-radius: 12px;
+    overflow: auto;
+    display: flex;
+    flex-direction: column;
+    backdrop-filter: 4px;
+
+    .cavans-content {
+        text-align: center;
+        padding: 0 12px;
+        height: fit-content;
+        overflow: auto;
+
+        .current-img {
+            width: 100%;
+        }
+    }
+
+    // 底部操作按钮
+    .bottom-actions {
+        flex-shrink: 0;
+
+        .action-buttons {
+            padding: 12px 0 4px 0;
+            display: flex;
+            justify-content: space-around;
+
+            .action-btn {
+                display: flex;
+                flex-direction: column;
+                align-items: center;
+                cursor: pointer;
+
+                &.text-btn {
+                    font-size: 12px;
+                    color: rgba(255, 255, 255, 0.7);
+                }
+
+                .icon-circle {
+                    width: 48px;
+                    height: 48px;
+                    border-radius: 50%;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    color: #fff;
+                    margin-bottom: 4px;
+
+                    .el-icon {
+                        color: #fff;
+                    }
+
+                    img {
+                        width: 50px;
+                    }
+                }
+
+                &.blue-btn .icon-circle {
+                    background: #2199f8;
+                }
+
+                &.green-btn .icon-circle {
+                    background: #07c160;
+                }
+
+                &.orange-btn .icon-circle {
+                    background: #ff790b;
+                }
+
+                .btn-label {
+                    font-size: 12px;
+                    color: #fff;
+                }
+            }
+        }
+
+        .cancel-btn {
+            text-align: center;
+            font-size: 18px;
+            color: #fff;
+            cursor: pointer;
+        }
+    }
+}
+</style>

+ 238 - 1479
src/views/old_mini/growth_report/index.vue

@@ -1,682 +1,235 @@
 <template>
-    <div class="achievement-report-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
+    <div class="growth-report-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
         <!-- 天气遮罩 -->
         <div class="weather-mask" v-show="isExpanded" @click="handleMaskClick"></div>
-        <!-- 组件:天气 -->
-        <div class="weather-info-wrap">
-            <weather-info ref="weatherInfoRef" from="growth_report" class="weather-info" :showTabMask="showTabMask"
+        <!-- 头部 -->
+        <div
+            v-show="activeGardenTab === 'current'"
+            class="growth-report-header"
+            :style="headerMotionStyle"
+        >
+            <weather-info ref="weatherInfoRef" :hasWeather="false" from="growth_report" class="weather-info"
                 @weatherExpanded="weatherExpanded" @changeGarden="changeGarden" @changeGardenTab="changeGardenTab"
-                @closeTabMask="closeTabMask">
+                :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="invite-follow" v-if="currentFarmName && activeGardenTab === 'current'">
-                <div class="invite-content">
-                    <icon name="share" />
-                    <span>{{ t('邀请关注') }}</span>
-                </div>
-            </div> -->
+            <div class="report-maintain-btn" @click="handleFarmInfoMaintain">
+                <img class="report-maintain-icon" src="@/assets/img/common/info.png" alt="" />
+                <span>农场信息维护</span>
+            </div>
         </div>
-
         <!-- 农场列表 -->
         <div v-show="activeGardenTab === 'list'">
             <garden-list ref="gardenListRef" :garden-id="selectedGardenId" @loaded="handleGardenLoaded"
                 @selectGarden="handleGardenSelected" />
         </div>
-
-        <div class="report-content-wrap" v-if="hasReport && activeGardenTab === 'current'" v-loading="loading"
-            element-loading-background="rgba(0, 0, 0, 0.1)">
-            <div class="history-risk-report-btn" @click="handleHistoryRiskReportClick">
-                <span class="risk-report-icon">
-                    <i></i>
-                </span>
-                <span class="risk-report-text">{{ t('历史风险报告') }}</span>
-            </div>
-
-            <div class="report-content has-report" :style="{ minHeight: `calc(100vh - ${tabBarHeight}px)` }">
-                <!-- <img src="@/assets/img/home/qrcode.png" alt="" class="code-icon" /> -->
-                <img class="header-img" src="@/assets/img/home/report.png" alt="" />
-
-                <div class="report-header">
-                    <!-- <div class="type-tabs report-tabs">
-                        <div class="type-item" :class="{ 'type-item-active': activeReportIndex === 0 }">{{ t('作物长势') }}</div>
-                        <div class="type-item" :class="{ 'type-item-active': activeReportIndex === 1 }">{{ t('历史风险') }}</div>
-                        <div class="type-item" :class="{ 'type-item-active': activeReportIndex === 2 }">{{ t('土壤改良') }}</div>
-                        <div class="type-item" :class="{ 'type-item-active': activeReportIndex === 3 }">{{ t('种植建议') }}</div>
-                    </div> -->
-                    <div class="type-tabs" v-if="subjectData.length > 1">
-                        <div
-                            @click="handleTypeTabClick(item, index)"
-                            class="type-item"
-                            v-for="(item, index) in visibleSubjectData"
-                            :class="{ 'type-item-active': activeSubjectIndex === index }"
-                            :key="index">{{ item.speciesName }}</div>
-                    <div
-                        v-if="showSubjectToggle"
-                        class="subject-toggle"
-                        @click="typeTabsExpanded = !typeTabsExpanded"
-                    >
-                        {{ typeTabsExpanded ? t("common.collapse") : t("common.expandMore") }}
-                    </div>
-                    </div>
-
-                    <div class="time-tag">{{ workItems?.[0]?.reportDate || new Date().toISOString().split('T')[0] }}</div>
-                    <div
-                        class="report-title"
-                        :class="{ 'report-title-toggle': localeToggleEnabled }"
-                        @click="onReportTitleClick"
-                    >{{ varietyName }}{{ t("growthReport.title") }}</div>
-                    <div class="report-info">
-                        <div class="info-item">
-                            <img class="info-icon" src="@/assets/img/home/farm.png" alt="" />
-                            <span class="info-text">{{ currentFarmName }}</span>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="report-box">
-                    <div class="box-title">{{ t("growthReport.weatherRisk") }}</div>
-                    <div class="box-text">
-                        <div class="box-bg">
-                            <!-- <div class="types-info">
-                                当前 <span class="text-bold">{{ t('水稻') }}</span>{{ t('处于为  分蘖初期') }}<span class="text-link" @click="handleAdjustPopup">{{ t('(校准物候期)') }}</span>
-                            </div> -->
-                            <div class="types-info">
-                                {{ t("common.current") }}
-                                <span class="text-bold">{{ varietyName }}</span>
-                                {{ t("growthReport.inStage", { stage: currentPhenologyStage }) }}
-                            </div>
-                            <div class="tp-img" v-if="currentFarmVariety === 1">
-                                <img src="@/assets/img/common/tp-1.png" alt="">
-                                <img src="@/assets/img/common/tp-2.png" alt="">
-                                <img src="@/assets/img/common/tp-3.png" alt="">
-                                <img src="@/assets/img/common/tp-4.png" alt="">
-                                <img src="@/assets/img/common/tp-5.png" alt="">
-                                <img src="@/assets/img/common/tp-6.png" alt="">
-                            </div>
-                            <div class="tp-img" v-else>
-                                <img src="@/assets/img/common/sd-1.jpg" alt="">
-                                <img src="@/assets/img/common/sd-2.jpg" alt="">
-                                <img src="@/assets/img/common/sd-3.jpg" alt="">
-                                <img src="@/assets/img/common/sd-4.jpg" alt="">
-                                <img src="@/assets/img/common/sd-5.jpg" alt="">
-                                <img src="@/assets/img/common/sd-6.jpg" alt="">
-                            </div>
-                        </div>
-                        <div class="warning-part">
-                            <div class="warning-title">
-                                <div class="title-l">
-                                    <div class="title-line"></div>
-                                    <div class="title-block"></div>
-                                </div>
-                                <div>{{ t("growthReport.futureWeatherRisk") }}</div>
-                                <div class="title-l">
-                                    <div class="title-block"></div>
-                                    <div class="title-line title-line-right"></div>
-                                </div>
-                            </div>
-
-                            <div class="report-part" v-for="(part, partI) in riskList" :key="partI">
-                                <div class="part-title">{{ part.title }}</div>
-                                <div class="part-text" v-html="boldKeywordsInText(part.description, part.boldKeywords)"></div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="report-box">
-                    <div class="box-title">{{ t("growthReport.farmAdvice") }}</div>
-                    <div class="box-text">
-                        <div class="warning-part" v-for="(part, partI) in adviceList" :key="partI">
-                            <div class="report-part">
-                                <div class="part-top">
-                                    <div class="part-title">{{ part.title }}</div>
-                                    <!-- <div class="part-link">
-                                        <el-icon class="part-link-icon"><Link /></el-icon>
-                                        <div class="text-link">{{ t('查看农事') }}</div>
-                                    </div> -->
-                                </div>
-                                <div class="part-text" v-html="boldKeywordsInText(part.description, part.boldKeywords)"></div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-                <div class="report-box">
-                    <div class="box-title">{{ t("growthReport.patrolFocus") }}</div>
-                    <div class="box-text">
-                        <div class="warning-part" v-for="(part, partI) in patrolList" :key="partI">
-                            <div class="report-part">
-                                <div class="part-top">
-                                    <div class="part-title">{{ part.title }}</div>
-                                    <!-- <div class="part-link">
-                                        <el-icon class="part-link-icon"><Link /></el-icon>
-                                        <div class="text-link">{{ t('查看互动') }}</div>
-                                    </div> -->
-                                </div>
-                                <div class="part-text" v-html="boldKeywordsInText(part.description, part.boldKeywords)"></div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="report-box" v-for="(work, workI) in workItems" :key="workI">
-                    <div class="box-title">{{ work?.title }}</div>
-                    <div class="box-text">
-                        <div class="box-bg" v-show="work?.backgroundDesc">
-                            <span class="box-subtitle">{{ t("common.backgroundDesc") }}</span>
-                            <div class="pre-text">{{ work?.backgroundDesc }}</div>
-                        </div>
-                        <div class="box-advice" v-show="work?.suggestion">
-                            <span class="box-subtitle">{{ t("common.suggestion") }}</span>
-                            <div class="pre-text">{{ work?.suggestion }}</div>
-                        </div>
-                        <div class="box-sum pre-text" v-show="work?.summary">{{ work?.summary }}</div>
-                    </div>
+        <div class="report-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>
-
-            <!-- <swipe ref="swipeRef" class="my-swipe" :loop="false" indicator-color="white" @change="handleSwipeChange">
-                <swipe-item v-for="(item, index) in regionsData" :key="index">
-                    
-                </swipe-item>
-            </swipe> -->
-        </div>
-
-        <div v-else-if="activeGardenTab === 'current'" class="fake-report-wrap report-content-wrap">
-            <div class="report-content">
-
-                <img class="header-img" src="@/assets/img/home/report.png" alt="" />
-                <div class="report-header" :class="{ 'no-farm': !currentFarmName }">
-                    <!-- <img class="header-book" src="@/assets/img/home/book.png" alt="" /> -->
-                    <div class="time-tag">{{ new Date().toISOString().split('T')[0] }}</div>
-                    <div
-                        class="report-title"
-                        :class="{ 'report-title-toggle': localeToggleEnabled }"
-                        @click="onReportTitleClick"
-                    >{{ t("growthReport.cropTitle") }}</div>
-                    <div class="report-info pb-4">
-                        <div class="info-item">
-                            <img class="info-icon" src="@/assets/img/home/farm.png" alt="" />
-                            <span class="info-text">{{ t("common.demoFarm") }}</span>
-                        </div>
-                    </div>
-                </div>
-                <div class="fake-img">
-                    <img src="@/assets/img/home/fake.png" alt="" class="fake-img-item" />
-                </div>
-
-                <div class="lock-img">
-                    <img @click="handleLockClick" src="@/assets/img/home/lock-blue.png" alt=""
-                        class="has-click lock-img-item" />
-                    <div class="lock-text">
-                        {{ t("growthReport.lockTitle") }}
-                        <div>{{ t("growthReport.lockSub") }}</div>
-                    </div>
-
-                    <div @click="handleLockClick" class="lock-btn has-click">{{ t("common.unlock") }}</div>
-                </div>
-
-                <div class="lock-bg"></div>
-            </div>
+            <div class="map-container" ref="mapContainer"></div>
         </div>
-
-        <!-- 农场列表引导 -->
-        <div class="mask-wrap" @click="closeTabMask" v-if="showTabMask"></div>
-
-        <tip-popup v-model:show="showBindSuccess" type="success" :text="t('growthReport.bindSuccess')" hideBtn />
-
-        <start-interact-popup ref="startInteractPopupRef" />
-
-        <agri-execute-popup ref="agriExecutePopupRef" />
-
-        <!-- 校准物候期 -->
-        <adjust-popup ref="adjustPopupRef" />
+        <risk-report-panel
+            v-show="activeGardenTab === 'current'"
+            :view-type="panelViewType"
+            :plot-detail="plotDetail"
+            @expand-progress="panelExpandProgress = $event"
+            @close-plot-detail="handleClosePlotDetail"
+        />
     </div>
 </template>
 
 <script setup>
-import wx from "weixin-js-sdk";
-import weatherInfo from "@/components/weatherInfo.vue";
-import { ref, onActivated, onDeactivated, onUnmounted, computed, nextTick } from "vue";
+import { computed, nextTick, onActivated, ref } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { useStore } from "vuex";
-import { Swipe, SwipeItem, Badge, Icon } from 'vant';
-import tipPopup from "@/components/popup/tipPopup.vue";
-import startInteractPopup from "@/components/popup/startInteractPopup.vue";
-import agriExecutePopup from "@/components/popup/agriExecutePopup.vue";
+import weatherInfo from "@/components/weatherInfo.vue";
 import gardenList from "@/components/gardenList.vue";
-import adjustPopup from "./adjustPopup.vue";
-import { useI18n } from "@/i18n";
-import { boldKeywordsInText } from "@/utils/boldKeywords";
+import GrowthReportMap from "./growthReportMap.js";
+import RiskReportPanel from "./components/RiskReportPanel.vue";
+import * as util from "@/common/ol_common.js";
 
-const { t, toggleLocale: dispatchToggleLocale } = useI18n();
-const store = useStore();
-
-/** 暂时隐藏标题中英切换,恢复时改为 true */
-const localeToggleEnabled = false;
-const tabBarHeight = computed(() => store.state.home.tabBarHeight);
+const DEFAULT_FARM_POINT = "POINT(113.6142086995688 23.585836479509055)";
 
+const store = useStore();
 const route = useRoute();
 const router = useRouter();
-const loading = ref(false);
-const hasReport = ref(true);
-const workItems = ref([]);
-const swipeRef = ref(null);
-
-//
-const riskList = computed(() => [
-    {
-        title:
-            currentFarmVariety.value == 1
-                ? t("growthReport.risk.pest.title")
-                : t("growthReport.risk.pest.titleRice"),
-        description:
-            currentFarmVariety.value == 1
-                ? t("growthReport.risk.pest.desc")
-                : t("growthReport.risk.pest.descRice"),
-        boldKeywords:
-            currentFarmVariety.value == 1
-                ? ["高温干旱", "中等水平", "果皮灼伤"]
-                : ["中等水平", "做好排水工作"],
-    },
-    // {
-    //     title: t("growthReport.risk.rain.title"),
-    //     description: t("growthReport.risk.rain.desc"),
-    // },
-]);
-
-const adviceList = computed(() => [
-    {
-        title:
-            currentFarmVariety.value == 1
-                ? t("growthReport.advice.foliar.title")
-                : t("growthReport.advice.foliar.titleRice"),
-        description:
-            currentFarmVariety.value == 1
-                ? t("growthReport.advice.foliar.desc")
-                : t("growthReport.advice.foliar.descRice"),
-        boldKeywords:
-            currentFarmVariety.value == 1 ? ["需及时喷撒清水"] : [],
-    },
-    // {
-    //     title: t("growthReport.advice.pestControl.title"),
-    //     description: t("growthReport.advice.pestControl.desc"),
-    // },
-]);
-
-const patrolList = computed(() => {
-    const isLychee = currentFarmVariety.value == 1;
-    return [
-        {
-            title: t("growthReport.patrol.process.title"),
-            description: isLychee
-                ? t("growthReport.patrol.process.desc")
-                : t("growthReport.patrol.process.descRice"),
-            boldKeywords: isLychee ? ["5%", "果实转色期"] : ["60%", "拔节期"],
-        },
-        {
-            title: t("growthReport.patrol.growth.title"),
-            description: isLychee
-                ? t("growthReport.patrol.growth.desc")
-                : t("growthReport.patrol.growth.descRice"),
-            boldKeywords: isLychee ? ["10%", "抽生新梢"] : ["10%", "干旱缺素"],
-        },
-    ];
-    // {
-    //     title: t("growthReport.patrol.pest.title"),
-    //     description: t("growthReport.patrol.pest.desc"),
-    // },
-});
-//
-const paramsPage = ref({});
-const showBindSuccess = ref(false);
-const startInteractPopupRef = ref(null);
-const agriExecutePopupRef = ref(null);
-const adjustPopupRef = ref(null);
+const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 
-const handleAdjustPopup = () => {
-    adjustPopupRef.value.open();
-}
-// 天气组件相关
 const isExpanded = ref(false);
 const weatherInfoRef = ref(null);
-
-const showTabMask = ref(false);
-const TAB_GUIDE_SHOWN_KEY = "GROWTH_REPORT_TAB_GUIDE_SHOWN";
-const weatherExpanded = (isExpandedValue) => {
-    isExpanded.value = isExpandedValue;
-};
-
-// 点击遮罩时收起天气
-const handleMaskClick = () => {
-    if (weatherInfoRef.value && weatherInfoRef.value.toggleExpand) {
-        weatherInfoRef.value.toggleExpand();
-    }
-};
-
-const currentFarmName = ref('');
+const defaultGardenId = ref(null);
 const selectedGardenId = ref(null);
 const gardenListRef = ref(null);
-const activeGardenTab = ref('current');
-const changeGardenTab = (tab) => {
-    activeGardenTab.value = tab;
+const activeGardenTab = ref("current");
+const panelExpandProgress = ref(0);
+const panelViewType = ref("risk");
+const plotDetail = ref(createDefaultPlotDetail());
+
+const HEADER_FADE_START = 0.68;
+
+function createDefaultPlotDetail() {
+    const defaultImage = require("@/assets/img/home/banner.png");
+    return {
+        name: "地块名称",
+        area: "289亩",
+        categories: "荔枝、水稻",
+        startTime: "2026/06/05",
+        harvestTime: "2026/06/05",
+        description: "当前处于物候期,有什么风险,做了什么农事,或者正在执行中,当前处于物候期,有什么风险",
+        images: [defaultImage, defaultImage, defaultImage, defaultImage],
+    };
 }
 
-const handleGardenLoaded = ({ hasFarm }) => {
-    weatherInfoRef.value?.setGardenLoaded?.(hasFarm);
-};
+function buildPlotDetailByTab(item) {
+    const tabLabelMap = {
+        soilImprovement: "土壤改良地块",
+        rotationAdvice: "轮作建议地块",
+    };
+    return {
+        ...createDefaultPlotDetail(),
+        name: tabLabelMap[item.key] || "地块名称",
+    };
+}
 
-const handleGardenSelected = (garden) => {
-    selectedGardenId.value = garden?.id ?? null;
-    weatherInfoRef.value?.setSelectedGarden?.(garden);
+const handleClosePlotDetail = () => {
+    panelViewType.value = "risk";
 };
 
-const currentFarmVariety = ref(null);
-const varietyName = ref(null);
-const currentPhenologyStage = computed(() =>
-    currentFarmVariety.value == 1
-        ? t("growthReport.phenologyFruitExpansion")
-        : t("growthReport.phenologyLateTillering")
-);
-// 切换农场时,更新报告数据
-const changeGarden = async ({ id, name,farm_variety,variety_name }) => {
-    currentFarmVariety.value = farm_variety;
-    varietyName.value = variety_name;
-    if (!id) return;
-    currentFarmName.value = name;
-    if (sessionStorage.getItem('activeSwipeIndex')) {
-        currentIndex.value = Number(sessionStorage.getItem('activeSwipeIndex'));
-    } else {
-        currentIndex.value = 0;
-        swipeRef.value && swipeRef.value.swipeTo(0, { immediate: true });
-    }
-    paramsPage.value = {
-        ...(paramsPage.value || {}),
-        subjectId: id,
+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",
     };
-    // 初始化品种/大物候期转换
-    startInteractPopupRef.value.getPhenologyInitOrConfirmStatus();
-    await getSubjectData(id);
-    hasReport.value = true;
-    // await getRegions();
-};
-
-onActivated(() => {
-    if (!localeToggleEnabled) {
-        store.dispatch("locale/setLocale", "zh");
-    }
-    window.scrollTo(0, 0);
-    // 从新增农场页返回时,优先用缓存中的最新选中农场
-    const savedFarmId = localStorage.getItem("selectedFarmId");
-    selectedGardenId.value = savedFarmId ? Number(savedFarmId) : null;
-    gardenListRef.value?.refreshFarmList?.();
-
-    // 如果路由中带有 miniJson,并且其中有 showBind,则展示绑定成功弹窗
-    const { miniJson } = route.query || {};
-    if (miniJson) {
-        try {
-            const parsed = typeof miniJson === "string" ? JSON.parse(miniJson) : miniJson;
-            if (parsed && parsed.showBind) {
-                showBindSuccess.value = true;
-                // 处理完后清空路由中的 miniJson 参数,避免重复弹出
-                const newQuery = { ...(route.query || {}) };
-                delete newQuery.miniJson;
-                router.replace({ path: route.path, query: newQuery });
-            }
-        } catch (e) {
-            // miniJson 解析失败时忽略,不影响正常流程
-        }
-    }
-    // getResultReport();
 });
 
-const closeTabMask = () => {
-    showTabMask.value = false;
-};
-
-const userInfo = localStorage.getItem("localUserInfo");
-const userInfoObj = userInfo ? JSON.parse(userInfo) : {};
-
-const handleLockClick = () => {
-    if (currentFarmName.value) {
-        // router.push("/interaction?subjectId=" + localStorage.getItem("selectedFarmId"));
-        router.push(`/create_farm?from=growth_report&isReload=true`);
-        return;
-    }
-    if (userInfoObj?.tel) {
-        router.push(`/create_farm?from=growth_report&isReload=true`);
+const currentFarmName = ref("");
+const currentFarmVariety = ref(null);
+const mapContainer = ref(null);
+const growthReportMap = new GrowthReportMap();
+
+const syncMapByFarm = async (wkt) => {
+    const location = DEFAULT_FARM_POINT;
+    await nextTick();
+    if (!mapContainer.value) return;
+    if (growthReportMap.kmap) {
+        const coordinate = util.wktCastGeom(location).getFirstCoordinate();
+        growthReportMap.setMapPosition(coordinate);
         return;
     }
-    wx.miniProgram.navigateTo({
-        url: '/pages/subPages/phone_auth/index',
-    });
-}
-
-const handleAddFarm = () => {
-    router.push(`/create_farm?from=growth_report&isReload=true`);
-}
-
-const handleHistoryRiskReportClick = () => {
-    router.push("/history_risk_report?farmVariety=" + currentFarmVariety.value + "&currentFarmName=" + currentFarmName.value);
-}
-
-const todayPatrolFocus = ref([]);
-const pendingFarmWork = ref([]);
-const handlePendingFarmWorkClick = (card) => {
-    router.push({
-        path: "/work_detail",
-        query: {
-            miniJson: JSON.stringify({
-                paramsPage: JSON.stringify({
-                    farmId: paramsPage.value.farmId,
-                    farmWorkLibId: card?.farmWorkLibId,
-                    recordId: card?.recordId,
-                    typeId: regionsData.value[currentIndex.value].typeId
-                }),
-            }),
-        },
-    });
-}
-// 点击今日巡园重点
-const handleTodayPatrolFocusClick = (card) => {
-    if (!card.interactionTypeId) return;
-    router.push(`/interaction_list?farmId=${paramsPage.value.farmId}&regionId=${paramsPage.value.regionId}&interactionTypeId=${card.interactionTypeId}`);
-}
-
-const getTodayPatrolFocus = () => {
-    VE_API.report.todayPatrolFocus({ farmId: paramsPage.value.farmId }).then(({ data }) => {
-        todayPatrolFocus.value = data || [];
-    });
-}
-
-const getPendingFarmWork = () => {
-    VE_API.report.pendingFarmWork({ farmId: paramsPage.value.farmId, regionId: paramsPage.value.regionId }).then(({ data }) => {
-        pendingFarmWork.value = data || [];
-    });
-}
-
-const currentIndex = ref(0);
-const handleSwipeChange = (index) => {
-    currentIndex.value = index;
-    if (paramsPage.value.regionId !== regionsData.value[index].regionId) {
-        paramsPage.value = {
-            ...(paramsPage.value || {}),
-            farmId: regionsData.value[index].farmId,
-            regionId: regionsData.value[index].regionId,
-        };
-        getTodayPatrolFocus();
-        getPendingFarmWork();
-        getDetail();
-    }
-}
+    growthReportMap.initMap(location, mapContainer.value);
+};
 
-const getDetail = () => {
-    if (!paramsPage.value.farmId) return;
-    loading.value = true;
-    VE_API.report
-        .reproductiveReport({ farmId: paramsPage.value.farmId, regionId: paramsPage.value.regionId })
-        .then(({ data }) => {
-            workItems.value = data || [];
-        })
-        .finally(() => {
-            loading.value = false;
-        });
+const initGrowthReportMap = async () => {
+    await syncMapByFarm();
 };
 
-const subjectData = ref([])
-const typeTabsExpanded = ref(false);
-const visibleSubjectData = computed(() => {
-    if (typeTabsExpanded.value) {
-        return subjectData.value;
+const mapLegendItems = [
+    { key: "zone", label: "管理分区", pillClass: "map-legend__pill--zone" },
+    { key: "growth", label: "长势异常", pillClass: "map-legend__pill--growth" },
+    { 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(
+            `/history_risk_report?farmVariety=${currentFarmVariety.value ?? ""}&currentFarmName=${currentFarmName.value ?? ""}`
+        );
+        return;
     }
-    return subjectData.value.slice(0, 4);
-});
-const showSubjectToggle = computed(() => subjectData.value.length > 4);
-
-const getSubjectData = async (id) => {
-    const res = await VE_API.monitor.listFarmsBySubjectId({ subjectId: id });
-    // subjectData.value = res.data || [];
-    subjectData.value = [...res.data, ...res.data, ...res.data, ...res.data];
-    typeTabsExpanded.value = false;
-}
-
-const activeReportIndex = ref(0);
-const activeSubjectIndex = ref(0);
-const handleTypeTabClick = (item, index) => {
-    activeSubjectIndex.value = index;
-    paramsPage.value = {
-            ...(paramsPage.value || {}),
-            farmId: item.farmId,
-    };
-    getTodayPatrolFocus();
-}
-
-const regionsData = ref([]);
-const getRegions = async () => {
-    VE_API.monitor.listRegionsBySubjectId({
-        subjectId: paramsPage.value.subjectId,
-    }).then(({ data }) => {
-        console.log(data);
-        regionsData.value = data || [];
-        if (regionsData.value.length > 0) {
-            hasReport.value = true;
-            const hasShownTabGuide = localStorage.getItem(TAB_GUIDE_SHOWN_KEY) === "1";
-
-            // 首次进入且有分区数据:显示农场列表引导
-            if (!hasShownTabGuide) {
-                showTabMask.value = true;
-                localStorage.setItem(TAB_GUIDE_SHOWN_KEY, "1");
-            } else {
-                showTabMask.value = false;
-            }
-
-            // 切换农场tab回到当前农场tab
-            weatherInfoRef.value && weatherInfoRef.value.handleGardenClick('current');
-
-            // 如果不是点击农情报告已生成弹窗过来的,则显示农情互动弹窗
-            if (!route.query.hideInteraction) {
-                agriExecutePopupRef.value.showPopup(regionsData.value[currentIndex.value].farmId);
-            }
-
-            paramsPage.value = {
-                ...(paramsPage.value || {}),
-                farmId: regionsData.value[currentIndex.value].farmId,
-                regionId: regionsData.value[currentIndex.value].regionId,
-            };
-            getTodayPatrolFocus();
-            getPendingFarmWork();
-            getDetail();
-            // 如果是新增品种后跳转过来的,等待 Swipe 实例挂载后再定位
-            if (route.query.addVarietyCount) {
-                const targetIndex = Number(route.query.addVarietyCount);
-                if (!Number.isNaN(targetIndex) && targetIndex >= 0) {
-                    const safeIndex = Math.min(targetIndex, regionsData.value.length - 1);
-                    const reverseIndex = Math.min(
-                        regionsData.value.length - 1,
-                        Math.max(0, regionsData.value.length - safeIndex)
-                    );
-                    nextTick(() => {
-                        swipeRef.value?.swipeTo?.(reverseIndex, { immediate: true });
-                    });
-                }
-            }
+    plotDetail.value = buildPlotDetailByTab(item);
+    panelViewType.value = "plot";
+};
 
-            if (sessionStorage.getItem('activeSwipeIndex')) {
-                nextTick(() => {
-                    swipeRef.value?.swipeTo?.(currentIndex.value, { immediate: true });
-                });
-                sessionStorage.removeItem('activeSwipeIndex');
-            }
-        } else {
-            // 切换农场tab回到当前农场tab
-            // weatherInfoRef.value && weatherInfoRef.value.handleGardenClick('current');
-            showTabMask.value = false;
-            hasReport.value = false;
-        }
-    });
-}
+const handleFarmInfoMaintain = () => {
+    if (!selectedGardenId.value) return;
+    console.log(selectedGardenId.value);
+    // router.push(`/farm_info?subjectId=${selectedGardenId.value}`);
+    // router.push(`/farm_info?subjectId=766`);
+};
 
-/** 切换语言后,用新 lang 参数重新请求页面数据 */
-const reloadPageDataAfterLocaleChange = () => {
-    gardenListRef.value?.refreshFarmList?.();
+const weatherExpanded = (isExpandedValue) => {
+    isExpanded.value = isExpandedValue;
+};
 
-    const subjectId = paramsPage.value.subjectId || localStorage.getItem("selectedFarmId");
-    if (subjectId) {
-        getSubjectData(subjectId);
+const handleMaskClick = () => {
+    if (weatherInfoRef.value?.toggleExpand) {
+        weatherInfoRef.value.toggleExpand();
     }
+};
 
-    if (paramsPage.value.farmId) {
-        getTodayPatrolFocus();
-        getPendingFarmWork();
-        getDetail();
+const changeGardenTab = (tab) => {
+    activeGardenTab.value = tab;
+    if (tab !== "current") {
+        panelExpandProgress.value = 0;
+        panelViewType.value = "risk";
     }
-
-    startInteractPopupRef.value?.getPhenologyInitOrConfirmStatus?.();
 };
 
-const toggleLocale = async () => {
-    await dispatchToggleLocale();
-    reloadPageDataAfterLocaleChange();
+const handleGardenLoaded = ({ hasFarm }) => {
+    weatherInfoRef.value?.setGardenLoaded?.(hasFarm);
 };
 
-const onReportTitleClick = () => {
-    if (!localeToggleEnabled) return;
-    toggleLocale();
+const handleGardenSelected = (garden) => {
+    selectedGardenId.value = garden?.id ?? null;
+    syncMapByFarm(garden?.wkt);
+    weatherInfoRef.value?.setSelectedGarden?.(garden);
 };
 
-// 清理数据的函数
-const clearData = () => {
-    workItems.value = [];
-    paramsPage.value = {};
-    loading.value = false;
+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;
+    syncMapByFarm(data.wkt);
 };
 
-onDeactivated(() => {
-    sessionStorage.setItem('activeSwipeIndex', currentIndex.value);
-    clearData();
-});
-
-onUnmounted(() => {
-    clearData();
+onActivated(async () => {
+    if (route.query?.farmId) {
+        defaultGardenId.value = route.query.farmId;
+    }
+    const savedFarmId = localStorage.getItem("selectedFarmId");
+    selectedGardenId.value = savedFarmId ? Number(savedFarmId) : null;
+    gardenListRef.value?.refreshFarmList?.();
+    await initGrowthReportMap();
 });
 </script>
 
 <style lang="scss" scoped>
-.mask-wrap {
-    position: fixed;
-    bottom: 0;
-    left: 0;
-    width: 100%;
-    height: 300px;
-    background-color: rgba(0, 0, 0, 0.52);
-    z-index: 99999;
-}
-
-.achievement-report-page {
+.growth-report-page {
     width: 100%;
-    height: 100vh;
-    background: linear-gradient(195.35deg, #d4e4ff 16.34%, rgba(93, 189, 255, 0) 50.3%),
-        linear-gradient(156.64deg, rgba(255, 255, 255, 0.16) 27.7%, rgba(255, 255, 255, 0) 72.82%);
+    height: 100%;
+    background: #F5F7FB;
+    box-sizing: border-box;
 
     .weather-mask {
         position: fixed;
@@ -688,920 +241,126 @@ onUnmounted(() => {
         z-index: 11;
     }
 
-    .weather-info-wrap {
-        width: calc(100% - 20px);
+    .growth-report-header {
         position: absolute;
         z-index: 12;
         left: 10px;
-        top: 10px;
+        top: 12px;
+        width: calc(100% - 20px);
+        will-change: transform, opacity;
+        transform-origin: center top;
 
         .weather-info {
             width: 100%;
-        }
-
-        .invite-follow {
-            position: absolute;
-            right: -6px;
-            top: 50px;
-
-            .invite-content {
-                display: flex;
-                align-items: center;
-                gap: 4px;
-                font-size: 14px;
-                font-family: "PangMenZhengDao";
-                color: #fff;
-                line-height: 30px;
-                padding: 0 12px;
-                border-radius: 20px 0 0 20px;
-                height: 30px;
-                cursor: pointer;
-                background: #2199F8;
-                position: relative;
+            position: relative;
+            left: auto;
+            top: auto;
 
-                &::after {
-                    content: '';
-                    position: absolute;
-                    bottom: -6px;
-                    right: 0;
-                    width: 0px;
-                    height: 0px;
-                    border-right: 3px solid transparent;
-                    border-top: 3px solid #75a8cd;
-                    border-left: 3px solid #75a8cd;
-                    border-bottom: 3px solid transparent;
+            :deep(.garden-tabs) {
+                .garden-item.left-item.active .current-name {
+                    color: #2199F8;
+                    font-weight: 600;
                 }
             }
         }
-    }
 
-
-    .fake-report-wrap {
-        width: 100%;
-
-        .no-report-img {
+        .report-tabs {
             width: 100%;
-        }
-
-        .fake-img {
-            position: relative;
-
-            .fake-img-item {
-                width: 100%;
-            }
-        }
-    }
-
-    .report-content-wrap {
-        height: 100%;
-        // padding-bottom: 60px;
-        overflow: auto;
-        box-sizing: border-box;
-        position: relative;
-
-        .history-risk-report-btn {
-            position: absolute;
-            right: 0px;
-            // top: 155px;
-            top: 120px;
-            z-index: 13;
-            height: 26px;
-            padding: 0 10px 0 8px;
-            display: inline-flex;
+            display: flex;
             align-items: center;
             gap: 4px;
-            color: #ffffff;
-            font-size: 14px;
-            border-radius: 13px 0 0 13px;
-            background: linear-gradient(180deg, #60c2ff 0%, #2199f8 100%);
-            box-shadow: 0 2px 6px rgba(33, 153, 248, 0.3);
-            cursor: pointer;
+            padding: 10px 9px 6px;
+            background: #fff;
+            box-sizing: border-box;
 
-            .risk-report-icon {
-                width: 14px;
-                height: 14px;
+            .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;
-                background: #ffffff;
-                position: relative;
-                display: inline-flex;
-                align-items: center;
-                justify-content: center;
-                transform: rotate(-12deg);
-
-                i {
-                    width: 8px;
-                    height: 2px;
-                    border-radius: 2px;
-                    background: #42a7ff;
-                    box-shadow: 0 3px 0 #42a7ff;
-                }
-            }
-
-            .risk-report-text {
-                line-height: 1;
-                white-space: nowrap;
             }
         }
 
-        .bottom-btn {
-            z-index: 2;
-            position: fixed;
-            bottom: 0;
-            left: 0;
-            width: 100%;
-            background: #fff;
-            height: 60px;
+        .report-maintain-btn {
+            position: absolute;
+            right: 0;
+            bottom: 10px;
+            z-index: 13;
             display: flex;
             align-items: center;
-            justify-content: space-between;
-            padding: 0 12px;
+            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;
-            box-shadow: 2px 2px 4.5px 0px rgba(0, 0, 0, 0.4);
-
-            .btn-item {
-                height: 40px;
-                line-height: 40px;
-                padding: 0 24px;
-                border-radius: 20px;
-                font-size: 14px;
 
-                &.second {
-                    color: #666666;
-                    border: 1px solid rgba(153, 153, 153, 0.5);
-                }
-
-                &.primay {
-                    padding: 0 34px;
-                    background: linear-gradient(180deg, #76c3ff, #2199f8);
-                    color: #fff;
-                }
+            .report-maintain-icon {
+                width: 14px;
+                height: 14px;
+                filter: brightness(0) invert(1);
             }
         }
     }
 
-    .code-icon {
-        position: absolute;
-        right: 10px;
-        top: 12px;
-        width: 48px;
-    }
-
     .report-content {
-        // background: url("@/assets/img/home/report_bg.png") no-repeat center center;
-        // background: linear-gradient(0deg, #9BCCFF, #9BCCFF),
-        //     linear-gradient(160deg, rgba(255, 255, 255, 0.16) 30%, rgba(255, 255, 255, 0) 72%);
-        background: #abd4ff;
-
-        background-size: 100% auto;
-        background-position: top center;
-        padding: 0 10px 26px 10px;
-        box-sizing: border-box;
         position: relative;
+        height: 100%;
+        box-sizing: border-box;
 
-        &.has-report {
-            min-height: 100%;
-            background: linear-gradient(0deg, #9BCCFF, #9BCCFF),
-                linear-gradient(156.64deg, rgba(255, 255, 255, 0.16) 27.7%, rgba(255, 255, 255, 0) 72.82%);
-        }
-
-        .lock-bg {
-            position: absolute;
-            top: 230px;
-            left: 0;
-            width: 100%;
-            height: calc(100% - 230px);
-            background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.38) 50%, rgba(255, 255, 255, 0) 100%),
-                linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2));
-        }
-
-        .lock-img {
-            pointer-events: none;
-            position: fixed;
-            z-index: 10;
-            top: 50%;
-            left: 50%;
-            transform: translate(-50%, -20%);
-            width: 100%;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            flex-direction: column;
-            gap: 16px;
-
-            .lock-img-item {
-                width: 57px;
-            }
-
-            .has-click {
-                pointer-events: auto;
-            }
-
-            .lock-text {
-                font-size: 14px;
-                color: #000;
-                padding: 5px 64px;
-                line-height: 21px;
-                background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 50%, rgba(255, 255, 255, 0) 100%);
-            }
-
-            .lock-btn {
-                width: 140px;
-                height: 40px;
-                line-height: 40px;
-                text-align: center;
-                background: linear-gradient(180deg, #76C3FF 0%, #2199F8 100%);
-                border-radius: 25px;
-                color: #fff;
-                font-size: 16px;
-            }
-        }
-
-        .header-img {
+        .map-legend {
             position: absolute;
-            top: 0;
-            left: 0;
-            width: 100%;
-        }
-
-        .type-tabs {
-            background: rgba(255, 255, 255, 0.8);
+            top: 110px;
+            right: 10px;
+            z-index: 15;
             display: flex;
             align-items: center;
-            flex-wrap: wrap;
-            gap: 8px;
-            width: fit-content;
-            border-radius: 5px;
-            padding: 5px;
-            margin-bottom: 22px;
-            &.report-tabs {
-                padding: 0 5px;
-                height: 38px;
-                background: none;
-                margin-bottom: 6px;
-                .type-item {
-                    height: 34px;
-                    line-height: 34px;
-                    font-size: 14px;
-                    color: #858585;
-                }
-            }
-            .type-item {
-                height: 28px;
-                line-height: 28px;
-                text-align: center;
-                padding: 0 6px;
-                min-width: 80px;
-                color: #9A9A9A;
-                background: #FFFFFF;
-                box-sizing: border-box;
-                border-radius: 2px;
-                &.type-item-active {
-                    background: #2199F8;
-                    color: #fff;
-                }
-            }
-        }
-
-        .subject-toggle {
-            margin-top: 4px;
-            margin-bottom: 5px;
-            font-size: 14px;
-            color: rgba(0, 0, 0, 0.6);
-            width: 100%;
-            cursor: pointer;
-            text-align: center;
-        }
-
-        .report-header {
-            position: relative;
-            // padding-top: 148px;
-            padding-top: 120px;
-
-            &.no-farm {
-                padding-top: 102px;
-            }
-
-            .header-book {
-                position: absolute;
-                right: 0;
-                bottom: -6px;
-                height: 88px;
-                z-index: 10;
-            }
-
-            .time-tag {
-                background: #2199F8;
-                border-radius: 5px 0 5px 0;
-                height: 23px;
-                line-height: 23px;
-                font-size: 13px;
-                font-weight: 500;
-                color: #fff;
-                padding: 0 9px;
-                width: fit-content;
-                margin-bottom: 4px;
-            }
-
-            .report-title {
-                font-family: "PangMenZhengDao";
-                font-size: 34px;
-                line-height: 38px;
-
-                &.report-title-toggle {
-                    cursor: pointer;
-                    user-select: none;
-                }
-                color: #000000;
-            }
-
-            .report-info {
-                padding: 12px 0 28px 0;
-
-                &.pb-4 {
-                    padding-bottom: 4px;
-                }
-
-                .info-item {
-                    width: fit-content;
-                    display: flex;
-                    height: 33px;
-                    align-items: center;
-                    padding: 0 18px 0 6px;
-                    background: rgba(255, 255, 255, 0.58);
-                    backdrop-filter: blur(5px);
-                    border-radius: 20px;
-                    gap: 6px;
-
-                    .info-icon {
-                        width: 26px;
-                        height: 26px;
-                        object-fit: cover;
-                        border-radius: 50%;
-                    }
-
-                    .info-text {
-                        font-size: 14px;
-                        color: #000;
-                    }
-                }
-
-                .info-item+.info-item {
-                    margin-top: 5px;
-                }
-            }
-
-            // 左滑查看更多标签
-            .swipe-more-tag {
-                position: absolute;
-                bottom: 10px;
-                right: -16px;
-                box-sizing: border-box;
-                width: 36px;
-                height: 86px;
-                // padding: 0px 10px 2px 0;
-                background: rgba(0, 0, 0, 0.7);
-                border-radius: 10px 0 0 10px;
-                letter-spacing: 2px;
-                color: #ffffff;
-                font-size: 12px;
-                text-align: center;
-                line-height: 14px;
-                writing-mode: vertical-rl;
-                text-orientation: mixed;
-                padding-right: 5px;
-            }
-        }
-
-        .report-box {
-            display: flex;
-            align-items: center;
-            padding: 8px 12px;
-            // background: linear-gradient(0deg, #ffffff 86%, #2199f8 136%);
-            background: #fff;
-            border: 1px solid #ffffff;
-            border-radius: 8px;
-            gap: 5px;
-            position: relative;
-            &.warning-bg {
-                background: linear-gradient(0deg, #FFFFFF 86%, #FF9B48 136%);
-            }
+            gap: 10px;
+            padding: 4px 10px;
+            background: rgba(0, 0, 0, 0.46);
+            backdrop-filter: blur(4px);
+            border-radius: 4px;
+            box-sizing: border-box;
 
-            .report-box-item {
-                flex: 1;
-                background: rgba(33, 153, 248, 0.1);
-                border-radius: 8px;
-                min-height: 62px;
-                box-sizing: border-box;
-                padding: 2px 4px;
+            &__item {
                 display: flex;
-                flex-direction: column;
-                justify-content: center;
-
-                .item-content {
-                    color: #2199f8;
-                    font-size: 14px;
-                    text-align: center;
-                }
-
-                .item-title {
-                    color: #000000;
-                    font-size: 10px;
-                    text-align: center;
-                    padding-top: 5px;
-                }
-            }
-
-            .box-title {
-                position: absolute;
-                top: -8px;
-                left: -1px;
-                height: 32px;
-                line-height: 26px;
-                font-family: "PangMenZhengDao";
-                font-size: 14px;
-                padding: 0 10px;
-                color: #ffffff;
-                background: url("@/assets/img/home/title-bg.png") no-repeat center center / 100% 100%;
-
-                &.warning {
-                    background: url("@/assets/img/home/title-bg-warning.png") no-repeat center center / 100% 100%;
-                }
-            }
-
-            .w-100 {
-                width: 100%;
+                align-items: center;
+                gap: 5px;
             }
 
-            .box-text {
-                padding: 22px 0 8px 0;
-                font-weight: 350;
-                line-height: 21px;
-                width: 100%;
-                box-sizing: border-box;
-
-                .pre-text {
-                    white-space: pre-line;
-                    word-break: break-word;
-                }
-
-                .box-subtitle {
-                    color: #000;
-                }
-
-                .box-bg {
-                    font-weight: 400;
-                    color: rgba(0, 0, 0, 0.5);
-                    margin-bottom: 8px;
-                }
-
-                .types-info {
-                    background: rgba(33, 153, 248, 0.1);
-                    color: #000000;
-                    padding: 6px;
-                    border-radius: 5px;
-                    .text-bold {
-                        font-weight: bold;
-                    }
-                }
-                .tp-img {
-                    display: grid;
-                    grid-template-columns: repeat(3, 1fr);
-                    gap: 6px;
-                    width: 100%;
-                    margin-top: 8px;
-                    box-sizing: border-box;
-                    img {
-                        width: 100%;
-                        height: 78px;
-                        object-fit: contain;
-                    }
-                }
-                .text-link {
-                    color: #2199F8;
-                    text-decoration: underline;
-                }
-
-                .report-part {
-                    color: rgba(0, 0, 0, 0.5);
-                    .part-title {
-                        background: #2199F8;
-                        height: 24px;
-                        line-height: 24px;
-                        padding: 0 8px;
-                        color: #fff;
-                        border-radius: 2px;
-                        width: fit-content;
-                    }
-                    .part-text {
-                        padding-top: 6px;
-                        :deep(.text-bold) {
-                            font-weight: bold;
-                        }
-                    }
-                    .part-top {
-                        display: flex;
-                        align-items: center;
-                        justify-content: space-between;
-                        .part-link {
-                            display: inline-flex;
-                            align-items: center;
-                            gap: 4px;
-                            color: #2199F8;
-                            .part-link-icon {
-                                transform: rotate(270deg);
-                            }
-                        }
-                    }
-                }
-                .warning-part + .warning-part {
-                    margin-top: 8px;
-                }
-                .report-part + .report-part {
-                    margin-top: 8px;
-                }
-
-                .warning-part {
-                    background: rgba(178, 178, 178, 0.08);
-                    border-radius: 5px;
-                    padding: 11px 6px 6px;
-                    color: rgba(0, 0, 0, 0.5);
-                    width: 100%;
-                    box-sizing: border-box;
-                    .warning-title {
-                        display: flex;
-                        align-items: center;
-                        justify-content: center;
-                        gap: 10px;
-                        padding-bottom: 13px;
-                        .title-l {
-                            display: flex;
-                            align-items: center;
-                            .title-line {
-                                width: 68px;
-                                height: 1px;
-                                background: linear-gradient(90deg, rgba(118, 118, 118, 0) 0%, rgba(118, 118, 118, 0.4) 100%);
-                                &.title-line-right {
-                                    background: linear-gradient(270deg, rgba(118, 118, 118, 0) 0%, rgba(118, 118, 118, 0.4) 100%);
-                                }
-                            }
-                            .title-block {
-                                width: 6px;
-                                height: 6px;
-                                background: rgba(61, 61, 61, 0.2);
-                                transform: rotate(45deg);
-                            }
-                        }
-                    }
-                }
+            &__pill {
+                width: 16px;
+                height: 5px;
+                border-radius: 10px;
 
-                .box-advice {
-                    color: rgba(0, 0, 0, 0.5);
-                    padding-top: 10px;
+                &--zone {
+                    background: #13a27f;
                 }
 
-                .box-sum {
-                    margin-top: 10px;
-                    background: rgba(33, 153, 248, 0.1);
-                    border-radius: 5px;
-                    padding: 10px;
-                    line-height: 20px;
-                    color: #2199F8;
+                &--growth {
+                    background: #ff9138;
                 }
 
-                &.next-info {
-                    padding: 8px 0 8px 0;
+                &--pest {
+                    background: #e62e2d;
                 }
             }
 
-            .row {
-                display: grid;
-                grid-template-columns: repeat(3, 1fr);
-                gap: 6px;
-
-
-
-                .status-card {
-                    border-radius: 2px;
-                    padding: 7px 0;
-                    background: #ffffff;
-                    border: 0.5px solid #e5e6eb;
-                    color: #000;
-                    display: flex;
-                    flex-direction: column;
-                    align-items: center;
-                    justify-content: center;
-
-                    &.today-red {
-                        background: #FF6A6A;
-                        color: #fff;
-
-                        .status-sub {
-                            color: #fff;
-                        }
-                    }
-
-                    &.pending-card {
-                        color: #fff;
-                        position: relative;
-                        padding: 9px 0 7px 0;
-
-                        .tag-name {
-                            position: absolute;
-                            top: -8px;
-                            right: 0;
-                            background: #fff;
-                            color: #FF6A6A;
-                            font-size: 10px;
-                            height: 17px;
-                            line-height: 17px;
-                            padding: 0 3px;
-                            border-radius: 2px;
-                            box-sizing: border-box;
-                            border: 0.5px solid #FF6A6A;
-                        }
-                    }
-
-                    .status-badge {
-                        // position: absolute;
-                        // top: 0;
-                        // right: 0;
-                    }
-
-                    .status-title {
-                        font-size: 16px;
-                        line-height: 24px;
-
-                        &.status-title-small {
-                            font-size: 13px;
-                            line-height: 18px;
-                        }
-                    }
-
-                    .status-sub {
-                        font-size: 10px;
-                        color: rgba(32, 32, 32, 0.4);
-                        line-height: 15px;
-
-                        &.pending-sub {
-                            color: #fff;
-                            line-height: 13px;
-                        }
-                    }
-
-                    &.risk-strong {
-                        background: #FF6A6A;
-                        border-color: #FF6A6A;
-
-                        .status-title,
-                        .status-sub {
-                            color: #ffffff;
-                        }
-                    }
-
-                    &.danger {
-                        background: #FFE9E9;
-                        border-color: #ff8e8e;
-
-                        .status-sub {
-                            color: #FF6A6A;
-                        }
-                    }
-                }
-            }
-        }
-
-        .report-box+.report-box {
-            margin-top: 20px;
-        }
-
-        .report-excute {
-            position: relative;
-            margin-top: 12px;
-
-            .tag-label {
-                position: absolute;
-                top: 0;
-                left: 0;
-                padding: 4px 10px;
-                background: rgba(54, 52, 52, 0.8);
-                color: #fff;
-                font-size: 12px;
-                border-radius: 8px 0 8px 0;
-                z-index: 1;
-            }
-
-            ::v-deep {
-                .carousel-container .carousel-wrapper .carousel-img {
-                    min-width: calc(100vw - 32px);
-                    width: calc(100vw - 32px);
-                }
-            }
-        }
-    }
-
-    .download-btn {
-        position: fixed;
-        bottom: 20px;
-        left: 50%;
-        // background: #fff;
-        // box-shadow: 2px 2px 4.5px 0px #00000066;
-        // width: 100%;
-        transform: translateX(-50%);
-    }
-
-    .review-hide-box {
-        position: absolute;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        z-index: -1;
-        bottom: 0;
-    }
-
-    .review-image {
-        position: relative;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        gap: 8px;
-        margin: 12px;
-        background: #fff;
-        border-radius: 8px;
-
-        .review-mask {
-            z-index: 1;
-            pointer-events: none;
-            position: absolute;
-            left: 0;
-            top: 0;
-            width: 100%;
-            height: 100%;
-            border-radius: 8px;
-            background: linear-gradient(360deg,
-                    rgba(0, 0, 0, 0.78) 0%,
-                    rgba(0, 0, 0, 0.437208) 19.87%,
-                    rgba(0, 0, 0, 0) 33.99%);
-            display: flex;
-            flex-direction: column;
-            align-items: baseline;
-            justify-content: end;
-            padding: 12px;
-            box-sizing: border-box;
-            color: #fff;
-
-            .review-text {
-                font-family: "PangMenZhengDao";
-                font-size: 16px;
-                margin-bottom: 1px;
-            }
-
-            .review-content {
-                font-size: 10px;
-                line-height: 15px;
-            }
-        }
-
-        .vs-wrap {
-            position: absolute;
-            left: 50%;
-            top: 50%;
-            transform: translate(-50%, -50%);
-            width: 40px;
-            height: 40px;
-            z-index: 10;
-
-            img {
-                width: 100%;
-                height: 100%;
-                object-fit: cover;
-            }
-        }
-
-        .review-image-item {
-            position: relative;
-            flex: 1;
-
-            .review-image-item-title {
-                position: absolute;
-                top: 0;
-                left: 0;
-                background: rgba(54, 52, 52, 0.6);
-                padding: 4px 10px;
-                border-radius: 8px 0 8px 0;
-                backdrop-filter: 4px;
+            &__text {
                 font-size: 12px;
                 color: #fff;
             }
-
-            // .review-image-item-img {
-            //     width: 100%;
-            //     height: 250px;
-            //     object-fit: cover;
-            // }
-            .review-image-item-img {
-                width: 100%;
-                height: 100%;
-                object-fit: cover;
-                object-position: center;
-            }
-
-            .left-img {
-                border-radius: 8px 0 0 8px;
-            }
-
-            .right-img {
-                border-radius: 0 8px 8px 0;
-            }
         }
-    }
-}
-
-
-.cavans-popup {
-    width: 100%;
-    max-width: 100%;
-    max-height: 92vh;
-    background: none;
-    border-radius: 12px;
-    overflow: auto;
-    display: flex;
-    flex-direction: column;
-    backdrop-filter: 4px;
-
-    .cavans-content {
-        text-align: center;
-        padding: 0 12px;
-        height: fit-content;
-        overflow: auto;
 
-        .current-img {
+        .map-container {
             width: 100%;
-        }
-    }
-
-    // 底部操作按钮
-    .bottom-actions {
-        flex-shrink: 0;
-
-        .action-buttons {
-            padding: 12px 0 4px 0;
-            display: flex;
-            justify-content: space-around;
-
-            .action-btn {
-                display: flex;
-                flex-direction: column;
-                align-items: center;
-                cursor: pointer;
-
-                &.text-btn {
-                    font-size: 12px;
-                    color: rgba(255, 255, 255, 0.7);
-                }
-
-                .icon-circle {
-                    width: 48px;
-                    height: 48px;
-                    border-radius: 50%;
-                    display: flex;
-                    align-items: center;
-                    justify-content: center;
-                    color: #fff;
-                    margin-bottom: 4px;
-
-                    .el-icon {
-                        color: #fff;
-                    }
-
-                    img {
-                        width: 50px;
-                    }
-                }
-
-                &.blue-btn .icon-circle {
-                    background: #2199f8;
-                }
-
-                &.green-btn .icon-circle {
-                    background: #07c160;
-                }
-
-                &.orange-btn .icon-circle {
-                    background: #ff790b;
-                }
-
-                .btn-label {
-                    font-size: 12px;
-                    color: #fff;
-                }
-            }
-        }
-
-        .cancel-btn {
-            text-align: center;
-            font-size: 18px;
-            color: #fff;
-            cursor: pointer;
+            height: 100%;
         }
     }
 }