From 72d3bed3a31af8bfe497c12bd62ad88fcdbc1c59 Mon Sep 17 00:00:00 2001 From: xiaosi <2652281683@qq.com> Date: Fri, 4 Sep 2026 10:04:32 +0800 Subject: [PATCH] fix: make route editor map resize and fit after layout Wait for editor host size, switch 2D mercator, and refit after resize so short routes are visible when editing. --- src/core/mapHelper.js | 11 ++++ src/styles/prototype.css | 9 +++- src/views/TasksView/TasksView.vue | 88 +++++++++++++++++++++++++------ 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/core/mapHelper.js b/src/core/mapHelper.js index ad332a8..e881941 100644 --- a/src/core/mapHelper.js +++ b/src/core/mapHelper.js @@ -19,6 +19,11 @@ class MapHelper { if (!this._map) return if (is3D) { + try { + this._map.setProjection('globe') + } catch (_) { + /* ignore */ + } this._map.easeTo({ pitch: 45, bearing: 0, duration: 0 }) this._map.dragRotate.enable() this._map.touchZoomRotate.enableRotation() @@ -27,6 +32,12 @@ class MapHelper { } if (is2D) { + // 航线/回放编辑需要平面投影;globe 下短航线 fitBounds 易失效 + try { + this._map.setProjection('mercator') + } catch (_) { + /* ignore */ + } this._map.easeTo({ pitch: 0, bearing: 0, duration: 0 }) this._map.dragRotate.disable() this._map.touchZoomRotate.disableRotation() diff --git a/src/styles/prototype.css b/src/styles/prototype.css index f641af2..50068c3 100644 --- a/src/styles/prototype.css +++ b/src/styles/prototype.css @@ -309,6 +309,11 @@ svg:not(.t-icon){ width: 18px; height: 18px; fill: none; stroke: currentColor; s .business-workspace{ flex: 1; min-height: 440px; display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--line); border-radius: 6px; background: #fff; } .business-panel{ display: none; flex: 1; min-height: 0; overflow: auto; } .business-panel.active{ display: block; } +.business-panel.route-library.active{ + display: flex; + flex-direction: column; + overflow: hidden; +} .business-toolbar{ min-height: 58px; padding: 11px 16px; display: flex; align-items: center; justify-content: space-between; gap: 12px; border-bottom: 1px solid var(--line); } .business-toolbar > div{ display: flex; align-items: center; gap: 8px; } .business-toolbar select, .task-dialog select{ height: 34px; padding: 0 28px 0 9px; border: 1px solid var(--line); border-radius: 4px; color: #536270; background: #fff; font-size: 11px; outline: 0; } @@ -352,7 +357,7 @@ svg:not(.t-icon){ width: 18px; height: 18px; fill: none; stroke: currentColor; s .route-card-actions{ margin-top: 8px; display: flex; align-items: center; gap: 2px; } .route-card-actions .t-button{ height: 28px; } .route-library-view[hidden], .route-editor[hidden]{ display: none !important; } -.route-editor{ height: 100%; min-height: 520px; display: flex; flex-direction: column; background: #fff; } +.route-editor{ flex: 1; min-height: 0; height: 100%; display: flex; flex-direction: column; background: #fff; } .route-editor-header{ height: 58px; min-height: 58px; padding: 0 14px; display: grid; grid-template-columns: 150px 1fr auto; align-items: center; gap: 12px; border-bottom: 1px solid var(--line); } .route-editor-header .back-command{ grid-column: auto; } .route-editor-header > div:nth-child(2){ min-width: 0; display: flex; align-items: baseline; gap: 9px; } @@ -482,7 +487,7 @@ svg:not(.t-icon){ width: 18px; height: 18px; fill: none; stroke: currentColor; s .waypoint-item-actions button:last-child:hover{ color: var(--red); background: #fff0f0; } .waypoint-item-actions button svg{ width: 11px; } .route-form-error{ min-height: 25px; padding-top: 8px; color: var(--red); font-size: 10px; } -.route-planner-map{ position: relative; min-width: 0; min-height: 430px; overflow: hidden; background: #75887a; cursor: crosshair; isolation: isolate; } +.route-planner-map{ position: relative; min-width: 0; min-height: 430px; height: 100%; overflow: hidden; background: #75887a; cursor: crosshair; isolation: isolate; } .route-planner-map > #map, .route-planner-map > .mapboxgl-map{ position: absolute !important; inset: 0; diff --git a/src/views/TasksView/TasksView.vue b/src/views/TasksView/TasksView.vue index b059249..941a2ba 100644 --- a/src/views/TasksView/TasksView.vue +++ b/src/views/TasksView/TasksView.vue @@ -674,22 +674,64 @@ function syncRouteLayer({ fit = false } = {}) { if (fit && coords.length) fitRouteInView(coords) } - function fitRouteInView(coords) { if (!routeMapInstance || !coords?.length) return + // 容器尚未布局完成时 fit 会落到错误相机,调用方应先 wait + resize + const canvas = routeMapInstance.getCanvas?.() + if (canvas && (canvas.clientWidth < 2 || canvas.clientHeight < 2)) return false if (coords.length === 1) { - routeMapInstance.easeTo({ + routeMapInstance.jumpTo({ center: coords[0], zoom: Math.max(routeMapInstance.getZoom(), 16), - duration: 400, + pitch: 0, + bearing: 0, }) - return + return true } const bounds = coords.reduce( (b, c) => b.extend(c), new mapboxgl.LngLatBounds(coords[0], coords[0]), ) - routeMapInstance.fitBounds(bounds, { padding: 80, duration: 400, maxZoom: 17 }) + routeMapInstance.fitBounds(bounds, { + padding: 80, + duration: 0, + maxZoom: 17, + pitch: 0, + bearing: 0, + }) + return true +} + +async function waitForMapHostSize(el, tries = 40) { + if (!el) return false + for (let i = 0; i < tries; i++) { + if (el.clientWidth > 10 && el.clientHeight > 10) return true + await new Promise((r) => requestAnimationFrame(r)) + } + return el.clientWidth > 0 && el.clientHeight > 0 +} + +function scheduleRouteSync({ fit = false } = {}) { + const map = routeMapInstance + if (!map) return + const run = (shouldFit) => { + if (!routeMapAttached || routeMapInstance !== map) return + try { + map.resize() + } catch (_) { + /* ignore */ + } + syncRouteLayer({ fit: shouldFit }) + } + // 双 rAF:等 hidden 解除后的布局/合成,再 resize + fit + requestAnimationFrame(() => { + requestAnimationFrame(() => { + run(fit) + if (!fit) return + // 再补一帧:ResizeObserver/样式生效后二次 fit,避免首帧 0 尺寸 + requestAnimationFrame(() => run(true)) + }) + }) } function onRouteMapClick(e) { @@ -732,6 +774,9 @@ async function attachRouteMap() { return } + // 编辑器从 display:none 打开时,先等到宿主有尺寸再搬地图 + await waitForMapHostSize(host) + routeMapInstance = map mapHelper.replaceMapContainer(host) mapHelper.toggleMapMode({ is2D: true }) @@ -748,12 +793,9 @@ async function attachRouteMap() { const c = map.getCenter() routeCursor.value = formatCoord(c.lng, c.lat) routeMapReady.value = true - requestAnimationFrame(() => { - map.resize() - const shouldFit = pendingFitRoute - pendingFitRoute = false - syncRouteLayer({ fit: shouldFit }) - }) + const shouldFit = pendingFitRoute + pendingFitRoute = false + scheduleRouteSync({ fit: shouldFit }) } if (map.isStyleLoaded()) { @@ -776,6 +818,13 @@ function teardownRouteMap() { } if (routeMapAttached) { mapHelper.restoreMapContainer() + // 监控页默认 globe + mapHelper.toggleMapMode({ is3D: true }) + try { + routeMapInstance?.easeTo?.({ pitch: 0, bearing: 0, duration: 0 }) + } catch (_) { + /* ignore */ + } routeMapAttached = false } routeMapInstance = null @@ -802,11 +851,17 @@ async function openRouteEditorForEdit(route) { const detail = await request.get(urls.ROUTE(route.id)) resetRouteEditor() editingRouteId.value = String(detail?.id ?? route.id) - const list = Array.isArray(detail?.waypoints) ? detail.waypoints : [] - routePoints.value = list.map(mapApiWaypointToPoint) + const list = Array.isArray(detail?.waypoints) + ? detail.waypoints + : Array.isArray(detail?.route?.waypoints) + ? detail.route.waypoints + : [] + routePoints.value = list.map(mapApiWaypointToPoint).filter( + (p) => Number.isFinite(p.longitude) && Number.isFinite(p.latitude), + ) const first = routePoints.value[0] Object.assign(routeForm, { - name: detail?.name || route.name || '', + name: detail?.name || detail?.route?.name || route.name || '', altitude: first ? first.altitude : 50, speed: first ? first.speed : 10, turnMode: first ? first.turnMode : 'auto', @@ -814,8 +869,9 @@ async function openRouteEditorForEdit(route) { pendingFitRoute = routePoints.value.length > 0 routeEditorOpen.value = true await attachRouteMap() - // boot already syncs+fits when pendingFitRoute; ready 兜底再 fit 一次 - if (routeMapReady.value) syncRouteLayer({ fit: routePoints.value.length > 0 }) + if (routeMapReady.value && routePoints.value.length) { + scheduleRouteSync({ fit: true }) + } } catch (e) { ui.toast(e.message || '加载航线详情失败') }