From bc6bf29da24a0ab4fa9a60db499288a8382fe48d Mon Sep 17 00:00:00 2001 From: szdot Date: Sun, 22 Jun 2025 06:06:18 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E7=B1=BB=20=20=E5=9E=8B=E3=80=91?= =?UTF-8?q?=EF=BC=9Afix=20=E3=80=90=E5=8E=9F=20=20=E5=9B=A0=E3=80=91?= =?UTF-8?q?=EF=BC=9A1=E4=BF=AE=E6=AD=A3=20=E5=9C=B0=E5=9B=BE=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E8=BD=BD=E5=85=A5=20=E5=86=85=E9=83=A8=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E8=B0=83=E7=94=A8=E8=BF=87=E6=97=A9=20=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=8A=A5=E9=94=99=20=202.=E5=AE=8C=E5=96=84=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=E6=BA=90=E5=9C=B0=E5=9D=80=20=E4=BB=8E=20=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E6=95=B0=E6=8D=AE=E5=BA=93=E6=8B=BF=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=20=E3=80=90=E8=BF=87=20=20=E7=A8=8B=E3=80=91=EF=BC=9A=20?= =?UTF-8?q?=E3=80=90=E5=BD=B1=20=20=E5=93=8D=E3=80=91=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 类型 包含: # feat:新功能(feature) # fix:修补bug # docs:文档(documentation) # style: 格式(不影响代码运行的变动) # refactor:重构(即不是新增功能,也不是修改bug的代码变动) # test:增加测试 # chore:构建过程或辅助工具的变动 --- src/components/MapBox.vue | 100 ++++++++++-------- src/store/index.js | 9 -- src/utils/index.js | 34 ++++++ .../layout/components/main/home/index.vue | 38 +++++-- .../layout/components/main/planes/index.vue | 43 +++++--- src/views/layout/index.vue | 4 +- 6 files changed, 147 insertions(+), 81 deletions(-) diff --git a/src/components/MapBox.vue b/src/components/MapBox.vue index f53f633..b1fbaa6 100644 --- a/src/components/MapBox.vue +++ b/src/components/MapBox.vue @@ -23,8 +23,7 @@ export default { takeoffLonLats: [], // 航线 第一个航点 起点 最后一个航点 isflow: false, // 飞机经纬度变化时是否跟随飞机 currentStyleIndex: 0, // 当前选中的地图样式索引 - guidedMarker: null, // 指点飞行地图标记 - mapStyles: this.$store.state.mapStyleList// 地图样式 + guidedMarker: null // 指点飞行地图标记 } }, props: { @@ -79,53 +78,63 @@ export default { }, defaultZoom () { return this.$store.getters['app/getDefaultZoom'] + }, + mapStyleList () { + return this.$store.state.mapStyleList } }, - mounted () { - this.init().then(() => { - // 地图初始化之后 - this.map.on('load', () => { - this.$emit('map-ready') // 外部组件回调 触发地图加载完成事件 传到外部组件调用 以确保执行时地图已经加载完成 - /* 更新样式,添加自定义 sprite */ + watch: { + }, + async mounted () { + // 等待 异步 加载 后端地图源 地址 + await this.$store.dispatch('fetchMapStyleList', this.$store.state.settings.host) + // 等待 异步 地图初始化 + await this.init() - // 星空背景 大气层 - this.map.setFog({ - range: [1.0, 8.0], - color: 'white', - 'horizon-blend': 0.01 - }) - // // 添加等高线图层 - // this.map.addSource('contours', { - // type: 'vector', - // url: 'mapbox://mapbox.mapbox-terrain-v2' - // }) - // this.map.addLayer({ - // id: 'contours', - // type: 'line', - // source: 'contours', - // 'source-layer': 'contour', - // layout: { - // 'line-join': 'round', - // 'line-cap': 'round' - // }, - // paint: { - // 'line-color': '#ff69b4',// 设置等高线颜色 - // 'line-width': 1 // 设置等高线宽度 - // } - // }) - // // 3D地图 - // this.map.addSource('mapbox-dem', { - // type: 'raster-dem', - // url: 'mapbox://mapbox.mapbox-terrain-dem-v1', - // tileSize: 512, - // maxzoom: 14 - // }) - // this.map.setTerrain({ source: 'mapbox-dem', exaggeration: 1.5 }) + // 地图初始化之后 + this.map.on('load', () => { + /* 更新样式,添加自定义 sprite */ + + // 星空背景 大气层 + this.map.setFog({ + range: [1.0, 8.0], + color: 'white', + 'horizon-blend': 0.01 }) + // // 添加等高线图层 + // this.map.addSource('contours', { + // type: 'vector', + // url: 'mapbox://mapbox.mapbox-terrain-v2' + // }) + // this.map.addLayer({ + // id: 'contours', + // type: 'line', + // source: 'contours', + // 'source-layer': 'contour', + // layout: { + // 'line-join': 'round', + // 'line-cap': 'round' + // }, + // paint: { + // 'line-color': '#ff69b4',// 设置等高线颜色 + // 'line-width': 1 // 设置等高线宽度 + // } + // }) + // // 3D地图 + // this.map.addSource('mapbox-dem', { + // type: 'raster-dem', + // url: 'mapbox://mapbox.mapbox-terrain-dem-v1', + // tileSize: 512, + // maxzoom: 14 + // }) + // this.map.setTerrain({ source: 'mapbox-dem', exaggeration: 1.5 }) + + // 外部组件回调 触发地图加载完成事件(on 'load') 传到外部组件调用 以确保执行时地图已经加载完成 + this.$emit('map-ready') // 判断地图开启了点飞功能 if (this.enableGuided) { - // 长按事件 传longPress到组件外部调用 + // 长按事件 传longPress到组件外部调用 let pressTimer = null let isLongPress = false // 标记是否为长按 let startPoint = null // 记录按下时的位置 @@ -186,7 +195,7 @@ export default { // 实例化map this.map = new mapboxgl.Map({ container: 'map', - style: this.mapStyles[0], + style: this.mapStyleList[0], center: this.defaultLonlat, zoom: this.defaultZoom, pitch: 0, @@ -211,7 +220,7 @@ export default { // 地图样式选择控件 // 自定义地图样式列表 if (this.enableSwitch) { - const styleSwitcherControl = new MapboxStyleSwitcherControl(this.mapStyles, 'iconfont icon-duozhang f-s-20', this.currentStyleIndex) + const styleSwitcherControl = new MapboxStyleSwitcherControl(this.mapStyleList, 'iconfont icon-duozhang f-s-20', this.currentStyleIndex) this.map.addControl(styleSwitcherControl, 'top-right') } @@ -871,8 +880,6 @@ export default { this.$store.commit('app/setDefaultLonLat', this.map.getCenter()) this.$store.commit('app/setDefaultZoom', this.map.getZoom()) } - }, - watch: { } } @@ -899,5 +906,4 @@ export default { .adsb-icon { will-change: transform; } - diff --git a/src/store/index.js b/src/store/index.js index 7dac9e0..ca7077e 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -26,7 +26,6 @@ const store = new Vuex.Store({ logList: [], // 操作日志列表 messageList: [], // 管理员公告列表 mapStyleList: [], // 地图样式列表 - mapIsStyleReady: false, // 地图样式是否加载完成 在全局入口判断设置 子页面调用地图组件时直接判断该值 crosFrequency: null, // 对频macadd ADSBList: [] // 存放当前活跃的 ADSB 飞机数据 }, @@ -155,13 +154,6 @@ const store = new Vuex.Store({ setMapStyleList (state, list) { state.mapStyleList = list }, - /** - * @description: 确认地图源地址 加载完成 - * @param {bool} status 是否完成 - */ - setMapIsStyleReady (state, status) { - state.mapIsStyleReady = status - }, /** * @description: 清除过期的 ADSB 数据 */ @@ -1209,7 +1201,6 @@ const store = new Vuex.Store({ } }) commit('setMapStyleList', list) - commit('setMapIsStyleReady', true) } else { commit('setMapStyleList', []) Message.error(res.data.msg || '地图样式获取失败') diff --git a/src/utils/index.js b/src/utils/index.js index b404516..e554a85 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -158,3 +158,37 @@ export function formatPrice (value) { } return formattedPrice } + +/** + * 等待 Mapbox 地图的画布容器(Canvas Container)准备就绪。 + * 因为 Mapbox 初始化是异步的,某些操作需要等待画布容器加载完成才能执行。 + * + * @param {Object} map - Mapbox GL JS 地图实例对象 + * @param {number} maxRetry - 最大重试次数,防止无限等待,默认5次 + * @returns {Promise} 返回一个 Promise,成功时返回画布容器元素,失败时抛出错误 + */ +export function waitForMapCanvasReady (map, maxRetry = 5) { + return new Promise((resolve, reject) => { + /** + * 递归检查函数,使用 requestAnimationFrame 尽可能快地检测画布容器是否可用 + * @param {number} retry 剩余重试次数 + */ + const check = (retry) => { + // 尝试获取画布容器元素 + const container = map?.getCanvasContainer?.() + + // 如果获取成功,表示地图画布已准备好 + if (container) { + resolve(container) // Promise 成功返回画布容器 + } else if (retry > 0) { // 如果还没准备好且重试次数未用尽,继续异步下一帧检测 + // requestAnimationFrame 在浏览器每一帧时执行回调,比 setTimeout 更高效和精准 + requestAnimationFrame(() => check(retry - 1)) + } else { // 超过最大重试次数,拒绝 Promise,避免无限等待 + reject(new Error('Map canvas container is not ready after multiple retries')) + } + } + + // 启动首次检测,带上最大重试次数参数 + check(maxRetry) + }) +} diff --git a/src/views/layout/components/main/home/index.vue b/src/views/layout/components/main/home/index.vue index 3d9101f..2e0bbce 100644 --- a/src/views/layout/components/main/home/index.vue +++ b/src/views/layout/components/main/home/index.vue @@ -1,21 +1,25 @@