|
|
|
@ -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 }) |
|
|
|
}) |
|
|
|
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 || '加载航线详情失败') |
|
|
|
} |
|
|
|
|