|
|
@@ -18,17 +18,13 @@
|
|
|
<div class="stat-num">{{ warningCount }}</div>
|
|
|
<div class="stat-label">一般报警</div>
|
|
|
</div>
|
|
|
- <div class="stat-item resolved" @click="filterAlerts('resolved')">
|
|
|
- <div class="stat-num">{{ resolvedCount }}</div>
|
|
|
- <div class="stat-label">已处置</div>
|
|
|
- </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 主内容区域:左侧报警列表 + 右侧详情研判 -->
|
|
|
<div class="main-content">
|
|
|
<!-- 左侧报警列表 -->
|
|
|
- <div class="alert-list-panel">
|
|
|
+ <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" />
|
|
|
@@ -71,20 +67,16 @@
|
|
|
<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">
|
|
|
+ <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 class="action-buttons">
|
|
|
- <el-button v-if="selectedAlert.status === 'pending'" type="primary" size="small" @click="startProcess">开始处理</el-button>
|
|
|
- <el-button v-if="selectedAlert.status !== 'resolved'" type="success" size="small" @click="resolveAsTrue">确认为真报警</el-button>
|
|
|
- <el-button v-if="selectedAlert.status !== 'resolved'" type="info" size="small" @click="resolveAsFalse">误报警排除</el-button>
|
|
|
- </div>
|
|
|
</div>
|
|
|
<el-descriptions :column="2" border>
|
|
|
<el-descriptions-item label="报警位置">
|
|
|
@@ -110,67 +102,6 @@
|
|
|
</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 实时监测曲线 (帮助分析异常趋势) -->
|
|
|
- <div class="detail-section">
|
|
|
- <div class="section-title">
|
|
|
- <span><el-icon><TrendCharts /></el-icon> 实时监测数据趋势(近1小时)</span>
|
|
|
- <span class="sub-hint">辅助综合研判,排除误报</span>
|
|
|
- </div>
|
|
|
- <div class="chart-container">
|
|
|
- <v-chart :option="realtimeChartOption" autoresize />
|
|
|
- </div>
|
|
|
- <div class="analysis-tips">
|
|
|
- <el-alert
|
|
|
- :title="analysisSuggestion"
|
|
|
- type="info"
|
|
|
- show-icon
|
|
|
- :closable="false"
|
|
|
- description="结合历史数据和当前环境因素,辅助判断报警真实性。"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 处置记录与研判日志 -->
|
|
|
- <div class="detail-section">
|
|
|
- <div class="section-title">
|
|
|
- <span><el-icon><Document /></el-icon> 处置记录 & 研判日志</span>
|
|
|
- <el-button size="small" link @click="addNote">添加备注</el-button>
|
|
|
- </div>
|
|
|
- <el-timeline>
|
|
|
- <el-timeline-item
|
|
|
- v-for="log in selectedAlert.logs"
|
|
|
- :key="log.id"
|
|
|
- :timestamp="log.time"
|
|
|
- :type="log.type"
|
|
|
- :hollow="true"
|
|
|
- >
|
|
|
- {{ log.content }}
|
|
|
- </el-timeline-item>
|
|
|
- <el-timeline-item v-if="!selectedAlert.logs?.length" timestamp="暂无处置记录" type="info">
|
|
|
- 系统已记录报警,待处理
|
|
|
- </el-timeline-item>
|
|
|
- </el-timeline>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 相似历史报警参考 -->
|
|
|
- <div class="detail-section">
|
|
|
- <div class="section-title">
|
|
|
- <span><el-icon></el-icon> 相似历史报警参考</span>
|
|
|
- </div>
|
|
|
- <div class="history-ref">
|
|
|
- <el-table :data="similarAlerts" size="small" style="width: 100%">
|
|
|
- <el-table-column prop="time" label="报警时间" width="140" />
|
|
|
- <el-table-column prop="alertType" label="报警类型" />
|
|
|
- <el-table-column prop="realData" label="实时数据" width="100" />
|
|
|
- <el-table-column prop="result" label="研判结果" width="100">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-tag :type="row.result === '真报警' ? 'danger' : 'info'" size="small">{{ row.result }}</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 未选中报警时的空状态 -->
|
|
|
@@ -179,169 +110,250 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 地图弹窗(模拟GIS位置) -->
|
|
|
+ <!-- 地图弹窗 -->
|
|
|
<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 || '121.487' }}, 纬度: {{ selectedAlert?.lat || '31.249' }}</div>
|
|
|
- <div style="margin-top: 20px; font-size: 12px; color: gray">(GIS模拟定位 - 报警点闪烁标记)</div>
|
|
|
+ <div>经度: {{ selectedAlert?.lng || '暂无数据' }}, 纬度: {{ selectedAlert?.lat || '暂无数据' }}</div>
|
|
|
+ <div style="margin-top: 20px; font-size: 12px; color: gray">当前仅展示接口返回的真实坐标信息</div>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, computed } from 'vue'
|
|
|
-import { BellFilled, WarningFilled, InfoFilled, CircleCheck, Location, Warning, TrendCharts, Document, CircleClose, Search } from '@element-plus/icons-vue'
|
|
|
-import { use } from 'echarts/core'
|
|
|
-import { CanvasRenderer } from 'echarts/renderers'
|
|
|
-import { LineChart } from 'echarts/charts'
|
|
|
-import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
|
|
|
-import { ElMessage } from 'element-plus'
|
|
|
+import {computed, onMounted, ref} 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'
|
|
|
|
|
|
-use([CanvasRenderer, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
|
|
-
|
|
|
-// 模拟报警数据
|
|
|
-const generateAlerts = () => [
|
|
|
- {
|
|
|
- id: 1, deviceId: 'MN-1001', deviceName: '人民路智能井盖', deviceType: '智能井盖监测仪',
|
|
|
- location: '人民路与解放路口东50m', lng: 121.487, lat: 31.249,
|
|
|
- alertType: '倾斜角度超限', alertParam: '倾斜角', realData: '12.5°', threshold: '8°', exceedValue: '12.5°',
|
|
|
- level: 'critical', time: '2025-04-02 14:23:15', status: 'pending',
|
|
|
- logs: [{ id: 1, time: '2025-04-02 14:23:20', content: '系统自动触发报警', type: 'danger' }]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 2, deviceId: 'MN-1002', deviceName: '滨江路防爆井盖', deviceType: '防爆井盖监测仪',
|
|
|
- location: '滨江路化工园区南门', lng: 121.502, lat: 31.235,
|
|
|
- alertType: '水位超限', alertParam: '水位深度', realData: '58cm', threshold: '40cm', exceedValue: '58cm',
|
|
|
- level: 'critical', time: '2025-04-02 13:55:02', status: 'pending',
|
|
|
- logs: [{ id: 1, time: '2025-04-02 13:55:10', content: '井下水位超阈值告警', type: 'danger' }]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 3, deviceId: 'MN-1003', deviceName: '古城街铸铁井盖', deviceType: '普通井盖监测仪',
|
|
|
- location: '古城街北段树下', lng: 121.478, lat: 31.258,
|
|
|
- alertType: '异常震动', alertParam: '震动强度', realData: '112mg', threshold: '80mg', exceedValue: '112mg',
|
|
|
- level: 'warning', time: '2025-04-02 12:10:33', status: 'processing',
|
|
|
- logs: [{ id: 1, time: '2025-04-02 12:15:00', content: '已派发巡检人员现场核实', type: 'warning' }]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 4, deviceId: 'MN-1005', deviceName: '南港路井盖', deviceType: '智能井盖监测仪',
|
|
|
- location: '南港路旧车站段', lng: 121.495, lat: 31.228,
|
|
|
- alertType: '井盖开启异常', alertParam: '开盖状态', realData: '开启', threshold: '关闭', exceedValue: '异常开启',
|
|
|
- level: 'warning', time: '2025-04-02 11:45:20', status: 'pending',
|
|
|
- logs: []
|
|
|
- },
|
|
|
- {
|
|
|
- id: 5, deviceId: 'EX-3002', deviceName: '石化区防爆井盖', deviceType: '防爆井盖监测仪',
|
|
|
- location: '石化大道南侧', lng: 121.524, lat: 31.239,
|
|
|
- alertType: '气体浓度超标', alertParam: 'H₂S浓度', realData: '28ppm', threshold: '15ppm', exceedValue: '28ppm',
|
|
|
- level: 'critical', time: '2025-04-02 10:30:45', status: 'resolved',
|
|
|
- logs: [{ id: 1, time: '2025-04-02 11:00:00', content: '经现场确认,为真报警,已通风处置', type: 'success' }]
|
|
|
- }
|
|
|
-]
|
|
|
-
|
|
|
-const alerts = ref(generateAlerts())
|
|
|
+const alerts = ref([])
|
|
|
const searchText = ref('')
|
|
|
const filterType = ref('all')
|
|
|
-const selectedAlert = ref(alerts.value[0])
|
|
|
+const selectedAlert = ref(null)
|
|
|
const mapDialogVisible = ref(false)
|
|
|
+const listLoading = ref(false)
|
|
|
+const detailLoading = ref(false)
|
|
|
|
|
|
-const filteredAlerts = computed(() => {
|
|
|
- let list = alerts.value
|
|
|
- if (filterType.value === 'critical') list = list.filter(a => a.level === 'critical' && a.status !== 'resolved')
|
|
|
- else if (filterType.value === 'warning') list = list.filter(a => a.level === 'warning' && a.status !== 'resolved')
|
|
|
- else if (filterType.value === 'resolved') list = list.filter(a => a.status === 'resolved')
|
|
|
- else list = list.filter(a => a.status !== 'resolved' || filterType.value === 'resolved' ? true : a.status !== 'resolved')
|
|
|
+function toNumber(value) {
|
|
|
+ const num = Number(value)
|
|
|
+ return Number.isFinite(num) ? num : null
|
|
|
+}
|
|
|
|
|
|
- if (searchText.value) {
|
|
|
- const kw = searchText.value.toLowerCase()
|
|
|
- list = list.filter(a => a.location.toLowerCase().includes(kw) || a.deviceId.toLowerCase().includes(kw))
|
|
|
+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}`
|
|
|
}
|
|
|
- return list
|
|
|
-})
|
|
|
+ if (minValue !== null && minValue !== undefined) return `<= ${minValue}`
|
|
|
+ if (maxValue !== null && maxValue !== undefined) return `>= ${maxValue}`
|
|
|
+ return '—'
|
|
|
+}
|
|
|
|
|
|
-const totalAlerts = computed(() => alerts.value.filter(a => a.status !== 'resolved').length)
|
|
|
-const criticalCount = computed(() => alerts.value.filter(a => a.level === 'critical' && a.status !== 'resolved').length)
|
|
|
-const warningCount = computed(() => alerts.value.filter(a => a.level === 'warning' && a.status !== 'resolved').length)
|
|
|
-const resolvedCount = computed(() => alerts.value.filter(a => a.status === 'resolved').length)
|
|
|
-const unhandledCount = computed(() => alerts.value.filter(a => a.status === 'pending').length)
|
|
|
+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)
|
|
|
|
|
|
-const filterAlerts = (type) => { filterType.value = type }
|
|
|
+ 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')
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-const selectAlert = (alert) => { selectedAlert.value = alert }
|
|
|
+ if (`${manholeData.waterInfiltrationAlarmStatus}` === '1') {
|
|
|
+ return {
|
|
|
+ hasAlert: true,
|
|
|
+ level: 'critical',
|
|
|
+ levelText: '严重报警',
|
|
|
+ alertType: '水浸报警',
|
|
|
+ alertParam: '水浸状态',
|
|
|
+ realData: '报警',
|
|
|
+ threshold: getThresholdTextByCode(raw, 'WARN-WATER-INFILTRATION')
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-// 模拟实时数据曲线(基于报警类型生成模拟趋势数据)
|
|
|
-const realtimeChartOption = computed(() => {
|
|
|
- if (!selectedAlert.value) return {}
|
|
|
- const param = selectedAlert.value.alertParam || '数值'
|
|
|
- const unit = selectedAlert.value.alertType.includes('角度') ? '°' : selectedAlert.value.alertType.includes('水位') ? 'cm' : selectedAlert.value.alertType.includes('震动') ? 'mg' : ''
|
|
|
- // 模拟最近12个时间点的数据,最后一点为报警值突出
|
|
|
- const baseValue = parseFloat(selectedAlert.value.realData) || 20
|
|
|
- const thresholdVal = parseFloat(selectedAlert.value.threshold) || 10
|
|
|
- const mockData = Array.from({ length: 12 }, (_, i) => {
|
|
|
- if (i === 11) return baseValue
|
|
|
- return baseValue - (Math.random() * (baseValue - thresholdVal) * 0.6 + 2)
|
|
|
- })
|
|
|
- const times = Array.from({ length: 12 }, (_, i) => `${i * 5}分钟前`).reverse()
|
|
|
- return {
|
|
|
- tooltip: { trigger: 'axis' },
|
|
|
- xAxis: { type: 'category', data: times },
|
|
|
- yAxis: { type: 'value', name: `${param} (${unit})` },
|
|
|
- series: [
|
|
|
- { name: '实时监测值', type: 'line', data: mockData, smooth: true, lineStyle: { color: '#f56c6c', width: 2 }, areaStyle: { opacity: 0.2 }, markLine: { data: [{ yAxis: thresholdVal, name: '阈值线', lineStyle: { color: '#e6a23c', type: 'dashed' } }] } }
|
|
|
- ]
|
|
|
+ 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') : '—'
|
|
|
+ }
|
|
|
}
|
|
|
-})
|
|
|
|
|
|
-const analysisSuggestion = computed(() => {
|
|
|
- if (!selectedAlert.value) return ''
|
|
|
- const alert = selectedAlert.value
|
|
|
- if (alert.alertType.includes('倾斜')) return '当前倾斜角度超阈值12.5°,超过设定值50%以上,且近期有道路施工记录,建议现场核实是否为施工影响或井盖破损。'
|
|
|
- if (alert.alertType.includes('水位')) return '水位突增至58cm,查看过去2小时降雨量数据无雨情,可能为管道堵塞,建议立即排水检修,排除误报。'
|
|
|
- if (alert.alertType.includes('震动')) return '震动峰值112mg,周边无大型车辆记录,可能由于井盖松动或人为敲击,建议巡检。'
|
|
|
- return '结合历史报警记录,该测点一周内无相似报警,当前数据超出阈值明显,真报警可能性较高,建议优先处置。'
|
|
|
-})
|
|
|
+ if (battery !== null && battery <= 20) {
|
|
|
+ return {
|
|
|
+ hasAlert: true,
|
|
|
+ level: 'warning',
|
|
|
+ levelText: '一般报警',
|
|
|
+ alertType: '低电量预警',
|
|
|
+ alertParam: '电池电量',
|
|
|
+ realData: `${battery}%`,
|
|
|
+ threshold: '20%'
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-const similarAlerts = computed(() => {
|
|
|
- if (!selectedAlert.value) return []
|
|
|
- return alerts.value.filter(a => a.alertType === selectedAlert.value.alertType && a.id !== selectedAlert.value.id && a.status === 'resolved').slice(0, 3).map(a => ({
|
|
|
- time: a.time, alertType: a.alertType, realData: a.realData, result: a.result || '真报警'
|
|
|
- }))
|
|
|
-})
|
|
|
+ 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: '—'
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
-const startProcess = () => {
|
|
|
- if (selectedAlert.value.status === 'pending') {
|
|
|
- selectedAlert.value.status = 'processing'
|
|
|
- selectedAlert.value.logs.push({ id: Date.now(), time: new Date().toLocaleString(), content: '值班人员开始处理报警', type: 'warning' })
|
|
|
- ElMessage.info('已开始处理该报警事件')
|
|
|
+function normalizeAlert(raw, baseAlert = null) {
|
|
|
+ const alertInfo = buildAlertInfo(raw)
|
|
|
+ if (!alertInfo.hasAlert) return null
|
|
|
+ return {
|
|
|
+ id: raw?.equipmentId || raw?.equipmentCode,
|
|
|
+ equipmentId: raw?.equipmentId || '',
|
|
|
+ deviceId: raw?.equipmentCode || raw?.equipmentId || '—',
|
|
|
+ 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
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const resolveAsTrue = () => {
|
|
|
- if (selectedAlert.value.status === 'resolved') return
|
|
|
- selectedAlert.value.status = 'resolved'
|
|
|
- selectedAlert.value.result = '真报警'
|
|
|
- selectedAlert.value.logs.push({ id: Date.now(), time: new Date().toLocaleString(), content: '经研判确认为真报警,已派发维修工单', type: 'success' })
|
|
|
- ElMessage.success('已确认真报警,处置流程已启动')
|
|
|
+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
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-const resolveAsFalse = () => {
|
|
|
- if (selectedAlert.value.status === 'resolved') return
|
|
|
- selectedAlert.value.status = 'resolved'
|
|
|
- selectedAlert.value.result = '误报警'
|
|
|
- selectedAlert.value.logs.push({ id: Date.now(), time: new Date().toLocaleString(), content: '经现场核实,该报警为误报(环境干扰/传感器波动),已排除', type: 'info' })
|
|
|
- ElMessage.info('已标记为误报警,事件关闭')
|
|
|
+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 viewOnMap = () => { mapDialogVisible.value = true }
|
|
|
-const addNote = () => {
|
|
|
- const note = prompt('请输入研判备注信息')
|
|
|
- if (note && selectedAlert.value) {
|
|
|
- selectedAlert.value.logs.push({ id: Date.now(), time: new Date().toLocaleString(), content: `备注: ${note}`, type: 'info' })
|
|
|
- ElMessage.success('备注已添加')
|
|
|
+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.deviceId || ''}`.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 filterAlerts = (type) => {
|
|
|
+ filterType.value = type
|
|
|
+}
|
|
|
+
|
|
|
+const selectAlert = async (alert) => {
|
|
|
+ selectedAlert.value = alert
|
|
|
+ await loadAlertDetail(alert)
|
|
|
}
|
|
|
+
|
|
|
+const viewOnMap = () => {
|
|
|
+ mapDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await loadAlerts()
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
@@ -433,9 +445,6 @@ const addNote = () => {
|
|
|
}
|
|
|
.real-time-data { background: #fff9f0; padding: 8px; border-radius: 8px; }
|
|
|
.warning-value { color: #f56c6c; font-size: 18px; margin: 0 4px; }
|
|
|
-.chart-container { height: 220px; }
|
|
|
-.analysis-tips { margin-top: 12px; }
|
|
|
.action-buttons { display: flex; gap: 8px; }
|
|
|
.empty-detail { flex: 1; display: flex; align-items: center; justify-content: center; background: white; border-radius: 20px; }
|
|
|
-.history-ref { margin-top: 8px; }
|
|
|
-</style>
|
|
|
+</style>
|