Bläddra i källkod

fix: 校验位置,农事列表

lxf 2 veckor sedan
förälder
incheckning
5c20126c0c

BIN
src/assets/img/agricultural/arrow.png


BIN
src/assets/img/agricultural/light.png


BIN
src/assets/img/agricultural/proxies.png


BIN
src/assets/img/home/user-icon-fill.png


+ 1 - 0
src/components/popup/diagnosisReportPopup.vue

@@ -4,6 +4,7 @@
         round
         teleport="body"
         class="diagnosis-report-popup"
+        :z-index="20000"
         :close-on-click-overlay="true"
     >
         <img class="diagnosis-report-popup__image" src="@/assets/img/agricultural/report.png" alt="" />

+ 270 - 0
src/components/popup/phenologyUpdatePopup.vue

@@ -0,0 +1,270 @@
+<template>
+    <popup
+        v-model:show="showValue"
+        round
+        teleport="body"
+        :z-index="20000"
+        class="phenology-update-popup"
+    >
+        <!-- :close-on-click-overlay="false" -->
+        <div class="phenology-update-popup__head">
+            <img class="phenology-update-popup__light" src="@/assets/img/agricultural/light.png" alt="" />
+            <span class="phenology-update-popup__head-text">{{ serviceName }} 申请更新生育期</span>
+        </div>
+
+        <div class="phenology-update-popup__body">
+            <div class="phenology-update-popup__compare">
+                <div class="phenology-update-popup__stage">
+                    <img class="phenology-update-popup__stage-img" :src="currentImage" alt="" />
+                    <div class="phenology-update-popup__stage-label phenology-update-popup__stage-label--current">
+                        当前:{{ currentStage }}
+                    </div>
+                </div>
+                <img class="phenology-update-popup__arrow" src="@/assets/img/agricultural/arrow.png" alt="" />
+                <div class="phenology-update-popup__stage">
+                    <img class="phenology-update-popup__stage-img" :src="targetImage" alt="" />
+                    <div class="phenology-update-popup__stage-label phenology-update-popup__stage-label--target">
+                        变更为:{{ targetStage }}
+                    </div>
+                </div>
+            </div>
+
+            <div class="phenology-update-popup__info">
+                <div class="phenology-update-popup__info-row">
+                    <span class="phenology-update-popup__info-label">更新品种:</span>
+                    <span class="phenology-update-popup__info-value">{{ varietyName }}</span>
+                </div>
+                <div class="phenology-update-popup__info-row">
+                    <span class="phenology-update-popup__info-label">农场位置:</span>
+                    <span class="phenology-update-popup__info-value">{{ farmLocation }}</span>
+                </div>
+            </div>
+
+            <div class="phenology-update-popup__actions">
+                <div class="phenology-update-popup__btn phenology-update-popup__btn--ghost" @click="handleReject">
+                    暂不更新
+                </div>
+                <div class="phenology-update-popup__btn phenology-update-popup__btn--primary" @click="handleConfirm">
+                    确认更新
+                </div>
+            </div>
+        </div>
+    </popup>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { Popup } from "vant";
+
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false,
+    },
+    /** 农服名称,后续由接口赋值 */
+    serviceName: {
+        type: String,
+        default: "某某农服",
+    },
+    /** 当前生育期 */
+    currentStage: {
+        type: String,
+        default: "花穗期",
+    },
+    /** 目标生育期 */
+    targetStage: {
+        type: String,
+        default: "小果期",
+    },
+    /** 当前生育期图片 */
+    currentImage: {
+        type: String,
+        default: "https://birdseye-img-ali-cdn.sysuimars.com/images/DJI_20260802135147_0183_V_code-ws0vjpmd33uy.JPG",
+    },
+    /** 目标生育期图片 */
+    targetImage: {
+        type: String,
+        default: "https://birdseye-img-ali-cdn.sysuimars.com/z_image_v2/2026-08-04/farm_98769/8月4日黄埔粤牧农业/DJI_20260804090945_0153_V_code-ws0srsddpy0p.JPG",
+    },
+    /** 更新品种 */
+    varietyName: {
+        type: String,
+        default: "荔枝-桂味",
+    },
+    /** 农场位置 */
+    farmLocation: {
+        type: String,
+        default: "位置位置位置",
+    },
+});
+
+const emit = defineEmits(["update:show", "reject", "confirm"]);
+
+const showValue = computed({
+    get: () => props.show,
+    set: (val) => emit("update:show", val),
+});
+
+const handleReject = () => {
+    emit("reject");
+    showValue.value = false;
+};
+
+const handleConfirm = () => {
+    emit("confirm");
+    showValue.value = false;
+};
+</script>
+
+<style scoped lang="scss">
+.phenology-update-popup {
+    width: 86%;
+    overflow: hidden;
+    padding: 20px;
+    background: linear-gradient(0deg, #FFFFFF 75%, #BDE2FF 105%, #2199F8 120%);
+
+    &__head {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+        padding: 4px 2px;
+        background: linear-gradient(90deg, #2199F8 43.19%, rgba(84, 179, 255, 0) 100%);
+        border-radius: 4px 0 0 4px;
+        box-sizing: border-box;
+    }
+
+    &__light {
+        width: 28px;
+        height: 28px;
+        flex-shrink: 0;
+        object-fit: contain;
+    }
+
+    &__head-text {
+        color: #fff;
+        font-size: 16px;
+        font-weight: 500;
+        line-height: 24px;
+    }
+
+    &__body {
+        padding: 10px 0 0px;
+        box-sizing: border-box;
+    }
+
+    &__compare {
+        position: relative;
+        display: flex;
+        align-items: center;
+        gap: 10px;
+    }
+
+    &__stage {
+        position: relative;
+        flex: 1;
+        min-width: 0;
+        overflow: hidden;
+        border-radius: 5px;
+        background: #f5f5f5;
+    }
+
+    &__stage-img {
+        display: block;
+        width: 100%;
+        height: 126px;
+        object-fit: cover;
+    }
+
+    &__stage-label {
+        position: absolute;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        padding: 0 8px;
+        color: #F2F4F5;
+        font-size: 12px;
+        height: 25px;
+        border-radius: 0 0 5px 5px;
+        line-height: 25px;
+        text-align: center;
+        box-sizing: border-box;
+
+        &--current {
+            background: rgba(0, 0, 0, 0.7);
+        }
+
+        &--target {
+            background: rgba(33, 153, 248, 0.8);
+            backdrop-filter: blur(4px);
+        }
+    }
+
+    &__arrow {
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        z-index: 2;
+        width: 36px;
+        height: 36px;
+        margin-left: -18px;
+        margin-top: -18px;
+        object-fit: contain;
+    }
+
+    &__info {
+        margin-top: 10px;
+        padding: 7px 10px;
+        border-radius: 10px;
+        background: #F7F7F7;
+        box-sizing: border-box;
+    }
+
+    &__info-row {
+        display: flex;
+        align-items: flex-start;
+        font-size: 14px;
+        line-height: 21px;
+
+        & + & {
+            margin-top: 4px;
+        }
+    }
+
+    &__info-label {
+        flex-shrink: 0;
+        color: rgba(10, 10, 10, 0.5);
+    }
+
+    &__info-value {
+        color: #0A0A0A;
+        word-break: break-all;
+    }
+
+    &__actions {
+        display: flex;
+        justify-content: space-between;
+        gap: 12px;
+        margin-top: 16px;
+    }
+
+    &__btn {
+        padding: 0 22px;
+        height: 40px;
+        line-height: 40px;
+        border-radius: 25px;
+        font-size: 16px;
+        text-align: center;
+        box-sizing: border-box;
+
+        &--ghost {
+            background: rgba(236, 236, 236, 0.5);
+            color: rgba(0, 0, 0, 0.6);
+        }
+
+        &--primary {
+            background: #2199f8;
+            color: #fff;
+        }
+    }
+}
+</style>

+ 149 - 0
src/components/popup/proxyAuthPopup.vue

@@ -0,0 +1,149 @@
+<template>
+    <popup
+        v-model:show="showValue"
+        round
+        teleport="body"
+        :z-index="20000"
+        class="proxy-auth-popup"
+    >
+        <!-- :close-on-click-overlay="false" -->
+        <div class="proxy-auth-popup__body">
+            <img class="proxy-auth-popup__icon" src="@/assets/img/agricultural/proxies.png" alt="" />
+            <div class="proxy-auth-popup__title">开启信息更新授权</div>
+            <div class="proxy-auth-popup__desc">
+                授权<span>「{{ serviceName }}」</span>帮助您更新生育期/物候期信息
+            </div>
+            <div class="proxy-auth-popup__tip">
+                每次修改都需您确认,农服提交后,会推送给您确认,您
+                <span>同意</span>
+                后才可生效
+            </div>
+            <div class="proxy-auth-popup__actions">
+                <div class="proxy-auth-popup__btn proxy-auth-popup__btn--ghost" @click="handleReject">
+                    不同意
+                </div>
+                <div class="proxy-auth-popup__btn proxy-auth-popup__btn--primary" @click="handleAllow">
+                    同意
+                </div>
+            </div>
+        </div>
+    </popup>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { Popup } from "vant";
+
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false,
+    },
+    /** 农服名称,后续由接口赋值 */
+    serviceName: {
+        type: String,
+        default: "农服名称",
+    },
+});
+
+const emit = defineEmits(["update:show", "reject", "allow"]);
+
+const showValue = computed({
+    get: () => props.show,
+    set: (val) => emit("update:show", val),
+});
+
+const handleReject = () => {
+    emit("reject");
+    showValue.value = false;
+};
+
+const handleAllow = () => {
+    emit("allow");
+    showValue.value = false;
+};
+</script>
+
+<style scoped lang="scss">
+.proxy-auth-popup {
+    width: 300px;
+    overflow: visible;
+    background: linear-gradient(180deg, #d6ecff 0%, #ffffff 42%);
+
+    &__body {
+        padding: 20px;
+        text-align: center;
+        box-sizing: border-box;
+    }
+
+    &__icon {
+        display: block;
+        width: 73px;
+        height: 73px;
+        margin: 0 auto 10px;
+        object-fit: contain;
+    }
+
+    &__title {
+        color: #1a1a1a;
+        font-size: 20px;
+        font-weight: 500;
+        line-height: 34px;
+    }
+
+    &__desc {
+        margin-top: 10px;
+        color: #000000;
+        font-size: 16px;
+        line-height: 24px;
+        text-align: left;
+
+        span {
+            color: #2199F8;
+            font-weight: 500;
+        }
+    }
+
+    &__tip {
+        margin-top: 10px;
+        padding: 5px 10px;
+        border-radius: 5px;
+        background: rgba(174, 174, 174, 0.16);
+        color: #5C5C5C;
+        font-size: 12px;
+        line-height: 20px;
+        text-align: left;
+
+        span {
+            color: #2199f8;
+        }
+    }
+
+    &__actions {
+        display: flex;
+        justify-content: space-between;
+        gap: 12px;
+        margin-top: 20px;
+    }
+
+    &__btn {
+        height: 40px;
+        line-height: 40px;
+        border-radius: 25px;
+        font-size: 16px;
+        text-align: center;
+        box-sizing: border-box;
+        padding: 0 38px;
+
+        &--ghost {
+            background: rgba(236, 236, 236, 0.5);
+            color: rgba(0, 0, 0, 0.6);
+        }
+
+        &--primary {
+            background: #2199f8;
+            color: #fff;
+        }
+    }
+}
+</style>

+ 41 - 70
src/components/popup/restorePlantingPopup.vue

@@ -1,6 +1,6 @@
 <template>
     <popup
-        v-model:show="showValue"
+        v-model:show="visible"
         round
         teleport="body"
         class="restore-planting-popup"
@@ -99,100 +99,71 @@
 </template>
 
 <script setup>
-import { computed, ref, watch } from "vue";
+import { computed, onMounted, ref } from "vue";
 import { Popup } from "vant";
 import { Check } from "@element-plus/icons-vue";
 import { useI18n } from "@/i18n";
 
 const { t } = useI18n();
 
-const props = defineProps({
-    show: {
-        type: Boolean,
-        default: false,
-    },
-    questionText: {
-        type: String,
-        default: "还记得农场的 “来梢” 的时间吗?",
-    },
-    highlightWord: {
-        type: String,
-        default: "来梢",
-    },
-    hintText: {
-        type: String,
-        default: "时间误差 5-7 天无需担心,系统将结合作物生长模拟模型自动校准",
-    },
-    sampleImage: {
-        type: String,
-        default: require("@/assets/img/agricultural/baidian.png"),
-    },
-    stageTag: {
-        type: String,
-        default: "白点",
-    },
-    detectText: {
-        type: String,
-        default: "生育期检测:当前检测到白点期",
-    },
-    phenologyQuestion: {
-        type: String,
-        default: "您的农场是否进入到 某某物候期 ?",
-    },
-    phenologyHighlight: {
-        type: String,
-        default: "某某物候期",
-    },
-    phenologyImage: {
-        type: String,
-        default: require("@/assets/img/common/sd-1.jpg"),
-    },
-});
-
-const emit = defineEmits(["update:show", "changeQuestion", "confirm", "viewGuide"]);
-
-const showValue = computed({
-    get: () => props.show,
-    set: (val) => emit("update:show", val),
-});
-
+/** 弹窗显隐由组件内接口控制,默认不展示 */
+const visible = ref(false);
 const questionType = ref("time"); // time | phenology
 const selectedDate = ref("");
 const phenologyAnswer = ref("reached");
 
+/** 问题内容:后续由接口赋值 */
+const questionText = ref("还记得农场的 “来梢” 的时间吗?");
+const highlightWord = ref("来梢");
+const hintText = ref("时间误差 5-7 天无需担心,系统将结合作物生长模拟模型自动校准");
+const sampleImage = ref(require("@/assets/img/agricultural/baidian.png"));
+const stageTag = ref("白点");
+const detectText = ref("生育期检测:当前检测到白点期");
+const phenologyQuestion = ref("您的农场是否进入到 某某物候期 ?");
+const phenologyHighlight = ref("某某物候期");
+const phenologyImage = ref(require("@/assets/img/common/sd-1.jpg"));
+
 const phenologyOptions = computed(() => [
     { value: "not_reached", label: t("agriFile.notReached") },
     { value: "reached", label: t("agriFile.reached") },
     { value: "passed", label: t("agriFile.passed") },
 ]);
 
-watch(
-    () => props.show,
-    (visible) => {
-        if (visible) {
-            questionType.value = "time";
-            selectedDate.value = "";
-            phenologyAnswer.value = "reached";
-        }
+const resetForm = () => {
+    questionType.value = "time";
+    selectedDate.value = "";
+    phenologyAnswer.value = "reached";
+};
+
+/** 请求接口判断是否展示,并填充问题内容 */
+const checkShouldShow = async () => {
+    try {
+        // TODO: 对接恢复种植服务弹窗展示接口
+        // const res = await xxxApi();
+        // visible.value = !!res?.needShow;
+        // if (res?.needShow) applyQuestionData(res);
+        visible.value = false;
+    } catch (e) {
+        visible.value = false;
     }
-);
+};
+
+onMounted(() => {
+    checkShouldShow();
+});
 
 const handleChangeQuestion = () => {
     questionType.value = questionType.value === "time" ? "phenology" : "time";
-    emit("changeQuestion", questionType.value);
 };
 
 const handleViewGuide = () => {
-    emit("viewGuide");
+    // TODO: 跳转巡园向导
 };
 
-const handleConfirm = () => {
-    emit("confirm", {
-        type: questionType.value,
-        date: selectedDate.value,
-        phenologyAnswer: phenologyAnswer.value,
-    });
-    showValue.value = false;
+const handleConfirm = async () => {
+    // TODO: 对接提交接口
+    visible.value = false;
+    resetForm();
 };
 </script>
 

+ 5 - 1
src/i18n/messages.js

@@ -148,7 +148,7 @@ export default {
             patrolGrowthTitle: "长势跟踪巡园要点",
             patrolAbnormalTitle: "异常态势巡园要点",
             patrolSubject: "某某主题",
-            patrolIssue: "互动问题互动问题互动问题互动问题互",
+            patrolIssue: "连阴雨泡得芽点发软,不清沟排水,芽都烂在土里今年连叶都没得看",
             forward: "转发",
             workName: "农事名称",
             interactTheme: "互动主题",
@@ -179,6 +179,8 @@ export default {
             reportGenerated: "已生成",
             clickToView: "点击查看",
             viewInDiagnosisHint: "还可在 农事档案-诊断报告 中查看",
+            initialReport: "初次报告",
+            forwardReport: "转发报告",
             confirmLeaveTitle: "确认要离开?",
             confirmLeaveDesc1: "长势巡园比例还没回答,确定要走吗?",
             confirmLeaveDesc2: "少了这一步,估产数据就不准了哦~",
@@ -622,6 +624,8 @@ export default {
             reportGenerated: "Generated",
             clickToView: "View now",
             viewInDiagnosisHint: "Also available in Crop Archives - Diagnosis report",
+            initialReport: "Initial report",
+            forwardReport: "Forward report",
             confirmLeaveTitle: "Leave this page?",
             confirmLeaveDesc1: "Growth patrol ratio is not entered yet. Leave anyway?",
             confirmLeaveDesc2: "Without this step, yield estimates will be less accurate~",

+ 13 - 0
src/router/globalRoutes.js

@@ -179,6 +179,12 @@ export default [
         meta: { keepAlive: true },
         component: () => import("@/views/old_mini/agri_file/pages/growthTrack.vue"),
     },
+    // 定位校验通过桥接(小程序写 localStorage 后回退)
+    {
+        path: "/location_check_pass",
+        name: "LocationCheckPass",
+        component: () => import("@/views/old_mini/agri_file/pages/locationCheckPass.vue"),
+    },
     // 病虫害互动选地块
     {
         path: "/pest_interact",
@@ -186,4 +192,11 @@ export default [
         meta: { keepAlive: true },
         component: () => import("@/views/old_mini/agri_file/pages/pestInteract.vue"),
     },
+    // 初次种植诊断报告
+    {
+        path: "/diagnosis_report",
+        name: "DiagnosisReport",
+        meta: { keepAlive: true },
+        component: () => import("@/views/old_mini/agri_file/pages/diagnosisReport.vue"),
+    },
 ];

+ 62 - 19
src/views/old_mini/agri_file/index.vue

@@ -56,7 +56,7 @@
                                     <img :src="item.icon" alt="" />
                                     <span class="tracking-item__reason">{{ item.subject }}</span>
                                 </div>
-                                <!-- <div class="tracking-item__issue">{{ item.issue }}</div> -->
+                                <div class="tracking-item__issue">{{ item.issue }}</div>
                             </div>
                             <div class="tracking-item__btn" @click.stop="handleUploadClick(item)">
                                 <el-icon>
@@ -138,11 +138,33 @@
         </div>
         <album-upload-popup v-model:show="showUploadPopup" />
         <complete-farm-info-popup v-model:show="showCompleteFarmPopup" @confirm="goCompleteFarmInfo" />
-        <diagnosis-report-popup v-model:show="showDiagnosisReportPopup" @confirm="goDiagnosisReport" />
-        <restore-planting-popup
-            v-model:show="showRestorePlantingPopup"
-            @change-question="handleChangeRestoreQuestion"
-            @confirm="handleRestorePlantingConfirm"
+
+        <!-- 诊断报告弹窗 -->
+        <diagnosis-report-popup 
+            v-model:show="showDiagnosisReportPopup"
+            @confirm="goDiagnosisReport" />
+
+        <!-- 恢复种植弹窗 -->
+        <restore-planting-popup />
+
+        <!-- 代管弹窗:后续由接口控制是否展示 -->
+        <proxy-auth-popup
+            v-model:show="showProxyAuthPopup"
+            :service-name="proxyServiceName"
+            @reject="handleProxyReject"
+            @allow="handleProxyAllow"
+        />
+
+        <!-- 申请更换物候期弹窗:后续由接口控制是否展示 -->
+        <phenology-update-popup
+            v-model:show="showPhenologyUpdatePopup"
+            :service-name="phenologyUpdateInfo.serviceName"
+            :current-stage="phenologyUpdateInfo.currentStage"
+            :target-stage="phenologyUpdateInfo.targetStage"
+            :current-image="phenologyUpdateInfo.currentImage"
+            :farm-location="phenologyUpdateInfo.farmLocation"
+            @reject="handlePhenologyUpdateReject"
+            @confirm="handlePhenologyUpdateConfirm"
         />
     </div>
 </template>
@@ -161,6 +183,8 @@ import albumUploadPopup from "./components/albumUploadPopup.vue";
 import completeFarmInfoPopup from "@/components/popup/completeFarmInfoPopup.vue";
 import diagnosisReportPopup from "@/components/popup/diagnosisReportPopup.vue";
 import restorePlantingPopup from "@/components/popup/restorePlantingPopup.vue";
+import proxyAuthPopup from "@/components/popup/proxyAuthPopup.vue";
+import phenologyUpdatePopup from "@/components/popup/phenologyUpdatePopup.vue";
 import PhenologyTrackTimelineItem from "@/components/pageComponents/PhenologyTrackTimelineItem.vue";
 import wx from "weixin-js-sdk";
 
@@ -224,8 +248,35 @@ const reportList = ref([
 const showUploadPopup = ref(false);
 const showCompleteFarmPopup = ref(false);
 const showDiagnosisReportPopup = ref(false);
-// 后续由接口控制是否展示恢复种植服务弹窗
-const showRestorePlantingPopup = ref(false);
+// 后续由接口控制是否展示代管授权弹窗
+const showProxyAuthPopup = ref(false);
+const proxyServiceName = ref("农服名称");
+
+const handleProxyReject = () => {
+    // TODO: 调用拒绝代管接口
+};
+
+const handleProxyAllow = () => {
+    // TODO: 调用允许代管接口
+};
+
+// 后续由接口控制是否展示申请更换物候期弹窗
+const showPhenologyUpdatePopup = ref(false);
+const phenologyUpdateInfo = ref({
+    serviceName: "某某农服",
+    currentStage: "花穗期",
+    targetStage: "小果期",
+    varietyName: "荔枝-桂味",
+    farmLocation: "位置位置位置",
+});
+
+const handlePhenologyUpdateReject = () => {
+    // TODO: 调用暂不更新接口
+};
+
+const handlePhenologyUpdateConfirm = () => {
+    // TODO: 调用确认更新接口
+};
 const patrolList = ref([
     {
         id: 1,
@@ -330,7 +381,7 @@ const fillMockLabels = () => {
         level: t("agriFile.riskLevel2"),
         title: t(index === 0 ? "agriFile.patrolGrowthTitle" : "agriFile.patrolAbnormalTitle"),
         zone: albumName,
-        subject: t("agriFile.patrolSubject") + '主题主题主题主题主题主题主题主题主题',
+        subject: t("agriFile.patrolSubject"),
         issue: t("agriFile.patrolIssue"),
     }));
     cropArchiveList.value = cropArchiveList.value.map((item, index) => ({
@@ -447,15 +498,7 @@ const goCompleteFarmInfo = () => {
 };
 
 const goDiagnosisReport = () => {
-    activeNav.value = "report";
-};
-
-const handleChangeRestoreQuestion = () => {
-    // 后续对接接口更换问题
-};
-
-const handleRestorePlantingConfirm = () => {
-    // 后续对接接口提交时间
+    router.push("/diagnosis_report");
 };
 
 const goAlbumDetail = (item) => {
@@ -971,7 +1014,7 @@ watch(activeNav, (key) => {
                 &::before {
                     content: "";
                     position: absolute;
-                    top: -7px;
+                    top: -6px;
                     left: 21%;
                     width: 0;
                     height: 0;

+ 403 - 0
src/views/old_mini/agri_file/pages/diagnosisReport.vue

@@ -0,0 +1,403 @@
+<template>
+    <div class="diagnosis-report-page">
+        <custom-header :name="t('agriFile.initialReport')" :isGoBack="true" @goback="handleBack" />
+
+        <div class="diagnosis-report-body">
+            <div class="report-hero">
+                <div class="report-hero__title">{{ reportData.title }}</div>
+                <div class="report-hero__desc">{{ reportData.intro }}</div>
+            </div>
+
+            <!-- 基本情况与种植价值 -->
+            <div class="report-section">
+                <div class="report-section__title">{{ reportData.basic.title }}</div>
+                <div v-for="card in reportData.basic.cards" :key="card.title" class="info-card">
+                    <div class="info-card__title">{{ card.title }}</div>
+                    <div v-for="(para, idx) in card.paragraphs" :key="idx" class="info-card__text">
+                        {{ para }}
+                    </div>
+                </div>
+            </div>
+
+            <!-- 决定产量的气象风险 -->
+            <div class="report-section">
+                <div class="report-section__title">{{ reportData.weatherRisk.title }}</div>
+                <div class="info-card__text mb-10">{{ reportData.weatherRisk.intro }}</div>
+                <div class="section-divider">
+                    <span>{{ reportData.weatherRisk.groupTitle }}</span>
+                </div>
+                <div class="info-card fertilizer-card">
+                    <div v-for="item in reportData.weatherRisk.items" :key="item.tag" class="tag-block">
+                        <span class="tag-block__tag">{{ item.tag }}</span>
+                        <div class="tag-block__text">{{ item.content }}</div>
+                    </div>
+                    <div class="suggest-card">
+                        <div class="suggest-card__title">{{ reportData.weatherRisk.suggest.title }}</div>
+                        <div v-for="row in reportData.weatherRisk.suggest.rows" :key="row.label"
+                            class="suggest-card__row">
+                            <span class="suggest-card__label">{{ row.label }}</span>
+                            <span>{{ row.content }}</span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 肥料供给效率分析 -->
+            <div class="report-section">
+                <div class="report-section__title">{{ reportData.fertilizer.title }}</div>
+                <div class="info-card fertilizer-card">
+                    <div class="info-card__text">{{ reportData.fertilizer.intro }}</div>
+                    <div class="section-divider">
+                        <span>{{ reportData.fertilizer.groupTitle }}</span>
+                    </div>
+                    <div v-for="item in reportData.fertilizer.items" :key="item.tag" class="tag-block">
+                        <span class="tag-block__tag">{{ item.tag }}</span>
+                        <div class="tag-block__text">{{ item.content }}</div>
+                    </div>
+                    <div class="suggest-card">
+                        <div class="suggest-card__title">{{ reportData.fertilizer.suggest.title }}</div>
+                        <div v-for="row in reportData.fertilizer.suggest.rows" :key="row.label"
+                            class="suggest-card__row">
+                            <span class="suggest-card__label">{{ row.label }}</span>
+                            <span>{{ row.content }}</span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="share-btn" @click="handleShare">{{ t("agriFile.forwardReport") }}</div>
+    </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import customHeader from "@/components/customHeader.vue";
+import { useI18n } from "@/i18n";
+import wx from "weixin-js-sdk";
+
+const { t } = useI18n();
+const router = useRouter();
+const route = useRoute();
+
+/** 假数据:后续可替换为接口 */
+const reportData = ref({
+    title: "《广州市从化区妃子笑荔枝初始种植诊断报告》",
+    intro: "针对您的果园坐标、妃子笑荔枝和种植类型,飞鸟结合从化区长期种植条件,为您生成专属初始种植诊断报告",
+    basic: {
+        title: "基本情况与种植价值",
+        cards: [
+            {
+                title: "种植基础",
+                paragraphs: [
+                    "从化地处北回归线附近低纬度地带,属南亚热带季风气候,年均气温约20℃上下,雨量充足、光照充沛,森林覆盖率高的低山丘陵为荔枝提供了较理想的生长环境。",
+                ],
+            },
+            {
+                title: "品种价值",
+                paragraphs: [
+                    "妃子笑为当地率先登场的早熟品种,早结、丰产稳产性好,果果大核小、肉厚清甜,商品性较好,且抗逆性和成花着果能力在同产区中表现突出。",
+                    "产期价值:本地妃子笑多在5月下旬至6月中旬成熟采收,比当地桂味、糯米糍、槐枝等中晚熟品种提早约一个月上市,能在荔枝上市初期先供应市场、填补尝鲜空档。",
+                ],
+            },
+            {
+                title: "商品定位",
+                paragraphs: [
+                    "依托从化自然环境与当季鲜采优势,妃子笑宜以「本地早熟、果大肉厚清甜、新摘即卖」为卖点,面向本地及周边短途鲜食市场,强调产地直供与口感新鲜度。",
+                ],
+            }
+        ],
+    },
+    weatherRisk: {
+        title: "决定产量的气象风险",
+        intro: "飞鸟系统基于当地多年气象与物候窗口,梳理对产量形成影响最大的关键风险,帮助提前布局应对。",
+        groupTitle: "开花坐果期",
+        items: [
+            {
+                tag: "风险判断",
+                content:
+                    "开花坐果期阴雨涝渍年发生概率55.0%,历史强度等级4级,是制约坐果稳定的重要风险,需重点对待。",
+            },
+            {
+                tag: "主要影响",
+                content:
+                    "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现萎蔫与灼伤。",
+            },
+            {
+                tag: "重点准备",
+                content:
+                    "围绕排水防涝、保温防寒和抗旱灌水提前备好物资与预案,把灾害应对前移到敏感窗口来临之前。",
+            },
+        ],
+        suggest: {
+            title: "飞鸟建议",
+            rows: [
+                {
+                    label: "优先能力:",
+                    content:
+                        "重点提升防灾保果与应急稳产的成套管理能力,尤其是排水防涝、保温防寒和抗旱灌水的机动响应。",
+                },
+                {
+                    label: "管理方向:",
+                    content:
+                        "围绕开花坐果和果实膨大两大敏感窗口,把灾害应对前移到物候来临之前,守住产量形成的稳定期。",
+                },
+            ],
+        },
+    },
+    fertilizer: {
+        title: "肥料供给效率分析",
+        intro: "飞鸟系统结合历史土壤、地形水文和遥感长势数据,进一步分析发现,当地肥料利用效率的主要限制在于酸性黏重土壤的通气缓冲和湿热旱交替下的根系活力波动。",
+        groupTitle: "土壤长期供肥",
+        items: [
+            {
+                tag: "主要问题",
+                content:
+                    "当地为赤红壤黏壤土,pH约5.4偏酸,有机质水平中等,养分缓冲与保蓄能力总体偏弱。",
+            },
+            {
+                tag: "形成原因",
+                content:
+                    "暴雨涝渍年发生概率65.0%,长期强降雨易促使可移动养分淋失,进一步削弱酸性土壤的养分缓冲能力。",
+            },
+            {
+                tag: "生产影响",
+                content:
+                    "养分流失加快、供应节奏不稳,可能影响花芽分化、开花坐果和幼果稳定所需的营养基础。",
+            },
+        ],
+        suggest: {
+            title: "飞鸟建议",
+            rows: [
+                {
+                    label: "优先能力:",
+                    content:
+                        "重点提升排水防涝、土壤改良与分期稳肥的成套管理能力,尤其是酸性黏重土壤的通气缓冲与根系养护。",
+                },
+                {
+                    label: "管理方向:",
+                    content:
+                        "围绕开花坐果和果实膨大两大敏感窗口,把灾害应对前移到物候来临之前,守住产量形成的稳定期。",
+                },
+            ],
+        },
+    },
+});
+
+const handleBack = () => {
+    router.back();
+};
+
+const handleShare = () => {
+    const query = {
+        askInfo: { title: "转发报告", content: "是否分享给好友" },
+        shareText: reportData.value.title,
+        targetUrl: "diagnosis_report",
+        paramsPage: JSON.stringify({
+            id: route.query.id,
+            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`,
+    });
+};
+</script>
+
+<style lang="scss" scoped>
+.diagnosis-report-page {
+    min-height: 100vh;
+    background: #f5f6f8;
+    box-sizing: border-box;
+    padding-bottom: 90px;
+}
+
+.diagnosis-report-body {
+    padding: 10px 10px 60px;
+    max-height: calc(100vh - 40px);
+    overflow: auto;
+    box-sizing: border-box;
+}
+
+.report-hero {
+    padding: 0px 0px 10px;
+    text-align: center;
+
+    &__title {
+        font-size: 16px;
+        font-weight: bold;
+        line-height: 24px;
+        color: #000000;
+    }
+
+    &__desc {
+        margin-top: 10px;
+        font-size: 16px;
+        font-weight: 350;
+        line-height: 24px;
+        color: rgba(6, 6, 6, 0.5);
+        text-align: left;
+    }
+}
+
+.report-section {
+    background: #fff;
+    padding: 10px;
+    border-radius: 10px;
+
+    &+& {
+        margin-top: 18px;
+    }
+
+    &__title {
+        margin-bottom: 10px;
+        font-size: 16px;
+        font-weight: 600;
+        line-height: 24px;
+        color: #000000;
+    }
+}
+
+.mb-10 {
+        margin-bottom: 10px;
+    }
+
+.info-card {
+    padding: 10px;
+    border-radius: 4px;
+    background: #F7F8FA;
+    box-sizing: border-box;
+
+    &.fertilizer-card {
+        background: transparent;
+        padding: 0;
+
+        .info-card__text {
+            margin-bottom: 10px;
+        }
+    }
+
+    &+& {
+        margin-top: 10px;
+    }
+
+    &__title {
+        margin-bottom: 4px;
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 22px;
+        color: #1D2129;
+    }
+
+    &__text {
+        // margin-bottom: 10px;
+        font-size: 12px;
+        line-height: 20px;
+        color: #4E5969;
+
+        &+& {
+            margin-top: 2px;
+        }
+    }
+}
+
+.section-divider {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 10px;
+    color: #1D2129;
+    font-size: 14px;
+    font-weight: 500;
+    line-height: 20px;
+
+    &::before,
+    &::after {
+        content: "";
+        flex: 1;
+        height: 1px;
+        background: linear-gradient(90deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0) 100%);
+    }
+
+    &::before {
+        background: linear-gradient(270deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0) 100%);
+    }
+}
+
+.tag-block {
+    padding: 12px;
+    border-radius: 8px;
+    background: #f7f8fa;
+
+    &+& {
+        margin-top: 10px;
+    }
+
+    &__tag {
+        display: inline-block;
+        padding: 0 10px;
+        border-radius: 2px;
+        background: #2199f8;
+        color: #fff;
+        font-size: 12px;
+        line-height: 22px;
+    }
+
+    &__text {
+        margin-top: 4px;
+        font-size: 12px;
+        line-height: 20px;
+        color: #4E5969;
+    }
+}
+
+.suggest-card {
+    margin-top: 10px;
+    padding: 12px;
+    border-radius: 8px;
+    background: rgba(33, 153, 248, 0.08);
+
+    &__title {
+        margin-bottom: 8px;
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 20px;
+        color: #2199f8;
+    }
+
+    &__row {
+        font-size: 12px;
+        line-height: 20px;
+        color: #4E5969;
+
+        &+& {
+            margin-top: 8px;
+        }
+    }
+
+    &__label {
+        color: #000000;
+    }
+}
+
+.share-btn {
+    position: fixed;
+    left: 50%;
+    bottom: 64px;
+    z-index: 10;
+    transform: translateX(-50%);
+    min-width: 120px;
+    height: 40px;
+    box-sizing: border-box;
+    padding: 0 30px;
+    border-radius: 22px;
+    background: linear-gradient(180deg, #72C1FF 0%, #2199F8 100%);
+    color: #fff;
+    font-size: 14px;
+    line-height: 40px;
+    text-align: center;
+    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.1);
+}
+</style>

+ 24 - 26
src/views/old_mini/agri_file/pages/growthTrack.vue

@@ -12,10 +12,7 @@
                 <div class="growth-hero__top">
                     <div class="growth-hero__main">
                         <div class="growth-hero__title-row">
-                            <span class="growth-hero__title">速长窗口不追肥?后期发育慢人一步,直接掉队!</span>
-                        </div>
-                        <div class="growth-hero__tags">
-
+                            <span class="growth-hero__title">{{ theme }}</span>
                             <span class="growth-hero__tag growth-hero__tag--level">
                                 <el-icon>
                                     <BellFilled />
@@ -24,6 +21,7 @@
                             </span>
                             <span class="growth-hero__tag growth-hero__tag--zone">{{ zoneName }}</span>
                         </div>
+                        <div class="growth-hero__reason">{{ reason }}</div>
                     </div> 
                     <div class="growth-hero__share" @click="handleShare">
                         <Icon name="share" />
@@ -188,27 +186,20 @@ const handleSubmit = () => {
 };
 
 const needDraw = ref(true);
+
+/** 先跳小程序校验定位;通过后由小程序直接打开勾画/病虫害/上传目标页 */
 const handleUploadClick = () => {
-    if (needDraw.value) {
-        router.push({
-            path: "/add_zone",
-            query: {
-                type: "farm",
-                targetUrl: isAbnormal.value ? "/pest_interact" : "/growth_track",
-                ...(isAbnormal.value ? { isAbnormal: "true" } : {}),
-                ...(route.query.id ? { trackId: route.query.id } : {}),
-                ...(route.query.type ? { trackType: route.query.type } : {}),
-            },
-        });
-        return;
-    }
-    if (isAbnormal.value) {
-        router.push({
-            path: "/pest_interact",
-        });
-        return;
-    }
-    showUploadPopup.value = true;
+    const query = [
+        `needDraw=${needDraw.value ? 1 : 0}`,
+        `isAbnormal=${isAbnormal.value ? 1 : 0}`,
+        route.query.id ? `id=${encodeURIComponent(String(route.query.id))}` : "",
+        route.query.type ? `type=${encodeURIComponent(String(route.query.type))}` : "",
+    ]
+        .filter(Boolean)
+        .join("&");
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/farm_location_check/index?${query}`,
+    });
 };
 
 const handleUploadConfirm = () => {
@@ -280,11 +271,18 @@ onActivated(() => {
             }
 
             &__title {
-                font-size: 22px;
+                font-size: 28px;
                 font-family: "PangMenZhengDao";
                 margin-top: -5px;
             }
 
+            &__reason {
+                margin-top: 10px;
+                padding: 4px 10px;
+                border-radius: 4px;
+                background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
+            }
+
             &__tag {
                 display: inline-flex;
                 align-items: center;
@@ -312,7 +310,7 @@ onActivated(() => {
             }
 
             &__share {
-                flex: none;
+                // flex: none;
                 display: flex;
                 align-items: center;
                 gap: 4px;

+ 23 - 0
src/views/old_mini/agri_file/pages/locationCheckPass.vue

@@ -0,0 +1,23 @@
+<template>
+    <div class="location-check-pass"></div>
+</template>
+
+<script setup>
+import { onMounted } from "vue";
+import wx from "weixin-js-sdk";
+
+const STORAGE_KEY = "FARM_LOCATION_CHECK_PASS";
+
+onMounted(() => {
+    localStorage.setItem(STORAGE_KEY, "1");
+    wx.miniProgram.navigateBack({ delta: 1 });
+});
+</script>
+
+<style scoped>
+.location-check-pass {
+    width: 100%;
+    height: 100vh;
+    background: #fff;
+}
+</style>

+ 3 - 1
src/views/old_mini/entry_information/components/manualFarmingService.vue

@@ -114,7 +114,9 @@ const props = defineProps({
     hasFruit: { type: Boolean, default: false },
 });
 
-const cropGroupLabel = ref("大田类");
+const cropGroupLabel = computed(() => {
+    return isFruitView.value ? "果树类" : "大田类";
+})
 
 const FIELD_TASKS = [
     { id: 1, name: "人工补苗" },

+ 1 - 1
src/views/old_mini/entry_information/components/selectCategory.vue

@@ -24,7 +24,7 @@
                     v-model="searchKeyword"
                     class="search-input"
                     type="text"
-                    placeholder="请输入品种名称"
+                    placeholder="请输入品类名称"
                     @keyup.enter="handleSearch"
                 />
                 <div class="search-btn" @click="handleSearch">搜索</div>

+ 34 - 0
src/views/old_mini/entry_information/components/selectEquipment.vue

@@ -297,6 +297,39 @@ function restoreSelection() {
     });
 }
 
+/**
+ * 果树步骤:大田已选且当前果树列表中存在的设备(同 machine_id 或同名)默认勾选
+ * 仅做加法,不取消果树侧已有选择
+ */
+function applyFieldOverlapSelection() {
+    if (getCurrentScope() !== "fruit") return;
+
+    const fieldList = getScopeEquipmentList(readJson(SESSION.EQUIPMENT), "field");
+    if (!fieldList.length) return;
+
+    const fieldIds = new Set(
+        fieldList.filter((item) => item?.id != null).map((item) => String(item.id))
+    );
+    const fieldNames = new Set(
+        fieldList.map((item) => item?.name).filter(Boolean)
+    );
+
+    let changed = false;
+    allEquipmentItems.value.forEach((item) => {
+        const id = String(item.id);
+        if (selectedIds.has(id)) return;
+        const matched =
+            fieldIds.has(id) ||
+            (item.name && fieldNames.has(item.name)) ||
+            (item.machineName && fieldNames.has(item.machineName));
+        if (!matched) return;
+        selectedIds.add(id);
+        changed = true;
+    });
+
+    if (changed) saveSelectionDraft();
+}
+
 /** 将当前选中项写入 session;农服按 field/fruit 分桶存储 */
 function buildSelectedEquipmentPayload() {
     const scope = getCurrentScope();
@@ -544,6 +577,7 @@ function handleSubmit() {
 onMounted(async () => {
     await fetchEquipmentList();
     restoreSelection();
+    applyFieldOverlapSelection();
 });
 </script>
 

+ 127 - 39
src/views/old_mini/work_detail/index.vue

@@ -10,10 +10,14 @@
         />
         <div class="work-detail-content">
             <!-- 顶部状态 -->
-            <div class="content-status">
+            <div class="content-status" :class="{'done-status': statusVal === 'completed'}">
                 <div class="status-l">
                     <div class="status-title">
-                        <span>{{ farmData.work_name }}</span>
+                        <span>08.18 {{ farmData.work_name }}</span>
+                        <!-- <span class="status-sub small-tag mt-4" :style="statusTagStyle[farmData?.farm_work_type]">标准类</span> -->
+                        <!-- <span class="status-sub small-tag mt-4 isAbnormal" :style="statusTagStyle[farmData?.farm_work_type]">异常类</span> -->
+                        <span class="status-sub small-tag mt-4 isWeather" :style="statusTagStyle[farmData?.farm_work_type]">气象类</span>
+                        <span class="status-sub small-tag mt-4 isHighRisk" :style="statusTagStyle[farmData?.farm_work_type]">高风险</span>
                     </div>
                     <div class="status-hint-card" v-if="farmData?.work_status === 0">
                         <div class="status-hint-text">{{ farmData?.best_time }}</div>
@@ -25,12 +29,21 @@
                         </div>
                     </div>
                     <div class="status-sub-wrap" :style="getFarmWorkTypeColorStyle(farmData)" v-else>
-                        <div class="status-sub" :style="statusTagStyle[farmData?.farm_work_type]">{{ farmWorkTypeObj[farmData?.farm_work_type] }}</div>
-                        <div class="status-sub">
-                            {{ farmData.best_time }}
+                        <div v-if="statusVal !== 'completed'" class="status-sub" :style="statusTagStyle[farmData?.farm_work_type]">{{ farmWorkTypeObj[farmData?.farm_work_type] }}</div>
+                        <div v-if="statusVal === 'completed'" class="status-sub done-status__text">
+                            <el-icon size="14" class="done-status__icon"><SuccessFilled /></el-icon>
+                            已完成
                         </div>
+                        <!-- <div class="status-sub">
+                            {{ farmData.best_time }}
+                        </div> -->
                     </div>
                 </div>
+
+                <div class="status-r">
+                    <img class="status-r__icon" src="@/assets/img/home/user-icon-fill.png" alt="">
+                    外包农事
+                </div>
             </div>
 
             <div class="work-wrap" :class="{ 'from-share': isFromShare }">
@@ -130,12 +143,20 @@
             </div>
         </div>
 
-        <div class="btn-fixed" :class="{ 'from-share': isFromShare }">
-            <div class="primary-btn" @click="goGrowthTrack">巡园要点</div>
+        <div v-if="statusVal !== 'completed'" class="warning-info" :class="{ 'from-share': isFromShare }">
+            <div class="warning-l">
+                <div class="warning-title">
+                    <el-icon size="16" color="#FF953D"><WarningFilled /></el-icon>
+                    发现异常后机动执行
+                </div>
+                <div class="warning-text">为您匹配最佳农事方案</div>
+            </div>
+            <div class="warning-r" @click="goGrowthTrack">巡园要点</div>
         </div>
 
-        <div class="footer-btn" v-if="!isFromShare">
+        <div class="footer-btn" :class="{'done-btn': statusVal === 'completed'}" v-if="!isFromShare">
             <div class="secondary-btn" @click="handleShare">转发</div>
+            <div class="primary-btn" v-if="statusVal !== 'completed'" @click="handleComplete">我已完成</div>
         </div>
         <ExecutePopup ref="executePopupRef" @executeSuccess="getDetail" />
         <upload-tips v-model:show="showUploadTipsPopup" />
@@ -144,6 +165,7 @@
 
 <script setup>
 import { ElMessage } from "element-plus";
+import { WarningFilled } from "@element-plus/icons-vue";
 import wx from "weixin-js-sdk";
 import customHeader from "@/components/customHeader.vue";
 import { ref, computed, watch, onMounted, onActivated, onDeactivated, onBeforeUnmount, nextTick } from "vue";
@@ -163,7 +185,7 @@ const { locale } = useI18n();
 const showUploadTipsPopup = ref(false);
 const router = useRouter();
 // const info = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
-const info = { appType: 1 };
+const statusVal = computed(() => pageParams.value?.statusVal ?? null);
 
 function isTruthyFlag(value) {
     return value === 1 || value === "1" || value === true || value === "true";
@@ -368,6 +390,10 @@ const handleExecute = (status, type) => {
     executePopupRef.value.openPopup(popupType);
 };
 
+const handleComplete = () => {
+    console.log('handleComplete');
+};
+
 watch(locale, () => {
     getDetail();
 });
@@ -569,9 +595,12 @@ const areaMap = new AreaMap();
         box-shadow: 2px 2px 4.5px 0px rgba(0, 0, 0, 0.4);
         display: flex;
         align-items: flex-start;
-        padding-top: 10px;
-        justify-content: center;
+        padding: 10px 12px 0 12px;
+        justify-content: space-between;
         box-sizing: border-box;
+        &.done-btn {
+            justify-content: center;
+        }
         .secondary-btn {
             padding: 0 38px;
             height: 40px;
@@ -581,27 +610,13 @@ const areaMap = new AreaMap();
             background: #F6F6F6;
             text-align: center;
         }
-    }
-
-    .btn-fixed {
-        position: fixed;
-        z-index: 2;
-        bottom: 80px;
-        left: 50%;
-        transform: translateX(-50%);
-        width: fit-content;
-        padding: 0 26px;
-        height: 38px;
-        line-height: 38px;
-        border-radius: 20px;
-        background: #2199F8;
-        color: #fff;
-        box-shadow: 2px 2px 4.5px 0px rgba(0, 0, 0, 0.4);
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        &.from-share {
-            bottom: 50px;
+        .primary-btn {
+            padding: 0 24px;
+            height: 40px;
+            border-radius: 20px;
+            line-height: 40px;
+            background: #2199F8;
+            color: #fff;
         }
     }
 }
@@ -614,8 +629,9 @@ const areaMap = new AreaMap();
     height: 92px;
     box-sizing: border-box;
     display: flex;
-    flex-direction: column;
-    justify-content: center;
+    align-items: center;
+    // flex-direction: column;
+    // justify-content: center;
 
     &::after {
         content: "";
@@ -628,7 +644,18 @@ const areaMap = new AreaMap();
         width: 100%;
     }
 
+    &.done-status {
+        &::after {
+            background: #4ABF32;
+        }
+    }
+
+    .mt-4 {
+        margin-top: 4px;
+    }
+
     .status-l {
+        flex: 1;
         .status-title {
             font-size: 21px;
             display: flex;
@@ -653,12 +680,38 @@ const areaMap = new AreaMap();
         }
 
         .status-sub {
-            font-size: 13px;
+            font-size: 14px;
             padding: 2px 8px;
             background: #fff;
             color: #2199F8;
             width: fit-content;
             border-radius: 2px;
+            &.small-tag {
+                padding: 0 5px;
+                height: 22px;
+                line-height: 22px;
+                font-size: 12px;
+                &.isAbnormal {
+                    color: #FF6A6A;
+                }
+                &.isWeather {
+                    color: #FDA202;
+                }
+                &.isHighRisk {
+                    color: #FFFFFF;
+                    background: #FF6A6A;
+                }
+            }
+            .done-status__icon {
+                color: #959595;
+            }
+            
+            &.done-status__text {
+                display: flex;
+                align-items: center;
+                gap: 4px;
+                color: #959595;
+            }
         }
 
         .status-hint-card {
@@ -700,6 +753,27 @@ const areaMap = new AreaMap();
             }
         }
     }
+
+    .status-r {
+        flex: none;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        gap: 2px;
+        padding: 6px;
+        border-radius: 5px;
+        background: rgba(255, 255, 255, 0.2);
+        border: 1px solid rgba(255, 255, 255, 0.6);
+        backdrop-filter: blur(4px);
+        color: #fff;
+        font-size: 12px;
+        .status-r__icon {
+            width: 14px;
+            height: 14px;
+            filter: brightness(0) invert(1);
+        }
+    }
 }
 
 .work-wrap {
@@ -1217,7 +1291,8 @@ const areaMap = new AreaMap();
 
 .warning-info {
     position: fixed;
-    bottom: 100px;
+    z-index: 2;
+    bottom: 80px;
     left: 10px;
     width: calc(100% - 20px);
     background: linear-gradient(180deg, #FFDFC5 -13%, #FFFFFF 39%);
@@ -1227,33 +1302,46 @@ const areaMap = new AreaMap();
     display: flex;
     align-items: center;
     justify-content: space-between;
-    box-shadow: 0px 4px 4px 0px #0000001A;
+    gap: 12px;
+    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.1);
+
+    &.from-share {
+        bottom: 50px;
+    }
 
     .warning-l {
+        min-width: 0;
+        flex: 1;
+
         .warning-title {
             display: inline-flex;
             align-items: center;
             gap: 6px;
             font-size: 16px;
+            font-weight: 500;
+            line-height: 20px;
             color: #FF953D;
         }
 
         .warning-text {
+            margin-top: 2px;
             color: rgba(0, 0, 0, 0.4);
             font-size: 12px;
-            // margin-top: 4px;
+            line-height: 16px;
         }
     }
 
     .warning-r {
+        flex-shrink: 0;
         background: #FF953D;
         color: #fff;
         font-size: 12px;
         cursor: pointer;
-        padding: 0 7px;
+        padding: 0 16px;
         height: 32px;
         line-height: 32px;
         border-radius: 20px;
+        white-space: nowrap;
     }
 }
 

+ 222 - 35
src/views/old_mini/work_execute/index.vue

@@ -8,20 +8,35 @@
             <customCalendar ref="calendarRef" @dateSelect="handleDateSelect"></customCalendar>
         </div>
         <div class="task-list">
+            <div class="task-tabs">
+                <div class="tab-item" :class="{ active: activeTab === 'pending' }" @click="handleTabChange('pending')">
+                    待执行({{ pendingCount }})
+                </div>
+                <div class="tab-item" :class="{ active: activeTab === 'completed' }" @click="handleTabChange('completed')">
+                    已完成({{ completedCount }})
+                </div>
+            </div>
             <div class="work-task-list">
                 <div
-                    v-for="item in taskList"
+                    v-for="item in displayTaskList"
                     :key="item.id"
                     class="task-card"
                     @click.stop="handleViewDetail(item)"
                     :class="{ 'is-risk': item.isHighRisk }"
                 >
-                    <div v-if="item.cornerTip" class="task-card__corner">{{ item.cornerTip }}</div>
+                    <div v-if="item.status === 'pending' && item.cornerTip" class="task-card__corner">{{ item.cornerTip }}</div>
+                    <div v-if="item.status === 'completed' && item.cornerTip" class="task-card__corner done-corner" :class="{ 'myself-corner': item.isByMyself }">
+                        <el-icon size="14" class="myself-corner__icon" :class="{ 'myself-corner__icon-success': item.isByMyself }"><SuccessFilled /></el-icon>
+                        {{ item.cornerTip }}
+                    </div>
 
                     <div class="task-card__header">
-                        <img class="task-card__icon" src="@/assets/img/home/task.png" alt="" />
-                        <span class="task-card__title van-ellipsis">{{ item.title }}</span>
-                        <span v-if="item.isHighRisk" class="risk-badge">{{ t("workExecute.highRisk") }}</span>
+                        <!-- <img class="task-card__icon" src="@/assets/img/home/task.png" alt="" /> -->
+                        <span class="task-card__title van-ellipsis">{{ item.executeDate + ' ' + item.title }}</span>
+                        <span v-if="item.isNormal" class="badge-tag normal-badge">标准类</span>
+                        <span v-if="item.isAbnormal" class="badge-tag abnormal-badge">异常类</span>
+                        <span v-if="item.isWeather" class="badge-tag weather-badge">气象类</span>
+                        <span v-if="item.isHighRisk" class="badge-tag risk-badge">{{ t("workExecute.highRisk") }}</span>
                     </div>
 
                     <div class="task-card__body">
@@ -34,16 +49,22 @@
                     </div>
 
                     <div class="task-card__footer">
-                        <div class="card-btn card-btn--ghost" @click.stop="handleForward(item)">
-                            {{ t("workExecute.forward") }}
+                        <div class="footer-l">
+                            <img class="footer-l__icon" src="@/assets/img/home/user-icon-fill.png" alt="">
+                            外包农事
                         </div>
-                        <div class="card-btn card-btn--primary" @click.stop="handleViewDetail(item)">
-                            {{ t("workExecute.viewDetail") }}
+                        <div class="footer-r">
+                            <div class="card-btn card-btn--ghost" @click.stop="handleForward(item)">
+                                {{ t("workExecute.forward") }}
+                            </div>
+                            <div class="card-btn card-btn--primary" @click.stop="handleViewDetail(item)">
+                                我已完成
+                            </div>
                         </div>
                     </div>
                 </div>
 
-                <div v-if="!taskList.length" class="empty-tip">{{ t("workExecute.noData") }}</div>
+                <div v-if="!displayTaskList.length" class="empty-tip">{{ t("workExecute.noData") }}</div>
             </div>
         </div>
         <farm-calendar-popup v-model:show="showFarmCalendarPopup" @confirm="goCompleteFarmInfo" />
@@ -72,7 +93,7 @@ const mapContainer = ref(null);
 const calendarRef = ref(null);
 const showFarmCalendarPopup = ref(false);
 /** 调查问卷弹窗:后续由接口决定是否展示 */
-const showSurveyAskPopup = ref(true);
+const showSurveyAskPopup = ref(false);
 const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 
 const formatDateYMD = (date = new Date()) =>
@@ -80,13 +101,17 @@ const formatDateYMD = (date = new Date()) =>
 
 const filterDate = ref(formatDateYMD());
 
-/** 假数据:后续可替换为接口 */
+/** 假数据:后续可替换为接口;status: pending 待执行 / completed 已完成 */
 const taskList = ref([
     {
         id: 1,
+        status: "pending",
         title: "喷施杀菌剂",
-        isHighRisk: true,
-        cornerTip: "发现异常后机动执行",
+        isHighRisk: false,
+        isNormal: true,
+        isAbnormal: false,
+        isWeather: false,
+        cornerTip: "待执行",
         analysisSummary: "近期高温高湿,蒂蛀虫风险上升",
         areaCode: "A-01 / 分区一",
         farmName: "喷施杀菌剂",
@@ -95,9 +120,13 @@ const taskList = ref([
     },
     {
         id: 2,
+        status: "pending",
         title: "叶面追肥",
-        isHighRisk: false,
-        cornerTip: "",
+        isHighRisk: true,
+        isNormal: false,
+        isAbnormal: true,
+        isWeather: false,
+        cornerTip: "发现异常后机动执行",
         analysisSummary: "转色期养分需求增加,需补充钾肥",
         areaCode: "B-03 / 分区二",
         farmName: "叶面追肥",
@@ -106,9 +135,13 @@ const taskList = ref([
     },
     {
         id: 3,
+        status: "pending",
         title: "灌溉补水",
         isHighRisk: true,
-        cornerTip: "发现异常后机动执行",
+        isNormal: false,
+        isAbnormal: false,
+        isWeather: true,
+        cornerTip: "建议执行",
         analysisSummary: "土壤墒情偏低,局部出现轻度干旱胁迫",
         areaCode: "C-02 / 分区三",
         farmName: "灌溉补水",
@@ -117,17 +150,82 @@ const taskList = ref([
     },
     {
         id: 4,
+        status: "completed",
         title: "病虫害巡查",
         isHighRisk: false,
-        cornerTip: "",
-        analysisSummary: "重点关注新梢与果实表面异常斑点",
+        isNormal: true,
+        isAbnormal: false,
+        isWeather: false,
+        isByMyself: true,
+        cornerTip: "本人完成",
+        analysisSummary: "新梢与果实表面未见明显异常斑点",
         areaCode: "D-05 / 全园",
-        executeDate: "08/06",
         farmName: "病虫害巡查",
-        wkt: "",
+        executeDate: "08/05",
+        wkt: "POINT(113.614209 23.585836)",
+    },
+    {
+        id: 5,
+        status: "completed",
+        title: "修剪整枝",
+        isHighRisk: false,
+        isNormal: true,
+        isAbnormal: false,
+        isWeather: false,
+        cornerTip: "已完成",
+        analysisSummary: "已清理过密枝条,改善通风透光",
+        areaCode: "A-02 / 分区一",
+        farmName: "修剪整枝",
+        executeDate: "08/04",
+        wkt: "POINT(113.611809 23.586236)",
+    },
+    {
+        id: 6,
+        status: "completed",
+        title: "除草松土",
+        isHighRisk: false,
+        isNormal: false,
+        isAbnormal: true,
+        isWeather: false,
+        cornerTip: "已完成",
+        analysisSummary: "行间杂草已清除,土壤表层已疏松",
+        areaCode: "B-01 / 分区二",
+        farmName: "除草松土",
+        executeDate: "08/03",
+        wkt: "POINT(113.616209 23.584936)",
+    },
+    {
+        id: 7,
+        status: "completed",
+        title: "防风加固",
+        isHighRisk: false,
+        isNormal: false,
+        isAbnormal: false,
+        isWeather: true,
+        cornerTip: "已完成",
+        analysisSummary: "台风过境前已完成支架加固与果袋检查",
+        areaCode: "C-01 / 分区三",
+        farmName: "防风加固",
+        executeDate: "08/02",
+        wkt: "POINT(113.613109 23.587436)",
     },
 ]);
 
+const activeTab = ref("pending");
+const pendingCount = computed(() => taskList.value.filter((item) => item.status === "pending").length);
+const completedCount = computed(() => taskList.value.filter((item) => item.status === "completed").length);
+const displayTaskList = computed(() => taskList.value.filter((item) => item.status === activeTab.value));
+
+const handleTabChange = (key) => {
+    if (activeTab.value === key) return;
+    activeTab.value = key;
+    nextTick(() => {
+        if (indexMap.kmap) {
+            indexMap.initData(getMapMarkerList(), "farmName", "wkt");
+        }
+    });
+};
+
 const handleDateSelect = (date) => {
     filterDate.value = date ?? formatDateYMD();
 };
@@ -154,7 +252,7 @@ const handleViewDetail = (item) => {
         path: "/work_detail",
         query: {
             id: item.id,
-            statusStr: item.isHighRisk ? "执行异常" : "待接受",
+            statusVal: item.status,
         },
     });
 };
@@ -170,7 +268,7 @@ const handleSurveyConfirm = () => {
 
 const mapPoint = ref(null);
 
-const getMapMarkerList = () => taskList.value.filter((item) => item.wkt);
+const getMapMarkerList = () => displayTaskList.value.filter((item) => item.wkt);
 
 onMounted(() => {
     mapPoint.value = store.state.home.miniUserLocationPoint || "POINT(113.614209 23.585836)";
@@ -235,6 +333,31 @@ onActivated(() => {
         padding: 0 12px 20px;
     }
 
+    .task-tabs {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: 50px;
+        margin: 2px 0 26px 0;
+        .tab-item {
+            color: #1D2129;
+            position: relative;
+            &.active {
+                color: #2199F8;
+                font-weight: 500;
+                &::after {
+                    content: '';
+                    position: absolute;
+                    bottom: -10px;
+                    left: 0;
+                    width: 100%;
+                    height: 2px;
+                    background: #2199F8;
+                }
+            }
+        }
+    }
+
     .work-task-list {
         display: flex;
         flex-direction: column;
@@ -254,16 +377,41 @@ onActivated(() => {
             position: absolute;
             top: 0;
             right: 0;
-            max-width: 58%;
-            padding: 4px 10px;
+            max-width: 78px;
+            padding: 2px 8px;
             border-radius: 0 8px 0 8px;
-            background: rgba(255, 106, 106, 0.1);
-            color: #FF6A6A;
+            background: rgba(33, 153, 248, 0.1);
+            color: #2199F8;
+            // background: rgba(255, 106, 106, 0.1);
+            // color: #FF6A6A;
             font-size: 12px;
             line-height: 16px;
-            white-space: nowrap;
-            overflow: hidden;
-            text-overflow: ellipsis;
+            white-space: wrap;
+            box-sizing: border-box;
+            text-align: center;
+            // overflow: hidden;
+            // text-overflow: ellipsis;
+
+            &.done-corner {
+                max-width: 86px;
+                height: 24px;
+                background: rgba(118, 126, 116, 0.1);
+                display: flex;
+                align-items: center;
+                gap: 4px;
+                color: #959595;
+                &.myself-corner {
+                    background: rgba(74, 191, 50, 0.1);
+                    color: #4ABF32;
+                }
+                .myself-corner__icon {
+                    color: #959595;
+                    flex-shrink: 0;
+                }
+                .myself-corner__icon-success {
+                    color: #4ABF32;
+                }
+            }
         }
 
         &__header {
@@ -287,15 +435,33 @@ onActivated(() => {
             color: #000;
         }
 
-        .risk-badge {
+        .badge-tag {
             flex-shrink: 0;
             height: 18px;
             padding: 0 6px;
             border-radius: 2px;
-            background: rgba(255, 106, 106, 0.12);
-            color: #ff6a6a;
             font-size: 12px;
         }
+        .badge-tag + .badge-tag {
+            margin-left: 4px;
+        }
+
+        .risk-badge {
+            background: #FF6A6A;
+            color: #FFFFFF;
+        }
+        .normal-badge {
+            background: rgba(33, 153, 248, 0.1);
+            color: #2199F8;
+        }
+        .abnormal-badge {
+            background: rgba(255, 106, 106, 0.1);
+            color: #FF6A6A;
+        }
+        .weather-badge {
+            background: rgba(255, 149, 61, 0.1);
+            color: #FDA202;
+        }
 
         &__body {
             margin-top: 10px;
@@ -319,6 +485,27 @@ onActivated(() => {
             justify-content: space-between;
             gap: 12px;
             margin-top: 14px;
+            .footer-l {
+                background: rgba(33, 153, 248, 0.1);
+                height: 20px;
+                display: flex;
+                align-items: center;
+                gap: 6px;
+                padding: 0 6px;
+                border-radius: 2px;
+                font-size: 12px;
+                color: #2199F8;
+                .footer-l__icon {
+                    width: 14px;
+                    height: 14px;
+                    flex-shrink: 0;
+                }
+            }
+            .footer-r {
+                display: flex;
+                align-items: center;
+                gap: 10px;
+            }
         }
 
         .card-btn {
@@ -335,8 +522,8 @@ onActivated(() => {
             }
 
             &--primary {
-                background: rgba(33, 153, 248, 0.1);
-                color: #2199f8;
+                background: #2199F8;
+                color: #FFFFFF;
                 padding: 0 16px;
             }
         }