【类 型】:feat

【原  因】:飞机控制页面 增加 禁飞区限飞区 显示隐藏的控件
【过  程】:
【影  响】:

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

View File

@ -172,7 +172,7 @@ export default {
type: Boolean,
default: false
},
showNofly: { //
enableShowNofly: { //
type: Boolean,
default: false
}
@ -366,17 +366,41 @@ export default {
}
//
if (this.showNofly) {
if (this.enableShowNofly) {
const noflyPolygons = this.$store.state.noflyData[0] || []
const restrictflyPolygons = this.$store.state.noflyData[1] || []
this.map.addControl(new PolygonToggleControl({
defaultIconClass: 'iconfont icon-polygon-default f-s-20',
activeIconClass: 'iconfont icon-polygon-active f-s-20 brandFontColor',
noflyPolygons,
restrictflyPolygons,
onDraw: (active) => {
console.log('禁飞/限飞区域控件状态:', active)
defaultIconClass: 'iconfont icon-jinfeiqu_weidianji f-s-20 seatFontColor',
activeIconClass: 'iconfont icon-jinfeiqu f-s-20 brandFontColor',
onToggle: (isActive) => {
if (isActive) {
//
noflyPolygons.forEach((coords, i) => {
this.drawPolygonWithLabel(coords, `nofly-polygon-${i}`, '#F33', '禁飞区')
})
//
restrictflyPolygons.forEach((coords, i) => {
this.drawPolygonWithLabel(coords, `restrict-polygon-${i}`, '#F83', '限飞区')
})
} else {
//
const layerIds = []
const sourceIds = []
noflyPolygons.forEach((_, i) => {
layerIds.push(`nofly-polygon-${i}`, `nofly-polygon-${i}-outline`, `nofly-polygon-${i}-label-layer`)
sourceIds.push(`nofly-polygon-${i}`, `nofly-polygon-${i}-label`)
})
restrictflyPolygons.forEach((_, i) => {
layerIds.push(`restrict-polygon-${i}`, `restrict-polygon-${i}-outline`, `restrict-polygon-${i}-label-layer`)
sourceIds.push(`restrict-polygon-${i}`, `restrict-polygon-${i}-label`)
})
this.clearMapElements(layerIds, sourceIds)
}
}
}), 'top-right')
}
@ -717,104 +741,90 @@ export default {
}
},
/**
* @description: 绘制一个多边形区域并在中心显示文字
* @param {Array} coordinatesArray 多边形坐标数组格式为 [ [lng, lat], ... ]首尾闭合
* @param {String} polygonId 图层 ID
* @param {String} fillColor 填充颜色默认红色
* @param {String} labelText 中心显示的文字为空则不显示
* @description: Mapbox 地图上绘制一个带轮廓线的多边形区域并可选添加标签文字
*
* @param {Array<Array<number>>} coords - 多边形的经纬度坐标数组格式为 [[lng, lat], [lng, lat], ...]
* @param {string} id - 图层和数据源的唯一标识符前缀用于创建 source layer ID
* @param {string} fillColor - 多边形的填充颜色 '#F00' 表示红色
* @param {string} labelText - 要显示的标签文字传空则不显示
*/
drawPolygonWithLabel (coordinatesArray, polygonId = 'polygon-area', fillColor = '#F33', labelText = '') {
// GeoJSON
const polygonGeoJSON = {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [coordinatesArray]
}
}
//
const calculateCenter = (coords) => {
let lngSum = 0
let latSum = 0
coords.forEach(([lng, lat]) => {
lngSum += lng
latSum += lat
})
return [lngSum / coords.length, latSum / coords.length]
}
const center = calculateCenter(coordinatesArray)
const labelGeoJSON = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: center
},
properties: {
title: labelText
}
}
//
if (this.map.getLayer(polygonId)) {
this.map.getSource(polygonId).setData(polygonGeoJSON)
} else {
this.map.addSource(polygonId, {
type: 'geojson',
data: polygonGeoJSON
})
this.map.addLayer({
id: polygonId,
type: 'fill',
source: polygonId,
paint: {
'fill-color': fillColor,
'fill-opacity': 0.4
drawPolygonWithLabel (coords, id, fillColor, labelText) {
// GeoJSON
this.map.addSource(id, {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [coords]
}
})
}
})
this.map.addLayer({
id: `${polygonId}-outline`,
type: 'line',
source: polygonId,
paint: {
'line-color': fillColor,
'line-width': 2
}
})
}
//
this.map.addLayer({
id: id,
type: 'fill',
source: id,
paint: {
'fill-color': fillColor,
'fill-opacity': 0.3
}
})
//
// 线
this.map.addLayer({
id: `${id}-outline`,
type: 'line',
source: id,
paint: {
'line-color': fillColor,
'line-width': 2
}
})
//
if (labelText) {
const labelSourceId = `${polygonId}-label`
const labelLayerId = `${polygonId}-label-layer`
//
const center = coords.reduce((acc, cur) => {
acc[0] += cur[0]
acc[1] += cur[1]
return acc
}, [0, 0]).map(v => v / coords.length)
if (this.map.getLayer(labelLayerId)) {
this.map.getSource(labelSourceId).setData(labelGeoJSON)
} else {
this.map.addSource(labelSourceId, {
type: 'geojson',
data: labelGeoJSON
})
this.map.addLayer({
id: labelLayerId,
type: 'symbol',
source: labelSourceId,
layout: {
'text-field': ['get', 'title'],
'text-size': 14,
'text-offset': [0, 0.5],
'text-anchor': 'top'
//
this.map.addSource(`${id}-label`, {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: center
},
paint: {
'text-color': '#000000'
properties: {
label: labelText
}
})
}
}
})
//
this.map.addLayer({
id: `${id}-label-layer`,
type: 'symbol',
source: `${id}-label`,
layout: {
'text-field': ['get', 'label'],
'text-size': 14,
'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
'text-offset': [0, 0.6],
'text-anchor': 'top'
},
paint: {
'text-color': fillColor,
'text-halo-color': '#fff',
'text-halo-width': 1.5
}
})
}
},
/**

View File

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

View File

@ -353,6 +353,7 @@ export class NoFlyControl {
f.properties.type = type
})
// 重新添加以确保类型被写入并触发样式
this._draw.delete(features.map(f => f.id))
this._draw.add({
type: 'FeatureCollection',
@ -401,49 +402,64 @@ export class NoFlyControl {
}
_getDrawStyles () {
const noflyColor = '#ff3333'
const limitColor = '#ff8833'
return [
// 区域填充样式
{
id: 'gl-draw-polygon-fill',
id: 'custom-polygon-fill',
type: 'fill',
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
filter: ['all', ['==', '$type', 'Polygon']],
paint: {
'fill-color': ['case',
['==', ['get', 'type'], 'nofly'], '#F33',
['==', ['get', 'type'], 'limit'], '#F83',
'#F83'
'fill-color': [
'case',
['==', ['get', 'type'], 'nofly'], noflyColor,
['==', ['get', 'type'], 'limit'], limitColor,
limitColor // 默认颜色
],
'fill-opacity': 0.3
}
},
// 区域边框线(不区分 static / active
{
id: 'gl-draw-polygon-stroke-active',
id: 'custom-polygon-stroke',
type: 'line',
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
filter: ['all', ['==', '$type', 'Polygon']],
paint: {
'line-color': ['case',
['==', ['get', 'type'], 'nofly'], '#F33',
['==', ['get', 'type'], 'limit'], '#F83',
'#F83'
'line-color': [
'case',
['==', ['get', 'type'], 'nofly'], noflyColor,
['==', ['get', 'type'], 'limit'], limitColor,
limitColor
],
'line-width': 2
'line-width': 2,
'line-dasharray': [2, 2]
}
},
// 顶点圆点
{
id: 'gl-draw-polygon-and-line-vertex-halo-active',
type: 'circle',
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
paint: {
'circle-radius': 7,
'circle-color': '#FFF'
}
},
{
id: 'gl-draw-polygon-and-line-vertex-active',
id: 'custom-vertex-point',
type: 'circle',
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
paint: {
'circle-radius': 5,
'circle-color': '#F83'
'circle-color': [
'case',
['==', ['get', 'type'], 'nofly'], noflyColor,
['==', ['get', 'type'], 'limit'], limitColor,
limitColor
]
}
},
// 顶点外光环
{
id: 'custom-vertex-halo',
type: 'circle',
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
paint: {
'circle-radius': 7,
'circle-color': '#FFFFFF'
}
}
]
@ -452,24 +468,16 @@ export class NoFlyControl {
// 显示禁飞区 限飞区
export class PolygonToggleControl {
/**
*
* @param {{
* defaultIconClass: string,
* activeIconClass: string,
* onDraw?: Function, // 激活时回调,传入激活状态
* noflyPolygons: Array, // 禁飞区多边形数据,数组里是多边形坐标数组
* restrictflyPolygons: Array // 限飞区多边形数据
* }} options
*/
constructor ({ defaultIconClass, activeIconClass, onDraw, noflyPolygons, restrictflyPolygons }) {
this._defaultIconClass = defaultIconClass
this._activeIconClass = activeIconClass
this._onDraw = onDraw
this._noflyPolygons = noflyPolygons
this._restrictflyPolygons = restrictflyPolygons
this._isActive = false
constructor (options = {}) {
this._map = null
this._container = null
this._active = false
this.defaultIconClass = options.defaultIconClass
this.activeIconClass = options.activeIconClass
// 外部传入的切换回调
this.onToggle = options.onToggle || (() => {})
}
onAdd (map) {
@ -478,144 +486,30 @@ export class PolygonToggleControl {
this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group'
this._button = document.createElement('button')
this._button.className = this._defaultIconClass
this._button.onclick = this._handleClick.bind(this)
this._button.type = 'button'
this._button.className = this.defaultIconClass
this._container.appendChild(this._button)
this._button.addEventListener('click', () => {
this._active = !this._active
this._update()
this.onToggle(this._active) // 通知外部状态切换
})
return this._container
}
_handleClick () {
this._isActive = !this._isActive
this._button.className = this._isActive ? this._activeIconClass : this._defaultIconClass
if (this._isActive) {
// 激活时绘制所有禁飞区和限飞区多边形
this._drawPolygons()
} else {
// 取消激活时移除所有相关图层
this._removePolygons()
}
if (this._onDraw) this._onDraw(this._isActive)
}
_drawPolygons () {
// 禁飞区用颜色 #FF3333id 用 nofly-0、nofly-1 ...
this._noflyPolygons.forEach((coords, idx) => {
this._drawPolygonWithLabel(coords, `nofly-${idx}`, '#FF3333', '禁飞区')
})
// 限飞区用颜色 #FF8833id 用 restrictfly-0、restrictfly-1 ...
this._restrictflyPolygons.forEach((coords, idx) => {
this._drawPolygonWithLabel(coords, `restrictfly-${idx}`, '#FF8833', '限飞区')
})
}
_removePolygons () {
// 卸载禁飞区相关图层和source
this._noflyPolygons.forEach((_, idx) => {
this._removePolygonLayers(`nofly-${idx}`)
})
// 卸载限飞区相关图层和source
this._restrictflyPolygons.forEach((_, idx) => {
this._removePolygonLayers(`restrictfly-${idx}`)
})
}
_removePolygonLayers (baseId) {
const outlineId = `${baseId}-outline`
const labelLayerId = `${baseId}-label-layer`
const labelSourceId = `${baseId}-label`
if (this._map.getLayer(labelLayerId)) this._map.removeLayer(labelLayerId)
if (this._map.getSource(labelSourceId)) this._map.removeSource(labelSourceId)
if (this._map.getLayer(outlineId)) this._map.removeLayer(outlineId)
if (this._map.getLayer(baseId)) this._map.removeLayer(baseId)
if (this._map.getSource(baseId)) this._map.removeSource(baseId)
}
_drawPolygonWithLabel (coordinatesArray, polygonId, fillColor, labelText) {
// 计算中心点
const calculateCenter = (coords) => {
let lngSum = 0; let latSum = 0
coords.forEach(([lng, lat]) => { lngSum += lng; latSum += lat })
return [lngSum / coords.length, latSum / coords.length]
}
const polygonGeoJSON = {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [coordinatesArray]
}
}
const center = calculateCenter(coordinatesArray)
const labelGeoJSON = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: center
},
properties: {
title: labelText
}
}
if (this._map.getLayer(polygonId)) {
this._map.getSource(polygonId).setData(polygonGeoJSON)
} else {
this._map.addSource(polygonId, { type: 'geojson', data: polygonGeoJSON })
this._map.addLayer({
id: polygonId,
type: 'fill',
source: polygonId,
paint: { 'fill-color': fillColor, 'fill-opacity': 0.4 }
})
this._map.addLayer({
id: `${polygonId}-outline`,
type: 'line',
source: polygonId,
paint: { 'line-color': fillColor, 'line-width': 2 }
})
}
if (labelText) {
const labelSourceId = `${polygonId}-label`
const labelLayerId = `${polygonId}-label-layer`
if (this._map.getLayer(labelLayerId)) {
this._map.getSource(labelSourceId).setData(labelGeoJSON)
} else {
this._map.addSource(labelSourceId, { type: 'geojson', data: labelGeoJSON })
this._map.addLayer({
id: labelLayerId,
type: 'symbol',
source: labelSourceId,
layout: {
'text-field': ['get', 'title'],
'text-size': 14,
'text-offset': [0, 0.5],
'text-anchor': 'top'
},
paint: {
'text-color': '#000000'
}
})
}
}
}
onRemove () {
this._removePolygons()
this._container.parentNode.removeChild(this._container)
this._map = undefined
this._map = null
}
_update () {
if (this._active) {
this._button.className = this.activeIconClass
} else {
this._button.className = this.defaultIconClass
}
}
}

View File

@ -14,8 +14,8 @@ export default {
},
data () {
return {
showMapbox: true,
mapboxKey: 0
showMapbox: true, //
mapboxKey: 0 // map-box
}
},
computed: {

View File

@ -1,8 +1,8 @@
<template>
<div class="h-100">
<!-- 地图组件 -->
<map-box ref="mapbox" :showNofly="true" :enableGuided="true" :enableFollow="true" :enblueScale="!$store.state.app.isWideScreen"
@longPress="handleLongPress">
<map-box ref="mapbox" v-if="showMapbox" :enableShowNofly="true" :enableGuided="true" :enableFollow="true" :enblueScale="!$store.state.app.isWideScreen"
@longPress="handleLongPress" :key="mapboxKey">
<template #content>
<BatteryStatus :plane="plane" />
<PlaneStatus :plane="plane" />
@ -47,7 +47,8 @@ export default {
guidedLonLat: {}, //
guidedAlt: '', //
isReserveGuidedMaker: false, //
mapBoxKey: '', // map-box
showMapbox: true, //
mapboxKey: 0, // map-box
planesId: this.$route.params.id,
localCount: 0 //
}
@ -76,6 +77,9 @@ export default {
return []
}
},
noflyData () {
return this.$store.state.noflyData
},
/**
* @description: 侧边栏显隐
*/
@ -181,6 +185,17 @@ export default {
},
deep: true
},
noflyData: {
deep: true,
immediate: true,
handler () {
this.showMapbox = false
this.$nextTick(() => {
this.mapboxKey++
this.showMapbox = true
})
}
},
/**
* @description: 侧边栏缩进有变化时 地图重新自适应
*/