<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)
|
return `${absVal}`
|
}
|
|
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>
|