lxf 4 dní pred
rodič
commit
8429082117

+ 0 - 21
src/components/allGarden.vue

@@ -5,10 +5,6 @@
         <div class="page-body">
             <div class="map-wrap">
                 <div class="map-container" ref="mapContainer"></div>
-                <div class="add-farm-btn" @click="handleAddFarm">
-                    <el-icon><Plus /></el-icon>
-                    <span>{{ t("garden.addFarm") }}</span>
-                </div>
             </div>
 
             <div class="farm-filter">
@@ -348,23 +344,6 @@ onActivated(() => {
         height: 162px;
         clip-path: inset(0 round 8px);
     }
-
-    .add-farm-btn {
-        position: absolute;
-        right: 10px;
-        bottom: 10px;
-        z-index: 2;
-        display: inline-flex;
-        align-items: center;
-        gap: 2px;
-        height: 28px;
-        padding: 0 10px;
-        border-radius: 14px;
-        background: #fff;
-        color: #2199f8;
-        font-size: 12px;
-        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
-    }
 }
 
 .farm-filter {

+ 1 - 1
src/i18n/messages.js

@@ -180,7 +180,7 @@ export default {
             clickToView: "点击查看",
             viewInDiagnosisHint: "还可在 农事档案-诊断报告 中查看",
             confirmLeaveTitle: "确认要离开?",
-            confirmLeaveDesc1: "长势巡园比例还没录入,确定要走吗?",
+            confirmLeaveDesc1: "长势巡园比例还没回答,确定要走吗?",
             confirmLeaveDesc2: "少了这一步,估产数据就不准了哦~",
             continueRecord: "继续记录",
             confirmLeave: "确认离开",

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

@@ -43,7 +43,7 @@
                                     {{ item.title }}<span v-if="item.zone" class="tracking-item__zone"> ({{ item.zone }})</span>
                                 </span>
                             </div>
-                            <div class="tracking-item__share" @click.stop>
+                            <div class="tracking-item__share" @click.stop="handleShare(item)">
                                 <Icon size="14" color="#2199F8" name="share" />
                                 <span>{{ t("agriFile.forward") }}</span>
                             </div>
@@ -162,6 +162,7 @@ import completeFarmInfoPopup from "@/components/popup/completeFarmInfoPopup.vue"
 import diagnosisReportPopup from "@/components/popup/diagnosisReportPopup.vue";
 import restorePlantingPopup from "@/components/popup/restorePlantingPopup.vue";
 import PhenologyTrackTimelineItem from "@/components/pageComponents/PhenologyTrackTimelineItem.vue";
+import wx from "weixin-js-sdk";
 
 const { t } = useI18n();
 const store = useStore();
@@ -345,6 +346,7 @@ const goAddZone = (type) => {
             path: "/add_zone",
             query: {
                 type,
+                isSuccessPopup: type === 'farm' ? '1' : '0',
             },
         }
     );
@@ -361,6 +363,23 @@ const goGrowthTrack = (item) => {
     });
 };
 
+const handleShare = (item) => {
+    const query = {
+        askInfo: { title: "转发巡园要点", content: "是否分享给好友" },
+        shareText: item?.title || "速长窗口不追肥?后期发育慢人一步,直接掉队!",
+        targetUrl: "growth_track",
+        paramsPage: JSON.stringify({
+            id: item?.id,
+            type: item?.type || "growth",
+            fromShare: 1,
+        }),
+        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 openUploadPopup = () => {
     showUploadPopup.value = true;
 };

+ 84 - 4
src/views/old_mini/agri_file/pages/addZone.vue

@@ -7,7 +7,8 @@
                 <location-search class="search-bar__search" :user-location="userLocation"
                     @change="handleLocationChange" />
             </div>
-            <div class="map-tip normal-text" v-show="isFarmDrawMode">请勾画 <span class="map-tip__highlight">桂味</span> 的种植区域</div>
+            <div class="map-tip normal-text" v-show="isFarmDrawMode">请勾画 <span class="map-tip__highlight">桂味</span>
+                的种植区域</div>
             <div class="map-tip" v-show="!isFarmDrawMode">{{ t("agriFile.selectZoneHint") }}</div>
             <div class="locate-btn" @click="handleLocate">
                 <img class="locate-btn__icon" src="@/assets/img/map/map-icon.png" alt="" />
@@ -19,6 +20,23 @@
         </div>
         <RegionNamePopup v-model:show="showRegionNamePopup" @confirm="handleFarmNameConfirm" />
         <add-zone-info-popup v-model:show="showZoneInfoPopup" @confirm="handleZoneInfoConfirm" />
+
+        <tip-popup v-model:show="showExecuteSuccessPopup" type="executeSuccess" text="分区/相册已创建成功" hideBtn>
+            <template #footer>
+                <div class="bottom-btn-container">
+                    <div class="bottom-btn secondary-btn" @click="handleComplete">完成</div>
+                    <div class="bottom-btn primary-btn" @click="handleContinueCreate">继续新建</div>
+                </div>
+            </template>
+        </tip-popup>
+
+        <tip-popup
+            v-model:show="showDrawSuccessPopup"
+            type="success"
+            text="区域已勾画完成"
+            hideBtn
+            @closedPopup="handleDrawSuccessClosed"
+        />
     </div>
 </template>
 
@@ -35,12 +53,16 @@ import MapManage from "@/views/old_mini/recordDetails/map/mapManage.js";
 import { POINT_ICON } from "@/views/old_mini/entry_information/map/selectLocationMap.js";
 import * as util from "@/common/ol_common.js";
 import { useI18n } from "@/i18n";
+import tipPopup from "@/components/popup/tipPopup.vue";
 
 const { t } = useI18n();
 const route = useRoute();
 const router = useRouter();
 const isFarmDrawMode = computed(() => route.query.type === "farm");
 const isRegionDrawMode = computed(() => route.query.type === "region");
+const isSuccessPopupParam = computed(() => route.query.isSuccessPopup === "1");
+const showDrawSuccessPopup = ref(false);
+
 const targetUrl = computed(() => route.query.targetUrl);
 const isAbnormal = computed(() => {
     const val = route.query.isAbnormal;
@@ -185,6 +207,10 @@ function initMapView() {
     mapManage.initMap(getMapLocation(), mapContainer.value, {
         editable: true,
         zoneStyle: ZONE_STYLE,
+        preventOverlapExisting: isRegionDrawMode.value,
+        onDrawOverlap: () => {
+            ElMessage.warning("新绘制范围不能与已有品种种植范围重叠");
+        },
     });
     mapManage.enableRegionDrawing();
     if (isFarmDrawMode.value) {
@@ -225,6 +251,10 @@ const handleConfirmDraw = () => {
         ElMessage.warning(t("agriFile.pleaseDrawZone"));
         return;
     }
+    if (isRegionDrawMode.value && mapManage.hasDrawnOverlapWithExisting()) {
+        ElMessage.warning("新绘制范围不能与已有品种种植范围重叠");
+        return;
+    }
     if (isFarmDrawMode.value) {
         showRegionNamePopup.value = true;
         return;
@@ -252,8 +282,12 @@ const handleFarmNameConfirm = (regionName) => {
         mianji: payload.mianji,
     };
     sessionStorage.setItem("FARM_RANGE_GEOMETRY", JSON.stringify(geometryData));
+    if (isSuccessPopupParam.value) {
+        showDrawSuccessPopup.value = true;
+        return;
+    }
     ElMessage.success(t("agriFile.drawFarmAreaSuccess"));
-    if (!isAbnormal.value) {
+    if (!isAbnormal.value && targetUrl.value) {
         setTimeout(() => {
             const query = { showUpload: "1" };
             if (route.query.trackId) query.id = route.query.trackId;
@@ -265,6 +299,7 @@ const handleFarmNameConfirm = (regionName) => {
     leaveAfterConfirm();
 };
 
+const showExecuteSuccessPopup = ref(false);
 const handleZoneInfoConfirm = (formData) => {
     const payload = mapManage.getAreaGeometry();
     const geometryData = {
@@ -273,10 +308,27 @@ const handleZoneInfoConfirm = (formData) => {
         mianji: payload.mianji,
     };
     sessionStorage.setItem("ADD_ZONE_GEOMETRY", JSON.stringify(geometryData));
-    ElMessage.success(t("agriFile.createZoneSuccess"));
-    leaveAfterConfirm();
+    showExecuteSuccessPopup.value = true;
+    
 };
 
+const handleDrawSuccessClosed = () => {
+    showDrawSuccessPopup.value = false;
+    leaveAfterConfirm();
+}
+
+const handleComplete = () => {
+    showExecuteSuccessPopup.value = false;
+
+    leaveAfterConfirm();
+}
+
+const handleContinueCreate = () => {
+    showExecuteSuccessPopup.value = false;
+    mapManage.clearLayer();
+    initMapView();
+}
+
 onActivated(() => {
     nextTick(() => initMapView());
 });
@@ -355,6 +407,7 @@ onDeactivated(() => {
         background: rgba(0, 0, 0, 0.5);
         color: #7CC5FF;
         font-size: 12px;
+
         &.normal-text {
             color: #fff;
         }
@@ -403,4 +456,31 @@ onDeactivated(() => {
         background: #2199f8;
     }
 }
+
+
+.bottom-btn-container {
+    display: flex;
+    justify-content: space-between;
+    gap: 12px;
+    width: 100%;
+    .bottom-btn {
+        padding: 0 25px;
+        border-radius: 4px;
+        font-size: 16px;
+        height: 40px;
+        line-height: 40px;
+        box-sizing: border-box;
+        font-weight: 500;
+        text-align: center;
+        &.secondary-btn {
+            color: #B1B1B1;
+            border: 1px solid #EAEAEA;
+        }
+        &.primary-btn {
+            flex: 1;
+            background: #2199F8;
+            color: #fff;
+        }
+    }
+}
 </style>

+ 0 - 63
src/views/old_mini/agri_file/pages/albumMap.vue

@@ -13,25 +13,13 @@
             <div class="locate-btn" @click="handleLocate">
                 <img class="locate-btn__icon" src="@/assets/img/map/map-icon.png" alt="" />
             </div>
-            <div class="album-map-footer">
-                <div class="album-map-footer__text">
-                    <div class="album-map-footer__title">{{ t("agriFile.phenologyInconsistent") }}</div>
-                    <div class="album-map-footer__desc">{{ t("agriFile.customizeZoneHint") }}</div>
-                </div>
-                <div class="album-map-footer__btn" @click="goAddZone">
-                    <el-icon><Plus /></el-icon>
-                    <span>{{ t("agriFile.addZone") }}</span>
-                </div>
-            </div>
         </div>
     </div>
 </template>
 
 <script setup>
 import { nextTick, onActivated, ref } from "vue";
-import { useRouter } from "vue-router";
 import { useStore } from "vuex";
-import { Plus } from "@element-plus/icons-vue";
 import customHeader from "@/components/customHeader.vue";
 import locationSearch from "@/components/pageComponents/locationSearch.vue";
 import FileMap from "../fileMap";
@@ -39,7 +27,6 @@ import * as util from "@/common/ol_common.js";
 import { useI18n } from "@/i18n";
 
 const { t } = useI18n();
-const router = useRouter();
 const store = useStore();
 const mapContainer = ref(null);
 const fileMap = new FileMap();
@@ -190,10 +177,6 @@ const handleLocate = () => {
     });
 };
 
-const goAddZone = () => {
-    router.push("/add_zone");
-};
-
 onActivated(() => {
     initAlbumMap();
 });
@@ -277,50 +260,4 @@ onActivated(() => {
         height: 18px;
     }
 }
-
-.album-map-footer {
-    position: absolute;
-    left: 12px;
-    right: 12px;
-    bottom: 16px;
-    z-index: 2;
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    gap: 12px;
-    padding: 12px 12px 12px 16px;
-    border-radius: 8px;
-    background: #fff;
-    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
-    box-sizing: border-box;
-
-    &__title {
-        color: #2199F8;
-        font-size: 15px;
-        font-weight: 600;
-        line-height: 21px;
-    }
-
-    &__desc {
-        margin-top: 4px;
-        color: rgba(0, 0, 0, 0.45);
-        font-size: 12px;
-        line-height: 17px;
-    }
-
-    &__btn {
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        flex-shrink: 0;
-        gap: 2px;
-        height: 36px;
-        padding: 0 14px;
-        border-radius: 18px;
-        background: #2199F8;
-        color: #fff;
-        font-size: 14px;
-        box-sizing: border-box;
-    }
-}
 </style>

+ 58 - 9
src/views/old_mini/agri_file/pages/growthTrack.vue

@@ -1,6 +1,12 @@
 <template>
     <div class="growth-track-page">
-        <custom-header :name="t('agriFile.workName')" :isGoBack="true" @goback="handleBack" />
+        <custom-header
+            :name="t('agriFile.workName')"
+            :isGoBack="!isFromShare"
+            :isClose="isFromShare"
+            :showClose="false"
+            @goback="handleBack"
+        />
         <div class="growth-track-body">
             <div class="growth-hero">
                 <div class="growth-hero__top">
@@ -18,8 +24,8 @@
                             </span>
                             <span class="growth-hero__tag growth-hero__tag--zone">{{ zoneName }}</span>
                         </div>
-                    </div>
-                    <div class="growth-hero__share">
+                    </div> 
+                    <div class="growth-hero__share" @click="handleShare">
                         <Icon name="share" />
                         <span>{{ t("agriFile.forward") }}</span>
                     </div>
@@ -104,12 +110,39 @@ import albumUploadPopup from "../components/albumUploadPopup.vue";
 import leaveConfirmPopup from "@/components/popup/leaveConfirmPopup.vue";
 import tipPopup from "@/components/popup/tipPopup.vue";
 import { useI18n } from "@/i18n";
+import wx from "weixin-js-sdk";
 
 const { t } = useI18n();
 const route = useRoute();
 const router = useRouter();
 
 const isAbnormal = computed(() => route.query.type === "abnormal");
+
+function isTruthyFlag(value) {
+    return value === 1 || value === "1" || value === true || value === "true";
+}
+
+function parseMaybeJson(value) {
+    if (value == null || value === "") return null;
+    if (typeof value === "object") return value;
+    try {
+        return JSON.parse(value);
+    } catch {
+        return null;
+    }
+}
+
+/** 通过分享进入:无返回箭头 */
+const isFromShare = computed(() => {
+    if (isTruthyFlag(route.query.fromShare)) return true;
+    const paramsPage = parseMaybeJson(route.query.paramsPage);
+    if (isTruthyFlag(paramsPage?.fromShare)) return true;
+    const miniJson = parseMaybeJson(route.query.miniJson);
+    if (isTruthyFlag(miniJson?.fromShare)) return true;
+    if (isTruthyFlag(parseMaybeJson(miniJson?.paramsPage)?.fromShare)) return true;
+    return false;
+});
+
 const theme = computed(() => t("agriFile.interactTheme"));
 const level = computed(() => t("agriFile.riskLevel2"));
 const reason = computed(() => t("agriFile.interactReason"));
@@ -129,6 +162,23 @@ const situationList = [
     { nameKey: "agriFile.rootAbnormal" },
 ];
 
+const handleShare = () => {
+    const query = {
+        askInfo: { title: "转发巡园要点", content: "是否分享给好友" },
+        shareText: "速长窗口不追肥?后期发育慢人一步,直接掉队!",
+        targetUrl: "growth_track",
+        paramsPage: JSON.stringify({
+            id: route.query.id,
+            type: route.query.type,
+            fromShare: 1,
+        }),
+        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 handleSubmit = () => {
     if (selectedOptionId.value == null) {
         ElMessage.warning(t("agriFile.pleaseSelectOption"));
@@ -137,7 +187,7 @@ const handleSubmit = () => {
     ElMessage.success(t("agriFile.submitSuccess"));
 };
 
-const needDraw = ref(false);
+const needDraw = ref(true);
 const handleUploadClick = () => {
     if (needDraw.value) {
         router.push({
@@ -177,10 +227,10 @@ const handleContinueUpload = () => {
 const hasAnswered = () => selectedOptionId.value != null;
 
 const handleBack = () => {
-    // if (!isAbnormal.value && !hasAnswered()) {
-    //     showLeavePopup.value = true;
-    //     return;
-    // }
+    if (!isAbnormal.value && !hasAnswered()) {
+        showLeavePopup.value = true;
+        return;
+    }
     router.back();
 };
 
@@ -190,7 +240,6 @@ const confirmLeave = () => {
 
 onActivated(() => {
     if (route.query.showUpload !== "1") return;
-    needDraw.value = false;
     nextTick(() => {
         showUploadPopup.value = true;
         const { showUpload, ...restQuery } = route.query;

+ 359 - 31
src/views/old_mini/growth_report/alert_detail.vue

@@ -2,77 +2,405 @@
     <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 class="section-card">
+                <div class="section-card__title">综合研判</div>
+                <div class="section-card__desc">{{ analysis.summary }}</div>
+
+                <div 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 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 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 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 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 class="diag-item__tag">{{ item.tag }}</span>
+                        <div class="diag-item__title">{{ item.title }}</div>
+                        <div class="diag-item__desc">{{ item.desc }}</div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 农事建议 -->
+            <div 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 class="alert-card__content">{{ content }}</div>
             </div>
         </div>
     </div>
 </template>
 
 <script setup>
-import { computed } from "vue";
+import { computed, ref } 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);
+
+const pageTitle = computed(() => route.query.title || "具体预警/具体胁迫");
+
+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 patrolCover = require("@/assets/img/common/tp-3.png");
+
+/** 综合研判 */
+const analysis = ref({
+    summary:
+        "综合巡园照片、当前物候、近期气象和土壤条件判断,本次异常以持续高温干旱造成的水分失衡为主,已表现为嫩梢萎蔫、卷曲和局部灼伤,并伴有浅层根区干燥",
+    crop: {
+        name: "荔枝",
+        cover: cropCover,
+        metrics: [
+            {
+                label: "当前物候",
+                value: "秋梢新叶速长—红黄叶旺长期",
+                icon: phenologyIcon,
+            },
+            {
+                label: "气象预警",
+                value: "连续3天最高温36-38°C,近7天基本无有效降雨",
+                icon: weatherIcon,
+            },
+            {
+                label: "土壤类型",
+                value: "酸性沙土,树盘及浅层根区明显偏干",
+                icon: soilIcon,
+            },
+        ],
+    },
+});
+
+/** 巡园结果与分析 */
+const patrolResults = ref([
+    {
+        id: 1,
+        title: "嫩梢萎蔫与灼伤",
+        cover: patrolCover,
+        desc: "最嫩叶出现轻度皱缩、展开不完全,个别嫩梢顶部出现干尖。成熟叶未见明显连续叶缘焦枯,老叶也未见明显脉间黄化。",
+    },
+    {
+        id: 2,
+        title: "嫩梢萎蔫与灼伤",
+        cover: patrolCover,
+        desc: "最嫩叶出现轻度皱缩、展开不完全,个别嫩梢顶部出现干尖。成熟叶未见明显连续叶缘焦枯,老叶也未见明显脉间黄化。",
+    },
+]);
+
+/** 诊断研判分析 */
+const diagnosisList = ref([
+    {
+        id: 1,
+        tag: "表观影响",
+        title: "浅层吸收根已经受到干旱影响,但尚无腐烂迹象",
+        desc: "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现",
+    },
+    {
+        id: 2,
+        tag: "表观影响",
+        title: "浅层吸收根已经受到干旱影响,但尚无腐烂迹象",
+        desc: "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现",
+    },
+]);
+
+/** 农事建议 */
+const adviceList = ref([
+    {
+        id: 1,
+        title: "优先恢复根区水分",
+        desc: "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现",
+    },
+    {
+        id: 2,
+        title: "暂不建议立即大剂量追肥",
+        desc: "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现",
+    },
+]);
 </script>
 
 <style lang="scss" scoped>
 .alert-detail-page {
     min-height: 100vh;
-    background: #f0f4f7;
+    background: #F2F5F6;
 }
 
 .alert-detail-body {
-    padding: 16px;
+    padding: 10px 10px 24px;
     box-sizing: border-box;
+    overflow-y: auto;
+    height: calc(100vh - 44px);
 }
 
-.alert-card {
-    padding: 16px;
-    border-radius: 12px;
+.section-card {
+    margin-bottom: 12px;
+    padding: 10px;
+    border-radius: 10px;
     background: #fff;
     box-sizing: border-box;
 
-    &__header {
+    &__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;
+    }
+}
+
+.crop-panel {
+    margin-top: 12px;
+    padding: 10px;
+    border-radius: 10px;
+    background: #E7F1FD;
+    box-sizing: border-box;
+
+    &__head {
         display: flex;
         align-items: center;
-        gap: 8px;
+        gap: 6px;
+        margin-bottom: 10px;
     }
 
-    &__icon {
+    &__cover {
         width: 24px;
         height: 24px;
+        border-radius: 50%;
+        object-fit: cover;
         flex-shrink: 0;
-        object-fit: contain;
+        background: #fff;
     }
 
-    &__title {
+    &__name {
         font-size: 16px;
-        font-weight: 600;
-        line-height: 24px;
-        color: #000;
+        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 {
-        margin-top: 12px;
+        display: flex;
+        align-items: flex-start;
+        gap: 6px;
+        flex: 1;
+        min-width: 0;
         font-size: 14px;
         line-height: 22px;
-        color: #666;
+        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;
     }
 }

+ 63 - 22
src/views/old_mini/growth_report/components/CropAlertPanel.vue

@@ -3,15 +3,13 @@
         <floating-panel
             class="floating-panel"
             :class="{ 'background-panel': isBackground }"
+            :style="{ bottom: `${tabBarHeight}px` }"
             v-model:height="height"
             :anchors="anchors"
             :content-draggable="true"
             @height-change="handleHeightChange"
         >
-            <div
-                class="floating-panel-content"
-                :style="{ paddingBottom: `${tabBarHeight}px` }"
-            >
+            <div class="floating-panel-content">
                 <div class="panel-shell" ref="panelShellRef">
                     <div class="crop-header">
                         <div class="crop-info">
@@ -103,10 +101,12 @@
 import { FloatingPanel } from "vant";
 import { CaretBottom, Link } from "@element-plus/icons-vue";
 import { nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
+import { useStore } from "vuex";
 import { useI18n } from "@/i18n";
 import { useRouter } from "vue-router";
 
 const router = useRouter();
+const store = useStore();
 const { t } = useI18n();
 
 const GUIDE_STORAGE_KEY = "GROWTH_REPORT_CROP_SWITCH_GUIDE";
@@ -129,7 +129,16 @@ const props = defineProps({
 const emit = defineEmits(["switchCategory", "viewDetail", "heightChange"]);
 
 const weatherIcon = require("@/assets/img/report/weather.png");
-const tabBarHeight = ref(Number(localStorage.getItem("tabBarHeight")) || 50);
+
+/** 底部 tabBar 高度(含小程序安全区),优先读 DOM / store */
+const resolveTabBarHeight = () => {
+    const fromDom = document.querySelector(".van-tabbar")?.offsetHeight;
+    const fromStore = Number(store.state.home.tabBarHeight) || 0;
+    const fromStorage = Number(localStorage.getItem("tabBarHeight")) || 0;
+    return fromDom || fromStore || fromStorage || 50;
+};
+
+const tabBarHeight = ref(resolveTabBarHeight());
 
 const currentCropName = ref(props.cropName);
 const cropMenuVisible = ref(false);
@@ -177,43 +186,69 @@ const handleSelectCrop = (name) => {
 
 const PANEL_HEADER_BAR = 14; // van-floating-panel 原生 header 高度
 const PANEL_BOTTOM_GAP = 12;
-const COLLAPSED_HEIGHT = 116;
+/** 收起态:仅作物头,高度不含 tabBar(tabBar 用 bottom 顶开) */
+const COLLAPSED_CONTENT_HEIGHT = 72;
 
-const defaultHeight = ref(COLLAPSED_HEIGHT);
-const anchors = ref([defaultHeight.value + tabBarHeight.value, 280 + tabBarHeight.value]);
-const height = ref(anchors.value[1]);
+/** 锚点只算面板自身高度,底部用 style.bottom 避开 tabBar,避免高度+padding 重复抬高 */
+const getCollapsedAnchor = () => COLLAPSED_CONTENT_HEIGHT;
 
+const anchors = ref([getCollapsedAnchor(), 280]);
+const height = ref(anchors.value[1]);
 const isBackground = ref(false);
 const panelShellRef = ref(null);
 let resizeObserver = null;
 let syncing = false;
 
+const emitPanelHeight = (panelH = height.value) => {
+    // 对外抛出「距视口底」的占用高度,方便邀请按钮等避让
+    emit("heightChange", panelH + tabBarHeight.value);
+};
+
 const syncMidAnchor = () => {
     const shell = panelShellRef.value;
     if (!shell || syncing) return;
 
+    tabBarHeight.value = resolveTabBarHeight();
+    const collapsed = getCollapsedAnchor();
+
     // scrollHeight 才是内容真实高度;offsetHeight 在被裁切时会偏小
     const contentH = shell.scrollHeight;
-    const idealMid = Math.round(
-        contentH + PANEL_HEADER_BAR + PANEL_BOTTOM_GAP + tabBarHeight.value
-    );
-    // 留一点顶部空间,避免直接顶满屏
-    const maxMid = Math.round(window.innerHeight) - 48;
-    const nextMid = Math.max(240, Math.min(idealMid, maxMid));
-
-    if (Math.abs(nextMid - anchors.value[1]) < 2) return;
+    const idealMid = Math.round(contentH + PANEL_HEADER_BAR + PANEL_BOTTOM_GAP);
+    // 留一点顶部空间,并扣掉底部 tabBar
+    const maxMid = Math.round(window.innerHeight - tabBarHeight.value) - 48;
+    const nextMid = Math.max(collapsed + 80, Math.min(idealMid, maxMid));
+
+    if (
+        Math.abs(nextMid - anchors.value[1]) < 2 &&
+        Math.abs(collapsed - anchors.value[0]) < 2
+    ) {
+        return;
+    }
 
     syncing = true;
     const wasAtMid = Math.abs(height.value - anchors.value[1]) < 2;
-    anchors.value = [defaultHeight.value, nextMid];
+    const wasCollapsed = Math.abs(height.value - anchors.value[0]) < 2;
+    anchors.value = [collapsed, nextMid];
     if (wasAtMid) {
         height.value = nextMid;
+    } else if (wasCollapsed) {
+        height.value = collapsed;
     }
     nextTick(() => {
         syncing = false;
+        emitPanelHeight();
     });
 };
 
+watch(
+    () => store.state.home.tabBarHeight,
+    (val) => {
+        if (!val) return;
+        tabBarHeight.value = val;
+        syncMidAnchor();
+    }
+);
+
 const alertCards = [
     {
         id: "warning",
@@ -248,24 +283,30 @@ const handlePatrolTip = (item) => {
 
 const handleHeightChange = ({ height: nextHeight }) => {
     isBackground.value = nextHeight < anchors.value[1];
-    emit("heightChange", nextHeight);
+    emitPanelHeight(nextHeight);
 };
 
 onMounted(() => {
     nextTick(() => {
-        // 等布局完成后再量一次,避免首屏高度偏小
+        tabBarHeight.value = resolveTabBarHeight();
+        // 等布局完成后再量一次,避免首屏高度偏小;小程序里 tabBar 可能稍晚才量到
         requestAnimationFrame(() => {
             syncMidAnchor();
             setTimeout(() => {
+                tabBarHeight.value = resolveTabBarHeight();
                 syncMidAnchor();
-                emit("heightChange", height.value);
+                emitPanelHeight();
                 tryShowGuide();
             }, 80);
+            setTimeout(() => {
+                tabBarHeight.value = resolveTabBarHeight();
+                syncMidAnchor();
+            }, 600);
         });
         if (!panelShellRef.value || typeof ResizeObserver === "undefined") return;
         resizeObserver = new ResizeObserver(() => {
             syncMidAnchor();
-            emit("heightChange", height.value);
+            emitPanelHeight();
         });
         resizeObserver.observe(panelShellRef.value);
     });

+ 0 - 1
src/views/old_mini/growth_report/index.vue

@@ -313,7 +313,6 @@ const handleViewDetail = (item) => {
         path: "/alert_detail",
         query: {
             title: item?.title || "具体预警",
-            content: item?.content || "",
         },
     });
 };

+ 183 - 2
src/views/old_mini/recordDetails/map/mapManage.js

@@ -167,12 +167,22 @@ class MapManage {
   }
 
   initMap(location, target, options = {}) {
-    const { editable = true, constrainedDrawing = false, onDrawOutsideBoundary, zoneStyle } = options;
+    const {
+      editable = true,
+      constrainedDrawing = false,
+      onDrawOutsideBoundary,
+      zoneStyle,
+      preventOverlapExisting = false,
+      onDrawOverlap,
+    } = options;
     this.zoneStyle = zoneStyle ? { ...DEFAULT_ZONE_STYLE, ...zoneStyle } : { ...DEFAULT_ZONE_STYLE };
     this.editable = editable;
     this.constrainedDrawing = constrainedDrawing;
+    this.preventOverlapExisting = !!preventOverlapExisting;
     this.onDrawOutsideBoundary = typeof onDrawOutsideBoundary === "function" ? onDrawOutsideBoundary : null;
+    this.onDrawOverlap = typeof onDrawOverlap === "function" ? onDrawOverlap : null;
     this.constrainedDrawingReady = false;
+    this.overlapConstraintReady = false;
     this.boundaryGeometry = null;
     let level = 16;
     let coordinate = util.wktCastGeom(location).getFirstCoordinate();
@@ -185,8 +195,13 @@ class MapManage {
     this.kmap.addLayer(this.gridLayer.layer);
 
     if (this.editable) {
-      this.kmap.initDraw(() => {});
+      this.kmap.initDraw((e) => {
+        if (this.preventOverlapExisting && e?.feature) {
+          this.handleDrawEndOverlap(e.feature);
+        }
+      });
       this.kmap.modifyDraw();
+      this.setupOverlapConstraintListeners();
       this.setRegionDrawingActive(false);
     } else if (this.constrainedDrawing) {
       this.setupConstrainedDrawing();
@@ -194,6 +209,169 @@ class MapManage {
     }
   }
 
+  notifyDrawOverlap() {
+    this.onDrawOverlap?.();
+  }
+
+  toTurfFeature(geometry) {
+    if (!geometry || !this.kmap) return null;
+    const geoJson = new GeoJSON();
+    const projection = this.kmap.map.getView().getProjection();
+    try {
+      return turf.feature(
+        geoJson.writeGeometryObject(geometry, {
+          dataProjection: "EPSG:4326",
+          featureProjection: projection,
+        })
+      );
+    } catch {
+      return null;
+    }
+  }
+
+  /** 点是否落在已有品种范围内 */
+  isCoordinateInExistingVariety(coordinate) {
+    if (!coordinate || !this.existingVarietyLayer?.source) return false;
+    return this.existingVarietyLayer.source.getFeatures().some((feature) => {
+      try {
+        return !!feature.getGeometry()?.intersectsCoordinate(coordinate);
+      } catch {
+        return false;
+      }
+    });
+  }
+
+  /**
+   * 几何是否与禁止区域重叠(已有品种 + 其他已勾画面,可排除自身)
+   * 仅面积相交算重叠,边界相接允许
+   */
+  geometryOverlapsForbidden(geometry, excludeFeature = null) {
+    if (!geometry || !this.kmap) return false;
+    const drawn = this.toTurfFeature(geometry);
+    if (!drawn) return false;
+
+    const overlaps = (otherFeature) => {
+      const otherGeom = otherFeature.getGeometry();
+      if (!otherGeom) return false;
+      const other = this.toTurfFeature(otherGeom);
+      if (!other) return false;
+      try {
+        let result = null;
+        try {
+          result = turf.intersect(turf.featureCollection([drawn, other]));
+        } catch {
+          result = turf.intersect(drawn, other);
+        }
+        if (!result?.geometry) return false;
+        return turf.area(result) > 0.5;
+      } catch {
+        return false;
+      }
+    };
+
+    const checkFeatures = (features) =>
+      features.some((feature) => {
+        if (excludeFeature && feature === excludeFeature) return false;
+        return overlaps(feature);
+      });
+
+    if (checkFeatures(this.existingVarietyLayer?.source?.getFeatures?.() || [])) {
+      return true;
+    }
+    if (checkFeatures(this.kmap.polygonLayer?.source?.getFeatures?.() || [])) {
+      return true;
+    }
+    return false;
+  }
+
+  handleDrawEndOverlap(feature) {
+    if (!feature) return;
+    const geometry = feature.getGeometry();
+    if (!this.geometryOverlapsForbidden(geometry, feature)) return;
+    if (this.kmap?.polygonLayer?.source) {
+      this.kmap.polygonLayer.source.removeFeature(feature);
+    }
+    this.notifyDrawOverlap();
+  }
+
+  handleModifyEndOverlap(feature) {
+    if (!feature) return;
+    const geometry = feature.getGeometry();
+    if (!this.geometryOverlapsForbidden(geometry, feature)) {
+      feature.unset("_preModifyGeom");
+      return;
+    }
+    const prev = feature.get("_preModifyGeom");
+    if (prev) {
+      feature.setGeometry(prev);
+    } else if (this.kmap?.polygonLayer?.source) {
+      this.kmap.polygonLayer.source.removeFeature(feature);
+    }
+    feature.unset("_preModifyGeom");
+    this.notifyDrawOverlap();
+  }
+
+  bindSketchOverlapConstraint(sketch) {
+    if (!sketch) return;
+    const onSketchChange = () => {
+      if (!this.preventOverlapExisting) return;
+      const geometry = sketch.getGeometry();
+      if (!geometry) return;
+      const type = geometry.getType();
+      if (type !== "LineString" && type !== "Point") return;
+      const last = this.getLastCoordinateFromGeometry(geometry);
+      if (last && this.isCoordinateInExistingVariety(last)) {
+        sketch.getGeometry()?.un("change", onSketchChange);
+        if (this.kmap?.draw && typeof this.kmap.draw.abortDrawing === "function") {
+          this.kmap.draw.abortDrawing();
+        }
+        this.notifyDrawOverlap();
+      }
+    };
+    sketch.getGeometry()?.on("change", onSketchChange);
+    const cleanup = () => sketch.getGeometry()?.un("change", onSketchChange);
+    this.kmap.draw.once("drawend", cleanup);
+    this.kmap.draw.once("drawabort", cleanup);
+  }
+
+  setupOverlapConstraintListeners() {
+    if (!this.kmap?.draw || this.overlapConstraintReady) return;
+    this.overlapConstraintReady = true;
+    this.kmap.draw.on("drawstart", (e) => {
+      if (!this.preventOverlapExisting) return;
+      const coordinate = e.coordinate || this.getLastCoordinateFromGeometry(e.feature?.getGeometry());
+      if (coordinate && this.isCoordinateInExistingVariety(coordinate)) {
+        if (this.kmap?.draw && typeof this.kmap.draw.abortDrawing === "function") {
+          this.kmap.draw.abortDrawing();
+        }
+        this.notifyDrawOverlap();
+        return;
+      }
+      this.bindSketchOverlapConstraint(e.feature);
+    });
+    if (this.kmap.modify) {
+      this.kmap.modify.on("modifystart", (e) => {
+        if (!this.preventOverlapExisting) return;
+        e.features?.forEach((feature) => {
+          const geom = feature.getGeometry();
+          if (geom) feature.set("_preModifyGeom", geom.clone());
+        });
+      });
+      this.kmap.modify.on("modifyend", (e) => {
+        if (!this.preventOverlapExisting) return;
+        e.features?.forEach((feature) => this.handleModifyEndOverlap(feature));
+      });
+    }
+  }
+
+  /** 当前勾画面是否与已有品种范围重叠(确认前提交校验) */
+  hasDrawnOverlapWithExisting() {
+    if (!this.kmap?.polygonLayer?.source) return false;
+    return this.kmap.polygonLayer.source.getFeatures().some((feature) =>
+      this.geometryOverlapsForbidden(feature.getGeometry(), feature)
+    );
+  }
+
   isCoordinateInsideBoundary(coordinate) {
     if (!this.boundaryGeometry || !coordinate || !this.kmap) return false;
     try {
@@ -855,8 +1033,11 @@ class MapManage {
     this.regionDrawingActive = false;
     this.constrainedDrawing = false;
     this.constrainedDrawingReady = false;
+    this.overlapConstraintReady = false;
+    this.preventOverlapExisting = false;
     this.boundaryGeometry = null;
     this.onDrawOutsideBoundary = null;
+    this.onDrawOverlap = null;
     this.selectedGridIds?.clear();
   }
 

+ 80 - 11
src/views/old_mini/work_detail/index.vue

@@ -1,8 +1,13 @@
 <template>
     <div class="work-detail">
-        <custom-header :name="$t('workDetail.title')" v-if="!miniJson?.hideDraw" :showClose="false" isGoBack
-            @goback="handleBack" />
-
+        <custom-header
+            v-if="!miniJson?.hideDraw"
+            :name="$t('workDetail.title')"
+            :isGoBack="!isFromShare"
+            :isClose="isFromShare"
+            :showClose="false"
+            @goback="handleBack"
+        />
         <div class="work-detail-content">
             <!-- 顶部状态 -->
             <div class="content-status">
@@ -28,7 +33,7 @@
                 </div>
             </div>
 
-            <div class="work-wrap has-bottom" :style="farmData.work_status === 0 ? 'margin-top: 35px;' : ''">
+            <div class="work-wrap">
                 <!-- 农事组信息 -->
                 <div class="group-info group-box" v-if="farmData.interaction_reason">
                     <div class="group-name">
@@ -129,8 +134,8 @@
             <div class="primary-btn" @click="goGrowthTrack">巡园要点</div>
         </div>
 
-        <div class="footer-btn">
-            <div class="secondary-btn">转发</div>
+        <div class="footer-btn" v-if="!isFromShare">
+            <div class="secondary-btn" @click="handleShare">转发</div>
         </div>
         <ExecutePopup ref="executePopupRef" @executeSuccess="getDetail" />
         <upload-tips v-model:show="showUploadTipsPopup" />
@@ -160,6 +165,47 @@ const router = useRouter();
 // const info = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
 const info = { appType: 1 };
 
+function isTruthyFlag(value) {
+    return value === 1 || value === "1" || value === true || value === "true";
+}
+
+function parseMaybeJson(value) {
+    if (value == null || value === "") return null;
+    if (typeof value === "object") return value;
+    try {
+        return JSON.parse(value);
+    } catch {
+        return null;
+    }
+}
+
+/**
+ * 分享链路:dev_login 会把整份登录 query 塞进 miniJson,
+ * 真正的页面参数在 miniJson.paramsPage 里(如 id、fromShare)
+ */
+function resolvePageParams() {
+    const mini = parseMaybeJson(route.query.miniJson);
+    if (mini) {
+        const page = parseMaybeJson(mini.paramsPage) || {};
+        return { ...mini, ...page };
+    }
+    const paramsPage = parseMaybeJson(route.query.paramsPage);
+    if (paramsPage) {
+        return { ...route.query, ...paramsPage };
+    }
+    return { ...(route.query || {}) };
+}
+
+const pageParams = computed(() => resolvePageParams());
+const workId = computed(() => pageParams.value?.id ?? null);
+
+/** 通过分享进入:无返回箭头、无底部转发 */
+const isFromShare = computed(() => isTruthyFlag(pageParams.value?.fromShare));
+
+const syncMiniJsonFromRoute = () => {
+    miniJson.value = resolvePageParams();
+};
+
 /** 接口根级与 detail 合并后的详情(pesticideList、prescriptionList) */
 const detail = ref({
     farmId: null,
@@ -265,6 +311,7 @@ onBeforeUnmount(() => {
 });
 
 onActivated(() => {
+    syncMiniJsonFromRoute();
     getDetail();
 });
 
@@ -306,8 +353,9 @@ const backgroundFarmWorkStatus = (status) => {
 }
 
 const getDetail = () => {
-    if (!route.query.id) return;
-    VE_API.monitor.getWorkDetail({ id: route.query.id }).then(res => {
+    const id = workId.value;
+    if (id == null || id === "") return;
+    VE_API.monitor.getWorkDetail({ id }).then(res => {
         if (res.code === 200 && res.data.length) {
             farmData.value = res.data[0];
             initWorkDetailMap();
@@ -435,7 +483,7 @@ const goGrowthTrack = () => {
     router.push({
         path: "/growth_track",
         query: {
-            id: farmData.value?.id || route.query.id,
+            id: farmData.value?.id || workId.value || route.query.id,
             type: farmData.value?.type || "growth",
         },
     });
@@ -445,6 +493,25 @@ const handleBack = () => {
     router.back();
 };
 
+const handleShare = () => {
+    const query = {
+        askInfo: { title: "转发农事", content: "是否分享给好友" },
+        shareText:
+            ((farmData.value?.work_name ||
+            detail.value?.farmWorkName) + '  已触发,请在最佳农时内及时执行') ||
+            "农事详情",
+        targetUrl: "work_detail",
+        paramsPage: JSON.stringify({
+            id: farmData.value?.id || workId.value || route.query.id,
+            fromShare: 1,
+        }),
+        imageUrl: "https://birdseye-img.sysuimars.com/birdseye-look-mini/work_img.png",
+    };
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
+    });
+};
+
 const handleConvert = () => {
     const query = {
         askInfo: { title: "农事执行", content: "是否分享该农事给好友" },
@@ -501,8 +568,10 @@ const areaMap = new AreaMap();
         background: #fff;
         box-shadow: 2px 2px 4.5px 0px rgba(0, 0, 0, 0.4);
         display: flex;
-        align-items: center;
+        align-items: flex-start;
+        padding-top: 10px;
         justify-content: center;
+        box-sizing: border-box;
         .secondary-btn {
             padding: 0 38px;
             height: 40px;
@@ -633,7 +702,7 @@ const areaMap = new AreaMap();
 .work-wrap {
     position: relative;
     top: 0;
-    padding: 0 12px 12px;
+    padding: 0 12px 62px;
     z-index: 2;
     // &.has-bottom {
     //     margin-bottom: 88px;

+ 16 - 1
src/views/old_mini/work_execute/index.vue

@@ -62,6 +62,7 @@ import farmHeader from "@/components/farmHeader.vue";
 import farmCalendarPopup from "@/components/popup/farmCalendarPopup.vue";
 import surveyAskPopup from "@/components/popup/surveyAskPopup.vue";
 import { useI18n } from "@/i18n";
+import wx from "weixin-js-sdk";
 
 const { t } = useI18n();
 const store = useStore();
@@ -131,7 +132,21 @@ const handleDateSelect = (date) => {
     filterDate.value = date ?? formatDateYMD();
 };
 
-const handleForward = () => {
+const handleForward = (item) => {
+    const workName = item?.title || item?.farmName || "农事详情";
+    const query = {
+        askInfo: { title: "转发农事", content: "是否分享给好友" },
+        shareText: `${workName}  已触发,请在最佳农时内及时执行`,
+        targetUrl: "work_detail",
+        paramsPage: JSON.stringify({
+            id: item?.id,
+            fromShare: 1,
+        }),
+        imageUrl: "https://birdseye-img.sysuimars.com/birdseye-look-mini/work_img.png",
+    };
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
+    });
 };
 
 const handleViewDetail = (item) => {