소스 검색

feat:修改报告接口对接

wangsisi 5 일 전
부모
커밋
4d522d849e
4개의 변경된 파일과 276개의 추가작업 그리고 171개의 파일을 삭제
  1. 0 1
      agriculture-demo
  2. 9 16
      src/views/old_mini/agri_file/index.vue
  3. 5 4
      src/views/old_mini/agri_file/pages/growthTrack.vue
  4. 262 150
      src/views/old_mini/growth_report/alert_detail.vue

+ 0 - 1
agriculture-demo

@@ -1 +0,0 @@
-体验版

+ 9 - 16
src/views/old_mini/agri_file/index.vue

@@ -458,11 +458,7 @@ async function fetchReportList() {
 
     const riskData = unwrapApiData(riskRaw);
     const stressData = unwrapApiData(stressRaw);
-    const abnormalRes = Array.isArray(abnormalRaw) ? abnormalRaw[0] : abnormalRaw;
-    const abnormalReports =
-        Number(abnormalRes?.code) === 200 && Array.isArray(abnormalRes?.data?.reports)
-            ? abnormalRes.data.reports
-            : [];
+    const abnormalReports = abnormalRaw?.code === 200 && abnormalRaw?.data?.report
 
     const baseList = [
         {
@@ -487,17 +483,14 @@ async function fetchReportList() {
         },
     ].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,
-        }));
+    const abnormalList = [{
+        id: `abnormal-${abnormalReports.id}`,
+        reportId: abnormalReports.id,
+        type: "abnormal_report",
+        title: "异常农情诊断报告",
+        desc: abnormalReports.explain_simple || "",
+        cover,
+    }]
 
     reportList.value = [...baseList, ...abnormalList];
 }

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

@@ -102,7 +102,7 @@
         </div>
 
         <album-upload-popup v-model:show="showUploadPopup" @confirm="handleUploadConfirm" />
-        <tip-popup v-model:show="showSuccessPopup" type="executeSuccess" text="您已上传成功" text2="请等待诊断报告生成" hideBtn>
+        <tip-popup v-model:show="showSuccessPopup" type="executeSuccess" text="您已上传成功" text2="诊断报告已生成" hideBtn>
             <template #footer>
                 <div class="bottom-btn-container">
                     <div class="bottom-btn primary-btn" @click="handleComplete">完成</div>
@@ -355,15 +355,16 @@ const handleSubmit = () => {
     router.replace("/agri_file");
 };
 
-/** 异常巡检:组装 report/inspect 入参 */
+/** 异常巡检:组装 report/inspect 入参(区县取新增农场时选的 adcode) */
 function buildInspectPayload(images) {
     const entry = readEntryFarmData();
     const rawFarmId = entry?.farmId ?? entry?.farm_id;
     const farmId = rawFarmId == null || rawFarmId === "" ? undefined : Number(rawFarmId);
+    const countyId = entry?.adcode != null && entry.adcode !== "" ? String(entry.adcode) : "";
     return {
         crop_category_name: entry?.crop || "",
         phenophase_code: entry?.cropCode != null ? String(entry.cropCode) : "",
-        risk_name: entry?.weatherStress || entry?.weatherRisk || "",
+        county_id: countyId,
         tel: entry?.phone || "",
         ...(Number.isFinite(farmId) ? { farm_id: farmId } : {}),
         images: (images || []).map(resolveImageUrl).filter(Boolean),
@@ -373,7 +374,7 @@ function buildInspectPayload(images) {
 /** 异常巡检:先提示并关页;inspect 成功后再拉异常报告,成功后弹窗 */
 function startAbnormalInspect(images) {
     const payload = buildInspectPayload(images);
-    if (!payload.crop_category_name || !payload.phenophase_code || !payload.risk_name || !payload.tel) {
+    if (!payload.crop_category_name || !payload.phenophase_code || !payload.county_id || !payload.tel) {
         ElMessage.warning("缺少农场信息,无法提交巡检");
         return;
     }

+ 262 - 150
src/views/old_mini/growth_report/alert_detail.vue

@@ -6,70 +6,81 @@
             :class="{ 'alert-detail-body--with-bar': detailType === 'stress' }"
             v-loading="loading"
         >
-            <!-- 异常农情研判报告:四段文本 -->
-            <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">
+            <!-- 综合研判:interact_explain + crop_name / phenophase_desc / risk_desc|stress_desc / soil_desc -->
+            <div class="section-card" v-if="hasAnalysisSection">
+                <div class="section-card__title">综合研判</div>
+                <div v-if="analysis.summary" class="section-card__desc">{{ analysis.summary }}</div>
+
+                <div class="crop-panel" v-if="analysis.crop.name || hasCropMetrics">
+                    <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">
                         <div
-                            v-for="(item, idx) in section.items"
-                            :key="`${section.key}-${idx}`"
-                            class="advice-item"
+                            v-for="item in analysis.crop.metrics"
+                            :key="item.label"
+                            class="crop-metric"
                         >
-                            <div v-if="item.title" class="advice-item__title">{{ item.title }}</div>
-                            <div class="advice-item__desc">{{ item.desc }}</div>
+                            <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>
-            </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>
+
+            <!-- 巡园结果与分析:result[] → title / desc + 图 -->
+            <div class="section-card" v-if="resultList.length">
+                <div class="section-card__title">巡园结果与分析</div>
+                <div class="result-list">
+                    <div
+                        v-for="(item, idx) in resultList"
+                        :key="`result-${idx}`"
+                        class="result-item"
+                    >
+                        <img v-if="item.image" class="result-item__img" :src="item.image" alt="" />
+                        <div class="result-item__body">
+                            <div v-if="item.title" class="result-item__title">{{ item.title }}</div>
+                            <div v-if="item.desc" class="result-item__desc">{{ item.desc }}</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>
+            </div>
+
+            <!-- 诊断研判分析:analysis[] → title / desc -->
+            <div class="section-card" v-if="analysisList.length">
+                <div class="section-card__title">诊断研判分析</div>
+                <div class="advice-list">
+                    <div
+                        v-for="(item, idx) in analysisList"
+                        :key="`analysis-${idx}`"
+                        class="advice-item advice-item--accent"
+                    >
+                        <div v-if="item.title" class="advice-item__title">{{ item.title }}</div>
+                        <div v-if="item.desc" class="advice-item__desc">{{ item.desc }}</div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 农事建议:farmwork_info[] → title / desc;胁迫仍用 importance_desc -->
+            <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 advice-item--accent"
+                    >
+                        <div v-if="item.title" class="advice-item__title">{{ item.title }}</div>
+                        <div v-if="item.desc" class="advice-item__desc">{{ item.desc }}</div>
                     </div>
                 </div>
-            </template>
+            </div>
         </div>
 
         <!-- 当下胁迫:底部巡园引导 -->
@@ -85,6 +96,7 @@
 import { computed, onActivated, onMounted, ref } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import customHeader from "@/components/customHeader.vue";
+import { base_img_url2 } from "@/api/config";
 
 // ---------------------------------------------------------------------------
 // Props / 路由
@@ -97,14 +109,6 @@ const router = useRouter();
 // ---------------------------------------------------------------------------
 const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
 
-/** 异常研判四段:字段 → 展示标题 */
-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");
@@ -116,7 +120,7 @@ const cropCover = require("@/assets/img/home/plant.png");
 const loading = ref(false);
 const reportTitle = ref("");
 
-/** 综合研判(risk / stress) */
+/** 综合研判 */
 const analysis = ref({
     summary: "",
     crop: {
@@ -130,12 +134,14 @@ const analysis = ref({
     },
 });
 
-/** 农事建议:farmwork_info(risk) */
+/** 巡园结果:result */
+const resultList = ref([]);
+/** 诊断研判:analysis */
+const analysisList = ref([]);
+/** 农事建议:farmwork_info */
 const adviceList = ref([]);
 /** 巡园重要性:importance_desc(stress) */
 const importanceDesc = ref("");
-/** 异常研判四段解析结果 */
-const abnormalSections = ref([]);
 
 // ---------------------------------------------------------------------------
 // 计算属性
@@ -151,6 +157,15 @@ const bottomSectionTitle = computed(() =>
 const showBottomSection = computed(() =>
     detailType.value === "stress" ? !!importanceDesc.value : adviceList.value.length > 0
 );
+const hasCropMetrics = computed(() =>
+    analysis.value.crop.metrics.some((item) => item.value)
+);
+const hasAnalysisSection = computed(
+    () =>
+        !!analysis.value.summary ||
+        !!analysis.value.crop.name ||
+        hasCropMetrics.value
+);
 
 // ---------------------------------------------------------------------------
 // 工具函数
@@ -180,6 +195,12 @@ function unwrapApiPayload(raw) {
     return res.data;
 }
 
+function resolveImageUrl(raw) {
+    if (!raw || typeof raw !== "string") return "";
+    if (/^https?:\/\//i.test(raw) || raw.startsWith("data:")) return raw;
+    return `${base_img_url2}${raw.replace(/^\//, "")}`;
+}
+
 function buildMetrics({ phenophaseDesc, middleLabel, middleValue, soilDesc }) {
     return [
         { label: "当前物候", value: phenophaseDesc || "", icon: phenologyIcon },
@@ -188,50 +209,63 @@ 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 }> }}
+ * 将数组 / 字典统一成 { title, desc, image? }[]
+ * 字典多个属性则逐条展示
  */
-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: [] };
+function normalizeTitleDescList(raw) {
+    if (raw == null || raw === "") return [];
+
+    let list = [];
+    if (Array.isArray(raw)) {
+        list = raw;
+    } else if (typeof raw === "object") {
+        const values = Object.values(raw);
+        const looksLikeItem =
+            "title" in raw ||
+            "desc" in raw ||
+            "农事名称" in raw ||
+            "内容信息" in raw ||
+            "farmwork_name" in raw ||
+            "name" in raw ||
+            "content" in raw;
+        list = looksLikeItem && !values.some((v) => v && typeof v === "object")
+            ? [raw]
+            : values.filter((item) => item != null);
+    } else if (typeof raw === "string") {
+        return [{ title: "", desc: raw, image: "" }];
     }
 
-    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 };
+    return list
+        .map((item, index) => {
+            if (item == null) return null;
+            if (typeof item === "string") {
+                return { id: index, title: "", desc: item, image: "" };
+            }
+            if (typeof item !== "object") return null;
+            const title =
+                item.title ||
+                item["农事名称"] ||
+                item.farmwork_name ||
+                item.name ||
+                "";
+            const desc =
+                item.desc ||
+                item["内容信息"] ||
+                item.content ||
+                item.description ||
+                "";
+            const image = resolveImageUrl(
+                item.url || item.image || item.img || item.pic || item.pic_url || ""
+            );
+            if (!title && !desc && !image) return null;
+            return { id: item.id ?? index, title: String(title), desc: String(desc), image };
+        })
+        .filter(Boolean);
 }
 
-// ---------------------------------------------------------------------------
-// 业务逻辑:风险 / 胁迫 / 异常研判
-// ---------------------------------------------------------------------------
-function applyRiskReport(data) {
-    if (!data || typeof data !== "object") return;
+/** 综合研判公共赋值 */
+function applyAnalysisPanel(data, { middleLabel, middleValue }) {
     const entry = readEntryFarmData();
     analysis.value = {
         summary: data.interact_explain || "",
@@ -240,65 +274,54 @@ function applyRiskReport(data) {
             cover: cropCover,
             metrics: buildMetrics({
                 phenophaseDesc: data.phenophase_desc,
-                middleLabel: "气象预警",
-                middleValue: data.risk_desc,
+                middleLabel,
+                middleValue,
                 soilDesc: data.soil_desc,
             }),
         },
     };
+}
 
+function applyResultAndAnalysis(data) {
+    resultList.value = normalizeTitleDescList(data?.result);
+    analysisList.value = normalizeTitleDescList(data?.analysis);
+}
+
+// ---------------------------------------------------------------------------
+// 业务逻辑:风险 / 胁迫 / 异常研判
+// ---------------------------------------------------------------------------
+function applyRiskReport(data) {
+    if (!data || typeof data !== "object") return;
+    applyAnalysisPanel(data, {
+        middleLabel: "气象预警",
+        middleValue: data.risk_desc,
+    });
+    applyResultAndAnalysis(data);
     importanceDesc.value = "";
-    const rawWorks = data.farmwork_info;
-    let works = [];
-    if (Array.isArray(rawWorks)) {
-        works = rawWorks;
-    } else if (rawWorks && typeof rawWorks === "object") {
-        // 接口可能返回单条对象,而非数组
-        if (rawWorks["农事名称"] || rawWorks["内容信息"] || rawWorks.farmwork_name) {
-            works = [rawWorks];
-        } else {
-            works = Object.values(rawWorks).filter((item) => item && typeof item === "object");
-        }
-    }
-    adviceList.value = works.map((item, index) => ({
-        id: item?.id ?? index,
-        title: item?.["农事名称"] || item?.farmwork_name || item?.name || "",
-        desc: item?.["内容信息"] || item?.content || item?.desc || "",
-    }));
+    adviceList.value = normalizeTitleDescList(data.farmwork_info);
 }
 
 function applyStressReport(data) {
     if (!data || typeof data !== "object") return;
-    const entry = readEntryFarmData();
-    analysis.value = {
-        summary: data.interact_explain || "",
-        crop: {
-            name: data.crop_name || entry?.crop || "",
-            cover: cropCover,
-            metrics: buildMetrics({
-                phenophaseDesc: data.phenophase_desc,
-                middleLabel: "当下胁迫",
-                middleValue: data.stress_desc,
-                soilDesc: data.soil_desc,
-            }),
-        },
-    };
-    adviceList.value = [];
+    applyAnalysisPanel(data, {
+        middleLabel: "当下胁迫",
+        middleValue: data.stress_desc || data.risk_desc,
+    });
+    applyResultAndAnalysis(data);
+    adviceList.value = normalizeTitleDescList(data.farmwork_info);
     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);
+    applyAnalysisPanel(report, {
+        middleLabel: "气象预警",
+        middleValue: report.risk_desc,
+    });
+    applyResultAndAnalysis(report);
+    importanceDesc.value = "";
+    adviceList.value = normalizeTitleDescList(report.farmwork_info);
 }
 
 async function requestReport(apiFn) {
@@ -309,12 +332,22 @@ async function requestReport(apiFn) {
     return Array.isArray(data) ? data[0] : data;
 }
 
-/** 异常研判:data.reports[],按 query.id 匹配,缺省取第一条 */
+/** 异常研判:兼容 data.reports[] / data.report / data 本体 */
 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 (!data) return null;
+
+    const reports = Array.isArray(data?.reports)
+        ? data.reports
+        : Array.isArray(data?.report)
+          ? data.report
+          : data?.report && typeof data.report === "object"
+            ? [data.report]
+            : Array.isArray(data)
+              ? data
+              : [data];
     if (!reports.length) return null;
 
     const targetId = route.query.id;
@@ -329,9 +362,23 @@ async function requestAbnormalReport() {
 async function fetchAlertDetail() {
     loading.value = true;
     adviceList.value = [];
+    resultList.value = [];
+    analysisList.value = [];
     importanceDesc.value = "";
-    abnormalSections.value = [];
     reportTitle.value = "";
+    analysis.value = {
+        summary: "",
+        crop: {
+            name: "",
+            cover: cropCover,
+            metrics: buildMetrics({
+                phenophaseDesc: "",
+                middleLabel: "气象预警",
+                middleValue: "",
+                soilDesc: "",
+            }),
+        },
+    };
     try {
         if (isAbnormal.value) {
             const report = await requestAbnormalReport();
@@ -549,6 +596,52 @@ onActivated(fetchAlertDetail);
     }
 }
 
+.result-list {
+    margin-top: 10px;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+}
+
+.result-item {
+    display: flex;
+    gap: 10px;
+    padding: 10px;
+    border-radius: 8px;
+    background: #F7F8FA;
+    box-sizing: border-box;
+
+    &__img {
+        flex: none;
+        width: 72px;
+        height: 72px;
+        border-radius: 6px;
+        object-fit: cover;
+        background: #e8e8e8;
+    }
+
+    &__body {
+        flex: 1;
+        min-width: 0;
+    }
+
+    &__title {
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 22px;
+        color: #000;
+    }
+
+    &__desc {
+        margin-top: 4px;
+        font-size: 13px;
+        line-height: 20px;
+        color: rgba(6, 6, 6, 0.55);
+        word-break: break-all;
+        white-space: pre-line;
+    }
+}
+
 .advice-list {
     margin-top: 10px;
     display: flex;
@@ -562,6 +655,25 @@ onActivated(fetchAlertDetail);
     background: #F7F8FA;
     box-sizing: border-box;
 
+    &--accent {
+        .advice-item__title {
+            position: relative;
+            padding-left: 8px;
+            color: #2199f8;
+
+            &::before {
+                content: "";
+                position: absolute;
+                left: 0;
+                top: 5px;
+                width: 3px;
+                height: 12px;
+                border-radius: 2px;
+                background: #2199f8;
+            }
+        }
+    }
+
     &__title {
         font-size: 14px;
         font-weight: 500;