Browse Source

feat: 农情研判

lxf 1 tuần trước cách đây
mục cha
commit
adcdd247ed

+ 7 - 0
src/router/globalRoutes.js

@@ -40,6 +40,13 @@ export default [
         meta: { showTabbar: true, keepAlive: true },
         component: () => import("@/views/old_mini/growth_report/index.vue"),
     },
+    // 具体预警详情
+    {
+        path: "/alert_detail",
+        name: "AlertDetail",
+        meta: { keepAlive: false },
+        component: () => import("@/views/old_mini/growth_report/alert_detail.vue"),
+    },
     // 农事互动列表
     {
         path: "/interaction_list",

+ 9 - 1
src/views/old_mini/agri_file/index.vue

@@ -79,7 +79,7 @@
                         <span>{{ t("agriFile.addAlbum") }}</span>
                     </div>
                     <div v-for="item in albumList" :key="item.id" class="album-card" :class="{ 'is-empty': !item.count }">
-                        <div class="album-card__cover">
+                        <div class="album-card__cover" @click.stop="goAlbumDetail(item)">
                             <img v-if="item.count && item.cover" :src="item.cover" alt="" />
                             <img v-else class="album-card__empty-icon" src="@/assets/img/agricultural/photo.png" alt="" />
                             <span class="album-card__count">{{ t("agriFile.photoCount", { count: item.count }) }}</span>
@@ -322,6 +322,14 @@ const goDiagnosisReport = () => {
     activeNav.value = "report";
 };
 
+const goAlbumDetail = (item) => {
+    if (!item.count) return;
+    router.push({
+        path: "/region_albums",
+        query: { id: item?.id },
+    });
+};
+
 onMounted(() => {
     fillMockLabels();
     initAlbumMap();

+ 79 - 0
src/views/old_mini/growth_report/alert_detail.vue

@@ -0,0 +1,79 @@
+<template>
+    <div class="alert-detail-page">
+        <custom-header :name="pageTitle" />
+        <div class="alert-detail-body">
+            <div class="alert-card">
+                <div class="alert-card__header">
+                    <img class="alert-card__icon" :src="icon" alt="" />
+                    <span class="alert-card__title">{{ title }}</span>
+                </div>
+                <div class="alert-card__content">{{ content }}</div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { useRoute } from "vue-router";
+import customHeader from "@/components/customHeader.vue";
+
+const route = useRoute();
+const defaultIcon = require("@/assets/img/report/weather.png");
+
+const pageTitle = computed(() => route.query.title || "具体预警");
+const title = computed(() => route.query.title || "具体预警");
+const content = computed(
+    () =>
+        route.query.content ||
+        "详情详情详情详情详情详情详情详情详情详情详情详情详情详情详情详情详情详情详情详情"
+);
+const icon = computed(() => route.query.icon || defaultIcon);
+</script>
+
+<style lang="scss" scoped>
+.alert-detail-page {
+    min-height: 100vh;
+    background: #f0f4f7;
+}
+
+.alert-detail-body {
+    padding: 16px;
+    box-sizing: border-box;
+}
+
+.alert-card {
+    padding: 16px;
+    border-radius: 12px;
+    background: #fff;
+    box-sizing: border-box;
+
+    &__header {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+    }
+
+    &__icon {
+        width: 24px;
+        height: 24px;
+        flex-shrink: 0;
+        object-fit: contain;
+    }
+
+    &__title {
+        font-size: 16px;
+        font-weight: 600;
+        line-height: 24px;
+        color: #000;
+    }
+
+    &__content {
+        margin-top: 12px;
+        font-size: 14px;
+        line-height: 22px;
+        color: #666;
+        word-break: break-all;
+    }
+}
+</style>

+ 6 - 3
src/views/old_mini/growth_report/components/CropAlertPanel.vue

@@ -133,9 +133,9 @@ const handleSelectCrop = (name) => {
     emit("switchCategory", name);
 };
 
-const PANEL_HEADER_BAR = 30; // van-floating-panel 原生 header 高度
-const PANEL_BOTTOM_GAP = 16;
-const COLLAPSED_HEIGHT = 130;
+const PANEL_HEADER_BAR = 14; // van-floating-panel 原生 header 高度
+const PANEL_BOTTOM_GAP = 12;
+const COLLAPSED_HEIGHT = 116;
 
 const defaultHeight = ref(COLLAPSED_HEIGHT);
 const anchors = ref([defaultHeight.value + tabBarHeight.value, 280 + tabBarHeight.value]);
@@ -238,6 +238,7 @@ onBeforeUnmount(() => {
     ::v-deep {
         .van-floating-panel__header {
             background: transparent;
+            height: 16px;
         }
 
         .van-floating-panel__header-bar {
@@ -249,6 +250,7 @@ onBeforeUnmount(() => {
 
         .van-floating-panel__content {
             background: transparent;
+
             overflow-y: auto;
         }
     }
@@ -256,6 +258,7 @@ onBeforeUnmount(() => {
     .floating-panel-content {
         width: calc(100% - 20px);
         margin: 0 auto;
+        // margin: 14px auto 0; // 补偿隐藏 header 的 10px,原本30px,整体下移
         box-sizing: border-box;
     }
 

+ 168 - 3
src/views/old_mini/growth_report/index.vue

@@ -45,29 +45,60 @@
             </div>
         </div>
 
+        <div class="map-legend" :style="{ bottom: `${inviteBottom}px` }">
+            <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="invite-group" :style="{ bottom: `${inviteBottom}px` }">
+            <div class="invite-btn">
+                <img class="invite-icon" src="@/assets/img/home/user-icon.png" alt="">
+                邀请农服</div>
+            <div class="invite-btn" @click="handleInvite('farmer')">
+                <img class="invite-icon" src="@/assets/img/home/user-icon.png" alt="">
+                邀请农户</div>
+        </div>
+
         <crop-alert-panel
             :crop-name="currentCropName"
             @switch-category="handleSwitchCategory"
             @view-detail="handleViewDetail"
             @patrol-tip="handlePatrolTip"
+            @height-change="handlePanelHeightChange"
         />
     </div>
 </template>
 
 <script setup>
 import { computed, nextTick, onActivated, onMounted, ref } from "vue";
+import { useRouter } from "vue-router";
 import { useStore } from "vuex";
 import { ElMessage } from "element-plus";
 import { LocationFilled } from "@element-plus/icons-vue";
 import { convertPointToArray } from "@/utils/index";
 import GrowthReportMap from "./growthReportMap.js";
 import CropAlertPanel from "./components/CropAlertPanel.vue";
+import wx from "weixin-js-sdk";
+
+const router = useRouter();
 
 const DEFAULT_MAP_POINT = "POINT(113.6142086995688 23.585836479509055)";
 const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 
 const menuIcon = require("@/assets/img/common/farm-active.png");
 
+const mapLegendItems = computed(() => [
+    { key: "zone", label: "胁迫", pillClass: "map-legend__pill--zone" },
+    { key: "growth", label: "长势", pillClass: "map-legend__pill--growth" },
+    { key: "pest", label: "病虫害", pillClass: "map-legend__pill--pest" },
+]);
+
 const store = useStore();
 const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 
@@ -77,6 +108,8 @@ const locationName = ref("");
 const currentCropName = ref("水稻");
 const activeStressKey = ref("hot-drought-2");
 const activeLayerKey = ref("growth");
+const panelHeight = ref(280);
+const inviteBottom = computed(() => panelHeight.value);
 
 const stressMenuItems = [
     { key: "stress", line1: "胁迫", line2: "胁迫", icon: menuIcon },
@@ -118,22 +151,52 @@ const initMap = async () => {
 };
 
 const handleSwitchLocation = () => {
-    ElMessage.info("切换位置功能即将开放");
 };
 
+
+const handleInvite = (item) => {
+    let inviteName = "好友";
+    try {
+        const userInfo = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
+        inviteName = userInfo.nickName || userInfo.userName || userInfo.name || inviteName;
+    } catch {
+        // ignore
+    }
+    const query = {
+        askInfo: { title: "邀请完善信息", content: "是否分享该邀请给好友" },
+        shareText: "邀请您完善地块种植信息",
+        targetUrl: `entry_information`,
+        paramsPage: JSON.stringify({ inviteName }),
+        imageUrl: 'https://birdseye-img.sysuimars.com/temp/field.png',
+    };
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
+    });
+}
+
 const handleSwitchCategory = (cropName) => {
     if (!cropName) return;
     currentCropName.value = cropName;
 };
 
-const handleViewDetail = () => {
-    ElMessage.info("详情功能即将开放");
+const handleViewDetail = (item) => {
+    router.push({
+        path: "/alert_detail",
+        query: {
+            title: item?.title || "具体预警",
+            content: item?.content || "",
+        },
+    });
 };
 
 const handlePatrolTip = () => {
     ElMessage.info("巡园要点功能即将开放");
 };
 
+const handlePanelHeightChange = (height) => {
+    panelHeight.value = Number(height) || 0;
+};
+
 onMounted(() => {
     getLocationName();
     initMap();
@@ -243,6 +306,108 @@ onActivated(() => {
         }
     }
 
+
+    .invite-group {
+        position: fixed;
+        right: 16px;
+        z-index: 20;
+        pointer-events: none;
+        transition: bottom 0.2s ease;
+
+        .invite-btn {
+            pointer-events: auto;
+            height: 32px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            border-radius: 5px;
+            background: #fff;
+            box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.1);
+            gap: 4px;
+            padding: 0 8px;
+            color: #2199F8;
+            font-weight: 500;
+            font-size: 12px;
+            .invite-icon {
+                width: 16px;
+            }
+        }
+        .invite-btn + .invite-btn {
+            margin-top: 10px;
+        }
+    }
+
+
+    .map-legend {
+        position: fixed;
+        left: 10px;
+        z-index: 20;
+        pointer-events: none;
+        transition: bottom 0.2s ease;
+        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;
+        }
+    }
+
     .report-content {
         position: relative;
         height: 100%;