Pārlūkot izejas kodu

feat:对接异常上传接口和查询图片接口

wangsisi 5 dienas atpakaļ
vecāks
revīzija
ef027a7c54

+ 12 - 0
src/App.vue

@@ -45,6 +45,18 @@
                     />
                 </template>
             </tabbar-item>
+            <tabbar-item replace to="/diagnosis_report">
+                <span>{{ t("tabbar.diagnosisReport") }}</span>
+                <template #icon="props">
+                    <img
+                        :src="
+                            props.active
+                                ? require('@/assets/img/tab_bar/baogao-active.png')
+                                : require('@/assets/img/tab_bar/baogao.png')
+                        "
+                    />
+                </template>
+            </tabbar-item>
             <tabbar-item replace to="/agri_file">
                 <span>{{ t("tabbar.agriFile") }}</span>
                 <template #icon="props">

+ 15 - 0
src/api/modules/questionnaire.js

@@ -91,4 +91,19 @@ module.exports = {
         url: config.base_new_url + "report/stress_report",
         type: "get",
     },
+    // 异常农情研判报告列表
+    queryAbnormalReport: {
+        url: config.base_new_url + "report/query_abnormal_report",
+        type: "get",
+    },
+    // 异常巡检拍照提交
+    inspect: {
+        url: config.base_new_url + "report/inspect",
+        type: "post",
+    },
+    // 农情相册临时照片
+    queryTmpImage: {
+        url: config.base_new_url + "report/query_tmp_image",
+        type: "get",
+    },
 }

BIN
src/assets/img/report/title-bg.png


BIN
src/assets/img/tab_bar/baogao-active.png


BIN
src/assets/img/tab_bar/baogao.png


+ 1 - 1
src/components/pageComponents/locationSearch.vue

@@ -45,7 +45,7 @@ const remoteMethod = async (keyword) => {
         const params = {
             key: MAP_KEY,
             keyword,
-            region: '河北省',
+            region: '广东省',
             region_fix: 1,
             location: props.userLocation,
         };

+ 2 - 0
src/i18n/messages.js

@@ -4,6 +4,7 @@ export default {
     zh: {
         tabbar: {
             situationAssessment: "农情研判",
+            diagnosisReport: "诊断报告",
             agriFile: "农事档案",
             agriRecord: "农事规划",
             schedule: "农事列表",
@@ -451,6 +452,7 @@ export default {
     en: {
         tabbar: {
             situationAssessment: "Situation Assessment",
+            diagnosisReport: "Diagnostic Report",
             agriFile: "Crop Archives",
             agriRecord: "Farm Planning",
             schedule: "Schedule",

+ 1 - 1
src/router/globalRoutes.js

@@ -196,7 +196,7 @@ export default [
     {
         path: "/diagnosis_report",
         name: "DiagnosisReport",
-        meta: { keepAlive: true },
+        meta: { showTabbar: true, keepAlive: true },
         component: () => import("@/views/old_mini/agri_file/pages/diagnosisReport.vue"),
     },
 ];

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

@@ -1,452 +0,0 @@
-<template>
-    <div class="agri-file-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
-        <!-- 天气遮罩 -->
-        <div class="weather-mask" v-show="isExpanded" @click="handleMaskClick"></div>
-        <!-- 头部 -->
-        <div class="agri-file-header" :style="activeGardenTab === 'current' ? headerMotionStyle : undefined">
-            <weather-info ref="weatherInfoRef" :hasWeather="false" from="agri_file" class="weather-info"
-                @weatherExpanded="weatherExpanded" @changeGarden="changeGarden" @changeGardenTab="changeGardenTab"
-                @reportTabClick="handleReportTabClick" :isGarden="true"
-                :gardenId="defaultGardenId" />
-        </div>
-        <!-- 农场列表 -->
-        <div v-show="activeGardenTab === 'list'">
-            <garden-list ref="gardenListRef" :garden-id="selectedGardenId" @loaded="handleGardenLoaded"
-                @selectGarden="handleGardenSelected" />
-        </div>
-
-        <div v-show="activeGardenTab === 'current'" class="tracking-list">
-            <div v-for="item in trackingList" :key="item.id" class="tracking-item"
-                :class="`tracking-item--${item.theme}`" @click="handleTrackingItemClick(item)">
-                <div class="tracking-item__header">
-                    <div class="tracking-item__header-row">
-                        <div class="tracking-item__header-left">
-                            <span class="tracking-item__level" v-show="item.risk_level">{{
-                                formatRiskLevel(item.risk_level) }}</span>
-                            <span class="tracking-item__title">{{ item.first_work?.work_name }}</span>
-                        </div>
-                        <div class="tracking-item__tags">
-                            <span v-for="(tag, tagIndex) in item.list" :key="tagIndex" class="tracking-item__tag">{{
-                                tag?.category_name || tag }}</span>
-                        </div>
-                    </div>
-                </div>
-                <div class="tracking-item__body">
-                    <div class="tracking-item__action">
-                        <div class="tracking-item__action-main">
-                            <div class="tracking-item__icon">
-                                <img :src="item.icon" alt="" />
-                                <div class="tracking-item__reason">{{ item.first_work?.work_reason_short }}</div>
-                            </div>
-                            <div class="tracking-item__info">
-                                <div class="tracking-item__issue">{{ item.first_work?.interaction_issue }}</div>
-                            </div>
-                        </div>
-                        <div class="tracking-item__btn">{{ $t('agriFile.recordNow') }}</div>
-                    </div>
-                    <div class="tracking-item__history" v-show="false">
-                        <div class="tracking-item__history-text">{{ item.historyText }}</div>
-                        <div class="tracking-item__images">
-                            <img v-for="(image, imageIndex) in item.images" :key="imageIndex"
-                                class="tracking-item__thumb" :src="image" alt="" />
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-</template>
-
-<script setup>
-import { computed, onActivated, ref } from "vue";
-import { useRoute, useRouter } from "vue-router";
-import { useStore } from "vuex";
-import { useI18n } from "@/i18n";
-import weatherInfo from "@/components/weatherInfo.vue";
-import gardenList from "@/components/gardenList.vue";
-
-const { t } = useI18n();
-
-const store = useStore();
-const route = useRoute();
-const router = useRouter();
-const tabBarHeight = computed(() => store.state.home.tabBarHeight);
-
-const isExpanded = ref(false);
-const weatherInfoRef = ref(null);
-const defaultGardenId = ref(null);
-const selectedGardenId = ref(null);
-const gardenListRef = ref(null);
-const activeGardenTab = ref("current");
-const panelExpandProgress = ref(0);
-const panelViewType = ref("risk");
-
-const HEADER_FADE_START = 0.68;
-
-const headerMotionStyle = computed(() => {
-    const progress = panelExpandProgress.value;
-    const fade = progress <= HEADER_FADE_START
-        ? 0
-        : (progress - HEADER_FADE_START) / (1 - HEADER_FADE_START);
-    const opacity = 1 - fade;
-    return {
-        opacity,
-        transform: `translateY(${-14 * fade}px) scale(${1 - fade * 0.04})`,
-        pointerEvents: opacity < 0.15 ? "none" : "auto",
-    };
-});
-
-const currentFarmName = ref("");
-const currentFarmVariety = ref(null);
-
-const defaultThumb = require("@/assets/img/home/banner.png");
-const trackingList = ref([]);
-// const trackingList = ref([
-//     {
-//         id: "phenology",
-//         theme: "blue",
-//         level: "二级",
-//         title: "物候跟踪记录",
-//         tags: ["物候", "物候", "物候"],
-//         icon: require("@/assets/img/report/wh-icon.png"),
-//         reason: "某某原因",
-//         issue: "互动问题互动问题互动问题互动问题互",
-//         recordText: "立即记录",
-//         historyText: "2026.06.07 某某区发生的事情",
-//         images: Array.from({ length: 5 }, () => defaultThumb),
-//     },
-//     {
-//         id: "pest",
-//         theme: "red",
-//         level: "二级",
-//         title: "病虫害态势监控",
-//         tags: ["真菌类", "真菌类", "真菌类"],
-//         icon: require("@/assets/img/report/bh-icon.png"),
-//         reason: "某某原因",
-//         issue: "互动问题互动问题互动问题互动问题互",
-//         recordText: "立即记录",
-//         historyText: "2026.06.07 某某区发生的事情",
-//         images: Array.from({ length: 5 }, () => defaultThumb),
-//     },
-//     {
-//         id: "growth",
-//         theme: "orange",
-//         level: "二级",
-//         title: "长势异常态势跟踪",
-//         tags: ["真菌类", "真菌类", "真菌类"],
-//         icon: require("@/assets/img/report/yc-icon.png"),
-//         reason: "某某原因",
-//         issue: "互动问题互动问题互动问题互动问题互",
-//         recordText: "立即记录",
-//         historyText: "2026.06.07 某某区发生的事情",
-//         images: Array.from({ length: 5 }, () => defaultThumb),
-//     },
-// ]);
-
-const handleReportTabClick = (item) => {
-    panelViewType.value = "plot";
-};
-
-const weatherExpanded = (isExpandedValue) => {
-    isExpanded.value = isExpandedValue;
-};
-
-const handleMaskClick = () => {
-    if (weatherInfoRef.value?.toggleExpand) {
-        weatherInfoRef.value.toggleExpand();
-    }
-};
-
-const changeGardenTab = (tab) => {
-    activeGardenTab.value = tab;
-    if (tab !== "current") {
-        panelExpandProgress.value = 0;
-        panelViewType.value = "risk";
-    }
-};
-
-const handleGardenLoaded = ({ hasFarm }) => {
-    weatherInfoRef.value?.setGardenLoaded?.(hasFarm);
-};
-
-const handleGardenSelected = (garden) => {
-    selectedGardenId.value = garden?.id ?? null;
-    weatherInfoRef.value?.setSelectedGarden?.(garden);
-};
-
-const changeGarden = (data) => {
-    if (!data?.id) return;
-    store.commit("home/SET_GARDEN_ID", data.id);
-    selectedGardenId.value = data.id;
-    currentFarmName.value = data.name ?? "";
-    currentFarmVariety.value = data.farm_variety ?? null;
-    getFarmRiskAndTracking();
-};
-
-onActivated(() => {
-    if (route.query?.farmId) {
-        defaultGardenId.value = route.query.farmId;
-    }
-    const savedFarmId = localStorage.getItem("selectedFarmId");
-    selectedGardenId.value = savedFarmId ? Number(savedFarmId) : null;
-    gardenListRef.value?.refreshFarmList?.();
-});
-
-const RISK_LEVEL_KEYS = { 1: "agriFile.riskLevel1", 2: "agriFile.riskLevel2", 3: "agriFile.riskLevel3" };
-const formatRiskLevel = (level) => {
-    const key = RISK_LEVEL_KEYS[Number(level)];
-    return key ? t(key) : "";
-};
-
-const currentPheCode = ref('');
-let fetchRequestId = 0;
-const getFarmRiskAndTracking = async () => {
-    const requestId = ++fetchRequestId;
-    trackingList.value = [];
-    const selectedFarmData = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
-    const res = await VE_API.monitor.getFarmRiskAndTracking({
-        farm_id: selectedGardenId.value,
-        crop_type: selectedFarmData.farm_variety,
-        category_code: selectedFarmData.farm_category
-    });
-    if (requestId !== fetchRequestId) return;
-    if (res.code === 200) {
-        currentPheCode.value = res.data.current_phe_code;
-        trackingList.value = [
-            { ...res.data.phenology_tracking, theme: "blue", icon: require("@/assets/img/report/wh-icon.png"), workType: 'phenology' },
-            { ...res.data.growth_abnormal_tracking, theme: "orange", icon: require("@/assets/img/report/yc-icon.png"), workType: 'growth' },
-            { ...res.data.pest_risk_assessment, theme: "red", icon: require("@/assets/img/report/bh-icon.png"), workType: 'pest' },
-        ];
-    }
-};
-
-const handleTrackingItemClick = (item) => {
-    router.push({
-        path: "/record_details",
-        query: { workId: item?.first_work?.work_id, type: item?.workType, curCode: currentPheCode.value },
-    });
-};
-</script>
-
-<style lang="scss" scoped>
-.agri-file-page {
-    width: 100%;
-    height: 100%;
-    background: #D0E6FC;
-
-    .weather-mask {
-        position: fixed;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        background-color: rgba(0, 0, 0, 0.52);
-        z-index: 11;
-    }
-
-    .agri-file-header {
-        position: absolute;
-        z-index: 12;
-        left: 10px;
-        top: 12px;
-        width: calc(100% - 20px);
-        will-change: transform, opacity;
-        transform-origin: center top;
-
-        .weather-info {
-            width: 100%;
-            position: relative;
-            left: auto;
-            top: auto;
-
-            :deep(.garden-tabs) {
-                .garden-item.left-item.active .current-name {
-                    color: #2199F8;
-                    font-weight: 600;
-                }
-            }
-        }
-    }
-
-    .tracking-list {
-        height: 100%;
-        padding: 65px 10px 12px;
-        // padding: 105px 10px 12px;
-        box-sizing: border-box;
-        overflow-y: auto;
-        display: flex;
-        flex-direction: column;
-        gap: 12px;
-
-        &::-webkit-scrollbar {
-            display: none;
-        }
-    }
-
-    .tracking-item {
-        width: 100%;
-        box-shadow: 0px 4px 4px 0px var(--tracking-shadow);
-        border: 1px solid #fff;
-        border-radius: 10px;
-
-        &--blue {
-            --tracking-header-bg: linear-gradient(0deg, #A6D8FF 0%, #39A7FF 100%);
-            --tracking-primary: #2199f8;
-            --tracking-action-border: rgba(33, 153, 248, 0.2);
-            --tracking-shadow: #2199f81a;
-        }
-
-        &--red {
-            --tracking-header-bg: linear-gradient(0deg, #FD9D9D 0%, #FF6A6A 100%);
-            --tracking-primary: #FF6A6A;
-            --tracking-action-border: rgba(255, 173, 173, 0.2);
-            --tracking-shadow: #ff6b6b1a;
-        }
-
-        &--orange {
-            --tracking-header-bg: linear-gradient(0deg, #FCB981 0%, #FF953D 100%);
-            --tracking-primary: #FA8D39;
-            --tracking-action-border: rgba(255, 173, 173, 0.2);
-            --tracking-shadow: #f593421a;
-        }
-
-        &__header {
-            position: relative;
-            padding: 10px 10px 20px;
-            border-radius: 10px 10px 0 0;
-            background: var(--tracking-header-bg);
-        }
-
-        &__header-row {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-        }
-
-        &__header-left {
-            display: flex;
-            align-items: center;
-            gap: 6px;
-        }
-
-        &__level {
-            padding: 1px 6px;
-            border-radius: 2px;
-            background: #fff;
-            color: #FF6A6A;
-            font-size: 12px;
-            min-width: fit-content;
-        }
-
-        &__title {
-            color: #fff;
-            font-size: 16px;
-            font-weight: 500;
-        }
-
-        &__tags {
-            display: flex;
-            align-items: center;
-            gap: 4px;
-        }
-
-        &__tag {
-            padding: 1px 5px;
-            border-radius: 2px;
-            background: #fff;
-            color: var(--tracking-primary);
-            font-size: 12px;
-            border: .5px solid var(--tracking-primary);
-            min-width: max-content;
-        }
-
-        &__body {
-            position: relative;
-            padding: 10px;
-            background: #fff;
-            border-radius: 10px;
-            margin-top: -8px;
-
-            &::before {
-                content: "";
-                position: absolute;
-                top: -7px;
-                left: 60px;
-                width: 0;
-                height: 0;
-                border-left: 7px solid transparent;
-                border-right: 7px solid transparent;
-                border-bottom: 7px solid #fff;
-            }
-        }
-
-        &__action {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            gap: 8px;
-            padding: 10px 8px;
-            border: 1px solid var(--tracking-action-border);
-            border-radius: 6px;
-            box-sizing: border-box;
-        }
-
-        &__icon {
-            display: flex;
-            align-items: center;
-            gap: 4px;
-
-            img {
-                width: 18px;
-                height: 16px;
-            }
-        }
-
-        &__reason {
-            color: var(--tracking-primary);
-            font-weight: 500;
-        }
-
-        &__issue {
-            margin-top: 3px;
-            color: var(--tracking-primary);
-            font-size: 12px;
-        }
-
-        &__btn {
-            padding: 5px 7px;
-            border-radius: 5px;
-            background: var(--tracking-primary);
-            color: #fff;
-            font-size: 12px;
-            min-width: fit-content;
-        }
-
-        &__history {
-            margin-top: 10px;
-            padding: 6px 8px;
-            border-radius: 6px;
-            background: rgba(168, 168, 168, 0.1);
-        }
-
-        &__history-text {
-            color: rgba(31, 31, 31, 0.5);
-            font-size: 12px;
-        }
-
-        &__images {
-            display: grid;
-            grid-template-columns: repeat(5, 1fr);
-            gap: 6px;
-            margin-top: 6px;
-        }
-
-        &__thumb {
-            width: 100%;
-            aspect-ratio: 1;
-            border-radius: 8px;
-            object-fit: cover;
-        }
-    }
-}
-</style>

+ 181 - 102
src/views/old_mini/agri_file/index.vue

@@ -135,6 +135,15 @@
         <album-upload-popup v-model:show="showUploadPopup" />
         <complete-farm-info-popup v-model:show="showCompleteFarmPopup" @confirm="goCompleteFarmInfo" />
 
+        <!-- 异常巡检上传成功(接口 200 后弹出) -->
+        <tip-popup
+            v-model:show="showInspectSuccessPopup"
+            type="executeSuccess"
+            text="您已上传成功"
+            text2="请等待诊断报告生成"
+            buttonText="完成"
+        />
+
         <!-- 诊断报告弹窗 -->
         <diagnosis-report-popup ref="diagnosisReportPopupRef" />
 
@@ -165,11 +174,13 @@ import * as util from "@/common/ol_common.js";
 import farmHeader from "@/components/farmHeader.vue";
 import albumUploadPopup from "./components/albumUploadPopup.vue";
 import completeFarmInfoPopup from "@/components/popup/completeFarmInfoPopup.vue";
+import tipPopup from "@/components/popup/tipPopup.vue";
 import diagnosisReportPopup from "@/components/popup/diagnosisReportPopup.vue";
 import restorePlantingPopup from "@/components/popup/restorePlantingPopup.vue";
 import proxyAuthPopup from "@/components/popup/proxyAuthPopup.vue";
 import phenologyUpdatePopup from "@/components/popup/phenologyUpdatePopup.vue";
 import PhenologyTrackTimelineItem from "@/components/pageComponents/PhenologyTrackTimelineItem.vue";
+import eventBus from "@/api/eventBus";
 import wx from "weixin-js-sdk";
 import { base_img_url2 } from "@/api/config";
 
@@ -179,6 +190,22 @@ const router = useRouter();
 const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 const diagnosisReportPopupRef = ref(null);
 
+/** 与 growthTrack 异常巡检约定:接口成功后弹出成功窗 */
+const INSPECT_SUCCESS_KEY = "AGRI_INSPECT_UPLOAD_SUCCESS";
+const INSPECT_SUCCESS_EVENT = "inspect-upload-success";
+const showInspectSuccessPopup = ref(false);
+
+function openInspectSuccessPopup() {
+    sessionStorage.removeItem(INSPECT_SUCCESS_KEY);
+    showInspectSuccessPopup.value = true;
+    fetchReportList();
+}
+
+function tryShowInspectSuccessPopup() {
+    if (sessionStorage.getItem(INSPECT_SUCCESS_KEY) !== "1") return;
+    openInspectSuccessPopup();
+}
+
 /** 恢复种植确认「已到达」后,再检查是否弹出诊断报告 */
 const onPhenologyReached = () => {
     diagnosisReportPopupRef.value?.checkShouldShow?.();
@@ -187,11 +214,10 @@ const onPhenologyReached = () => {
 const DIAGNOSIS_GUIDE_STORAGE_KEY = "AGRI_FILE_DIAGNOSIS_REPORT_GUIDE";
 const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
 const CROP_ARCHIVE_KEY = "CROP_ARCHIVE_RECORDS";
-/** crop_question 返回的物候配图,相册只展示这一张 */
+/** crop_question 物候配图,诊断报告封面复用 */
 const CROP_QUESTION_PIC_KEY = "CROP_QUESTION_PIC_URL";
 const guideVisible = ref(false);
 
-const defaultCover = require("@/assets/img/home/banner.png");
 const DEFAULT_CENTER = [113.6142086995688, 23.585836479509055];
 
 const activeNav = ref("patrol");
@@ -208,11 +234,11 @@ const navCards = [
         labelKey: "agriFile.patrolRecord",
         icon: require("@/assets/img/agricultural/icon-1.png"),
     },
-    // {
-    //     key: "album",
-    //     labelKey: "agriFile.agriAlbum",
-    //     icon: require("@/assets/img/agricultural/icon-2.png"),
-    // },
+    {
+        key: "album",
+        labelKey: "agriFile.agriAlbum",
+        icon: require("@/assets/img/agricultural/icon-2.png"),
+    },
     {
         key: "report",
         labelKey: "agriFile.diagnosisReport",
@@ -253,24 +279,9 @@ function resolveSelectedCropName() {
     return readEntryFarmData()?.crop || "";
 }
 
-function buildZoneList(coordinate = readEntryFarmCoordinate() || DEFAULT_CENTER) {
-    return [
-        {
-            id: 1,
-            name: "",
-            count: 1,
-            cover: defaultCover,
-            longitude: Number(coordinate[0]),
-            latitude: Number(coordinate[1]),
-        },
-    ];
-}
-
-const zoneList = ref(buildZoneList());
+const zoneList = ref([]);
 
-const albumList = ref([
-    { id: 1, name: "", count: 1, cover: defaultCover },
-]);
+const albumList = ref([]);
 
 function resolveCropQuestionImageUrl(path) {
     if (!path) return "";
@@ -282,55 +293,112 @@ function readStoredCropQuestionPic() {
     return localStorage.getItem(CROP_QUESTION_PIC_KEY) || "";
 }
 
-/** 相册地图气泡 + 下方卡片:只用 crop_question 的一张图,数量固定 1;名称用用户品类 */
-function applyCropQuestionAlbumCover(coverUrl) {
-    const cover = coverUrl || readStoredCropQuestionPic() || defaultCover;
-    const cropName = resolveSelectedCropName();
-    zoneList.value = zoneList.value.map((item) => ({
-        ...item,
-        name: cropName || item.name,
-        cover,
-        count: 1,
-    }));
-    albumList.value = albumList.value.map((item) => ({
-        ...item,
-        name: cropName || item.name,
-        cover,
-        count: 1,
-    }));
-}
-
-/** 拉取 crop_question 配图并缓存 */
+/** 拉取 crop_question 配图并缓存,诊断报告封面复用 */
 async function fetchCropQuestionAlbumPic() {
     const entry = readEntryFarmData();
     const crop = entry?.crop;
     const code = entry?.cropCode;
-    if (!crop || !code) {
-        applyCropQuestionAlbumCover();
-        return;
-    }
+    if (!crop || !code) return;
     try {
         const res = await VE_API.questionnaire.phenologyProblem({
             crop_type: crop,
             phenophase_code: String(code),
             code: String(code),
         });
-        if (res?.code !== 200 || !res.data) {
-            applyCropQuestionAlbumCover();
-            return;
-        }
+        if (res?.code !== 200 || !res.data) return;
         const url = resolveCropQuestionImageUrl(
             res.data.phenophase_pic_url || res.data.interact_pic_url
         );
         if (url) localStorage.setItem(CROP_QUESTION_PIC_KEY, url);
-        applyCropQuestionAlbumCover(url);
     } catch {
-        applyCropQuestionAlbumCover();
+        // ignore
+    }
+}
+
+function resolveAlbumPoint(data) {
+    const lng = Number(data?.longitude);
+    const lat = Number(data?.latitude);
+    if (Number.isFinite(lng) && Number.isFinite(lat)) return [lng, lat];
+    return readEntryFarmCoordinate() || DEFAULT_CENTER;
+}
+
+function buildEmptyAlbum() {
+    const coordinate = readEntryFarmCoordinate() || DEFAULT_CENTER;
+    return {
+        id: "album-empty",
+        name: resolveSelectedCropName(),
+        count: 0,
+        cover: "",
+        longitude: Number(coordinate[0]),
+        latitude: Number(coordinate[1]),
+    };
+}
+
+/** query_tmp_image:按分区名聚合;无分区名时归到用户品类,地图点用接口经纬度 */
+function mapTmpImageAlbum(data) {
+    const images = Array.isArray(data?.images) ? data.images : [];
+    const cropName = resolveSelectedCropName();
+    const [lng, lat] = resolveAlbumPoint(data);
+    const groups = new Map();
+    images.forEach((img) => {
+        if (!img?.cloud_url) return;
+        const name = String(img.zone_name || "").trim() || cropName || "";
+        if (!groups.has(name)) groups.set(name, []);
+        groups.get(name).push(img);
+    });
+    const albums = [...groups.entries()].map(([name, list], index) => ({
+        id: `${list[0]?.farm_id || "album"}-${index}`,
+        name,
+        count: list.length,
+        cover: list[0].cloud_url,
+        longitude: lng,
+        latitude: lat,
+    }));
+    if (!albums.length) {
+        return { albums: [buildEmptyAlbum()], zones: [] };
     }
+    const total = albums.reduce((sum, item) => sum + item.count, 0);
+    return {
+        albums,
+        zones: [
+            {
+                id: "album-point",
+                name: albums.length === 1 ? albums[0].name : cropName,
+                count: total,
+                cover: albums[0].cover,
+                longitude: lng,
+                latitude: lat,
+            },
+        ],
+    };
+}
+
+let tmpAlbumTask = null;
+
+/** 农情相册:query_tmp_image,手机号与诊断报告同一来源 */
+async function fetchTmpAlbumImages() {
+    if (tmpAlbumTask) return tmpAlbumTask;
+    tmpAlbumTask = (async () => {
+        const tel = resolveUserTel();
+        let data = null;
+        if (tel) {
+            try {
+                data = unwrapApiData(await VE_API.questionnaire.queryTmpImage({ tel }));
+            } catch {
+                data = null;
+            }
+        }
+        const mapped = mapTmpImageAlbum(data);
+        albumList.value = mapped.albums;
+        zoneList.value = mapped.zones;
+    })().finally(() => {
+        tmpAlbumTask = null;
+    });
+    return tmpAlbumTask;
 }
 
 const reportCover = require("@/assets/img/common/sd-1.jpg");
-/** 诊断报告列表:1 风险报告 / 2 胁迫报告 / 3 种植诊断报告 */
+/** 诊断报告列表:风险报告 / 胁迫报告 / 异常农情研判报告 */
 const reportList = ref([]);
 
 function resolveReportCover() {
@@ -358,24 +426,19 @@ function pickReportDate(data) {
     return data?.create_time || data?.update_time || data?.date || data?.report_date || "";
 }
 
-/** 从诊断报告 markdown 取标题与首段简介 */
-function pickDiagnosisCard(data) {
-    const md = data?.report;
-    if (!md || typeof md !== "string") return null;
-    const titleMatch = md.match(/^#\s+(.+)$/m);
-    const title = (titleMatch?.[1] || "").trim() || t("agriFile.plantingDiagnosisReport");
-    const intro = md
-        .split(/\n+/)
-        .map((line) => line.trim())
-        .find((line) => line && !line.startsWith("#")) || "";
-    return {
-        title,
-        desc: intro,
-        date: pickReportDate(data),
-    };
+/** 异常研判报告:取 summary 首段正文作卡片简介 */
+function pickAbnormalReportDesc(summary) {
+    const text = String(summary || "").trim();
+    if (!text) return "";
+    return (
+        text
+            .split(/\n+/)
+            .map((line) => line.trim())
+            .find((line) => line && !/^[一二三四五六七八九十]+[、..]/.test(line)) || text
+    );
 }
 
-/** 拼装诊断报告 tab:risk_report / stress_report / report.check */
+/** 拼装诊断报告 tab:risk_report / stress_report / query_abnormal_report */
 async function fetchReportList() {
     const tel = resolveUserTel();
     const cover = resolveReportCover();
@@ -387,20 +450,21 @@ async function fetchReportList() {
         return;
     }
 
-    const [riskRaw, stressRaw, diagnosisRaw] = await Promise.all([
+    const [riskRaw, stressRaw, abnormalRaw] = await Promise.all([
         VE_API.questionnaire.riskReport({ tel }).catch(() => null),
         VE_API.questionnaire.stressReport({ tel }).catch(() => null),
-        VE_API.questionnaire.checkReportGenerated({ tel }).catch(() => null),
+        VE_API.questionnaire.queryAbnormalReport({ tel }).catch(() => null),
     ]);
 
     const riskData = unwrapApiData(riskRaw);
     const stressData = unwrapApiData(stressRaw);
-    const diagnosisRes = Array.isArray(diagnosisRaw) ? diagnosisRaw[0] : diagnosisRaw;
-    const diagnosisData =
-        Number(diagnosisRes?.code) === 200 && diagnosisRes?.data ? diagnosisRes.data : null;
-    const diagnosisCard = pickDiagnosisCard(diagnosisData);
+    const abnormalRes = Array.isArray(abnormalRaw) ? abnormalRaw[0] : abnormalRaw;
+    const abnormalReports =
+        Number(abnormalRes?.code) === 200 && Array.isArray(abnormalRes?.data?.reports)
+            ? abnormalRes.data.reports
+            : [];
 
-    reportList.value = [
+    const baseList = [
         {
             id: 1,
             type: "risk",
@@ -421,15 +485,21 @@ async function fetchReportList() {
             date: pickReportDate(stressData),
             cover,
         },
-        {
-            id: 3,
-            type: "diagnosis",
-            title: diagnosisCard?.title || "",
-            desc: diagnosisCard?.desc || "",
-            date: diagnosisCard?.date || "",
-            cover,
-        },
     ].filter((item) => item.title || item.desc);
+
+    const abnormalList = abnormalReports
+        .filter((report) => report?.title || report?.summary)
+        .map((report) => ({
+            id: `abnormal-${report.id}`,
+            reportId: report.id,
+            type: "abnormal_report",
+            title: report.title || "",
+            desc: pickAbnormalReportDesc(report.summary),
+            date: report.created_time || "",
+            cover,
+        }));
+
+    reportList.value = [...baseList, ...abnormalList];
 }
 
 const showUploadPopup = ref(false);
@@ -591,22 +661,19 @@ const bindZoneOverlays = () => {
     });
 };
 
-const syncZoneFromEntry = () => {
-    const coordinate = readEntryFarmCoordinate() || getFarmCenter();
-    const cropName = resolveSelectedCropName() || zoneList.value[0]?.name || "";
-    const cover = readStoredCropQuestionPic() || zoneList.value[0]?.cover || defaultCover;
-    zoneList.value = buildZoneList(coordinate).map((item) => ({
-        ...item,
-        name: cropName,
-        cover,
-        count: 1,
-    }));
+const focusAlbumPoint = () => {
+    const point = zoneList.value[0];
+    if (!point || point.longitude == null || point.latitude == null || !fileMap.kmap?.getView) return;
+    fileMap.kmap.getView().animate({
+        center: [Number(point.longitude), Number(point.latitude)],
+        zoom: 16,
+        duration: 0,
+    });
 };
 
 const initAlbumMap = async () => {
     await nextTick();
     if (!mapContainer.value) return;
-    syncZoneFromEntry();
     const center = getFarmCenter();
     fileMap.initMap(`POINT(${center[0]} ${center[1]})`, mapContainer.value);
     await nextTick();
@@ -615,15 +682,14 @@ const initAlbumMap = async () => {
         albumList.value = [];
         return;
     }
-    applyCropQuestionAlbumCover();
+    await fetchTmpAlbumImages();
+    await nextTick();
     bindZoneOverlays();
+    focusAlbumPoint();
     fileMap.kmap?.map?.updateSize?.();
 };
 
 const fillMockLabels = () => {
-    const cropName = resolveSelectedCropName();
-    zoneList.value = zoneList.value.map((item) => ({ ...item, name: cropName }));
-    albumList.value = albumList.value.map((item) => ({ ...item, name: cropName }));
     reportList.value = reportList.value.map((item) => ({
         ...item,
         title: t("agriFile.reportTitle"),
@@ -703,8 +769,11 @@ async function loadPatrolContent() {
     await fetchPatrolGrowthInteract();
     await fetchPatrolAbnormalInteract();
     await fetchCropQuestionAlbumPic();
+    await fetchTmpAlbumImages();
     await fetchReportList();
     await fetchCropArchiveList();
+    await nextTick();
+    bindZoneOverlays();
 }
 
 const goAddZone = (type) => {
@@ -735,11 +804,16 @@ const goGrowthTrack = (item) => {
     });
 };
 
-/** 诊断报告:风险/胁迫进详情页,种植诊断报告进报告页 */
+/** 诊断报告:风险/胁迫/异常研判进详情页,种植诊断进报告页 */
 const goReportDetail = (item) => {
     if (!item) return;
     if (item.type === "diagnosis") {
-        router.push("/diagnosis_report");
+        router.push({
+            path: "/diagnosis_report",
+            query: {
+                id: item.reportId || item.id || "",
+            },
+        });
         return;
     }
     router.push({
@@ -747,6 +821,7 @@ const goReportDetail = (item) => {
         query: {
             title: item.title || "具体预警",
             detailType: item.type || "",
+            id: item.reportId || item.id || "",
         },
     });
 };
@@ -848,14 +923,18 @@ const goAlbumDetail = (item) => {
 onMounted(() => {
     initAlbumMap();
     tryShowGuide();
+    tryShowInspectSuccessPopup();
+    eventBus.on(INSPECT_SUCCESS_EVENT, openInspectSuccessPopup);
     document.addEventListener("click", handleOutsideClick, true);
 });
 onActivated(() => {
     loadPatrolContent();
     initAlbumMap();
     tryShowGuide();
+    tryShowInspectSuccessPopup();
 });
 onBeforeUnmount(() => {
+    eventBus.off(INSPECT_SUCCESS_EVENT, openInspectSuccessPopup);
     clearMapOverlays();
     document.removeEventListener("click", handleOutsideClick, true);
 });

+ 58 - 49
src/views/old_mini/agri_file/pages/albumMap.vue

@@ -31,11 +31,8 @@ const store = useStore();
 const mapContainer = ref(null);
 const fileMap = new FileMap();
 
-const defaultCover = require("@/assets/img/home/banner.png");
 const DEFAULT_MAP_LOCATION = "POINT(113.6142086995688 23.585836479509055)";
 const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
-/** 与农情档案相册页共用:crop_question 配图 */
-const CROP_QUESTION_PIC_KEY = "CROP_QUESTION_PIC_URL";
 
 const userLocation = ref(
     store.state.home.miniUserLocation ||
@@ -43,29 +40,6 @@ const userLocation = ref(
         "113.61702297075017,23.584863449735067"
 );
 
-/** 不展示分区范围与分区名称,仅保留照片点 */
-const MOCK_ZONES = [];
-
-const MOCK_PHOTOS = [
-    { id: 1, dlng: 0, dlat: 0 },
-];
-
-function readAlbumCover() {
-    return localStorage.getItem(CROP_QUESTION_PIC_KEY) || defaultCover;
-}
-
-function squarePolygonWkt(lng, lat, delta = 0.0014) {
-    const ring = [
-        [lng - delta, lat + delta * 0.7],
-        [lng + delta * 0.35, lat + delta],
-        [lng + delta, lat - delta * 0.2],
-        [lng + delta * 0.15, lat - delta],
-        [lng - delta * 0.85, lat - delta * 0.45],
-        [lng - delta, lat + delta * 0.7],
-    ];
-    return `POLYGON((${ring.map((point) => point.join(" ")).join(", ")}))`;
-}
-
 function getFarmData() {
     try {
         return JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
@@ -148,34 +122,69 @@ function getDefaultMapCoordinate() {
     return util.wktCastGeom(getDefaultMapLocation()).getFirstCoordinate();
 }
 
-const loadAlbumLayers = () => {
-    const [lng, lat] = getDefaultMapCoordinate();
-    const labels = MOCK_ZONES.map((item) => ({
-        name: t(item.nameKey),
-        longitude: lng + item.dlng,
-        latitude: lat + item.dlat,
-    }));
-    // 与外面农情相册一致:crop_question 图 + 数量 1
-    const cover = readAlbumCover();
-    const photos = MOCK_PHOTOS.map((item) => ({
-        count: 1,
-        cover,
-        longitude: lng + item.dlng,
-        latitude: lat + item.dlat,
-    }));
-    const zoneRecords = MOCK_ZONES.map((item) => ({
-        zone_name: "",
-        polygon: squarePolygonWkt(lng + item.dlng, lat + item.dlat, item.delta),
-    }));
-    fileMap.setRecordPolygons(zoneRecords, "album");
-    fileMap.setAlbumMarkers({ labels, photos });
+function resolveAlbumTel() {
+    try {
+        const data = JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
+        if (data?.phone) return String(data.phone).trim();
+    } catch {
+        // ignore
+    }
+    try {
+        const userInfo = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
+        return String(userInfo.tel || userInfo.phone || userInfo.mobile || "").trim();
+    } catch {
+        return "";
+    }
+}
+
+const albumCenter = ref(null);
+
+const loadAlbumLayers = async () => {
+    const tel = resolveAlbumTel();
+    let center = getDefaultMapCoordinate();
+    let photos = [];
+    if (tel) {
+        try {
+            const res = await VE_API.questionnaire.queryTmpImage({ tel });
+            const payload = Array.isArray(res) ? res[0] : res;
+            if (Number(payload?.code) === 200 && payload.data) {
+                const images = Array.isArray(payload.data.images) ? payload.data.images : [];
+                const lng = Number(payload.data.longitude);
+                const lat = Number(payload.data.latitude);
+                if (Number.isFinite(lng) && Number.isFinite(lat)) center = [lng, lat];
+                const cover = images.find((item) => item?.cloud_url)?.cloud_url || "";
+                if (images.length && cover) {
+                    photos = [
+                        {
+                            count: images.length,
+                            cover,
+                            longitude: center[0],
+                            latitude: center[1],
+                        },
+                    ];
+                }
+            }
+        } catch {
+            // ignore
+        }
+    }
+    fileMap.setRecordPolygons([], "album");
+    fileMap.setAlbumMarkers({ labels: [], photos });
+    albumCenter.value = center;
+    if (fileMap.kmap?.getView) {
+        fileMap.kmap.getView().animate({
+            center,
+            zoom: 16,
+            duration: 0,
+        });
+    }
 };
 
 const initAlbumMap = async () => {
     await nextTick();
     if (!mapContainer.value) return;
     fileMap.initMap(getDefaultMapLocation(), mapContainer.value);
-    loadAlbumLayers();
+    await loadAlbumLayers();
     fileMap.kmap?.map?.updateSize?.();
 };
 
@@ -191,7 +200,7 @@ const handleLocationChange = (payload) => {
 const handleLocate = () => {
     if (!fileMap.kmap) return;
     fileMap.kmap.getView().animate({
-        center: getDefaultMapCoordinate(),
+        center: albumCenter.value || getDefaultMapCoordinate(),
         zoom: 16,
         duration: 0,
     });

+ 2 - 2
src/views/old_mini/agri_file/pages/diagnosisReport.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="diagnosis-report-page">
-        <custom-header :name="t('agriFile.initialReport')" :isGoBack="true" @goback="handleBack" />
+        <!-- <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>
@@ -340,7 +340,7 @@ onMounted(() => {
 }
 
 .diagnosis-report-body {
-    padding: 10px 10px 60px;
+    padding: 10px 10px 20px;
     max-height: calc(100vh - 40px);
     overflow: auto;
     box-sizing: border-box;

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

@@ -0,0 +1,420 @@
+<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>

+ 79 - 4
src/views/old_mini/agri_file/pages/growthTrack.vue

@@ -121,6 +121,7 @@ import customHeader from "@/components/customHeader.vue";
 import albumUploadPopup from "../components/albumUploadPopup.vue";
 import leaveConfirmPopup from "@/components/popup/leaveConfirmPopup.vue";
 import tipPopup from "@/components/popup/tipPopup.vue";
+import eventBus from "@/api/eventBus";
 import { useI18n } from "@/i18n";
 import { base_img_url2 } from "@/api/config";
 
@@ -129,6 +130,9 @@ const route = useRoute();
 const router = useRouter();
 
 const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
+/** 异常巡检成功后,回农事档案页弹出成功提示 */
+const INSPECT_SUCCESS_KEY = "AGRI_INSPECT_UPLOAD_SUCCESS";
+const INSPECT_SUCCESS_EVENT = "inspect-upload-success";
 
 const isAbnormal = computed(() => route.query.type === "abnormal");
 const pageTitle = computed(() => route.query.pageName || "巡园要点");
@@ -190,11 +194,23 @@ function readEntryFarmData() {
 
 const interactImg = ref("");
 
-function resolveGuideImg(item) {
-    const raw = item?.url || "";
+function resolveImageUrl(raw) {
     if (!raw || typeof raw !== "string") return "";
     if (/^https?:\/\//i.test(raw) || raw.startsWith("data:")) return raw;
-    return `${base_img_url2}${raw}`;
+    return `${base_img_url2}${raw.replace(/^\//, "")}`;
+}
+
+function resolveGuideImg(item) {
+    return resolveImageUrl(item?.url || "");
+}
+
+function unwrapApiRes(raw) {
+    return Array.isArray(raw) ? raw[0] : raw;
+}
+
+function isApiOk(res) {
+    const code = Number(res?.code);
+    return code === 200 || code === 0 || code === 1;
 }
 
 function applyInteractData(data) {
@@ -339,7 +355,66 @@ const handleSubmit = () => {
     router.replace("/agri_file");
 };
 
-const handleUploadConfirm = () => {
+/** 异常巡检:组装 report/inspect 入参 */
+function buildInspectPayload(images) {
+    const entry = readEntryFarmData();
+    const rawFarmId = entry?.farmId ?? entry?.farm_id;
+    const farmId = rawFarmId == null || rawFarmId === "" ? undefined : Number(rawFarmId);
+    return {
+        crop_category_name: entry?.crop || "",
+        phenophase_code: entry?.cropCode != null ? String(entry.cropCode) : "",
+        risk_name: entry?.weatherStress || entry?.weatherRisk || "",
+        tel: entry?.phone || "",
+        ...(Number.isFinite(farmId) ? { farm_id: farmId } : {}),
+        images: (images || []).map(resolveImageUrl).filter(Boolean),
+    };
+}
+
+/** 异常巡检:先提示并关页;inspect 成功后再拉异常报告,成功后弹窗 */
+function startAbnormalInspect(images) {
+    const payload = buildInspectPayload(images);
+    if (!payload.crop_category_name || !payload.phenophase_code || !payload.risk_name || !payload.tel) {
+        ElMessage.warning("缺少农场信息,无法提交巡检");
+        return;
+    }
+    if (!payload.images.length) {
+        ElMessage.warning("请先上传照片");
+        return;
+    }
+
+    ElMessage.success("报告正在生成中...");
+    router.replace("/agri_file");
+
+    VE_API.questionnaire
+        .inspect(payload)
+        .then((raw) => {
+            const res = unwrapApiRes(raw);
+            if (!isApiOk(res)) {
+                ElMessage.error(res?.message || res?.msg || "提交失败,请稍后再试");
+                return null;
+            }
+            return VE_API.questionnaire.queryAbnormalReport({ tel: payload.tel });
+        })
+        .then((raw) => {
+            if (raw == null) return;
+            const res = unwrapApiRes(raw);
+            if (!isApiOk(res)) {
+                ElMessage.error(res?.message || res?.msg || "报告查询失败,请稍后再试");
+                return;
+            }
+            sessionStorage.setItem(INSPECT_SUCCESS_KEY, "1");
+            eventBus.emit(INSPECT_SUCCESS_EVENT);
+        })
+        .catch(() => {
+            ElMessage.error("提交失败,请稍后再试");
+        });
+}
+
+const handleUploadConfirm = ({ images } = {}) => {
+    if (isAbnormal.value) {
+        startAbnormalInspect(images);
+        return;
+    }
     showSuccessPopup.value = true;
 };
 

+ 42 - 18
src/views/old_mini/agri_file/pages/regionAlbums.vue

@@ -76,8 +76,6 @@ const { t } = useI18n();
 const route = useRoute();
 const router = useRouter();
 
-/** 与外面农情相册一致:只用 crop_question 缓存的一张图 */
-const CROP_QUESTION_PIC_KEY = "CROP_QUESTION_PIC_URL";
 const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
 
 const loading = ref(false);
@@ -96,8 +94,26 @@ function readEntryCropName() {
     }
 }
 
-function readCropQuestionPic() {
-    return localStorage.getItem(CROP_QUESTION_PIC_KEY) || "";
+function resolveUserTel() {
+    try {
+        const data = JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
+        if (data?.phone) return String(data.phone).trim();
+    } catch {
+        // ignore
+    }
+    try {
+        const userInfo = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
+        return String(userInfo.tel || userInfo.phone || userInfo.mobile || "").trim();
+    } catch {
+        return "";
+    }
+}
+
+/** 卡片名是分区名,没有分区名时用用户品类 */
+function belongsToAlbum(img, albumName, cropName) {
+    if (!albumName) return true;
+    const name = String(img?.zone_name || "").trim() || cropName || "";
+    return name === albumName;
 }
 
 const regionName = computed(
@@ -176,7 +192,7 @@ const isEmpty = computed(
     () => forceEmpty.value || (!loading.value && !imageList.value.length)
 );
 
-/** 仅展示外面缓存的那一张 crop_question 配图 */
+/** 与农情档案相册页同一接口:query_tmp_image */
 const fetchAlbumImages = async () => {
     if (forceEmpty.value) {
         imageList.value = [];
@@ -185,24 +201,32 @@ const fetchAlbumImages = async () => {
     }
     loading.value = true;
     try {
-        const cover = readCropQuestionPic();
-        if (!cover) {
+        const tel = resolveUserTel();
+        if (!tel) {
             imageList.value = [];
             return;
         }
-        const today = new Date();
-        const uploadDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
-        imageList.value = [
-            {
-                id: "crop-question-cover",
-                filename: cover,
-                uploadDate,
-                url: cover,
-                fullUrl: cover,
+        const res = await VE_API.questionnaire.queryTmpImage({ tel });
+        const payload = Array.isArray(res) ? res[0] : res;
+        const images =
+            Number(payload?.code) === 200 && Array.isArray(payload?.data?.images)
+                ? payload.data.images
+                : [];
+        const cropName = readEntryCropName();
+        const albumName = String(route.query.name || route.query.regionName || "").trim();
+        imageList.value = images
+            .filter((img) => img?.cloud_url && belongsToAlbum(img, albumName, cropName))
+            .map((img) => ({
+                id: img.id,
+                filename: img.cloud_url,
+                uploadDate: String(img.created_time || "").slice(0, 10),
+                url: img.cloud_url,
+                fullUrl: img.cloud_url,
                 growText: "",
                 watermarkMsg: "",
-            },
-        ];
+            }));
+    } catch {
+        imageList.value = [];
     } finally {
         loading.value = false;
     }

+ 2 - 2
src/views/old_mini/entry_information/components/baInformation.vue

@@ -96,9 +96,9 @@ const HAS_FARM_KEY = "HAS_ENTRY_FARM";
 const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
 /** 默认定位:河北宁晋县 */
 /** 默认定位:广东省梅州 */
-const DEFAULT_POINT = "POINT(114.91863572509733 37.67702031436258)";
+const DEFAULT_POINT = "POINT (116.120276 24.230084)";
 /** 默认区县:广东省梅州 */
-const DEFAULT_COUNTY_CODE = "130528";
+const DEFAULT_COUNTY_CODE = "441402";
 const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 
 const router = useRouter();

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

@@ -34,8 +34,8 @@ const SESSION_KEY_RESIDENT = "ENTRY_RESIDENT_LOCATION";
 const SESSION_KEY_GROWTH = "GROWTH_REPORT_LOCATION";
 /** 默认定位:河北宁晋县 */
 /** 默认定位:广东省梅州 */
-const DEFAULT_POINT = "POINT(114.91863572509733 37.67702031436258)";
-const DEFAULT_USER_LOCATION = "114.91863572509733,37.67702031436258";
+const DEFAULT_POINT = "POINT(116.120276 24.230084)";
+const DEFAULT_USER_LOCATION = "116.120276 24.230084";
 const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 
 const router = useRouter();

+ 187 - 49
src/views/old_mini/growth_report/alert_detail.vue

@@ -6,48 +6,70 @@
             :class="{ 'alert-detail-body--with-bar': detailType === 'stress' }"
             v-loading="loading"
         >
-            <!-- 综合研判 -->
-            <div class="section-card">
-                <div class="section-card__title">综合研判</div>
-                <div class="section-card__desc">{{ analysis.summary }}</div>
-
-                <div class="crop-panel">
-                    <div class="crop-panel__head">
-                        <!-- <img class="crop-panel__cover" :src="analysis.crop.cover" alt="" /> -->
-                        <span class="crop-panel__name">{{ analysis.crop.name }}</span>
-                        <span class="crop-panel__tag">当前作物</span>
-                    </div>
-                    <div class="crop-panel__list">
+            <!-- 异常农情研判报告:四段文本 -->
+            <template v-if="isAbnormal">
+                <div
+                    v-for="section in abnormalSections"
+                    :key="section.key"
+                    class="section-card"
+                >
+                    <div class="section-card__title">{{ section.title }}</div>
+                    <div v-if="section.desc" class="section-card__desc">{{ section.desc }}</div>
+                    <div v-if="section.items.length" class="advice-list">
                         <div
-                            v-for="item in analysis.crop.metrics"
-                            :key="item.label"
-                            class="crop-metric"
+                            v-for="(item, idx) in section.items"
+                            :key="`${section.key}-${idx}`"
+                            class="advice-item"
                         >
-                            <img class="crop-metric__icon" :src="item.icon" alt="" />
-                            <div class="crop-metric__content">
-                                <div class="crop-metric__label">{{ item.label }}</div>
-                                <div class="crop-metric__value">{{ item.value }}</div>
+                            <div v-if="item.title" class="advice-item__title">{{ item.title }}</div>
+                            <div class="advice-item__desc">{{ item.desc }}</div>
+                        </div>
+                    </div>
+                </div>
+            </template>
+
+            <!-- 风险 / 胁迫:综合研判 + 农事建议 -->
+            <template v-else>
+                <div class="section-card">
+                    <div class="section-card__title">综合研判</div>
+                    <div class="section-card__desc">{{ analysis.summary }}</div>
+
+                    <div class="crop-panel">
+                        <div class="crop-panel__head">
+                            <span class="crop-panel__name">{{ analysis.crop.name }}</span>
+                            <span class="crop-panel__tag">当前作物</span>
+                        </div>
+                        <div class="crop-panel__list">
+                            <div
+                                v-for="item in analysis.crop.metrics"
+                                :key="item.label"
+                                class="crop-metric"
+                            >
+                                <img class="crop-metric__icon" :src="item.icon" alt="" />
+                                <div class="crop-metric__content">
+                                    <div class="crop-metric__label">{{ item.label }}</div>
+                                    <div class="crop-metric__value">{{ item.value }}</div>
+                                </div>
                             </div>
                         </div>
                     </div>
                 </div>
-            </div>
-
-            <!-- 农事建议(风险) / 巡园重要性(胁迫) -->
-            <div class="section-card" v-if="showBottomSection">
-                <div class="section-card__title">{{ bottomSectionTitle }}</div>
-                <div v-if="importanceDesc" class="section-card__desc">{{ importanceDesc }}</div>
-                <div v-else class="advice-list">
-                    <div
-                        v-for="item in adviceList"
-                        :key="item.id"
-                        class="advice-item"
-                    >
-                        <div class="advice-item__title">{{ item.title }}</div>
-                        <div class="advice-item__desc">{{ item.desc }}</div>
+
+                <div class="section-card" v-if="showBottomSection">
+                    <div class="section-card__title">{{ bottomSectionTitle }}</div>
+                    <div v-if="importanceDesc" class="section-card__desc">{{ importanceDesc }}</div>
+                    <div v-else class="advice-list">
+                        <div
+                            v-for="item in adviceList"
+                            :key="item.id"
+                            class="advice-item"
+                        >
+                            <div class="advice-item__title">{{ item.title }}</div>
+                            <div class="advice-item__desc">{{ item.desc }}</div>
+                        </div>
                     </div>
                 </div>
-            </div>
+            </template>
         </div>
 
         <!-- 当下胁迫:底部巡园引导 -->
@@ -64,22 +86,37 @@ import { computed, onActivated, onMounted, ref } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import customHeader from "@/components/customHeader.vue";
 
+// ---------------------------------------------------------------------------
+// Props / 路由
+// ---------------------------------------------------------------------------
 const route = useRoute();
 const router = useRouter();
+
+// ---------------------------------------------------------------------------
+// 常量
+// ---------------------------------------------------------------------------
 const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
 
-const pageTitle = computed(() => route.query.title || "预警详情");
-/** risk:气象风险;stress:当下胁迫 */
-const detailType = computed(() => String(route.query.detailType || "risk"));
+/** 异常研判四段:字段 → 展示标题 */
+const ABNORMAL_SECTION_META = [
+    { key: "summary", title: "综合研判" },
+    { key: "inspection", title: "巡园结果与分析" },
+    { key: "diagnosis", title: "诊断研判分析" },
+    { key: "suggestion", title: "农事建议" },
+];
 
 const phenologyIcon = require("@/assets/img/common/header-icon-3.png");
 const weatherIcon = require("@/assets/img/common/header-icon-1.png");
 const soilIcon = require("@/assets/img/common/header-icon-2.png");
 const cropCover = require("@/assets/img/home/plant.png");
 
+// ---------------------------------------------------------------------------
+// 页面状态
+// ---------------------------------------------------------------------------
 const loading = ref(false);
+const reportTitle = ref("");
 
-/** 综合研判 */
+/** 综合研判(risk / stress) */
 const analysis = ref({
     summary: "",
     crop: {
@@ -97,7 +134,17 @@ const analysis = ref({
 const adviceList = ref([]);
 /** 巡园重要性:importance_desc(stress) */
 const importanceDesc = ref("");
+/** 异常研判四段解析结果 */
+const abnormalSections = ref([]);
 
+// ---------------------------------------------------------------------------
+// 计算属性
+// ---------------------------------------------------------------------------
+const detailType = computed(() => String(route.query.detailType || "risk"));
+const isAbnormal = computed(() => detailType.value === "abnormal_report");
+const pageTitle = computed(
+    () => reportTitle.value || route.query.title || "预警详情"
+);
 const bottomSectionTitle = computed(() =>
     detailType.value === "stress" ? "巡园重要性" : "农事建议"
 );
@@ -105,6 +152,9 @@ const showBottomSection = computed(() =>
     detailType.value === "stress" ? !!importanceDesc.value : adviceList.value.length > 0
 );
 
+// ---------------------------------------------------------------------------
+// 工具函数
+// ---------------------------------------------------------------------------
 function readEntryFarmData() {
     try {
         return JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
@@ -124,6 +174,12 @@ function resolveUserTel() {
     }
 }
 
+function unwrapApiPayload(raw) {
+    const res = Array.isArray(raw) ? raw[0] : raw;
+    if (Number(res?.code) !== 200 || !res.data) return null;
+    return res.data;
+}
+
 function buildMetrics({ phenophaseDesc, middleLabel, middleValue, soilDesc }) {
     return [
         { label: "当前物候", value: phenophaseDesc || "", icon: phenologyIcon },
@@ -132,6 +188,48 @@ function buildMetrics({ phenophaseDesc, middleLabel, middleValue, soilDesc }) {
     ];
 }
 
+/** 去掉「一、」「二、」等章节序号行 */
+function stripChapterHeading(text = "") {
+    return String(text || "")
+        .replace(/^[一二三四五六七八九十]+[、..]\s*[^\n]*\n?/, "")
+        .trim();
+}
+
+/**
+ * 解析异常报告字段:支持 ### 子标题拆成条目;无 ### 则整段作为 desc
+ * @returns {{ desc: string, items: Array<{ title: string, desc: string }> }}
+ */
+function parseAbnormalField(rawText) {
+    const text = stripChapterHeading(rawText);
+    if (!text) return { desc: "", items: [] };
+
+    const parts = text.split(/(?=^#{2,3}\s+)/m).map((p) => p.trim()).filter(Boolean);
+    const hasHeading = parts.some((p) => /^#{2,3}\s+/.test(p));
+
+    if (!hasHeading) {
+        return { desc: text, items: [] };
+    }
+
+    const items = [];
+    let leadingDesc = "";
+
+    parts.forEach((part) => {
+        if (!/^#{2,3}\s+/.test(part)) {
+            leadingDesc = [leadingDesc, part].filter(Boolean).join("\n");
+            return;
+        }
+        const lines = part.split("\n");
+        const title = lines[0].replace(/^#{2,3}\s+/, "").replace(/^\d+\.\s*/, "").trim();
+        const desc = lines.slice(1).join("\n").trim();
+        if (title || desc) items.push({ title, desc });
+    });
+
+    return { desc: leadingDesc.trim(), items };
+}
+
+// ---------------------------------------------------------------------------
+// 业务逻辑:风险 / 胁迫 / 异常研判
+// ---------------------------------------------------------------------------
 function applyRiskReport(data) {
     if (!data || typeof data !== "object") return;
     const entry = readEntryFarmData();
@@ -189,22 +287,57 @@ function applyStressReport(data) {
     importanceDesc.value = data.importance_desc || "";
 }
 
+function applyAbnormalReport(report) {
+    if (!report || typeof report !== "object") return;
+    reportTitle.value = report.title || "";
+    abnormalSections.value = ABNORMAL_SECTION_META.map((meta) => {
+        const parsed = parseAbnormalField(report[meta.key]);
+        return {
+            key: meta.key,
+            title: meta.title,
+            desc: parsed.desc,
+            items: parsed.items,
+        };
+    }).filter((section) => section.desc || section.items.length);
+}
+
 async function requestReport(apiFn) {
-    // TODO: 接口有正式数据后改回 resolveUserTel()
     const tel = resolveUserTel();
     if (!tel) return null;
-    const raw = await apiFn({ tel });
-    const res = Array.isArray(raw) ? raw[0] : raw;
-    if (Number(res?.code) !== 200 || !res.data) return null;
-    return Array.isArray(res.data) ? res.data[0] : res.data;
+    const data = unwrapApiPayload(await apiFn({ tel }));
+    if (!data) return null;
+    return Array.isArray(data) ? data[0] : data;
+}
+
+/** 异常研判:data.reports[],按 query.id 匹配,缺省取第一条 */
+async function requestAbnormalReport() {
+    const tel = resolveUserTel();
+    if (!tel) return null;
+    const data = unwrapApiPayload(await VE_API.questionnaire.queryAbnormalReport({ tel }));
+    const reports = Array.isArray(data?.reports) ? data.reports : [];
+    if (!reports.length) return null;
+
+    const targetId = route.query.id;
+    if (targetId !== undefined && targetId !== null && String(targetId) !== "") {
+        const matched = reports.find((item) => String(item?.id) === String(targetId));
+        if (matched) return matched;
+    }
+    return reports[0];
 }
 
-/** 按 detailType 拉取详情:risk → risk_report;stress → stress_report */
+/** 按 detailType 拉取详情 */
 async function fetchAlertDetail() {
     loading.value = true;
     adviceList.value = [];
     importanceDesc.value = "";
+    abnormalSections.value = [];
+    reportTitle.value = "";
     try {
+        if (isAbnormal.value) {
+            const report = await requestAbnormalReport();
+            if (report) applyAbnormalReport(report);
+            return;
+        }
         if (detailType.value === "stress") {
             const data = await requestReport(VE_API.questionnaire.stressReport);
             if (data) applyStressReport(data);
@@ -219,9 +352,9 @@ async function fetchAlertDetail() {
     }
 }
 
-onMounted(fetchAlertDetail);
-onActivated(fetchAlertDetail);
-
+// ---------------------------------------------------------------------------
+// 事件 / 生命周期
+// ---------------------------------------------------------------------------
 function handleGoPatrol() {
     router.push({
         path: "/growth_track",
@@ -231,6 +364,9 @@ function handleGoPatrol() {
         },
     });
 }
+
+onMounted(fetchAlertDetail);
+onActivated(fetchAlertDetail);
 </script>
 
 <style lang="scss" scoped>
@@ -263,7 +399,6 @@ function handleGoPatrol() {
     border-radius: 12px;
     background: linear-gradient(180deg, #FFDFC5 -13%, #FFFFFF 38%);
     box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.1);
-    // background: linear-gradient(90deg, #fff7f0 0%, #ffffff 100%);
     box-sizing: border-box;
 
     &__icon {
@@ -435,12 +570,15 @@ function handleGoPatrol() {
     }
 
     &__desc {
-        margin-top: 6px;
         font-size: 13px;
         line-height: 20px;
         color: rgba(6, 6, 6, 0.55);
         word-break: break-all;
         white-space: pre-line;
     }
+
+    &__title + &__desc {
+        margin-top: 6px;
+    }
 }
 </style>