You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

222 lines
5.5 KiB

<script setup>
import { onMounted, onUnmounted } from 'vue'
import mapboxgl from 'mapbox-gl'
import mapConfig, { MAP_STYLES, MAP_OVERVIEW, MAP_VIEW } from '@/config/map'
import commonRefs from '@/utils/commonRefs'
import mapHelper from '@/core/mapHelper'
import { applyChineseLabels } from '@/utils/mapLabels'
import 'mapbox-gl/dist/mapbox-gl.css'
// 保证任意路径创建的 Map 都挂到 DOM/window,避免 HMR/异常吞错后 Monitor 丢实例
if (!mapboxgl.Map.__laicPatched) {
const OriginalMap = mapboxgl.Map
class LaicMap extends OriginalMap {
constructor(options) {
super(options)
try {
const el = this.getContainer?.()
if (el) el.__laicMap = this
if (typeof window !== 'undefined') {
window.__LAIC_MAPBOX_MAP__ = this
window.dispatchEvent(new CustomEvent('laic-map-ready', { detail: this }))
}
} catch (_) {
/* ignore */
}
}
}
LaicMap.__laicPatched = true
mapboxgl.Map = LaicMap
}
let map = null
let resizeObserver = null
function resizeMap(target = map) {
if (!target) return
try {
target.resize()
} catch (_) {
/* ignore */
}
}
function bindResizeObserver(target) {
if (resizeObserver) {
resizeObserver.disconnect()
resizeObserver = null
}
const el = target?.getContainer?.()
if (!el || typeof ResizeObserver === 'undefined') return
resizeObserver = new ResizeObserver(() => {
resizeMap(target)
})
resizeObserver.observe(el)
}
function ensureGlobe(target) {
if (!target) return
try {
target.setProjection('globe')
} catch (error) {
console.warn('[MapLayer] setProjection(globe) failed:', error)
}
}
function clearMapRef(target) {
if (!target) return
// 仅清理属于当前实例的引用,避免 HMR 下旧 onUnmounted 误删新 map
if (mapHelper.getMap() === target) {
mapHelper.setMap(null)
commonRefs.removeRef('map')
}
if (typeof window !== 'undefined' && window.__LAIC_MAPBOX_MAP__ === target) {
delete window.__LAIC_MAPBOX_MAP__
}
try {
if (target.getContainer?.().__laicMap === target) delete target.getContainer().__laicMap
} catch (_) {
/* ignore */
}
}
function exposeMap(target) {
if (!target) return
const el =
(typeof document !== 'undefined' ? document.getElementById('map') : null) ||
target.getContainer?.()
if (el) {
try {
Object.defineProperty(el, '__laicMap', { value: target, writable: true, configurable: true })
} catch (_) {
el.__laicMap = target
}
}
if (typeof window !== 'undefined') {
try {
Object.defineProperty(window, '__LAIC_MAPBOX_MAP__', {
value: target,
writable: true,
configurable: true,
})
} catch (_) {
window.__LAIC_MAPBOX_MAP__ = target
}
window.dispatchEvent(new CustomEvent('laic-map-ready', { detail: target }))
}
commonRefs.setRef('map', target)
mapHelper.setMap(target)
}
function destroyOrphanMap(container) {
if (!container) return
// HMR / 重复挂载时容器上常残留旧 Mapbox 实例,继续 new Map 会直接抛错
const orphan = container.__laicMap
if (orphan && typeof orphan.remove === 'function') {
try {
orphan.remove()
} catch (_) {
/* ignore */
}
}
delete container.__laicMap
// Mapbox 会给容器加 mapboxgl-map;清掉子节点避免二次初始化失败
if (container.classList?.contains('mapboxgl-map')) {
container.innerHTML = ''
container.classList.remove('mapboxgl-map')
container.removeAttribute('style')
}
}
function initMap() {
const tk = mapConfig.token
if (!tk) {
console.error('[MapLayer] 缺少 APP_MAPBOX_TOKEN,跳过地图初始化')
commonRefs.setRef('map', null)
mapHelper.setMap(null)
return
}
const container =
(typeof document !== 'undefined' ? document.getElementById('map') : null) || 'map'
if (typeof container !== 'string') destroyOrphanMap(container)
mapboxgl.accessToken = tk
map = new mapboxgl.Map({
container,
style: MAP_STYLES.satellite,
center: MAP_OVERVIEW.center,
zoom: MAP_OVERVIEW.zoom,
minZoom: MAP_VIEW.minZoom,
attributionControl: false,
projection: 'globe',
})
exposeMap(map)
console.info('[MapLayer] map ready', {
hasDom: !!(typeof document !== 'undefined' && document.getElementById('map')?.__laicMap),
hasWin: !!(typeof window !== 'undefined' && window.__LAIC_MAPBOX_MAP__),
})
if (map.isStyleLoaded()) {
ensureGlobe(map)
applyChineseLabels(map)
} else {
map.once('style.load', () => {
ensureGlobe(map)
applyChineseLabels(map)
})
}
// 布局未稳定时 Mapbox 落到错误高度(常见 ~300px),初始化后强制校正
map.once('load', () => resizeMap(map))
requestAnimationFrame(() => {
resizeMap(map)
requestAnimationFrame(() => resizeMap(map))
})
bindResizeObserver(map)
}
onMounted(() => {
try {
initMap()
} catch (error) {
console.error('[MapLayer] 地图初始化失败:', error)
commonRefs.setRef('map', null)
mapHelper.setMap(null)
}
})
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
resizeObserver = null
}
if (map) {
const dying = map
map = null
try {
dying.remove()
} catch (_) {
/* ignore */
}
clearMapRef(dying)
}
})
</script>
<template>
<div id="map" :class="s.root" />
</template>
<style lang="less" module="s">
.root {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
height: 100%;
z-index: 0;
}
</style>