|
@@ -1,458 +1,481 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="agri-file-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
|
|
<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 class="farm-header">
|
|
|
|
|
+ <div class="farm-header__info">
|
|
|
|
|
+ <el-avatar class="farm-header__avatar" :size="46" :src="circleUrl" />
|
|
|
|
|
+ <div class="farm-header__text">
|
|
|
|
|
+ <div class="farm-header__name van-ellipsis">{{ farmName }}</div>
|
|
|
|
|
+ <div class="farm-header__location van-ellipsis">{{ farmLocation }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="farm-header__more">
|
|
|
|
|
+ <el-icon class="farm-header__more-icon">
|
|
|
|
|
+ <Switch />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ <span>{{ t("agriFile.moreFarms") }}</span>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <!-- 农场列表 -->
|
|
|
|
|
- <div v-show="activeGardenTab === 'list'">
|
|
|
|
|
- <garden-list ref="gardenListRef" :garden-id="selectedGardenId" @loaded="handleGardenLoaded"
|
|
|
|
|
- @selectGarden="handleGardenSelected" />
|
|
|
|
|
|
|
+ <div class="nav-cards">
|
|
|
|
|
+ <div v-for="item in navCards" :key="item.key" class="nav-card"
|
|
|
|
|
+ :class="{ 'is-active': activeNav === item.key }" @click="activeNav = item.key">
|
|
|
|
|
+ <img class="nav-card__icon" :src="item.icon" alt="" />
|
|
|
|
|
+ <span class="nav-card__label">{{ t(item.labelKey) }}</span>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</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 v-show="activeNav === 'album'" class="content-panel">
|
|
|
|
|
+ <div class="map-card">
|
|
|
|
|
+ <div ref="mapContainer" class="map-container"></div>
|
|
|
|
|
+ <div class="add-zone-btn">
|
|
|
|
|
+ <el-icon>
|
|
|
|
|
+ <Plus />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ <span>{{ t("agriFile.addZone") }}</span>
|
|
|
</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 v-for="item in zoneList" :key="item.id" :ref="(el) => setMarkerEl(item.id, el)"
|
|
|
|
|
+ class="zone-marker">
|
|
|
|
|
+ <div class="zone-marker__label">{{ item.name }}</div>
|
|
|
|
|
+ <div class="zone-marker__thumb">
|
|
|
|
|
+ <img :src="item.cover" alt="" />
|
|
|
|
|
+ <span class="zone-marker__badge">{{ item.count }}</span>
|
|
|
</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 class="zone-marker__arrow"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="album-grid">
|
|
|
|
|
+ <div class="album-add">
|
|
|
|
|
+ <el-icon class="album-add__icon">
|
|
|
|
|
+ <Plus />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ <span>{{ t("agriFile.addAlbum") }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-for="item in albumList" :key="item.id" class="album-card">
|
|
|
|
|
+ <div class="album-card__cover">
|
|
|
|
|
+ <img :src="item.cover" alt="" />
|
|
|
|
|
+ <span class="album-card__count">{{ t("agriFile.photoCount", { count: item.count }) }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="album-card__name">{{ item.name }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="add-album-btn">
|
|
|
|
|
+ <el-icon>
|
|
|
|
|
+ <Plus />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { computed, onActivated, ref } from "vue";
|
|
|
|
|
-import { useRoute, useRouter } from "vue-router";
|
|
|
|
|
|
|
+import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
|
import { useStore } from "vuex";
|
|
import { useStore } from "vuex";
|
|
|
|
|
+import { Switch, Plus } from "@element-plus/icons-vue";
|
|
|
|
|
+import Overlay from "ol/Overlay";
|
|
|
import { useI18n } from "@/i18n";
|
|
import { useI18n } from "@/i18n";
|
|
|
-import weatherInfo from "@/components/weatherInfo.vue";
|
|
|
|
|
-import gardenList from "@/components/gardenList.vue";
|
|
|
|
|
|
|
+import FileMap from "./fileMap";
|
|
|
|
|
+import * as util from "@/common/ol_common.js";
|
|
|
|
|
|
|
|
const { t } = useI18n();
|
|
const { t } = useI18n();
|
|
|
-
|
|
|
|
|
const store = useStore();
|
|
const store = useStore();
|
|
|
-const route = useRoute();
|
|
|
|
|
-const router = useRouter();
|
|
|
|
|
const tabBarHeight = computed(() => store.state.home.tabBarHeight);
|
|
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) => {
|
|
|
|
|
- if (item.key === "historyRisk") {
|
|
|
|
|
- router.push(
|
|
|
|
|
- `/history_risk_report?farmVariety=${currentFarmVariety.value ?? ""}¤tFarmName=${currentFarmName.value ?? ""}`
|
|
|
|
|
- );
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- panelViewType.value = "plot";
|
|
|
|
|
|
|
+const defaultCover = require("@/assets/img/home/banner.png");
|
|
|
|
|
+const DEFAULT_CENTER = [113.6142086995688, 23.585836479509055];
|
|
|
|
|
+
|
|
|
|
|
+const defaultAvatar = require("@/assets/img/home/banner.png");
|
|
|
|
|
+const farmName = ref("");
|
|
|
|
|
+const farmLocation = ref("");
|
|
|
|
|
+const circleUrl = ref(defaultAvatar);
|
|
|
|
|
+const activeNav = ref("album");
|
|
|
|
|
+const mapContainer = ref(null);
|
|
|
|
|
+const fileMap = new FileMap();
|
|
|
|
|
+const markerEls = {};
|
|
|
|
|
+const mapOverlays = [];
|
|
|
|
|
+
|
|
|
|
|
+const navCards = [
|
|
|
|
|
+ {
|
|
|
|
|
+ key: "patrol",
|
|
|
|
|
+ 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: "report",
|
|
|
|
|
+ labelKey: "agriFile.diagnosisReport",
|
|
|
|
|
+ icon: require("@/assets/img/agricultural/icon-3.png"),
|
|
|
|
|
+ },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const zoneList = ref([
|
|
|
|
|
+ { id: 1, name: "", count: 2, cover: defaultCover, longitude: 113.612409, latitude: 23.587036 },
|
|
|
|
|
+ { id: 2, name: "", count: 2, cover: defaultCover, longitude: 113.615809, latitude: 23.586636 },
|
|
|
|
|
+ { id: 3, name: "", count: 2, cover: defaultCover, longitude: 113.613609, latitude: 23.584436 },
|
|
|
|
|
+ { id: 4, name: "", count: 2, cover: defaultCover, longitude: 113.615609, latitude: 23.585036 },
|
|
|
|
|
+]);
|
|
|
|
|
+
|
|
|
|
|
+const albumList = ref([
|
|
|
|
|
+ { id: 1, name: "", count: 125, cover: defaultCover },
|
|
|
|
|
+ { id: 2, name: "", count: 125, cover: defaultCover },
|
|
|
|
|
+ { id: 3, name: "", count: 125, cover: defaultCover },
|
|
|
|
|
+]);
|
|
|
|
|
+
|
|
|
|
|
+const setMarkerEl = (id, el) => {
|
|
|
|
|
+ if (el) markerEls[id] = el;
|
|
|
|
|
+ else delete markerEls[id];
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const weatherExpanded = (isExpandedValue) => {
|
|
|
|
|
- isExpanded.value = isExpandedValue;
|
|
|
|
|
|
|
+const getFarmCenter = () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
|
|
|
|
|
+ const wkt = farm.wkt || farm.geom_wkt || farm.farm_location;
|
|
|
|
|
+ if (typeof wkt === "string" && /^POINT\s*\(/i.test(wkt.trim())) {
|
|
|
|
|
+ return util.wktCastGeom(wkt).getFirstCoordinate();
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // ignore
|
|
|
|
|
+ }
|
|
|
|
|
+ return DEFAULT_CENTER;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const handleMaskClick = () => {
|
|
|
|
|
- if (weatherInfoRef.value?.toggleExpand) {
|
|
|
|
|
- weatherInfoRef.value.toggleExpand();
|
|
|
|
|
|
|
+const clearMapOverlays = () => {
|
|
|
|
|
+ if (!fileMap.kmap?.map) {
|
|
|
|
|
+ mapOverlays.length = 0;
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
+ mapOverlays.forEach((overlay) => fileMap.kmap.map.removeOverlay(overlay));
|
|
|
|
|
+ mapOverlays.length = 0;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const changeGardenTab = (tab) => {
|
|
|
|
|
- activeGardenTab.value = tab;
|
|
|
|
|
- if (tab !== "current") {
|
|
|
|
|
- panelExpandProgress.value = 0;
|
|
|
|
|
- panelViewType.value = "risk";
|
|
|
|
|
- }
|
|
|
|
|
|
|
+const bindZoneOverlays = () => {
|
|
|
|
|
+ if (!fileMap.kmap?.map) return;
|
|
|
|
|
+ clearMapOverlays();
|
|
|
|
|
+ zoneList.value.forEach((zone) => {
|
|
|
|
|
+ const el = markerEls[zone.id];
|
|
|
|
|
+ if (!el || zone.longitude == null || zone.latitude == null) return;
|
|
|
|
|
+ const overlay = new Overlay({
|
|
|
|
|
+ element: el,
|
|
|
|
|
+ position: [zone.longitude, zone.latitude],
|
|
|
|
|
+ positioning: "bottom-center",
|
|
|
|
|
+ stopEvent: false,
|
|
|
|
|
+ });
|
|
|
|
|
+ fileMap.kmap.map.addOverlay(overlay);
|
|
|
|
|
+ mapOverlays.push(overlay);
|
|
|
|
|
+ });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const handleGardenLoaded = ({ hasFarm }) => {
|
|
|
|
|
- weatherInfoRef.value?.setGardenLoaded?.(hasFarm);
|
|
|
|
|
|
|
+const initAlbumMap = async () => {
|
|
|
|
|
+ await nextTick();
|
|
|
|
|
+ if (!mapContainer.value) return;
|
|
|
|
|
+ const center = getFarmCenter();
|
|
|
|
|
+ fileMap.initMap(`POINT(${center[0]} ${center[1]})`, mapContainer.value);
|
|
|
|
|
+ await nextTick();
|
|
|
|
|
+ bindZoneOverlays();
|
|
|
|
|
+ fileMap.kmap?.map?.updateSize?.();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const handleGardenSelected = (garden) => {
|
|
|
|
|
- selectedGardenId.value = garden?.id ?? null;
|
|
|
|
|
- weatherInfoRef.value?.setSelectedGarden?.(garden);
|
|
|
|
|
|
|
+const fillMockLabels = () => {
|
|
|
|
|
+ const zoneName = t("agriFile.zoneLabel", { n: 1 });
|
|
|
|
|
+ const albumName = t("agriFile.zoneOne");
|
|
|
|
|
+ zoneList.value = zoneList.value.map((item) => ({ ...item, name: zoneName }));
|
|
|
|
|
+ albumList.value = albumList.value.map((item) => ({ ...item, name: albumName }));
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-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();
|
|
|
|
|
|
|
+const loadFarmInfo = () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
|
|
|
|
|
+ farmName.value = farm.farm_name || farm.name || t("agriFile.farmName");
|
|
|
|
|
+ farmLocation.value = farm.farm_address || farm.rawAddress || farm.city || t("agriFile.farmLocation");
|
|
|
|
|
+ circleUrl.value = farm.cover || farm.image || farm.avatar || defaultAvatar;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ farmName.value = t("agriFile.farmName");
|
|
|
|
|
+ farmLocation.value = t("agriFile.farmLocation");
|
|
|
|
|
+ circleUrl.value = defaultAvatar;
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ fillMockLabels();
|
|
|
|
|
+ loadFarmInfo();
|
|
|
|
|
+ initAlbumMap();
|
|
|
|
|
+});
|
|
|
onActivated(() => {
|
|
onActivated(() => {
|
|
|
- if (route.query?.farmId) {
|
|
|
|
|
- defaultGardenId.value = route.query.farmId;
|
|
|
|
|
- }
|
|
|
|
|
- const savedFarmId = localStorage.getItem("selectedFarmId");
|
|
|
|
|
- selectedGardenId.value = savedFarmId ? Number(savedFarmId) : null;
|
|
|
|
|
- gardenListRef.value?.refreshFarmList?.();
|
|
|
|
|
|
|
+ fillMockLabels();
|
|
|
|
|
+ loadFarmInfo();
|
|
|
|
|
+ initAlbumMap();
|
|
|
});
|
|
});
|
|
|
|
|
+onBeforeUnmount(clearMapOverlays);
|
|
|
|
|
|
|
|
-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' },
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+watch(activeNav, (key) => {
|
|
|
|
|
+ if (key === "album") {
|
|
|
|
|
+ initAlbumMap();
|
|
|
}
|
|
}
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-const handleTrackingItemClick = (item) => {
|
|
|
|
|
- router.push({
|
|
|
|
|
- path: "/record_details",
|
|
|
|
|
- query: { workId: item?.first_work?.work_id, type: item?.workType, curCode: currentPheCode.value },
|
|
|
|
|
- });
|
|
|
|
|
-};
|
|
|
|
|
|
|
+});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
|
.agri-file-page {
|
|
.agri-file-page {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
height: 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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ background: linear-gradient(180deg, #D1EBFF 0%, #FFFFFF 100%);
|
|
|
|
|
|
|
|
- .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;
|
|
|
|
|
|
|
+ .farm-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
|
|
|
- :deep(.garden-tabs) {
|
|
|
|
|
- .garden-item.left-item.active .current-name {
|
|
|
|
|
- color: #2199F8;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .farm-header__info {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+
|
|
|
|
|
+ .farm-header__name {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ .farm-header__location {
|
|
|
|
|
+ margin-top: 2px;
|
|
|
|
|
+ color: rgba(0, 0, 0, 0.5);
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .farm-header__more {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+ padding: 6px 10px;
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: #2199F8;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .tracking-list {
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- padding: 65px 10px 12px;
|
|
|
|
|
- // padding: 105px 10px 12px;
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
- overflow-y: auto;
|
|
|
|
|
|
|
+ .nav-cards {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ z-index: 2;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
|
|
+ justify-content: space-around;
|
|
|
|
|
+ padding: 10px 10px;
|
|
|
|
|
|
|
|
- &::-webkit-scrollbar {
|
|
|
|
|
- display: none;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .nav-card {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ width: 90px;
|
|
|
|
|
+ height: 64px;
|
|
|
|
|
+ box-shadow: 0px 4px 4px 0px #0000000D;
|
|
|
|
|
+
|
|
|
|
|
+ .nav-card__icon {
|
|
|
|
|
+ width: 26px;
|
|
|
|
|
+ height: 26px;
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ &.is-active {
|
|
|
|
|
+ background: linear-gradient(180deg, #5BB8FF 0%, #2199F8 100%);
|
|
|
|
|
+ box-shadow: 0 4px 10px rgba(33, 153, 248, 0.28);
|
|
|
|
|
|
|
|
- &--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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .nav-card__icon {
|
|
|
|
|
+ filter: brightness(0) invert(1);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- &--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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .nav-card__label {
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- &__header {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- padding: 10px 10px 20px;
|
|
|
|
|
- border-radius: 10px 10px 0 0;
|
|
|
|
|
- background: var(--tracking-header-bg);
|
|
|
|
|
|
|
+ &::after {
|
|
|
|
|
+ content: "";
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ left: 50%;
|
|
|
|
|
+ bottom: -6px;
|
|
|
|
|
+ transform: translateX(-50%);
|
|
|
|
|
+ border-left: 6px solid transparent;
|
|
|
|
|
+ border-right: 6px solid transparent;
|
|
|
|
|
+ border-top: 6px solid #2199F8;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- &__header-row {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .content-panel {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ background: linear-gradient(360deg, rgba(229, 243, 255, 0.55) 0%, rgba(255, 255, 255, 0.55) 100%);
|
|
|
|
|
+ border-radius: 20px 20px 0 0;
|
|
|
|
|
+ border: 1px solid #FFFFFF;
|
|
|
|
|
+ padding: 18px 10px;
|
|
|
|
|
|
|
|
- &__header-left {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 6px;
|
|
|
|
|
|
|
+ &::-webkit-scrollbar {
|
|
|
|
|
+ display: none;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- &__level {
|
|
|
|
|
- padding: 1px 6px;
|
|
|
|
|
- border-radius: 2px;
|
|
|
|
|
- background: #fff;
|
|
|
|
|
- color: #FF6A6A;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- min-width: fit-content;
|
|
|
|
|
|
|
+ .map-card {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ height: 210px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- &__title {
|
|
|
|
|
- color: #fff;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
|
|
+ .map-container {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ clip-path: inset(0px round 8px);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- &__tags {
|
|
|
|
|
|
|
+ .zone-marker {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
align-items: center;
|
|
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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+
|
|
|
|
|
+ &__label {
|
|
|
|
|
+ padding: 3px 14px;
|
|
|
|
|
+ border-radius: 25px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: #0A0A0A;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- &__body {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- padding: 10px;
|
|
|
|
|
- background: #fff;
|
|
|
|
|
- border-radius: 10px;
|
|
|
|
|
- margin-top: -8px;
|
|
|
|
|
|
|
+ &__thumb {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 42px;
|
|
|
|
|
+ height: 42px;
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+
|
|
|
|
|
+ img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ border: 2px solid #fff;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- &::before {
|
|
|
|
|
- content: "";
|
|
|
|
|
|
|
+ &__badge {
|
|
|
position: absolute;
|
|
position: absolute;
|
|
|
- top: -7px;
|
|
|
|
|
- left: 60px;
|
|
|
|
|
|
|
+ top: -6px;
|
|
|
|
|
+ right: -6px;
|
|
|
|
|
+ min-width: 18px;
|
|
|
|
|
+ height: 18px;
|
|
|
|
|
+ line-height: 16px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: #0A0A0A;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &__arrow {
|
|
|
width: 0;
|
|
width: 0;
|
|
|
height: 0;
|
|
height: 0;
|
|
|
- border-left: 7px solid transparent;
|
|
|
|
|
- border-right: 7px solid transparent;
|
|
|
|
|
- border-bottom: 7px solid #fff;
|
|
|
|
|
|
|
+ border-left: 5px solid transparent;
|
|
|
|
|
+ border-right: 5px solid transparent;
|
|
|
|
|
+ border-top: 6px 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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .add-zone-btn {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ right: 5px;
|
|
|
|
|
+ bottom: 10px;
|
|
|
|
|
+ z-index: 5;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 2px;
|
|
|
|
|
+ padding: 5px 10px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: #2199F8;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .album-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
|
|
|
- &__icon {
|
|
|
|
|
|
|
+ .album-add {
|
|
|
|
|
+ align-self: start;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 4px;
|
|
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ aspect-ratio: 1;
|
|
|
|
|
+ border-radius: 5px;
|
|
|
|
|
+ background: #F5F5F7;
|
|
|
|
|
+ color: #969494;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
|
|
|
- img {
|
|
|
|
|
- width: 18px;
|
|
|
|
|
- height: 16px;
|
|
|
|
|
|
|
+ &__icon {
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+ font-size: 20px;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- &__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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .album-card {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
|
|
|
- &__history {
|
|
|
|
|
- margin-top: 10px;
|
|
|
|
|
- padding: 6px 8px;
|
|
|
|
|
- border-radius: 6px;
|
|
|
|
|
- background: rgba(168, 168, 168, 0.1);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ &__cover {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ aspect-ratio: 1;
|
|
|
|
|
+ img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- &__history-text {
|
|
|
|
|
- color: rgba(31, 31, 31, 0.5);
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ &__count {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 1px;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ padding: 1px 5px;
|
|
|
|
|
+ border-radius: 0 5px 0 5px;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.4);
|
|
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- &__images {
|
|
|
|
|
- display: grid;
|
|
|
|
|
- grid-template-columns: repeat(5, 1fr);
|
|
|
|
|
- gap: 6px;
|
|
|
|
|
- margin-top: 6px;
|
|
|
|
|
|
|
+ &__name {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ color: #1F1F1F;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- &__thumb {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- aspect-ratio: 1;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- object-fit: cover;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .add-album-btn {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ bottom: 78px;
|
|
|
|
|
+ right: 16px;
|
|
|
|
|
+ z-index: 10;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 44px;
|
|
|
|
|
+ height: 44px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #2199F8;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 22px;
|
|
|
|
|
+ box-shadow: 0 4px 10px rgba(33, 153, 248, 0.35);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|