|
|
@@ -16,28 +16,6 @@
|
|
|
<div class="metric"><span>平均响应时间</span><strong>{{ stats.avgResponseMinutes ??
|
|
|
stats.averageResponseMinutes ?? '-' }}</strong><small>分钟</small></div>
|
|
|
</div>
|
|
|
- <div v-if="mode === 'map'" class="metric-grid">
|
|
|
- <div class="metric">
|
|
|
- <el-icon class="metric-icon">
|
|
|
- <Bell/>
|
|
|
- </el-icon>
|
|
|
- <span>报警总数</span><strong>{{ mapAlarmCount }}</strong><small>条</small></div>
|
|
|
- <div class="metric">
|
|
|
- <el-icon class="metric-icon">
|
|
|
- <Warning/>
|
|
|
- </el-icon>
|
|
|
- <span>高等级报警</span><strong class="danger">{{ mapCriticalCount }}</strong><small>条</small></div>
|
|
|
- <div class="metric">
|
|
|
- <el-icon class="metric-icon">
|
|
|
- <CircleCheck/>
|
|
|
- </el-icon>
|
|
|
- <span>已处理报警</span><strong>{{ mapResolvedCount }}</strong><small>条</small></div>
|
|
|
- <div class="metric">
|
|
|
- <el-icon class="metric-icon">
|
|
|
- <Location/>
|
|
|
- </el-icon>
|
|
|
- <span>地图点位</span><strong>{{ mappedCount }}</strong><small>个</small></div>
|
|
|
- </div>
|
|
|
<el-card v-if="mode === 'manage'" shadow="never" class="realtime-card">
|
|
|
<div class="card-title"><span>实时异常卡片</span><span class="muted">设备、位置与阈值</span></div>
|
|
|
<div v-if="!realtime.length" class="empty-inline">当前无未处理报警</div>
|
|
|
@@ -117,61 +95,102 @@
|
|
|
layout="total, sizes, prev, pager, next" :page-sizes="[10,20,50]" @change="loadData"/>
|
|
|
</el-card>
|
|
|
<el-card v-else shadow="never" class="water-panel map-card">
|
|
|
- <div class="card-title"><span><el-icon><MapLocation/></el-icon> 监测报警一张图</span><span class="muted">按监测点状态着色</span>
|
|
|
- </div>
|
|
|
- <div class="map-workspace">
|
|
|
- <GisMapCanvas
|
|
|
- ref="gisMapRef"
|
|
|
- :points="gisMapPoints"
|
|
|
- :center="{ lng: 110.3965, lat: 28.4638 }"
|
|
|
- :zoom="15"
|
|
|
- @point-click="openDetail"
|
|
|
- @map-click="detailVisible = false"
|
|
|
- @error="mapError = $event"
|
|
|
- />
|
|
|
- <el-alert v-if="mapError" type="warning" show-icon :closable="false" :title="mapError" />
|
|
|
- <el-empty v-if="!mapPoints.length" description="暂无报警点位"/>
|
|
|
- <aside class="map-side-panel"><h3>报警图层与图例</h3>
|
|
|
- <div class="map-legend">
|
|
|
- <div class="map-legend-item"><i class="map-legend-dot status-dot-normal"/>正常</div>
|
|
|
- <div class="map-legend-item"><i class="map-legend-dot status-dot-alarm"/>报警</div>
|
|
|
- <div class="map-legend-item"><i class="map-legend-dot status-dot-warning"/>预警</div>
|
|
|
- <div class="map-legend-item"><i class="map-legend-dot status-dot-work-order"/>关联工单</div>
|
|
|
- <div class="map-legend-item"><i class="map-legend-dot status-dot-offline"/>离线</div>
|
|
|
+ <WaterGisPageShell
|
|
|
+ embedded
|
|
|
+ eyebrow="WATER SUPPLY ALARM GIS"
|
|
|
+ title="监测报警一张图"
|
|
|
+ subtitle="按监测点状态着色"
|
|
|
+ :loading="loading"
|
|
|
+ >
|
|
|
+ <template #actions>
|
|
|
+ <el-input v-model="alarmSearch" class="search-input" placeholder="搜索设备/报警类型" clearable
|
|
|
+ :prefix-icon="Search"/>
|
|
|
+ <el-button :icon="Refresh" @click="loadData">刷新</el-button>
|
|
|
+ </template>
|
|
|
+ <template #metrics>
|
|
|
+ <div class="metric">
|
|
|
+ <el-icon class="metric-icon"><Bell/></el-icon>
|
|
|
+ <span>报警总数</span><strong>{{ mapAlarmCount }}</strong><small>条</small>
|
|
|
+ </div>
|
|
|
+ <div class="metric">
|
|
|
+ <el-icon class="metric-icon"><Warning/></el-icon>
|
|
|
+ <span>高等级报警</span><strong class="danger">{{ mapCriticalCount }}</strong><small>条</small>
|
|
|
+ </div>
|
|
|
+ <div class="metric">
|
|
|
+ <el-icon class="metric-icon"><CircleCheck/></el-icon>
|
|
|
+ <span>已处理报警</span><strong>{{ mapResolvedCount }}</strong><small>条</small>
|
|
|
</div>
|
|
|
- <div class="alarm-detail"><h3>选中报警</h3>
|
|
|
+ <div class="metric">
|
|
|
+ <el-icon class="metric-icon"><Location/></el-icon>
|
|
|
+ <span>地图点位</span><strong>{{ mappedCount }}</strong><small>个</small>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #controls>
|
|
|
+ <div class="panel-section alarm-detail">
|
|
|
+ <h3>选中报警</h3>
|
|
|
<template v-if="selectedPoint.id || selectedPoint.alarmId">
|
|
|
- <div class="detail-name">{{ selectedPoint.deviceCode || selectedPoint.name || '报警点' }}
|
|
|
- </div>
|
|
|
+ <div class="detail-name">{{ selectedPoint.deviceCode || selectedPoint.name || '报警点' }}</div>
|
|
|
<el-descriptions :column="1" border size="small">
|
|
|
<el-descriptions-item label="报警等级">
|
|
|
- <el-tag :type="levelType(selectedPoint.alarmLevel)">{{ selectedPoint.alarmLevelText
|
|
|
- || levelText(selectedPoint.alarmLevel) }}
|
|
|
- </el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="报警类型">{{ selectedPoint.warningType ||
|
|
|
- selectedPoint.alarmType || '-' }}
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="触发时间">{{ selectedPoint.alarmTime ||
|
|
|
- selectedPoint.createTime || '-' }}
|
|
|
+ <el-tag :type="levelType(selectedPoint.alarmLevel)">{{ selectedPoint.alarmLevelText || levelText(selectedPoint.alarmLevel) }}</el-tag>
|
|
|
</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="报警类型">{{ selectedPoint.warningType || selectedPoint.alarmType || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="触发时间">{{ selectedPoint.alarmTime || selectedPoint.createTime || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="状态">
|
|
|
- <el-tag :type="Number(selectedPoint.alarmStatus) === 1 ? 'success' : 'danger'">{{
|
|
|
- Number(selectedPoint.alarmStatus) === 1 ? '已处理' : '未处理' }}
|
|
|
+ <el-tag :type="Number(selectedPoint.alarmStatus) === 1 ? 'success' : 'danger'">
|
|
|
+ {{ Number(selectedPoint.alarmStatus) === 1 ? '已处理' : '未处理' }}
|
|
|
</el-tag>
|
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="位置">{{ selectedPoint.equipmentLocation ||
|
|
|
- selectedPoint.location || '-' }}
|
|
|
- </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="位置">{{ selectedPoint.equipmentLocation || selectedPoint.location || '-' }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
- <el-button class="detail-action" type="primary" link @click="openDetail(selectedPoint)">
|
|
|
- 查看完整详情
|
|
|
- </el-button>
|
|
|
+ <el-button class="detail-action" type="primary" link @click="openDetail(selectedPoint)">查看完整详情</el-button>
|
|
|
</template>
|
|
|
<el-empty v-else description="点击点位查看详情" :image-size="65"/>
|
|
|
</div>
|
|
|
- </aside>
|
|
|
- </div>
|
|
|
+ </template>
|
|
|
+ <template #legend>
|
|
|
+ <div class="panel-section">
|
|
|
+ <h3>报警图层与图例</h3>
|
|
|
+ <div class="map-legend">
|
|
|
+ <div class="map-legend-item"><i class="map-legend-dot status-dot-normal"/>正常</div>
|
|
|
+ <div class="map-legend-item"><i class="map-legend-dot status-dot-alarm"/>报警</div>
|
|
|
+ <div class="map-legend-item"><i class="map-legend-dot status-dot-warning"/>预警</div>
|
|
|
+ <div class="map-legend-item"><i class="map-legend-dot status-dot-work-order"/>关联工单</div>
|
|
|
+ <div class="map-legend-item"><i class="map-legend-dot status-dot-offline"/>离线</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #results>
|
|
|
+ <div class="panel-section result-section">
|
|
|
+ <h3>搜索报警</h3>
|
|
|
+ <el-alert v-if="mapError" type="warning" show-icon :closable="false" :title="mapError"/>
|
|
|
+ <el-empty v-else-if="!filteredAlarmPoints.length" description="暂无报警点位" :image-size="72"/>
|
|
|
+ <div v-else class="device-result-list">
|
|
|
+ <button v-for="point in filteredAlarmPoints" :key="point.id || point.alarmId || point.deviceCode"
|
|
|
+ type="button" @click="locateAlarmPoint(point)">
|
|
|
+ <span class="device-name">{{ point.deviceName || point.deviceCode || point.name || '报警点' }}</span>
|
|
|
+ <span class="device-meta">{{ point.warningType || point.alarmType || '监测报警' }} · {{ point.alarmTime || point.createTime || '-' }}</span>
|
|
|
+ <el-tag size="small" :type="levelType(point.alarmLevel)">
|
|
|
+ {{ point.alarmLevelText || levelText(point.alarmLevel) }}
|
|
|
+ </el-tag>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #map>
|
|
|
+ <GisMapCanvas
|
|
|
+ ref="gisMapRef"
|
|
|
+ :points="gisMapPoints"
|
|
|
+ :center="{ lng: 110.3965, lat: 28.4638 }"
|
|
|
+ :zoom="15"
|
|
|
+ @point-click="openDetail"
|
|
|
+ @map-click="detailVisible = false"
|
|
|
+ @error="mapError = $event"
|
|
|
+ />
|
|
|
+ <el-alert v-if="mapError" class="map-error" type="warning" show-icon :closable="false" :title="mapError"/>
|
|
|
+ <el-empty v-if="!gisMapPoints.length && !mapError" class="map-empty" description="当前筛选下没有可显示的报警点位"/>
|
|
|
+ </template>
|
|
|
+ </WaterGisPageShell>
|
|
|
</el-card>
|
|
|
|
|
|
<el-drawer v-model="detailVisible" class="detail-drawer alarm-detail-drawer"
|
|
|
@@ -260,7 +279,7 @@
|
|
|
<script setup>
|
|
|
import {computed, onMounted, ref} from 'vue'
|
|
|
import {ElMessage} from 'element-plus'
|
|
|
- import {Bell, MapLocation} from '@element-plus/icons-vue'
|
|
|
+ import {Bell, MapLocation, Refresh, Search} from '@element-plus/icons-vue'
|
|
|
import {
|
|
|
auditAlarm,
|
|
|
dispatchAlarm,
|
|
|
@@ -274,6 +293,7 @@
|
|
|
resolveAlarm
|
|
|
} from '@/api/pipeNetwork/waterSupply'
|
|
|
import GisMapCanvas from '@/components/GisMapCanvas.vue'
|
|
|
+ import WaterGisPageShell from '@/components/WaterGisPageShell.vue'
|
|
|
import { getMonitoringPointColor, getMonitoringPointIcon } from '@/utils/gisMapColors'
|
|
|
import {normalizeMapPoints, normalizePage, unwrapResponse} from '@/utils/waterSupplyModel'
|
|
|
import {getRequestErrorMessage} from '@/utils/waterFacilityQuery'
|
|
|
@@ -289,6 +309,7 @@
|
|
|
const realtime = ref([]);
|
|
|
const mapPoints = ref([]);
|
|
|
const mapError = ref('')
|
|
|
+ const alarmSearch = ref('')
|
|
|
const gisMapRef = ref(null)
|
|
|
const loading = ref(false);
|
|
|
const detailVisible = ref(false);
|
|
|
@@ -304,7 +325,15 @@
|
|
|
label: '四级',
|
|
|
value: 4
|
|
|
}]
|
|
|
- const gisMapPoints = computed(() => mapPoints.value.map(point => ({
|
|
|
+ const filteredAlarmPoints = computed(() => {
|
|
|
+ const keyword = alarmSearch.value.trim().toLocaleLowerCase();
|
|
|
+ if (!keyword) return mapPoints.value;
|
|
|
+ return mapPoints.value.filter(point => [
|
|
|
+ point.deviceName, point.deviceCode, point.name, point.warningType, point.alarmType
|
|
|
+ ].some(value => String(value || '').toLocaleLowerCase().includes(keyword)));
|
|
|
+ });
|
|
|
+
|
|
|
+ const gisMapPoints = computed(() => filteredAlarmPoints.value.map(point => ({
|
|
|
...point,
|
|
|
lng: point._longitude ?? point.longitude,
|
|
|
lat: point._latitude ?? point.latitude,
|
|
|
@@ -371,6 +400,18 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ function alarmPointFocus(point) {
|
|
|
+ return {
|
|
|
+ lng: point._longitude ?? point.longitude,
|
|
|
+ lat: point._latitude ?? point.latitude
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ function locateAlarmPoint(point) {
|
|
|
+ gisMapRef.value?.locatePoint(alarmPointFocus(point));
|
|
|
+ openDetail(point);
|
|
|
+ }
|
|
|
+
|
|
|
async function handleResolve(row) {
|
|
|
try {
|
|
|
await resolveAlarm(row.id || row.alarmId, '前端确认处理')
|
|
|
@@ -574,6 +615,72 @@
|
|
|
min-height: 560px
|
|
|
}
|
|
|
|
|
|
+ .map-card .search-input {
|
|
|
+ width: 220px
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-card .panel-section h3 {
|
|
|
+ margin: 0 0 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #29567e
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-card .alarm-detail {
|
|
|
+ margin-top: 0;
|
|
|
+ padding-top: 0;
|
|
|
+ border-top: 0
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-card .device-result-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 6px
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-card .device-result-list button {
|
|
|
+ width: 100%;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: minmax(0, 1fr) auto;
|
|
|
+ gap: 3px 8px;
|
|
|
+ align-items: center;
|
|
|
+ border: 1px solid #dbe8f5;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 8px 10px;
|
|
|
+ text-align: left;
|
|
|
+ cursor: pointer
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-card .device-name {
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #274b6d
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-card .device-meta {
|
|
|
+ grid-column: 1 / -1;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #7793aa
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-card .map-error {
|
|
|
+ position: absolute;
|
|
|
+ left: 16px;
|
|
|
+ bottom: 16px;
|
|
|
+ width: 360px;
|
|
|
+ z-index: 1
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-card .map-empty {
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ z-index: 1
|
|
|
+ }
|
|
|
+
|
|
|
.alarm-map {
|
|
|
position: relative;
|
|
|
height: 500px;
|