diff --git a/src/views/TasksView/TasksView.vue b/src/views/TasksView/TasksView.vue index 9bb333b..677457c 100644 --- a/src/views/TasksView/TasksView.vue +++ b/src/views/TasksView/TasksView.vue @@ -288,6 +288,7 @@ let routeMapInstance = null let routeMapAttached = false let pendingStyleHandler = null let pendingFitRoute = false +let styleRebuildHandler = null const routeForm = reactive({ name: '', altitude: 50, speed: 10, turnMode: 'auto' }) const routePoints = ref([]) @@ -711,6 +712,34 @@ async function waitForMapHostSize(el, tries = 40) { return el.clientWidth > 0 && el.clientHeight > 0 } +async function waitForMapStyleReady(map, tries = 40) { + if (!map) return false + for (let i = 0; i < tries; i++) { + try { + if (map.isStyleLoaded?.() && map.loaded?.() !== false) { + // 再等一帧,避开 setProjection 后瞬时仍 isStyleLoaded 的窗口 + await new Promise((r) => requestAnimationFrame(r)) + if (map.isStyleLoaded?.()) return true + } + } catch (_) { + /* ignore */ + } + await new Promise((r) => { + const onIdle = () => { + map.off?.('idle', onIdle) + r() + } + try { + map.once?.('idle', onIdle) + setTimeout(r, 50) + } catch (_) { + setTimeout(r, 50) + } + }) + } + return !!map.isStyleLoaded?.() +} + function scheduleRouteSync({ fit = false } = {}) { const map = routeMapInstance if (!map) return @@ -721,47 +750,18 @@ function scheduleRouteSync({ fit = false } = {}) { } catch (_) { /* ignore */ } + // projection/style 变更后 source 可能被清掉,先重建 + if (!ensureTaskLayers(map)) return syncRouteLayer({ fit: shouldFit }) } - // 双 rAF:等 hidden 解除后的布局/合成,再 resize + fit requestAnimationFrame(() => { requestAnimationFrame(() => { run(fit) if (!fit) return - // 再补一帧:ResizeObserver/样式生效后二次 fit,避免首帧 0 尺寸 requestAnimationFrame(() => run(true)) }) }) } - -function onRouteMapClick(e) { - if (!routeMapAttached || !routeMapInstance) return - const hit = routeMapInstance.queryRenderedFeatures(e.point, { - layers: [WP_CIRCLE, WP_LABEL].filter((id) => routeMapInstance.getLayer(id)), - }) - if (hit[0]?.properties?.index != null) { - openWaypointEditor(Number(hit[0].properties.index)) - return - } - if (!validRouteDefaults()) return - const { lng, lat } = e.lngLat - routePoints.value.push({ - longitude: lng, - latitude: lat, - altitude: Number(routeForm.altitude), - speed: Number(routeForm.speed), - turnMode: routeForm.turnMode, - }) - routeCursor.value = formatCoord(lng, lat) - routeFormError.value = '' - syncRouteLayer() -} - -function onRouteMapMove(e) { - if (!routeMapAttached) return - routeCursor.value = formatCoord(e.lngLat.lng, e.lngLat.lat) -} - async function attachRouteMap() { await nextTick() const host = routeMapEl.value @@ -774,7 +774,6 @@ async function attachRouteMap() { return } - // 编辑器从 display:none 打开时,先等到宿主有尺寸再搬地图 await waitForMapHostSize(host) routeMapInstance = map @@ -783,34 +782,65 @@ async function attachRouteMap() { routeMapAttached = true routeMapReady.value = false + // setProjection(mercator) 可能异步重建 style,先等稳定再挂图层 + await waitForMapStyleReady(map) + const boot = () => { - pendingStyleHandler = null if (!routeMapAttached || routeMapInstance !== map) return clearTaskLayers(map) if (!ensureTaskLayers(map)) return + map.off('click', onRouteMapClick) + map.off('mousemove', onRouteMapMove) map.on('click', onRouteMapClick) map.on('mousemove', onRouteMapMove) const c = map.getCenter() routeCursor.value = formatCoord(c.lng, c.lat) routeMapReady.value = true const shouldFit = pendingFitRoute - pendingFitRoute = false - scheduleRouteSync({ fit: shouldFit }) + // pendingFitRoute 只在首次 boot 消费;style 重建时仍按当前航点 fit + if (shouldFit) pendingFitRoute = false + scheduleRouteSync({ fit: shouldFit || routePoints.value.length > 0 }) + } + + if (pendingStyleHandler) { + map.off('load', pendingStyleHandler) + map.off('style.load', pendingStyleHandler) + pendingStyleHandler = null + } + if (styleRebuildHandler) { + map.off('style.load', styleRebuildHandler) + styleRebuildHandler = null } + // style 后续再变(底图切换/projection)时重建航线层 + styleRebuildHandler = () => { + if (!routeMapAttached || routeMapInstance !== map) return + boot() + } + map.on('style.load', styleRebuildHandler) + if (map.isStyleLoaded()) { boot() } else { - pendingStyleHandler = boot + pendingStyleHandler = () => { + pendingStyleHandler = null + boot() + } map.once('load', pendingStyleHandler) + map.once('style.load', pendingStyleHandler) } } function teardownRouteMap() { if (routeMapInstance && pendingStyleHandler) { routeMapInstance.off('load', pendingStyleHandler) + routeMapInstance.off('style.load', pendingStyleHandler) pendingStyleHandler = null } + if (routeMapInstance && styleRebuildHandler) { + routeMapInstance.off('style.load', styleRebuildHandler) + styleRebuildHandler = null + } if (routeMapInstance) { routeMapInstance.off('click', onRouteMapClick) routeMapInstance.off('mousemove', onRouteMapMove) @@ -818,7 +848,6 @@ function teardownRouteMap() { } if (routeMapAttached) { mapHelper.restoreMapContainer() - // 监控默认 globe + pitch 0;不要切 3D pitch 45 try { mapHelper.getMap()?.setProjection?.('globe') } catch (_) { @@ -831,6 +860,35 @@ function teardownRouteMap() { pendingFitRoute = false } +function onRouteMapClick(e) { + if (!routeMapAttached || !routeMapInstance) return + const hit = routeMapInstance.queryRenderedFeatures(e.point, { + layers: [WP_CIRCLE, WP_LABEL].filter((id) => routeMapInstance.getLayer(id)), + }) + if (hit[0]?.properties?.index != null) { + openWaypointEditor(Number(hit[0].properties.index)) + return + } + if (!validRouteDefaults()) return + const { lng, lat } = e.lngLat + routePoints.value.push({ + longitude: lng, + latitude: lat, + altitude: Number(routeForm.altitude), + speed: Number(routeForm.speed), + turnMode: routeForm.turnMode, + }) + routeCursor.value = formatCoord(lng, lat) + routeFormError.value = '' + syncRouteLayer() +} + +function onRouteMapMove(e) { + if (!routeMapAttached) return + routeCursor.value = formatCoord(e.lngLat.lng, e.lngLat.lat) +} + + function openRouteEditor() { resetRouteEditor() routeEditorOpen.value = true