From 5c925f9d963af25f1663383ef173d16c5b5e8a43 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Fri, 23 Jan 2026 18:00:43 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'
---
applications/drone-command/src/views/basicManage/maintainRecord/MaintenanceDiaLog.vue | 2
applications/drone-command/src/views/areaManage/areaStatistics/FormDiaLog.vue | 2
applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue | 2
applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue | 2
applications/drone-command/src/assets/images/dataCockpit/favorite-y.png | 0
applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue | 2
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue | 2
applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue | 2
applications/drone-command/src/components/map-container/device-map-container.vue | 565 ++++-----------------
applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue | 2
applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue | 2
applications/drone-command/src/views/permissionManage/permissionRole/FormDiaLog.vue | 2
applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue | 2
applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue | 20
applications/drone-command/src/views/basicManage/deviceStock/DeviceTrackDiaLog.vue | 2
applications/drone-command/src/assets/images/dataCockpit/favorite-n.png | 0
applications/drone-command/src/views/permissionManage/permissionUser/FormDiaLog.vue | 2
applications/drone-command/src/components/map-container/components/DronePopup.vue | 323 ++++++++++++
applications/drone-command/src/views/basicManage/deviceStock/DeviceScrapDiaLog.vue | 2
applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue | 2
applications/drone-command/src/components/map-container/components/DevicePopup.vue | 325 +++++++++++++
applications/drone-command/env/.env.development | 4
applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue | 2
applications/drone-command/src/views/basicManage/deviceScrap/FormDiaLog.vue | 2
applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue | 2
applications/drone-command/src/components/map-container/device-map-materials.js | 188 +++++++
applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue | 2
27 files changed, 989 insertions(+), 474 deletions(-)
diff --git a/applications/drone-command/env/.env.development b/applications/drone-command/env/.env.development
index 368f458..585cd95 100644
--- a/applications/drone-command/env/.env.development
+++ b/applications/drone-command/env/.env.development
@@ -2,7 +2,7 @@
# @Author : yuan
# @Date : 2026-01-07 14:58:30
# @LastEditors : yuan
- # @LastEditTime : 2026-01-22 15:51:00
+ # @LastEditTime : 2026-01-23 17:51:43
# @FilePath : \applications\drone-command\env\.env.development
# @Description :
# Copyright 2026 OBKoro1, All Rights Reserved.
@@ -16,7 +16,7 @@
#开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
# VITE_APP_URL = https://wrj.shuixiongit.com/api
-VITE_APP_URL= http://192.168.1.204
+VITE_APP_URL= http://192.168.1.33
#新大屏地址
VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/'
diff --git a/applications/drone-command/src/assets/images/dataCockpit/favorite-n.png b/applications/drone-command/src/assets/images/dataCockpit/favorite-n.png
new file mode 100644
index 0000000..c4b4cf3
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/favorite-n.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/dataCockpit/favorite-y.png b/applications/drone-command/src/assets/images/dataCockpit/favorite-y.png
new file mode 100644
index 0000000..7815723
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/favorite-y.png
Binary files differ
diff --git a/applications/drone-command/src/components/map-container/components/DevicePopup.vue b/applications/drone-command/src/components/map-container/components/DevicePopup.vue
new file mode 100644
index 0000000..e1f24f5
--- /dev/null
+++ b/applications/drone-command/src/components/map-container/components/DevicePopup.vue
@@ -0,0 +1,325 @@
+<template>
+ <div v-if="visible" class="device-popup" :style="popupStyle" @click.stop>
+ <div class="device-popup__header">
+ <el-tooltip
+ class="device-popup__title-tooltip"
+ :content="popupTitle"
+ placement="top"
+ effect="dark"
+ :show-after="200"
+ >
+ <span class="device-popup__title">{{ popupTitleDisplay }}</span>
+ </el-tooltip>
+
+ <img class="device-popup__close" :src="popupClose" alt="" @click.stop="emit('close')" />
+ </div>
+ <div class="device-popup__type">
+ <span class="value">{{ popupTypeText }}</span>
+ </div>
+
+ <div class="device-popup__subtitle">
+ <span class="device-popup__dot" :class="popupActionClass"></span>
+ <span class="device-popup__action">{{ popupActionText }}</span>
+ </div>
+
+ <div class="device-popup__rows">
+ <div class="device-popup__row">
+ <div class="device-popup__col">
+ <span class="label">状态</span>
+ <span class="value" :class="popupStatusClass">{{ popupStatusText }}</span>
+ </div>
+
+ <span class="divider">|</span>
+
+ <div class="device-popup__col">
+ <span class="label">电量</span>
+ <span class="value">{{ popupBatteryText }}</span>
+ </div>
+ </div>
+ <div class="device-popup__row">
+ <div class="device-popup__col">
+ <span class="label">方位角</span>
+ <span class="value">{{ popupAzimuthText }}</span>
+ </div>
+
+ <span class="divider">|</span>
+
+ <div class="device-popup__col">
+ <span class="label">俯仰角</span>
+ <span class="value">{{ popupElevationText }}</span>
+ </div>
+ </div>
+ <div class="device-popup__row">
+ <span class="label">侦测目标</span>
+ <span class="value">{{ popupDetectCountText }}</span>
+ </div>
+ <div class="device-popup__row">
+ <span class="label">有效范围</span>
+ <span class="value">{{ popupRangeText }}</span>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+import popupClose from '@/assets/images/dataCockpit/map/popup-close.png'
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ default: false,
+ },
+ position: {
+ type: Object,
+ default: () => ({ x: 0, y: 0 }),
+ },
+ device: {
+ type: Object,
+ default: () => ({}),
+ },
+ titleMax: {
+ type: Number,
+ default: 12,
+ },
+})
+
+const emit = defineEmits(['close'])
+
+const popupStyle = computed(() => ({
+ left: `${props.position?.x ?? 0}px`,
+ top: `${props.position?.y ?? 0}px`,
+}))
+
+const popupTitle = computed(() => props.device?.deviceName || props.device?.deviceModel || '-')
+const popupTitleOverflow = computed(() => popupTitle.value.length > props.titleMax)
+const popupTitleDisplay = computed(() => {
+ if (!popupTitleOverflow.value) return popupTitle.value
+ return `${popupTitle.value.slice(0, props.titleMax)}……`
+})
+
+const popupStatusText = computed(() => {
+ const status = props.device?.status
+ if (status === 0) return '在线'
+ if (status === 1) return '离线'
+ if (status === 2) return '故障'
+ if (status === 3) return '报废'
+ return '-'
+})
+const popupStatusClass = computed(() => (props.device?.status === 0 ? 'online' : ''))
+const popupActionText = computed(() => {
+ const workMode = props.device?.workMode
+ if (workMode === 1 || workMode === '1') return '侦测中'
+ if (workMode === 2 || workMode === '2') return '信号干扰中'
+ if (workMode === 3 || workMode === '3') return '诱导驱离中'
+ if (workMode === 4 || workMode === '4') return '待机'
+ return workMode || '-'
+})
+const popupActionClass = computed(() => {
+ const workMode = props.device?.workMode
+ if (workMode === 1 || workMode === '1') return 'is-detecting'
+ if (workMode === 2 || workMode === '2') return 'is-jamming'
+ if (workMode === 3 || workMode === '3') return 'is-driving'
+ if (workMode === 4 || workMode === '4') return 'is-standby'
+ return 'is-standby'
+})
+const popupTypeText = computed(() => {
+ const type = props.device?.deviceType
+ if (type === 1 || type === '1') return '便捷侦测箱'
+ if (type === 2 || type === '2') return '反制枪'
+ if (type === 3 || type === '3') return '察打一体'
+ return type || '-'
+})
+const popupBatteryText = computed(() => {
+ const val = props.device?.batteryPct
+ if (val == null) return '-'
+ return `${val}%`
+})
+const popupAzimuthText = computed(() => {
+ const val = props.device?.azimuth
+ if (val == null) return '-'
+ return `${val}°`
+})
+const popupElevationText = computed(() => {
+ const val = props.device?.elevation
+ if (val == null) return '-'
+ return `${val}°`
+})
+const popupDetectCountText = computed(() => {
+ const val = props.device?.detectTargetCnt
+ if (val == null) return '-'
+ return `${val}台`
+})
+const popupRangeText = computed(() => {
+ const val = props.device?.effectiveRangeKm
+ if (val == null) return '-'
+ return `${val}KM`
+})
+</script>
+
+<style lang="scss" scoped>
+.device-popup {
+ position: absolute;
+ transform: translate(-50%, calc(-100% - 72px));
+ width: 202px;
+ padding: 0 16px 20px;
+ border-radius: 16px;
+ background: rgba(5, 5, 15, 0.7);
+ backdrop-filter: blur(16px);
+ color: #C3C3DD;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-size: 12px;
+ pointer-events: auto;
+ z-index: 11;
+ box-sizing: border-box;
+
+ &::after {
+ content: "";
+ position: absolute;
+ left: 50%;
+ bottom: -8px;
+ transform: translateX(-50%);
+ width: 0;
+ height: 0;
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-top: 8px solid rgba(5, 5, 15, 0.7);
+ }
+
+ .device-popup__header {
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ position: relative;
+
+ font-weight: bold;
+ font-size: 14px;
+ color: #FFFFFF;
+
+ &::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 50px;
+ height: 2px;
+ background: #284FE3;
+ box-shadow: 0px 3px 2px 0px rgba(15, 89, 255, 0.1), 0px 7px 5px 0px rgba(15, 89, 255, 0.15), 0px 13px 10px 0px rgba(15, 89, 255, 0.18), 0px 22px 18px 0px rgba(15, 89, 255, 0.21), 0px 42px 33px 0px rgba(15, 89, 255, 0.26), 0px 100px 80px 0px rgba(15, 89, 255, 0.36);
+ border-radius: 5px 5px 0px 0px;
+ }
+
+ &::before {
+ content: "";
+ position: absolute;
+ left: 55px;
+ right: 0;
+ bottom: 0;
+ height: 2px;
+ background: #323245;
+ border-radius: 0px 0px 5px 5px;
+ }
+
+ .device-popup__title-tooltip {
+ flex: 1;
+ min-width: 0;
+ display: block;
+ }
+
+ .device-popup__title {
+ display: inline-block;
+ max-width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .device-popup__close {
+ cursor: pointer;
+ }
+ }
+
+ .device-popup__type {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ margin-top: 8px;
+ margin-bottom: 10px;
+ color: #d2d7df;
+
+ .label {
+ color: #9aa4b2;
+ }
+ }
+
+ .device-popup__subtitle {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 2px 10px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.06);
+ font-size: 12px;
+ margin-bottom: 10px;
+
+ .device-popup__dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: #6b7685;
+
+ &.is-detecting {
+ background: #35f08c;
+ box-shadow: 0 0 6px rgba(53, 240, 140, 0.8);
+ }
+
+ &.is-jamming {
+ background: #ff4444;
+ box-shadow: 0 0 6px rgba(255, 68, 68, 0.7);
+ }
+
+ &.is-driving {
+ background: #ffb020;
+ box-shadow: 0 0 6px rgba(255, 176, 32, 0.7);
+ }
+
+ &.is-standby {
+ background: #9aa4b2;
+ }
+ }
+ }
+
+ .device-popup__rows {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ font-size: 12px;
+ color: #d2d7df;
+
+ .device-popup__row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+
+ .device-popup__col {
+ flex: 1;
+ display: flex;
+ align-items: center;
+
+ &:last-child {
+ justify-content: flex-end;
+ }
+ }
+ }
+
+ .value {
+ margin-left: 10px;
+ }
+
+ .value.online {
+ color: #35f08c;
+ }
+ }
+}
+</style>
diff --git a/applications/drone-command/src/components/map-container/components/DronePopup.vue b/applications/drone-command/src/components/map-container/components/DronePopup.vue
new file mode 100644
index 0000000..759e748
--- /dev/null
+++ b/applications/drone-command/src/components/map-container/components/DronePopup.vue
@@ -0,0 +1,323 @@
+<template>
+ <div v-if="visible" class="drone-popup" :style="popupStyle" @click.stop>
+ <div class="drone-popup__header">
+ <el-tooltip
+ class="drone-popup__title-tooltip"
+ :content="popupTitle"
+ placement="top"
+ effect="dark"
+ :show-after="200"
+ >
+ <span class="drone-popup__title">{{ titleDisplay }}</span>
+ </el-tooltip>
+
+ <div class="drone-popup__header-actions">
+ <img
+ class="drone-popup__favorite"
+ :src="favoriteIcon"
+ alt="收藏"
+ @click.stop="emit('toggle-favorite')"
+ />
+ <img class="drone-popup__close" :src="popupClose" alt="" @click.stop="emit('close')" />
+ </div>
+ </div>
+
+ <div class="drone-popup__serial">{{ serial }}</div>
+
+ <div class="drone-popup__status">
+ <span class="drone-popup__status-dot" :class="statusClass"></span>
+ <span class="drone-popup__status-text">{{ statusText }}</span>
+ </div>
+
+ <div class="drone-popup__row">
+ <span class="label">数据源</span>
+ <span class="value">{{ dataSource }}</span>
+ </div>
+
+ <div class="drone-popup__row drone-popup__row-split">
+ <div class="drone-popup__col">
+ <span class="label">高度</span>
+ <span class="value">{{ height }}</span>
+ </div>
+ <span class="divider">|</span>
+ <div class="drone-popup__col">
+ <span class="label">速度</span>
+ <span class="value">{{ speed }}</span>
+ </div>
+ </div>
+
+ <div class="drone-popup__row drone-popup__row-coord">
+ <span class="label">经纬度</span>
+ <div class="drone-popup__coord">
+ <div>{{ longitude }}</div>
+ <div>{{ latitude }}</div>
+ </div>
+ </div>
+
+ <div class="drone-popup__footer">
+ <button class="drone-popup__btn ghost" @click.stop="emit('signal')">信号干扰</button>
+ <button class="drone-popup__btn primary" @click.stop="emit('counter')">诱导驱离</button>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+import popupClose from '@/assets/images/dataCockpit/map/popup-close.png'
+import favoriteNo from '@/assets/images/dataCockpit/favorite-n.png'
+import favoriteYes from '@/assets/images/dataCockpit/favorite-y.png'
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ default: false,
+ },
+ position: {
+ type: Object,
+ default: () => ({ x: 0, y: 0 }),
+ },
+ drone: {
+ type: Object,
+ default: () => ({}),
+ },
+ titleMax: {
+ type: Number,
+ default: 12,
+ },
+ favorite: {
+ type: Boolean,
+ default: false,
+ },
+})
+
+const emit = defineEmits(['close', 'toggle-favorite', 'signal', 'counter'])
+
+const popupStyle = computed(() => ({
+ left: `${props.position?.x ?? 0}px`,
+ top: `${props.position?.y ?? 0}px`,
+}))
+const favoriteIcon = computed(() => (props.favorite ? favoriteYes : favoriteNo))
+
+const title = computed(() => props.drone?.droneName || '无人机名称')
+const titleOverflow = computed(() => title.value.length > props.titleMax)
+const titleDisplay = computed(() => {
+ if (!titleOverflow.value) return title.value
+ return `${title.value.slice(0, props.titleMax)}……`
+})
+
+const serial = computed(() => props.drone?.droneSerialNo || '-')
+const statusText = computed(() => {
+ const status = props.drone?.flightStatus
+ if (status === 1 || status === '1') return '侦测中'
+ if (status === 2 || status === '2') return '反制中'
+ return status || '侦测中'
+})
+const statusClass = computed(() => (statusText.value.includes('反制') ? 'is-driving' : 'is-detecting'))
+
+const dataSource = computed(() => props.drone?.dataSource || props.drone?.deviceName || props.drone?.areaName || '-')
+const counterDevice = computed(() => props.drone?.counterDeviceName || props.drone?.deviceName || '-')
+
+const formatNumber = (value, unit = '', digits = 0) => {
+ if (value == null || value === '') return '-'
+ const num = Number(value)
+ if (!Number.isFinite(num)) return '-'
+ return `${num.toFixed(digits)}${unit}`
+}
+const formatCoord = (value, type) => {
+ const num = Number(value)
+ if (!Number.isFinite(num)) return '-'
+ const absVal = Math.abs(num).toFixed(6)
+ const dir = type === 'lng' ? (num >= 0 ? 'E' : 'W') : num >= 0 ? 'N' : 'S'
+ return `${absVal} ${dir}`
+}
+
+const height = computed(() => formatNumber(props.drone?.flightHeightM, 'm', 0))
+const speed = computed(() => formatNumber(props.drone?.flightSpeedMs, 'm/s', 1))
+const longitude = computed(() => formatCoord(props.drone?.longitude, 'lng'))
+const latitude = computed(() => formatCoord(props.drone?.latitude, 'lat'))
+</script>
+
+<style lang="scss" scoped>
+.drone-popup {
+ position: absolute;
+ transform: translate(-50%, calc(-100% - 42px));
+ width: 202px;
+ padding: 0 16px 20px;
+ border-radius: 16px;
+ background: rgba(5, 5, 15, 0.7);
+ backdrop-filter: blur(16px);
+ color: #C3C3DD;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-size: 12px;
+ pointer-events: auto;
+ z-index: 12;
+ box-sizing: border-box;
+
+ &::after {
+ content: "";
+ position: absolute;
+ left: 50%;
+ bottom: -8px;
+ transform: translateX(-50%);
+ width: 0;
+ height: 0;
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-top: 8px solid rgba(5, 5, 15, 0.7);
+ }
+
+ .drone-popup__header {
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ position: relative;
+
+ font-weight: bold;
+ font-size: 14px;
+ color: #FFFFFF;
+
+ &::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 50px;
+ height: 2px;
+ background: #284FE3;
+ box-shadow: 0px 3px 2px 0px rgba(15, 89, 255, 0.1), 0px 7px 5px 0px rgba(15, 89, 255, 0.15), 0px 13px 10px 0px rgba(15, 89, 255, 0.18), 0px 22px 18px 0px rgba(15, 89, 255, 0.21), 0px 42px 33px 0px rgba(15, 89, 255, 0.26), 0px 100px 80px 0px rgba(15, 89, 255, 0.36);
+ border-radius: 5px 5px 0px 0px;
+ }
+
+ &::before {
+ content: "";
+ position: absolute;
+ left: 55px;
+ right: 0;
+ bottom: 0;
+ height: 2px;
+ background: #323245;
+ border-radius: 0px 0px 5px 5px;
+ }
+
+ .drone-popup__title-tooltip {
+ flex: 1;
+ min-width: 0;
+ display: block;
+ }
+
+ .drone-popup__title {
+ display: inline-block;
+ max-width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .drone-popup__header-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ color: #C9CBE6;
+ font-size: 11px;
+ }
+
+ .drone-popup__favorite,
+ .drone-popup__close {
+ width: 16px;
+ height: 16px;
+ display: inline-flex;
+ align-items: flex-start;
+ vertical-align: middle;
+ cursor: pointer;
+ }
+
+ .drone-popup__favorite {
+ width: 12px;
+ height: 12px;
+ }
+ }
+}
+
+.drone-popup__serial {
+ margin-top: 8px;
+ color: #B7BACF;
+ font-size: 12px;
+ letter-spacing: 0.3px;
+}
+
+.drone-popup__status {
+ margin-top: 10px;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 3px 10px;
+ border-radius: 999px;
+ background: #303041;
+}
+
+.drone-popup__status-dot {
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: #4CC3FF;
+}
+
+.drone-popup__status-dot.is-driving {
+ background: #FF7A4A;
+}
+
+.drone-popup__row {
+ margin-top: 10px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.drone-popup__row-split {
+ justify-content: flex-start;
+ gap: 12px;
+}
+
+.drone-popup__col {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.drone-popup__row-coord {
+ align-items: flex-start;
+}
+
+.drone-popup__coord {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.drone-popup__footer {
+ margin-top: 14px;
+ display: flex;
+ gap: 12px;
+}
+
+.drone-popup__btn {
+ flex: 1;
+ height: 34px;
+ border-radius: 8px;
+ border: none;
+ font-size: 13px;
+ cursor: pointer;
+ color: #FFFFFF;
+ background: #3B3E55;
+}
+
+.drone-popup__btn.primary {
+ background: #2F5BFF;
+}
+
+.drone-popup__btn.ghost {
+ background: rgba(59, 62, 85, 0.9);
+ border: 1px solid rgba(111, 118, 150, 0.6);
+}
+</style>
diff --git a/applications/drone-command/src/components/map-container/device-map-container.vue b/applications/drone-command/src/components/map-container/device-map-container.vue
index 6e584a0..723c82f 100644
--- a/applications/drone-command/src/components/map-container/device-map-container.vue
+++ b/applications/drone-command/src/components/map-container/device-map-container.vue
@@ -20,66 +20,23 @@
</div>
</div>
- <div v-if="popupVisible" class="device-popup" :style="popupStyle" @click.stop>
- <div class="device-popup__header">
- <el-tooltip
- class="device-popup__title-tooltip"
- :content="popupTitle"
- placement="top"
- effect="dark"
- :show-after="200"
- >
- <span class="device-popup__title">{{ popupTitleDisplay }}</span>
- </el-tooltip>
+ <DevicePopup
+ :visible="popupVisible && !isDronePopup"
+ :position="popupPosition"
+ :device="selectedDevice"
+ @close="closePopup"
+ />
- <img class="device-popup__close" :src="popupClose" alt="" @click.stop="closePopup">
- </div>
- <div class="device-popup__type">
- <span class="value">{{ popupTypeText }}</span>
- </div>
-
- <div class="device-popup__subtitle">
- <span class="device-popup__dot" :class="popupActionClass"></span>
- <span class="device-popup__action">{{ popupActionText }}</span>
- </div>
-
- <div class="device-popup__rows">
- <div class="device-popup__row">
- <div class="device-popup__col">
- <span class="label">状态</span>
- <span class="value" :class="popupStatusClass">{{ popupStatusText }}</span>
- </div>
-
- <span class="divider">|</span>
-
- <div class="device-popup__col">
- <span class="label">电量</span>
- <span class="value">{{ popupBatteryText }}</span>
- </div>
- </div>
- <div class="device-popup__row">
- <div class="device-popup__col">
- <span class="label">方位角</span>
- <span class="value">{{ popupAzimuthText }}</span>
- </div>
-
- <span class="divider">|</span>
-
- <div class="device-popup__col">
- <span class="label">俯仰角</span>
- <span class="value">{{ popupElevationText }}</span>
- </div>
- </div>
- <div class="device-popup__row">
- <span class="label">侦测目标</span>
- <span class="value">{{ popupDetectCountText }}</span>
- </div>
- <div class="device-popup__row">
- <span class="label">有效范围</span>
- <span class="value">{{ popupRangeText }}</span>
- </div>
- </div>
- </div>
+ <DronePopup
+ :visible="popupVisible && isDronePopup"
+ :position="popupPosition"
+ :drone="selectedDevice"
+ :favorite="Boolean(selectedDevice?.isFavorite)"
+ @close="closePopup"
+ @toggle-favorite="toggleDroneFavorite"
+ @signal="handleDroneSignal"
+ @counter="handleDroneCounter"
+ />
</div>
</template>
@@ -96,8 +53,16 @@
import droneIcon from '@/assets/images/dataCockpit/map/drone.png'
import aggregationIcon from '@/assets/images/dataCockpit/map/aggregation.png'
import commandPostIcon from '@/assets/images/dataCockpit/map/command-post.png'
-import popupClose from '@/assets/images/dataCockpit/map/popup-close.png'
import jaGeojsonRaw from '@/assets/geojson/ja.geojson?raw'
+import DevicePopup from './components/DevicePopup.vue'
+import DronePopup from './components/DronePopup.vue'
+import {
+ RING_STYLES,
+ addDeviceRings,
+ createDroneTrackMaterial,
+ createRadialGradientMaterial,
+ getTexturedVertexFormat,
+} from './device-map-materials'
const props = defineProps({
onlineDevices: {
@@ -117,6 +82,7 @@
default: true,
},
})
+const emit = defineEmits(['droneSignal', 'droneCounter'])
const DEFAULT_ZONE_PAGE_SIZE = 999
@@ -124,6 +90,7 @@
let viewer = null
let cockpitPrimitiveLayer = null
const devicePickMap = new Map()
+const dronePickMap = new Map()
let deviceBillboardCollection = null
let deviceRingOutlinePrimitives = []
let deviceRingFillPrimitives = []
@@ -144,6 +111,7 @@
const showLayerPanel = ref(false)
const layerWrapRef = ref(null)
const selectedDevice = ref(null)
+const selectedTargetType = ref('device')
let selectedDeviceBillboard = null
let deviceClickHandler = null
let popupRenderHandler = null
@@ -371,24 +339,36 @@
const clearDroneTrackEntities = () => {
if (!viewer) return
+ stopDroneTrackAnimation()
if (droneTrackBillboardCollection) {
- droneTrackBillboardCollection.removeAll()
- removeCockpitPrimitive(droneTrackBillboardCollection)
+ if (!droneTrackBillboardCollection.isDestroyed?.()) {
+ removeCockpitPrimitive(droneTrackBillboardCollection)
+ }
droneTrackBillboardCollection = null
}
if (droneTrackPolylineCollection) {
- droneTrackPolylineCollection.removeAll()
- removeCockpitPrimitive(droneTrackPolylineCollection)
+ if (!droneTrackPolylineCollection.isDestroyed?.()) {
+ removeCockpitPrimitive(droneTrackPolylineCollection)
+ }
droneTrackPolylineCollection = null
}
droneTrackRuntime = []
- stopDroneTrackAnimation()
+ dronePickMap.clear()
+ if (selectedTargetType.value === 'drone') {
+ closePopup()
+ }
}
const ensureDroneTrackCollections = () => {
if (!viewer) return
ensureCockpitPrimitiveLayer()
+ if (droneTrackBillboardCollection?.isDestroyed?.()) {
+ droneTrackBillboardCollection = null
+ }
+ if (droneTrackPolylineCollection?.isDestroyed?.()) {
+ droneTrackPolylineCollection = null
+ }
if (!droneTrackBillboardCollection) {
droneTrackBillboardCollection = new Cesium.BillboardCollection()
addCockpitPrimitive(droneTrackBillboardCollection)
@@ -404,10 +384,17 @@
stopDroneTrackAnimation()
droneTrackStartTime = Cesium.JulianDate.now()
droneTrackTickHandler = clock => {
+ if (!viewer || viewer.isDestroyed?.() || !viewer.scene) {
+ stopDroneTrackAnimation()
+ return
+ }
if (!droneTrackRuntime.length) return
const current = clock.currentTime
const elapsed = Cesium.JulianDate.secondsDifference(current, droneTrackStartTime)
droneTrackRuntime.forEach(track => {
+ if (!track?.billboard || track.billboard.isDestroyed?.()) return
+ if (!track.billboard._scene) return
+ if (!track.positions || track.positions.length < 2) return
const duration = track.duration
if (duration <= 0) return
const t = ((elapsed % duration) + duration) % duration
@@ -434,33 +421,62 @@
const renderSimulatedDroneTrack = () => {
if (!viewer) return
+ clearDroneTrackEntities()
ensureCountyCenterMap()
ensureDroneTrackCollections()
- droneTrackBillboardCollection.removeAll()
- droneTrackPolylineCollection.removeAll()
droneTrackRuntime = []
- const centers = Array.from(countyCenterMap.values())
- const baseCenters = centers.length ? centers.slice(0, 4) : [{ longitude: 116.397, latitude: 39.908 }]
+ const centers = Array.from(countyCenterMap.entries())
+ const baseCenters = centers.length
+ ? centers.slice(0, 4)
+ : [['默认区域', { longitude: 116.397, latitude: 39.908 }]]
const segmentDuration = 6
- baseCenters.forEach((center, trackIndex) => {
+ const baseTrackColor = Cesium.Color.fromCssColorString('red')
+ baseCenters.forEach(([centerName, center], trackIndex) => {
const points = buildSimulatedTrackPoints(center)
if (points.length < 2) return
const positions = points.map(point =>
Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
)
+ let trackMaterial = createDroneTrackMaterial({
+ color: baseTrackColor,
+ speed: 4.5,
+ headWidth: 0.2,
+ glowPower: 1.8,
+ backgroundAlpha: 0.34,
+ })
+ if (!trackMaterial) {
+ trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor })
+ }
droneTrackPolylineCollection.add({
positions,
- width: 2,
- material: Cesium.Material.fromType('Color', {
- color: Cesium.Color.fromCssColorString('#2AEDBF'),
- }),
+ width: 3,
+ material: trackMaterial,
})
+ const droneId = `drone-track-${trackIndex}`
const billboard = droneTrackBillboardCollection.add({
position: positions[0],
image: droneIcon,
width: 36,
height: 36,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ })
+ billboard.id = droneId
+ const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
+ dronePickMap.set(droneId, {
+ data: {
+ droneName: `无人机-${trackIndex + 1}`,
+ droneSerialNo: `UAV-${trackIndex + 1}`,
+ flightHeightM: points[0].height,
+ flightSpeedMs: speedMs,
+ longitude: points[0].longitude,
+ latitude: points[0].latitude,
+ flightStatus: 1,
+ deviceName: centerName,
+ dataSource: centerName,
+ counterDeviceName: centerName,
+ },
+ billboard,
})
droneTrackRuntime.push({
positions,
@@ -472,128 +488,6 @@
viewer.clock.multiplier = 1
startDroneTrackAnimation()
reorderCockpitPrimitives()
-}
-
-const RING_STYLES = [
- { inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] }
-]
-
-const MATERIAL_TYPE = 'RadialGradientMaterial'
-const TEXTURED_VERTEX_FORMAT =
- Cesium.MaterialAppearance?.MaterialSupport?.TEXTURED?.vertexFormat || Cesium.VertexFormat.POSITION_AND_ST
-let materialRegistered = false
-
-const registerRadialGradientMaterial = () => {
- if (materialRegistered || !Cesium?.Material) return
- materialRegistered = true
- Cesium.Material._materialCache.addMaterial(MATERIAL_TYPE, {
- fabric: {
- type: MATERIAL_TYPE,
- uniforms: {
- color1: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
- color2: new Cesium.Color(0.0, 0.0, 0.0, 1.0),
- },
- source: `
- czm_material czm_getMaterial(czm_materialInput materialInput) {
- czm_material material = czm_getDefaultMaterial(materialInput);
- vec2 st = materialInput.st - vec2(0.5);
- float t = clamp(length(st) * 2.0, 0.0, 1.0);
- vec4 color = mix(color1, color2, t);
- material.diffuse = color.rgb;
- material.alpha = color.a;
- return material;
- }
- `,
- },
- translucent: () => true,
- })
-}
-
-const buildCirclePositions = (center, radiusMeters, steps = 64) => {
- const positions = []
- const latRad = Cesium.Math.toRadians(center.latitude)
- const metersToLat = 1 / 111320
- const metersToLon = 1 / (111320 * Math.cos(latRad))
- for (let i = 0; i <= steps; i += 1) {
- const angle = Cesium.Math.toRadians((i / steps) * 360)
- const dx = radiusMeters * Math.cos(angle)
- const dy = radiusMeters * Math.sin(angle)
- const lon = center.longitude + dx * metersToLon
- const lat = center.latitude + dy * metersToLat
- positions.push(Cesium.Cartesian3.fromDegrees(lon, lat))
- }
- return positions
-}
-
-const addDeviceRings = (center, ringFillInstancesByStyle, ringOutlineInstancesByStyle) => {
- if (!ringFillInstancesByStyle) return
- const centerPosition = Cesium.Cartesian3.fromDegrees(center.longitude, center.latitude, 0)
- RING_STYLES.forEach((ring, index) => {
- if (!ringFillInstancesByStyle[index]) return
- ringFillInstancesByStyle[index].push(
- new Cesium.GeometryInstance({
- geometry: new Cesium.EllipseGeometry({
- center: centerPosition,
- semiMajorAxis: ring.outer,
- semiMinorAxis: ring.outer,
- vertexFormat: TEXTURED_VERTEX_FORMAT,
- }),
- })
- )
- })
- if (!ringOutlineInstancesByStyle) return
- RING_STYLES.forEach((ring, index) => {
- if (!ringOutlineInstancesByStyle[index]) return
- const positions = buildCirclePositions(center, ring.outer)
- ringOutlineInstancesByStyle[index].push(
- new Cesium.GeometryInstance({
- geometry: new Cesium.GroundPolylineGeometry({
- positions,
- width: 2,
- }),
- attributes: {
- color: Cesium.ColorGeometryInstanceAttribute.fromColor(
- Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.8)
- ),
- },
- })
- )
- })
-}
-
-class RadialGradientMaterialProperty {
- constructor(color1, color2) {
- this._definitionChanged = new Cesium.Event()
- this.color1 = color1
- this.color2 = color2
- }
-
- get isConstant () {
- return true
- }
-
- get definitionChanged () {
- return this._definitionChanged
- }
-
- getType () {
- return MATERIAL_TYPE
- }
-
- getValue (time, result) {
- const target = result || {}
- target.color1 = this.color1
- target.color2 = this.color2
- return target
- }
-
- equals (other) {
- return (
- other instanceof RadialGradientMaterialProperty &&
- Cesium.Color.equals(this.color1, other.color1) &&
- Cesium.Color.equals(this.color2, other.color2)
- )
- }
}
const renderDeviceEntities = devices => {
@@ -628,17 +522,16 @@
billboard.id = entityId
devicePickMap.set(entityId, { data: item, billboard })
})
- if (selectedDeviceBillboard && !devicePickMap.has(selectedDeviceBillboard.id)) {
+ if (selectedTargetType.value === 'device' && selectedDeviceBillboard && !devicePickMap.has(selectedDeviceBillboard.id)) {
closePopup()
}
- registerRadialGradientMaterial()
ringFillInstancesByStyle.forEach((instances, index) => {
if (!instances.length) return
const ring = RING_STYLES[index]
- const material = Cesium.Material.fromType(MATERIAL_TYPE, {
- color1: Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.64),
- color2: Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(0.64),
- })
+ const material = createRadialGradientMaterial(
+ Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.64),
+ Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(0.64)
+ )
const primitive = new Cesium.GroundPrimitive({
geometryInstances: instances,
appearance: new Cesium.MaterialAppearance({
@@ -679,14 +572,14 @@
if (!viewer) return { primitive: null, outlinePrimitive: null }
const polygonInstances = []
const lineInstances = []
- registerRadialGradientMaterial()
+ const texturedVertexFormat = getTexturedVertexFormat()
; (zones || []).forEach((zone, index) => {
if (!zone?.geom) return
const positions = getDefenseZonePositions(zone.geom)
if (!positions.length) return
const polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(positions),
- vertexFormat: TEXTURED_VERTEX_FORMAT,
+ vertexFormat: texturedVertexFormat,
})
polygonInstances.push(
new Cesium.GeometryInstance({
@@ -712,10 +605,7 @@
if (polygonInstances.length) {
const baseColor1 = Cesium.Color.fromCssColorString(fillColor1)
const baseColor2 = Cesium.Color.fromCssColorString(fillColor2)
- const material = Cesium.Material.fromType(MATERIAL_TYPE, {
- color1: baseColor1.withAlpha(0.64),
- color2: baseColor2.withAlpha(0.64),
- })
+ const material = createRadialGradientMaterial(baseColor1.withAlpha(0.64), baseColor2.withAlpha(0.64))
primitive = new Cesium.GroundPrimitive({
geometryInstances: polygonInstances,
appearance: new Cesium.MaterialAppearance({
@@ -822,12 +712,15 @@
})
}
-const getPickedDevice = picks => {
+const getPickedTarget = picks => {
for (const pick of picks) {
const pickId = pick?.id
const resolvedId = typeof pickId === 'string' ? pickId : pickId?.id
if (resolvedId && devicePickMap.has(resolvedId)) {
- return devicePickMap.get(resolvedId)
+ return { type: 'device', ...devicePickMap.get(resolvedId) }
+ }
+ if (resolvedId && dronePickMap.has(resolvedId)) {
+ return { type: 'drone', ...dronePickMap.get(resolvedId) }
}
}
return null
@@ -858,13 +751,14 @@
const handleDeviceClick = movement => {
if (!viewer) return
const picks = viewer.scene.drillPick(movement.position) || []
- const pickedDevice = getPickedDevice(picks)
- if (!pickedDevice) {
+ const pickedTarget = getPickedTarget(picks)
+ if (!pickedTarget) {
closePopup()
return
}
- selectedDevice.value = pickedDevice.data
- selectedDeviceBillboard = pickedDevice.billboard
+ selectedTargetType.value = pickedTarget.type
+ selectedDevice.value = pickedTarget.data
+ selectedDeviceBillboard = pickedTarget.billboard
startPopupRender()
}
@@ -883,80 +777,19 @@
function closePopup () {
selectedDevice.value = null
selectedDeviceBillboard = null
+ selectedTargetType.value = 'device'
stopPopupRender()
}
const popupPosition = ref({ x: 0, y: 0 })
const popupVisible = computed(() => Boolean(selectedDevice.value))
-const popupStyle = computed(() => ({
- left: `${popupPosition.value.x}px`,
- top: `${popupPosition.value.y}px`,
-}))
-
-const POPUP_TITLE_MAX = 12
-const popupTitle = computed(() => selectedDevice.value?.deviceName || selectedDevice.value?.deviceModel || '-')
-const popupTitleOverflow = computed(() => popupTitle.value.length > POPUP_TITLE_MAX)
-const popupTitleDisplay = computed(() => {
- if (!popupTitleOverflow.value) return popupTitle.value
- return `${popupTitle.value.slice(0, POPUP_TITLE_MAX)}……`
-})
-const popupStatusText = computed(() => {
- const status = selectedDevice.value?.status
- if (status === 0) return '在线'
- if (status === 1) return '离线'
- if (status === 2) return '故障'
- if (status === 3) return '报废'
- return '-'
-})
-const popupStatusClass = computed(() => (selectedDevice.value?.status === 0 ? 'online' : ''))
-const popupActionText = computed(() => {
- const workMode = selectedDevice.value?.workMode
- if (workMode === 1 || workMode === '1') return '侦测中'
- if (workMode === 2 || workMode === '2') return '信号干扰中'
- if (workMode === 3 || workMode === '3') return '诱导驱离中'
- if (workMode === 4 || workMode === '4') return '待机'
- return workMode || '-'
-})
-const popupActionClass = computed(() => {
- const workMode = selectedDevice.value?.workMode
- if (workMode === 1 || workMode === '1') return 'is-detecting'
- if (workMode === 2 || workMode === '2') return 'is-jamming'
- if (workMode === 3 || workMode === '3') return 'is-driving'
- if (workMode === 4 || workMode === '4') return 'is-standby'
- return 'is-standby'
-})
-const popupTypeText = computed(() => {
- const type = selectedDevice.value?.deviceType
- if (type === 1 || type === '1') return '便捷侦测箱'
- if (type === 2 || type === '2') return '反制枪'
- if (type === 3 || type === '3') return '察打一体'
- return type || '-'
-})
-const popupBatteryText = computed(() => {
- const val = selectedDevice.value?.batteryPct
- if (val == null) return '-'
- return `${val}%`
-})
-const popupAzimuthText = computed(() => {
- const val = selectedDevice.value?.azimuth
- if (val == null) return '-'
- return `${val}°`
-})
-const popupElevationText = computed(() => {
- const val = selectedDevice.value?.elevation
- if (val == null) return '-'
- return `${val}°`
-})
-const popupDetectCountText = computed(() => {
- const val = selectedDevice.value?.detectTargetCnt
- if (val == null) return '-'
- return `${val}台`
-})
-const popupRangeText = computed(() => {
- const val = selectedDevice.value?.effectiveRangeKm
- if (val == null) return '-'
- return `${val}KM`
-})
+const isDronePopup = computed(() => selectedTargetType.value === 'drone')
+const toggleDroneFavorite = () => {
+ if (!selectedDevice.value) return
+ selectedDevice.value.isFavorite = !selectedDevice.value.isFavorite
+}
+const handleDroneSignal = () => emit('droneSignal', selectedDevice.value)
+const handleDroneCounter = () => emit('droneCounter', selectedDevice.value)
const renderCommandPosts = list => {
if (!viewer) return
@@ -1085,172 +918,6 @@
left: 0;
width: 100%;
height: 100%;
-}
-
-.device-popup {
- position: absolute;
- transform: translate(-50%, calc(-100% - 72px));
- width: 202px;
- padding: 0 16px 20px;
- border-radius: 16px;
- background: rgba(5, 5, 15, 0.7);
- backdrop-filter: blur(16px);
- color: #C3C3DD;
- font-family: Source Han Sans CN, Source Han Sans CN;
- font-size: 12px;
- pointer-events: auto;
- z-index: 11;
- box-sizing: border-box;
-
- &::after {
- content: "";
- position: absolute;
- left: 50%;
- bottom: -8px;
- transform: translateX(-50%);
- width: 0;
- height: 0;
- border-left: 8px solid transparent;
- border-right: 8px solid transparent;
- border-top: 8px solid rgba(5, 5, 15, 0.7);
- }
-
- .device-popup__header {
- height: 40px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- position: relative;
-
- font-weight: bold;
- font-size: 14px;
- color: #FFFFFF;
-
- &::after {
- content: "";
- position: absolute;
- left: 0;
- bottom: 0;
- width: 50px;
- height: 2px;
- background: #284FE3;
- box-shadow: 0px 3px 2px 0px rgba(15, 89, 255, 0.1), 0px 7px 5px 0px rgba(15, 89, 255, 0.15), 0px 13px 10px 0px rgba(15, 89, 255, 0.18), 0px 22px 18px 0px rgba(15, 89, 255, 0.21), 0px 42px 33px 0px rgba(15, 89, 255, 0.26), 0px 100px 80px 0px rgba(15, 89, 255, 0.36);
- border-radius: 5px 5px 0px 0px;
- }
-
- &::before {
- content: "";
- position: absolute;
- left: 55px;
- right: 0;
- bottom: 0;
- height: 2px;
- background: #323245;
- border-radius: 0px 0px 5px 5px;
- }
-
- .device-popup__title-tooltip {
- flex: 1;
- min-width: 0;
- display: block;
- }
-
- .device-popup__title {
- display: inline-block;
- max-width: 100%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .device-popup__close {
- cursor: pointer;
- }
- }
-
- .device-popup__type {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- margin-top: 8px;
- margin-bottom: 10px;
- color: #d2d7df;
-
- .label {
- color: #9aa4b2;
- }
- }
-
- .device-popup__subtitle {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 2px 10px;
- border-radius: 999px;
- background: rgba(255, 255, 255, 0.06);
- font-size: 12px;
- margin-bottom: 10px;
-
- .device-popup__dot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background: #6b7685;
-
- &.is-detecting {
- background: #35f08c;
- box-shadow: 0 0 6px rgba(53, 240, 140, 0.8);
- }
-
- &.is-jamming {
- background: #ff4444;
- box-shadow: 0 0 6px rgba(255, 68, 68, 0.7);
- }
-
- &.is-driving {
- background: #ffb020;
- box-shadow: 0 0 6px rgba(255, 176, 32, 0.7);
- }
-
- &.is-standby {
- background: #9aa4b2;
- }
- }
- }
-
- .device-popup__rows {
- display: flex;
- flex-direction: column;
- gap: 6px;
- font-size: 12px;
- color: #d2d7df;
-
- .device-popup__row {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
-
- .device-popup__col {
- flex: 1;
- display: flex;
- align-items: center;
-
- &:last-child {
- justify-content: flex-end;
- }
- }
- }
-
- .value {
- margin-left: 10px;
- }
-
- .value.online {
- color: #35f08c;
- }
- }
-
}
.layer-control-root {
diff --git a/applications/drone-command/src/components/map-container/device-map-materials.js b/applications/drone-command/src/components/map-container/device-map-materials.js
new file mode 100644
index 0000000..bf2edc8
--- /dev/null
+++ b/applications/drone-command/src/components/map-container/device-map-materials.js
@@ -0,0 +1,188 @@
+import * as Cesium from 'cesium'
+
+export const RING_STYLES = [{ inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] }]
+
+export const MATERIAL_TYPE = 'RadialGradientMaterial'
+export const DRONE_TRACK_MATERIAL_TYPE = 'DroneTrackFlowMaterial'
+
+let materialRegistered = false
+let droneTrackMaterialRegistered = false
+let texturedVertexFormat = null
+
+export const getTexturedVertexFormat = () => {
+ if (!texturedVertexFormat) {
+ texturedVertexFormat =
+ Cesium.MaterialAppearance?.MaterialSupport?.TEXTURED?.vertexFormat || Cesium.VertexFormat.POSITION_AND_ST
+ }
+ return texturedVertexFormat
+}
+
+export const registerRadialGradientMaterial = () => {
+ if (materialRegistered || !Cesium?.Material) return
+ materialRegistered = true
+ Cesium.Material._materialCache.addMaterial(MATERIAL_TYPE, {
+ fabric: {
+ type: MATERIAL_TYPE,
+ uniforms: {
+ color1: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
+ color2: new Cesium.Color(0.0, 0.0, 0.0, 1.0),
+ },
+ source: `
+ czm_material czm_getMaterial(czm_materialInput materialInput) {
+ czm_material material = czm_getDefaultMaterial(materialInput);
+ vec2 st = materialInput.st - vec2(0.5);
+ float t = clamp(length(st) * 2.0, 0.0, 1.0);
+ vec4 color = mix(color1, color2, t);
+ material.diffuse = color.rgb;
+ material.alpha = color.a;
+ return material;
+ }
+ `,
+ },
+ translucent: () => true,
+ })
+}
+
+export const registerDroneTrackMaterial = () => {
+ if (droneTrackMaterialRegistered || !Cesium?.Material) return
+ droneTrackMaterialRegistered = true
+ Cesium.Material._materialCache.addMaterial(DRONE_TRACK_MATERIAL_TYPE, {
+ fabric: {
+ type: DRONE_TRACK_MATERIAL_TYPE,
+ uniforms: {
+ color: new Cesium.Color(1.0, 0.28, 0.18, 1.0),
+ speed: 4.0,
+ headWidth: 0.18,
+ glowPower: 1.6,
+ backgroundAlpha: 0.22,
+ },
+ source: `
+ uniform vec4 color;
+ uniform float speed;
+ uniform float headWidth;
+ uniform float glowPower;
+ uniform float backgroundAlpha;
+
+ czm_material czm_getMaterial(czm_materialInput materialInput) {
+ czm_material material = czm_getDefaultMaterial(materialInput);
+ vec2 st = materialInput.st;
+ float time = fract(czm_frameNumber * speed / 1000.0);
+ float flow = fract(st.s - time);
+ float head = smoothstep(headWidth, 0.0, flow) * smoothstep(0.0, headWidth, flow);
+ float alpha = mix(backgroundAlpha, 1.0, head);
+ vec3 rgb = color.rgb;
+ material.diffuse = rgb;
+ material.emission = rgb * alpha * glowPower;
+ material.alpha = color.a * alpha;
+ return material;
+ }
+ `,
+ },
+ translucent: () => true,
+ })
+}
+
+export const createRadialGradientMaterial = (color1, color2) => {
+ registerRadialGradientMaterial()
+ return Cesium.Material.fromType(MATERIAL_TYPE, { color1, color2 })
+}
+
+export const createDroneTrackMaterial = options => {
+ registerDroneTrackMaterial()
+ return Cesium.Material.fromType(DRONE_TRACK_MATERIAL_TYPE, {
+ color: new Cesium.Color(1.0, 0.28, 0.18, 1.0),
+ speed: 4.0,
+ headWidth: 0.18,
+ glowPower: 1.6,
+ backgroundAlpha: 0.22,
+ ...(options || {}),
+ })
+}
+
+export const buildCirclePositions = (center, radiusMeters, steps = 64) => {
+ const positions = []
+ const latRad = Cesium.Math.toRadians(center.latitude)
+ const metersToLat = 1 / 111320
+ const metersToLon = 1 / (111320 * Math.cos(latRad))
+ for (let i = 0; i <= steps; i += 1) {
+ const angle = Cesium.Math.toRadians((i / steps) * 360)
+ const dx = radiusMeters * Math.cos(angle)
+ const dy = radiusMeters * Math.sin(angle)
+ const lon = center.longitude + dx * metersToLon
+ const lat = center.latitude + dy * metersToLat
+ positions.push(Cesium.Cartesian3.fromDegrees(lon, lat))
+ }
+ return positions
+}
+
+export const addDeviceRings = (center, ringFillInstancesByStyle, ringOutlineInstancesByStyle) => {
+ if (!ringFillInstancesByStyle) return
+ const centerPosition = Cesium.Cartesian3.fromDegrees(center.longitude, center.latitude, 0)
+ const vertexFormat = getTexturedVertexFormat()
+ RING_STYLES.forEach((ring, index) => {
+ if (!ringFillInstancesByStyle[index]) return
+ ringFillInstancesByStyle[index].push(
+ new Cesium.GeometryInstance({
+ geometry: new Cesium.EllipseGeometry({
+ center: centerPosition,
+ semiMajorAxis: ring.outer,
+ semiMinorAxis: ring.outer,
+ vertexFormat,
+ }),
+ })
+ )
+ })
+ if (!ringOutlineInstancesByStyle) return
+ RING_STYLES.forEach((ring, index) => {
+ if (!ringOutlineInstancesByStyle[index]) return
+ const positions = buildCirclePositions(center, ring.outer)
+ ringOutlineInstancesByStyle[index].push(
+ new Cesium.GeometryInstance({
+ geometry: new Cesium.GroundPolylineGeometry({
+ positions,
+ width: 2,
+ }),
+ attributes: {
+ color: Cesium.ColorGeometryInstanceAttribute.fromColor(
+ Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.8)
+ ),
+ },
+ })
+ )
+ })
+}
+
+export class RadialGradientMaterialProperty {
+ constructor(color1, color2) {
+ this._definitionChanged = new Cesium.Event()
+ this.color1 = color1
+ this.color2 = color2
+ }
+
+ get isConstant () {
+ return true
+ }
+
+ get definitionChanged () {
+ return this._definitionChanged
+ }
+
+ getType () {
+ return MATERIAL_TYPE
+ }
+
+ getValue (time, result) {
+ const target = result || {}
+ target.color1 = this.color1
+ target.color2 = this.color2
+ return target
+ }
+
+ equals (other) {
+ return (
+ other instanceof RadialGradientMaterialProperty &&
+ Cesium.Color.equals(this.color1, other.color1) &&
+ Cesium.Color.equals(this.color2, other.color2)
+ )
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/areaStatistics/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/areaStatistics/FormDiaLog.vue
index c3ec2e5..5ad124e 100644
--- a/applications/drone-command/src/views/areaManage/areaStatistics/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/areaStatistics/FormDiaLog.vue
@@ -42,7 +42,7 @@
</el-row>
</div>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">关闭</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">关闭</el-button>
</template>
</el-dialog>
</template>
diff --git a/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue
index 763d8d7..6b799c1 100644
--- a/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue
@@ -85,7 +85,7 @@
</el-form>
<div class="footer">
- <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
:disabled="submitting" @click="handleSubmit">
确定
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 00e1113..0b2786e 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -154,7 +154,7 @@
</el-form>
<div class="footer">
- <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
:disabled="submitting" @click="handleSubmit">
确定
diff --git a/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
index de089a8..d76eae9 100644
--- a/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
@@ -52,7 +52,7 @@
</el-row>
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting"
@click="handleSubmit">
确定
diff --git a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
index b6558c9..e546c27 100644
--- a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
@@ -129,7 +129,7 @@
</el-form>
<div class="footer">
- <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
:disabled="submitting" @click="handleSubmit">
确定
diff --git a/applications/drone-command/src/views/basicManage/deviceScrap/FormDiaLog.vue b/applications/drone-command/src/views/basicManage/deviceScrap/FormDiaLog.vue
index cb60414..7e3ef76 100644
--- a/applications/drone-command/src/views/basicManage/deviceScrap/FormDiaLog.vue
+++ b/applications/drone-command/src/views/basicManage/deviceScrap/FormDiaLog.vue
@@ -50,7 +50,7 @@
</el-row>
</div>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
</template>
</el-dialog>
</template>
diff --git a/applications/drone-command/src/views/basicManage/deviceStock/DeviceScrapDiaLog.vue b/applications/drone-command/src/views/basicManage/deviceStock/DeviceScrapDiaLog.vue
index 38e194f..ad4958c 100644
--- a/applications/drone-command/src/views/basicManage/deviceStock/DeviceScrapDiaLog.vue
+++ b/applications/drone-command/src/views/basicManage/deviceStock/DeviceScrapDiaLog.vue
@@ -16,7 +16,7 @@
</el-row>
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button
v-if="!dialogReadonly"
type="primary"
diff --git a/applications/drone-command/src/views/basicManage/deviceStock/DeviceTrackDiaLog.vue b/applications/drone-command/src/views/basicManage/deviceStock/DeviceTrackDiaLog.vue
index dac459c..1f905b1 100644
--- a/applications/drone-command/src/views/basicManage/deviceStock/DeviceTrackDiaLog.vue
+++ b/applications/drone-command/src/views/basicManage/deviceStock/DeviceTrackDiaLog.vue
@@ -47,7 +47,7 @@
</el-row>
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button
v-if="!dialogReadonly"
type="primary"
diff --git a/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue b/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
index c115e2e..b570b0f 100644
--- a/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
+++ b/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
@@ -162,7 +162,7 @@
</el-row>
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!dialogReadonly" type="primary" :loading="submitting"
:disabled="submitting" @click="handleSubmit">
保存
diff --git a/applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue b/applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue
index e9ffb94..adc1346 100644
--- a/applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue
+++ b/applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue
@@ -112,7 +112,7 @@
</el-row>
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!dialogReadonly" type="primary" :loading="submitting"
:disabled="submitting" @click="handleSubmit">
保存
diff --git a/applications/drone-command/src/views/basicManage/maintainRecord/MaintenanceDiaLog.vue b/applications/drone-command/src/views/basicManage/maintainRecord/MaintenanceDiaLog.vue
index db0e3fd..5d7d61f 100644
--- a/applications/drone-command/src/views/basicManage/maintainRecord/MaintenanceDiaLog.vue
+++ b/applications/drone-command/src/views/basicManage/maintainRecord/MaintenanceDiaLog.vue
@@ -56,7 +56,7 @@
</el-row>
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button
v-if="!dialogReadonly"
type="primary"
diff --git a/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue b/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue
index 79119bf..e834cff 100644
--- a/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue
@@ -18,9 +18,12 @@
</div>
<div class="actions">
- <span class="star" @click="emit('favorite')">
- {{ data.isFavorite ? '★' : '☆' }}
- </span>
+ <img
+ class="favorite-icon"
+ :src="favoriteIcon"
+ alt="收藏"
+ @click="emit('favorite')"
+ />
</div>
</div>
@@ -67,8 +70,11 @@
</template>
<script setup>
+import { computed } from 'vue'
import { saveOperationLog } from '@ztzf/apis'
import { useRoute } from 'vue-router'
+import favoriteNo from '@/assets/images/dataCockpit/favorite-n.png'
+import favoriteYes from '@/assets/images/dataCockpit/favorite-y.png'
const props = defineProps({
data: {
@@ -80,6 +86,7 @@
const route = useRoute()
const emit = defineEmits(['signal', 'counter', 'favorite'])
+const favoriteIcon = computed(() => (props.data?.isFavorite ? favoriteYes : favoriteNo))
// 信号干扰操作
const handleSignal = () => {
@@ -250,6 +257,11 @@
gap: 8px;
font-size: 16px;
cursor: pointer;
+
+ .favorite-icon {
+ width: 16px;
+ height: 16px;
+ }
}
}
@@ -296,4 +308,4 @@
}
}
}
-</style>
\ No newline at end of file
+</style>
diff --git a/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue b/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue
index 28dafb2..edd2ae5 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue
@@ -67,7 +67,7 @@
</el-row>
</div>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">关闭</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">关闭</el-button>
</template>
</el-dialog>
</template>
diff --git a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
index 176171b..79a1287 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
@@ -129,7 +129,7 @@
</el-form-item>
</el-form>
<div class="footer">
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button
color="#284FE3"
v-if="!dialogReadonly"
diff --git a/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue b/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue
index 9cde3af..eeab733 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue
@@ -152,7 +152,7 @@
</el-row>
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
<el-button
color="#284FE3"
v-if="!readonly"
diff --git a/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue b/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue
index c9f1591..a217adb 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue
@@ -142,7 +142,7 @@
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting"
@click="handleSubmit">
保存
diff --git a/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue b/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
index 74c37cd..95955ad 100644
--- a/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
+++ b/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
@@ -115,7 +115,7 @@
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!dialogReadonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">
保存
</el-button>
diff --git a/applications/drone-command/src/views/permissionManage/permissionRole/FormDiaLog.vue b/applications/drone-command/src/views/permissionManage/permissionRole/FormDiaLog.vue
index 66d2840..0622c6f 100644
--- a/applications/drone-command/src/views/permissionManage/permissionRole/FormDiaLog.vue
+++ b/applications/drone-command/src/views/permissionManage/permissionRole/FormDiaLog.vue
@@ -57,7 +57,7 @@
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!dialogReadonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">
保存
</el-button>
diff --git a/applications/drone-command/src/views/permissionManage/permissionUser/FormDiaLog.vue b/applications/drone-command/src/views/permissionManage/permissionUser/FormDiaLog.vue
index 4cea05b..0aca836 100644
--- a/applications/drone-command/src/views/permissionManage/permissionUser/FormDiaLog.vue
+++ b/applications/drone-command/src/views/permissionManage/permissionUser/FormDiaLog.vue
@@ -231,7 +231,7 @@
</el-form>
<template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
<el-button color="#284FE3" v-if="!dialogReadonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">
保存
</el-button>
diff --git a/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue b/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
index 4c18aaa..869bdae 100644
--- a/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
+++ b/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
@@ -56,7 +56,7 @@
</div>
</div>
<div class="footer">
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
</div>
</div>
</div>
--
Gitblit v1.9.3