Explorar o código

feat:添加诊断报告页面

wangsisi hai 5 días
pai
achega
7c2400fcc7

BIN=BIN
src/assets/img/report/base-bg.png


BIN=BIN
src/assets/img/report/base-title.png


BIN=BIN
src/assets/img/report/p1.png


BIN=BIN
src/assets/img/report/p2.png


BIN=BIN
src/assets/img/report/p3.png


BIN=BIN
src/assets/img/report/star.png


+ 91 - 72
src/views/old_mini/agri_file/index.vue

@@ -81,15 +81,7 @@
                         </el-icon>
                         <span>{{ t("agriFile.addZone") }}</span>
                     </div> -->
-                    <div v-for="item in zoneList" :key="item.id" :ref="(el) => setMarkerEl(item.id, el)"
-                        class="zone-marker">
-                        <div class="zone-marker__label">{{ item.name }}</div>
-                        <div class="zone-marker__thumb">
-                            <img :src="item.cover" alt="" />
-                            <span class="zone-marker__badge">{{ item.count }}</span>
-                        </div>
-                        <div class="zone-marker__arrow"></div>
-                    </div>
+                    <!-- 地图标记由 Overlay 托管 DOM,勿用 Vue v-for 渲染,否则 KeepAlive 更新会报 __vnode -->
                 </div>
                 <div class="album-grid" v-if="!noFarmRange">
                     <!-- <div class="album-add" @click.stop="goAddZone('region')">
@@ -199,6 +191,7 @@ function openInspectSuccessPopup() {
     sessionStorage.removeItem(INSPECT_SUCCESS_KEY);
     showInspectSuccessPopup.value = true;
     fetchReportList();
+    fetchCropArchiveList();
 }
 
 function tryShowInspectSuccessPopup() {
@@ -223,7 +216,6 @@ const DEFAULT_CENTER = [113.6142086995688, 23.585836479509055];
 const activeNav = ref("patrol");
 const mapContainer = ref(null);
 const fileMap = new FileMap();
-const markerEls = {};
 const mapOverlays = [];
 
 const noFarmRange = ref(false);
@@ -619,11 +611,6 @@ async function fetchCropArchiveList() {
     }
 }
 
-const setMarkerEl = (id, el) => {
-    if (el) markerEls[id] = el;
-    else delete markerEls[id];
-};
-
 const getFarmCenter = () => {
     const entryCoordinate = readEntryFarmCoordinate();
     if (entryCoordinate) return entryCoordinate;
@@ -639,12 +626,42 @@ const getFarmCenter = () => {
     return DEFAULT_CENTER;
 };
 
+/** 手动创建标记节点,避免 OL Overlay 挪走 Vue vnode 对应的 DOM */
+function createZoneMarkerEl(zone) {
+    const root = document.createElement("div");
+    root.className = "zone-marker";
+
+    const label = document.createElement("div");
+    label.className = "zone-marker__label";
+    label.textContent = zone.name || "";
+
+    const thumb = document.createElement("div");
+    thumb.className = "zone-marker__thumb";
+    const img = document.createElement("img");
+    img.src = zone.cover || "";
+    img.alt = "";
+    const badge = document.createElement("span");
+    badge.className = "zone-marker__badge";
+    badge.textContent = String(zone.count ?? 0);
+    thumb.append(img, badge);
+
+    const arrow = document.createElement("div");
+    arrow.className = "zone-marker__arrow";
+
+    root.append(label, thumb, arrow);
+    return root;
+}
+
 const clearMapOverlays = () => {
     if (!fileMap.kmap?.map) {
         mapOverlays.length = 0;
         return;
     }
-    mapOverlays.forEach((overlay) => fileMap.kmap.map.removeOverlay(overlay));
+    mapOverlays.forEach((overlay) => {
+        fileMap.kmap.map.removeOverlay(overlay);
+        const el = overlay.getElement?.();
+        el?.remove?.();
+    });
     mapOverlays.length = 0;
 };
 
@@ -652,10 +669,9 @@ const bindZoneOverlays = () => {
     if (!fileMap.kmap?.map) return;
     clearMapOverlays();
     zoneList.value.forEach((zone) => {
-        const el = markerEls[zone.id];
-        if (!el || zone.longitude == null || zone.latitude == null) return;
+        if (zone.longitude == null || zone.latitude == null) return;
         const overlay = new Overlay({
-            element: el,
+            element: createZoneMarkerEl(zone),
             position: [zone.longitude, zone.latitude],
             positioning: "bottom-center",
             stopEvent: false,
@@ -1131,59 +1147,6 @@ watch(activeNav, (key) => {
             }
         }
 
-        .zone-marker {
-            display: flex;
-            flex-direction: column;
-            align-items: center;
-            pointer-events: none;
-
-            &__label {
-                padding: 3px 14px;
-                border-radius: 25px;
-                background: #fff;
-                color: #0A0A0A;
-                font-size: 12px;
-            }
-
-            &__thumb {
-                position: relative;
-                width: 42px;
-                height: 42px;
-                margin-top: 8px;
-
-                img {
-                    width: 100%;
-                    height: 100%;
-                    object-fit: cover;
-                    border: 2px solid #fff;
-                    border-radius: 6px;
-                    box-sizing: border-box;
-                }
-            }
-
-            &__badge {
-                position: absolute;
-                top: -6px;
-                right: -6px;
-                min-width: 18px;
-                height: 18px;
-                line-height: 16px;
-                border-radius: 50%;
-                background: #fff;
-                color: #0A0A0A;
-                font-size: 12px;
-                text-align: center;
-            }
-
-            &__arrow {
-                width: 0;
-                height: 0;
-                border-left: 5px solid transparent;
-                border-right: 5px solid transparent;
-                border-top: 6px solid #fff;
-            }
-        }
-
         .add-zone-btn {
             position: absolute;
             right: 5px;
@@ -1526,3 +1489,59 @@ watch(activeNav, (key) => {
     }
 }
 </style>
+
+<!-- Overlay 移出 Vue 树后无 scoped attr,标记样式需非 scoped -->
+<style lang="scss">
+.zone-marker {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    pointer-events: none;
+
+    &__label {
+        padding: 3px 14px;
+        border-radius: 25px;
+        background: #fff;
+        color: #0a0a0a;
+        font-size: 12px;
+    }
+
+    &__thumb {
+        position: relative;
+        width: 42px;
+        height: 42px;
+        margin-top: 8px;
+
+        img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+            border: 2px solid #fff;
+            border-radius: 6px;
+            box-sizing: border-box;
+        }
+    }
+
+    &__badge {
+        position: absolute;
+        top: -6px;
+        right: -6px;
+        min-width: 18px;
+        height: 18px;
+        line-height: 16px;
+        border-radius: 50%;
+        background: #fff;
+        color: #0a0a0a;
+        font-size: 12px;
+        text-align: center;
+    }
+
+    &__arrow {
+        width: 0;
+        height: 0;
+        border-left: 5px solid transparent;
+        border-right: 5px solid transparent;
+        border-top: 6px solid #fff;
+    }
+}
+</style>

+ 529 - 373
src/views/old_mini/agri_file/pages/diagnosisReport.vue

@@ -1,281 +1,333 @@
 <template>
     <div class="diagnosis-report-page">
-        <!-- <custom-header :name="t('agriFile.initialReport')" :isGoBack="true" @goback="handleBack" /> -->
-
+        <div class="report-header">
+            <div class="report-header__title">
+                种植报告
+                <img src="@/assets/img/report/star.png" alt="" class="report-header__star">
+            </div>
+            <div class="report-header__desc">梅县区雁洋镇南福村的飞鸟智慧农业</div>
+            <div class="report-header__desc">梅州柚子基地</div>
+        </div>
         <div v-if="loading" class="diagnosis-report-status">加载中...</div>
         <div v-else-if="!reportData" class="diagnosis-report-status">暂无报告数据</div>
 
-        <div v-else class="diagnosis-report-body">
-            <div class="report-hero">
-                <div class="report-hero__title">{{ reportData.title }}</div>
-                <div class="report-hero__desc">{{ reportData.intro }}</div>
+        <div v-else class="report-main">
+            <div class="base-bg">
+                <div class="base-title">
+                    <img src="@/assets/img/report/base-title.png" alt="" class="base-title__icon">
+                    <span class="base-title__text">基本信息</span>
+                </div>
+                <div class="base-content">
+                    <div class="base-content__row">
+                        <span class="base-content__label">种植作物:</span>
+                        <span class="base-content__value">柚子—金柚</span>
+                    </div>
+                    <div class="base-content__row">
+                        <span class="base-content__label">品种特质:</span>
+                        <span class="base-content__value">晚熟型特色柚类品种,生育周期长、品质形成期较长,能够充分利用成熟前的光温条件,因此对膨果期水分管理和成熟期气候调控要求较高。</span>
+                    </div>
+                    <div class="base-content__row">
+                        <span class="base-content__label">物候信息:</span>
+                        <span class="base-content__value">当前处于果实膨大后期至成熟前期,预计11月上中旬采收。</span>
+                    </div>
+                    <div class="base-content__row">
+                        <span class="base-content__label">土壤条件:</span>
+                        <span class="base-content__value">土壤类型为赤红壤,表层(0-30 cm)黏粒含量约35%,属于偏黏质地;土壤pH约5.2,有机碳含量约12 g/kg,总氮含量约0.8 g/kg。根据土壤养分评价,该地块氮素供应处于中等偏低水平。</span>
+                    </div>
+                    <div class="base-content__row">
+                        <span class="base-content__label">地形条件:</span>
+                        <span class="base-content__value">丘陵缓坡,海拔约150 m,坡度约8°,东南坡。</span>
+                    </div>
+                </div>
+            </div>
+            <!-- 锚点导航 -->
+            <div class="report-tabs">
+                <div
+                    v-for="tab in tabs"
+                    :key="tab.key"
+                    class="report-tabs__item"
+                    :class="{ active: activeTab === tab.key }"
+                    @click="scrollToSection(tab.key)"
+                >
+                    <svg class="report-tabs__icon" viewBox="0 0 24 24" fill="none">
+                        <path
+                            v-for="(d, i) in tab.paths"
+                            :key="i"
+                            :d="d"
+                            stroke="currentColor"
+                            stroke-width="1.6"
+                            stroke-linecap="round"
+                            stroke-linejoin="round"
+                        />
+                    </svg>
+                    <span>{{ tab.label }}</span>
+                    <i v-if="activeTab === tab.key" class="report-tabs__arrow"></i>
+                </div>
             </div>
 
             <div
-                v-for="(section, sIdx) in reportData.sections"
-                :key="sIdx"
-                class="report-section"
+                v-for="tab in tabs"
+                :id="`report-section-${tab.key}`"
+                :key="tab.key"
+                class="report-panel"
             >
-                <div class="report-section__title">{{ section.displayTitle || section.title }}</div>
-
-                <!-- 基本情况与种植价值:灰色卡片(无 ### 分组) -->
-                <template v-if="section.type === 'basic'">
-                    <div v-for="card in section.cards" :key="card.title" class="info-card">
-                        <div class="info-card__title">{{ card.title }}</div>
-                        <div v-for="(para, idx) in card.paragraphs" :key="idx" class="info-card__text">
-                            {{ para }}
-                        </div>
+                <div class="report-panel__title">
+                    <span>{{ tab.label }}</span>
+                </div>
+
+                <div
+                    v-for="(section, sIdx) in SECTION_MAP[tab.key]"
+                    :key="sIdx"
+                    class="report-block"
+                >
+                    <div v-if="section.title" class="report-block__divider">
+                        <span>{{ section.title }}</span>
                     </div>
-                </template>
 
-                <!-- 气象风险 / 肥料 / 病虫害:分组 + 飞鸟建议 -->
-                <template v-else>
-                    <div v-if="section.intro" class="info-card__text mb-10">{{ section.intro }}</div>
+                    <!-- 导语在图表前(品质潜力) -->
+                    <p
+                        v-if="section.intro && section.introFirst"
+                        class="report-block__intro"
+                    >{{ section.intro }}</p>
+
+                    <!-- 图表占位(后续替换图片) -->
+                    <div v-if="section.showChart && !section.chartAfter" class="report-block__chart">
+                        <img
+                            class="report-block__chart-img"
+                            :class="{ 'report-block__chart-img--natural': section.chartNatural }"
+                            :src="section.chartSrc || ''"
+                            alt=""
+                        />
+                    </div>
 
-                    <div v-for="(group, gIdx) in section.groups" :key="gIdx" class="risk-group">
-                        <div class="section-divider">
-                            <span>{{ group.title }}</span>
-                        </div>
-                        <div class="info-card fertilizer-card">
-                            <div v-for="item in group.items" :key="item.tag" class="tag-block">
-                                <span class="tag-block__tag">{{ item.tag }}</span>
-                                <div class="tag-block__text">{{ item.content }}</div>
-                            </div>
-                        </div>
+                    <p v-if="section.note" class="report-block__note">{{ section.note }}</p>
+
+                    <!-- 导语在图表后(气象风险) -->
+                    <p
+                        v-if="section.intro && !section.introFirst"
+                        class="report-block__intro"
+                    >{{ section.intro }}</p>
+
+                    <!-- 段落型 -->
+                    <div
+                        v-if="section.type === 'text'"
+                        class="report-block__card"
+                    >
+                        <p class="report-block__text">{{ section.content }}</p>
                     </div>
 
-                    <div v-if="section.suggest" class="suggest-card">
-                        <div class="suggest-card__title">{{ section.suggest.title }}</div>
+                    <!-- 条目型 -->
+                    <template v-else-if="section.type === 'list'">
                         <div
-                            v-for="row in section.suggest.rows"
-                            :key="row.label"
-                            class="suggest-card__row"
+                            v-for="(item, iIdx) in section.items"
+                            :key="iIdx"
+                            class="report-block__card"
+                            :class="{
+                                'report-block__card--plain': section.plain,
+                                'report-block__card--blue': section.cardTone === 'blue',
+                            }"
                         >
-                            <span class="suggest-card__label">{{ row.label }}</span>
-                            <span>{{ row.content }}</span>
+                            <div class="report-block__item-title">{{ item.title }}</div>
+                            <p class="report-block__text">{{ item.content }}</p>
                         </div>
+                    </template>
+
+                    <!-- 图表在内容后(农事管理) -->
+                    <div v-if="section.showChart && section.chartAfter" class="report-block__chart report-block__chart--after">
+                        <img
+                            class="report-block__chart-img"
+                            :src="section.chartSrc || ''"
+                            alt=""
+                        />
                     </div>
-                </template>
+                </div>
             </div>
         </div>
 
-        <!-- <div v-if="reportData" class="share-btn" @click="handleShare">{{ t("agriFile.forwardReport") }}</div> -->
+        <!-- <div v-if="reportData" class="share-btn" @click="handleShare">转发报告</div> -->
     </div>
 </template>
 
 <script setup>
 import { onMounted, ref } from "vue";
-import { useRouter, useRoute } from "vue-router";
+import { useRoute } from "vue-router";
 import { ElMessage } from "element-plus";
-import customHeader from "@/components/customHeader.vue";
-import { useI18n } from "@/i18n";
 import wx from "weixin-js-sdk";
 
-const { t } = useI18n();
-const router = useRouter();
-const route = useRoute();
-
-const loading = ref(false);
-const reportData = ref(null);
-
 // ---------------------------------------------------------------------------
-// Markdown 解析:将接口 report 文本转成页面结构
+// 常量
 // ---------------------------------------------------------------------------
+const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
 
-/** 去掉 markdown 标题前缀,保留书名号 */
-const cleanTitle = (text = "") =>
-    String(text)
-        .replace(/^#+\s*/, "")
-        .trim();
-
-/** 去掉「一、」「二、」等章节序号,用于展示 */
-const stripChapterNo = (title = "") =>
-    String(title)
-        .replace(/^[一二三四五六七八九十百]+[、..]\s*/, "")
-        .trim();
-
-/** 是否为「基本情况与种植价值」章节(结构与其它章节不同:仅有一级列表卡片) */
-const isBasicSectionTitle = (title = "") => /基本情况|种植价值/.test(title);
-
-/** 解析 `- 标签:内容` / `- 标签:内容` */
-const parseLabeledItem = (line) => {
-    const text = String(line).replace(/^[-*•]\s+/, "").trim();
-    const match = text.match(/^([^::]+)[::]\s*(.+)$/);
-    if (!match) return null;
-    return {
-        label: match[1].trim(),
-        content: match[2].trim(),
-    };
+const TABS = [
+    {
+        key: "weather",
+        label: "气象风险",
+        paths: ["M13 2L4 14h7l-1 8 9-12h-7l1-8z"],
+    },
+    {
+        key: "quality",
+        label: "品质潜力",
+        paths: [
+            "M12 21c0-6 4-10 9-11-1 6-5 10-9 11z",
+            "M12 21c0-6-4-10-9-11 1 6 5 10 9 11z",
+            "M12 21V10",
+        ],
+    },
+    {
+        key: "input",
+        label: "必要投入",
+        paths: [
+            "M7 3h7l4 4v14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z",
+            "M14 3v4h4",
+            "M9 13h6",
+            "M9 17h6",
+        ],
+    },
+    {
+        key: "manage",
+        label: "农事管理",
+        paths: [
+            "M6 4h12a1 1 0 0 1 1 1v15H5V5a1 1 0 0 1 1-1z",
+            "M9 2v3",
+            "M15 2v3",
+            "M5 9h14",
+            "M9 13h.01",
+            "M12 13h.01",
+            "M15 13h.01",
+            "M9 17h.01",
+            "M12 17h.01",
+        ],
+    },
+];
+
+/** 各模块演示内容(后续接接口解析结果) */
+const SECTION_MAP = {
+    weather: [
+        {
+            title: "气候条件",
+            type: "text",
+            content:
+                "梅州属亚热带季风气候,年平均气温约21~23℃,年降雨量约1470~1800 mm,无霜期300天以上,光热水资源总体充足,能够较好满足金柚生长需求(适宜年均温约18~21℃,≥10℃积温约6000℃以上,年降雨量约1500 mm左右或以上)。",
+        },
+        {
+            title: "主要气象风险",
+            type: "list",
+            showChart: true,
+            chartSrc: require("@/assets/img/report/p1.png"),
+            intro: "通过统计该点位近10年历史气象数据,得到各阶段主要气象风险构成(如图)。结果显示,该点位主要气象风险集中在以下关键阶段:",
+            items: [
+                {
+                    title: "春梢—开花期低温冷害和阴雨寡照,影响授粉坐果稳定性。",
+                    content:
+                        "该阶段低温冷害和阴雨寡照风险占比分别约为30%和34%。低温冷害可能降低花粉活性,阴雨寡照会削弱光合作用,共同影响花器发育和坐果形成。",
+                },
+                {
+                    title: "坐果—幼果期暴雨涝渍和阴雨涝渍,影响幼果正常发育。",
+                    content:
+                        "该阶段暴雨涝渍和阴雨涝渍风险占比分别约为32%和28%。持续降雨容易导致根区水分过多、土壤通气下降,影响根系吸收和幼果稳定生长。",
+                },
+                {
+                    title: "果实膨大期高温干旱和旱涝急转,影响果实膨大与水分平衡。",
+                    content:
+                        "该阶段高温干旱、暴雨涝渍和旱涝急转风险分别为约34%、24%和18%。高温干旱易造成树体水分胁迫,旱涝急转则会引起果实快速吸水膨胀,增加裂果风险。",
+                },
+                {
+                    title: "转色—成熟期台风大风和连续降雨,影响果实品质形成。",
+                    content:
+                        "该阶段台风大风、连续降雨和暴雨涝渍风险分别为约28%、26%和20%。台风大风易造成落果和枝条损伤,连续降雨会降低光合积累,影响糖分形成和品质稳定。",
+                },
+            ],
+        },
+    ],
+    quality: [
+        {
+            type: "list",
+            introFirst: true,
+            intro: "围绕金柚品质形成过程,基于“生长环境—光合积累—果实发育—成熟品质”四个维度分析该点位品质形成优势及限制因素,结果如图:",
+            showChart: true,
+            chartNatural: true,
+            chartSrc: require("@/assets/img/report/p2.png"),
+            note: "注:橙色表示高于平均水平,蓝色表示低于平均水平;单元格内上方为偏离幅度,下方为原始值(点位/平均水平)",
+            items: [
+                {
+                    title: "生长环境条件总体适宜,为金柚生长提供基础保障。",
+                    content:
+                        "重点关注温度和水分条件。该点位秋梢生长期、春梢—开花期和果实膨大期平均气温分别为28.5℃、17.8℃和27.3℃,与梅州平均水平接近;对应累计降水量分别为420 mm、260 mm和680 mm,整体满足金柚生长和果实发育需求。",
+                },
+                {
+                    title: "光合积累条件较好,但秋梢期光照略显不足。",
+                    content:
+                        "重点关注日照条件。春梢—开花期累计日照时数为210 h,高于梅州平均(140 h),有利于树体营养积累和后续果实品质形成;但秋梢期日照时数为510 h,低于平均水平(720 h),可能影响秋梢营养储备。",
+                },
+                {
+                    title: "果实发育期温差条件优势明显,有利于果实膨大。",
+                    content:
+                        "重点关注膨果期温度、水分和高温胁迫。果实膨大期平均昼夜温差为9.1℃,高于梅州平均(7.5℃),较大的昼夜温差有利于糖分积累和干物质形成;同时高温日数为22天,低于平均水平(35天),可降低高温对果实发育的不利影响。",
+                },
+                {
+                    title: "成熟期具备较好的品质形成条件,但降雨偏多限制品质稳定。",
+                    content:
+                        "重点关注转色—成熟期光照、温差和水分条件。该阶段平均昼夜温差为9.8℃,累计日照时数为480 h,均高于梅州平均(8.8℃、450 h),有利于糖分积累和风味形成;但累计降水量达到150 mm,高于平均水平(88 mm),成熟期水分偏多可能增加裂果风险,并影响品质稳定。",
+                },
+            ],
+        },
+    ],
+    input: [
+        {
+            type: "list",
+            cardTone: "blue",
+            items: [
+                {
+                    title: "必须投入",
+                    content:
+                        "排灌设施:当前处于果实膨大后期至成熟前期,果实对水分稳定性要求较高。该点位土壤偏黏,雨后排水压力较大,建议配置蓄水、滴灌/微喷及排水沟,实现旱时补水、雨时排涝。",
+                },
+                {
+                    title: "建议投入",
+                    content:
+                        "防风防灾设施:该点位位于丘陵缓坡区域,成熟前期果实负载增加,强风和强降雨可能造成落果、枝条损伤及坡面冲刷。建议加强树体支撑、防风加固和坡面排水措施。",
+                },
+                {
+                    title: "优化投入",
+                    content:
+                        "水肥一体化与墒情监测:土壤保肥保水能力一般,且养分供应存在不足风险。建议配置墒情监测和水肥一体化设备,根据果实膨大和成熟需求精准调控水肥,提高果实品质和投入效率。",
+                },
+            ],
+        },
+    ],
+    manage: [
+        {
+            type: "text",
+            content: "结合金柚生长发育特点,将风险防控措施与关键农事目标相结合,形成全年动态管理方案。",
+            showChart: true,
+            chartAfter: true,
+            chartSrc: require("@/assets/img/report/p3.png"),
+        },
+    ],
 };
 
-/** 判断是否为「飞鸟建议」小节 */
-const isSuggestHeading = (title) => /飞鸟建议/.test(title || "");
-
-/**
- * 将 markdown 报告解析为 { title, intro, sections }
- * - basic:基本情况与种植价值(- 标签:内容 → 灰色卡片)
- * - risk:含 ### 分组 + 可选飞鸟建议
- */
-const parseReportMarkdown = (markdown) => {
-    const lines = String(markdown || "")
-        .replace(/\r\n/g, "\n")
-        .split("\n")
-        .map((line) => line.trimEnd());
-
-    let title = "";
-    const introLines = [];
-    const sections = [];
-    let currentSection = null;
-    let currentGroup = null;
-    let inIntro = true;
-
-    const createSection = (rawTitle) => {
-        const fullTitle = cleanTitle(rawTitle);
-        const basic = isBasicSectionTitle(fullTitle);
-        return {
-            title: fullTitle,
-            displayTitle: stripChapterNo(fullTitle),
-            type: basic ? "basic" : "risk",
-            intro: "",
-            cards: [],
-            groups: [],
-            suggest: null,
-            _introLines: [],
-        };
-    };
-
-    const flushGroup = () => {
-        if (!currentSection || !currentGroup) return;
-        // 基本情况章节不应出现 ###,若误入则并入卡片
-        if (currentSection.type === "basic") {
-            currentGroup.items.forEach((item) => {
-                currentSection.cards.push({
-                    title: item.tag,
-                    paragraphs: [item.content],
-                });
-            });
-            currentGroup = null;
-            return;
-        }
-        if (isSuggestHeading(currentGroup.title)) {
-            currentSection.suggest = {
-                title: currentGroup.title,
-                rows: currentGroup.items.map((item) => ({
-                    label: `${item.tag}:`,
-                    content: item.content,
-                })),
-            };
-        } else {
-            currentSection.groups.push({
-                title: currentGroup.title,
-                items: currentGroup.items,
-            });
-        }
-        currentGroup = null;
-    };
-
-    for (const raw of lines) {
-        const line = raw.trim();
-        if (!line) continue;
-
-        if (/^#\s+/.test(line) && !/^##/.test(line)) {
-            title = cleanTitle(line);
-            continue;
-        }
-
-        if (/^##\s+/.test(line)) {
-            flushGroup();
-            inIntro = false;
-            currentSection = createSection(line);
-            sections.push(currentSection);
-            continue;
-        }
-
-        if (/^###\s+/.test(line)) {
-            flushGroup();
-            if (!currentSection) {
-                currentSection = createSection("");
-                sections.push(currentSection);
-            }
-            // 基本情况章节忽略 ###,当作普通内容跳过标题行
-            if (currentSection.type === "basic") {
-                currentGroup = null;
-                continue;
-            }
-            currentGroup = {
-                title: cleanTitle(line).replace(/^\d+\.\s*/, ""),
-                items: [],
-            };
-            continue;
-        }
-
-        if (/^[-*•]\s+/.test(line)) {
-            const item = parseLabeledItem(line);
-            if (!item) continue;
-            if (!currentSection) {
-                currentSection = createSection("");
-                sections.push(currentSection);
-            }
-
-            // 基本情况:一律进灰色卡片
-            if (currentSection.type === "basic") {
-                const exist = currentSection.cards.find((c) => c.title === item.label);
-                if (exist) {
-                    exist.paragraphs.push(item.content);
-                } else {
-                    currentSection.cards.push({
-                        title: item.label,
-                        paragraphs: [item.content],
-                    });
-                }
-                continue;
-            }
-
-            if (currentGroup) {
-                currentGroup.items.push({
-                    tag: item.label,
-                    content: item.content,
-                });
-            } else {
-                // risk 章节若列表在 ### 之前,先挂到临时卡片,一般不会出现
-                currentSection.cards.push({
-                    title: item.label,
-                    paragraphs: [item.content],
-                });
-            }
-            continue;
-        }
-
-        if (inIntro && !currentSection) {
-            introLines.push(line);
-        } else if (currentSection && !currentGroup) {
-            currentSection._introLines.push(line);
-        }
-    }
-
-    flushGroup();
+// ---------------------------------------------------------------------------
+// 页面状态
+// ---------------------------------------------------------------------------
+const route = useRoute();
+const loading = ref(false);
+const reportData = ref(null);
+const activeTab = ref("weather");
+const tabs = TABS;
 
-    sections.forEach((section) => {
-        section.intro = (section._introLines || []).join("");
-        delete section._introLines;
-        // 再次按标题校正类型,避免误判
-        if (isBasicSectionTitle(section.title)) {
-            section.type = "basic";
-        } else if (section.groups.length || section.suggest) {
-            section.type = "risk";
-        }
-    });
+const entryFarmData = JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
 
-    return {
-        title: title || "初始种植诊断报告",
-        intro: introLines.join(""),
-        sections,
-    };
+// ---------------------------------------------------------------------------
+// 锚点定位
+// ---------------------------------------------------------------------------
+const scrollToSection = (key) => {
+    activeTab.value = key;
+    const el = document.getElementById(`report-section-${key}`);
+    if (!el) return;
+    el.scrollIntoView({ behavior: "smooth", block: "start" });
 };
 
-const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
-const entryFarmData = JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
-
 // ---------------------------------------------------------------------------
 // 接口
 // ---------------------------------------------------------------------------
@@ -287,7 +339,7 @@ const fetchReport = async () => {
             tel: entryFarmData.phone,
         });
         if (res?.code === 200 && res.data?.report) {
-            reportData.value = parseReportMarkdown(res.data.report);
+            reportData.value = res.data.report;
         } else {
             ElMessage.error(res?.msg || "获取诊断报告失败");
         }
@@ -298,14 +350,10 @@ const fetchReport = async () => {
     }
 };
 
-const handleBack = () => {
-    router.back();
-};
-
 const handleShare = () => {
     const query = {
         askInfo: { title: "转发报告", content: "是否分享给好友" },
-        shareText: reportData.value?.title || "",
+        shareText: "",
         targetUrl: "diagnosis_report",
         paramsPage: JSON.stringify({
             id: route.query.id,
@@ -326,10 +374,41 @@ onMounted(() => {
 
 <style lang="scss" scoped>
 .diagnosis-report-page {
-    min-height: 100vh;
+    height: 100vh;
+    overflow-y: auto;
+    -webkit-overflow-scrolling: touch;
     background: #f5f6f8;
     box-sizing: border-box;
-    padding-bottom: 90px;
+    padding-bottom: 60px;
+
+    .report-header {
+        width: 100%;
+        height: 148px;
+        box-sizing: border-box;
+        padding: 36px 20px 0;
+        background: url("@/assets/img/report/title-bg.png") no-repeat center center;
+        background-size: 100% 100%;
+        color: #fff;
+
+        &__title {
+            position: relative;
+            font-size: 35px;
+            font-family: 'pangmenzhengdao';
+        }
+
+        &__star {
+            width: 23px;
+            height: 23px;
+            margin-top: -8px;
+        }
+
+        &__desc {
+            font-size: 13px;
+            line-height: 20px;
+            font-weight: 400;
+            opacity: 0.95;
+        }
+    }
 }
 
 .diagnosis-report-status {
@@ -339,176 +418,253 @@ onMounted(() => {
     color: #86909c;
 }
 
-.diagnosis-report-body {
-    padding: 10px 10px 20px;
-    max-height: calc(100vh - 40px);
-    overflow: auto;
-    box-sizing: border-box;
-}
-
-.report-hero {
-    padding: 0 0 10px;
-    text-align: center;
+.report-main {
+    padding: 0 12px;
+    position: relative;
+    z-index: 1;
+
+    .base-bg {
+        width: 100%;
+        height: 335px;
+        margin: 12px 0;
+        padding: 5px;
+        box-sizing: border-box;
+        background: url("@/assets/img/report/base-bg.png") no-repeat center center /100% 100%;
+        .base-title {
+            display: flex;
+            gap: 5px;
+            .base-title__icon {
+                width: 44px;
+                height: 44px;
+            }
+            .base-title__text {
+                font-size: 18px;
+                font-weight: 500;
+                color: #fff;
+                margin-top: 5px;
+            }
+        }
+        .base-content {
+            background: linear-gradient(180deg, rgba(255, 255, 255, 0.79) 0%, #FFFFFF 11.7%, #FFFFFF 100%);
+            border-radius: 10px;
+            border: 1px solid #FFFFFF;
+            padding: 12px 14px;
+            margin-top: -10px;
+            font-size: 13px;
+            line-height: 22px;
+
+            &__row {
+                & + & {
+                    margin-top: 8px;
+                }
+            }
 
-    &__title {
-        font-size: 16px;
-        font-weight: bold;
-        line-height: 24px;
-        color: #000;
-    }
+            &__label {
+                color: #86909c;
+            }
 
-    &__desc {
-        margin-top: 10px;
-        font-size: 16px;
-        font-weight: 350;
-        line-height: 24px;
-        color: rgba(6, 6, 6, 0.5);
-        text-align: left;
+            &__value {
+                color: #1d2129;
+            }
+        }
     }
 }
 
-.report-section {
-    background: #fff;
-    padding: 10px;
-    border-radius: 10px;
+.report-tabs {
+    display: flex;
+    gap: 8px;
+    padding-bottom: 6px;
 
-    & + & {
-        margin-top: 18px;
+    &__item {
+        position: relative;
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        gap: 4px;
+        height: 64px;
+        border-radius: 10px;
+        background: #eef0f3;
+        color: #86909c;
+        font-size: 12px;
+        line-height: 16px;
+        box-sizing: border-box;
+        border: 1px solid transparent;
+
+        &.active {
+            background: #fff;
+            border-color: rgba(33, 153, 248, 0.45);
+            color: #1d2129;
+            font-weight: 500;
+            box-shadow: 0 2px 8px rgba(33, 153, 248, 0.08);
+
+            .report-tabs__icon {
+                color: #2199f8;
+            }
+        }
     }
 
-    &__title {
-        margin-bottom: 10px;
-        font-size: 16px;
-        font-weight: 600;
-        line-height: 24px;
-        color: #000;
+    &__icon {
+        width: 22px;
+        height: 22px;
+        color: #86909c;
     }
-}
 
-.risk-group {
-    & + & {
-        margin-top: 14px;
+    &__arrow {
+        position: absolute;
+        left: 50%;
+        bottom: -6px;
+        width: 0;
+        height: 0;
+        margin-left: -6px;
+        border-left: 6px solid transparent;
+        border-right: 6px solid transparent;
+        border-top: 6px solid #fff;
+        filter: drop-shadow(0 1px 0 rgba(33, 153, 248, 0.25));
     }
 }
 
-.mb-10 {
-    margin-bottom: 10px;
-}
-
-.info-card {
-    padding: 10px;
-    border-radius: 4px;
-    background: #f7f8fa;
-    box-sizing: border-box;
+.report-panel {
+    margin-top: 12px;
+    padding: 16px 12px 18px;
+    border-radius: 12px;
+    background: #fff;
+    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
+    scroll-margin-top: 8px;
 
-    &.fertilizer-card {
-        background: transparent;
-        padding: 0;
+    &__title {
+        position: relative;
+        display: inline-block;
+        margin-bottom: 16px;
+        font-size: 18px;
+        font-weight: 700;
+        line-height: 26px;
+        color: #1d2129;
 
-        .info-card__text {
-            margin-bottom: 10px;
+        &::after {
+            content: "";
+            position: absolute;
+            left: 0;
+            bottom: 2px;
+            width: 36px;
+            height: 8px;
+            border-radius: 4px;
+            background: rgba(33, 153, 248, 0.35);
+            z-index: -1;
         }
     }
+}
 
+.report-block {
     & + & {
-        margin-top: 10px;
+        margin-top: 16px;
     }
 
-    &__title {
-        margin-bottom: 4px;
+    &__divider {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        margin-bottom: 10px;
+        color: #1d2129;
         font-size: 14px;
         font-weight: 500;
-        line-height: 22px;
-        color: #1d2129;
-    }
-
-    &__text {
-        font-size: 12px;
         line-height: 20px;
-        color: #4e5969;
 
-        & + & {
-            margin-top: 2px;
+        &::before,
+        &::after {
+            content: "";
+            flex: 1;
+            height: 1px;
+            background: #e5e6eb;
         }
     }
-}
-
-.section-divider {
-    display: flex;
-    align-items: center;
-    gap: 10px;
-    margin-bottom: 10px;
-    color: #1d2129;
-    font-size: 14px;
-    font-weight: 500;
-    line-height: 20px;
-
-    &::before,
-    &::after {
-        content: "";
-        flex: 1;
-        height: 1px;
-        background: linear-gradient(90deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0) 100%);
-    }
 
-    &::before {
-        background: linear-gradient(270deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0) 100%);
+    &__chart {
+        margin-bottom: 10px;
+        width: 100%;
+        overflow-x: auto;
+        overflow-y: hidden;
+        -webkit-overflow-scrolling: touch;
+        border-radius: 8px;
+
+        &--after {
+            margin-top: 10px;
+            margin-bottom: 0;
+
+            .report-block__chart-img {
+                height: auto;
+            }
+        }
     }
-}
 
-.tag-block {
-    padding: 12px;
-    border-radius: 8px;
-    background: #f7f8fa;
+    &__chart-img {
+        display: block;
+        width: auto;
+        max-width: none;
+        height: 145px;
 
-    & + & {
-        margin-top: 10px;
+        &--natural {
+            height: 245px;
+        }
     }
 
-    &__tag {
-        display: inline-block;
-        padding: 0 10px;
-        border-radius: 2px;
-        background: #2199f8;
-        color: #fff;
-        font-size: 12px;
-        line-height: 22px;
+    &__intro {
+        margin: 0 0 10px;
+        font-size: 13px;
+        line-height: 20px;
+        color: #1d2129;
     }
 
-    &__text {
-        margin-top: 4px;
+    &__note {
+        margin: 0 0 12px;
         font-size: 12px;
-        line-height: 20px;
-        color: #4e5969;
+        line-height: 18px;
+        color: #86909c;
     }
-}
 
-.suggest-card {
-    margin-top: 10px;
-    padding: 12px;
-    border-radius: 8px;
-    background: rgba(33, 153, 248, 0.08);
-
-    &__title {
-        margin-bottom: 8px;
-        font-size: 14px;
-        font-weight: 500;
-        line-height: 20px;
-        color: #2199f8;
+    &__plain {
+        & + & {
+            margin-top: 12px;
+        }
     }
 
-    &__row {
-        font-size: 12px;
-        line-height: 20px;
-        color: #4e5969;
+    &__card {
+        padding: 12px;
+        border-radius: 8px;
+        background: #f7f8fa;
+        box-sizing: border-box;
 
         & + & {
-            margin-top: 8px;
+            margin-top: 10px;
+        }
+
+        &--blue {
+            background: #edf6ff;
+
+            .report-block__item-title {
+                color: #2199f8;
+            }
+
+            .report-block__text {
+                color: #1d2129;
+            }
         }
     }
 
-    &__label {
-        color: #000;
+    &__item-title {
+        margin-bottom: 4px;
+        font-size: 14px;
+        font-weight: 600;
+        line-height: 22px;
+        color: #1d2129;
+    }
+
+    &__text {
+        margin: 0;
+        font-size: 13px;
+        line-height: 20px;
+        color: #4e5969;
     }
 }
 

+ 0 - 420
src/views/old_mini/agri_file/pages/diagnosisReport1.vue

@@ -1,420 +0,0 @@
-<template>
-    <div class="diagnosis-report-page">
-        <div class="report-header"></div>
-        <div v-if="loading" class="diagnosis-report-status">加载中...</div>
-        <div v-else-if="!reportData" class="diagnosis-report-status">暂无报告数据</div>
-
-        <div v-else class="report-main">
-            <!-- Tab -->
-            <div class="report-tabs">
-                <div
-                    v-for="tab in tabs"
-                    :key="tab.key"
-                    class="report-tabs__item"
-                    :class="{ active: activeTab === tab.key }"
-                    @click="activeTab = tab.key"
-                >
-                    <svg class="report-tabs__icon" viewBox="0 0 24 24" fill="none">
-                        <path
-                            v-for="(d, i) in tab.paths"
-                            :key="i"
-                            :d="d"
-                            stroke="currentColor"
-                            stroke-width="1.6"
-                            stroke-linecap="round"
-                            stroke-linejoin="round"
-                        />
-                    </svg>
-                    <span>{{ tab.label }}</span>
-                    <i v-if="activeTab === tab.key" class="report-tabs__arrow"></i>
-                </div>
-            </div>
-
-            <!-- 内容卡片 -->
-            <div class="report-panel">
-                <div class="report-panel__title">
-                    <span>{{ activeTabMeta.label }}</span>
-                </div>
-
-                <div
-                    v-for="(section, sIdx) in activeSections"
-                    :key="sIdx"
-                    class="report-block"
-                >
-                    <div class="report-block__divider">
-                        <span>{{ section.title }}</span>
-                    </div>
-
-                    <!-- 段落型 -->
-                    <div
-                        v-if="section.type === 'text'"
-                        class="report-block__card"
-                    >
-                        <p class="report-block__text">{{ section.content }}</p>
-                    </div>
-
-                    <!-- 条目型 -->
-                    <template v-else>
-                        <div
-                            v-for="(item, iIdx) in section.items"
-                            :key="iIdx"
-                            class="report-block__card"
-                        >
-                            <div class="report-block__item-title">{{ item.title }}</div>
-                            <p class="report-block__text">{{ item.content }}</p>
-                        </div>
-                    </template>
-                </div>
-            </div>
-        </div>
-
-        <!-- <div v-if="reportData" class="share-btn" @click="handleShare">转发报告</div> -->
-    </div>
-</template>
-
-<script setup>
-import { computed, onMounted, ref } from "vue";
-import { useRoute } from "vue-router";
-import { ElMessage } from "element-plus";
-import wx from "weixin-js-sdk";
-
-// ---------------------------------------------------------------------------
-// 常量
-// ---------------------------------------------------------------------------
-const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
-
-const TABS = [
-    {
-        key: "weather",
-        label: "气象风险",
-        paths: ["M13 2L4 14h7l-1 8 9-12h-7l1-8z"],
-    },
-    {
-        key: "quality",
-        label: "品质潜力",
-        paths: [
-            "M12 21c0-6 4-10 9-11-1 6-5 10-9 11z",
-            "M12 21c0-6-4-10-9-11 1 6 5 10 9 11z",
-            "M12 21V10",
-        ],
-    },
-    {
-        key: "input",
-        label: "必要投入",
-        paths: [
-            "M7 3h7l4 4v14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z",
-            "M14 3v4h4",
-            "M9 13h6",
-            "M9 17h6",
-        ],
-    },
-    {
-        key: "manage",
-        label: "农事管理",
-        paths: [
-            "M6 4h12a1 1 0 0 1 1 1v15H5V5a1 1 0 0 1 1-1z",
-            "M9 2v3",
-            "M15 2v3",
-            "M5 9h14",
-            "M9 13h.01",
-            "M12 13h.01",
-            "M15 13h.01",
-            "M9 17h.01",
-            "M12 17h.01",
-        ],
-    },
-];
-
-/** 各 Tab 演示内容(后续接接口解析结果) */
-const SECTION_MAP = {
-    weather: [
-        {
-            title: "气候条件",
-            type: "text",
-            content:
-                "梅州属亚热带季风气候,年均温约21.3℃,年降雨量约1600毫米,无霜期约300天,整体适合龙眼种植。",
-        },
-        {
-            title: "主要气象风险",
-            type: "list",
-            items: [
-                {
-                    title: "花期低温阴雨",
-                    content: "3—4月连续低温阴雨容易影响授粉和坐果。",
-                },
-                {
-                    title: "初夏连续强降雨",
-                    content: "5—6月降雨集中,易造成根区过湿、落果和病害。",
-                },
-            ],
-        },
-    ],
-    quality: [
-        {
-            title: "品质概况",
-            type: "text",
-            content: "当地光温条件有利于糖分积累,具备形成优质果品的基础潜力。",
-        },
-    ],
-    input: [
-        {
-            title: "投入要点",
-            type: "text",
-            content: "建议重点保障水肥、病虫害防控及关键物候期人工投入。",
-        },
-    ],
-    manage: [
-        {
-            title: "管理建议",
-            type: "text",
-            content: "按物候期安排修剪、疏果与采收,结合气象预警及时调整农事。",
-        },
-    ],
-};
-
-// ---------------------------------------------------------------------------
-// 页面状态
-// ---------------------------------------------------------------------------
-const route = useRoute();
-const loading = ref(false);
-const reportData = ref(null);
-const activeTab = ref("weather");
-const tabs = TABS;
-
-const entryFarmData = JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
-
-// ---------------------------------------------------------------------------
-// 计算属性
-// ---------------------------------------------------------------------------
-const activeTabMeta = computed(
-    () => tabs.find((t) => t.key === activeTab.value) || tabs[0]
-);
-const activeSections = computed(() => SECTION_MAP[activeTab.value] || []);
-
-// ---------------------------------------------------------------------------
-// 接口
-// ---------------------------------------------------------------------------
-const fetchReport = async () => {
-    loading.value = true;
-    reportData.value = null;
-    try {
-        const res = await VE_API.questionnaire.checkReportGenerated({
-            tel: entryFarmData.phone,
-        });
-        if (res?.code === 200 && res.data?.report) {
-            reportData.value = res.data.report;
-        } else {
-            ElMessage.error(res?.msg || "获取诊断报告失败");
-        }
-    } catch (e) {
-        ElMessage.error(e?.response?.data?.msg || e?.message || "获取诊断报告失败");
-    } finally {
-        loading.value = false;
-    }
-};
-
-const handleShare = () => {
-    const query = {
-        askInfo: { title: "转发报告", content: "是否分享给好友" },
-        shareText: "",
-        targetUrl: "diagnosis_report",
-        paramsPage: JSON.stringify({
-            id: route.query.id,
-            zone_id: route.query.zone_id || route.query.zoneId || 42,
-            fromShare: 1,
-        }),
-        imageUrl: "https://birdseye-img.sysuimars.com/temp/field.png",
-    };
-    wx.miniProgram.navigateTo({
-        url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
-    });
-};
-
-onMounted(() => {
-    fetchReport();
-});
-</script>
-
-<style lang="scss" scoped>
-.diagnosis-report-page {
-    min-height: 100vh;
-    background: #f5f6f8;
-    box-sizing: border-box;
-    padding-bottom: 60px;
-
-    .report-header {
-        width: 100%;
-        height: 148px;
-        background: url("@/assets/img/report/title-bg.png") no-repeat center center;
-        background-size: 100% 100%;
-    }
-}
-
-.diagnosis-report-status {
-    padding: 48px 16px;
-    text-align: center;
-    font-size: 14px;
-    color: #86909c;
-}
-
-.report-main {
-    margin-top: -40px;
-    padding: 0 12px;
-    position: relative;
-    z-index: 1;
-}
-
-.report-tabs {
-    display: flex;
-    gap: 8px;
-
-    &__item {
-        position: relative;
-        flex: 1;
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        justify-content: center;
-        gap: 4px;
-        height: 64px;
-        border-radius: 10px;
-        background: #eef0f3;
-        color: #86909c;
-        font-size: 12px;
-        line-height: 16px;
-        box-sizing: border-box;
-        border: 1px solid transparent;
-
-        &.active {
-            background: #fff;
-            border-color: rgba(33, 153, 248, 0.45);
-            color: #1d2129;
-            font-weight: 500;
-            box-shadow: 0 2px 8px rgba(33, 153, 248, 0.08);
-
-            .report-tabs__icon {
-                color: #2199f8;
-            }
-        }
-    }
-
-    &__icon {
-        width: 22px;
-        height: 22px;
-        color: #86909c;
-    }
-
-    &__arrow {
-        position: absolute;
-        left: 50%;
-        bottom: -6px;
-        width: 0;
-        height: 0;
-        margin-left: -6px;
-        border-left: 6px solid transparent;
-        border-right: 6px solid transparent;
-        border-top: 6px solid #fff;
-        filter: drop-shadow(0 1px 0 rgba(33, 153, 248, 0.25));
-    }
-}
-
-.report-panel {
-    margin-top: 12px;
-    padding: 16px 12px 18px;
-    border-radius: 12px;
-    background: #fff;
-    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
-
-    &__title {
-        position: relative;
-        display: inline-block;
-        margin-bottom: 16px;
-        font-size: 18px;
-        font-weight: 700;
-        line-height: 26px;
-        color: #1d2129;
-
-        &::after {
-            content: "";
-            position: absolute;
-            left: 0;
-            bottom: 2px;
-            width: 36px;
-            height: 8px;
-            border-radius: 4px;
-            background: rgba(33, 153, 248, 0.35);
-            z-index: -1;
-        }
-    }
-}
-
-.report-block {
-    & + & {
-        margin-top: 16px;
-    }
-
-    &__divider {
-        display: flex;
-        align-items: center;
-        gap: 10px;
-        margin-bottom: 10px;
-        color: #1d2129;
-        font-size: 14px;
-        font-weight: 500;
-        line-height: 20px;
-
-        &::before,
-        &::after {
-            content: "";
-            flex: 1;
-            height: 1px;
-            background: #e5e6eb;
-        }
-    }
-
-    &__card {
-        padding: 12px;
-        border-radius: 8px;
-        background: #f7f8fa;
-        box-sizing: border-box;
-
-        & + & {
-            margin-top: 10px;
-        }
-    }
-
-    &__item-title {
-        margin-bottom: 4px;
-        font-size: 14px;
-        font-weight: 600;
-        line-height: 22px;
-        color: #1d2129;
-    }
-
-    &__text {
-        margin: 0;
-        font-size: 13px;
-        line-height: 20px;
-        color: #4e5969;
-    }
-}
-
-.share-btn {
-    position: fixed;
-    left: 50%;
-    bottom: 64px;
-    z-index: 10;
-    transform: translateX(-50%);
-    min-width: 120px;
-    height: 40px;
-    box-sizing: border-box;
-    padding: 0 30px;
-    border-radius: 22px;
-    background: linear-gradient(180deg, #72c1ff 0%, #2199f8 100%);
-    color: #fff;
-    font-size: 14px;
-    line-height: 40px;
-    text-align: center;
-    box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.1);
-}
-</style>

+ 533 - 0
src/views/old_mini/agri_file/pages/diagnosisReport2.vue

@@ -0,0 +1,533 @@
+<template>
+    <div class="diagnosis-report-page">
+        <!-- <custom-header :name="t('agriFile.initialReport')" :isGoBack="true" @goback="handleBack" /> -->
+
+        <div v-if="loading" class="diagnosis-report-status">加载中...</div>
+        <div v-else-if="!reportData" class="diagnosis-report-status">暂无报告数据</div>
+
+        <div v-else class="diagnosis-report-body">
+            <div class="report-hero">
+                <div class="report-hero__title">{{ reportData.title }}</div>
+                <div class="report-hero__desc">{{ reportData.intro }}</div>
+            </div>
+
+            <div
+                v-for="(section, sIdx) in reportData.sections"
+                :key="sIdx"
+                class="report-section"
+            >
+                <div class="report-section__title">{{ section.displayTitle || section.title }}</div>
+
+                <!-- 基本情况与种植价值:灰色卡片(无 ### 分组) -->
+                <template v-if="section.type === 'basic'">
+                    <div v-for="card in section.cards" :key="card.title" class="info-card">
+                        <div class="info-card__title">{{ card.title }}</div>
+                        <div v-for="(para, idx) in card.paragraphs" :key="idx" class="info-card__text">
+                            {{ para }}
+                        </div>
+                    </div>
+                </template>
+
+                <!-- 气象风险 / 肥料 / 病虫害:分组 + 飞鸟建议 -->
+                <template v-else>
+                    <div v-if="section.intro" class="info-card__text mb-10">{{ section.intro }}</div>
+
+                    <div v-for="(group, gIdx) in section.groups" :key="gIdx" class="risk-group">
+                        <div class="section-divider">
+                            <span>{{ group.title }}</span>
+                        </div>
+                        <div class="info-card fertilizer-card">
+                            <div v-for="item in group.items" :key="item.tag" class="tag-block">
+                                <span class="tag-block__tag">{{ item.tag }}</span>
+                                <div class="tag-block__text">{{ item.content }}</div>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div v-if="section.suggest" class="suggest-card">
+                        <div class="suggest-card__title">{{ section.suggest.title }}</div>
+                        <div
+                            v-for="row in section.suggest.rows"
+                            :key="row.label"
+                            class="suggest-card__row"
+                        >
+                            <span class="suggest-card__label">{{ row.label }}</span>
+                            <span>{{ row.content }}</span>
+                        </div>
+                    </div>
+                </template>
+            </div>
+        </div>
+
+        <!-- <div v-if="reportData" class="share-btn" @click="handleShare">{{ t("agriFile.forwardReport") }}</div> -->
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { ElMessage } from "element-plus";
+import customHeader from "@/components/customHeader.vue";
+import { useI18n } from "@/i18n";
+import wx from "weixin-js-sdk";
+
+const { t } = useI18n();
+const router = useRouter();
+const route = useRoute();
+
+const loading = ref(false);
+const reportData = ref(null);
+
+// ---------------------------------------------------------------------------
+// Markdown 解析:将接口 report 文本转成页面结构
+// ---------------------------------------------------------------------------
+
+/** 去掉 markdown 标题前缀,保留书名号 */
+const cleanTitle = (text = "") =>
+    String(text)
+        .replace(/^#+\s*/, "")
+        .trim();
+
+/** 去掉「一、」「二、」等章节序号,用于展示 */
+const stripChapterNo = (title = "") =>
+    String(title)
+        .replace(/^[一二三四五六七八九十百]+[、..]\s*/, "")
+        .trim();
+
+/** 是否为「基本情况与种植价值」章节(结构与其它章节不同:仅有一级列表卡片) */
+const isBasicSectionTitle = (title = "") => /基本情况|种植价值/.test(title);
+
+/** 解析 `- 标签:内容` / `- 标签:内容` */
+const parseLabeledItem = (line) => {
+    const text = String(line).replace(/^[-*•]\s+/, "").trim();
+    const match = text.match(/^([^::]+)[::]\s*(.+)$/);
+    if (!match) return null;
+    return {
+        label: match[1].trim(),
+        content: match[2].trim(),
+    };
+};
+
+/** 判断是否为「飞鸟建议」小节 */
+const isSuggestHeading = (title) => /飞鸟建议/.test(title || "");
+
+/**
+ * 将 markdown 报告解析为 { title, intro, sections }
+ * - basic:基本情况与种植价值(- 标签:内容 → 灰色卡片)
+ * - risk:含 ### 分组 + 可选飞鸟建议
+ */
+const parseReportMarkdown = (markdown) => {
+    const lines = String(markdown || "")
+        .replace(/\r\n/g, "\n")
+        .split("\n")
+        .map((line) => line.trimEnd());
+
+    let title = "";
+    const introLines = [];
+    const sections = [];
+    let currentSection = null;
+    let currentGroup = null;
+    let inIntro = true;
+
+    const createSection = (rawTitle) => {
+        const fullTitle = cleanTitle(rawTitle);
+        const basic = isBasicSectionTitle(fullTitle);
+        return {
+            title: fullTitle,
+            displayTitle: stripChapterNo(fullTitle),
+            type: basic ? "basic" : "risk",
+            intro: "",
+            cards: [],
+            groups: [],
+            suggest: null,
+            _introLines: [],
+        };
+    };
+
+    const flushGroup = () => {
+        if (!currentSection || !currentGroup) return;
+        // 基本情况章节不应出现 ###,若误入则并入卡片
+        if (currentSection.type === "basic") {
+            currentGroup.items.forEach((item) => {
+                currentSection.cards.push({
+                    title: item.tag,
+                    paragraphs: [item.content],
+                });
+            });
+            currentGroup = null;
+            return;
+        }
+        if (isSuggestHeading(currentGroup.title)) {
+            currentSection.suggest = {
+                title: currentGroup.title,
+                rows: currentGroup.items.map((item) => ({
+                    label: `${item.tag}:`,
+                    content: item.content,
+                })),
+            };
+        } else {
+            currentSection.groups.push({
+                title: currentGroup.title,
+                items: currentGroup.items,
+            });
+        }
+        currentGroup = null;
+    };
+
+    for (const raw of lines) {
+        const line = raw.trim();
+        if (!line) continue;
+
+        if (/^#\s+/.test(line) && !/^##/.test(line)) {
+            title = cleanTitle(line);
+            continue;
+        }
+
+        if (/^##\s+/.test(line)) {
+            flushGroup();
+            inIntro = false;
+            currentSection = createSection(line);
+            sections.push(currentSection);
+            continue;
+        }
+
+        if (/^###\s+/.test(line)) {
+            flushGroup();
+            if (!currentSection) {
+                currentSection = createSection("");
+                sections.push(currentSection);
+            }
+            // 基本情况章节忽略 ###,当作普通内容跳过标题行
+            if (currentSection.type === "basic") {
+                currentGroup = null;
+                continue;
+            }
+            currentGroup = {
+                title: cleanTitle(line).replace(/^\d+\.\s*/, ""),
+                items: [],
+            };
+            continue;
+        }
+
+        if (/^[-*•]\s+/.test(line)) {
+            const item = parseLabeledItem(line);
+            if (!item) continue;
+            if (!currentSection) {
+                currentSection = createSection("");
+                sections.push(currentSection);
+            }
+
+            // 基本情况:一律进灰色卡片
+            if (currentSection.type === "basic") {
+                const exist = currentSection.cards.find((c) => c.title === item.label);
+                if (exist) {
+                    exist.paragraphs.push(item.content);
+                } else {
+                    currentSection.cards.push({
+                        title: item.label,
+                        paragraphs: [item.content],
+                    });
+                }
+                continue;
+            }
+
+            if (currentGroup) {
+                currentGroup.items.push({
+                    tag: item.label,
+                    content: item.content,
+                });
+            } else {
+                // risk 章节若列表在 ### 之前,先挂到临时卡片,一般不会出现
+                currentSection.cards.push({
+                    title: item.label,
+                    paragraphs: [item.content],
+                });
+            }
+            continue;
+        }
+
+        if (inIntro && !currentSection) {
+            introLines.push(line);
+        } else if (currentSection && !currentGroup) {
+            currentSection._introLines.push(line);
+        }
+    }
+
+    flushGroup();
+
+    sections.forEach((section) => {
+        section.intro = (section._introLines || []).join("");
+        delete section._introLines;
+        // 再次按标题校正类型,避免误判
+        if (isBasicSectionTitle(section.title)) {
+            section.type = "basic";
+        } else if (section.groups.length || section.suggest) {
+            section.type = "risk";
+        }
+    });
+
+    return {
+        title: title || "初始种植诊断报告",
+        intro: introLines.join(""),
+        sections,
+    };
+};
+
+const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
+const entryFarmData = JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
+
+// ---------------------------------------------------------------------------
+// 接口
+// ---------------------------------------------------------------------------
+const fetchReport = async () => {
+    loading.value = true;
+    reportData.value = null;
+    try {
+        const res = await VE_API.questionnaire.checkReportGenerated({
+            tel: entryFarmData.phone,
+        });
+        if (res?.code === 200 && res.data?.report) {
+            reportData.value = parseReportMarkdown(res.data.report);
+        } else {
+            ElMessage.error(res?.msg || "获取诊断报告失败");
+        }
+    } catch (e) {
+        ElMessage.error(e?.response?.data?.msg || e?.message || "获取诊断报告失败");
+    } finally {
+        loading.value = false;
+    }
+};
+
+const handleBack = () => {
+    router.back();
+};
+
+const handleShare = () => {
+    const query = {
+        askInfo: { title: "转发报告", content: "是否分享给好友" },
+        shareText: reportData.value?.title || "",
+        targetUrl: "diagnosis_report",
+        paramsPage: JSON.stringify({
+            id: route.query.id,
+            zone_id: route.query.zone_id || route.query.zoneId || 42,
+            fromShare: 1,
+        }),
+        imageUrl: "https://birdseye-img.sysuimars.com/temp/field.png",
+    };
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
+    });
+};
+
+onMounted(() => {
+    fetchReport();
+});
+</script>
+
+<style lang="scss" scoped>
+.diagnosis-report-page {
+    min-height: 100vh;
+    background: #f5f6f8;
+    box-sizing: border-box;
+    padding-bottom: 90px;
+}
+
+.diagnosis-report-status {
+    padding: 48px 16px;
+    text-align: center;
+    font-size: 14px;
+    color: #86909c;
+}
+
+.diagnosis-report-body {
+    padding: 10px 10px 20px;
+    max-height: calc(100vh - 40px);
+    overflow: auto;
+    box-sizing: border-box;
+}
+
+.report-hero {
+    padding: 0 0 10px;
+    text-align: center;
+
+    &__title {
+        font-size: 16px;
+        font-weight: bold;
+        line-height: 24px;
+        color: #000;
+    }
+
+    &__desc {
+        margin-top: 10px;
+        font-size: 16px;
+        font-weight: 350;
+        line-height: 24px;
+        color: rgba(6, 6, 6, 0.5);
+        text-align: left;
+    }
+}
+
+.report-section {
+    background: #fff;
+    padding: 10px;
+    border-radius: 10px;
+
+    & + & {
+        margin-top: 18px;
+    }
+
+    &__title {
+        margin-bottom: 10px;
+        font-size: 16px;
+        font-weight: 600;
+        line-height: 24px;
+        color: #000;
+    }
+}
+
+.risk-group {
+    & + & {
+        margin-top: 14px;
+    }
+}
+
+.mb-10 {
+    margin-bottom: 10px;
+}
+
+.info-card {
+    padding: 10px;
+    border-radius: 4px;
+    background: #f7f8fa;
+    box-sizing: border-box;
+
+    &.fertilizer-card {
+        background: transparent;
+        padding: 0;
+
+        .info-card__text {
+            margin-bottom: 10px;
+        }
+    }
+
+    & + & {
+        margin-top: 10px;
+    }
+
+    &__title {
+        margin-bottom: 4px;
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 22px;
+        color: #1d2129;
+    }
+
+    &__text {
+        font-size: 12px;
+        line-height: 20px;
+        color: #4e5969;
+
+        & + & {
+            margin-top: 2px;
+        }
+    }
+}
+
+.section-divider {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 10px;
+    color: #1d2129;
+    font-size: 14px;
+    font-weight: 500;
+    line-height: 20px;
+
+    &::before,
+    &::after {
+        content: "";
+        flex: 1;
+        height: 1px;
+        background: linear-gradient(90deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0) 100%);
+    }
+
+    &::before {
+        background: linear-gradient(270deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0) 100%);
+    }
+}
+
+.tag-block {
+    padding: 12px;
+    border-radius: 8px;
+    background: #f7f8fa;
+
+    & + & {
+        margin-top: 10px;
+    }
+
+    &__tag {
+        display: inline-block;
+        padding: 0 10px;
+        border-radius: 2px;
+        background: #2199f8;
+        color: #fff;
+        font-size: 12px;
+        line-height: 22px;
+    }
+
+    &__text {
+        margin-top: 4px;
+        font-size: 12px;
+        line-height: 20px;
+        color: #4e5969;
+    }
+}
+
+.suggest-card {
+    margin-top: 10px;
+    padding: 12px;
+    border-radius: 8px;
+    background: rgba(33, 153, 248, 0.08);
+
+    &__title {
+        margin-bottom: 8px;
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 20px;
+        color: #2199f8;
+    }
+
+    &__row {
+        font-size: 12px;
+        line-height: 20px;
+        color: #4e5969;
+
+        & + & {
+            margin-top: 8px;
+        }
+    }
+
+    &__label {
+        color: #000;
+    }
+}
+
+.share-btn {
+    position: fixed;
+    left: 50%;
+    bottom: 64px;
+    z-index: 10;
+    transform: translateX(-50%);
+    min-width: 120px;
+    height: 40px;
+    box-sizing: border-box;
+    padding: 0 30px;
+    border-radius: 22px;
+    background: linear-gradient(180deg, #72c1ff 0%, #2199f8 100%);
+    color: #fff;
+    font-size: 14px;
+    line-height: 40px;
+    text-align: center;
+    box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.1);
+}
+</style>