【类 型】:feat

【原  因】:
【过  程】:1.优化飞机状态组件 在飞将列表里面加入 在线字段 解锁字段 2.增加概况多架飞机 状态组件
【影  响】:

# 类型 包含:
# feat:新功能(feature)
# fix:修补bug
# docs:文档(documentation)
# style: 格式(不影响代码运行的变动)
# refactor:重构(即不是新增功能,也不是修改bug的代码变动)
# test:增加测试
# chore:构建过程或辅助工具的变动
This commit is contained in:
szdot 2025-06-23 01:00:21 +08:00
parent e7506728af
commit e14aa9a975
9 changed files with 140 additions and 51 deletions

View File

@ -8,6 +8,7 @@
import mapboxgl from 'mapbox-gl'
import { MapboxStyleSwitcherControl, FollowControl, CustomFullscreenControl, NoFlyControl, RestrictflyControl, SaveToFileControl, PolygonToggleControl } from '@/utils/mapboxgl_plugs'
import planeIcon from '@/assets/svg/plane.svg'
// import unlineIcon from '@/assets/svg/plane_unline.svg'
import civilIcon from '@/assets/svg/civil.svg'
export default {

View File

@ -1,15 +1,16 @@
<template>
<div class="mainBox flex column no-select">
<!-- 心跳 -->
<!-- 心跳 ps:黑色只网络通 绿色飞控有效心跳-->
<div class="flex">
<div class="tag flex mac mc iconfont"
:class="online ? heartAnimation ? 'icon-heart online' : 'icon-heart1 online' : 'icon-xinsui offline'">
<div class="tag flex mac mc iconfont" :class="[
online ? (heartAnimation ? 'icon-heart online' : 'icon-heart1 online') : 'icon-xinsui offline',
plane.heartBeat ? 'useful-heart' : ''
]">
</div>
</div>
<!-- 状态 -->
<!-- 锁状态 -->
<div class="flex">
<div class="tag flex mac mc iconfont" :class="isLockState ? 'icon-suoding' : 'icon-jiesuo'">
</div>
<div class="tag flex mac mc iconfont" :class="isUnlock ? 'icon-jiesuo' : 'icon-suoding'"></div>
</div>
<!-- 飞机模式 -->
<div class="flex">
@ -22,7 +23,7 @@
<!-- 卫星 -->
<div class="flex">
<div v-if="satCount" class="plane-mode p-l-5 p-r-5 mc mac">
<font class="plane-mode-text">{{ fixType }} {{ satCount }}</font>
<font class="plane-mode-text">{{ fixType }} {{ satCount }}</font>
</div>
<div class="tag flex mac mc iconfont icon-weixing">
</div>
@ -62,7 +63,9 @@
<!-- 飞机载重 钩子状态 -->
<div class="flex">
<div v-if="loadweight" class="plane-mode p-l-5 p-r-5 mc mac">
<font class="plane-mode-text">{{hookstatus}} {{ loadweight }}</font>
<font class="plane-mode-text" v-if="hookstatus || loadweight">
{{ hookstatus || '' }} {{ loadweight ? loadweight + '克' : '' }}
</font>
</div>
<div class="tag flex mac mc iconfont icon-mianxingdiaogou">
</div>
@ -78,9 +81,7 @@ export default {
data () {
return {
/* 心跳 */
heartAnimation: false, //
online: false,
isOnlineSetTimeout: null
heartAnimation: false //
}
},
props: {
@ -92,6 +93,10 @@ export default {
components: {
},
computed: {
// 线
online () {
return this.plane?.online ?? false
},
//
heartRandom () {
if (this.plane && this.plane.planeState) {
@ -99,14 +104,9 @@ export default {
}
return null
},
//
isLockState () {
if (this.plane && this.plane.planeState) {
if (Number(this.plane.planeState.heartBeat) & 128) {
return false
}
}
return true
//
isUnlock () {
return this.plane?.planeState?.isUnlock ?? false
},
//
getPlaneMode () {
@ -180,20 +180,11 @@ export default {
watch: {
heartRandom: {
handler () {
console.log('心跳:', this.plane.heartBeat)
//
this.heartAnimation = true
setTimeout(() => {
this.heartAnimation = false
}, 500)
// 线
if (this.isOnlineSetTimeout) { // 线
clearInterval(this.isOnlineSetTimeout)
}
this.online = true
this.isOnlineSetTimeout = setTimeout(() => { // 10 线
this.online = false
}, 10000)
}
}
},
@ -203,9 +194,6 @@ export default {
created () {
},
destroyed () {
if (this.isOnlineSetTimeout) {
clearInterval(this.isOnlineSetTimeout)
}
}
}
@ -249,6 +237,11 @@ export default {
.plane-mode-text {
display: inline-block;
white-space: nowrap; /* 防止内容换行 */
white-space: nowrap;
/* 防止内容换行 */
}
.useful-heart {
color: $success-color;
}
</style>

View File

@ -1,17 +1,44 @@
<template>
<div class="mainBox flex column no-select">
<!-- 心跳 -->
<!-- <div class="flex">
<div class="tag flex mac mc iconfont"
:class="online ? heartAnimation ? 'icon-heart online' : 'icon-heart1 online' : 'icon-xinsui offline'">
<div class="flex stat-row">
<div class="plane-mode p-l-5 p-r-5 mc mac">
<font class="plane-mode-text">总架数{{ totalCount }}</font>
</div>
</div> -->
<div class="tag flex mac mc iconfont icon-zongshu"></div>
</div>
<div class="flex stat-row">
<div class="plane-mode p-l-5 p-r-5 mc mac">
<font class="plane-mode-text">在线数{{ onlineCount }}</font>
</div>
<div class="tag flex mac mc iconfont icon-zaixian1"></div>
</div>
<div class="flex stat-row">
<div class="plane-mode p-l-5 p-r-5 mc mac">
<font class="plane-mode-text">总作业架数{{ unlockedCount }}</font>
</div>
<div class="tag flex mac mc iconfont icon-wurenjijiesuo"></div>
</div>
<div class="flex stat-row">
<div class="plane-mode p-l-5 p-r-5 mc mac">
<font class="plane-mode-text">总作业时长{{ formattedDuration }}</font>
</div>
<div class="tag flex mac mc iconfont icon-shichang"></div>
</div>
<div class="flex stat-row">
<div class="plane-mode p-l-5 p-r-5 mc mac">
<font class="plane-mode-text">作业总数{{ totalWorkingDistance.toFixed(1) }} </font>
</div>
<div class="tag flex mac mc iconfont icon-pin-distance-line"></div>
</div>
</div>
</template>
<script>
import geodist from 'geodist'
export default {
name: 'Statistics',
@ -20,7 +47,7 @@ export default {
}
},
props: {
plane: {
planes: {
type: Object,
deep: true
}
@ -28,6 +55,52 @@ export default {
components: {
},
computed: {
totalCount () {
return Object.keys(this.planes || {}).length
},
onlineCount () {
return Object.values(this.planes || {}).filter(p => p.planeState?.online).length
},
totalWorkingDuration () {
// - startTime
const now = Math.floor(Date.now() / 1000)
return Object.values(this.planes || {}).reduce((total, p) => {
const s = p.planeState?.flyDataSave?.startTime
const isUnlock = p.planeState?.isUnlock
if (isUnlock && s) {
return total + (now - s)
}
return total
}, 0)
},
totalWorkingDistance () {
let totalDistance = 0
Object.values(this.planes || {}).forEach(p => {
const path = p.planeState?.flyDataSave?.path || []
for (let i = 1; i < path.length; i++) {
const prev = path[i - 1]
const curr = path[i]
if (prev && curr) {
totalDistance += geodist(
{ lat: prev[1], lon: prev[0] },
{ lat: curr[1], lon: curr[0] },
{ exact: true, unit: 'meters' }
)
}
}
})
return totalDistance
},
unlockedCount () {
return Object.values(this.planes || {}).filter(p => p.planeState?.isUnlock).length
},
formattedDuration () {
const sec = this.totalWorkingDuration
const h = Math.floor(sec / 3600).toString().padStart(2, '0')
const m = Math.floor((sec % 3600) / 60).toString().padStart(2, '0')
const s = (sec % 60).toString().padStart(2, '0')
return `${h}:${m}:${s}`
}
},
watch: {
@ -83,4 +156,11 @@ export default {
display: inline-block;
white-space: nowrap; /* 防止内容换行 */
}
.item {
padding: 0 6px;
height: 29px;
line-height: 29px;
font-size: 13px;
}
</style>

View File

@ -65,7 +65,7 @@ const routes = [
redirect: '/model/index',
meta: {
title: '机型管理',
icon: 'el-icon-edit-outline',
icon: 'iconfont icon-chuiqigudingyi',
roles: ['admin', 'editor'],
tapName: 'plane'
},

View File

@ -348,7 +348,7 @@ const store = new Vuex.Store({
const res = await api.get('getAirList')
res.data.airList.forEach(plane => {
plane.planeState = { // 飞机状态初始化字段
flyDataMark: false, // 飞机解锁标记成真
isUnlock: false, // 飞机解锁标记成真
flyDataSave: { // 飞机加锁截至 待上传飞行数据之后 再清空此值
startTime: null, // 解锁时的时间戳(秒)
endTime: null, // 加锁时的时间戳(秒)
@ -358,6 +358,8 @@ const store = new Vuex.Store({
},
heartBeat: null, // 心跳
heartRandom: null, // 每次接收到心跳创建一个随机数 用于watch监听
online: false, // 是否在线
onlineTimeout: null, // 存放定时器引用
voltagBattery: null, // 电压信息
currentBattery: null, // 电流信息
batteryRemaining: null, // 电池电量

View File

@ -1 +1 @@
@import 'https://at.alicdn.com/t/c/font_3703467_cqwk36imkj.css'; //iconfont阿里巴巴
@import 'https://at.alicdn.com/t/c/font_3703467_17bw22w7wt3g.css'; //iconfont阿里巴巴

View File

@ -3,7 +3,7 @@
<map-box ref="mapbox" @map-ready="onMapReady">
<template #content>
<div v-show="mapReady">
<!-- <Statistics :plane="plane" /> -->
<Statistics :planes="airList" />
</div>
</template>
</map-box>
@ -13,7 +13,7 @@
<script>
import MapBox from '@/components/MapBox'
import { waitForMapCanvasReady } from '@/utils'
// import Statistics from '@/components/Statistics'
import Statistics from '@/components/Statistics'
export default {
name: 'Home',
@ -23,8 +23,8 @@ export default {
}
},
components: {
MapBox
// Statistics
MapBox,
Statistics
},
computed: {
airList () {

View File

@ -6,7 +6,7 @@
<template #content>
<div v-show="mapReady">
<!-- <SwarmStatus :planes="planeList" /> -->
<SwarmControllerTabs :planes="planeList" @mapXOffset="mapXOffset" @makeRoute="makeRoute" @clearRoute="clearRoute" />
<SwarmControllerTabs :planes="planeList"/>
</div>
</template>
</map-box>

View File

@ -151,13 +151,26 @@ export default {
plane.planeState.heartRandom = Math.random()
plane.planeState.heartBeat = jsonData.heartBeat
const oldMark = plane.planeState.flyDataMark
/* 判断飞机是否在线 */
// 线
plane.planeState.online = true
// 线
if (plane.planeState.onlineTimeout) {
clearTimeout(plane.planeState.onlineTimeout)
}
// 线10线
plane.planeState.onlineTimeout = setTimeout(() => {
plane.planeState.online = false
}, 10000)
/* 飞行数据上传 */
const oldMark = plane.planeState.isUnlock
const newMark = (Number(jsonData.heartBeat) & 128) !== 0
//
if (!oldMark && newMark) {
//
plane.planeState.flyDataMark = true
plane.planeState.isUnlock = true
plane.planeState.flyDataSave = {
startTime: Math.floor(Date.now() / 1000), //
startBattery: plane.planeState.batteryRemaining,
@ -166,7 +179,7 @@ export default {
path: []
}
} else if (oldMark && !newMark) {
plane.planeState.flyDataMark = false
plane.planeState.isUnlock = false
this.handleFlightDataUpload(plane) //
@ -192,7 +205,7 @@ export default {
plane.planeState.position.shift() //
}
//
if (plane.planeState.flyDataMark && plane.planeState.flyDataSave?.path) {
if (plane.planeState.isUnlock && plane.planeState.flyDataSave?.path) {
plane.planeState.flyDataSave.path.push([
position.lon / 10e6,
position.lat / 10e6,