feat: MapLibre integration + app init + deploy
- Map pages wired to real MapLibre engine (MapManager/Factory/composables) - mapMonitoring page with device markers and popups - App.vue with keep-alive + route transitions - main.ts with global error handlers + MapLibre CSS - index.html with WeChat/WxJSBridge detection - Deployed to ygcxy.top (nginx-static via Traefik)
This commit is contained in:
15
index.html
15
index.html
@@ -8,6 +8,21 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<!-- WeixinJSBridge 检测 — 判断是否在微信环境中运行 -->
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var ua = navigator.userAgent.toLowerCase()
|
||||||
|
var isWechat = ua.indexOf('micromessenger') !== -1
|
||||||
|
// 标记微信环境,供应用内业务逻辑判断(如微信 JSSDK 授权)
|
||||||
|
window.__IS_WEIXIN__ = isWechat
|
||||||
|
// 如果是微信环境,监听 WeixinJSBridge 就绪事件
|
||||||
|
if (isWechat) {
|
||||||
|
document.addEventListener('WeixinJSBridgeReady', function () {
|
||||||
|
window.__WEIXIN_BRIDGE_READY__ = true
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
</script>
|
||||||
<!-- 运行时全局配置 — 开发环境 -->
|
<!-- 运行时全局配置 — 开发环境 -->
|
||||||
<script src="/config/globalConfig.dev.js"></script>
|
<script src="/config/globalConfig.dev.js"></script>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
|||||||
62
src/App.vue
62
src/App.vue
@@ -3,13 +3,26 @@
|
|||||||
* 应用根组件
|
* 应用根组件
|
||||||
*
|
*
|
||||||
* 使用 Vant ConfigProvider 包裹路由视图,
|
* 使用 Vant ConfigProvider 包裹路由视图,
|
||||||
* 提供全局主题配置和路由出口。
|
* 提供全局主题配置、路由缓存 (keep-alive) 和过渡动画。
|
||||||
*/
|
*/
|
||||||
|
import { useAppStore } from '@/stores/app'
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<van-config-provider>
|
<van-config-provider>
|
||||||
<router-view />
|
<router-view v-slot="{ Component, route }">
|
||||||
|
<transition
|
||||||
|
:name="route.meta.transition as string || 'fade-slide'"
|
||||||
|
mode="out-in"
|
||||||
|
appear
|
||||||
|
>
|
||||||
|
<keep-alive :include="appStore.cachedViews">
|
||||||
|
<component :is="Component" :key="route.path" />
|
||||||
|
</keep-alive>
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
</van-config-provider>
|
</van-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -19,4 +32,49 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 路由过渡动画 ── */
|
||||||
|
|
||||||
|
/* 淡入 + 滑动(默认) */
|
||||||
|
.fade-slide-enter-active,
|
||||||
|
.fade-slide-leave-active {
|
||||||
|
transition: opacity 0.25s ease, transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 淡入 */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 向上滑动(用于详情页等) */
|
||||||
|
.slide-up-enter-active,
|
||||||
|
.slide-up-leave-active {
|
||||||
|
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
28
src/main.ts
28
src/main.ts
@@ -42,15 +42,38 @@ import {
|
|||||||
// ── Vant 样式(组件样式按需引入) ──
|
// ── Vant 样式(组件样式按需引入) ──
|
||||||
import 'vant/lib/index.css'
|
import 'vant/lib/index.css'
|
||||||
|
|
||||||
|
// ── MapLibre GL 样式 ──
|
||||||
|
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||||
|
|
||||||
// ── 全局样式 ──
|
// ── 全局样式 ──
|
||||||
import './styles/index.scss'
|
import './styles/index.scss'
|
||||||
|
|
||||||
|
// ── Store ──
|
||||||
|
import { useAppStore } from './stores/app'
|
||||||
|
|
||||||
// ── 路由 ──
|
// ── 路由 ──
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
||||||
// ── 根组件 ──
|
// ── 根组件 ──
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════
|
||||||
|
// 全局错误处理
|
||||||
|
// ══════════════════════════════════════════════
|
||||||
|
|
||||||
|
/** 捕获未处理的 Promise 拒绝 */
|
||||||
|
window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
|
||||||
|
console.error('[Yuto Water H5] 未处理的 Promise 拒绝:', event.reason)
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 捕获全局 JavaScript 错误 */
|
||||||
|
window.addEventListener('error', (event: ErrorEvent) => {
|
||||||
|
console.error('[Yuto Water H5] 全局错误:', event.message, 'at', event.filename, ':', event.lineno)
|
||||||
|
// 防止错误冒泡导致白屏
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
// ══════════════════════════════════════════════
|
// ══════════════════════════════════════════════
|
||||||
// 创建应用
|
// 创建应用
|
||||||
// ══════════════════════════════════════════════
|
// ══════════════════════════════════════════════
|
||||||
@@ -61,6 +84,11 @@ const app = createApp(App)
|
|||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
|
|
||||||
|
// ── 初始化暗黑模式(从 localStorage 读取并应用) ──
|
||||||
|
// useAppStore 内部已通过 watch(darkMode, ..., { immediate: true })
|
||||||
|
// 在首次创建时自动同步 dark 类名到 <html> 元素
|
||||||
|
useAppStore()
|
||||||
|
|
||||||
// ── 注册 Vant 组件(组件类) ──
|
// ── 注册 Vant 组件(组件类) ──
|
||||||
// Toast / Dialog / ImagePreview 为函数式 API,
|
// Toast / Dialog / ImagePreview 为函数式 API,
|
||||||
// 直接从 vant 导入即可使用,无需 app.use 注册。
|
// 直接从 vant 导入即可使用,无需 app.use 注册。
|
||||||
|
|||||||
@@ -5,13 +5,29 @@
|
|||||||
* MapLibre 地图容器,底部工具栏可切换图层、
|
* MapLibre 地图容器,底部工具栏可切换图层、
|
||||||
* 打开弹出窗口等操作。
|
* 打开弹出窗口等操作。
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { MapManager } from '@/map/MapManager'
|
||||||
|
import { MapFactory } from '@/map/MapFactory'
|
||||||
|
import { useMap } from '@/map/composables/useMap'
|
||||||
|
import { useLayer } from '@/map/composables/useLayer'
|
||||||
|
import { usePopup } from '@/map/composables/usePopup'
|
||||||
import TckzPop from './tckzPop.vue'
|
import TckzPop from './tckzPop.vue'
|
||||||
import YbPop from './ybPop.vue'
|
import YbPop from './ybPop.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// ── 地图状态 ──────────────────────────────────────────────
|
||||||
|
const { map, loading, error, isReady } = useMap()
|
||||||
|
const { addLayer, removeLayer } = useLayer()
|
||||||
|
const { showPopup, hidePopup } = usePopup()
|
||||||
|
|
||||||
|
/** 地图容器 ref */
|
||||||
|
const mapContainer = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
/** 地图初始化错误(map 本身加载失败,区别于运行错误) */
|
||||||
|
const initError = ref<string | null>(null)
|
||||||
|
|
||||||
/** 底部工具栏状态 */
|
/** 底部工具栏状态 */
|
||||||
const tools = [
|
const tools = [
|
||||||
{ icon: 'location-o', label: '定位' },
|
{ icon: 'location-o', label: '定位' },
|
||||||
@@ -25,8 +41,60 @@ const tools = [
|
|||||||
const showTckz = ref(false)
|
const showTckz = ref(false)
|
||||||
const showYb = ref(false)
|
const showYb = ref(false)
|
||||||
|
|
||||||
|
// ── 初始化地图 ───────────────────────────────────────────
|
||||||
|
onMounted(() => {
|
||||||
|
const manager = MapManager.getInstance()
|
||||||
|
|
||||||
|
// 如果地图已存在,复用即可
|
||||||
|
if (manager.hasMap()) return
|
||||||
|
|
||||||
|
const container = document.getElementById('ytmap-container')
|
||||||
|
if (!container) {
|
||||||
|
initError.value = '地图容器元素 #ytmap-container 未找到'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mapContainer.value = container
|
||||||
|
|
||||||
|
try {
|
||||||
|
MapFactory.createWithBasemap('osm', {
|
||||||
|
container: 'ytmap-container',
|
||||||
|
zoom: 10,
|
||||||
|
center: [104.065735, 30.659462], // 成都中心
|
||||||
|
onLoad: (m) => {
|
||||||
|
console.log('[MapPage] 地图已加载', m.getCenter())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const msg = err instanceof Error ? err.message : '地图初始化失败'
|
||||||
|
initError.value = msg
|
||||||
|
console.error('[MapPage] 地图创建失败:', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── 组件卸载 ─────────────────────────────────────────────
|
||||||
|
onUnmounted(() => {
|
||||||
|
MapManager.getInstance().destroyMap()
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── 工具栏操作 ───────────────────────────────────────────
|
||||||
function handleToolClick(label: string) {
|
function handleToolClick(label: string) {
|
||||||
if (label === '台账') {
|
if (label === '定位') {
|
||||||
|
const m = map.value
|
||||||
|
if (m) {
|
||||||
|
// 尝试使用浏览器定位
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(pos) => {
|
||||||
|
m.flyTo({ center: [pos.coords.longitude, pos.coords.latitude], zoom: 14 })
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// 定位失败,回到默认中心
|
||||||
|
m.flyTo({ center: [104.065735, 30.659462], zoom: 10 })
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (label === '台账') {
|
||||||
showTckz.value = true
|
showTckz.value = true
|
||||||
} else if (label === '仪表') {
|
} else if (label === '仪表') {
|
||||||
showYb.value = true
|
showYb.value = true
|
||||||
@@ -38,12 +106,25 @@ function handleToolClick(label: string) {
|
|||||||
<div class="map-page">
|
<div class="map-page">
|
||||||
<van-nav-bar title="地图" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
|
<van-nav-bar title="地图" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
|
||||||
|
|
||||||
<!-- MapLibre 地图容器占位 -->
|
<!-- MapLibre 地图容器 -->
|
||||||
<div id="ytmap-container" class="map-container">
|
<div id="ytmap-container" class="map-container">
|
||||||
<div class="map-placeholder">
|
<!-- 加载状态 -->
|
||||||
<van-icon name="map-marked" size="64" color="#ccc" />
|
<div v-if="loading && !error && !initError" class="map-overlay map-loading">
|
||||||
<p class="placeholder-title">智慧水务地图</p>
|
<van-loading type="spinner" size="32" color="var(--color-primary)" />
|
||||||
<p class="placeholder-hint">MapLibre 地图将在此处加载</p>
|
<p class="overlay-text">地图加载中…</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 初始化错误 -->
|
||||||
|
<div v-if="initError" class="map-overlay map-error">
|
||||||
|
<van-icon name="warning-o" size="48" color="#F44336" />
|
||||||
|
<p class="overlay-text">{{ initError }}</p>
|
||||||
|
<van-button size="small" type="primary" @click="router.back()">返回</van-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 运行时错误 -->
|
||||||
|
<div v-if="error" class="map-error-banner">
|
||||||
|
<van-icon name="warning-o" size="16" />
|
||||||
|
<span>{{ error }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,9 +166,15 @@ function handleToolClick(label: string) {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
:deep(.maplibregl-map) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-placeholder {
|
/* ── 加载 & 错误覆盖层 ── */
|
||||||
|
.map-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -95,20 +182,39 @@ function handleToolClick(label: string) {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #f0f3f7;
|
background: #f0f3f7;
|
||||||
|
z-index: 5;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.placeholder-title {
|
.overlay-text {
|
||||||
margin: 12px 0 4px 0;
|
font-size: 14px;
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-hint {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--color-text-placeholder);
|
color: var(--color-text-placeholder);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-error {
|
||||||
|
.overlay-text {
|
||||||
|
color: #F44336;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 运行时错误横幅 ── */
|
||||||
|
.map-error-banner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 6;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #fff3f3;
|
||||||
|
color: #F44336;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-tools {
|
.bottom-tools {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
|
|||||||
@@ -5,13 +5,25 @@
|
|||||||
* 在地图上展示监测设备位置和状态,
|
* 在地图上展示监测设备位置和状态,
|
||||||
* 可点击设备查看实时数据。
|
* 可点击设备查看实时数据。
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue'
|
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import maplibregl from 'maplibre-gl'
|
||||||
|
import { MapManager } from '@/map/MapManager'
|
||||||
|
import { MapFactory } from '@/map/MapFactory'
|
||||||
|
import { useMap } from '@/map/composables/useMap'
|
||||||
|
import { usePopup } from '@/map/composables/usePopup'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
/** 地图加载状态 */
|
// ── 地图状态 ──────────────────────────────────────────────
|
||||||
const mapLoaded = ref(false)
|
const { map, loading, error, isReady } = useMap()
|
||||||
|
const { showPopup, hidePopup } = usePopup()
|
||||||
|
|
||||||
|
/** 初始化错误 */
|
||||||
|
const initError = ref<string | null>(null)
|
||||||
|
|
||||||
|
/** 标记是否已添加设备标记 */
|
||||||
|
const markersAdded = ref(false)
|
||||||
|
|
||||||
/** 模拟设备点位 */
|
/** 模拟设备点位 */
|
||||||
const devicePoints = ref([
|
const devicePoints = ref([
|
||||||
@@ -27,7 +39,98 @@ const statusColorMap: Record<string, string> = {
|
|||||||
offline: '#999',
|
offline: '#999',
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 跳转设备详情 */
|
/** 存储动态创建的 Marker,用于清理 */
|
||||||
|
const markers: maplibregl.Marker[] = []
|
||||||
|
|
||||||
|
// ── 添加设备点位标记 ────────────────────────────────────
|
||||||
|
function addDeviceMarkers(m: maplibregl.Map): void {
|
||||||
|
if (markersAdded.value) return
|
||||||
|
markersAdded.value = true
|
||||||
|
|
||||||
|
devicePoints.value.forEach((point) => {
|
||||||
|
const color = statusColorMap[point.status] || '#999'
|
||||||
|
const el = document.createElement('div')
|
||||||
|
el.style.cssText = `
|
||||||
|
width: 14px; height: 14px; border-radius: 50%;
|
||||||
|
background: ${color}; border: 2px solid #fff;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.3); cursor: pointer;
|
||||||
|
`
|
||||||
|
const marker = new maplibregl.Marker({ element: el })
|
||||||
|
.setLngLat([point.lng, point.lat])
|
||||||
|
.addTo(m)
|
||||||
|
|
||||||
|
// 点击弹出设备信息
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
showPopup(
|
||||||
|
[point.lng, point.lat],
|
||||||
|
`<div class="device-popup">
|
||||||
|
<strong>${point.name}</strong><br/>
|
||||||
|
<span style="font-size:12px;color:#666">状态: ${point.status}</span><br/>
|
||||||
|
<span style="font-size:11px;color:#999">${point.lat}, ${point.lng}</span>
|
||||||
|
</div>`,
|
||||||
|
{ maxWidth: '220px', closeOnClick: true },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
markers.push(marker)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMarkers(): void {
|
||||||
|
markers.forEach((m) => m.remove())
|
||||||
|
markers.length = 0
|
||||||
|
markersAdded.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 初始化地图 ───────────────────────────────────────────
|
||||||
|
onMounted(() => {
|
||||||
|
const manager = MapManager.getInstance()
|
||||||
|
|
||||||
|
// 如果地图已存在,复用并添加标记
|
||||||
|
if (manager.hasMap()) {
|
||||||
|
const m = manager.getMap()
|
||||||
|
if (m) addDeviceMarkers(m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.getElementById('mapmonitor-container')
|
||||||
|
if (!container) {
|
||||||
|
initError.value = '地图容器 #mapmonitor-container 未找到'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MapFactory.createWithBasemap('osm', {
|
||||||
|
container: 'mapmonitor-container',
|
||||||
|
zoom: 10,
|
||||||
|
center: [104.0, 30.5],
|
||||||
|
onLoad: (m) => {
|
||||||
|
console.log('[MapMonitoring] 地图已加载')
|
||||||
|
addDeviceMarkers(m)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const msg = err instanceof Error ? err.message : '地图初始化失败'
|
||||||
|
initError.value = msg
|
||||||
|
console.error('[MapMonitoring] 地图创建失败:', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── 监听 isReady,加载标记 ──────────────────────────────
|
||||||
|
watch(isReady, (ready) => {
|
||||||
|
if (ready && map.value && !markersAdded.value) {
|
||||||
|
addDeviceMarkers(map.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── 组件卸载 ─────────────────────────────────────────────
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearMarkers()
|
||||||
|
hidePopup()
|
||||||
|
MapManager.getInstance().destroyMap()
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── 跳转设备详情 ─────────────────────────────────────────
|
||||||
function goEquipmentInfo(id: number) {
|
function goEquipmentInfo(id: number) {
|
||||||
router.push(`/equipmentInfo?id=${id}`)
|
router.push(`/equipmentInfo?id=${id}`)
|
||||||
}
|
}
|
||||||
@@ -37,11 +140,25 @@ function goEquipmentInfo(id: number) {
|
|||||||
<div class="map-monitoring-page">
|
<div class="map-monitoring-page">
|
||||||
<van-nav-bar title="地图监控" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
|
<van-nav-bar title="地图监控" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
|
||||||
|
|
||||||
<!-- 地图占位区 -->
|
<!-- MapLibre 地图容器 -->
|
||||||
<div class="map-placeholder">
|
<div id="mapmonitor-container" class="map-area">
|
||||||
<van-icon name="location-o" size="48" color="#ccc" />
|
<!-- 加载状态 -->
|
||||||
<p class="map-title">地图监控</p>
|
<div v-if="!map && loading && !initError" class="map-status map-loading">
|
||||||
<p class="map-hint">设备点位将在此地图上展示</p>
|
<van-loading type="spinner" size="28" color="var(--color-primary)" />
|
||||||
|
<p>地图加载中…</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 初始化错误 -->
|
||||||
|
<div v-if="initError" class="map-status map-error-state">
|
||||||
|
<van-icon name="warning-o" size="40" color="#F44336" />
|
||||||
|
<p>{{ initError }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 运行时错误 -->
|
||||||
|
<div v-if="error" class="map-error-banner">
|
||||||
|
<van-icon name="warning-o" size="14" />
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 设备点位列表面板(底部浮动) -->
|
<!-- 设备点位列表面板(底部浮动) -->
|
||||||
@@ -82,31 +199,60 @@ function goEquipmentInfo(id: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-placeholder {
|
.map-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
margin: 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 300px;
|
||||||
|
|
||||||
|
:deep(.maplibregl-map) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-status {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #f5f7fa;
|
background: #f5f7fa;
|
||||||
margin: 8px;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px dashed #ddd;
|
z-index: 5;
|
||||||
min-height: 300px;
|
gap: 8px;
|
||||||
|
|
||||||
.map-title {
|
p {
|
||||||
margin: 12px 0 4px 0;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-hint {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--color-text-placeholder);
|
color: var(--color-text-placeholder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-error-state {
|
||||||
|
p {
|
||||||
|
color: #F44336;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-error-banner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 6;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: #fff3f3;
|
||||||
|
color: #F44336;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.device-panel {
|
.device-panel {
|
||||||
background: var(--color-bg-card);
|
background: var(--color-bg-card);
|
||||||
margin: 0 8px 8px;
|
margin: 0 8px 8px;
|
||||||
|
|||||||
Reference in New Issue
Block a user