|
|
@@ -1,19 +1,19 @@
|
|
|
<template>
|
|
|
<div class="alert-detail-page">
|
|
|
<custom-header :name="pageTitle" />
|
|
|
- <div class="alert-detail-body">
|
|
|
+ <div class="alert-detail-body" v-loading="loading">
|
|
|
<!-- 综合研判 -->
|
|
|
- <div class="section-card">
|
|
|
+ <div v-if="analysis.summary || analysis.crop.name" class="section-card">
|
|
|
<div class="section-card__title">综合研判</div>
|
|
|
- <div class="section-card__desc">{{ analysis.summary }}</div>
|
|
|
+ <div v-if="analysis.summary" class="section-card__desc">{{ analysis.summary }}</div>
|
|
|
|
|
|
- <div class="crop-panel">
|
|
|
+ <div v-if="analysis.crop.name || analysis.crop.metrics.length" 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">
|
|
|
+ <div v-if="analysis.crop.metrics.length" class="crop-panel__list">
|
|
|
<div
|
|
|
v-for="item in analysis.crop.metrics"
|
|
|
:key="item.label"
|
|
|
@@ -30,7 +30,7 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 巡园结果与分析 -->
|
|
|
- <div class="section-card">
|
|
|
+ <div v-if="patrolResults.length" class="section-card">
|
|
|
<div class="section-card__title">巡园结果与分析</div>
|
|
|
<div class="patrol-list">
|
|
|
<div
|
|
|
@@ -38,7 +38,7 @@
|
|
|
:key="item.id"
|
|
|
class="patrol-item"
|
|
|
>
|
|
|
- <img class="patrol-item__cover" :src="item.cover" alt="" />
|
|
|
+ <img v-if="item.cover" class="patrol-item__cover" :src="item.cover" alt="" />
|
|
|
<div class="patrol-item__body">
|
|
|
<div class="patrol-item__title">{{ item.title }}</div>
|
|
|
<div class="patrol-item__desc">{{ item.desc }}</div>
|
|
|
@@ -48,7 +48,7 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 诊断研判分析 -->
|
|
|
- <div class="section-card">
|
|
|
+ <div v-if="diagnosisList.length" class="section-card">
|
|
|
<div class="section-card__title">诊断研判分析</div>
|
|
|
<div class="diag-list">
|
|
|
<div
|
|
|
@@ -56,15 +56,15 @@
|
|
|
:key="item.id"
|
|
|
class="diag-item"
|
|
|
>
|
|
|
- <span class="diag-item__tag">{{ item.tag }}</span>
|
|
|
- <div class="diag-item__title">{{ item.title }}</div>
|
|
|
- <div class="diag-item__desc">{{ item.desc }}</div>
|
|
|
+ <span v-if="item.tag" class="diag-item__tag">{{ item.tag }}</span>
|
|
|
+ <div v-if="item.title" class="diag-item__title">{{ item.title }}</div>
|
|
|
+ <div v-if="item.desc" class="diag-item__desc">{{ item.desc }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 农事建议 -->
|
|
|
- <div class="section-card">
|
|
|
+ <div v-if="adviceList.length" class="section-card">
|
|
|
<div class="section-card__title">农事建议</div>
|
|
|
<div class="advice-list">
|
|
|
<div
|
|
|
@@ -82,92 +82,155 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { computed, ref } from "vue";
|
|
|
+import { computed, onMounted, ref } from "vue";
|
|
|
import { useRoute } from "vue-router";
|
|
|
import customHeader from "@/components/customHeader.vue";
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
-const pageTitle = computed(() => route.query.title || "具体预警/具体胁迫");
|
|
|
+const QUERY_PARAMS = {
|
|
|
+ farm_id: "321",
|
|
|
+ zone_id: "46",
|
|
|
+ date: "2026-08-11",
|
|
|
+};
|
|
|
+const DETAIL_TYPE = {
|
|
|
+ WARNING: "warning",
|
|
|
+ STRESS: "stress",
|
|
|
+};
|
|
|
|
|
|
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 patrolCover = require("@/assets/img/common/tp-3.png");
|
|
|
|
|
|
-/** 综合研判 */
|
|
|
+const detailType = computed(() =>
|
|
|
+ String(route.query.type || "") === DETAIL_TYPE.STRESS
|
|
|
+ ? DETAIL_TYPE.STRESS
|
|
|
+ : DETAIL_TYPE.WARNING
|
|
|
+);
|
|
|
+const detailTitle = ref("");
|
|
|
+const pageTitle = computed(
|
|
|
+ () => detailTitle.value || route.query.title || "具体预警/具体胁迫"
|
|
|
+);
|
|
|
+
|
|
|
const analysis = ref({
|
|
|
- summary:
|
|
|
- "综合巡园照片、当前物候、近期气象和土壤条件判断,本次异常以持续高温干旱造成的水分失衡为主,已表现为嫩梢萎蔫、卷曲和局部灼伤,并伴有浅层根区干燥",
|
|
|
+ summary: "",
|
|
|
crop: {
|
|
|
- name: "荔枝",
|
|
|
+ name: "",
|
|
|
cover: cropCover,
|
|
|
- metrics: [
|
|
|
- {
|
|
|
- label: "当前物候",
|
|
|
- value: "秋梢新叶速长—红黄叶旺长期",
|
|
|
- icon: phenologyIcon,
|
|
|
- },
|
|
|
- {
|
|
|
- label: "气象预警",
|
|
|
- value: "连续3天最高温36-38°C,近7天基本无有效降雨",
|
|
|
- icon: weatherIcon,
|
|
|
- },
|
|
|
- {
|
|
|
- label: "土壤类型",
|
|
|
- value: "酸性沙土,树盘及浅层根区明显偏干",
|
|
|
- icon: soilIcon,
|
|
|
- },
|
|
|
- ],
|
|
|
+ metrics: [],
|
|
|
},
|
|
|
});
|
|
|
+const patrolResults = ref([]);
|
|
|
+const diagnosisList = ref([]);
|
|
|
+const adviceList = ref([]);
|
|
|
+const loading = ref(false);
|
|
|
+
|
|
|
+/** 接口可能返回对象或数组,统一取第一条 */
|
|
|
+const pickFirstRecord = (res) => {
|
|
|
+ const data = res?.data ?? res;
|
|
|
+ if (Array.isArray(data)) return data[0] || null;
|
|
|
+ if (data && typeof data === "object") return data;
|
|
|
+ return null;
|
|
|
+};
|
|
|
+
|
|
|
+const buildMetrics = (record, type) => {
|
|
|
+ const isStress = type === DETAIL_TYPE.STRESS;
|
|
|
+ return [
|
|
|
+ { label: "当前物候", value: record.phenophase_desc || "", icon: phenologyIcon },
|
|
|
+ {
|
|
|
+ label: isStress ? "气象胁迫" : "气象预警",
|
|
|
+ value: (isStress ? record.stress_desc : record.risk_desc) || "",
|
|
|
+ icon: weatherIcon,
|
|
|
+ },
|
|
|
+ { label: "土壤类型", value: record.soil_desc || "", icon: soilIcon },
|
|
|
+ ].filter((item) => item.value);
|
|
|
+};
|
|
|
+
|
|
|
+/** 农事内容按「1. 标题。正文」拆成建议列表 */
|
|
|
+const parseFarmworkAdvice = (farmworkInfo) => {
|
|
|
+ if (!farmworkInfo || typeof farmworkInfo !== "object") return [];
|
|
|
+ const fallbackTitle = farmworkInfo["农事名称"] || "";
|
|
|
+ const content = String(farmworkInfo["内容信息"] || "").trim();
|
|
|
+ if (!content && !fallbackTitle) return [];
|
|
|
+
|
|
|
+ const blocks = content
|
|
|
+ .split(/\n(?=\d+\.\s*)/)
|
|
|
+ .map((s) => s.trim())
|
|
|
+ .filter(Boolean);
|
|
|
+
|
|
|
+ if (!blocks.length) {
|
|
|
+ return [{ id: 1, title: fallbackTitle, desc: content }];
|
|
|
+ }
|
|
|
|
|
|
-/** 巡园结果与分析 */
|
|
|
-const patrolResults = ref([
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- title: "嫩梢萎蔫与灼伤",
|
|
|
- cover: patrolCover,
|
|
|
- desc: "最嫩叶出现轻度皱缩、展开不完全,个别嫩梢顶部出现干尖。成熟叶未见明显连续叶缘焦枯,老叶也未见明显脉间黄化。",
|
|
|
- },
|
|
|
- {
|
|
|
- id: 2,
|
|
|
- title: "嫩梢萎蔫与灼伤",
|
|
|
- cover: patrolCover,
|
|
|
- desc: "最嫩叶出现轻度皱缩、展开不完全,个别嫩梢顶部出现干尖。成熟叶未见明显连续叶缘焦枯,老叶也未见明显脉间黄化。",
|
|
|
- },
|
|
|
-]);
|
|
|
-
|
|
|
-/** 诊断研判分析 */
|
|
|
-const diagnosisList = ref([
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- tag: "表观影响",
|
|
|
- title: "浅层吸收根已经受到干旱影响,但尚无腐烂迹象",
|
|
|
- desc: "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现",
|
|
|
- },
|
|
|
- {
|
|
|
- id: 2,
|
|
|
- tag: "表观影响",
|
|
|
- title: "浅层吸收根已经受到干旱影响,但尚无腐烂迹象",
|
|
|
- desc: "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现",
|
|
|
- },
|
|
|
-]);
|
|
|
-
|
|
|
-/** 农事建议 */
|
|
|
-const adviceList = ref([
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- title: "优先恢复根区水分",
|
|
|
- desc: "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现",
|
|
|
- },
|
|
|
- {
|
|
|
- id: 2,
|
|
|
- title: "暂不建议立即大剂量追肥",
|
|
|
- desc: "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现",
|
|
|
- },
|
|
|
-]);
|
|
|
+ return blocks.map((block, index) => {
|
|
|
+ const text = block.replace(/^\d+\.\s*/, "");
|
|
|
+ const sep = text.indexOf("。");
|
|
|
+ if (sep > 0 && sep < 40) {
|
|
|
+ return {
|
|
|
+ id: index + 1,
|
|
|
+ title: text.slice(0, sep),
|
|
|
+ desc: text.slice(sep + 1).trim(),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ id: index + 1,
|
|
|
+ title: fallbackTitle || `建议${index + 1}`,
|
|
|
+ desc: text,
|
|
|
+ };
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const applyRecord = (record, type) => {
|
|
|
+ if (!record) return;
|
|
|
+ const isStress = type === DETAIL_TYPE.STRESS;
|
|
|
+ detailTitle.value = isStress ? record.stress_name || "" : record.risk_name || "";
|
|
|
+
|
|
|
+ analysis.value = {
|
|
|
+ summary: record.interact_explain || "",
|
|
|
+ crop: {
|
|
|
+ name: record.crop_name || "",
|
|
|
+ cover: cropCover,
|
|
|
+ metrics: buildMetrics(record, type),
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ diagnosisList.value = [
|
|
|
+ {
|
|
|
+ id: record.id || 1,
|
|
|
+ tag: (isStress ? record.stress_type : record.risk_type) || "",
|
|
|
+ title: (isStress ? record.stress_name : record.risk_name) || "",
|
|
|
+ desc:
|
|
|
+ (isStress
|
|
|
+ ? record.importance_desc || record.explain_simple
|
|
|
+ : record.explain_simple) || "",
|
|
|
+ },
|
|
|
+ ].filter((item) => item.title || item.desc);
|
|
|
+
|
|
|
+ adviceList.value = parseFarmworkAdvice(record.farmwork_info);
|
|
|
+ patrolResults.value = [];
|
|
|
+};
|
|
|
+
|
|
|
+const fetchDetail = async () => {
|
|
|
+ const type = detailType.value;
|
|
|
+ const request =
|
|
|
+ type === DETAIL_TYPE.STRESS
|
|
|
+ ? VE_API.questionnaire.getWeatherStress
|
|
|
+ : VE_API.questionnaire.getWeatherRisk;
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await request(QUERY_PARAMS);
|
|
|
+ applyRecord(pickFirstRecord(res), type);
|
|
|
+ } catch {
|
|
|
+ // 详情页保持空态
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ fetchDetail();
|
|
|
+});
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
@@ -205,6 +268,7 @@ const adviceList = ref([
|
|
|
word-break: break-all;
|
|
|
text-align: justify;
|
|
|
font-weight: 350;
|
|
|
+ white-space: pre-line;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -402,6 +466,7 @@ const adviceList = ref([
|
|
|
line-height: 20px;
|
|
|
color: #4E5969;
|
|
|
word-break: break-all;
|
|
|
+ white-space: pre-line;
|
|
|
}
|
|
|
}
|
|
|
</style>
|