Jelajahi Sumber

feat:修改农情互动显示逻辑和图片显示逻辑

wangsisi 10 jam lalu
induk
melakukan
fdd0914b3d

+ 2 - 2
src/views/old_mini/interactionList/components/examplePopup.vue

@@ -129,8 +129,8 @@ const nextExample = () => {
 
         .example-img {
             width: 100%;
-            height: 375px;
-            object-fit: cover;
+            height: 400px;
+            object-fit: contain;
         }
 
         .img-tag {

+ 9 - 0
src/views/old_mini/interactionList/drawRegion.vue

@@ -61,6 +61,15 @@ onActivated(() => {
     // 先绘制地块
     const polygonData = route.query.polygonData;
 
+    const rangeWkt = route.query.rangeWkt;
+    if (rangeWkt) {
+        const regions = JSON.parse(rangeWkt)?.geometryArr.map(item => ({
+            geometry: item,
+            status: "resolved",
+            updatedTime: route.query.updatedTime,
+        }));
+        drawRegionMap.setStatusRegions(regions);
+    }
     if (polygonData) {
         drawRegionMap.setAreaGeometry(JSON.parse(polygonData)?.geometryArr);
     }

+ 218 - 61
src/views/old_mini/interactionList/index.vue

@@ -21,7 +21,7 @@
             <!-- 未上传状态内容 -->
             <div class="uploaded-content" v-show="item.questionStatus === 3 || item.expanded">
                 <div class="content-wrapper">
-                    <text-ellipsis class="item-desc" rows="2" :content="item.reason" expand-text="展开"
+                    <text-ellipsis class="item-desc" rows="2" :content="item.remark + item.reason" expand-text="展开"
                         collapse-text="收起" />
                     <div class="tip-box">如果不确定是否发生,直接上传照片即可</div>
                     <div class="example-wrapper">
@@ -32,14 +32,14 @@
                         </div>
                         <div class="example-list" v-if="item.exampleImagesJson.length > 0">
                             <div class="image-item-wrapper" v-for="(example, exIndex) in item.exampleImagesJson"
-                                :key="example" @click="showExample(item.exampleImagesJson, exIndex)">
+                                :key="example" @click="showExample(item,item.exampleImagesJson, exIndex)">
                                 <img class="image-item" :src="example" alt="" />
                             </div>
                         </div>
                     </div>
                     <text-ellipsis class="patrol-suggestion" rows="2" :content="'巡园建议:' + item.patrolSuggestion">
                         <template #action="{ expanded }"><span class="action-text">{{ expanded ? '收起' : '展开'
-                                }}</span></template>
+                        }}</span></template>
                     </text-ellipsis>
                 </div>
 
@@ -50,7 +50,7 @@
                     <!-- 图片展示 -->
                     <div class="uploaded-images">
                         <div class="uploaded-img-wrap" v-for="(image, imgIndex) in item.imagePaths" :key="image"
-                            @click="showExample(item.imagePaths.map(p => base_img_url2 + p), imgIndex, { hideLabel: true })">
+                            @click="showExample(item,item.imagePaths.map(p => base_img_url2 + p), imgIndex, { hideLabel: true })">
                             <img class="uploaded-img" :src="base_img_url2 + image" alt="" />
                             <span v-show="item.questionStatus === 3" class="uploaded-img-remove"
                                 @click.stop="removeUploadedImage(item, imgIndex)">×</span>
@@ -80,14 +80,18 @@
                 </uploader>
 
                 <div class="question-wrapper" v-if="item.imagePaths.length">
-                    <div class="question-text">
-                        <span class="text-title">{{ item.question }}</span>
-                        <el-input v-model="item.answerValues[0]" :disabled="item.questionStatus !== 3" type="number"
-                            style="width: 70px">
-                            <template #suffix>
-                                <span class="text-unit">{{ item.indicators[0]?.unit || '%' }}</span>
-                            </template>
-                        </el-input>
+                    <div class="question-cont">
+                        <div class="question-text"
+                            v-for="(q, qIdx) in (item.questionList || [item.question].filter(Boolean))" :key="qIdx">
+                            <span class="text-title">{{ q }}</span>
+                            <el-input v-model="item.answerValues[qIdx]" :disabled="item.questionStatus !== 3"
+                                type="number" style="width: 70px">
+                                <template #suffix>
+                                    <span class="text-unit">{{ item.indicators[qIdx]?.unit ?? item.indicators[0]?.unit
+                                        ?? '%' }}</span>
+                                </template>
+                            </el-input>
+                        </div>
                     </div>
                     <div class="draw-region-btn" v-if="item.interactionTypeId != 1 && item.questionStatus === 3"
                         @click="handleDrawRegion(item)">
@@ -114,9 +118,10 @@
             <div class="proportion-info" v-show="item.questionStatus !== 3"
                 :style="{ justifyContent: item.expanded ? 'center' : 'space-between' }">
                 <template v-if="!item.expanded">
-                    <span class="proportion-text">{{ item.question }}{{
-                        item.answerValues[0] || 0
-                        }}{{ item.indicators[0]?.unit || '%' }}</span>
+                    <span class="proportion-text">{{(item.questionList && item.questionList.length
+                        ? item.questionList.map((q, i) => q + (item.answerValues[i] ?? '') + (item.indicators[i]?.unit
+                            ?? item.indicators[0]?.unit ?? '%')).join('')
+                        : item.question + (item.answerValues[0] ?? '') + (item.indicators[0]?.unit ?? '%')) }}</span>
                 </template>
                 <div class="toggle-btn" @click="toggleExpand(item)">
                     <span>{{ item.expanded ? "收起" : "展开" }}</span>
@@ -141,8 +146,8 @@
     <drone-consult-popup v-model:show="showDroneConsultPopup" @copy="onCopyWechatId" />
 
     <!-- 示例照片轮播组件 -->
-    <example-popup v-model:show="showExamplePopup" :images="exampleList" :start-index="exampleStartIndex"
-        title="蒂蛀虫示例图" :show-title-and-tips="exampleShowTitleAndTips" />
+    <example-popup v-model:show="showExamplePopup" :images="exampleList" :tips="exampleTips" :start-index="exampleStartIndex" :title="exampleTitle"
+        :show-title-and-tips="exampleShowTitleAndTips" />
     <!-- 照片上传进度 -->
     <popup v-model:show="showUploadProgressPopup" round class="upload-progress-popup">
         <div class="upload-progress-title">
@@ -155,11 +160,14 @@
             </upload>
         </div>
         <div class="input-box">
-            <div class="input-item">
-                <span class="label-text">{{ currentItem.question }}</span>
-                <el-input class="label-input" v-model="answerValue" placeholder="请输入" type="number">
+            <div class="input-item"
+                v-for="(q, qIdx) in (currentItem?.questionList?.length ? currentItem.questionList : (currentItem?.question ? [currentItem.question] : []))"
+                :key="qIdx">
+                <span class="label-text">{{ q }}</span>
+                <el-input class="label-input" v-model="currentItem.answerValues[qIdx]" placeholder="请输入" type="number">
                     <template #suffix>
-                        <span class="unit">{{ currentItem.indicators[0]?.unit || '%' }}</span>
+                        <span class="unit">{{ currentItem.indicators[qIdx]?.unit ?? currentItem.indicators[0]?.unit ??
+                            '%' }}</span>
                     </template>
                 </el-input>
             </div>
@@ -167,7 +175,7 @@
         </div>
         <template v-if="currentItem.interactionTypeId != 1">
             <div class="region-tips">勾画新发生区域,精准匹配专属农事方案</div>
-            <div class="region-map" ref="mapContainer" @click="handleDrawRegion">
+            <div class="region-map" ref="mapContainer" @click="handleDrawRegion(currentItem)">
                 <div class="region-map-text">点击勾画新发生区域</div>
             </div>
         </template>
@@ -181,7 +189,7 @@
         请点击查看" buttonText="查看报告" @confirm="handleBtn" :zIndex="9999" />
 </template>
 <script setup>
-import { ref, onMounted, onActivated, computed, onDeactivated } from "vue";
+import { ref, onMounted, onActivated, computed, onUnmounted } from "vue";
 import { ElMessage } from "element-plus";
 import { Uploader, Popup, TextEllipsis } from "vant";
 import customHeader from "@/components/customHeader.vue";
@@ -220,7 +228,6 @@ const query = ref(useRoute().query);
 const morePopupRef = ref(null);
 //照片上传进度
 const showUploadProgressPopup = ref(false);
-const answerValue = ref('');
 // 上传进度统计
 const totalUploadCount = ref(0); // 本次选择的总文件数(固定不随上传成功变化)
 const uploadedSuccessCount = ref(0); // 已上传成功的文件数
@@ -236,29 +243,93 @@ const format = () => {
 const drawRegionMap = new DrawRegionMap();
 const mapContainer = ref(null);
 
-// 从 sessionStorage 中回显已勾画的区域
 const renderRegionFromSession = () => {
+    if (!drawRegionMap.kmap) return;
+
+    // 先清空图层
+    drawRegionMap.clearLayer && drawRegionMap.clearLayer();
+
+    // 1)通过保存的互动项 ID,在 listData 中找到对应项,用接口 rangeWkt 回显只读区域
+    const savedId = sessionStorage.getItem("drawRegionInteractionId");
+    if (savedId && Array.isArray(listData.value) && listData.value.length > 0) {
+        const targetItem = listData.value.find(
+            (it) =>
+                it &&
+                it.id != null &&
+                (String(it.id) === savedId || Number(it.id) === Number(savedId))
+        );
+        if (targetItem && targetItem.rangeWkt && targetItem.rangeWkt.length > 10) {
+            // 使用 setStatusRegions 显示接口返回的区域
+            renderRegionFromItemWkt(targetItem);
+        }
+    }
+
+    // 2)再回显本地勾画的多边形到可编辑图层(两者可共存)
     const polygonStr = sessionStorage.getItem("drawRegionPolygonData");
-    if (!polygonStr) return;
-    try {
-        const polygonData = JSON.parse(polygonStr);
-        if (polygonData && Array.isArray(polygonData.geometryArr) && polygonData.geometryArr.length > 0) {
-            // 先清空原有图层,再回显
-            drawRegionMap.clearLayer && drawRegionMap.clearLayer();
-            // needFitView 设为 true,让视图适配当前地块;同时传入面积用于只读模式展示“XX亩”
-            drawRegionMap.setAreaGeometry(polygonData.geometryArr, true, polygonData.mianji);
+    if (polygonStr) {
+        try {
+            const polygonData = JSON.parse(polygonStr);
+            if (
+                polygonData &&
+                Array.isArray(polygonData.geometryArr) &&
+                polygonData.geometryArr.length > 0 &&
+                drawRegionMap.kmap &&
+                drawRegionMap.kmap.polygonLayer
+            ) {
+                // 注意这里不再调用 clearLayer,避免清掉上一步 setStatusRegions 的只读区域
+                // 此处也不单独做 fit,由下方统一调用 fitAllRegions 适配所有图层
+                drawRegionMap.setAreaGeometry(polygonData.geometryArr, false, polygonData.mianji);
+            }
+        } catch (e) {
+            console.error("解析 drawRegionPolygonData 失败:", e);
+        }
+    }
+
+    // 3)统一根据只读图层 + 可编辑图层的范围自适应视图,保证所有区域都在视野内
+    if (drawRegionMap.fitAllRegions) {
+        drawRegionMap.fitAllRegions();
+    }
+};
+
+// 用当前项的 rangeWkt(接口返回的已保存区域)回显到地图;使用 setStatusRegions 方法显示
+const renderRegionFromItemWkt = (item) => {
+    const raw = item?.rangeWkt;
+    if (!raw || typeof raw !== 'string' || raw.length <= 10) return;
+    if (!drawRegionMap.kmap || !drawRegionMap.staticRegionLayer) return;
+    let geometryArr = [];
+    const trimmed = raw.trim();
+    if (trimmed.startsWith('{')) {
+        try {
+            const parsed = JSON.parse(raw);
+            if (parsed && Array.isArray(parsed.geometryArr) && parsed.geometryArr.length > 0) {
+                geometryArr = parsed.geometryArr;
+            }
+        } catch (e) {
+            console.error("解析 rangeWkt JSON 失败:", e);
+            return;
+        }
+    } else {
+        geometryArr = [raw];
+    }
+    if (geometryArr.length > 0) {
+        // 转换为 setStatusRegions 需要的格式:[{ geometry: wkt, status?: 'resolved' | 'unresolved' }]
+        const regions = geometryArr
+            .filter(wkt => wkt && typeof wkt === 'string' && wkt.trim().length > 10)
+            .map(wkt => ({
+                geometry: wkt.trim(),
+                status: 'resolved' // 接口返回的区域默认显示为未解决状态
+            }));
+        if (regions.length > 0) {
+            drawRegionMap.setStatusRegions(regions);
         }
-    } catch (e) {
-        console.error("解析 drawRegionPolygonData 失败:", e);
     }
 };
 
 //
 const urgentType = {
-    "0": "普通",
+    "0": "一般紧急",
     "1": "紧急",
     "2": "非常紧急",
-    null: "未设置",
 }
 
 const uploadFileObj = new UploadFile();
@@ -276,8 +347,14 @@ const afterReadUpload = async (data) => {
     }
     totalUploadCount.value = data.length;
     uploadedSuccessCount.value = 0;
-    drawRegionMap.clearLayer && drawRegionMap.clearLayer();
-    sessionStorage.removeItem("drawRegionPolygonData");
+    // 仅当「编辑发生区域」和本次上传是同一项时保留勾画数据并回显;否则清除,避免数据串
+    const savedInteractionId = sessionStorage.getItem("drawRegionInteractionId");
+    const isSameItem = savedInteractionId != null && String(currentItem.value?.id) === savedInteractionId;
+    if (!isSameItem) {
+        drawRegionMap.clearLayer && drawRegionMap.clearLayer();
+        sessionStorage.removeItem("drawRegionPolygonData");
+        sessionStorage.removeItem("drawRegionInteractionId");
+    }
 
     for (let file of data) {
         // 将文件上传至服务器
@@ -303,7 +380,7 @@ const afterReadUpload = async (data) => {
     showUploadProgressPopup.value = true;
 
     // 所有文件上传结束后再打开进度弹窗,此时 imgArr 已包含全部图片
-    if (initImgArr.value.length > 0 && currentItem.value.interactionTypeId != 1) {
+    if (initImgArr.value.length > 0 && currentItem.value.interactionTypeId != 1 && currentItem.value.questionStatus === 3) {
         setTimeout(() => {
             // 只在第一次时初始化地图,后续复用已有实例
             if (!drawRegionMap.kmap) {
@@ -315,8 +392,50 @@ const afterReadUpload = async (data) => {
                     false
                 );
             }
-            // 每次打开弹窗都尝试根据 sessionStorage 回显最新地块
-            renderRegionFromSession();
+            // 先清空所有图层,再按规则分别回显:接口 rangeWkt 用 setStatusRegions,本地 session 用 setAreaGeometry,可共存
+            drawRegionMap.clearLayer && drawRegionMap.clearLayer();
+            const savedId = sessionStorage.getItem("drawRegionInteractionId");
+            const currentId = currentItem.value?.id;
+            const isSameItem =
+                savedId != null &&
+                currentId != null &&
+                (String(currentId) === savedId || Number(currentId) === Number(savedId));
+            const hasSessionPolygon = !!sessionStorage.getItem("drawRegionPolygonData");
+
+            // 1)接口返回的 rangeWkt:始终用 setStatusRegions 渲染到只读图层
+            if (currentItem.value?.rangeWkt?.length > 10) {
+                renderRegionFromItemWkt(currentItem.value);
+            }
+
+            // 2)本地刚编辑的多边形(同一项且有 session 数据):用 setAreaGeometry 渲染到可编辑图层
+            if (isSameItem && hasSessionPolygon) {
+                const polygonStr = sessionStorage.getItem("drawRegionPolygonData");
+                if (polygonStr) {
+                    try {
+                        const polygonData = JSON.parse(polygonStr);
+                        if (
+                            polygonData &&
+                            Array.isArray(polygonData.geometryArr) &&
+                            polygonData.geometryArr.length > 0 &&
+                            drawRegionMap.kmap &&
+                            drawRegionMap.kmap.polygonLayer
+                        ) {
+                            // 此处不再调用 clearLayer,避免清掉上一步 setStatusRegions 的只读区域
+                            drawRegionMap.setAreaGeometry(
+                                polygonData.geometryArr,
+                                true,
+                                polygonData.mianji
+                            );
+                        }
+                    } catch (e) {
+                        console.error("解析 drawRegionPolygonData 失败:", e);
+                    }
+                }
+            }
+            if (drawRegionMap.fitAllRegions) {
+                drawRegionMap.fitAllRegions();
+            }
+            // 3)其它情况:保持清空后的空地图
         }, 100);
     }
 };
@@ -324,16 +443,21 @@ const afterReadUpload = async (data) => {
 const currentItem = ref(null);
 const handleUploadClick = (item) => {
     currentItem.value = item;
-    answerValue.value = item.answerValues[0] || '';
 };
+
 // 示例照片轮播
 const showExamplePopup = ref(false);
 const exampleList = ref([]);
 const exampleStartIndex = ref(0);
 const exampleShowTitleAndTips = ref(true);
-const showExample = (list, index, options = {}) => {
+const exampleTitle = ref('')
+const exampleTips = ref('')
+const showExample = (item, list, index, options = {}) => {
+    console.log("list", item);
+    exampleTitle.value = item.exampleImageAnnotation;
     exampleList.value = list || [];
     exampleStartIndex.value = index || 0;
+    exampleTips.value = item.shootingMethod;
     exampleShowTitleAndTips.value = options.hideLabel !== true;
     showExamplePopup.value = true;
 };
@@ -361,6 +485,21 @@ const loadData = async () => {
             } else {
                 item.exampleImagesJson = [];
             }
+            // question 按 || 切割成数组,用于循环渲染
+            const questionStr = item.question != null ? String(item.question) : '';
+            item.questionList = questionStr
+                ? questionStr.split('||').map(s => s.trim()).filter(Boolean)
+                : [];
+            if (!item.questionList.length && questionStr !== '') {
+                item.questionList = [questionStr];
+            }
+            // 保证 answerValues 与 questionList 长度一致,便于每项对应一个输入框
+            if (!Array.isArray(item.answerValues)) {
+                item.answerValues = [];
+            }
+            while (item.answerValues.length < item.questionList.length) {
+                item.answerValues.push('');
+            }
             return {
                 ...item
             };
@@ -386,8 +525,13 @@ const removeUploadedImage = (item, imgIndex) => {
 // 确认上传 / 暂未到达进程
 const handleConfirm = async (item, isConfirm) => {
     if (isConfirm) {
-        if (item.answerValues[0] === '' || item.answerValues[0] === null || item.answerValues[0] === undefined) {
-            ElMessage.warning("请输入当前果园比例");
+        const list = item.questionList || [];
+        const needFill = list.length || 1;
+        const hasEmpty = Array.from({ length: needFill }, (_, i) => i).some(
+            (i) => item.answerValues[i] === '' || item.answerValues[i] === null || item.answerValues[i] === undefined
+        );
+        if (hasEmpty) {
+            ElMessage.warning("请填写当前果园比例");
             return;
         }
         if (item.imagePaths.length === 0 && uploadData.value.length === 0) {
@@ -422,25 +566,31 @@ const handleConfirmUpload = async () => {
         ElMessage.warning("请先上传照片");
         return;
     }
-    // 校验是否填写了输入框内容(百分比)
-    if (answerValue.value === '' || answerValue.value === null || answerValue.value === undefined) {
-        ElMessage.warning("请输入占比数值");
+    const item = currentItem.value;
+    const len = item?.questionList?.length || 1;
+    const hasEmpty = Array.from({ length: len }, (_, i) => i).some(
+        (i) => item.answerValues[i] === '' || item.answerValues[i] === null || item.answerValues[i] === undefined
+    );
+    if (hasEmpty) {
+        ElMessage.warning("请填写占比数值");
         return;
     }
+    const answerVals = (item.answerValues || []).slice(0, len);
     const parmas = {
-        interactionId: currentItem.value.id,
+        interactionId: item.id,
         farmId: localStorage.getItem("selectedFarmId"),
         imagePaths: uploadData.value,
         rangeWkt: sessionStorage.getItem("drawRegionPolygonData") || '',
-        replyText: currentItem.value.replyText,
-        answerValues: [answerValue.value],
+        replyText: item.replyText,
+        answerValues: answerVals,
     }
     const { code, msg } = await VE_API.home.uploadAnswerData(parmas);
     if (code === 0) {
         ElMessage.success("确认成功");
-        // 清空上传数据
+        // 清空上传数据及勾画关联
         uploadData.value = [];
         sessionStorage.removeItem("drawRegionPolygonData");
+        sessionStorage.removeItem("drawRegionInteractionId");
         showUploadProgressPopup.value = false;
         // 刷新列表
         await refreshList();
@@ -455,13 +605,16 @@ const toggleExpand = (item) => {
 };
 
 const handleDrawRegion = (item) => {
+    if (sessionStorage.getItem("drawRegionInteractionId") != item.id) {
+        sessionStorage.removeItem("drawRegionPolygonData");
+        sessionStorage.removeItem("drawRegionInteractionId");
+    }
+    // 记录本次勾画对应的互动项 id,上传时若是同一项则保留回显,否则清除避免数据串
+    sessionStorage.setItem("drawRegionInteractionId", String(item.id));
     const polygonData = sessionStorage.getItem("drawRegionPolygonData");
+
     if (item.rangeWkt && item.rangeWkt.length > 10) {
-        if (polygonData) {
-            router.push(`/draw_region?polygonData=${polygonData}`);
-        } else {
-            router.push(`/draw_region?polygonData=${item.rangeWkt}`);
-        }
+        router.push(`/draw_region?polygonData=${polygonData}&rangeWkt=${item.rangeWkt}&updatedTime=${item.updatedTime.slice(0,10)}`);
     } else {
         if (polygonData) {
             router.push(`/draw_region?polygonData=${polygonData}`);
@@ -472,8 +625,9 @@ const handleDrawRegion = (item) => {
 
 };
 
-onDeactivated(() => {
+onUnmounted(() => {
     sessionStorage.removeItem("drawRegionPolygonData");
+    sessionStorage.removeItem("drawRegionInteractionId");
 });
 
 const uploadData = ref([]);
@@ -844,13 +998,12 @@ const handleSubmitAll = () => {
 
     .question-wrapper {
         display: flex;
-        align-items: center;
+        align-items: flex-start;
         justify-content: space-between;
         gap: 10px;
         margin: 12px 0 6px;
         box-sizing: border-box;
         width: 100%;
-
         .question-text {
             background: #ffffff;
             color: #6f6f6f;
@@ -866,6 +1019,10 @@ const handleSubmitAll = () => {
             }
         }
 
+        .question-text + .question-text {
+            margin-top: 6px;
+        }
+
         .draw-region-btn {
             background: rgba(33, 153, 248, 0.1);
             border-radius: 4px;

+ 49 - 5
src/views/old_mini/interactionList/map/drawRegionMap.js

@@ -64,7 +64,7 @@ class DrawRegionMap {
 
                 const text2 = new Style({
                     text: new Text({
-                        text: "发现时间:2025.05.06",
+                        text: `发现时间:${f.get("updatedTime")}`,
                         font: "12px sans-serif",
                         offsetY: -24,
                         fill: new Fill({ color: "#ffffff" }),
@@ -277,16 +277,60 @@ class DrawRegionMap {
                 });
                 const feature = new Feature({ geometry });
                 feature.set("status", region.status || "unresolved");
+                feature.set("updatedTime", region.updatedTime);
                 this.staticRegionLayer.addFeature(feature);
             } catch (e) {
                 // 单个区域解析失败时忽略
             }
         });
 
-        // 自动缩放到这些区域
-        if (this.staticRegionLayer.source.getFeatures().length > 0) {
-            const extent = this.staticRegionLayer.source.getExtent();
-            this.kmap.getView().fit(extent, { duration: 500, padding: [100, 100, 100, 100] });
+        // 不在这里直接缩放,避免只按某一层适配视图;
+        // 由调用方在需要时统一调用 fitAllRegions 进行视图自适应
+    }
+
+    /**
+     * 视图自适应到「只读区域图层 + 可编辑多边形图层」的联合范围
+     * 适用于同时存在接口返回区域和本地勾画区域时,保证都能出现在视野内
+     */
+    fitAllRegions() {
+        if (!this.kmap) return;
+
+        const extents = [];
+
+        // 只读状态区域图层范围
+        if (this.staticRegionLayer && this.staticRegionLayer.source) {
+            const features = this.staticRegionLayer.source.getFeatures();
+            if (features && features.length > 0) {
+                extents.push(this.staticRegionLayer.source.getExtent());
+            }
+        }
+
+        // 可编辑 polygon 图层范围
+        if (this.kmap.polygonLayer && this.kmap.polygonLayer.source) {
+            const features = this.kmap.polygonLayer.source.getFeatures();
+            if (features && features.length > 0) {
+                extents.push(this.kmap.polygonLayer.source.getExtent());
+            }
+        }
+
+        if (extents.length === 0) return;
+
+        // 计算所有范围的并集 [minX, minY, maxX, maxY]
+        const merged = extents.reduce(
+            (acc, cur) => {
+                if (!acc) return cur.slice();
+                return [
+                    Math.min(acc[0], cur[0]),
+                    Math.min(acc[1], cur[1]),
+                    Math.max(acc[2], cur[2]),
+                    Math.max(acc[3], cur[3]),
+                ];
+            },
+            null
+        );
+
+        if (merged) {
+            this.kmap.getView().fit(merged, { duration: 500, padding: [100, 100, 100, 100] });
         }
     }
 }