Explorar el Código

feat:对接农事列表接口我已完成功能

wangsisi hace 2 semanas
padre
commit
c30995e583

+ 5 - 0
src/api/modules/questionnaire.js

@@ -36,6 +36,11 @@ module.exports = {
         url: config.base_new_url + "report/user_work",
         type: "get",
     },
+    // 修改农事状态(完成)
+    updateWorkStatus: {
+        url: config.base_new_url + "report/update_work_status",
+        type: "post",
+    },
     // 物候期问题查询
     phenologyProblem: {
         url: config.base_new_url + "report/crop_question",

+ 160 - 78
src/views/old_mini/work_detail/index.vue

@@ -13,21 +13,33 @@
             <div class="content-status" :class="{'done-status': statusVal === 'completed'}">
                 <div class="status-l">
                     <div class="status-title">
-                        <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>
-                        <div class="status-hint-question">
-                            <el-icon class="status-hint-link-icon">
-                                <Link />
-                            </el-icon>
-                            <span class="status-hint-question-text">{{ farmData.interaction_issue }}</span>
+                        <span class="status-title-text">08.18 {{ farmData.fw_name }}</span>
+                        <div class="status-title-tags">
+                            <span
+                                v-if="farmData.isNormal"
+                                class="status-sub small-tag"
+                                :style="statusTagStyle[farmData?.farm_work_type]"
+                            >标准类</span>
+                            <span
+                                v-if="farmData.isAbnormal"
+                                class="status-sub small-tag isAbnormal"
+                                :style="statusTagStyle[farmData?.farm_work_type]"
+                            >异常类</span>
+                            <span
+                                v-if="farmData.isWeather"
+                                class="status-sub small-tag isWeather"
+                                :style="statusTagStyle[farmData?.farm_work_type]"
+                            >气象类</span>
+                            <span
+                                v-if="farmData.isHighRisk"
+                                class="status-sub small-tag isHighRisk"
+                                :style="statusTagStyle[farmData?.farm_work_type]"
+                            >高风险</span>
                         </div>
                     </div>
+                    <div class="status-hint-card" v-if="farmData.cornerTip">
+                        <div class="status-hint-text">{{ farmData.cornerTip }}</div>
+                    </div>
                     <div class="status-sub-wrap" :style="getFarmWorkTypeColorStyle(farmData)" v-else>
                         <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">
@@ -40,18 +52,18 @@
                     </div>
                 </div>
 
-                <div class="status-r">
+                <!-- <div class="status-r">
                     <img class="status-r__icon" src="@/assets/img/home/user-icon-fill.png" alt="">
                     外包农事
-                </div>
+                </div> -->
             </div>
 
             <div class="work-wrap" :class="{ 'from-share': isFromShare }">
                 <!-- 农事组信息 -->
-                <div class="group-info group-box" v-if="farmData.interaction_reason">
+                <div class="group-info group-box" v-if="farmData.fw_explain_simple">
                     <div class="group-name">
                         <div class="group-title">农情研判:</div>
-                        {{ farmData.interaction_reason }}
+                        {{ farmData.fw_explain_simple }}
                     </div>
                 </div>
 
@@ -60,7 +72,7 @@
                         <div class="execution-area-card" @click="handleViewArea">
                             <div class="execution-area-card__header">
                                 <span class="title">{{ $t('agriRecordDetail.executionArea') }}</span>
-                                <span class="code">ws0gefwg9tdn</span>
+                                <span class="code">{{ farmData.crop_category_name }}</span>
                             </div>
                             <div class="map-container" ref="mapContainer"></div>
                         </div>
@@ -94,31 +106,20 @@
                 </div>
 
                 <div class="box-wrap stage-card">
-                    <div class="method-switch">
-                        <div
-                            v-for="tab in methodTabs"
-                            :key="tab.value"
-                            class="method-switch__item"
-                            :class="{ active: executeMethod === tab.value }"
-                            @click="executeMethod = tab.value"
-                        >
-                            {{ tab.label }}
-                        </div>
-                    </div>
                     <div class="work-info">
                         <div class="info-item">
                             <div class="info-title"><span class="title-block"></span>{{ $t('workDetail.prescription') }}
                             </div>
-                            <div class="info-value">{{ farmData.drug_prescription }}</div>
+                            <div class="info-value">{{ farmData.fw_precrip }}</div>
                         </div>
                         <div class="info-item">
                             <div class="info-title"><span class="title-block"></span>{{ $t('workDetail.executeParameters')
                                 }}</div>
-                            <div class="info-value">{{ farmData.execution_method }}</div>
+                            <div class="info-value">{{ farmData.fw_params }}</div>
                         </div>
                         <div class="info-item">
                             <div class="info-title"><span class="title-block"></span>{{ $t('workDetail.notes') }}</div>
-                            <div class="info-value">{{ farmData.precautions }}</div>
+                            <div class="info-value">{{ farmData.fw_atten }}</div>
                         </div>
                     </div>
                 </div>
@@ -143,7 +144,7 @@
             </div>
         </div>
 
-        <div v-if="statusVal !== 'completed'" class="warning-info" :class="{ 'from-share': isFromShare }">
+        <div v-if="statusVal !== 'completed' && farmData.isAbnormal" class="warning-info" :class="{ 'from-share': isFromShare }">
             <div class="warning-l">
                 <div class="warning-title">
                     <el-icon size="16" color="#FF953D"><WarningFilled /></el-icon>
@@ -164,7 +165,7 @@
 </template>
 
 <script setup>
-import { ElMessage } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
 import { WarningFilled } from "@element-plus/icons-vue";
 import wx from "weixin-js-sdk";
 import customHeader from "@/components/customHeader.vue";
@@ -282,6 +283,44 @@ onMounted(() => {
 
 const miniJson = ref(null);
 const farmData = ref({});
+/** 列表页带入的 agronomy_detail 缓存 */
+const WORK_AGRONOMY_DETAIL_KEY = "WORK_AGRONOMY_DETAIL";
+
+function readWorkAgronomyPayload() {
+    try {
+        return JSON.parse(sessionStorage.getItem(WORK_AGRONOMY_DETAIL_KEY) || "null");
+    } catch {
+        return null;
+    }
+}
+
+/** 列表带入:agronomy_detail 原样 + 列表上的类型/状态标签 */
+function applyAgronomyDetailPayload(payload) {
+    if (!payload || typeof payload !== "object") return false;
+    const detail =
+        payload.agronomy_detail && typeof payload.agronomy_detail === "object"
+            ? payload.agronomy_detail
+            : payload.fw_name || payload.fw_explain_simple
+              ? payload
+              : null;
+    if (!detail) return false;
+    const workType = payload.work_type;
+    const isWeather = workType === "weather_warning" || workType === "warning";
+    farmData.value = {
+        ...detail,
+        work_id: payload.work_id ?? detail.id,
+        work_type: workType,
+        work_status: payload.work_status,
+        status: payload.status,
+        // 与列表卡片标签一致
+        isNormal: payload.isNormal ?? (!isWeather && workType === "standard"),
+        isAbnormal: payload.isAbnormal ?? false,
+        isWeather: payload.isWeather ?? isWeather,
+        isHighRisk: payload.isHighRisk ?? isWeather,
+        cornerTip: payload.cornerTip || "",
+    };
+    return true;
+}
 
 const wktFmt = new WKT();
 const MAP_CENTER_FALLBACK = "POINT(113.1093017627431 22.57454083668)";
@@ -375,14 +414,22 @@ const backgroundFarmWorkStatus = (status) => {
 }
 
 const getDetail = () => {
+    const payload = readWorkAgronomyPayload();
+    const payloadId = payload?.work_id != null ? String(payload.work_id) : "";
     const id = workId.value;
+    // 优先使用列表带入的 agronomy_detail
+    if (payload?.agronomy_detail && (!id || !payloadId || payloadId === String(id))) {
+        applyAgronomyDetailPayload(payload);
+        initWorkDetailMap();
+        return;
+    }
     if (id == null || id === "") return;
-    VE_API.monitor.getWorkDetail({ id }).then(res => {
+    VE_API.monitor.getWorkDetail({ id }).then((res) => {
         if (res.code === 200 && res.data.length) {
             farmData.value = res.data[0];
             initWorkDetailMap();
         }
-    })
+    });
 };
 
 const handleExecute = (status, type) => {
@@ -390,21 +437,65 @@ const handleExecute = (status, type) => {
     executePopupRef.value.openPopup(popupType);
 };
 
-const handleComplete = () => {
-    console.log('handleComplete');
+/** 完成农事用手机号:优先录入信息 */
+function resolveUserTel() {
+    try {
+        const entry = JSON.parse(localStorage.getItem("ENTRY_FARM_DATA") || "null");
+        if (entry?.phone) return String(entry.phone).trim();
+    } catch {
+        // ignore
+    }
+    try {
+        const userInfo = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
+        return String(userInfo.tel || userInfo.phone || userInfo.mobile || "").trim();
+    } catch {
+        return "";
+    }
+}
+
+const handleComplete = async () => {
+    const tel = resolveUserTel();
+    const id = farmData.value?.work_id ?? workId.value;
+    if (!tel) {
+        ElMessage.error("缺少手机号,无法完成农事");
+        return;
+    }
+    if (id == null || id === "") {
+        ElMessage.error("缺少农事信息,无法完成");
+        return;
+    }
+    try {
+        await ElMessageBox.confirm("确认将该农事标记为已完成?", "提示", {
+            confirmButtonText: "确认",
+            cancelButtonText: "取消",
+            type: "warning",
+        });
+    } catch {
+        return;
+    }
+    try {
+        const raw = await VE_API.questionnaire.updateWorkStatus({
+            work_type: farmData.value?.work_type || "standard",
+            work_id: id,
+            tel,
+        });
+        const res = Array.isArray(raw) ? raw[0] : raw;
+        const code = Number(res?.code);
+        if (!(code === 200 || code === 0 || code === 1)) {
+            ElMessage.error(res?.message || res?.msg || "完成农事失败");
+            return;
+        }
+        ElMessage.success("农事已完成");
+        router.back();
+    } catch {
+        ElMessage.error("完成农事失败,请稍后再试");
+    }
 };
 
 watch(locale, () => {
     getDetail();
 });
 
-// 执行方式切换:机械 / 人工
-const methodTabs = [
-    { label: "机械方式", value: "machine" },
-    { label: "人工方式", value: "manual" },
-];
-const executeMethod = ref("machine");
-
 // 执行方式 Tab 配置
 const executionTabs = [
     { label: "植保机", value: 1 },
@@ -626,12 +717,10 @@ const areaMap = new AreaMap();
     padding: 12px;
     color: #fff;
     z-index: 1;
-    height: 92px;
+    min-height: 92px;
     box-sizing: border-box;
     display: flex;
     align-items: center;
-    // flex-direction: column;
-    // justify-content: center;
 
     &::after {
         content: "";
@@ -650,18 +739,30 @@ const areaMap = new AreaMap();
         }
     }
 
-    .mt-4 {
-        margin-top: 4px;
-    }
-
     .status-l {
         flex: 1;
+        min-width: 0;
         .status-title {
             font-size: 21px;
             display: flex;
-            align-items: center;
+            align-items: flex-start;
             gap: 9px;
 
+            .status-title-text {
+                flex: 1;
+                min-width: 0;
+                line-height: 1.3;
+                word-break: break-word;
+            }
+
+            .status-title-tags {
+                flex-shrink: 0;
+                display: flex;
+                flex-direction: row;
+                align-items: center;
+                gap: 4px;
+            }
+
             .status-tag {
                 font-size: 12px;
                 color: #2199F8;
@@ -691,6 +792,8 @@ const areaMap = new AreaMap();
                 height: 22px;
                 line-height: 22px;
                 font-size: 12px;
+                white-space: nowrap;
+                flex-shrink: 0;
                 &.isAbnormal {
                     color: #FF6A6A;
                 }
@@ -716,6 +819,9 @@ const areaMap = new AreaMap();
 
         .status-hint-card {
             margin-top: 10px;
+            display: inline-block;
+            width: fit-content;
+            max-width: 100%;
             background: #fff;
             border-radius: 2px;
             padding: 5px 8px;
@@ -724,6 +830,7 @@ const areaMap = new AreaMap();
             .status-hint-text {
                 font-size: 12px;
                 color: #626262;
+                white-space: nowrap;
             }
 
             .status-hint-question {
@@ -1012,31 +1119,6 @@ const areaMap = new AreaMap();
 }
 
 .stage-card {
-    .method-switch {
-        display: flex;
-        gap: 10px;
-        margin-bottom: 10px;
-
-        .method-switch__item {
-            height: 28px;
-            line-height: 28px;
-            padding: 0 12px;
-            text-align: center;
-            font-size: 12px;
-            color: #8E8E8E;
-            background: #fff;
-            border: 1px solid #fff;
-            border-radius: 2px;
-            box-sizing: border-box;
-
-            &.active {
-                color: #2199F8;
-                background: rgba(33, 153, 248, 0.1);
-                border-color: #2199f8;
-            }
-        }
-    }
-
     .stage-header {
         padding-bottom: 12px;
         display: flex;

+ 23 - 14
src/views/old_mini/work_execute/components/calendar.vue

@@ -106,26 +106,29 @@ const calendarDays = computed(() => {
 });
 
 // 根据日期获取农事数据
+function resolveTaskDate(task) {
+    const raw = task?.workTime || task?.work_time || task?.expectedExecuteDate || task?.executeDeadlineDate;
+    if (!raw) return null;
+    const str = String(raw).trim();
+    if (/^\d{4}-\d{2}-\d{2}/.test(str)) return str.slice(0, 10);
+    const d = new Date(str);
+    if (Number.isNaN(d.getTime())) return null;
+    return formatDate(d);
+}
+
 function getTaskByDate(dateStr) {
     if (!taskListData.value || taskListData.value.length === 0) {
         return null;
     }
-    // 查找匹配日期的农事
-    const matchedTasks = taskListData.value.filter(task => {
-        if (!task.expectedExecuteDate && !task.executeDeadlineDate) return false;
-        // 格式化日期字符串进行比较(确保格式一致)
-        const taskDate = formatDate(new Date(task.expectedExecuteDate || task.executeDeadlineDate));
-        return taskDate === dateStr;
-    });
-
+    // 角标只统计待执行
+    const matchedTasks = taskListData.value.filter(
+        (task) => resolveTaskDate(task) === dateStr && task.status === "pending"
+    );
     if (matchedTasks.length === 0) {
         return null;
     }
-
-    // const farmWorkNames = matchedTasks.map(task => task.farmWorkName || '').filter(Boolean);
     return {
         lengthVal: matchedTasks.length,
-        // farmWorkName: farmWorkNames.length > 1 ? farmWorkNames.join('、') : farmWorkNames[0] || ''
     };
 }
 
@@ -380,13 +383,19 @@ const selectedDate = ref(null);
             position: absolute;
             top: -5px;
             right: -5px;
+            z-index: 1;
             color: #fff;
             font-size: 10px;
             background: #2199F8;
-            width: 14px;
-            height: 14px;
-            line-height: 16px;
+            min-width: 16px;
+            height: 16px;
+            padding: 0 4px;
+            box-sizing: border-box;
             border-radius: 50%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            line-height: 1;
         }
         .days-one {
             text-align: center;

+ 128 - 82
src/views/old_mini/work_execute/index.vue

@@ -77,7 +77,7 @@
 import { computed, nextTick, onActivated, ref } from "vue";
 import { useStore } from "vuex";
 import { useRouter } from "vue-router";
-import { ElMessage } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
 import IndexMap from "./index";
 import customCalendar from "./components/calendar.vue";
 import farmHeader from "@/components/farmHeader.vue";
@@ -105,47 +105,14 @@ const filterDate = ref(formatDateYMD());
 
 const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
 const MAP_POINT_STORAGE_KEY = "GROWTH_REPORT_MAP_POINT";
+/** 农事详情:列表点击时带入的 agronomy_detail */
+const WORK_AGRONOMY_DETAIL_KEY = "WORK_AGRONOMY_DETAIL";
 const DEFAULT_MAP_POINT = "POINT(113.6142086995688 23.585836479509055)";
 
-/**
- * 假数据模板(已完成 tab 兜底):wkt 相对中心点偏移
- */
-const COMPLETED_MOCK_TEMPLATE = [
-    {
-        id: "mock-c-4",
-        status: "completed",
-        title: "病虫害巡查",
-        isHighRisk: false,
-        isNormal: true,
-        isAbnormal: false,
-        isWeather: false,
-        isByMyself: true,
-        cornerTip: "已完成",
-        analysisSummary: "新梢与果实表面未见明显异常斑点",
-        areaCode: "分区一",
-        farmName: "病虫害巡查",
-        executeDate: "08/05",
-        offset: [0.0004, -0.0003],
-    },
-    {
-        id: "mock-c-5",
-        status: "completed",
-        title: "修剪整枝",
-        isHighRisk: false,
-        isNormal: true,
-        isAbnormal: false,
-        isWeather: false,
-        cornerTip: "已完成",
-        analysisSummary: "已清理过密枝条,改善通风透光",
-        areaCode: "分区一",
-        farmName: "修剪整枝",
-        executeDate: "08/04",
-        offset: [-0.0024, 0.0004],
-    },
-];
-
-/** status: pending 待执行 / completed 已完成 */
+/** status: pending 待执行 / completed 已完成(下方列表,按日期筛选) */
 const taskList = ref([]);
+/** 地图气泡:不传日期,展示全部农事 */
+const mapTaskList = ref([]);
 
 function readEntryFarmData() {
     try {
@@ -179,22 +146,6 @@ function formatWorkTime(workTime) {
     return formatExecuteDate(d);
 }
 
-/** 根据中心点生成附近任务点位(已完成 mock) */
-const buildCompletedNearCenter = (centerWkt) => {
-    const coord = convertPointToArray(centerWkt);
-    const lng = Number(coord?.[0]);
-    const lat = Number(coord?.[1]);
-    if (!Number.isFinite(lng) || !Number.isFinite(lat)) return [];
-    return COMPLETED_MOCK_TEMPLATE.map((item) => {
-        const [dx = 0, dy = 0] = item.offset || [];
-        const { offset, ...rest } = item;
-        return {
-            ...rest,
-            wkt: `POINT(${lng + dx} ${lat + dy})`,
-        };
-    });
-};
-
 /** user_work 单条 → 列表卡片 */
 function mapUserWorkToTask(item, index, centerWkt) {
     const detail = item?.agronomy_detail || {};
@@ -207,7 +158,9 @@ function mapUserWorkToTask(item, index, centerWkt) {
     const title = detail.fw_name || "";
     const executeDate = formatWorkTime(item.work_time);
     const isWarning = item.work_type === "weather_warning" || item.work_type === "warning";
-    const isCompleted = Number(item.work_status) === 1;
+    // 接口约定:0 未完成,1 已完成(兼容拼写 work_stauts)
+    const rawStatus = item.work_status ?? item.work_stauts;
+    const isCompleted = Number(rawStatus) === 1;
     return {
         id: item.work_id ?? detail.id ?? `work-${index}`,
         status: isCompleted ? "completed" : "pending",
@@ -221,6 +174,7 @@ function mapUserWorkToTask(item, index, centerWkt) {
         analysisSummary: detail.fw_explain_simple || detail.fw_explain || "",
         areaCode: detail.crop_category_name || "分区一",
         executeDate,
+        workTime: item.work_time,
         phenophaseCode: detail.phenophase_code,
         phenophaseName: detail.phenophase_name,
         fwExplain: detail.fw_explain,
@@ -228,35 +182,63 @@ function mapUserWorkToTask(item, index, centerWkt) {
         fwParams: detail.fw_params,
         fwPrecrip: detail.fw_precrip,
         workType: item.work_type,
-        workStatus: item.work_status,
+        workStatus: rawStatus,
         agronomyType: isWarning ? "warning" : "standard",
+        // 点击进详情原样带入
+        agronomy_detail: item?.agronomy_detail || null,
         wkt: hasCoord ? `POINT(${lng + dx} ${lat + dy})` : "",
     };
 }
 
-/** 拉取用户农事列表(report/user_work) */
-async function fetchUserWorkTasks(centerWkt) {
+/**
+ * 拉取用户农事(report/user_work)
+ * @param {string} centerWkt
+ * @param {string} [workTime] YYYY-MM-DD;不传则返回全部(地图用)
+ */
+async function requestUserWork(centerWkt, workTime) {
     const tel = resolveUserTel();
-    if (!tel) {
-        taskList.value = buildCompletedNearCenter(centerWkt);
-        return;
+    if (!tel) return [];
+
+    const params = { tel };
+    if (workTime) params.work_time = workTime;
+
+    const raw = await VE_API.questionnaire.userWork(params);
+    const res = Array.isArray(raw) ? raw[0] : raw;
+    const code = Number(res?.code);
+    if (!(code === 200 || code === 0 || code === 1)) {
+        ElMessage.error(res?.message || res?.msg || "获取农事列表失败");
+        return [];
     }
+    const list = Array.isArray(res?.data) ? res.data : [];
+    return list.map((item, index) => mapUserWorkToTask(item, index, centerWkt));
+}
+
+/** 地图:不传日期,全量 */
+async function fetchMapTasks(centerWkt) {
     try {
-        const raw = await VE_API.questionnaire.userWork({ tel });
-        const res = Array.isArray(raw) ? raw[0] : raw;
-        const code = Number(res?.code);
-        if (!(code === 200 || code === 0 || code === 1)) {
-            ElMessage.error(res?.message || res?.msg || "获取农事列表失败");
-            taskList.value = buildCompletedNearCenter(centerWkt);
-            return;
-        }
-        const list = Array.isArray(res?.data) ? res.data : [];
-        const mapped = list.map((item, index) => mapUserWorkToTask(item, index, centerWkt));
-        const hasCompleted = mapped.some((item) => item.status === "completed");
-        taskList.value = hasCompleted ? mapped : [...mapped, ...buildCompletedNearCenter(centerWkt)];
+        mapTaskList.value = await requestUserWork(centerWkt);
+        syncCalendarBadges();
+    } catch {
+        ElMessage.error("获取农事地图数据失败,请稍后再试");
+        mapTaskList.value = [];
+        syncCalendarBadges();
+    }
+}
+
+/** 用全量农事刷新日期角标数量 */
+function syncCalendarBadges() {
+    nextTick(() => {
+        calendarRef.value?.setSolarTerm?.(mapTaskList.value);
+    });
+}
+
+/** 下方列表:传选中日期 */
+async function fetchListTasks(centerWkt, workTime) {
+    try {
+        taskList.value = await requestUserWork(centerWkt, workTime || undefined);
     } catch {
         ElMessage.error("获取农事列表失败,请稍后再试");
-        taskList.value = buildCompletedNearCenter(centerWkt);
+        taskList.value = [];
     }
 }
 const activeTab = ref("pending");
@@ -275,8 +257,11 @@ const handleTabChange = (key) => {
     });
 };
 
-const handleDateSelect = (date) => {
-    filterDate.value = date ?? formatDateYMD();
+const handleDateSelect = async (date) => {
+    // null:取消筛选,列表也不传日期
+    filterDate.value = date || "";
+    const center = mapPoint.value || resolveMapPoint();
+    await fetchListTasks(center, filterDate.value);
 };
 
 const handleForward = (item) => {
@@ -297,6 +282,24 @@ const handleForward = (item) => {
 };
 
 const handleViewDetail = (item) => {
+    if (item?.agronomy_detail) {
+        sessionStorage.setItem(
+            WORK_AGRONOMY_DETAIL_KEY,
+            JSON.stringify({
+                agronomy_detail: item.agronomy_detail,
+                work_id: item.id,
+                work_time: item.workTime || item.executeDate,
+                work_type: item.workType,
+                work_status: item.workStatus,
+                status: item.status,
+                isNormal: item.isNormal,
+                isAbnormal: item.isAbnormal,
+                isWeather: item.isWeather,
+                isHighRisk: item.isHighRisk,
+                cornerTip: item.cornerTip || "",
+            })
+        );
+    }
     router.push({
         path: "/work_detail",
         query: {
@@ -306,9 +309,46 @@ const handleViewDetail = (item) => {
     });
 };
 
-const handleComplete = (item) => {
-    // TODO: 对接完成农事接口
-    ElMessage.success("农事已完成");
+const handleComplete = async (item) => {
+    const tel = resolveUserTel();
+    const workId = item?.id;
+    if (!tel) {
+        ElMessage.error("缺少手机号,无法完成农事");
+        return;
+    }
+    if (workId == null || workId === "") {
+        ElMessage.error("缺少农事信息,无法完成");
+        return;
+    }
+    try {
+        await ElMessageBox.confirm("确认将该农事标记为已完成?", "提示", {
+            confirmButtonText: "确认",
+            cancelButtonText: "取消",
+            type: "warning",
+        });
+    } catch {
+        return;
+    }
+    try {
+        const raw = await VE_API.questionnaire.updateWorkStatus({
+            work_type: item.workType || "standard",
+            work_id: workId,
+            tel,
+        });
+        const res = Array.isArray(raw) ? raw[0] : raw;
+        const code = Number(res?.code);
+        if (!(code === 200 || code === 0 || code === 1)) {
+            ElMessage.error(res?.message || res?.msg || "完成农事失败");
+            return;
+        }
+        ElMessage.success("农事已完成");
+        const center = mapPoint.value || resolveMapPoint();
+        const listDate = filterDate.value || formatDateYMD();
+        await Promise.all([fetchMapTasks(center), fetchListTasks(center, listDate)]);
+        refreshMapMarkers();
+    } catch {
+        ElMessage.error("完成农事失败,请稍后再试");
+    }
 };
 
 const goCompleteFarmInfo = () => {
@@ -356,7 +396,9 @@ const resolveMapPoint = () => {
     return DEFAULT_MAP_POINT;
 };
 
-const getMapMarkerList = () => displayTaskList.value.filter((item) => item.wkt);
+/** 地图只展示待执行农事(不按日期、不跟已完成 tab) */
+const getMapMarkerList = () =>
+    mapTaskList.value.filter((item) => item.status === "pending" && item.wkt);
 
 const refreshMapMarkers = () => {
     if (!mapContainer.value) return;
@@ -374,7 +416,11 @@ const refreshMapMarkers = () => {
 const loadWorkPage = async () => {
     if (!mapContainer.value) return;
     mapPoint.value = resolveMapPoint();
-    await fetchUserWorkTasks(mapPoint.value);
+    const listDate = filterDate.value || formatDateYMD();
+    await Promise.all([
+        fetchMapTasks(mapPoint.value),
+        fetchListTasks(mapPoint.value, listDate),
+    ]);
     refreshMapMarkers();
     if (mapContainer.value && indexMap.kmap?.map) {
         const checkAndUpdateSize = () => {