| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- <template>
- <div class="growth-report-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
- <!-- 天气遮罩 -->
- <div class="weather-mask" v-show="isExpanded" @click="handleMaskClick"></div>
- <!-- 头部 -->
- <div
- class="growth-report-header"
- :style="activeGardenTab === 'current' ? headerMotionStyle : undefined"
- >
- <weather-info ref="weatherInfoRef" :hasWeather="false" from="growth_report" class="weather-info"
- @weatherExpanded="weatherExpanded" @changeGarden="changeGarden" @changeGardenTab="changeGardenTab"
- @reportTabClick="handleReportTabClick" :isGarden="true" :gardenId="defaultGardenId" />
- </div>
- <!-- 农场列表 -->
- <div v-show="activeGardenTab === 'list'">
- <garden-list ref="gardenListRef" :garden-id="selectedGardenId" @loaded="handleGardenLoaded"
- @selectGarden="handleGardenSelected" />
- </div>
- <div class="report-content" v-show="activeGardenTab === 'current'">
- <div class="map-legend" :class="{ 'map-legend--en': locale === 'en' }">
- <div
- v-for="item in mapLegendItems"
- :key="item.key"
- class="map-legend__item"
- >
- <span class="map-legend__pill" :class="item.pillClass"></span>
- <span class="map-legend__text">{{ item.label }}</span>
- </div>
- </div>
- <div class="map-container" ref="mapContainer"></div>
- </div>
- <risk-report-panel
- v-show="activeGardenTab === 'current'"
- :view-type="panelViewType"
- :plot-detail="plotDetail"
- @expand-progress="panelExpandProgress = $event"
- @close-plot-detail="handleClosePlotDetail"
- />
- </div>
- </template>
- <script setup>
- import { computed, nextTick, onActivated, ref } from "vue";
- import { useRoute, useRouter } from "vue-router";
- import { useStore } from "vuex";
- import { useI18n } from "@/i18n";
- import weatherInfo from "@/components/weatherInfo.vue";
- import gardenList from "@/components/gardenList.vue";
- import GrowthReportMap from "./growthReportMap.js";
- import RiskReportPanel from "./components/RiskReportPanel.vue";
- import * as util from "@/common/ol_common.js";
- const DEFAULT_FARM_POINT = "POINT(113.6142086995688 23.585836479509055)";
- function isPointWkt(value) {
- return typeof value === "string" && /^POINT\s*\(/i.test(value.trim());
- }
- function isPolygonWkt(value) {
- return typeof value === "string" && /^(MULTI)?POLYGON\s*\(/i.test(value.trim());
- }
- function resolveFarmPolygonWkt(farm) {
- if (!farm || typeof farm === "string") return null;
- const polygon = farm.farm_polygon;
- return isPolygonWkt(polygon) ? polygon : null;
- }
- function resolveFarmLocationWkt(farm, polygonWkt) {
- if (typeof farm === "string" && isPointWkt(farm)) return farm;
- if (!farm || typeof farm === "string") return null;
- const candidates = [farm.wkt, farm.geom_wkt, farm.farm_location];
- for (const candidate of candidates) {
- if (isPointWkt(candidate)) return candidate;
- }
- if (polygonWkt) {
- const geom = util.wktCastGeom(polygonWkt);
- const extent = geom.getExtent();
- return `POINT(${(extent[0] + extent[2]) / 2} ${(extent[1] + extent[3]) / 2})`;
- }
- return null;
- }
- const store = useStore();
- const { t, locale } = useI18n();
- const route = useRoute();
- const router = useRouter();
- 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 plotDetail = ref(createDefaultPlotDetail());
- const HEADER_FADE_START = 0.68;
- function createDefaultPlotDetail() {
- const defaultImage = require("@/assets/img/home/banner.png");
- return {
- name: "地块名称",
- area: "289亩",
- categories: "荔枝、水稻",
- startTime: "2026/06/05",
- harvestTime: "2026/06/05",
- description: "当前处于物候期,有什么风险,做了什么农事,或者正在执行中,当前处于物候期,有什么风险",
- images: [defaultImage, defaultImage, defaultImage, defaultImage],
- };
- }
- function buildPlotDetailByTab(item) {
- const tabLabelMap = {
- soilImprovement: "土壤改良地块",
- rotationAdvice: "轮作建议地块",
- };
- return {
- ...createDefaultPlotDetail(),
- name: tabLabelMap[item.key] || "地块名称",
- };
- }
- const handleClosePlotDetail = () => {
- panelViewType.value = "risk";
- };
- 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 mapContainer = ref(null);
- const growthReportMap = new GrowthReportMap();
- const syncMapByFarm = async (farm) => {
- const polygonWkt = resolveFarmPolygonWkt(farm);
- const location = resolveFarmLocationWkt(farm, polygonWkt) || DEFAULT_FARM_POINT;
- await nextTick();
- if (!mapContainer.value) return;
- if (growthReportMap.kmap) {
- growthReportMap.kmap.map?.updateSize?.();
- if (polygonWkt) {
- growthReportMap.setAreaGeometry([polygonWkt]);
- const coordinate = util.wktCastGeom(location).getFirstCoordinate();
- growthReportMap.setMapPoint(coordinate);
- growthReportMap.scheduleFitView();
- } else {
- growthReportMap.clearLayer();
- const coordinate = util.wktCastGeom(location).getFirstCoordinate();
- growthReportMap.setMapPosition(coordinate);
- }
- return;
- }
- growthReportMap.initMap(location, mapContainer.value);
- if (polygonWkt) {
- growthReportMap.setAreaGeometry([polygonWkt]);
- growthReportMap.scheduleFitView();
- }
- };
- const initGrowthReportMap = async () => {
- await syncMapByFarm();
- };
- const mapLegendItems = computed(() => [
- { key: "zone", label: t("agriFile.legendZone"), pillClass: "map-legend__pill--zone" },
- { key: "growth", label: t("agriFile.legendGrowth"), pillClass: "map-legend__pill--growth" },
- { key: "pest", label: t("agriFile.legendPest"), pillClass: "map-legend__pill--pest" },
- ]);
- const handleReportTabClick = (item) => {
- if (item.key === "historyRisk") {
- router.push(
- `/history_risk_report?farmVariety=${currentFarmVariety.value ?? ""}¤tFarmName=${currentFarmName.value ?? ""}`
- );
- return;
- }
- plotDetail.value = buildPlotDetailByTab(item);
- panelViewType.value = "plot";
- };
- const weatherExpanded = (isExpandedValue) => {
- isExpanded.value = isExpandedValue;
- };
- const handleMaskClick = () => {
- if (weatherInfoRef.value?.toggleExpand) {
- weatherInfoRef.value.toggleExpand();
- }
- };
- const changeGardenTab = (tab) => {
- activeGardenTab.value = tab;
- if (tab !== "current") {
- panelExpandProgress.value = 0;
- panelViewType.value = "risk";
- return;
- }
- nextTick(() => {
- growthReportMap.kmap?.map?.updateSize?.();
- growthReportMap.scheduleFitView?.();
- });
- };
- const handleGardenLoaded = ({ hasFarm }) => {
- weatherInfoRef.value?.setGardenLoaded?.(hasFarm);
- };
- const handleGardenSelected = (garden) => {
- selectedGardenId.value = garden?.id ?? null;
- syncMapByFarm(garden);
- weatherInfoRef.value?.setSelectedGarden?.(garden);
- };
- 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;
- syncMapByFarm(data);
- };
- onActivated(async () => {
- if (route.query?.farmId) {
- defaultGardenId.value = route.query.farmId;
- }
- const savedFarmId = localStorage.getItem("selectedFarmId");
- selectedGardenId.value = savedFarmId ? Number(savedFarmId) : null;
- gardenListRef.value?.refreshFarmList?.();
- await initGrowthReportMap();
- });
- </script>
- <style lang="scss" scoped>
- .growth-report-page {
- width: 100%;
- height: 100%;
- background: #F5F7FB;
- box-sizing: border-box;
- .weather-mask {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.52);
- z-index: 11;
- }
- .growth-report-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;
- :deep(.garden-tabs) {
- .garden-item.left-item.active .current-name {
- color: #2199F8;
- font-weight: 600;
- }
- }
- }
- }
- .report-content {
- position: relative;
- height: 100%;
- box-sizing: border-box;
- .map-legend {
- position: absolute;
- top: 110px;
- right: 10px;
- z-index: 15;
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 4px 10px;
- background: rgba(0, 0, 0, 0.46);
- backdrop-filter: blur(4px);
- border-radius: 4px;
- box-sizing: border-box;
- &--en {
- flex-direction: column;
- align-items: flex-start;
- gap: 6px;
- padding: 8px 10px;
- background: rgba(0, 0, 0, 0.72);
- border-radius: 6px;
- .map-legend__item {
- gap: 6px;
- }
- .map-legend__pill {
- width: 18px;
- height: 4px;
- flex-shrink: 0;
- }
- .map-legend__text {
- font-size: 11px;
- line-height: 1.2;
- white-space: nowrap;
- }
- }
- &__item {
- display: flex;
- align-items: center;
- gap: 5px;
- }
- &__pill {
- width: 16px;
- height: 5px;
- border-radius: 10px;
- &--zone {
- background: #13a27f;
- }
- &--growth {
- background: #ff9138;
- }
- &--pest {
- background: #e62e2d;
- }
- }
- &__text {
- font-size: 12px;
- color: #fff;
- }
- }
- .map-container {
- width: 100%;
- height: 100%;
- }
- }
- }
- </style>
|