吉安感知网项目-前端
罗广辉
2026-01-21 b4d75c3917bab54f677dab0570ff028c0609a99a
feat: flyVisual
18 files modified
418 ■■■■■ changed files
applications/drone-command/src/components/PlaybackVideo/components/MapContainer.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/mapContainer.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/hooks/useRouteLine/useRouteLine.js 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/DrawPolygon.js 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/mapUtil.js 61 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/job/components/DeviceJobDetailsMap.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/layerManagement/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/monitor/log/flightLog.vue 4 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue 6 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/resource/components/spotDetails.vue 92 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/components/PlaybackVideo/components/MapContainer.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/components/map-container/mapContainer.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/hooks/useRouteLine/useRouteLine.js 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/utils/cesium/DrawPolygon.js 3 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/utils/cesium/mapUtil.js 59 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue 7 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/resource/components/spotDetails.vue 92 ●●●● patch | view | raw | blame | history
packages/utils/map/index.js 75 ●●●● patch | view | raw | blame | history
applications/drone-command/src/components/PlaybackVideo/components/MapContainer.vue
@@ -15,7 +15,7 @@
import PlanarRouteLineList from '@/components/PlanarRouteLineList/PlanarRouteLineList.vue'
import { flyVisual } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
import * as Cesium from 'cesium'
import { PublicCesium } from '@/utils/cesium/publicCesium'
applications/drone-command/src/components/map-container/mapContainer.vue
@@ -30,7 +30,7 @@
import { Cartesian3, Terrain, Viewer } from 'cesium';
import { PublicCesium } from '@/utils/cesium/publicCesium';
import ImageTrailMaterial from '@/utils/cesium/ImageTrailMaterial';
import { flyVisual } from '@/utils/cesium/mapUtil';
import { flyVisual } from '@ztzf/utils';
import * as turf from '@turf/turf';
import { nextTick, onBeforeUnmount, onMounted, onUnmounted } from 'vue';
applications/drone-command/src/hooks/useRouteLine/useRouteLine.js
@@ -11,7 +11,7 @@
import endPointImg from '@/assets/images/EndPointicon.png'
import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
import { flyVisual } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
import { useStore } from 'vuex'
applications/drone-command/src/utils/cesium/DrawPolygon.js
@@ -2,7 +2,8 @@
import * as turf from '@turf/turf'
import { boxTransformScale } from '@/utils/turfFunc'
import { ElMessage } from 'element-plus'
import { flyVisual, getPointPositionsHeight } from '@/utils/cesium/mapUtil'
import { getPointPositionsHeight } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
/**
 * 多边形绘制与编辑工具类
applications/drone-command/src/utils/cesium/mapUtil.js
@@ -449,67 +449,6 @@
    }
}
/**
 * 飞到中心点并且所有点在可视范围
 *  @param positionsData 二维数组,每项为 [lon, lat] 或 三维数组 [lon, lat, height]
 *  @param viewer Cesium.Viewer 实例
 *  @param multiple 缩放倍数-默认为4
 *  @param pitch 俯仰角-默认为-90
 */
export function flyVisual({ positionsData, viewer, multiple = 4, pitch = -90 }) {
    if (!Array.isArray(positionsData) || positionsData.length === 0) return
    // 如果是一个点,加两个点方便后续生成外包围盒
    if (positionsData.length === 1) {
        const [lon, lat, height = 0] = positionsData[0]
        positionsData = [
            [lon + 0.001, lat + 0.001, height],
            [lon - 0.001, lat - 0.001, height],
            [lon, lat, height],
        ]
    }
    const positions = positionsData.map(([lon, lat, height = 0]) =>
        Cesium.Cartesian3.fromDegrees(Number(lon), Number(lat), Number(height || 0))
    )
    // 计算最高高度和中心点经纬度
    let maxHeight = -Infinity
    let sumLon = 0,
        sumLat = 0
    for (const [lon, lat, height] of positionsData) {
        sumLon += Number(lon)
        sumLat += Number(lat)
        maxHeight = Math.max(maxHeight, Number(height || 0))
    }
    const centerLon = sumLon / positionsData.length
    const centerLat = sumLat / positionsData.length
    const boundingSphere = Cesium.BoundingSphere.fromPoints(positions)
    // 暂时飞向 BoundingSphere
    viewer.camera.flyToBoundingSphere(Cesium.BoundingSphere.fromPoints(positions), {
        duration: 0,
        offset: {
            heading: Cesium.Math.toRadians(0),
            pitch: Cesium.Math.toRadians(pitch),
            range: boundingSphere.radius * multiple,
        },
        complete: () => {
            // 飞行完成后检查相机高度是否小于最高点
            const cameraHeight = Cesium.Cartographic.fromCartesian(viewer.camera.position).height
            if (cameraHeight < maxHeight) {
                viewer.camera.flyTo({
                    destination: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, maxHeight + 100), // 加100米缓冲
                    duration: 1.5,
                    orientation: {
                        heading: viewer.camera.heading,
                        pitch: viewer.camera.pitch,
                        roll: viewer.camera.roll,
                    },
                })
            }
        },
    })
}
// 获取视口地图中心点
export function getMapCenterPoint(viewer) {
    const centerResult = viewer.camera.pickEllipsoid(
applications/drone-command/src/views/job/components/DeviceJobDetailsMap.vue
@@ -2,7 +2,7 @@
    <div id="deviceJobDetailsMap" class="command-cesium"></div>
</template>
<script setup>
import { flyVisual } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
import _ from 'lodash'
import * as Cesium from 'cesium'
applications/drone-command/src/views/layerManagement/index.vue
@@ -57,7 +57,7 @@
import { dataFolderApi } from '@/api/layer/index';
import folderFile from '@/views/layerManagement/components/folderFile.vue';
import { parseGeoDataToPositions } from '@/utils/geoParseUtil';
import { flyVisual } from '@/utils/cesium/mapUtil';
import { flyVisual } from '@ztzf/utils';
import rightEdit from '@/views/layerManagement/components/rightEdit.vue';
import leftList from '@/views/layerManagement/components/leftList.vue';
import { DrawPolygon } from '@/views/layerManagement/components/utils';
applications/drone-command/src/views/monitor/log/flightLog.vue
@@ -123,7 +123,7 @@
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
import {Delete, Refresh, Search} from '@element-plus/icons-vue';
import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
import { flyVisual } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
import rwqfdImg from '@/assets/images/signMachineNest/rwqfd.png'
import endPointImg from '@/assets/images/EndPointicon.png'
import { nextTick } from 'vue';
@@ -526,4 +526,4 @@
  height: 500px;
  width: 100%;
}
</style>
</style>
applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
@@ -14,10 +14,10 @@
            <div class="right-container">
                <div class="header">
                    <span>{{ dialogTitle }}</span>
                    <el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon>
                </div>
                <div class="content">
                    <div class="detail-title">轨迹信息</div>
                    <el-row>
@@ -71,7 +71,7 @@
import { fwDroneFlightRecordDetailPageApi } from '@/views/recordManage/historyTracks/flyTrajectory'
import { PublicCesium } from '@/utils/cesium/publicCesium'
import * as Cesium from 'cesium'
import { flyVisual } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
import droneIcon from '@/assets/images/dataCockpit/map/drone.png'
applications/drone-command/src/views/resource/components/spotDetails.vue
@@ -61,24 +61,24 @@
        <div class="table-overlay">
          <div class="table-content">
            <div class="tabname">图斑列表</div>
       <div class="tabBoxLoading"
       <div class="tabBoxLoading"
        v-loading="tableLoading"
            element-loading-text="加载中..."
            element-loading-text="加载中..."
            element-loading-background="rgba(0, 0, 0, 0.1)"
            >
             <el-table
             v-if="tableData.length > 0"
              ref="polygonTableEle"
              highlight-current-row
              :row-class-name="tableRowClassName"
              :data="tableData"
              @row-click="handleLocationPolygon"
            >
              <el-table-column type="index" align="center" :width="pxToRemNum(30)" label="序号">
               <template #default="{ $index }">
                {{ ($index + 1).toString().padStart(2, '0') }}
                {{ ($index + 1).toString().padStart(2, '0') }}
              </template>
              </el-table-column>
              <el-table-column prop="dkbh" align="center"  label="图斑名称" show-overflow-tooltip />
@@ -99,25 +99,25 @@
                    {{ isEditing && selectionIds === scope.row.id ? '取消编辑' : '编辑' }}
                  </span> -->
               <template v-if="scope.row.is_exception == 2">
                    <el-button
                    <el-button
                      v-if="!isEditing || selectionIds !== scope.row.id"
                      icon="el-icon-edit"
                      link
                      icon="el-icon-edit"
                      link
                      @click.stop="handleSelectionChange(scope.row)"
                    />
                    <el-button
                    <el-button
                      v-else
                      icon="el-icon-circle-close"
                      link
                      icon="el-icon-circle-close"
                      link
                      @click.stop="handleSelectionChange(scope.row)"
                    />
                  </template>
                </template>
              </el-table-column>
            </el-table>
       </div>
          </div>
        </div>
        <!--绘制按钮-->
@@ -126,7 +126,7 @@
          ref="drawPolygonRef"
          v-if="isEditing"
          @upDateDrawState="handleUpDateDrawState"
        />
        <!-- 完成/取消 -->
        <div class="btnGroups" v-if="props.title === '图斑编辑'">
@@ -141,7 +141,7 @@
</template>
<script setup>
import { flyVisual } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
import { pxToRem, pxToRemNum } from '@/utils/rem'
import DrawPolygon from '@/views/resource/components/DrawPolygon.vue';
import { ElMessage, ElMessageBox } from 'element-plus';
@@ -270,7 +270,7 @@
const isInit = ref(true);
// 图斑管理表格
const getTableList = () => {
  tableLoading.value = true;
  tableLoading.value = true;
  const requestParams = {
    patchesInfoId: props.detailid,
  };
@@ -297,7 +297,7 @@
// 地图
const initMap = () => {
  if (!document.getElementById('spotMap') || isMapReady.value) return;
  publicCesiumInstance = new PublicCesium({
    dom: 'spotMap',
    flatMode: false,
@@ -305,17 +305,17 @@
    layerMode: 4,
    contour: false,
  });
  homeViewer.value = publicCesiumInstance.getViewer();
  viewer = publicCesiumInstance.getViewer();
  viewInstance.value = publicCesiumInstance;
  viewInstance.value = publicCesiumInstance;
  viewer.scene.globe.depthTestAgainstTerrain = true;
  viewInstance.value.switchContour(true);
  // 确保 readyPromise 存在
  if (viewer.readyPromise) {
    viewer.readyPromise.then(() => {
      isMapReady.value = true;
      isMapReady.value = true;
      getTableList();
    }).catch((error) => {
      console.error('地图加载失败:', error);
@@ -335,18 +335,18 @@
// 仅弹框初始化时执行的定位逻辑
const initMapLocation = () => {
  if (tbJwdList.length === 0 || !homeViewer.value) return;
  // 计算所有图斑的包围球(用于初始化定位)
  const allPositions = tbJwdList.flatMap(item =>
  const allPositions = tbJwdList.flatMap(item =>
    Cesium.Cartesian3.fromDegreesArray(item.grouped)
  );
  const boundingSphere = Cesium.BoundingSphere.fromPoints(allPositions);
  // 初始化定位(仅执行一次)
  homeViewer.value.camera.flyToBoundingSphere(boundingSphere, {
    duration: 0,
    offset: new Cesium.HeadingPitchRange(
      Cesium.Math.toRadians(0),
      Cesium.Math.toRadians(0),
      Cesium.Math.toRadians(-90)
    ),
  });
@@ -356,7 +356,7 @@
if (!isMapReady.value || !viewer) return;
  viewer?.entities.removeAll();
  tbJwdList = []; // 重置经纬度列表
  tableData.value.forEach(item => {
    const numbersWithCommas = item.dkfw.match(/\d+(\.\d+)?/g);
    if (!numbersWithCommas) return;
@@ -365,11 +365,11 @@
    tbJwdList.push({ ...item, grouped }); // 存储经纬度用于后续操作
    // 绘制图斑(保留原有逻辑,仅移除定位相关代码)
    const fillColor = item.is_exception === 2
      ? Cesium.Color.RED.withAlpha(0.5)
    const fillColor = item.is_exception === 2
      ? Cesium.Color.RED.withAlpha(0.5)
      : Cesium.Color.YELLOW.withAlpha(0.5);
    const outlineColor = item.is_exception === 2
      ? Cesium.Color.RED
    const outlineColor = item.is_exception === 2
      ? Cesium.Color.RED
      : Cesium.Color.YELLOW;
    homeViewer.value?.entities?.add({
@@ -492,13 +492,13 @@
    for (let i = 0; i < numbersWithCommas.length; i += 2) {
      const lon = Number(numbersWithCommas[i]);
      const lat = Number(numbersWithCommas[i + 1]);
      positionsData.push([lon, lat, 10]);
      positionsData.push([lon, lat, 10]);
    }
    flyVisual({
      positionsData: positionsData,
      viewer: homeViewer.value,
      multiple: 15,
      positionsData: positionsData,
      viewer: homeViewer.value,
      multiple: 15,
    });
  }
}
@@ -522,7 +522,7 @@
  // 定位到选中的图斑
  handleLocationPolygon(row);
  // 仅异常图斑可编辑
  if (row.is_exception !== 2) {
    ElMessage.warning('仅异常图斑支持编辑绘制');
@@ -645,21 +645,21 @@
watch(uploadPatchDialog, newVal => {
  if (newVal) {
    isInit.value = true;
    isMapReady.value = false;
    isInit.value = true;
    isMapReady.value = false;
    setTimeout(() => {
      initMap();
      getspotManagementTableApi();
      initMap();
      getspotManagementTableApi();
    }, 0);
  } else {
 tableLoading.value = false;
    tableData.value = [];
    tableData.value = [];
    isEditing.value = false;
    handleUpDateDrawState(false);
    destroyMap();
    isInit.value = false;
    isInit.value = false;
    clearSelect();
    isMapReady.value = false;
    isMapReady.value = false;
  }
  refreshonload();
});
@@ -682,7 +682,7 @@
}
:global(.spotDialog .el-dialog__body) {
    padding: 2rem 2rem 0 !important;
  }
.container {
  display: flex;
@@ -854,7 +854,7 @@
    // 选中行
    .current-row td {
      background-color: rgba(64, 158, 255,0.3) !important;
    }
  }
@@ -948,8 +948,8 @@
  width: 100%;
}
.el-button {
 padding: 0;
      color: #fff;
 padding: 0;
      color: #fff;
      width: 17px;
}
</style>
applications/task-work-order/src/components/PlaybackVideo/components/MapContainer.vue
@@ -15,7 +15,7 @@
import PlanarRouteLineList from '@/components/PlanarRouteLineList/PlanarRouteLineList.vue'
import { flyVisual } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
import * as Cesium from 'cesium'
import { PublicCesium } from '@/utils/cesium/publicCesium'
applications/task-work-order/src/components/map-container/mapContainer.vue
@@ -30,7 +30,7 @@
import { Cartesian3, Terrain, Viewer } from 'cesium';
import { PublicCesium } from '@/utils/cesium/publicCesium';
import ImageTrailMaterial from '@/utils/cesium/ImageTrailMaterial';
import { flyVisual } from '@/utils/cesium/mapUtil';
import { flyVisual } from '@ztzf/utils';
import * as turf from '@turf/turf';
import { nextTick, onBeforeUnmount, onMounted, onUnmounted } from 'vue';
applications/task-work-order/src/hooks/useRouteLine/useRouteLine.js
@@ -11,7 +11,7 @@
import endPointImg from '@/assets/images/EndPointicon.png'
import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
import { flyVisual } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
import { useStore } from 'vuex'
applications/task-work-order/src/utils/cesium/DrawPolygon.js
@@ -2,7 +2,8 @@
import * as turf from '@turf/turf'
import { boxTransformScale } from '@/utils/turfFunc'
import { ElMessage } from 'element-plus'
import { flyVisual, getPointPositionsHeight } from '@/utils/cesium/mapUtil'
import { getPointPositionsHeight } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
/**
 * 多边形绘制与编辑工具类
applications/task-work-order/src/utils/cesium/mapUtil.js
@@ -449,66 +449,7 @@
    }
}
/**
 * 飞到中心点并且所有点在可视范围
 *  @param positionsData 二维数组,每项为 [lon, lat] 或 三维数组 [lon, lat, height]
 *  @param viewer Cesium.Viewer 实例
 *  @param multiple 缩放倍数-默认为4
 *  @param pitch 俯仰角-默认为-90
 */
export function flyVisual({ positionsData, viewer, multiple = 4, pitch = -90 }) {
    if (!Array.isArray(positionsData) || positionsData.length === 0) return
    // 如果是一个点,加两个点方便后续生成外包围盒
    if (positionsData.length === 1) {
        const [lon, lat, height = 0] = positionsData[0]
        positionsData = [
            [lon + 0.001, lat + 0.001, height],
            [lon - 0.001, lat - 0.001, height],
            [lon, lat, height],
        ]
    }
    const positions = positionsData.map(([lon, lat, height = 0]) =>
        Cesium.Cartesian3.fromDegrees(Number(lon), Number(lat), Number(height || 0))
    )
    // 计算最高高度和中心点经纬度
    let maxHeight = -Infinity
    let sumLon = 0,
        sumLat = 0
    for (const [lon, lat, height] of positionsData) {
        sumLon += Number(lon)
        sumLat += Number(lat)
        maxHeight = Math.max(maxHeight, Number(height || 0))
    }
    const centerLon = sumLon / positionsData.length
    const centerLat = sumLat / positionsData.length
    const boundingSphere = Cesium.BoundingSphere.fromPoints(positions)
    // 暂时飞向 BoundingSphere
    viewer.camera.flyToBoundingSphere(Cesium.BoundingSphere.fromPoints(positions), {
        duration: 0,
        offset: {
            heading: Cesium.Math.toRadians(0),
            pitch: Cesium.Math.toRadians(pitch),
            range: boundingSphere.radius * multiple,
        },
        complete: () => {
            // 飞行完成后检查相机高度是否小于最高点
            const cameraHeight = Cesium.Cartographic.fromCartesian(viewer.camera.position).height
            if (cameraHeight < maxHeight) {
                viewer.camera.flyTo({
                    destination: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, maxHeight + 100), // 加100米缓冲
                    duration: 1.5,
                    orientation: {
                        heading: viewer.camera.heading,
                        pitch: viewer.camera.pitch,
                        roll: viewer.camera.roll,
                    },
                })
            }
        },
    })
}
// 获取视口地图中心点
export function getMapCenterPoint(viewer) {
applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
@@ -201,7 +201,7 @@
<script setup>
import { computed, ref, onMounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { dateRangeFormat, fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
import { dateRangeFormat, fieldRules, flyVisual, geomAnalysis, getDictLabel } from '@ztzf/utils'
import {
    gdWorkOrderDetailApi,
    gdWorkOrderFlowListApi,
@@ -403,7 +403,10 @@
            material: Cesium.Color.RED,
        },
    })
    viewer.flyTo(mian, { duration: 0 })
    flyVisual({
        positionsData: pointList.map(i => [i.longitude, i.latitude, i.height || 0]),
        viewer,
    })
}
// 同步关联场景
applications/task-work-order/src/views/resource/components/spotDetails.vue
@@ -61,24 +61,24 @@
        <div class="table-overlay">
          <div class="table-content">
            <div class="tabname">图斑列表</div>
       <div class="tabBoxLoading"
       <div class="tabBoxLoading"
        v-loading="tableLoading"
            element-loading-text="加载中..."
            element-loading-text="加载中..."
            element-loading-background="rgba(0, 0, 0, 0.1)"
            >
             <el-table
             v-if="tableData.length > 0"
              ref="polygonTableEle"
              highlight-current-row
              :row-class-name="tableRowClassName"
              :data="tableData"
              @row-click="handleLocationPolygon"
            >
              <el-table-column type="index" align="center" :width="pxToRemNum(30)" label="序号">
               <template #default="{ $index }">
                {{ ($index + 1).toString().padStart(2, '0') }}
                {{ ($index + 1).toString().padStart(2, '0') }}
              </template>
              </el-table-column>
              <el-table-column prop="dkbh" align="center"  label="图斑名称" show-overflow-tooltip />
@@ -99,25 +99,25 @@
                    {{ isEditing && selectionIds === scope.row.id ? '取消编辑' : '编辑' }}
                  </span> -->
               <template v-if="scope.row.is_exception == 2">
                    <el-button
                    <el-button
                      v-if="!isEditing || selectionIds !== scope.row.id"
                      icon="el-icon-edit"
                      link
                      icon="el-icon-edit"
                      link
                      @click.stop="handleSelectionChange(scope.row)"
                    />
                    <el-button
                    <el-button
                      v-else
                      icon="el-icon-circle-close"
                      link
                      icon="el-icon-circle-close"
                      link
                      @click.stop="handleSelectionChange(scope.row)"
                    />
                  </template>
                </template>
              </el-table-column>
            </el-table>
       </div>
          </div>
        </div>
        <!--绘制按钮-->
@@ -126,7 +126,7 @@
          ref="drawPolygonRef"
          v-if="isEditing"
          @upDateDrawState="handleUpDateDrawState"
        />
        <!-- 完成/取消 -->
        <div class="btnGroups" v-if="props.title === '图斑编辑'">
@@ -141,7 +141,7 @@
</template>
<script setup>
import { flyVisual } from '@/utils/cesium/mapUtil'
import { flyVisual } from '@ztzf/utils'
import { pxToRem, pxToRemNum } from '@/utils/rem'
import DrawPolygon from '@/views/resource/components/DrawPolygon.vue';
import { ElMessage, ElMessageBox } from 'element-plus';
@@ -270,7 +270,7 @@
const isInit = ref(true);
// 图斑管理表格
const getTableList = () => {
  tableLoading.value = true;
  tableLoading.value = true;
  const requestParams = {
    patchesInfoId: props.detailid,
  };
@@ -297,7 +297,7 @@
// 地图
const initMap = () => {
  if (!document.getElementById('spotMap') || isMapReady.value) return;
  publicCesiumInstance = new PublicCesium({
    dom: 'spotMap',
    flatMode: false,
@@ -305,17 +305,17 @@
    layerMode: 4,
    contour: false,
  });
  homeViewer.value = publicCesiumInstance.getViewer();
  viewer = publicCesiumInstance.getViewer();
  viewInstance.value = publicCesiumInstance;
  viewInstance.value = publicCesiumInstance;
  viewer.scene.globe.depthTestAgainstTerrain = true;
  viewInstance.value.switchContour(true);
  // 确保 readyPromise 存在
  if (viewer.readyPromise) {
    viewer.readyPromise.then(() => {
      isMapReady.value = true;
      isMapReady.value = true;
      getTableList();
    }).catch((error) => {
      console.error('地图加载失败:', error);
@@ -335,18 +335,18 @@
// 仅弹框初始化时执行的定位逻辑
const initMapLocation = () => {
  if (tbJwdList.length === 0 || !homeViewer.value) return;
  // 计算所有图斑的包围球(用于初始化定位)
  const allPositions = tbJwdList.flatMap(item =>
  const allPositions = tbJwdList.flatMap(item =>
    Cesium.Cartesian3.fromDegreesArray(item.grouped)
  );
  const boundingSphere = Cesium.BoundingSphere.fromPoints(allPositions);
  // 初始化定位(仅执行一次)
  homeViewer.value.camera.flyToBoundingSphere(boundingSphere, {
    duration: 0,
    offset: new Cesium.HeadingPitchRange(
      Cesium.Math.toRadians(0),
      Cesium.Math.toRadians(0),
      Cesium.Math.toRadians(-90)
    ),
  });
@@ -356,7 +356,7 @@
if (!isMapReady.value || !viewer) return;
  viewer?.entities.removeAll();
  tbJwdList = []; // 重置经纬度列表
  tableData.value.forEach(item => {
    const numbersWithCommas = item.dkfw.match(/\d+(\.\d+)?/g);
    if (!numbersWithCommas) return;
@@ -365,11 +365,11 @@
    tbJwdList.push({ ...item, grouped }); // 存储经纬度用于后续操作
    // 绘制图斑(保留原有逻辑,仅移除定位相关代码)
    const fillColor = item.is_exception === 2
      ? Cesium.Color.RED.withAlpha(0.5)
    const fillColor = item.is_exception === 2
      ? Cesium.Color.RED.withAlpha(0.5)
      : Cesium.Color.YELLOW.withAlpha(0.5);
    const outlineColor = item.is_exception === 2
      ? Cesium.Color.RED
    const outlineColor = item.is_exception === 2
      ? Cesium.Color.RED
      : Cesium.Color.YELLOW;
    homeViewer.value?.entities?.add({
@@ -492,13 +492,13 @@
    for (let i = 0; i < numbersWithCommas.length; i += 2) {
      const lon = Number(numbersWithCommas[i]);
      const lat = Number(numbersWithCommas[i + 1]);
      positionsData.push([lon, lat, 10]);
      positionsData.push([lon, lat, 10]);
    }
    flyVisual({
      positionsData: positionsData,
      viewer: homeViewer.value,
      multiple: 15,
      positionsData: positionsData,
      viewer: homeViewer.value,
      multiple: 15,
    });
  }
}
@@ -522,7 +522,7 @@
  // 定位到选中的图斑
  handleLocationPolygon(row);
  // 仅异常图斑可编辑
  if (row.is_exception !== 2) {
    ElMessage.warning('仅异常图斑支持编辑绘制');
@@ -645,21 +645,21 @@
watch(uploadPatchDialog, newVal => {
  if (newVal) {
    isInit.value = true;
    isMapReady.value = false;
    isInit.value = true;
    isMapReady.value = false;
    setTimeout(() => {
      initMap();
      getspotManagementTableApi();
      initMap();
      getspotManagementTableApi();
    }, 0);
  } else {
 tableLoading.value = false;
    tableData.value = [];
    tableData.value = [];
    isEditing.value = false;
    handleUpDateDrawState(false);
    destroyMap();
    isInit.value = false;
    isInit.value = false;
    clearSelect();
    isMapReady.value = false;
    isMapReady.value = false;
  }
  refreshonload();
});
@@ -682,7 +682,7 @@
}
:global(.spotDialog .el-dialog__body) {
    padding: 2rem 2rem 0 !important;
  }
.container {
  display: flex;
@@ -854,7 +854,7 @@
    // 选中行
    .current-row td {
      background-color: rgba(64, 158, 255,0.3) !important;
    }
  }
@@ -948,8 +948,8 @@
  width: 100%;
}
.el-button {
 padding: 0;
      color: #fff;
 padding: 0;
      color: #fff;
      width: 17px;
}
</style>
packages/utils/map/index.js
@@ -1,16 +1,4 @@
/*
 * @Author       : yuan
 * @Date         : 2025-12-26 11:01:31
 * @LastEditors  : yuan
 * @LastEditTime : 2025-12-31 16:00:54
 * @FilePath     : \packages\utils\map\index.js
 * @Description  :
 * Copyright 2025 OBKoro1, All Rights Reserved.
 * 2025-12-26 11:01:31
 */
import * as Cesium from 'cesium'
// 获取正射影像4至点
export function getOrthoImageBoundaryPoints (data) {
@@ -29,4 +17,65 @@
        east,
        north
    )
}
}
/**
 * 飞到中心点并且所有点在可视范围
 *  @param positionsData 二维数组,每项为 [lon, lat] 或 三维数组 [lon, lat, height]
 *  @param viewer Cesium.Viewer 实例
 *  @param multiple 缩放倍数-默认为4
 *  @param pitch 俯仰角-默认为-90
 */
export function flyVisual({ positionsData, viewer, multiple = 4, pitch = -90 }) {
    if (!Array.isArray(positionsData) || positionsData.length === 0) return
    // 如果是一个点,加两个点方便后续生成外包围盒
    if (positionsData.length === 1) {
        const [lon, lat, height = 0] = positionsData[0]
        positionsData = [
            [lon + 0.001, lat + 0.001, height],
            [lon - 0.001, lat - 0.001, height],
            [lon, lat, height],
        ]
    }
    const positions = positionsData.map(([lon, lat, height = 0]) =>
        Cesium.Cartesian3.fromDegrees(Number(lon), Number(lat), Number(height || 0))
    )
    // 计算最高高度和中心点经纬度
    let maxHeight = -Infinity
    let sumLon = 0,
        sumLat = 0
    for (const [lon, lat, height] of positionsData) {
        sumLon += Number(lon)
        sumLat += Number(lat)
        maxHeight = Math.max(maxHeight, Number(height || 0))
    }
    const centerLon = sumLon / positionsData.length
    const centerLat = sumLat / positionsData.length
    const boundingSphere = Cesium.BoundingSphere.fromPoints(positions)
    // 暂时飞向 BoundingSphere
    viewer.camera.flyToBoundingSphere(Cesium.BoundingSphere.fromPoints(positions), {
        duration: 0,
        offset: {
            heading: Cesium.Math.toRadians(0),
            pitch: Cesium.Math.toRadians(pitch),
            range: boundingSphere.radius * multiple,
        },
        complete: () => {
            // 飞行完成后检查相机高度是否小于最高点
            const cameraHeight = Cesium.Cartographic.fromCartesian(viewer.camera.position).height
            if (cameraHeight < maxHeight) {
                viewer.camera.flyTo({
                    destination: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, maxHeight + 100), // 加100米缓冲
                    duration: 1.5,
                    orientation: {
                        heading: viewer.camera.heading,
                        pitch: viewer.camera.pitch,
                        roll: viewer.camera.roll,
                    },
                })
            }
        },
    })
}