diff --git a/index.html b/index.html index a12b994..4ab38ad 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + 嘉谷低空智控平台 diff --git a/public/assets/icons/dock-marker.svg b/public/assets/icons/dock-marker.svg new file mode 100644 index 0000000..5f99e50 --- /dev/null +++ b/public/assets/icons/dock-marker.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/icons/dock.svg b/public/assets/icons/dock.svg new file mode 100644 index 0000000..3a18485 --- /dev/null +++ b/public/assets/icons/dock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/icons/drone-marker.svg b/public/assets/icons/drone-marker.svg new file mode 100644 index 0000000..69d409a --- /dev/null +++ b/public/assets/icons/drone-marker.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/icons/drone.svg b/public/assets/icons/drone.svg new file mode 100644 index 0000000..1f06737 --- /dev/null +++ b/public/assets/icons/drone.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/assets/login-hero-dock-drone.png b/public/assets/login-hero-dock-drone.png index 050ea69..e931d3b 100644 Binary files a/public/assets/login-hero-dock-drone.png and b/public/assets/login-hero-dock-drone.png differ diff --git a/public/assets/logo.png b/public/assets/logo.png new file mode 100644 index 0000000..678408c Binary files /dev/null and b/public/assets/logo.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..eca969a Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/core/mapHelper.js b/src/core/mapHelper.js index cc0c635..ad332a8 100644 --- a/src/core/mapHelper.js +++ b/src/core/mapHelper.js @@ -72,4 +72,8 @@ class MapHelper { } } -export default new MapHelper() +const g = typeof globalThis !== 'undefined' ? globalThis : undefined +const singleton = (g && g.__LAIC_MAP_HELPER__) || new MapHelper() +if (g) g.__LAIC_MAP_HELPER__ = singleton + +export default singleton diff --git a/src/layout/MapLayer.vue b/src/layout/MapLayer.vue index e3e7a4a..db918ee 100644 --- a/src/layout/MapLayer.vue +++ b/src/layout/MapLayer.vue @@ -4,9 +4,55 @@ 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 @@ -24,6 +70,62 @@ function clearMapRef(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() { @@ -35,9 +137,13 @@ function initMap() { 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: 'map', + container, style: MAP_STYLES.satellite, center: MAP_OVERVIEW.center, zoom: MAP_OVERVIEW.zoom, @@ -46,11 +152,29 @@ function initMap() { projection: 'globe', }) - if (map.isStyleLoaded()) ensureGlobe(map) - else map.once('style.load', () => ensureGlobe(map)) + 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) + }) + } - commonRefs.setRef('map', map) - mapHelper.setMap(map) + // 布局未稳定时 Mapbox 落到错误高度(常见 ~300px),初始化后强制校正 + map.once('load', () => resizeMap(map)) + requestAnimationFrame(() => { + resizeMap(map) + requestAnimationFrame(() => resizeMap(map)) + }) + bindResizeObserver(map) } onMounted(() => { @@ -64,6 +188,10 @@ onMounted(() => { }) onUnmounted(() => { + if (resizeObserver) { + resizeObserver.disconnect() + resizeObserver = null + } if (map) { const dying = map map = null diff --git a/src/layout/components/SideMenu.vue b/src/layout/components/SideMenu.vue index 73aeca1..1550f8a 100644 --- a/src/layout/components/SideMenu.vue +++ b/src/layout/components/SideMenu.vue @@ -81,7 +81,7 @@ async function logout() {