|
|
@@ -111,19 +111,24 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 地图弹窗 -->
|
|
|
- <el-dialog v-model="mapDialogVisible" title="报警位置地图" width="600px">
|
|
|
- <div id="simpleMap" style="height: 400px; background: #e9f5e9; border-radius: 12px; display: flex; align-items: center; justify-content: center; flex-direction: column;">
|
|
|
- <el-icon :size="48" color="#f56c6c"><Location /></el-icon>
|
|
|
- <div><strong>{{ selectedAlert?.location }}</strong></div>
|
|
|
- <div>经度: {{ selectedAlert?.lng || '暂无数据' }}, 纬度: {{ selectedAlert?.lat || '暂无数据' }}</div>
|
|
|
- <div style="margin-top: 20px; font-size: 12px; color: gray">当前仅展示接口返回的真实坐标信息</div>
|
|
|
+ <el-dialog v-model="mapDialogVisible" title="报警位置地图" width="720px" @opened="handleMapDialogOpened">
|
|
|
+ <div class="dialog-map-wrapper">
|
|
|
+ <div ref="dialogMapRef" class="dialog-baidu-map"></div>
|
|
|
+ <div v-if="dialogMapError" class="dialog-map-error">{{ dialogMapError }}</div>
|
|
|
+ <div v-if="!hasAlertCoordinate" class="dialog-map-empty">
|
|
|
+ 当前报警缺少真实坐标,已展示默认地图视野
|
|
|
+ </div>
|
|
|
+ <div class="dialog-map-footer">
|
|
|
+ <div><strong>{{ selectedAlert?.location || '暂无位置' }}</strong></div>
|
|
|
+ <div>经度: {{ selectedAlert?.lng || '暂无数据' }}, 纬度: {{ selectedAlert?.lat || '暂无数据' }}</div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import {computed, onMounted, ref} from 'vue'
|
|
|
+import {computed, nextTick, onMounted, onUnmounted, ref, watch} from 'vue'
|
|
|
import {CircleCheck, InfoFilled, Location, Warning, WarningFilled} from '@element-plus/icons-vue'
|
|
|
import {ElMessage} from 'element-plus'
|
|
|
import {getManholeAlarmDeviceDetail, getManholeAlarmDeviceList} from '@/api/pipeNetwork/basic'
|
|
|
@@ -135,12 +140,25 @@ const selectedAlert = ref(null)
|
|
|
const mapDialogVisible = ref(false)
|
|
|
const listLoading = ref(false)
|
|
|
const detailLoading = ref(false)
|
|
|
+const dialogMapRef = ref(null)
|
|
|
+const dialogMapError = ref('')
|
|
|
+
|
|
|
+const BAIDU_MAP_AK = import.meta.env.VITE_BAIDU_MAP_AK || ''
|
|
|
+const DEFAULT_MAP_CENTER = { lng: 110.393, lat: 28.452 }
|
|
|
+
|
|
|
+let dialogMapInstance = null
|
|
|
+let dialogMapOverlays = []
|
|
|
+let baiduMapScriptPromise = null
|
|
|
|
|
|
function toNumber(value) {
|
|
|
const num = Number(value)
|
|
|
return Number.isFinite(num) ? num : null
|
|
|
}
|
|
|
|
|
|
+function isValidCoordinate(lng, lat) {
|
|
|
+ return lng !== null && lat !== null && lng >= 70 && lng <= 140 && lat >= 3 && lat <= 55
|
|
|
+}
|
|
|
+
|
|
|
function formatDateTime(value) {
|
|
|
if (!value) return '—'
|
|
|
return String(value).replace('T', ' ')
|
|
|
@@ -337,6 +355,7 @@ const totalAlerts = computed(() => alerts.value.length)
|
|
|
const criticalCount = computed(() => alerts.value.filter(a => a.level === 'critical').length)
|
|
|
const warningCount = computed(() => alerts.value.filter(a => a.level === 'warning').length)
|
|
|
const unhandledCount = computed(() => alerts.value.length)
|
|
|
+const hasAlertCoordinate = computed(() => isValidCoordinate(toNumber(selectedAlert.value?.lng), toNumber(selectedAlert.value?.lat)))
|
|
|
|
|
|
const filterAlerts = (type) => {
|
|
|
filterType.value = type
|
|
|
@@ -351,9 +370,173 @@ const viewOnMap = () => {
|
|
|
mapDialogVisible.value = true
|
|
|
}
|
|
|
|
|
|
+function clearDialogMapMarker() {
|
|
|
+ if (!dialogMapInstance || !dialogMapOverlays.length) return
|
|
|
+ dialogMapOverlays.forEach((overlay) => {
|
|
|
+ try {
|
|
|
+ dialogMapInstance.removeOverlay(overlay)
|
|
|
+ } catch (_) {}
|
|
|
+ })
|
|
|
+ dialogMapOverlays = []
|
|
|
+}
|
|
|
+
|
|
|
+function getDialogInfoWindowHtml() {
|
|
|
+ const levelTagColor = selectedAlert.value?.level === 'critical' ? '#f56c6c' : '#e6a23c'
|
|
|
+ const levelText = selectedAlert.value?.levelText || '报警'
|
|
|
+ return `
|
|
|
+ <div style="min-width:220px;max-width:280px;padding:4px 2px;line-height:1.7;color:#303133;">
|
|
|
+ <div style="font-size:14px;font-weight:700;color:#1f2937;margin-bottom:6px;">
|
|
|
+ ${selectedAlert.value?.deviceName || '报警设备'}
|
|
|
+ </div>
|
|
|
+ <div style="margin-bottom:4px;">
|
|
|
+ <span style="display:inline-block;padding:1px 8px;border-radius:999px;background:${levelTagColor};color:#fff;font-size:12px;">
|
|
|
+ ${levelText}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div style="font-size:12px;"><span style="color:#909399;">报警类型:</span>${selectedAlert.value?.alertType || '—'}</div>
|
|
|
+ <div style="font-size:12px;"><span style="color:#909399;">报警时间:</span>${selectedAlert.value?.time || '—'}</div>
|
|
|
+ <div style="font-size:12px;"><span style="color:#909399;">实时数据:</span>${selectedAlert.value?.realData || selectedAlert.value?.exceedValue || '—'}</div>
|
|
|
+ <div style="font-size:12px;"><span style="color:#909399;">阈值:</span>${selectedAlert.value?.threshold || '—'}</div>
|
|
|
+ <div style="font-size:12px;"><span style="color:#909399;">报警位置:</span>${selectedAlert.value?.location || '—'}</div>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
+}
|
|
|
+
|
|
|
+function ensureBaiduMap() {
|
|
|
+ if (window.BMapGL) {
|
|
|
+ return Promise.resolve(window.BMapGL)
|
|
|
+ }
|
|
|
+ if (baiduMapScriptPromise) {
|
|
|
+ return baiduMapScriptPromise
|
|
|
+ }
|
|
|
+ if (!BAIDU_MAP_AK) {
|
|
|
+ return Promise.reject(new Error('未配置百度地图 AK,请在 .env.development 中配置 VITE_BAIDU_MAP_AK'))
|
|
|
+ }
|
|
|
+
|
|
|
+ baiduMapScriptPromise = new Promise((resolve, reject) => {
|
|
|
+ const existing = document.querySelector('script[data-baidu-map="webgl"]')
|
|
|
+ if (existing) {
|
|
|
+ let attempts = 0
|
|
|
+ const timer = window.setInterval(() => {
|
|
|
+ attempts++
|
|
|
+ if (window.BMapGL) {
|
|
|
+ window.clearInterval(timer)
|
|
|
+ resolve(window.BMapGL)
|
|
|
+ } else if (attempts >= 50) {
|
|
|
+ window.clearInterval(timer)
|
|
|
+ reject(new Error('百度地图脚本加载超时'))
|
|
|
+ }
|
|
|
+ }, 200)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const script = document.createElement('script')
|
|
|
+ script.src = `https://api.map.baidu.com/api?type=webgl&v=1.0&ak=${BAIDU_MAP_AK}`
|
|
|
+ script.async = true
|
|
|
+ script.defer = true
|
|
|
+ script.setAttribute('data-baidu-map', 'webgl')
|
|
|
+ script.onload = () => {
|
|
|
+ let attempts = 0
|
|
|
+ const timer = window.setInterval(() => {
|
|
|
+ attempts++
|
|
|
+ if (window.BMapGL) {
|
|
|
+ window.clearInterval(timer)
|
|
|
+ resolve(window.BMapGL)
|
|
|
+ } else if (attempts >= 50) {
|
|
|
+ window.clearInterval(timer)
|
|
|
+ reject(new Error('百度地图脚本初始化超时'))
|
|
|
+ }
|
|
|
+ }, 200)
|
|
|
+ }
|
|
|
+ script.onerror = () => reject(new Error('百度地图脚本加载失败'))
|
|
|
+ document.head.appendChild(script)
|
|
|
+ })
|
|
|
+
|
|
|
+ return baiduMapScriptPromise
|
|
|
+}
|
|
|
+
|
|
|
+function renderDialogMapMarker() {
|
|
|
+ if (!dialogMapInstance || !window.BMapGL) return
|
|
|
+ clearDialogMapMarker()
|
|
|
+
|
|
|
+ const lng = toNumber(selectedAlert.value?.lng)
|
|
|
+ const lat = toNumber(selectedAlert.value?.lat)
|
|
|
+ if (!isValidCoordinate(lng, lat)) {
|
|
|
+ dialogMapInstance.centerAndZoom(new window.BMapGL.Point(DEFAULT_MAP_CENTER.lng, DEFAULT_MAP_CENTER.lat), 13)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const point = new window.BMapGL.Point(lng, lat)
|
|
|
+ dialogMapInstance.centerAndZoom(point, 15)
|
|
|
+ const marker = new window.BMapGL.Marker(point)
|
|
|
+ const infoWindow = new window.BMapGL.InfoWindow(getDialogInfoWindowHtml(), {
|
|
|
+ width: 260,
|
|
|
+ title: '报警信息'
|
|
|
+ })
|
|
|
+ dialogMapInstance.addOverlay(marker)
|
|
|
+ dialogMapOverlays.push(marker)
|
|
|
+ marker.addEventListener('click', () => {
|
|
|
+ dialogMapInstance.openInfoWindow(infoWindow, point)
|
|
|
+ })
|
|
|
+
|
|
|
+ const label = new window.BMapGL.Label(
|
|
|
+ `<div style="padding:4px 8px;border-radius:999px;background:rgba(20,33,61,0.82);color:#fff;font-size:12px;line-height:1.2;white-space:nowrap;">${selectedAlert.value?.deviceName || '报警设备'}</div>`,
|
|
|
+ {
|
|
|
+ position: point,
|
|
|
+ offset: new window.BMapGL.Size(-30, -40)
|
|
|
+ }
|
|
|
+ )
|
|
|
+ label.setStyle({
|
|
|
+ border: 'none',
|
|
|
+ background: 'transparent',
|
|
|
+ padding: '0'
|
|
|
+ })
|
|
|
+ label.addEventListener('click', () => {
|
|
|
+ dialogMapInstance.openInfoWindow(infoWindow, point)
|
|
|
+ })
|
|
|
+ dialogMapInstance.addOverlay(label)
|
|
|
+ dialogMapOverlays.push(label)
|
|
|
+ dialogMapInstance.openInfoWindow(infoWindow, point)
|
|
|
+}
|
|
|
+
|
|
|
+async function initDialogMap() {
|
|
|
+ if (!dialogMapRef.value) return
|
|
|
+ try {
|
|
|
+ await ensureBaiduMap()
|
|
|
+ if (!dialogMapInstance) {
|
|
|
+ dialogMapInstance = new window.BMapGL.Map(dialogMapRef.value, { enableMapClick: true })
|
|
|
+ dialogMapInstance.enableScrollWheelZoom(true)
|
|
|
+ dialogMapInstance.centerAndZoom(new window.BMapGL.Point(DEFAULT_MAP_CENTER.lng, DEFAULT_MAP_CENTER.lat), 13)
|
|
|
+ }
|
|
|
+ dialogMapError.value = ''
|
|
|
+ renderDialogMapMarker()
|
|
|
+ } catch (error) {
|
|
|
+ dialogMapError.value = error?.message || '百度地图加载失败'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function handleMapDialogOpened() {
|
|
|
+ await nextTick()
|
|
|
+ await initDialogMap()
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => selectedAlert.value?.id,
|
|
|
+ async () => {
|
|
|
+ if (!mapDialogVisible.value) return
|
|
|
+ await nextTick()
|
|
|
+ renderDialogMapMarker()
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
onMounted(async () => {
|
|
|
await loadAlerts()
|
|
|
})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ clearDialogMapMarker()
|
|
|
+ dialogMapInstance = null
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
@@ -447,4 +630,45 @@ onMounted(async () => {
|
|
|
.warning-value { color: #f56c6c; font-size: 18px; margin: 0 4px; }
|
|
|
.action-buttons { display: flex; gap: 8px; }
|
|
|
.empty-detail { flex: 1; display: flex; align-items: center; justify-content: center; background: white; border-radius: 20px; }
|
|
|
+.dialog-map-wrapper {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+.dialog-baidu-map {
|
|
|
+ height: 420px;
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #eef2f0;
|
|
|
+}
|
|
|
+.dialog-map-footer {
|
|
|
+ padding-top: 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+ line-height: 1.8;
|
|
|
+}
|
|
|
+.dialog-map-empty {
|
|
|
+ position: absolute;
|
|
|
+ top: 16px;
|
|
|
+ left: 16px;
|
|
|
+ background: rgba(255, 255, 255, 0.94);
|
|
|
+ color: #909399;
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ border-radius: 10px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
|
+ z-index: 2;
|
|
|
+}
|
|
|
+.dialog-map-error {
|
|
|
+ position: absolute;
|
|
|
+ top: 16px;
|
|
|
+ right: 16px;
|
|
|
+ max-width: 280px;
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ color: #f56c6c;
|
|
|
+ border: 1px solid rgba(245, 108, 108, 0.25);
|
|
|
+ border-radius: 10px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.6;
|
|
|
+ z-index: 2;
|
|
|
+}
|
|
|
</style>
|