diff --git a/.gitignore b/.gitignore index 57494a3..bf3ef72 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /dist /package-lock.json /src/components/statistics.vue +/src/components/SwarmStatus.vue diff --git a/src/components/MapBox.vue b/src/components/MapBox.vue index db225a7..2ed2b08 100644 --- a/src/components/MapBox.vue +++ b/src/components/MapBox.vue @@ -882,19 +882,30 @@ export default { */ makePlane (plane, index = 0) { const customIcon = document.createElement('div') - customIcon.className = 'custom-marker' // 添加自定义样式类名 - customIcon.style.backgroundImage = `url(${planeIcon})` // 使用引入的 SVG 图标 - customIcon.style.width = '64px' // 图标宽度 - customIcon.style.height = '64px' // 图标高度 - // 创建一个marker对象 - this.planes[index] = new mapboxgl.Marker(customIcon) + customIcon.className = 'custom-marker' + customIcon.style.backgroundImage = `url(${planeIcon})` + customIcon.style.width = '64px' + customIcon.style.height = '64px' + + const marker = new mapboxgl.Marker(customIcon) .setLngLat([plane.lon, plane.lat]) - .setPopup( - new mapboxgl.Popup({ offset: 25 }).setHTML( - '

' + plane.name + '


macID:' + plane.macadd + '

' - ) - ) // 添加弹出窗口 .addTo(this.map) + + // 创建popup,但不绑定到marker,手动控制显示 + const popup = new mapboxgl.Popup({ + offset: 25, + closeButton: true, + closeOnClick: true + }).setHTML(`

${plane.name}

`) + marker.setPopup(popup) + + // 点击图标,弹出对应popup(每次点击都打开对应popup) + customIcon.addEventListener('click', (e) => { + e.stopPropagation() // 阻止地图的click事件影响 + popup.setLngLat([plane.lon, plane.lat]).addTo(this.map) + }) + + this.planes[index] = marker // 存储marker到公用数组中 }, /** * @description: 移除页面上的所有飞机 @@ -979,6 +990,70 @@ export default { }) } }, + /** + * @description 实时更新地图上指定飞机图标的弹窗内容(Popup) + * @param {Object} stateObj 飞机状态对象,包含飞机的状态信息 + * @param {number} index 飞机对象索引 ps:用这个索引 取本组件组件共有变量planes对象组 指定对象 + */ + updatePopupContent (stateObj, index = 0) { + const plane = this.planes[index] + if (!plane) return + + // 选出最后一条位置数据 + const lastPos = stateObj.position?.[stateObj.position.length - 1] ?? [] + + // 心跳图标样式 + const heartClass = stateObj.online + ? 'heart-icon heart-online' + : 'heart-icon heart-offline' + + // 拼接弹窗内容 HTML + const popupContent = ` + + +

${stateObj.name}

+
+

心跳:

+

解锁状态: + + ${stateObj.isUnlock ? '已解锁' : '未解锁'} + +

+

模式: ${stateObj.getPlaneMode ?? '未知'}

+

定位状态: ${stateObj.fixType ?? '--'}

+

卫星颗数: ${stateObj.satCount ?? '--'}

+

电压: ${stateObj.voltagBattery ?? '--'} V

+

电流: ${stateObj.currentBattery ?? '--'} A

+

高度: ${lastPos[2] ?? '--'} 米

+

纬度: ${lastPos[0] ?? '--'}°

+

经度: ${lastPos[1] ?? '--'}°

+

对地速度: ${stateObj.groundSpeed ?? '--'} 米/秒

+ ` + + const popup = plane.getPopup() + if (popup) { + popup.setHTML(popupContent) + } + }, /** * @description: 镜头跳转 * @param {obj} lonLat {lon:lon,lat:lat} 经纬度 @@ -1047,4 +1122,27 @@ export default { .adsb-icon { will-change: transform; } + +/*飞机popup 心跳图标样式*/ +@keyframes heartbeat { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.3); } +} + +.heart-icon { + display: inline-block; + width: 16px; + height: 16px; + margin-left: 5px; + border-radius: 50%; +} + +.heart-online { + background-color: green; + animation: heartbeat 1s infinite; +} + +.heart-offline { + background-color: gray; +} diff --git a/src/components/PlaneStatus.vue b/src/components/PlaneStatus.vue index aca87cd..7392a2f 100644 --- a/src/components/PlaneStatus.vue +++ b/src/components/PlaneStatus.vue @@ -2,11 +2,7 @@
-
-
+
@@ -95,7 +91,10 @@ export default { computed: { // 飞机在线状态 online () { - return this.plane?.online ?? false + if (this.plane && this.plane.planeState) { + return this.plane.planeState.online + } + return false }, // 心跳随机数 heartRandom () { @@ -104,9 +103,21 @@ export default { } return null }, + heartIconClass () { + if (!this.online) { + return 'icon-xinsui offline' + } + return this.heartAnimation ? 'icon-heart online' : 'icon-heart1 online' + }, + usefulHeartClass () { + return this.online && this.plane.planeState.heartBeat ? 'useful-heart' : '' + }, // 解锁状态 isUnlock () { - return this.plane?.planeState?.isUnlock ?? false + if (this.plane && this.plane.planeState) { + return this.plane.planeState.isUnlock + } + return false }, // 飞机模式 getPlaneMode () { diff --git a/src/components/Statistics.vue b/src/components/Statistics.vue index 9b86211..c031576 100644 --- a/src/components/Statistics.vue +++ b/src/components/Statistics.vue @@ -48,7 +48,7 @@ export default { }, props: { planes: { - type: Object, + type: Array, deep: true } }, diff --git a/src/components/SwarmStatus.vue b/src/components/SwarmStatus.vue deleted file mode 100644 index 30c9342..0000000 --- a/src/components/SwarmStatus.vue +++ /dev/null @@ -1,254 +0,0 @@ - - - - - diff --git a/src/views/layout/components/main/home/index.vue b/src/views/layout/components/main/home/index.vue index 194d8f8..7c54e44 100644 --- a/src/views/layout/components/main/home/index.vue +++ b/src/views/layout/components/main/home/index.vue @@ -30,6 +30,10 @@ export default { airList () { return this.$store.state.airList }, + // 过滤出所有飞机状态列表s + planeStatus () { + return this.airList.map(plane => plane.planeState) + }, /** * @description: 侧边栏显隐 */ @@ -82,6 +86,16 @@ export default { }, immediate: true }, + // 实时更新所有状态 到对应飞机弹出框 + planeStatus: { + handler (val) { + val.forEach((stateObj, index) => { + stateObj.name = this.airList[index].name // 保留飞机名称 + this.$refs.mapbox.updatePopupContent(stateObj, index) + }) + }, + deep: true + }, /** * @description: 侧边栏显隐 */ diff --git a/src/views/layout/components/main/home/set.vue b/src/views/layout/components/main/home/set.vue index 8c07a9e..1d8ec97 100644 --- a/src/views/layout/components/main/home/set.vue +++ b/src/views/layout/components/main/home/set.vue @@ -48,16 +48,18 @@ export default { currentLang: this.$store.state.settings.language || 'zh-CN', moduleOptions: [ { value: 'home', label: '概况' }, - { value: 'model', label: '机型管理' }, - { value: 'register', label: '飞机管理' }, + { value: 'model', label: '机型列表' }, + { value: 'register', label: '飞机列表' }, { value: 'nofly', label: '飞行限制' }, { value: 'route', label: '航线管理' }, { value: 'site', label: '站点管理' }, { value: 'planes', label: '无人机' }, { value: 'shop', label: '商铺管理' }, - { value: 'admin', label: '管理员管理' }, + { value: 'admin', label: '账户列表' }, { value: 'category', label: '分类管理' }, - { value: 'product', label: '商品管理' } + { value: 'product', label: '商品管理' }, + { value: 'broadcast', label: '广告管理' }, + { value: 'order', label: '订单与统计' } ], selectedModules: [] } diff --git a/src/views/layout/components/main/planes/index.vue b/src/views/layout/components/main/planes/index.vue index c9922ee..a6e12d0 100644 --- a/src/views/layout/components/main/planes/index.vue +++ b/src/views/layout/components/main/planes/index.vue @@ -80,6 +80,13 @@ export default { } return null }, + planeState () { + if (this.plane && this.plane.planeState) { + return this.plane.planeState + } else { + return {} + } + }, position () { if (this.plane) { if (this.plane.planeState.position.length > 0) { @@ -211,6 +218,7 @@ export default { mounted () { }, watch: { + // 这里不要deep:true 会一直新创飞机图标 plane: { async handler () { try { @@ -218,12 +226,24 @@ export default { await waitForMapCanvasReady(this.$refs.mapbox.map) // 画布准备好后执行你自己的逻辑 this.onMapReady() + console.warn('飞机数据更新', this.plane) } catch (err) { console.debug('等待地图画布准备超时', err) } }, immediate: true }, + // 实时更新飞机状态 到 到飞机弹出框 + planeState: { + handler (val) { + if (val) { + val.name = this.plane.name // 保留飞机名称 + // 调用更新 popup 内容方法 + this.$refs.mapbox.updatePopupContent(val) + } + }, + deep: true + }, /** * @description: 更新飞机位置 并画出轨迹 跟随飞机 */ diff --git a/src/views/layout/components/main/planes/swarm.vue b/src/views/layout/components/main/planes/swarm.vue index 1c3b01e..3e05a7a 100644 --- a/src/views/layout/components/main/planes/swarm.vue +++ b/src/views/layout/components/main/planes/swarm.vue @@ -1,11 +1,10 @@