|
|
@@ -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);
|
|
|
});
|