吉安感知网项目-前端
张含笑
2026-01-21 792403d97b0b0e06b9faba5c18c4886bf9f7586b
Merge remote-tracking branch 'origin/master'
29 files modified
514 ■■■■ 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 74 ●●●● 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 48 ●●●●● 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 20 ●●●●● 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 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/resource/components/spotDetails.vue 2 ●●● 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 2 ●●● patch | view | raw | blame | history
packages/utils/common/index.js 8 ●●●● patch | view | raw | blame | history
packages/utils/map/index.js 73 ●●●● 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
@@ -11,18 +11,10 @@
      <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,8 +29,9 @@
  </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',
@@ -49,19 +42,19 @@
      passwd: '',
      passwdError: false,
      pass: false,
    };
    }
  },
  created() {
    this.getTime();
    if (this.timeTimer) clearInterval(this.timeTimer);
    this.getTime()
    if (this.timeTimer) clearInterval(this.timeTimer)
    this.timeTimer = setInterval(() => {
      this.getTime();
    }, 1000);
      this.getTime()
    }, 1000)
  },
  beforeDestroy() {
    if (this.timeTimer) {
      clearInterval(this.timeTimer);
      this.timeTimer = null;
      clearInterval(this.timeTimer)
      this.timeTimer = null
    }
  },
  mounted() {},
@@ -71,46 +64,47 @@
  props: [],
  methods: {
    getTime() {
      this.time = this.$dayjs().format('YYYY年MM月DD日 HH:mm:ss');
      this.time = this.$dayjs().format('YYYY年MM月DD日 HH:mm:ss')
    },
    handleLogout() {
      this.$confirm('是否退出系统, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
      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() {
      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" />
@@ -162,7 +139,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/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
@@ -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';
applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
@@ -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
@@ -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';
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
@@ -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';
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) {
@@ -30,3 +18,64 @@
        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,
                    },
                })
            }
        },
    })
}