吉安感知网项目-前端
张含笑
2026-01-21 792403d97b0b0e06b9faba5c18c4886bf9f7586b
Merge remote-tracking branch 'origin/master'
29 files modified
746 ■■■■ 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/page/index/top/index.vue 9 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/page/lock/index.vue 98 ●●●● 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/aICrudTemplate/index.vue 66 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/index.vue 7 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/partitionApi.js 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceStock/index.vue 7 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue 89 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/detectionCountermeasure/taskSchedule/index.vue 22 ●●●●● 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/common-cesium-map.vue 4 ●●●● 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/adminBoundary.js 8 ●●●● 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 15 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/resource/components/spotDetails.vue 92 ●●●● patch | view | raw | blame | history
packages/utils/common/index.js 8 ●●●● 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/page/index/top/index.vue
@@ -36,6 +36,8 @@
</template>
<script>
import { ElMessage, ElMessageBox } from 'element-plus'
import logo from '@/assets/images/topContainer/logo.png'
import { mapGetters } from 'vuex'
@@ -84,10 +86,11 @@
  },
  methods: {
    logout () {
      this.$confirm(this.$t('logoutTip'), this.$t('提示'), {
        confirmButtonText: this.$t('submitText'),
        cancelButtonText: this.$t('cancelText'),
      ElMessageBox.confirm(`是否退出系统, 是否继续?`, '提示', {
        type: 'warning',
        customClass: 'command-page-view-message-box',
        confirmButtonClass: 'command-message-box-confirm',
        cancelButtonClass: 'command-message-box-cancel',
      }).then(() => {
        this.$store.dispatch('LogOut').then(() => {
          const env = import.meta.env.VITE_APP_ENV
applications/drone-command/src/page/lock/index.vue
@@ -5,24 +5,16 @@
    </div>
    <div class="login-weaper">
      <div class="login-left animate__animated animate__fadeInLeft">
        <img class="img" src="/img/logo.png" alt=""  />
        <img class="img" src="/img/logo.png" alt="" />
        <p class="title">{{ $t('login.info') }}</p>
      </div>
      <div class="login-border animate__animated animate__fadeInRight">
        <div class="login-main">
          <div class="lock-form animate__animated animate__bounceInDown">
            <div
              class="animate__animated"
              :class="{ shake: passwdError, animate__bounceOut: pass }"
            >
            <div class="animate__animated" :class="{ shake: passwdError, animate__bounceOut: pass }">
              <h3 style="color: #333">{{ userInfo.username }}</h3>
              <el-input
                placeholder="请输入登录密码"
                type="password"
                class="input-with-select animated"
                v-model="passwd"
                @keyup.enter="handleLogin"
              >
              <el-input placeholder="请输入登录密码" type="password" class="input-with-select animated" v-model="passwd"
                @keyup.enter="handleLogin">
                <template #append>
                  <i class="icon-bofangqi-suoping" @click="handleLogin"></i>
                  &nbsp; &nbsp;
@@ -37,80 +29,82 @@
  </div>
</template>
<script>
import { mapGetters } from 'vuex';
import { removeRefreshToken, removeToken } from '@/utils/auth';
import { mapGetters } from 'vuex'
import { removeRefreshToken, removeToken } from '@/utils/auth'
import { ElMessage, ElMessageBox } from 'element-plus'
export default {
  name: 'lock',
  data() {
  data () {
    return {
      time: '',
      timeTimer: null,
      passwd: '',
      passwdError: false,
      pass: false,
    };
  },
  created() {
    this.getTime();
    if (this.timeTimer) clearInterval(this.timeTimer);
    this.timeTimer = setInterval(() => {
      this.getTime();
    }, 1000);
  },
  beforeDestroy() {
    if (this.timeTimer) {
      clearInterval(this.timeTimer);
      this.timeTimer = null;
    }
  },
  mounted() {},
  created () {
    this.getTime()
    if (this.timeTimer) clearInterval(this.timeTimer)
    this.timeTimer = setInterval(() => {
      this.getTime()
    }, 1000)
  },
  beforeDestroy () {
    if (this.timeTimer) {
      clearInterval(this.timeTimer)
      this.timeTimer = null
    }
  },
  mounted () { },
  computed: {
    ...mapGetters(['userInfo', 'tag', 'lockPasswd']),
  },
  props: [],
  methods: {
    getTime() {
      this.time = this.$dayjs().format('YYYY年MM月DD日 HH:mm:ss');
    getTime () {
      this.time = this.$dayjs().format('YYYY年MM月DD日 HH:mm:ss')
    },
    handleLogout() {
      this.$confirm('是否退出系统, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
    handleLogout () {
      ElMessageBox.confirm(`是否退出系统, 是否继续?`, '提示', {
        type: 'warning',
        customClass: 'command-page-view-message-box',
        confirmButtonClass: 'command-message-box-confirm',
        cancelButtonClass: 'command-message-box-cancel',
      }).then(() => {
        this.$store.dispatch('LogOut').then(() => {
          // 清除token信息
          removeToken();
          removeRefreshToken();
          this.$router.push({ path: '/login' });
        });
      });
          removeToken()
          removeRefreshToken()
          this.$router.push({ path: '/login' })
        })
      })
    },
    handleLogin() {
    handleLogin () {
      if (this.passwd !== this.lockPasswd) {
        this.passwd = '';
        this.passwd = ''
        this.$message({
          message: '解锁密码错误,请重新输入',
          type: 'error',
        });
        this.passwdError = true;
        })
        this.passwdError = true
        setTimeout(() => {
          this.passwdError = false;
        }, 1000);
        return;
          this.passwdError = false
        }, 1000)
        return
      }
      this.pass = true;
      this.pass = true
      setTimeout(() => {
        this.$store.commit('CLEAR_LOCK');
        this.$store.commit('CLEAR_LOCK')
        this.$router.push({
          path: this.tag.path,
        });
      }, 1000);
        })
      }, 1000)
    },
  },
  components: {},
};
}
</script>
<style lang="scss"></style>
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/aICrudTemplate/index.vue
@@ -10,41 +10,22 @@
                <el-col :span="4">
                    <el-form-item label="类型" prop="deviceType">
                        <el-select v-model="searchParams.deviceType" placeholder="请选择" clearable @change="handleSearch">
                            <el-option
                                v-for="item in dictObj.deviceType"
                                :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey"
                            />
                            <el-option v-for="item in dictObj.deviceType" :key="item.dictKey" :label="item.dictValue"
                                :value="item.dictKey" />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="4">
                    <el-form-item label="部门" prop="belongDept">
                        <el-tree-select
                            v-model="searchParams.belongDept"
                            :data="deptTree"
                            :props="treeProps"
                            node-key="id"
                            check-strictly
                            clearable
                            @change="handleSearch"
                        />
                        <el-tree-select v-model="searchParams.belongDept" :data="deptTree" :props="treeProps"
                            node-key="id" check-strictly clearable @change="handleSearch" />
                    </el-form-item>
                </el-col>
                <el-col :span="4">
                    <el-form-item label="时间">
                        <el-date-picker
                            popper-class="command-date-picker-popper"
                            class="command-date-picker"
                            v-model="dateRange"
                            type="daterange"
                            range-separator="至"
                            start-placeholder="开始日期"
                            end-placeholder="结束日期"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            @change="handleSearch"
                        />
                        <el-date-picker popper-class="command-date-picker-popper" class="command-date-picker"
                            v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期"
                            end-placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:ss" @change="handleSearch" />
                    </el-form-item>
                </el-col>
                <el-col :span="4">
@@ -80,12 +61,8 @@
            </el-table-column>
        </el-table>
        <div>
            <el-pagination
                v-model:current-page="searchParams.current"
                v-model:page-size="searchParams.size"
                :total="total"
                @change="getList"
            />
            <el-pagination v-model:current-page="searchParams.current" v-model:page-size="searchParams.size"
                :total="total" @change="getList" />
        </div>
        <FormDiaLog v-if="dialogVisible" v-model="dialogVisible" ref="dialogRef" @success="getList" />
@@ -132,7 +109,7 @@
provide('deptTree', deptTree)
// 获取列表
async function getList() {
async function getList () {
    const range = dateRangeFormat(dateRange.value)
    loading.value = true
    try {
@@ -146,13 +123,13 @@
}
// 查询
function handleSearch() {
function handleSearch () {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
function resetForm () {
    dateRange.value = []
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
@@ -160,9 +137,14 @@
}
// 删除
async function handleDelete(row) {
async function handleDelete (row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', {
        type: 'warning',
        customClass: 'command-page-view-message-box',
        confirmButtonClass: 'command-message-box-confirm',
        cancelButtonClass: 'command-message-box-cancel',
    })
    const ids = row ? row.id : selectedIds.value.join(',')
    await fwDeviceRemoveApi({ ids })
    ElMessage.success('删除成功')
@@ -171,12 +153,12 @@
}
// 勾选值设置
function handleSelectionChange(rows) {
function handleSelectionChange (rows) {
    selectedIds.value = rows.map(item => item.id)
}
// 导出
function exportFile() {
function exportFile () {
    exportLoading.value = true
    exportFwDeviceApi()
        .then(res => {
@@ -188,21 +170,21 @@
}
// 获取字典
function getDictList() {
function getDictList () {
    getDictionaryByCode('deviceType,deviceAtt').then(res => {
        dictObj.value = res.data.data
    })
}
// 获取部门树
function getDeptTreeFun() {
function getDeptTreeFun () {
    getDeptTree().then(res => {
        deptTree.value = res.data.data
    })
}
// 新增/编辑/查看 弹框
function openForm(mode, row) {
function openForm (mode, row) {
    dialogVisible.value = true
    nextTick(() => {
        dialogRef.value?.open({ mode, row })
applications/drone-command/src/views/areaManage/partition/index.vue
@@ -129,7 +129,12 @@
// 删除
async function handleDelete (row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', {
        type: 'warning',
        customClass: 'command-page-view-message-box',
        confirmButtonClass: 'command-message-box-confirm',
        cancelButtonClass: 'command-message-box-cancel',
    })
    const ids = row ? row.id : selectedIds.value.join(',')
    await fwAreaDivideRemoveApi({ ids })
    ElMessage.success('删除成功')
applications/drone-command/src/views/areaManage/partition/partitionApi.js
@@ -8,7 +8,7 @@
        params: { descs: 'update_time', ...params },
    })
}
// 查list
// 查list --- 区域
export const fwAreaDivideListApi = params => {
    return request({
        url: `/drone-fw/area/fwAreaDivide/list`,
applications/drone-command/src/views/basicManage/deviceStock/index.vue
@@ -212,7 +212,12 @@
// 删除
async function handleDelete(row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', {
        type: 'warning',
        customClass: 'command-page-view-message-box',
        confirmButtonClass: 'command-message-box-confirm',
        cancelButtonClass: 'command-message-box-cancel',
    })
    const ids = row ? row.id : selectedIds.value.join(',')
    await fwDeviceRemoveApi({ ids })
    ElMessage.success('删除成功')
applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue
@@ -12,12 +12,12 @@
                    <div class="val">{{ formData.deviceSn }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">场景</div>
                    <div class="val">{{ formData.defenseSceneName }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">区域</div>
                    <div class="val">{{ formData.areaDivideName }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">场景</div>
                    <div class="val">{{ formData.defenseSceneName }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">调度人员</div>
@@ -71,39 +71,30 @@
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="场景" prop="defenseSceneId">
                        <el-select class="command-select" popper-class="command-select-popper"
                            v-model="formData.defenseSceneId" placeholder="请选择" clearable @change="handleSceneChange">
                            <el-option v-for="item in sceneList" :key="item.id" :label="item.sceneName"
                                :value="item.id" />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="区域" prop="areaDivideId">
                        <el-select class="command-select" popper-class="command-select-popper"
                            v-model="formData.areaDivideId" placeholder="请先选择场景" clearable
                            :disabled="!formData.defenseSceneId" @change="handleAreaChange">
                            v-model="formData.areaDivideId" placeholder="请选择" clearable
                             @change="handleAreaChange">
                            <el-option v-for="item in areaList" :key="item.id" :label="item.areaName"
                                :value="item.id" />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="调度人员" prop="dispatchUser">
                        <el-input class="command-input" v-model="formData.dispatchUser" maxlength="50" placeholder="请输入"
                            clearable />
                    <el-form-item label="场景" prop="defenseSceneId">
                        <el-select class="command-select" popper-class="command-select-popper"
                            v-model="formData.defenseSceneId" placeholder="请先选择区域" clearable
                            :disabled="!formData.areaDivideId" @change="handleSceneChange">
                            <el-option v-for="item in sceneList" :key="item.id" :label="item.sceneName"
                                :value="item.id" />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="调度日期" prop="dispatchDateRange">
                        <el-date-picker class="command-date-picker" popper-class="command-date-picker-popper"
                            v-model="formData.dispatchDateRange" type="datetimerange" range-separator="至"
                            start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:ss" />
                    </el-form-item>
                </el-col>
                <el-col :span="24">
                    <el-form-item label="调度位置" prop="longitude">
                        <!--                        <el-button @click="selectLocation" style="width: 100%">-->
                        <el-button style="width: 100%">
@@ -114,6 +105,21 @@
                        </el-button>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="调度人员" prop="dispatchUser">
                        <el-input class="command-input" v-model="formData.dispatchUser" maxlength="50" placeholder="请输入"
                            clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="调度日期" prop="dispatchDateRange">
                        <el-date-picker class="command-date-picker" popper-class="command-date-picker-popper"
                            v-model="formData.dispatchDateRange" type="daterange" range-separator="至"
                            start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:ss" />
                    </el-form-item>
                </el-col>
                <el-col :span="24">
                    <el-form-item label="调度内容" prop="dispatchContent">
                        <el-input class="command-input" v-model="formData.dispatchContent" type="textarea" :rows="3"
@@ -158,6 +164,7 @@
import { ElMessage } from 'element-plus'
import { fwTaskScheduleDetailApi, fwTaskScheduleSubmitApi } from './taskScheduleApi'
import { fwAreaDivideListApi } from '@/views/areaManage/partition/partitionApi'
import { fwDefenseSceneListApi } from '@/views/areaManage/sceneConfig/sceneConfigApi'
import { fieldRules } from '@ztzf/utils'
import { PublicCesium } from '@/utils/cesium/publicCesium'
import * as Cesium from 'cesium'
@@ -186,7 +193,6 @@
})
const props = defineProps({
    sceneList: { type: Array, default: () => [] },
    deviceList: { type: Array, default: () => [] },
})
@@ -203,6 +209,7 @@
const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
const tempLocation = ref({ longitude: null, latitude: null })
const areaList = ref([]) // 区域列表
const sceneList = ref([]) // 场景列表
const deviceStatus = ref('')
const rules = {
@@ -229,26 +236,32 @@
    }
}
// 场景选择变化处理
async function handleSceneChange (sceneId) {
    formData.value.areaDivideId = ''
    if (sceneId) {
        await loadAreaList(sceneId)
async function handleAreaChange (areaId) {
    formData.value.defenseSceneId = ''
    if (areaId) {
        await loadSceneList(areaId)
    } else {
        areaList.value = []
        sceneList.value = []
    }
}
function handleAreaChange (areaId) {
    const find = areaList.value.find(item => item.id === areaId)
// 场景选择变化处理
async function handleSceneChange (sceneId) {
    const find = sceneList.value.find(item => item.id === sceneId)
    formData.value.longitude = find?.longitude
    formData.value.latitude = find?.latitude
}
// 加载区域列表
async function loadAreaList (sceneId) {
    const res = await fwAreaDivideListApi({ sceneId })
async function loadAreaList () {
    const res = await fwAreaDivideListApi()
    areaList.value = res?.data?.data ?? []
}
// 加载场景列表
async function loadSceneList(areaId) {
    const res = await fwDefenseSceneListApi({areaId})
    sceneList.value = res?.data?.data ?? []
}
// 关闭弹框
@@ -385,6 +398,10 @@
}
defineExpose({ open })
onMounted(() => {
    loadAreaList()
})
</script>
<style scoped lang="scss">
#mapContainer {
applications/drone-command/src/views/detectionCountermeasure/taskSchedule/index.vue
@@ -62,7 +62,7 @@
                    <el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
                    <el-table-column prop="deviceName" show-overflow-tooltip width="140" label="设备名称" />
                    <el-table-column prop="deviceSn" show-overflow-tooltip width="140" label="设备编码" />
                    <el-table-column prop="status" show-overflow-tooltip width="100" label="设备状态">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.deviceStatus, dictObj.deviceStatus) }}
@@ -77,7 +77,7 @@
                    <el-table-column prop="dispatchUser" show-overflow-tooltip width="100" label="调度人员" />
                    <el-table-column show-overflow-tooltip label="调度日期">
                        <template v-slot="{ row }">
                            {{ formatDispatchDate(row) }}
                            {{dayjs(row.dispatchStartTime).format('YYYY-MM-DD')}}至{{dayjs(row.dispatchEndTime).format('YYYY-MM-DD')}}
                        </template>
                    </el-table-column>
                    <el-table-column prop="dispatchContent" show-overflow-tooltip width="160" label="调度内容" />
@@ -109,18 +109,17 @@
            @success="getList"
            v-if="dialogVisible"
            v-model="dialogVisible"
            :sceneList="sceneList"
            :deviceList="deviceList"
        />
    </basic-container>
</template>
<script setup>
import dayjs from 'dayjs'
import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref, provide } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fwTaskSchedulePageApi, fwTaskScheduleRemoveApi } from './taskScheduleApi'
import { fwDefenseSceneListApi } from '@/views/areaManage/sceneConfig/sceneConfigApi'
import { fwDeviceListApi } from '@/views/basicManage/deviceStock/fwDevice'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import FormDiaLog from './FormDiaLog.vue'
@@ -146,7 +145,6 @@
    deviceType: [], // 设备类型
    deviceStatus: [], // 设备状态
})
const sceneList = ref([]) // 场景列表
const deviceList = ref([]) // 设备列表
// 注入字典到子组件
@@ -191,7 +189,12 @@
// 删除
async function handleDelete(row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', {
        type: 'warning',
        customClass: 'command-page-view-message-box',
        confirmButtonClass: 'command-message-box-confirm',
        cancelButtonClass: 'command-message-box-cancel',
    })
    const ids = row ? row.id : selectedIds.value.join(',')
    await fwTaskScheduleRemoveApi({ ids })
    ElMessage.success('删除成功')
@@ -219,12 +222,6 @@
    })
}
// 加载场景列表
async function loadSceneList() {
    const res = await fwDefenseSceneListApi()
    sceneList.value = res?.data?.data ?? []
}
// 加载设备列表
async function loadDeviceList() {
    const res = await fwDeviceListApi()
@@ -233,7 +230,6 @@
onMounted(() => {
    getDictList()
    loadSceneList()
    loadDeviceList()
    getList()
})
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/common-cesium-map.vue
@@ -79,7 +79,6 @@
    })
    viewer.value = viewInstance.value.getViewer()
    if (props.showAdminBoundary) {
        console.log(props.zoomToBoundary,'props.zoomToBoundary')
        adminBoundarySources = await loadJaAdminBoundary(viewer.value, {
            zoomTo: props.zoomToBoundary,
        })
@@ -124,8 +123,9 @@
const getMap = () => ({ viewer: viewer.value, publicCesium: viewInstance.value })
const getViewer = () => viewer.value
const getPublicCesium = () => viewInstance.value
const flyBoundary = () => adminBoundarySources.flyBoundary()
defineExpose({ getMap, getViewer, getPublicCesium })
defineExpose({ getMap, getViewer, getPublicCesium,flyBoundary })
</script>
<style scoped lang="scss">
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/adminBoundary.js
@@ -70,13 +70,17 @@
    viewer.dataSources.add(labelSource)
    if (config.zoomTo) {
        await viewer.flyTo(boundarySource, {
        flyBoundary()
    }
    const flyBoundary = () => {
         viewer.flyTo(boundarySource, {
            duration: 0,
            offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-75), 0),
        })
    }
    return { boundarySource, lineSource, labelSource }
    return { boundarySource, lineSource, labelSource,flyBoundary }
}
export const removeJaAdminBoundary = (viewer, sources) => {
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
@@ -8,7 +8,7 @@
        destroy-on-close
    >
        <div class="content" style="display: flex">
            <div class="processBox">
            <div class="processBox" v-if="dialogMode !== 'add'">
                <el-steps direction="vertical" :active="processList.length">
                    <el-step v-for="item in processList" :title="item.flowName" :description="item.flowDesc" />
                </el-steps>
@@ -130,6 +130,7 @@
                    :layer-mode="4"
                    :boundary="false"
                    :zoomToBoundary="false"
                    @ready="mapReady"
                />
            </div>
            <div class="rightBox" v-if="!dialogReadonly">
@@ -200,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,
@@ -402,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,
    })
}
// 同步关联场景
@@ -444,6 +448,11 @@
    viewer = map?.viewer || null
}
function mapReady() {
    console.log(6363)
    dialogMode.value === 'add' && mapRef.value.flyBoundary()
}
defineExpose({ open })
</script>
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/common/index.js
@@ -22,9 +22,13 @@
    return rules
}
export function getDictLabel(value, dictList = []) {
    return dictList.filter(item => String(value).includes(String(item.dictKey))).map(item => item.dictValue).join(',')
    if (value === null || value === undefined || value === '') return '';
    const values = String(value).split(',');
    return dictList
        .filter(item => values.includes(String(item.dictKey)))
        .map(item => item.dictValue)
        .join(',');
}
export function blobDownload(res) {
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,
                    },
                })
            }
        },
    })
}