From 655bb65d00d6203871d6a824d20c1ac7a2d12137 Mon Sep 17 00:00:00 2001 From: lion <120344285@qq.com> Date: Tue, 9 Jun 2026 17:25:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.production | 2 +- src/assets/dashboard-page.css | 28 ++- src/components/TiandituPickMap.vue | 133 ++++++++---- src/utils/tiandituMap.ts | 261 +++++++++++++++++++++++- src/views/assets/map/index.vue | 60 +++--- src/views/assets/universities/index.vue | 11 +- src/views/demands/index.vue | 13 +- src/views/teachers/index.vue | 12 +- 8 files changed, 442 insertions(+), 78 deletions(-) diff --git a/.env.production b/.env.production index 15e7382..436389e 100644 --- a/.env.production +++ b/.env.production @@ -1,4 +1,4 @@ # 生产环境请改为实际后端 API 根路径(需包含 /api 前缀,或与后端约定一致) -VITE_API_BASE_URL=https://your-api.example.com/api +VITE_API_BASE_URL=https://slake.ali251.langye.net/api # 天地图 JavaScript API 4.0 Key VITE_TIANDITU_TK= diff --git a/src/assets/dashboard-page.css b/src/assets/dashboard-page.css index 4ff984d..153af5b 100644 --- a/src/assets/dashboard-page.css +++ b/src/assets/dashboard-page.css @@ -601,16 +601,33 @@ min-height: 460px; } -.radar-map-container { +.radar-map-stage { min-height: 460px; position: relative; background: #e8eef5; } -/* 高校点位:圆点 + 校名横排(对齐原型 .dashboard-radar-dot) */ +.radar-map-container { + position: absolute; + inset: 0; + /* z-index:0 建立层叠上下文,将天地图 SDK 内部 pane(tile-pane z=200、 + map-pane z=400、marker-pane z=600 等)限制在容器内, + 否则它们会盖住下方 z-index:20 的点位层导致“点位被瓦片遮盖” */ + z-index: 0; + background: #e8eef5; + cursor: grab; +} + +.radar-map-container:active { + cursor: grabbing; +} + +/* 高校点位:圆点 + 校名横排(天地图 SDK 覆盖物,整体可点击选中高校) */ .radar-map-container .slake-map-school-marker { position: absolute; z-index: 400; + /* 圆点与校名整体可点击,点名字也能加载老师 */ + pointer-events: auto; display: inline-flex; align-items: center; gap: 4px; @@ -644,6 +661,7 @@ .radar-map-container .slake-map-school-marker.is-active .slake-map-school-dot { width: 13px; height: 13px; + background: #8a1418; box-shadow: 0 0 0 6px rgba(177, 30, 35, 0.18), 0 8px 18px rgba(15, 23, 42, 0.16); @@ -668,6 +686,12 @@ color: #1f2937; } +.radar-map-container .slake-map-school-marker.is-active .slake-map-school-label { + border-color: rgba(177, 30, 35, 0.55); + color: #8a1418; + font-weight: 600; +} + .radar-map-placeholder { position: absolute; inset: 0; diff --git a/src/components/TiandituPickMap.vue b/src/components/TiandituPickMap.vue index 00dc772..d106ebd 100644 --- a/src/components/TiandituPickMap.vue +++ b/src/components/TiandituPickMap.vue @@ -2,13 +2,16 @@ import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { ElMessage } from 'element-plus' import { + bindMapResizeObserver, + configureMapInteraction, + createTiandituMap, getTiandituKey, - invalidateMapSize, loadTianditu, normalizeTiandituPois, parseTiandituPoiLonlat, + refreshMapView, + waitForMapContainerReady, SUZHOU_MAP_CENTER, - SUZHOU_MAP_ZOOM, type TiandituLocalSearch, type TiandituMap, type TiandituMapClickEvent, @@ -24,6 +27,7 @@ const props = withDefaults( const longitude = defineModel('longitude', { default: '' }) const latitude = defineModel('latitude', { default: '' }) +const mapContainerId = `tianditu-pick-map-${Math.random().toString(36).slice(2)}` const containerRef = ref(null) const mapError = ref('') const mapLoading = ref(false) @@ -35,32 +39,30 @@ let map: TiandituMap | null = null let marker: TiandituMarker | null = null let localSearch: TiandituLocalSearch | null = null let tiandituApi: NonNullable | null = null -let resizeTimers: ReturnType[] = [] +let unbindResizeObserver: (() => void) | null = null +let pendingDefaultSearch = false function parseCoord(v: string) { + // 注意:Number('') === 0 且 0 是有限数,若不先判空,空坐标会被当成 (0,0) + // 导致新增(无坐标)时地图定位到经纬度 0,0(几内亚湾,天地图无瓦片)而空白 + if (v == null || String(v).trim() === '') return null const n = Number(v) return Number.isFinite(n) ? n : null } -function clearResizeTimers() { - for (const timer of resizeTimers) clearTimeout(timer) - resizeTimers = [] -} - -function scheduleMapResize() { - clearResizeTimers() - const delays = [0, 80, 240, 480] - for (const delay of delays) { - resizeTimers.push( - setTimeout(() => { - if (map) invalidateMapSize(map) - }, delay), - ) +function getMapCenter() { + const lng = parseCoord(longitude.value) + const lat = parseCoord(latitude.value) + if (lng != null && lat != null) { + return { lng, lat, zoom: 14 } } + return { lng: SUZHOU_MAP_CENTER.lng, lat: SUZHOU_MAP_CENTER.lat, zoom: 14 } } function destroyMapInstance() { - clearResizeTimers() + unbindResizeObserver?.() + unbindResizeObserver = null + pendingDefaultSearch = false if (map && marker) { try { map.removeOverLay(marker) @@ -79,7 +81,7 @@ function applyPick(lng: number, lat: number, zoom = 16) { longitude.value = lng.toFixed(6) latitude.value = lat.toFixed(6) if (map && tiandituApi) { - map.centerAndZoom(new tiandituApi.LngLat(lng, lat), zoom) + refreshMapView(map, tiandituApi, { lng, lat }, zoom) placeMarker(tiandituApi, lng, lat) } } @@ -142,9 +144,25 @@ function selectPoi(poi: TiandituSearchPoi) { searchResults.value = [] } +function runPendingDefaultSearch() { + if (!pendingDefaultSearch) return + pendingDefaultSearch = false + if (props.defaultKeyword.trim()) { + searchKeyword.value = props.defaultKeyword.trim() + runSearch() + } +} + +function forceDefaultMapRefresh() { + if (!map || !tiandituApi) return + const view = getMapCenter() + refreshMapView(map, tiandituApi, { lng: view.lng, lat: view.lat }, view.zoom) +} + async function initMap() { await nextTick() - if (!containerRef.value) return + const container = containerRef.value + if (!container) return mapError.value = '' if (!getTiandituKey()) { @@ -156,22 +174,32 @@ async function initMap() { try { destroyMapInstance() + const ready = await waitForMapContainerReady(container) + if (!ready) { + mapError.value = '地图容器未就绪,请关闭后重试' + return + } + const T = await loadTianditu() tiandituApi = T - const container = containerRef.value container.innerHTML = '' + container.id = mapContainerId + container.style.width = '100%' + container.style.height = `${props.height}px` + + map = createTiandituMap(T, mapContainerId) + configureMapInteraction(map) - map = new T.Map(container) - map.enableScrollWheelZoom() + const center = getMapCenter() + map.centerAndZoom(new T.LngLat(center.lng, center.lat), center.zoom) const lng = parseCoord(longitude.value) const lat = parseCoord(latitude.value) if (lng != null && lat != null) { - map.centerAndZoom(new T.LngLat(lng, lat), 14) placeMarker(T, lng, lat) } else { - map.centerAndZoom(new T.LngLat(SUZHOU_MAP_CENTER.lng, SUZHOU_MAP_CENTER.lat), SUZHOU_MAP_ZOOM) + placeMarker(T, center.lng, center.lat) } initLocalSearch(T) @@ -184,17 +212,30 @@ async function initMap() { searchResults.value = [] }) - scheduleMapResize() + map.addEventListener?.('load', () => { + if (!map || !tiandituApi) return + const view = getMapCenter() + refreshMapView(map, tiandituApi, { lng: view.lng, lat: view.lat }, view.zoom) + runPendingDefaultSearch() + }) + + unbindResizeObserver = bindMapResizeObserver(container, () => map) + refreshMapView(map, T, { lng: center.lng, lat: center.lat }, center.zoom) + window.setTimeout(forceDefaultMapRefresh, 300) + window.setTimeout(forceDefaultMapRefresh, 800) + window.setTimeout(forceDefaultMapRefresh, 1500) if (props.defaultKeyword.trim()) { - searchKeyword.value = props.defaultKeyword.trim() - runSearch() + pendingDefaultSearch = true } } catch (e) { mapError.value = e instanceof Error ? e.message : '地图加载失败' } finally { mapLoading.value = false - scheduleMapResize() + if (map && tiandituApi) { + const view = getMapCenter() + refreshMapView(map, tiandituApi, { lng: view.lng, lat: view.lat }, view.zoom) + } } } @@ -207,11 +248,7 @@ watch([longitude, latitude], async () => { }) onMounted(() => { - requestAnimationFrame(() => { - requestAnimationFrame(() => { - void initMap() - }) - }) + void initMap() }) onBeforeUnmount(() => { @@ -242,7 +279,8 @@ onBeforeUnmount(() => {
{{ mapError }}
-
+
+
地图加载中…
{ .pick-map-shell { position: relative; width: 100%; + min-height: 200px; overflow: hidden; border: 1px solid var(--el-border-color-light); border-radius: 6px; background: #e8eef5; } +.pick-map-loading { + position: absolute; + inset: 0; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + color: var(--el-text-color-secondary); + font-size: 13px; + background: rgba(255, 255, 255, 0.72); + pointer-events: none; +} + .pick-map { position: relative; z-index: 0; width: 100%; overflow: hidden; cursor: crosshair; - isolation: isolate; } +.pick-map :deep(.tdt-container), .pick-map :deep(.tdt-map) { width: 100% !important; height: 100% !important; } +.pick-map :deep(.tdt-overlay-pane), +.pick-map :deep(.tdt-marker-pane) { + pointer-events: none; +} + +.pick-map :deep(.tdt-marker-pane img) { + pointer-events: auto; +} + .pick-map-error { padding: 24px 12px; text-align: center; diff --git a/src/utils/tiandituMap.ts b/src/utils/tiandituMap.ts index 8f29af8..a05a0c7 100644 --- a/src/utils/tiandituMap.ts +++ b/src/utils/tiandituMap.ts @@ -1,7 +1,10 @@ declare global { interface Window { T?: { - Map: new (container: HTMLElement | string) => TiandituMap + Map: new ( + container: HTMLElement | string, + options?: { layers?: TiandituTileLayer[] }, + ) => TiandituMap LngLat: new (lng: number, lat: number) => TiandituLngLat Marker: new (lnglat: TiandituLngLat, options?: { icon?: TiandituIcon }) => TiandituMarker Icon: new (options: { @@ -10,6 +13,10 @@ declare global { iconAnchor?: TiandituPoint }) => TiandituIcon Point: new (x: number, y: number) => TiandituPoint + TileLayer?: new ( + url: string, + options?: { minZoom?: number; maxZoom?: number }, + ) => TiandituTileLayer Label: new (options: { text: string position: TiandituLngLat @@ -81,6 +88,10 @@ export interface TiandituIcon { // marker icon handle } +export interface TiandituTileLayer { + // tile layer handle +} + export interface TiandituPoint { x: number y: number @@ -88,28 +99,254 @@ export interface TiandituPoint { export interface TiandituMapClickEvent { lnglat?: TiandituLngLat + containerPoint?: TiandituPoint } export interface TiandituMap { centerAndZoom(lnglat: TiandituLngLat, zoom: number): void enableScrollWheelZoom(): void - addOverLay(overlay: TiandituMarker | TiandituSchoolOverlay): void - removeOverLay(overlay: TiandituMarker | TiandituSchoolOverlay): void + enableDrag?(): void + disableDrag?(): void + enableAutoResize?(): void + panBy?(position: TiandituPoint): void + addOverLay(overlay: TiandituMarker | TiandituLabel | TiandituSchoolOverlay): void + removeOverLay(overlay: TiandituMarker | TiandituLabel | TiandituSchoolOverlay): void setViewport?(points: TiandituLngLat[]): void clearOverLays?(): void + addLayer?(layer: TiandituTileLayer): void addEventListener?(type: string, handler: (e?: TiandituMapClickEvent) => void): void removeEventListener?(type: string, handler: (e?: TiandituMapClickEvent) => void): void lngLatToLayerPoint?(lnglat: TiandituLngLat): TiandituPoint - getPanes?(): { overlayPane: HTMLElement } + lngLatToContainerPoint?(lnglat: TiandituLngLat): TiandituPoint + getPanes?(): Record + getContainer?(): HTMLElement checkResize?(): void destroy?(): void } +const MAP_INTERACTIVE_PANE_KEYS = new Set(['mapPane', 'tilePane', 'floatPane']) +const MAP_PASSTHROUGH_PANE_CLASS_RE = + /tdt-overlay-pane|tdt-marker-pane|tdt-tooltip-pane|tdt-popup-pane|tdt-shadow-pane/i + +function invokeMapMethod(map: TiandituMap, method: string) { + const fn = (map as unknown as Record)[method] + if (typeof fn === 'function') { + ;(fn as () => void).call(map) + } +} + +/** 创建天地图实例。使用 SDK 默认底图,避免自定义瓦片图层影响拖拽/初始化。 */ +export function createTiandituMap( + T: NonNullable, + container: HTMLElement | string, +): TiandituMap { + return new T.Map(container) +} + /** 弹窗内地图在容器尺寸变化后需重算布局,避免瓦片错位到页面顶部 */ export function invalidateMapSize(map: TiandituMap) { map.checkResize?.() } +/** 等待地图容器具备有效宽高(弹窗动画期间常为 0) */ +export async function waitForMapContainerReady( + container: HTMLElement, + timeoutMs = 5000, +): Promise { + const deadline = Date.now() + timeoutMs + while (Date.now() < deadline) { + const rect = container.getBoundingClientRect() + if (rect.width > 0 && rect.height > 0) return true + await new Promise((resolve) => requestAnimationFrame(() => resolve())) + } + return false +} + +/** 重新计算尺寸并刷新视野,修复弹窗内瓦片不渲染 */ +export function refreshMapView( + map: TiandituMap, + T: NonNullable, + center: { lng: number; lat: number }, + zoom: number, +) { + invalidateMapSize(map) + map.centerAndZoom(new T.LngLat(center.lng, center.lat), zoom) + scheduleMapResize(map, [50, 200, 500]) +} + +/** 容器尺寸变化时自动触发 checkResize(弹窗、侧栏展开等场景) */ +export function bindMapResizeObserver( + container: HTMLElement, + getMap: () => TiandituMap | null, +): () => void { + if (typeof ResizeObserver === 'undefined') { + return () => {} + } + + const observer = new ResizeObserver(() => { + const map = getMap() + if (map) invalidateMapSize(map) + }) + observer.observe(container) + return () => observer.disconnect() +} + +/** 多次延迟触发 checkResize,覆盖弹窗动画结束后的布局 */ +export function scheduleMapResize(map: TiandituMap, delays = [0, 80, 240, 480, 800]) { + for (const delay of delays) { + window.setTimeout(() => invalidateMapSize(map), delay) + } +} + +/** + * 覆盖物层不拦截鼠标,仅可交互标记响应点击。 + * SDK 的 getPanes 与 DOM class 双路径设置,避免类名不一致导致拖拽失效。 + */ +export function applyOverlayPassthrough(map: TiandituMap) { + const panes = map.getPanes?.() + if (panes) { + for (const [key, pane] of Object.entries(panes)) { + if (!pane || MAP_INTERACTIVE_PANE_KEYS.has(key)) continue + pane.style.pointerEvents = 'none' + } + } + + const root = map.getContainer?.() + if (root) { + root.querySelectorAll('[class*="tdt-"][class*="pane"]').forEach((pane) => { + if (MAP_PASSTHROUGH_PANE_CLASS_RE.test(pane.className)) { + pane.style.pointerEvents = 'none' + } + }) + root.querySelectorAll('.slake-map-school-marker').forEach((marker) => { + marker.style.pointerEvents = 'auto' + }) + } +} + +/** 启用滚轮缩放与拖拽,并让覆盖物层不挡住地图 */ +export function configureMapInteraction(map: TiandituMap) { + invokeMapMethod(map, 'enableScrollWheelZoom') + invokeMapMethod(map, 'enableDrag') + invokeMapMethod(map, 'enableInertia') + invokeMapMethod(map, 'enableAutoResize') + + const setOptions = (map as TiandituMap & { setOptions?: (opts: { drag?: boolean }) => void }) + .setOptions + setOptions?.({ drag: true }) +} + +/** + * 自定义 HTML 覆盖物会挡住 SDK 内置拖拽时,改用手动 panBy 平移。 + * 会关闭 SDK 拖拽,避免与手动平移冲突。 + */ +export function bindMapDragPan( + map: TiandituMap, + T: NonNullable, + options: { ignoreSelector?: string } = {}, +): () => void { + const container = map.getContainer?.() + if (!container || !map.panBy) return () => {} + const root: HTMLElement = container + + invokeMapMethod(map, 'disableDrag') + + const ignoreSelector = + options.ignoreSelector ?? '.slake-map-school-marker, .slake-map-school-marker *' + + let panning = false + let lastX = 0 + let lastY = 0 + let pendingDx = 0 + let pendingDy = 0 + let panRaf = 0 + + function shouldIgnore(target: EventTarget | null) { + return target instanceof Element && Boolean(target.closest(ignoreSelector)) + } + + function startPan(clientX: number, clientY: number) { + panning = true + lastX = clientX + lastY = clientY + root.style.cursor = 'grabbing' + } + + function movePan(clientX: number, clientY: number) { + if (!panning) return + const dx = clientX - lastX + const dy = clientY - lastY + if (!dx && !dy) return + lastX = clientX + lastY = clientY + pendingDx += dx + pendingDy += dy + if (panRaf) return + panRaf = requestAnimationFrame(() => { + panRaf = 0 + if (!pendingDx && !pendingDy) return + map.panBy?.(new T.Point(-pendingDx, -pendingDy)) + pendingDx = 0 + pendingDy = 0 + }) + } + + function endPan() { + if (!panning) return + panning = false + root.style.cursor = 'grab' + } + + const onMouseDown = (e: MouseEvent) => { + if (e.button !== 0 || shouldIgnore(e.target)) return + startPan(e.clientX, e.clientY) + e.preventDefault() + } + + const onMouseMove = (e: MouseEvent) => { + movePan(e.clientX, e.clientY) + } + + const onMouseUp = () => endPan() + + const onTouchStart = (e: TouchEvent) => { + if (shouldIgnore(e.target) || e.touches.length !== 1) return + startPan(e.touches[0].clientX, e.touches[0].clientY) + } + + const onTouchMove = (e: TouchEvent) => { + if (!panning || e.touches.length !== 1) return + movePan(e.touches[0].clientX, e.touches[0].clientY) + e.preventDefault() + } + + const onTouchEnd = () => endPan() + + root.style.cursor = 'grab' + root.addEventListener('mousedown', onMouseDown) + window.addEventListener('mousemove', onMouseMove) + window.addEventListener('mouseup', onMouseUp) + root.addEventListener('touchstart', onTouchStart, { passive: false }) + root.addEventListener('touchmove', onTouchMove, { passive: false }) + root.addEventListener('touchend', onTouchEnd) + root.addEventListener('touchcancel', onTouchEnd) + + return () => { + if (panRaf) { + cancelAnimationFrame(panRaf) + panRaf = 0 + } + root.removeEventListener('mousedown', onMouseDown) + window.removeEventListener('mousemove', onMouseMove) + window.removeEventListener('mouseup', onMouseUp) + root.removeEventListener('touchstart', onTouchStart) + root.removeEventListener('touchmove', onTouchMove) + root.removeEventListener('touchend', onTouchEnd) + root.removeEventListener('touchcancel', onTouchEnd) + root.style.cursor = '' + } +} + interface SchoolMapOverlayInstance { lnglat: TiandituLngLat options: SchoolMapOverlayOptions @@ -204,13 +441,23 @@ function getSchoolMapOverlayClass(T: NonNullable) { if (this.options.active) div.classList.add('is-active') div.setAttribute('role', 'button') div.setAttribute('tabindex', '0') + div.style.pointerEvents = 'auto' div.innerHTML = '' + `${escapeMapLabelHtml(this.options.name.trim() || '—')}` this._div = div - map.getPanes?.().overlayPane.appendChild(div) - - this._onMapChange = () => this.update() + const panes = map.getPanes?.() + const overlayPane = panes?.overlayPane ?? panes?.markerPane + if (overlayPane) overlayPane.appendChild(div) + + let moveRaf = 0 + this._onMapChange = () => { + if (moveRaf) return + moveRaf = requestAnimationFrame(() => { + moveRaf = 0 + this.update() + }) + } map.addEventListener?.('move', this._onMapChange) map.addEventListener?.('zoomend', this._onMapChange) diff --git a/src/views/assets/map/index.vue b/src/views/assets/map/index.vue index 4201389..b1858d2 100644 --- a/src/views/assets/map/index.vue +++ b/src/views/assets/map/index.vue @@ -13,10 +13,15 @@ import TeacherDetailDialog from '@/views/teachers/components/TeacherDetailDialog import { fetchRadarMap, type RadarMapData, type RadarSchool } from '@/api/admin/assets' import { starDisplay } from '@/utils/teacherStar' import { + applyOverlayPassthrough, + bindMapDragPan, centerMapOnSuzhou, + configureMapInteraction, createSchoolMapOverlay, + createTiandituMap, getTiandituKey, loadTianditu, + scheduleMapResize, type TiandituMap, type TiandituSchoolOverlay, } from '@/utils/tiandituMap' @@ -32,7 +37,8 @@ const detailTeacherId = ref(null) const mapContainerRef = ref(null) let mapInstance: TiandituMap | null = null -let schoolOverlays: { overlay: TiandituSchoolOverlay; schoolId: number }[] = [] +let unbindDragPan: (() => void) | null = null +let schoolOverlays: { school: RadarSchool; overlay: TiandituSchoolOverlay }[] = [] const summary = computed(() => mapData.value?.summary) const quality = computed(() => mapData.value?.quality || []) @@ -57,13 +63,8 @@ function openTeacher(id: number) { function selectSchool(school: RadarSchool) { activeSchool.value = school - refreshMarkerActiveState() -} - -function refreshMarkerActiveState() { - const activeId = activeSchool.value?.id ?? null - for (const { overlay, schoolId } of schoolOverlays) { - overlay.setActive?.(schoolId === activeId) + for (const item of schoolOverlays) { + item.overlay.setActive?.(item.school.id === school.id) } } @@ -105,28 +106,38 @@ async function initMap() { const container = mapContainerRef.value container.innerHTML = '' - mapInstance = new T.Map(container) - mapInstance.enableScrollWheelZoom() + mapInstance = createTiandituMap(T, container) + configureMapInteraction(mapInstance) + centerMapOnSuzhou(mapInstance, T) schoolOverlays = [] - const activeId = activeSchool.value?.id ?? null for (const school of schools) { const overlay = createSchoolMapOverlay( T, { name: school.name, longitude: school.longitude, latitude: school.latitude }, - activeId === school.id, + activeSchool.value?.id === school.id, ) - mapInstance.addOverLay(overlay) overlay.addEventListener('click', () => selectSchool(school)) - schoolOverlays.push({ overlay, schoolId: school.id }) + mapInstance.addOverLay(overlay) + schoolOverlays.push({ school, overlay }) } - centerMapOnSuzhou(mapInstance, T) + // 覆盖物层不拦截鼠标(仅圆点/校名响应点击),并以手动 panBy 平移确保地图可拖动 + applyOverlayPassthrough(mapInstance) + unbindDragPan = bindMapDragPan(mapInstance, T) + + scheduleMapResize(mapInstance) + const reapplyPassthrough = () => { + if (mapInstance) applyOverlayPassthrough(mapInstance) + } + mapInstance.addEventListener?.('load', reapplyPassthrough) + window.setTimeout(reapplyPassthrough, 300) + window.setTimeout(reapplyPassthrough, 800) } catch (e) { const msg = e instanceof Error ? e.message : '地图初始化失败' mapError.value = msg.includes('脚本') || msg.includes('SDK') - ? `${msg}。若 Key 已配置域名白名单,请使用 https://slake.ali251.langye.net 访问(本地可在 hosts 绑定该域名后访问 http://slake.ali251.langye.net:5173)` + ? `${msg}。若 Key 已配置域名白名单,请使用 https://slake.ali251.langye.net 访问(本地开发可在 hosts 绑定该域名后访问 http://slake.ali251.langye.net:5173)` : msg destroyMap() } finally { @@ -135,15 +146,11 @@ async function initMap() { } function destroyMap() { + unbindDragPan?.() + unbindDragPan = null if (mapInstance) { - for (const { overlay } of schoolOverlays) { - try { - mapInstance.removeOverLay(overlay) - } catch { - /* 覆盖物可能已被地图销毁 */ - } - } mapInstance.clearOverLays?.() + mapInstance.destroy?.() } schoolOverlays = [] mapInstance = null @@ -210,10 +217,11 @@ onBeforeUnmount(destroyMap)
+ class="radar-map-stage" + > +
+