| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- <template>
- <div class="alert-detail-page">
- <custom-header :name="pageTitle" />
- <div class="alert-detail-body" v-loading="loading">
- <!-- 综合研判 -->
- <div v-if="analysis.summary || analysis.crop.name" class="section-card">
- <div class="section-card__title">综合研判</div>
- <div v-if="analysis.summary" class="section-card__desc">{{ analysis.summary }}</div>
- <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 v-if="analysis.crop.metrics.length" 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 v-if="patrolResults.length" class="section-card">
- <div class="section-card__title">巡园结果与分析</div>
- <div class="patrol-list">
- <div
- v-for="item in patrolResults"
- :key="item.id"
- class="patrol-item"
- >
- <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>
- </div>
- </div>
- </div>
- </div>
- <!-- 诊断研判分析 -->
- <div v-if="diagnosisList.length" class="section-card">
- <div class="section-card__title">诊断研判分析</div>
- <div class="diag-list">
- <div
- v-for="item in diagnosisList"
- :key="item.id"
- class="diag-item"
- >
- <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 v-if="adviceList.length" class="section-card">
- <div class="section-card__title">农事建议</div>
- <div 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>
- </div>
- </template>
- <script setup>
- import { computed, onMounted, ref } from "vue";
- import { useRoute } from "vue-router";
- import customHeader from "@/components/customHeader.vue";
- const route = useRoute();
- const FALLBACK_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 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: "",
- crop: {
- name: "",
- cover: cropCover,
- metrics: [],
- },
- });
- const patrolResults = ref([]);
- const diagnosisList = ref([]);
- const adviceList = ref([]);
- const loading = ref(false);
- const getSelectedFarm = () => {
- try {
- return JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
- } catch {
- return {};
- }
- };
- const formatLocalDate = (date) => {
- const y = date.getFullYear();
- const m = String(date.getMonth() + 1).padStart(2, "0");
- const d = String(date.getDate()).padStart(2, "0");
- return `${y}-${m}-${d}`;
- };
- /** 有选中农场用缓存 farm_id/zone_id + 当天日期,否则用默认参数 */
- const getQueryParams = () => {
- const farm = getSelectedFarm();
- const farmId = farm.farm_id ?? farm.id;
- const zoneId = farm.zone_id ?? farm.zoneId;
- if (farmId == null || farmId === "" || zoneId == null || zoneId === "") {
- return { ...FALLBACK_QUERY_PARAMS };
- }
- return {
- farm_id: farmId,
- zone_id: zoneId,
- date: formatLocalDate(new Date()),
- };
- };
- /** 接口可能返回对象或数组,统一取第一条 */
- 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 }];
- }
- 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(getQueryParams());
- applyRecord(pickFirstRecord(res), type);
- } catch {
- // 详情页保持空态
- } finally {
- loading.value = false;
- }
- };
- onMounted(() => {
- fetchDetail();
- });
- </script>
- <style lang="scss" scoped>
- .alert-detail-page {
- min-height: 100vh;
- background: #F2F5F6;
- }
- .alert-detail-body {
- padding: 10px 10px 24px;
- box-sizing: border-box;
- overflow-y: auto;
- height: calc(100vh - 44px);
- }
- .section-card {
- margin-bottom: 12px;
- padding: 10px;
- border-radius: 10px;
- background: #fff;
- box-sizing: border-box;
- &__title {
- font-size: 16px;
- font-weight: 500;
- line-height: 24px;
- color: #000;
- }
- &__desc {
- margin-top: 10px;
- font-size: 14px;
- line-height: 22px;
- color: rgba(6, 6, 6, 0.5);
- word-break: break-all;
- text-align: justify;
- font-weight: 350;
- white-space: pre-line;
- }
- }
- .crop-panel {
- margin-top: 12px;
- padding: 10px;
- border-radius: 10px;
- background: #E7F1FD;
- box-sizing: border-box;
- &__head {
- display: flex;
- align-items: center;
- gap: 6px;
- margin-bottom: 10px;
- }
- &__cover {
- width: 24px;
- height: 24px;
- border-radius: 50%;
- object-fit: cover;
- flex-shrink: 0;
- background: #fff;
- }
- &__name {
- font-size: 16px;
- font-weight: 500;
- color: #2199F8;
- }
- &__tag {
- padding: 0 5px;
- border-radius: 2px;
- background: #2199f8;
- backdrop-filter: blur(4px);
- color: #fff;
- font-size: 12px;
- height: 18px;
- line-height: 18px;
- flex-shrink: 0;
- }
- &__list {
- display: flex;
- flex-direction: column;
- gap: 10px;
- background: #fff;
- padding: 10px;
- border-radius: 10px;
- box-sizing: border-box;
- }
- }
- .crop-metric {
- display: flex;
- align-items: flex-start;
- gap: 6px;
- &__icon {
- width: 16px;
- height: 16px;
- flex-shrink: 0;
- object-fit: contain;
- filter: grayscale(1);
- flex: none;
- margin-top: 2px;
- }
- &__content {
- display: flex;
- align-items: flex-start;
- gap: 6px;
- flex: 1;
- min-width: 0;
- font-size: 14px;
- line-height: 22px;
- word-break: break-all;
- }
- &__label {
- color: #9F9F9F;
- flex: none;
- }
- &__value {
- color: #1a1a1a;
- }
- }
- .patrol-list {
- margin-top: 12px;
- display: flex;
- flex-direction: column;
- gap: 14px;
- }
- .patrol-item {
- display: flex;
- align-items: flex-start;
- gap: 10px;
- background: #F7F8FA;
- padding: 10px;
- border-radius: 4px;
- box-sizing: border-box;
- &__cover {
- width: 100px;
- height: 86px;
- border-radius: 5px;
- object-fit: cover;
- flex-shrink: 0;
- background: #f5f5f5;
- }
- &__body {
- flex: 1;
- min-width: 0;
- }
- &__title {
- font-size: 14px;
- font-weight: 500;
- line-height: 22px;
- color: #1D2129;
- }
- &__desc {
- margin-top: 4px;
- font-size: 12px;
- line-height: 20px;
- color: #4E5969;
- word-break: break-all;
- text-align: justify;
- }
- }
- .diag-list,
- .advice-list {
- margin-top: 10px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .diag-item,
- .advice-item {
- padding: 10px;
- border-radius: 4px;
- background: #F7F8FA;
- box-sizing: border-box;
- }
- .diag-item {
- &__tag {
- display: inline-block;
- padding: 0 8px;
- height: 22px;
- line-height: 22px;
- border-radius: 2px;
- background: #2199f8;
- color: #fff;
- font-size: 12px;
- }
- &__title {
- margin-top: 4px;
- font-size: 14px;
- font-weight: 500;
- line-height: 22px;
- color: #1D2129;
- }
- &__desc {
- margin-top: 4px;
- font-size: 12px;
- line-height: 20px;
- color: #4E5969;
- word-break: break-all;
- }
- }
- .advice-item {
- &__title {
- font-size: 14px;
- font-weight: 500;
- line-height: 22px;
- color: #1D2129;
- }
- &__desc {
- margin-top: 4px;
- font-size: 12px;
- line-height: 20px;
- color: #4E5969;
- word-break: break-all;
- white-space: pre-line;
- }
- }
- </style>
|