浏览代码

feat:对接农事规划是否为溯源接口和病虫害回答接口

wangsisi 6 天之前
父节点
当前提交
3126b3782a

+ 13 - 6
src/components/pageComponents/ArchivesFarmTimeLine.vue

@@ -91,6 +91,10 @@ const props = defineProps({
         type: [String, Number],
         default: null,
     },
+    activeFarmWorkTab: {
+        type: [String, Number],
+        default: '0',
+    },
 });
 
 const emits = defineEmits(["row-click", "card-click"]);
@@ -213,7 +217,7 @@ const lastRequestedScopeKey = ref(null);
 const lastFetchedLocale = ref(null);
 
 const farmWorkPlanScopeKey = () =>
-    JSON.stringify([props.farmId ?? null, locale.value ?? "zh"]);
+    JSON.stringify([props.farmId ?? null, props.activeFarmWorkTab ?? "0", locale.value ?? "zh"]);
 
 const resetTimelineData = () => {
     phenologyList.value = [];
@@ -390,6 +394,7 @@ const getFarmWorkPlan = () => {
 
     const params = {
         farm_id: props.farmId,
+        is_suyuan: props.activeFarmWorkTab,
         crop_variety: JSON.parse(localStorage.getItem("selectedFarmData")).farm_variety,
     };
     VE_API.monitor.getPhenologyList(params)
@@ -423,11 +428,13 @@ const updateFarmWorkPlan = () => {
 };
 
 watch(
-    () => props.farmId,
-    (val, oldVal) => {
-        if (!props.farmId) return;
-        const changed = oldVal == null || val !== oldVal;
-        if (changed) {
+    () => [props.farmId, props.activeFarmWorkTab],
+    ([farmId, tab], oldVal) => {
+        if (!farmId) return;
+        const [oldFarmId, oldTab] = oldVal ?? [];
+        const scopeChanged =
+            oldFarmId == null || farmId !== oldFarmId || tab !== oldTab;
+        if (scopeChanged) {
             lastRequestedScopeKey.value = null;
         }
         updateFarmWorkPlan();

+ 56 - 30
src/components/pageComponents/PhenologyTrackTimelineItem.vue

@@ -1,48 +1,74 @@
 <template>
-    <div class="phenology-track-item">
-        <div class="track-axis">
-            <span class="track-date-side">{{ date }}</span>
-            <div class="track-line-wrap">
-                <span class="track-dot"></span>
-                <div class="track-line"></div>
+    <template v-if="imageList.length">
+        <div class="phenology-track-item" v-for="item in imageList" :key="item.id">
+            <div class="track-axis">
+                <span class="track-date-side">{{ item?.latest_time?.slice(5, 10) }}</span>
+                <div class="track-line-wrap">
+                    <span class="track-dot"></span>
+                    <div class="track-line"></div>
+                </div>
             </div>
-        </div>
-        <div class="track-card">
-            <div class="track-card-inner">
-                <span class="track-badge">{{ date }}</span>
-                <span class="track-content">{{ content }}</span>
-            </div>
-            <div v-if="hasImages" class="track-images">
-                <img v-for="(src, i) in images" :key="i" class="track-thumb" :src="src" alt="" />
+            <div class="track-card">
+                <div class="track-card-inner">
+                    <span class="track-badge">{{ item?.latest_time?.slice(5, 10) }}</span>
+                    <span class="track-content">{{ item.zone_name }}</span>
+                </div>
+                <div class="track-images">
+                    <img v-for="(src, i) in item.images" :key="i" class="track-thumb" :src="src.cloud_url" alt="" />
+                </div>
             </div>
         </div>
-    </div>
+    </template>
+    <div v-else-if="dataLoaded" class="track-empty">{{ t('agriFile.noData') }}</div>
 </template>
 
 <script setup>
-import { computed } from "vue";
+import { onMounted, ref } from "vue";
+import { useI18n } from "@/i18n";
+
+const { t } = useI18n();
 
 const props = defineProps({
-    date: {
-        type: String,
-        default: "",
-    },
-    content: {
-        type: String,
-        default: "",
-    },
-    images: {
-        type: Array,
-        default: () => [],
+    abnormalType: {
+        type: [String, Number],
+        default: 1,
     },
 });
 
-const hasImages = computed(
-    () => Array.isArray(props.images) && props.images.length > 0
-);
+const imageList = ref([]);
+const dataLoaded = ref(false);
+
+const getFarmImages = async () => {
+    const farmData = JSON.parse(localStorage.getItem('selectedFarmData'));
+    const params = {
+        farm_id: farmData.farm_id,
+        variety_code: farmData.farm_variety,
+        abnormal_type: props.abnormalType,
+        record_id: 21,
+    };
+    try {
+        const res = await VE_API.record.farmImages(params);
+        if (res.code === 200) {
+            imageList.value = res.data.zones || [];
+        }
+    } finally {
+        dataLoaded.value = true;
+    }
+};
+
+onMounted(() => {
+    getFarmImages();
+})
 </script>
 
 <style scoped lang="scss">
+.track-empty {
+    padding: 24px 0;
+    text-align: center;
+    font-size: 14px;
+    color: rgba(60, 60, 60, 0.45);
+}
+
 /* Grid:左侧时间轴整列与右侧卡片等高,竖线随卡片高度自适应 */
 .phenology-track-item {
     display: grid;

+ 87 - 9
src/components/popup/UploadProgressPopup.vue

@@ -2,22 +2,23 @@
     <popup :show="show" round :close-on-click-overlay="false" class="upload-progress-popup"
         @update:show="emit('update:show', $event)">
         <slot name="header"></slot>
-        <div class="upload-box" v-loading="popupImageUploadLoading" element-loading-:text="t('上传中...')">
+        <div class="upload-box" v-loading="loading" element-loading-:text="t('上传中...')">
             <div class="box-header">
                 <div class="upload-title">
                     <span>{{ t('上传照片') }}</span>
-                    <span class="optional">{{ t('(可选)') }}</span>
+                    <span v-if="!uploadRequired" class="optional">{{ t('(可选)') }}</span>
                 </div>
                 <div class="ai-btn">{{ t('AI 智能分析') }}</div>
             </div>
-            <upload ref="uploadRef" :maxCount="10" :initImgArr="initImgArr" @handleUpload="onHandleUpload">
+            <upload ref="uploadRef" :maxCount="10" :initImgArr="displayImgArr" :before-read="beforeReadUpload"
+                :after-read="afterReadUpload">
             </upload>
             <!-- <div class="upload-result">{{ t('AI识别结果:该病为该病为该病为该病为病为该病为病为该病为') }}</div> -->
         </div>
         <slot name="footer"></slot>
         <div class="upload-action-btns">
             <div class="cancel-btn" @click="emit('cancel')">{{ t('取消') }}</div>
-            <div class="confirm-btn" @click="emit('confirm')">{{ confirmText }}</div>
+            <div class="confirm-btn" @click="handleConfirm">{{ confirmText }}</div>
         </div>
     </popup>
 </template>
@@ -25,11 +26,15 @@
 <script setup>
 import { useI18n } from "@/i18n";
 const { t } = useI18n();
-import { ref } from 'vue';
+import { ref, computed, watch } from 'vue';
 import { Popup } from 'vant';
+import { ElMessage } from 'element-plus';
 import upload from '@/components/upload.vue';
+import UploadFile from "@/utils/upliadFile";
+import { getFileExt } from "@/utils/util";
+import {base_img_url2} from '@/api/config';
 
-defineProps({
+const props = defineProps({
     show: {
         type: Boolean,
         default: false,
@@ -46,20 +51,93 @@ defineProps({
         type: String,
         default: '确认信息',
     },
+    /** 是否必传图片,默认必传 */
+    uploadRequired: {
+        type: Boolean,
+        default: true,
+    },
 });
 
-const emit = defineEmits(['update:show', 'cancel', 'confirm', 'handleUpload']);
+const emit = defineEmits(['update:show', 'cancel', 'confirm', 'reset']);
 
 const uploadRef = ref(null);
+const popupInnerImgArr = ref([]);
+const popupInnerLoading = ref(false);
+const uploadFileObj = new UploadFile();
+const miniUserId = localStorage.getItem("MINI_USER_ID");
+
+const loading = computed(() => props.popupImageUploadLoading || popupInnerLoading.value);
 
-function onHandleUpload(payload) {
-    emit('handleUpload', payload);
+/** 弹窗内上传优先展示,否则回显父组件传入的 initImgArr */
+const displayImgArr = computed(() =>
+    popupInnerImgArr.value.length ? popupInnerImgArr.value : props.initImgArr,
+);
+
+function getConfirmImgArr() {
+    if (popupInnerImgArr.value.length) {
+        return [...popupInnerImgArr.value.map(item => base_img_url2 + item)];
+    }
+    return [...(props.initImgArr || [])];
+}
+
+const beforeReadUpload = () => {
+    popupInnerLoading.value = false;
+    return true;
+};
+
+const afterReadUpload = async (data) => {
+    if (!Array.isArray(data)) {
+        data = [data];
+    }
+    popupInnerLoading.value = true;
+    try {
+        for (const file of data) {
+            const fileVal = file.file;
+            file.status = "uploading";
+            file.message = "上传中...";
+            const ext = getFileExt(fileVal.name);
+            const key = `birdseye-look-mini/${miniUserId}/${new Date().getTime()}.${ext}`;
+            const resFilename = await uploadFileObj.put(key, fileVal);
+            if (resFilename) {
+                file.status = "done";
+                file.message = "";
+                popupInnerImgArr.value.push(resFilename);
+            } else {
+                file.status = "failed";
+                file.message = "上传失败";
+                ElMessage.error("图片上传失败,请稍后再试!");
+            }
+        }
+    } finally {
+        popupInnerLoading.value = false;
+    }
+};
+
+function handleConfirm() {
+    const imgArr = getConfirmImgArr();
+    if (props.uploadRequired && !imgArr.length) {
+        ElMessage.warning(t('请先上传照片'));
+        return;
+    }
+    emit('confirm', imgArr);
 }
 
 function uploadReset() {
+    popupInnerImgArr.value = [];
+    popupInnerLoading.value = false;
     uploadRef.value?.uploadReset?.();
 }
 
+watch(
+    () => props.show,
+    (val) => {
+        if (val) {
+            uploadReset();
+            emit('reset');
+        }
+    },
+);
+
 defineExpose({
     uploadReset,
 });

+ 26 - 3
src/components/upload.vue

@@ -12,7 +12,8 @@
     <div class="upload-content">
       <img v-if="exampleImg" @click="showExample(null)" class="example" src="@/assets/img/home/example-4.png" alt="" />
       <uploader class="uploader" :class="{ 'uploader-list': exampleImg }" v-model="fileList"
-        :multiple="props.maxCount > 1" :max-count="props.maxCount" :after-read="afterRead" @delete="deleteImg">
+        :multiple="props.maxCount > 1" :max-count="props.maxCount" :before-read="onBeforeRead" :after-read="onAfterRead"
+        @delete="deleteImg">
         <template v-if="exampleImg">
           <slot v-if="!fileList.length"></slot>
           <img class="plus" v-else src="@/assets/img/home/plus.png" alt="">
@@ -72,7 +73,15 @@ const props = defineProps({
   initImgArr: {
     type: Array,
     default: () => []
-  }
+  },
+  beforeRead: {
+    type: Function,
+    default: null,
+  },
+  afterRead: {
+    type: Function,
+    default: null,
+  },
 })
 
 
@@ -94,7 +103,21 @@ const showExample = (example) => {
   showExamplePopup.value = true;
 };
 
-const afterRead = async (files) => {
+const onBeforeRead = (file) => {
+  if (typeof props.beforeRead === 'function') {
+    return props.beforeRead(file);
+  }
+  return true;
+};
+
+const onAfterRead = async (files) => {
+  if (typeof props.afterRead === 'function') {
+    return props.afterRead(files);
+  }
+  return defaultAfterRead(files);
+};
+
+const defaultAfterRead = async (files) => {
   if (!Array.isArray(files)) {
     files = [files];
   }

+ 4 - 0
src/i18n/recordDetails-messages.js

@@ -40,9 +40,11 @@ export const recordDetailsZh = {
     abnormalRecord: "异常记录",
     abnormalCount: "有多少植株出现了异常?",
     inputPlaceholder: "请输入",
+    ratioRequired: "请输入占比",
     patrolDesc: "巡园描述",
     inputDescPlaceholder: "请输入描述",
     uploadPhotoFirst: "请先上传照片",
+    drawMapFirst: "请先勾画地图区域",
     confirmSuccess: "确认成功",
     pestDisease: "病害",
     pestInsect: "虫害",
@@ -125,9 +127,11 @@ export const recordDetailsEn = {
     abnormalRecord: "Abnormal log",
     abnormalCount: "What % of plants show abnormality?",
     inputPlaceholder: "Enter",
+    ratioRequired: "Please enter the ratio",
     patrolDesc: "Patrol notes",
     inputDescPlaceholder: "Enter description",
     uploadPhotoFirst: "Please upload photos first",
+    drawMapFirst: "Please draw the area on the map first",
     confirmSuccess: "Confirmed",
     pestDisease: "Disease",
     pestInsect: "Insect",

+ 2 - 0
src/router/globalRoutes.js

@@ -177,12 +177,14 @@ export default [
     {
         path: "/record_details",
         name: "RecordDetails",
+        meta: { keepAlive: true },
         component: () => import("@/views/old_mini/recordDetails/index.vue"),
     },
     // 地图管理
     {
         path: "/map_manage",
         name: "MapManage",
+        meta: { keepAlive: true },
         component: () => import("@/views/old_mini/recordDetails/mapManage.vue"),
     },
 ];

+ 30 - 0
src/store/modules/recordDetails/index.js

@@ -0,0 +1,30 @@
+const emptyMapConfirmPayload = () => ({
+    zone_name: '',
+    coordinates: [],
+});
+
+export default {
+    namespaced: true,
+    state() {
+        return {
+            mapConfirmPayload: emptyMapConfirmPayload(),
+        };
+    },
+    getters: {
+        hasMapConfirmPayload(state) {
+            const { zone_name, coordinates } = state.mapConfirmPayload;
+            return Boolean(zone_name) || (Array.isArray(coordinates) && coordinates.length > 0);
+        },
+    },
+    mutations: {
+        SET_MAP_CONFIRM_PAYLOAD(state, payload) {
+            state.mapConfirmPayload = {
+                zone_name: payload?.zone_name || '',
+                coordinates: Array.isArray(payload?.coordinates) ? payload.coordinates : [],
+            };
+        },
+        CLEAR_MAP_CONFIRM_PAYLOAD(state) {
+            state.mapConfirmPayload = emptyMapConfirmPayload();
+        },
+    },
+};

+ 11 - 12
src/views/old_mini/agri_record/index.vue

@@ -38,22 +38,17 @@
             </div>
             <div class="farm-work-tabs">
                 <div
+                    v-for="tab in farmWorkTabs"
+                    :key="tab.key"
                     class="farm-work-tabs__item"
-                    :class="{ 'farm-work-tabs__item--active': activeFarmWorkTab === 'all' }"
-                    @click="handleFarmWorkTabClick('all')"
+                    :class="{ 'farm-work-tabs__item--active': activeFarmWorkTab === tab.key }"
+                    @click="handleFarmWorkTabClick(tab.key)"
                 >
-                    {{ t('agriRecord.allFarmWork') }}
-                </div>
-                <div
-                    class="farm-work-tabs__item"
-                    :class="{ 'farm-work-tabs__item--active': activeFarmWorkTab === 'trace' }"
-                    @click="handleFarmWorkTabClick('trace')"
-                >
-                    {{ t('agriRecord.traceFarmWork') }}
+                    {{ t(tab.labelKey) }}
                 </div>
             </div>
             <div class="archives-time-line-content">
-                <archives-farm-time-line :farmId="gardenId"></archives-farm-time-line>
+                <archives-farm-time-line :farmId="gardenId" :activeFarmWorkTab="activeFarmWorkTab"></archives-farm-time-line>
             </div>
         </div>
     </div>
@@ -108,7 +103,11 @@ const defaultGardenId = ref(null);
 const selectedGardenId = ref(null);
 const gardenListRef = ref(null);
 const activeGardenTab = ref('current');
-const activeFarmWorkTab = ref('all');
+const activeFarmWorkTab = ref('0');
+const farmWorkTabs = [
+    { key: '0', labelKey: 'agriRecord.allFarmWork' },
+    { key: '1', labelKey: 'agriRecord.traceFarmWork' },
+];
 const trendMonitorMockList = ref([]);
 
 const handleFarmWorkTabClick = (tab) => {

+ 101 - 0
src/views/old_mini/recordDetails/components/RegionNamePopup.vue

@@ -0,0 +1,101 @@
+<template>
+    <popup
+        v-model:show="showValue"
+        round
+        closeable
+        class="region-name-popup"
+        :close-on-click-overlay="false"
+        teleport="body"
+    >
+        <div class="region-name-popup__content">
+            <div class="region-name-popup__title">{{ $t('区域名称') }}</div>
+            <el-input
+                v-model="regionName"
+                size="large"
+                :placeholder="$t('请输入区域名称')"
+            />
+            <div class="region-name-popup__confirm" @click="handleConfirm">
+                {{ $t('确认区域') }}
+            </div>
+        </div>
+    </popup>
+</template>
+
+<script setup>
+import { ref, computed, watch } from "vue";
+import { Popup } from "vant";
+import { ElMessage } from "element-plus";
+
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false,
+    },
+});
+
+const emit = defineEmits(["update:show", "confirm"]);
+
+const regionName = ref("");
+
+const showValue = computed({
+    get: () => props.show,
+    set: (value) => emit("update:show", value),
+});
+
+watch(
+    () => props.show,
+    (val) => {
+        if (val) {
+            regionName.value = "";
+        }
+    },
+);
+
+const handleConfirm = () => {
+    const name = regionName.value.trim();
+    if (!name) {
+        ElMessage.warning("请输入区域名称");
+        return;
+    }
+    emit("confirm", name);
+    emit("update:show", false);
+};
+</script>
+
+<style scoped lang="scss">
+.region-name-popup {
+    width: 100%;
+
+    :deep(.van-popup__close-icon) {
+        color: #333333;
+        font-size: 18px;
+    }
+
+    &__content {
+        padding: 24px 16px 20px;
+        background: linear-gradient(360deg, #ffffff 74.2%, #d1ebff 100%);
+        border-radius: 16px;
+        box-sizing: border-box;
+    }
+
+    &__title {
+        font-size: 16px;
+        margin-bottom: 12px;
+    }
+
+    :deep(.el-input__wrapper) {
+        border-radius: 4px;
+        box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1) inset;
+    }
+
+    &__confirm {
+        margin-top: 24px;
+        padding: 8px;
+        border-radius: 25px;
+        background: #2199f8;
+        color: #fff;
+        font-size: 16px;
+        text-align: center;
+    }
+}
+</style>

+ 167 - 170
src/views/old_mini/recordDetails/index.vue

@@ -4,7 +4,7 @@
         <div class="record-content">
             <div class="record-header" v-if="recordType === 'growth'">
                 <span>{{ t('agriRecord.growthWorkName') }}</span>
-                <div class="question">{{currentGrowthAnomalyDetail?.interact_question }}</div>
+                <div class="question">{{ currentGrowthAnomalyDetail?.interact_question }}</div>
             </div>
             <div class="record-header" v-else-if="recordType === 'pest'">
                 <span>{{ t('agriRecord.pestWorkName') }}</span>
@@ -18,29 +18,34 @@
                 <div class="card-wrap" v-if="recordType === 'growth'">
                     <div class="card-item">
                         <span class="item-label">{{ t('recordDetails.scienceLabel') }}</span>
-                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand" :collapse-text="expandCollapse.collapse" rows="3"
+                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand"
+                            :collapse-text="expandCollapse.collapse" rows="3"
                             :content="currentGrowthAnomalyDetail?.agri_judgment" />
                     </div>
                     <div class="tabs-list" v-if="!showMap">
                         <div class="item-tab" :class="{ 'item-tab--active': activeGrowthAnomalyIndex === index }"
-                            v-for="(item, index) in growthAnomalyTabs" :key="index" @click="handleGrowthAnomalyTabClick(index)">{{ item.label }}
+                            v-for="(item, index) in growthAnomalyTabs" :key="index"
+                            @click="handleGrowthAnomalyTabClick(index)">{{ item.label }}
                         </div>
                     </div>
                     <div class="card-item">
                         <span class="item-label">{{ t('recordDetails.phenotypeLabel') }}</span>
-                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand" :collapse-text="expandCollapse.collapse" rows="3"
+                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand"
+                            :collapse-text="expandCollapse.collapse" rows="3"
                             :content="currentGrowthAnomalyDetail?.phenotype" />
                     </div>
                     <div class="card-item">
                         <span class="item-label">{{ t('recordDetails.highRiskLabel') }}</span>
-                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand" :collapse-text="expandCollapse.collapse" rows="3"
+                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand"
+                            :collapse-text="expandCollapse.collapse" rows="3"
                             :content="currentGrowthAnomalyDetail?.patrol_points" />
                     </div>
                 </div>
                 <div class="card-wrap" v-else-if="recordType === 'pest'">
                     <div class="card-item">
                         <span class="item-label">{{ t('recordDetails.scienceLabel') }}</span>
-                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand" :collapse-text="expandCollapse.collapse" rows="3"
+                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand"
+                            :collapse-text="expandCollapse.collapse" rows="3"
                             :content="t('recordDetails.pestScience')" />
                     </div>
                     <div class="pest-classify-picker">
@@ -65,29 +70,33 @@
                     </div>
                     <div class="card-item">
                         <span class="item-label">{{ t('recordDetails.phenotypeLabel') }}</span>
-                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand" :collapse-text="expandCollapse.collapse" rows="3"
-                            :content="currentPestDetail?.phenotype" />
+                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand"
+                            :collapse-text="expandCollapse.collapse" rows="3" :content="currentPestDetail?.phenotype" />
                     </div>
                     <div class="card-item">
                         <span class="item-label">{{ t('recordDetails.highRiskLabel') }}</span>
-                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand" :collapse-text="expandCollapse.collapse" rows="3"
+                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand"
+                            :collapse-text="expandCollapse.collapse" rows="3"
                             :content="currentPestDetail?.patrol_points" />
                     </div>
                 </div>
                 <div class="card-wrap" v-else>
                     <div class="card-item">
                         <span class="item-label">{{ t('recordDetails.farmAnalysisLabel') }}</span>
-                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand" :collapse-text="expandCollapse.collapse" rows="3"
+                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand"
+                            :collapse-text="expandCollapse.collapse" rows="3"
                             :content="t('recordDetails.phenologyAnalysis')" />
                     </div>
                     <div class="card-item">
                         <span class="item-label">{{ t('recordDetails.patrolLabel') }}</span>
-                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand" :collapse-text="expandCollapse.collapse" rows="3"
+                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand"
+                            :collapse-text="expandCollapse.collapse" rows="3"
                             :content="workDetail.inspection_keypoints" />
                     </div>
                     <div class="card-item">
                         <span class="item-label">{{ t('recordDetails.phenotypeLabel') }}</span>
-                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand" :collapse-text="expandCollapse.collapse" rows="3"
+                        <text-ellipsis class="item-value" :expand-text="expandCollapse.expand"
+                            :collapse-text="expandCollapse.collapse" rows="3"
                             :content="t('recordDetails.phenologyPhenotype')" />
                     </div>
                 </div>
@@ -124,8 +133,7 @@
                         </div>
                         <div class="time-line">
                             <GrowthStageTimeline v-model="growthStageIndex" :stages="growthStages"
-                                @scroll-settled="onStageScrollSettled"
-                                @locale-change="getFindPhenologyInfo" />
+                                @scroll-settled="onStageScrollSettled" @locale-change="getFindPhenologyInfo" />
                             <!-- <GrowthStageTimeline v-model="growthStageIndex" :stages="growthStages" /> -->
                         </div>
                         <div class="confirm-btn-wrap">
@@ -142,8 +150,7 @@
                         </div>
                     </div>
                     <div class="phenology-track-section">
-                        <PhenologyTrackTimelineItem v-for="(row, idx) in displayPhenologyTrackList" :key="idx" :date="row.date"
-                            :content="row.content" :images="row.images" />
+                        <PhenologyTrackTimelineItem :abnormalType="recordTypeObj[recordType]" />
                     </div>
                 </div>
             </div>
@@ -199,12 +206,16 @@
 
         <UploadProgressPopup ref="uploadProgressPopupRef" v-model:show="showUploadProgressPopup"
             :popup-image-upload-loading="popupImageUploadLoading" :init-img-arr="initImgArr"
-            @cancel="handleCancelUploadPopup" @confirm="handleConfirmUpload" @handleUpload="handleUploadSuccess">
+            :confirm-text="t('recordDetails.confirmUpload')" :upload-required="false"
+            @reset="handleUploadPopupReset" @cancel="handleCancelUploadPopup" @confirm="handleConfirmUpload">
             <template #header>
                 <div class="upload-form">
-                    <div class="form-item aaa">
-                        <div class="item-label" style="font-size: 14px;color: #5A5A5A;">{{ t('recordDetails.abnormalCount') }}</div>
-                        <el-input v-model="formData.regionName" size="large" :placeholder="t('recordDetails.inputPlaceholder')">
+                    <div class="form-item special-input">
+                        <div class="item-label" style="font-size: 14px;color: #5A5A5A;">
+                            <span>{{ t('recordDetails.abnormalCount') }}</span>
+                        </div>
+                        <el-input v-model="formData.ratio" type="number" size="large"
+                            :placeholder="t('recordDetails.inputPlaceholder')">
                             <template #suffix>
                                 %
                             </template>
@@ -216,10 +227,11 @@
                 <div class="upload-form">
                     <div class="form-item">
                         <div class="item-label">{{ t('recordDetails.patrolDesc') }}</div>
-                        <el-input v-model="formData.regionName" size="large" :placeholder="t('recordDetails.inputDescPlaceholder')" />
+                        <el-input v-model="formData.regionName" size="large"
+                            :placeholder="t('recordDetails.inputDescPlaceholder')" />
                     </div>
-                    <div class="map-container" ref="mapContainer">
-                        <div class="tip">{{ t('recordDetails.drawTip') }}</div>
+                    <div class="map-container" ref="uploadMapContainer" @click="handleMapClick">
+                        <div class="tip" v-if="!hasMapConfirmGeometry">{{ t('recordDetails.drawTip') }}</div>
                     </div>
                 </div>
             </template>
@@ -232,8 +244,9 @@ import customHeader from "@/components/customHeader.vue";
 import PhenologyTrackTimelineItem from "@/components/pageComponents/PhenologyTrackTimelineItem.vue";
 import GrowthStageTimeline from "@/components/pageComponents/GrowthStageTimeline.vue";
 import UploadProgressPopup from "@/components/popup/UploadProgressPopup.vue";
-import { ref, onMounted, watch, nextTick, computed } from 'vue';
+import { ref, onActivated, watch, nextTick, computed } from 'vue';
 import { useRouter, useRoute } from 'vue-router';
+import { useStore } from 'vuex';
 import { useI18n } from '@/i18n';
 import { RECORD_KEY_MAP } from '@/i18n/recordTextMap';
 import { Uploader, TextEllipsis } from "vant";
@@ -245,6 +258,7 @@ import { Plus } from "@element-plus/icons-vue";
 
 const router = useRouter();
 const route = useRoute();
+const store = useStore();
 const { t } = useI18n();
 
 const TYPE_LEGACY_MAP = {
@@ -350,6 +364,7 @@ const handlePestDetailClick = (index) => {
 
 const indexMap = new IndexMap();
 const mapContainer = ref(null);
+const uploadMapContainer = ref(null);
 const location = ref(null);
 const input = ref('');
 
@@ -381,29 +396,96 @@ const currentStatusText = computed(() =>
     })
 );
 
+const DEFAULT_MAP_LOCATION = "POINT(113.6142086995688 23.585836479509055)";
+
+const mapConfirmPayload = computed(() => store.state.recordDetails.mapConfirmPayload);
+
+const hasMapConfirmGeometry = computed(() => {
+    const coordinates = mapConfirmPayload.value?.coordinates;
+    return Array.isArray(coordinates) && coordinates.length > 0;
+});
+
 const formData = ref({
+    ratio: '',
     regionName: '',
 });
+
+function destroyUploadMap() {
+    if (indexMap.kmap?.destroy) {
+        indexMap.kmap.destroy();
+    }
+    indexMap.kmap = null;
+}
+
+function ensureUploadMap() {
+    if (!uploadMapContainer.value) return false;
+    location.value = location.value || DEFAULT_MAP_LOCATION;
+    destroyUploadMap();
+    indexMap.initMap(location.value, uploadMapContainer.value, { editable: false, movable: false });
+    return Boolean(indexMap.kmap);
+}
+
+function resizeUploadMap() {
+    indexMap.kmap?.map?.updateSize?.();
+}
+
+/** 弹窗动画结束后再初始化,避免容器尺寸为 0 导致白屏 */
+function syncUploadMapView() {
+    const { coordinates } = mapConfirmPayload.value;
+    nextTick(() => {
+        setTimeout(() => {
+            if (!ensureUploadMap()) return;
+            resizeUploadMap();
+            if (Array.isArray(coordinates) && coordinates.length) {
+                indexMap.setAreaGeometry(coordinates);
+                resizeUploadMap();
+            }
+        }, 250);
+    });
+}
+
+function syncFormFromMapConfirmPayload() {
+    if (!showUploadProgressPopup.value) return;
+    syncUploadMapView();
+}
+
 const handleAbnormalRecord = () => {
+    formData.value.ratio = '';
+    formData.value.regionName = '';
     showUploadProgressPopup.value = true;
-    nextTick(() => {
-        location.value = "POINT(113.6142086995688 23.585836479509055)";
-        indexMap.initMap(location.value, mapContainer.value);
-    })
+};
+
+const handleMapClick = () => {
+    router.push({
+        name: 'MapManage', query: {
+            type: 'pest',
+        }
+    });
 }
 
 const showUploadProgressPopup = ref(false);
+
+watch(showUploadProgressPopup, (show) => {
+    if (show) {
+        syncUploadMapView();
+    } else {
+        destroyUploadMap();
+    }
+});
+
 const initImgArr = ref([]);
 const uploadProgressPopupRef = ref(null);
 const popupImageUploadLoading = ref(false);
 const uploadFileObj = new UploadFile();
 const miniUserId = localStorage.getItem("MINI_USER_ID");
 
-const beforeReadUpload = (file) => {
-    showUploadProgressPopup.value = true;
+const handleUploadPopupReset = () => {
     initImgArr.value = [];
     popupImageUploadLoading.value = false;
-    uploadProgressPopupRef.value?.uploadReset();
+};
+
+const beforeReadUpload = (file) => {
+    showUploadProgressPopup.value = true;
     return true;
 };
 
@@ -468,54 +550,62 @@ const afterReadUpload = async (data) => {
     }
 };
 
-const uploadData = ref([]);
-const handleUploadSuccess = (data) => {
-    uploadData.value = data.imgArr;
-};
-
 const handleCancelUploadPopup = () => {
     showUploadProgressPopup.value = false;
+    store.commit('recordDetails/CLEAR_MAP_CONFIRM_PAYLOAD');
 };
 
-const handleConfirmUpload = () => {
-    if (!uploadData.value || uploadData.value.length === 0) {
-        ElMessage.warning(t('recordDetails.uploadPhotoFirst'));
-        return;
-    }
-    showUploadProgressPopup.value = false;
+const recordTypeObj = {
+    phenology: 0,
+    pest: 1,
+    growth: 2,
 };
+function validateAbnormalRatio() {
+    const ratio = String(formData.value.ratio ?? '').trim();
+    if (ratio === '' || Number.isNaN(Number(ratio))) {
+        ElMessage.warning(t('recordDetails.ratioRequired'));
+        return false;
+    }
+    return true;
+}
 
-const handleUploadClick = () => { };
+const handleConfirmUpload = (imgArr) => {
+    if (!validateAbnormalRatio()) return;
 
-/** 物候跟踪时间轴原始数据,接入接口后可替换 */
-const phenologyTrackRawList = ref([]);
+    if (recordType.value === 'pest') {
+        const coordinates = mapConfirmPayload.value?.coordinates;
+        const hasDrawnMap = Array.isArray(coordinates) && coordinates.length > 0;
+        if (!hasDrawnMap) {
+            handleMapClick();
+            return;
+        }
+    }
 
-const formatTrackItemContent = (item) => {
-    const recordText = t(RECORD_KEY_MAP[item.record] || item.record);
-    const ratio = String(item.ratio ?? "").trim();
-    return ratio ? `${recordText}${ratio}%` : recordText;
+    const farmData = JSON.parse(localStorage.getItem('selectedFarmData'));
+    const params = {
+        farm_id: farmData.farm_id,
+        variety_code: farmData.farm_variety,
+        category_code: farmData.farm_category,
+        type: recordTypeObj[recordType.value],
+        time: new Date().toISOString().slice(0, 10),
+        zone_name: mapConfirmPayload.value.zone_name,
+        coordinates: mapConfirmPayload.value.coordinates[0],
+        "recog_type": "20",
+        image_urls: imgArr,
+        ...formData.value,
+    }
+    VE_API.record.writeFarmRecord(params).then(res => {
+        if (res.code === 200) {
+            showUploadProgressPopup.value = false;
+            store.commit('recordDetails/CLEAR_MAP_CONFIRM_PAYLOAD');
+            ElMessage.success(t('recordDetails.confirmSuccess'));
+        } else {
+            ElMessage.error(res.msg);
+        }
+    });
 };
 
-const displayPhenologyTrackList = computed(() =>
-    phenologyTrackRawList.value.map((item) => ({
-        date: item.time,
-        content: formatTrackItemContent(item),
-        images: item.images || [],
-    }))
-);
-// const phenologyTrackList = ref([
-//     { date: '04/18', content: '有 60%已经来花了', images: [] },
-//     {
-//         date: '04/18',
-//         content: '有 60% 进入 红黄叶 阶段',
-//         images: [
-//             'https://birdseye-img.sysuimars.com/birdseye-look-mini/custom-empty-image.png',
-//             'https://birdseye-img.sysuimars.com/birdseye-look-mini/custom-empty-image.png',
-//             'https://birdseye-img.sysuimars.com/birdseye-look-mini/custom-empty-image.png',
-//             'https://birdseye-img.sysuimars.com/birdseye-look-mini/custom-empty-image.png',
-//         ],
-//     },
-// ]);
+const handleUploadClick = () => { };
 
 const activeTab = ref(0);
 
@@ -546,7 +636,9 @@ function syncCurStageFromModel() {
 
 watch([growthStages, growthStageIndex], syncCurStageFromModel, { immediate: true });
 
-onMounted(() => {
+onActivated(() => {
+    sessionStorage.removeItem('mapManageConfirmPayload');
+    syncFormFromMapConfirmPayload();
     if (route.query.workId) {
         getWorkDetail();
     }
@@ -556,7 +648,6 @@ onMounted(() => {
     if (route.query.type === 'growth') {
         getGrowthAnomalyInfo();
     }
-    getFarmRecord()
     getFindPhenologyInfo()
     if (showMap.value) {
         location.value = "POINT(113.6142086995688 23.585836479509055)";
@@ -574,9 +665,11 @@ const getWorkDetail = async () => {
 
 // 获取生长异常列表
 const getGrowthAnomalyInfo = async () => {
+    const farmData = JSON.parse(localStorage.getItem('selectedFarmData'));
     const params = {
-        crop_type: JSON.parse(localStorage.getItem('selectedFarmData')).farm_variety,
+        crop_type: farmData.farm_variety,
         phenology_code: route.query.curCode,
+        farm_id: farmData.farm_id,
     }
     const res = await VE_API.record.growthAnomalyInfo(params);
     if (res.code === 200 && res.data?.length) {
@@ -588,103 +681,6 @@ const getGrowthAnomalyInfo = async () => {
     }
 }
 
-const getFarmRecord = async () => {
-    // const res = await VE_API.monitor.getFarmRecord();
-    const res = {
-        "code": 200,
-        "data": {
-            "abnormal": [
-                {
-                    "id": 2,
-                    "ratio": "5",
-                    "record": "果园出现蒂蛀虫为害果,占比",
-                    "time": "05-12"
-                },
-                {
-                    "id": 3,
-                    "ratio": "8",
-                    "record": "果园出现缺钙裂果症状,占比",
-                    "time": "05-12"
-                },
-                {
-                    "id": 4,
-                    "ratio": "",
-                    "record": "果园出现荔枝蝽象若虫为害",
-                    "time": "05-03"
-                },
-                {
-                    "id": 1,
-                    "ratio": "",
-                    "record": "果园出现花而不实现象",
-                    "time": "04-15"
-                }
-            ],
-            "farming": [
-                {
-                    "id": 2,
-                    "ratio": "",
-                    "record": "执行了膨果壮果追肥农事",
-                    "time": "05-07"
-                },
-                {
-                    "id": 3,
-                    "ratio": "",
-                    "record": "执行了蒂蛀虫防治农事",
-                    "time": "05-03"
-                },
-                {
-                    "id": 4,
-                    "ratio": "",
-                    "record": "执行了保花保果药剂喷施农事",
-                    "time": "04-10"
-                },
-                {
-                    "id": 1,
-                    "ratio": "",
-                    "record": "执行了花前病虫综合防治农事",
-                    "time": "03-15"
-                }
-            ],
-            "phenology": [
-                {
-                    "id": 2,
-                    "ratio": "65",
-                    "record": "果园出现果实迅速膨大,占比",
-                    "time": "05-10"
-                },
-                {
-                    "id": 3,
-                    "ratio": "80",
-                    "record": "果园出现谢花坐果,占比",
-                    "time": "04-25"
-                },
-                {
-                    "id": 4,
-                    "ratio": "",
-                    "record": "果园进入盛花期",
-                    "time": "03-20"
-                },
-                {
-                    "id": 1,
-                    "ratio": "100",
-                    "record": "果园出现花穗抽生",
-                    "time": "02-15"
-                }
-            ],
-            "polygon":[
-                "MULTIPOLYGON (((110.48938292603424 21.417825047316228, 110.4899730323636 21.417990023279287, 110.49068052543606 21.417796493784067, 110.49084867401376 21.41706679240906, 110.49043306149144 21.415388479246303, 110.48927822974991 21.415515383833224, 110.48873888525532 21.416276811355146, 110.48938292603424 21.417825047316228)))",
-                "MULTIPOLYGON (((110.48996192821238 21.41538768609246, 110.49012614274784 21.415411734511736, 110.4903759544273 21.415328199567455, 110.4905258604706 21.415064079395847, 110.4906400745989 21.41417654044062, 110.49084470824545 21.413217617655164, 110.49126349338252 21.41276314060309, 110.49141339942582 21.412484743665402, 110.49060914160583 21.412625131864786, 110.49000713797136 21.41311768029294, 110.48996192821238 21.41538768609246)))",
-                "MULTIPOLYGON (((110.49144762747073 21.417923243516555, 110.49146967053326 21.417909231135127, 110.49165526849174 21.41746823769546, 110.49183769383541 21.416946342581525, 110.49196777103703 21.416824196916536, 110.49210815923641 21.416644944187453, 110.4923865561741 21.416283266114533, 110.49282437699918 21.415975522491067, 110.49311308493458 21.415770095690846, 110.49327568143673 21.415638432181936, 110.49335023788149 21.41553373589761, 110.49344541632172 21.41515064267577, 110.49345255470479 21.414922214419164, 110.49277207842925 21.414636431237852, 110.49198459250442 21.414455575677493, 110.49168240095673 21.414377846617867, 110.49167605572734 21.414614206411272, 110.49148411253958 21.414625310562485, 110.4913199297302 21.414685590241334, 110.4913104118861 21.414970332408416, 110.49127789258574 21.415159896135265, 110.49120730190918 21.415339148864177, 110.49111212346895 21.41546288083657, 110.49093366389366 21.415717483164258, 110.49088924728812 21.415978430721168, 110.49090193774674 21.416193375365253, 110.49094714750589 21.41630679633994, 110.49102963548751 21.41653443144287, 110.49111767554473 21.4168675559838, 110.49114940169142 21.41712136515764, 110.49114860853774 21.41767498641832, 110.4913286544205 21.41780585677361, 110.49144762747073 21.417923243516555)))",
-                // "POLYGON ((110.48760606287757 21.42397019891643, 110.4878409330982 21.423999557694007, 110.48775285676547 21.423823405028536, 110.48760606287757 21.42397019891643))"
-            ]
-        }
-    }
-    if (res.code === 200 && res.data?.abnormal?.length) {
-        phenologyTrackRawList.value =
-            recordType.value === "phenology" ? res.data.phenology || [] : res.data.abnormal || [];
-    }
-}
-
 function normalizeTimeDescribe(raw) {
     if (Array.isArray(raw)) return raw;
     if (typeof raw === 'string' && raw.trim()) {
@@ -1087,7 +1083,8 @@ const getFindPhenologyInfo = async () => {
             }
         }
     }
-    .aaa{
+
+    .special-input {
         padding: 8px;
         background: rgba(33, 153, 248, 0.1);
         border-radius: 5px;

+ 20 - 3
src/views/old_mini/recordDetails/map/index.js

@@ -34,15 +34,32 @@ class IndexMap {
     });
   }
 
-  initMap(location, target) {
+  /**
+   * @param {string} location WKT 点位
+   * @param {HTMLElement} target 地图容器
+   * @param {{ editable?: boolean, movable?: boolean }} [options]
+   *   editable 是否允许点击地图修改点位,默认 true
+   *   movable 是否允许拖动/缩放地图,默认 true
+   */
+  initMap(location, target, options = {}) {
+    const { editable = true, movable = true } = options;
     let level = 16;
     let coordinate = util.wktCastGeom(location).getFirstCoordinate();
     this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 8, 22);
     let xyz2 = config.base_img_url3 + "map/lby/{z}/{x}/{y}.png";
     this.kmap.addXYZLayer(xyz2, { minZoom: 8, maxZoom: 22 }, 2);
     this.kmap.addLayer(this.clickPointLayer.layer);
-    this.setMapPoint(coordinate)
-    this.addMapSingerClick()
+    this.setMapPoint(coordinate);
+    if (editable) {
+      this.addMapSingerClick();
+    }
+    if (!movable && this.kmap?.setStates) {
+      this.kmap.setStates({
+        DoubleClickZoom: false,
+        DragAndDrop: false,
+        MouseWheelZoom: false,
+      });
+    }
   }
 
   setMapPosition(center) {

+ 11 - 1
src/views/old_mini/recordDetails/map/mapManage.js

@@ -119,12 +119,22 @@ class MapManage {
   }
 
   clearLayer() {
-    if (this.kmap?.draw && typeof this.kmap.draw.abortDrawing === "function") {
+    if (!this.kmap?.polygonLayer?.source) return;
+    if (this.kmap.draw && typeof this.kmap.draw.abortDrawing === "function") {
       this.kmap.draw.abortDrawing();
     }
     this.kmap.polygonLayer.source.clear();
   }
 
+  destroyMap() {
+    this.clearLayer();
+    if (this.kmap && typeof this.kmap.destroy === "function") {
+      this.kmap.destroy();
+    }
+    this.kmap = null;
+    this.regionDrawingActive = false;
+  }
+
   /**
    * 地图上全部已勾画地块:WKT 列表、每块亩数、合计亩数(亩换算与互动勾画页一致)
    */

+ 150 - 32
src/views/old_mini/recordDetails/mapManage.vue

@@ -4,10 +4,21 @@
         <div class="map-manage-content">
             <locationSearch class="location-search" @change="handleLocationChange"></locationSearch>
             <div class="map-container" ref="mapContainer"></div>
+            <div class="map-tip" v-if="recordType === 'pest'">{{ $t('建立您的专属病虫害地图档案,生成分区靶向治疗方案') }}</div>
             <div class="new-region-btn" v-show="!drawingEnabled" @click="onStartRegionDrawing">{{ $t('新建管理分区') }}</div>
             <div class="map-icon" v-show="drawingEnabled" @click="handleMapIconClick">
                 <img src="@/assets/img/map/map-icon.png" alt="">
             </div>
+            <div class="map-legend" v-if="recordType === 'pest'">
+                <div class="map-legend__item">
+                    <span class="map-legend__dot map-legend__dot--past"></span>
+                    <span class="map-legend__text">{{ $t('过往病虫害') }}</span>
+                </div>
+                <div class="map-legend__item">
+                    <span class="map-legend__dot map-legend__dot--zone"></span>
+                    <span class="map-legend__text">{{ $t('病虫害区域') }}</span>
+                </div>
+            </div>
         </div>
         <div class="custom-bottom-fixed-btns" :style="{ justifyContent: drawingEnabled ? 'space-between' : 'center' }">
             <template v-if="drawingEnabled">
@@ -18,9 +29,9 @@
         </div>
 
         <UploadProgressPopup ref="uploadProgressPopupRef" v-model:show="showUploadProgressPopup"
-            :popup-image-upload-loading="popupImageUploadLoading" :init-img-arr="initImgArr"
-            @cancel="handleCancelUploadPopup" @confirm="handleConfirmUpload" @handleUpload="handleUploadSuccess"
-            confirm-:text="$t('点击上传')">
+            :confirm-params="{ regionName: formData.regionName }"
+            :confirm-text="$t('点击上传')"
+            @cancel="handleCancelUploadPopup" @confirm="handleConfirmUpload">
             <template #header>
                 <div class="upload-form">
                     <div class="form-item">
@@ -41,6 +52,8 @@
                 </div>
             </template>
         </UploadProgressPopup>
+
+        <RegionNamePopup v-model:show="showRegionNamePopup" @confirm="handleRegionNameConfirm" />
     </div>
 </template>
 
@@ -49,23 +62,25 @@ import customHeader from "@/components/customHeader.vue";
 import locationSearch from "@/components/pageComponents/locationSearch.vue";
 import GrowthStageTimeline from "@/components/pageComponents/GrowthStageTimeline.vue";
 import UploadProgressPopup from "@/components/popup/UploadProgressPopup.vue";
+import RegionNamePopup from "./components/RegionNamePopup.vue";
 import MapManage from "./map/mapManage.js";
-import { ref, onMounted } from "vue";
+import { ref, onActivated, onDeactivated, computed, nextTick } from "vue";
 import { ElMessageBox, ElMessage } from "element-plus";
-import { useRouter } from "vue-router";
+import { useRouter, useRoute } from "vue-router";
+import { useStore } from "vuex";
 
 const router = useRouter();
-
+const route = useRoute();
+const store = useStore();
+const recordType = computed(() => route.query.type);
 const mapManage = new MapManage();
 
 const mapContainer = ref(null);
 const drawingEnabled = ref(false);
 
 const showUploadProgressPopup = ref(false);
-const initImgArr = ref([]);
+const showRegionNamePopup = ref(false);
 const uploadProgressPopupRef = ref(null);
-const popupImageUploadLoading = ref(false);
-const uploadData = ref([]);
 const formData = ref({
     regionName: '',
 });
@@ -117,15 +132,20 @@ const handleCancelUploadPopup = () => {
 };
 
 const handleConfirmUpload = () => {
-    if (!uploadData.value || uploadData.value.length === 0) {
-        ElMessage.warning("请先上传照片");
-        return;
-    }
     showUploadProgressPopup.value = false;
 };
 
-const handleUploadSuccess = (data) => {
-    uploadData.value = data.imgArr;
+const confirmPayload = ref({
+    zone_name: '',
+    coordinates: [],
+});
+
+const handleRegionNameConfirm = (regionName) => {
+    store.commit('recordDetails/SET_MAP_CONFIRM_PAYLOAD', {
+        zone_name: regionName,
+        coordinates: confirmPayload.value.coordinates,
+    });
+    router.back();
 };
 
 const onStartRegionDrawing = () => {
@@ -155,25 +175,74 @@ const handleClearDraw = () => {
 };
 
 const handleConfirmDraw = () => {
-    // const payload = mapManage.getAreaGeometry();
-    // if (!payload.geometryArr.length) {
-    //     ElMessage.warning("请先勾画地块");
-    //     return;
-    // }
-    // console.log("地块数据", payload);
-    // console.log("WKT 列表", payload.geometryArr);
-    // console.log("合计亩数", payload.mianji);
-    // console.log("分块明细", payload.parcels);
-    // router.back();
-    initImgArr.value = [];
-    popupImageUploadLoading.value = false;
-    uploadProgressPopupRef.value?.uploadReset?.();
-    showUploadProgressPopup.value = true;
+    if (recordType.value === 'pest') {
+        const payload = mapManage.getAreaGeometry();
+        if (!payload.geometryArr.length) {
+            ElMessage.warning("请先勾画地块");
+            return;
+        }
+        // console.log("地块数据", payload);
+        // console.log("WKT 列表", payload.geometryArr);
+        // console.log("合计亩数", payload.mianji);
+        // console.log("分块明细", payload.parcels);
+        confirmPayload.value.coordinates = payload.geometryArr;
+        showRegionNamePopup.value = true;
+    } else {
+        uploadProgressPopupRef.value?.uploadReset?.();
+        showUploadProgressPopup.value = true;
+    }
 };
 
-onMounted(() => {
-    mapManage.initMap("POINT(113.6142086995688 23.585836479509055)", mapContainer.value);
-}); 
+const DEFAULT_MAP_POINT = "POINT(113.6142086995688 23.585836479509055)";
+
+function initMapManageView() {
+    if (!mapContainer.value) return;
+    mapManage.destroyMap();
+    mapManage.initMap(DEFAULT_MAP_POINT, mapContainer.value);
+
+    const stored = store.state.recordDetails.mapConfirmPayload;
+    if (store.getters['recordDetails/hasMapConfirmPayload']) {
+        const coordinates = Array.isArray(stored.coordinates) ? [...stored.coordinates] : [];
+        confirmPayload.value = {
+            zone_name: stored.zone_name || '',
+            coordinates,
+        };
+        if (coordinates.length) {
+            mapManage.setAreaGeometry(coordinates);
+        }
+    } else {
+        mapManage.clearLayer();
+        confirmPayload.value = {
+            zone_name: '',
+            coordinates: [],
+        };
+    }
+
+    if (recordType.value === 'pest') {
+        onStartRegionDrawing();
+    }
+}
+
+onActivated(() => {
+    nextTick(() => initMapManageView());
+});
+
+onDeactivated(() => {
+    if (recordType.value === 'pest' && mapManage.kmap) {
+        const payload = mapManage.getAreaGeometry();
+        if (payload.geometryArr?.length) {
+            const zoneName = confirmPayload.value.zone_name
+                || store.state.recordDetails.mapConfirmPayload.zone_name;
+            store.commit('recordDetails/SET_MAP_CONFIRM_PAYLOAD', {
+                zone_name: zoneName,
+                coordinates: payload.geometryArr,
+            });
+            confirmPayload.value.coordinates = payload.geometryArr;
+        }
+    }
+    mapManage.destroyMap();
+    drawingEnabled.value = false;
+});
 </script>
 
 <style scoped lang="scss">
@@ -198,6 +267,22 @@ onMounted(() => {
             height: 100%;
         }
 
+        .map-tip {
+            position: absolute;
+            top: 80px;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            z-index: 2;
+            padding: 10px;
+            font-size: 12px;
+            color: #fff;
+            text-align: center;
+            background: rgba(0, 0, 0, 0.5);
+            border-radius: 25px;
+            box-sizing: border-box;
+            width: max-content;
+        }
+
         .map-icon {
             position: absolute;
             width: 35px;
@@ -228,6 +313,39 @@ onMounted(() => {
             border-radius: 25px;
             padding: 10px 24px;
         }
+
+        .map-legend {
+            position: absolute;
+            bottom: 16px;
+            left: 16px;
+            z-index: 2;
+            display: flex;
+            align-items: center;
+            gap: 20px;
+            padding: 6px 20px;
+            background: #fff;
+            border-radius: 25px;
+
+            &__item {
+                display: flex;
+                align-items: center;
+                gap: 5px;
+            }
+
+            &__dot {
+                width: 14px;
+                height: 14px;
+                border-radius: 50%;
+
+                &--past {
+                    background: rgba(100, 0, 0, 0.4);
+                }
+
+                &--zone {
+                    background: rgba(0, 0, 0, 0.57);
+                }
+            }
+        }
     }
 
     .custom-bottom-fixed-btns {

+ 1 - 1
src/views/old_mini/work_detail/index.vue

@@ -17,7 +17,7 @@
                         预计{{ detail?.executeDate || "--" }}执行,执行时间需巡园校准
                     </div> -->
                     <div class="status-sub">
-                        {{ $t('workDetail.certifiedSuccess') }}
+                        {{ farmData.best_time }}
                     </div>
                 </div>
             </div>