| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674 |
- <!--状态监测报警-->
- <!--监测报警一张图-->
- <template>
- <div class="alert-dashboard">
- <!-- 头部统计告警栏 -->
- <div class="alert-header">
- <div class="stats-cards">
- <div class="stat-item" :class="{ active: filterType === 'all' }" @click="filterAlerts('all')">
- <div class="stat-num">{{ totalAlerts }}</div>
- <div class="stat-label">全部报警</div>
- </div>
- <div class="stat-item critical" :class="{ active: filterType === 'critical' }" @click="filterAlerts('critical')">
- <div class="stat-num">{{ criticalCount }}</div>
- <div class="stat-label">严重报警</div>
- </div>
- <div class="stat-item warning" :class="{ active: filterType === 'warning' }" @click="filterAlerts('warning')">
- <div class="stat-num">{{ warningCount }}</div>
- <div class="stat-label">一般报警</div>
- </div>
- </div>
- </div>
- <!-- 主内容区域:左侧报警列表 + 右侧详情研判 -->
- <div class="main-content">
- <!-- 左侧报警列表 -->
- <div class="alert-list-panel" v-loading="listLoading">
- <div class="panel-header">
- <span>报警事件列表 <el-badge :value="unhandledCount" class="badge" /></span>
- <el-input v-model="searchText" placeholder="搜索位置/设备编号" prefix-icon="Search" clearable size="small" style="width: 180px" />
- </div>
- <div class="alert-list">
- <div
- v-for="alert in filteredAlerts"
- :key="alert.id"
- class="alert-item"
- :class="{
- selected: selectedAlert?.id === alert.id,
- critical: alert.level === 'critical',
- warning: alert.level === 'warning'
- }"
- @click="selectAlert(alert)"
- >
- <div class="alert-level-icon">
- <el-icon v-if="alert.level === 'critical'"><WarningFilled /></el-icon>
- <el-icon v-else-if="alert.level === 'warning'"><InfoFilled /></el-icon>
- <el-icon v-else><CircleCheck /></el-icon>
- </div>
- <div class="alert-content">
- <div class="alert-title">
- <span>{{ alert.deviceName }}</span>
- <span class="alert-time">{{ alert.time }}</span>
- </div>
- <div class="alert-location">
- <el-icon><Location /></el-icon> {{ alert.location }}
- </div>
- <div class="alert-type">{{ alert.alertType }}</div>
- <div class="alert-value" v-if="alert.exceedValue">实时值: <strong class="exceed">{{ alert.exceedValue }}</strong> (阈值: {{ alert.threshold }})</div>
- </div>
- <div class="alert-status" v-if="alert.status === 'resolved'">
- <el-tag type="success" size="small">已处置</el-tag>
- </div>
- <div class="alert-status" v-else-if="alert.status === 'processing'">
- <el-tag type="warning" size="small">处理中</el-tag>
- </div>
- <div class="alert-status" v-else>
- <el-tag type="danger" size="small">待处理</el-tag>
- </div>
- </div>
- <el-empty v-if="!filteredAlerts.length && !listLoading" description="暂无报警事件" :image-size="100" />
- </div>
- </div>
- <!-- 右侧报警详情与研判面板 -->
- <div class="detail-panel" v-if="selectedAlert" v-loading="detailLoading">
- <!-- 报警基本信息 -->
- <div class="detail-section">
- <div class="section-title">
- <span><el-icon><Warning /></el-icon> 报警详情 - 辅助研判</span>
- </div>
- <el-descriptions :column="2" border>
- <el-descriptions-item label="报警位置">
- <strong>{{ selectedAlert.location }}</strong> <el-button link type="primary" size="small" @click="viewOnMap">查看地图</el-button>
- </el-descriptions-item>
- <el-descriptions-item label="报警级别">
- <el-tag :type="selectedAlert.level === 'critical' ? 'danger' : 'warning'" size="default">
- {{ selectedAlert.level === 'critical' ? '严重报警' : '一般报警' }}
- </el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="设备编号">{{ selectedAlert.deviceCode || '—' }}</el-descriptions-item>
- <el-descriptions-item label="设备类型">{{ selectedAlert.deviceType }}</el-descriptions-item>
- <el-descriptions-item label="报警类型">{{ selectedAlert.alertType }}</el-descriptions-item>
- <el-descriptions-item label="报警时间">{{ selectedAlert.time }}</el-descriptions-item>
- <el-descriptions-item label="实时数据" :span="2">
- <div class="real-time-data">
- <span v-if="selectedAlert.realData">
- 当前 {{ selectedAlert.alertParam }} = <strong class="warning-value">{{ selectedAlert.realData }}</strong>
- (阈值: {{ selectedAlert.threshold }})
- </span>
- <span v-else>{{ selectedAlert.exceedValue || '超出阈值' }}</span>
- </div>
- </el-descriptions-item>
- </el-descriptions>
- </div>
- </div>
- <!-- 未选中报警时的空状态 -->
- <div v-else class="empty-detail">
- <el-empty description="请从左侧选择一个报警事件进行研判" :image-size="140" />
- </div>
- </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, 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'
- const alerts = ref([])
- const searchText = ref('')
- const filterType = ref('all')
- 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', ' ')
- }
- function getThresholdTextByCode(raw, warningCode) {
- const list = Array.isArray(raw?.warningThresholdList) ? raw.warningThresholdList : []
- const target = list.find(item => item.warningCode === warningCode)
- if (!target) return '—'
- const { minValue, maxValue } = target
- if (minValue !== null && minValue !== undefined && maxValue !== null && maxValue !== undefined) {
- return `${minValue} ~ ${maxValue}`
- }
- if (minValue !== null && minValue !== undefined) return `<= ${minValue}`
- if (maxValue !== null && maxValue !== undefined) return `>= ${maxValue}`
- return '—'
- }
- function buildAlertInfo(raw) {
- const status = raw?.equipmentStatus || {}
- const manholeData = raw?.manholeData || {}
- const battery = toNumber(manholeData.batteryLevel)
- const signal = toNumber(manholeData.signalStrength)
- const tiltAngle = toNumber(manholeData.tiltAngle)
- if (`${manholeData.waterLevelAlarmStatus}` === '1') {
- return {
- hasAlert: true,
- level: 'critical',
- levelText: '严重报警',
- alertType: '水位报警',
- alertParam: '水位',
- realData: manholeData.waterLevel !== undefined && manholeData.waterLevel !== null ? `${manholeData.waterLevel}cm` : '报警',
- threshold: getThresholdTextByCode(raw, 'WARN-WATER-LEVEL')
- }
- }
- if (`${manholeData.waterInfiltrationAlarmStatus}` === '1') {
- return {
- hasAlert: true,
- level: 'critical',
- levelText: '严重报警',
- alertType: '水浸报警',
- alertParam: '水浸状态',
- realData: '报警',
- threshold: getThresholdTextByCode(raw, 'WARN-WATER-INFILTRATION')
- }
- }
- if (Number(status.alarmStatus) === 1) {
- return {
- hasAlert: true,
- level: 'critical',
- levelText: '严重报警',
- alertType: tiltAngle !== null ? '倾斜报警' : '设备报警',
- alertParam: tiltAngle !== null ? '倾斜角' : '设备状态',
- realData: tiltAngle !== null ? `${tiltAngle}°` : '报警',
- threshold: tiltAngle !== null ? getThresholdTextByCode(raw, 'WARN-TILT') : '—'
- }
- }
- if (battery !== null && battery <= 20) {
- return {
- hasAlert: true,
- level: 'warning',
- levelText: '一般报警',
- alertType: '低电量预警',
- alertParam: '电池电量',
- realData: `${battery}%`,
- threshold: '20%'
- }
- }
- if (signal !== null && signal <= 20) {
- return {
- hasAlert: true,
- level: 'warning',
- levelText: '一般报警',
- alertType: '弱信号预警',
- alertParam: '信号强度',
- realData: `${signal}%`,
- threshold: '20%'
- }
- }
- return {
- hasAlert: false,
- level: '',
- levelText: '',
- alertType: '',
- alertParam: '',
- realData: '',
- threshold: '—'
- }
- }
- function normalizeAlert(raw, baseAlert = null) {
- const alertInfo = buildAlertInfo(raw)
- if (!alertInfo.hasAlert) return null
- return {
- id: raw?.equipmentId || raw?.equipmentCode,
- equipmentId: raw?.equipmentId || '',
- deviceCode: raw?.equipmentCode || '',
- deviceName: raw?.equipmentName || raw?.equipmentCode || '未命名设备',
- deviceType: raw?.equipmentTypeName || '井盖监测设备',
- location: raw?.equipmentLocation || '暂无位置信息',
- lng: raw?.longitude || baseAlert?.lng || '',
- lat: raw?.latitude || baseAlert?.lat || '',
- alertType: alertInfo.alertType,
- alertParam: alertInfo.alertParam,
- realData: alertInfo.realData,
- threshold: alertInfo.threshold,
- exceedValue: alertInfo.realData || '报警',
- level: alertInfo.level,
- levelText: alertInfo.levelText,
- time: formatDateTime(raw?.equipmentStatus?.statusUpdateTime || raw?.updateTime || raw?.installTime),
- status: raw?.equipmentStatus?.alarmStatus === 1 ? 'pending' : 'resolved',
- statusText: raw?.equipmentStatus?.alarmStatus === 1 ? '待处理' : '已恢复',
- raw
- }
- }
- async function loadAlerts() {
- listLoading.value = true
- try {
- const res = await getManholeAlarmDeviceList({
- alarmStatus: 1,
- isQueryManholeData: true
- })
- const list = Array.isArray(res?.data) ? res.data : []
- alerts.value = list
- .map(item => normalizeAlert(item))
- .filter(Boolean)
- if (!alerts.value.length) {
- selectedAlert.value = null
- return
- }
- const currentId = selectedAlert.value?.id
- selectedAlert.value = alerts.value.find(item => item.id === currentId) || alerts.value[0]
- if (selectedAlert.value?.equipmentId) {
- await loadAlertDetail(selectedAlert.value)
- }
- } catch (error) {
- alerts.value = []
- selectedAlert.value = null
- ElMessage.error('报警设备加载失败')
- } finally {
- listLoading.value = false
- }
- }
- async function loadAlertDetail(alert) {
- if (!alert?.equipmentId) return
- detailLoading.value = true
- try {
- const res = await getManholeAlarmDeviceDetail(alert.equipmentId)
- if (res?.data) {
- const merged = normalizeAlert(res.data, alert)
- if (merged) {
- const idx = alerts.value.findIndex(item => item.id === merged.id)
- if (idx > -1) {
- alerts.value[idx] = merged
- }
- selectedAlert.value = merged
- }
- }
- } catch (error) {
- ElMessage.error('报警详情加载失败')
- } finally {
- detailLoading.value = false
- }
- }
- const filteredAlerts = computed(() => {
- let list = alerts.value
- if (filterType.value === 'critical') {
- list = list.filter(a => a.level === 'critical')
- } else if (filterType.value === 'warning') {
- list = list.filter(a => a.level === 'warning')
- }
- if (searchText.value) {
- const kw = searchText.value.toLowerCase()
- list = list.filter(a =>
- `${a.location || ''}`.toLowerCase().includes(kw) ||
- `${a.deviceCode || ''}`.toLowerCase().includes(kw) ||
- `${a.deviceName || ''}`.toLowerCase().includes(kw)
- )
- }
- return list
- })
- 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
- }
- const selectAlert = async (alert) => {
- selectedAlert.value = alert
- await loadAlertDetail(alert)
- }
- 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>
- .alert-dashboard {
- background: #f0f2f6;
- min-height: 100vh;
- padding: 20px;
- }
- .alert-header {
- background: white;
- border-radius: 20px;
- padding: 16px 24px;
- margin-bottom: 20px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.04);
- }
- .title-badge {
- display: flex;
- align-items: center;
- gap: 16px;
- margin-bottom: 16px;
- }
- .blink-icon {
- font-size: 28px;
- color: #f56c6c;
- animation: blink 1s infinite;
- }
- @keyframes blink { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }
- .title { font-size: 22px; font-weight: 700; }
- .stats-cards { display: flex; gap: 20px; margin-top: 6px; }
- .stat-item { background: #f8fafc; padding: 10px 24px; border-radius: 16px; cursor: pointer; text-align: center; transition: all 0.2s; min-width: 100px; }
- .stat-item.active { background: #ecf5ff; border: 1px solid #409eff; }
- .stat-num { font-size: 28px; font-weight: bold; }
- .stat-item.critical .stat-num { color: #f56c6c; }
- .stat-item.warning .stat-num { color: #e6a23c; }
- .main-content { display: flex; gap: 20px; }
- .alert-list-panel {
- width: 400px;
- background: white;
- border-radius: 20px;
- padding: 16px;
- display: flex;
- flex-direction: column;
- }
- .panel-header { display: flex; justify-content: space-between; margin-bottom: 16px; font-weight: 600; }
- .alert-list { max-height: calc(100vh - 200px); overflow-y: auto; }
- .alert-item {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- padding: 14px;
- margin-bottom: 12px;
- border-radius: 14px;
- background: #fafbfc;
- cursor: pointer;
- border: 1px solid #e9ecef;
- transition: all 0.2s;
- }
- .alert-item.selected { background: #ecf5ff; border-color: #409eff; }
- .alert-item.critical { border-left: 4px solid #f56c6c; }
- .alert-item.warning { border-left: 4px solid #e6a23c; }
- .alert-level-icon { font-size: 22px; margin-top: 4px; }
- .alert-content { flex: 1; }
- .alert-title { display: flex; justify-content: space-between; font-weight: 600; margin-bottom: 4px; }
- .alert-time { font-size: 11px; color: #909399; }
- .alert-location { font-size: 12px; color: #606266; display: flex; align-items: center; gap: 4px; margin: 4px 0; }
- .alert-type { font-size: 12px; color: #409eff; }
- .alert-value { font-size: 12px; margin-top: 6px; }
- .exceed { color: #f56c6c; }
- .detail-panel {
- flex: 1;
- background: white;
- border-radius: 20px;
- padding: 20px;
- overflow-y: auto;
- max-height: calc(100vh - 150px);
- }
- .detail-section {
- margin-bottom: 24px;
- border-bottom: 1px solid #f0f0f0;
- padding-bottom: 20px;
- }
- .section-title {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16px;
- font-weight: 600;
- font-size: 16px;
- }
- .real-time-data { background: #fff9f0; padding: 8px; border-radius: 8px; }
- .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>
|