feat(ems): 增加实时告警处置措施功能

- 新增 POST 方式查询告警措施步骤接口,解决 URL 编码问题
- 在 Navbar 组件中添加实时告警处置措施标签页
- 实现加载和显示实时告警处置措施的功能
- 优化实时告警弹窗,增加自动加载第一个告警规则的处置措施
boardTest
zch 4 weeks ago
parent dd9f9def4c
commit b2c4446745

@ -28,8 +28,12 @@ export function getEmsAlarmActionStepsByRuleId(ruleObjId) {
// 根据报警数据信息查询措施步骤列表(包含图片) // 根据报警数据信息查询措施步骤列表(包含图片)
export function getEmsAlarmActionStepsByAlarmInfo(monitorId, cause) { export function getEmsAlarmActionStepsByAlarmInfo(monitorId, cause) {
return request({ return request({
url: '/ems/base/emsAlarmActionStep/alarm/' + encodeURIComponent(monitorId) + '/' + encodeURIComponent(cause), url: '/ems/base/emsAlarmActionStep/getByAlarmInfo',
method: 'get' method: 'post',
data: {
monitorId: monitorId,
cause: cause
}
}) })
} }

@ -212,136 +212,204 @@
<el-dialog <el-dialog
title="⚠️ 实时告警通知" title="⚠️ 实时告警通知"
:visible.sync="realtimeAlarmDialog" :visible.sync="realtimeAlarmDialog"
width="800px" width="900px"
append-to-body append-to-body
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
class="realtime-alarm-dialog" class="realtime-alarm-dialog"
> >
<div v-if="currentRealtimeAlarm" class="alarm-content"> <el-tabs v-model="realtimeActiveTab" type="card">
<!-- 设备信息 --> <!-- 告警详情标签页 -->
<div class="alarm-section"> <el-tab-pane label="告警详情" name="alarmDetail">
<h3 class="section-title">📟 设备信息</h3> <div v-if="currentRealtimeAlarm" class="alarm-content">
<el-row :gutter="16"> <!-- 设备信息 -->
<el-col :span="12"> <div class="alarm-section">
<div class="info-item"> <h3 class="section-title">📟 设备信息</h3>
<span class="label">设备ID</span> <el-row :gutter="16">
<span class="value">{{ currentRealtimeAlarm.monitorId }}</span> <el-col :span="12">
</div> <div class="info-item">
</el-col> <span class="label">设备ID</span>
<el-col :span="12"> <span class="value">{{ currentRealtimeAlarm.monitorId }}</span>
<div class="info-item"> </div>
<span class="label">告警时间</span> </el-col>
<span class="value">{{ formatAlarmTime(currentRealtimeAlarm.recordTime) }}</span> <el-col :span="12">
</div> <div class="info-item">
</el-col> <span class="label">告警时间</span>
</el-row> <span class="value">{{ formatAlarmTime(currentRealtimeAlarm.recordTime) }}</span>
</div> </div>
</el-col>
</el-row>
</div>
<!-- 设备当前数据 --> <!-- 设备当前数据 -->
<div class="alarm-section" v-if="currentRealtimeAlarm.deviceParam"> <div class="alarm-section" v-if="currentRealtimeAlarm.deviceParam">
<h3 class="section-title">📊 设备当前数据</h3> <h3 class="section-title">📊 设备当前数据</h3>
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.temperature !== null"> <el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.temperature !== null">
<div class="data-item"> <div class="data-item">
<span class="data-label">温度</span> <span class="data-label">温度</span>
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.temperature }}°C</span> <span class="data-value">{{ currentRealtimeAlarm.deviceParam.temperature }}°C</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.humidity !== null"> <el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.humidity !== null">
<div class="data-item"> <div class="data-item">
<span class="data-label">湿度</span> <span class="data-label">湿度</span>
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.humidity }}%</span> <span class="data-value">{{ currentRealtimeAlarm.deviceParam.humidity }}%</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.illuminance !== null"> <el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.illuminance !== null">
<div class="data-item"> <div class="data-item">
<span class="data-label">照度</span> <span class="data-label">照度</span>
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.illuminance }}lx</span> <span class="data-value">{{ currentRealtimeAlarm.deviceParam.illuminance }}lx</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.noise !== null"> <el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.noise !== null">
<div class="data-item"> <div class="data-item">
<span class="data-label">噪声</span> <span class="data-label">噪声</span>
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.noise }}dB</span> <span class="data-value">{{ currentRealtimeAlarm.deviceParam.noise }}dB</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.concentration !== null"> <el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.concentration !== null">
<div class="data-item"> <div class="data-item">
<span class="data-label">气体浓度</span> <span class="data-label">气体浓度</span>
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.concentration }}ppm</span> <span class="data-value">{{ currentRealtimeAlarm.deviceParam.concentration }}ppm</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationSpeed !== null"> <el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationSpeed !== null">
<div class="data-item"> <div class="data-item">
<span class="data-label">振动速度</span> <span class="data-label">振动速度</span>
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationSpeed || currentRealtimeAlarm.deviceParam.VibrationSpeed }}mm/s</span> <span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationSpeed || currentRealtimeAlarm.deviceParam.VibrationSpeed }}mm/s</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationDisplacement !== null || currentRealtimeAlarm.deviceParam.VibrationDisplacement !== null"> <el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationDisplacement !== null || currentRealtimeAlarm.deviceParam.VibrationDisplacement !== null">
<div class="data-item"> <div class="data-item">
<span class="data-label">振动位移</span> <span class="data-label">振动位移</span>
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationDisplacement || currentRealtimeAlarm.deviceParam.VibrationDisplacement }}um</span> <span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationDisplacement || currentRealtimeAlarm.deviceParam.VibrationDisplacement }}um</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationAcceleration !== null || currentRealtimeAlarm.deviceParam.VibrationAcceleration !== null"> <el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationAcceleration !== null || currentRealtimeAlarm.deviceParam.VibrationAcceleration !== null">
<div class="data-item"> <div class="data-item">
<span class="data-label">振动加速度</span> <span class="data-label">振动加速度</span>
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationAcceleration || currentRealtimeAlarm.deviceParam.VibrationAcceleration }}g</span> <span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationAcceleration || currentRealtimeAlarm.deviceParam.VibrationAcceleration }}g</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationTemp !== null || currentRealtimeAlarm.deviceParam.VibrationTemp !== null"> <el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationTemp !== null || currentRealtimeAlarm.deviceParam.VibrationTemp !== null">
<div class="data-item"> <div class="data-item">
<span class="data-label">振动温度</span> <span class="data-label">振动温度</span>
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationTemp || currentRealtimeAlarm.deviceParam.VibrationTemp }}</span> <span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationTemp || currentRealtimeAlarm.deviceParam.VibrationTemp }}</span>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<!-- 触发的告警规则 --> <!-- 触发的告警规则 -->
<div class="alarm-section" v-if="currentRealtimeAlarm.alarmRules && currentRealtimeAlarm.alarmRules.length > 0"> <div class="alarm-section" v-if="currentRealtimeAlarm.alarmRules && currentRealtimeAlarm.alarmRules.length > 0">
<h3 class="section-title">🚨 触发的告警规则</h3> <h3 class="section-title">🚨 触发的告警规则</h3>
<div class="alarm-rules"> <div class="alarm-rules">
<div <div
v-for="(rule, index) in currentRealtimeAlarm.alarmRules" v-for="(rule, index) in currentRealtimeAlarm.alarmRules"
:key="index" :key="index"
class="rule-item" class="rule-item"
> >
<div class="rule-header"> <div class="rule-header">
<span class="rule-name">{{ rule.ruleName }}</span> <span class="rule-name">{{ rule.ruleName }}</span>
<el-tag :type="rule.triggerRule === 0 ? 'danger' : 'warning'" size="small"> <el-tag :type="rule.triggerRule === 0 ? 'danger' : 'warning'" size="small">
{{ rule.triggerRule === 0 ? '大于阈值' : '小于阈值' }} {{ rule.triggerRule === 0 ? '大于阈值' : '小于阈值' }}
</el-tag> </el-tag>
</div>
<div class="rule-details">
<span class="detail-item">阈值{{ rule.triggerValue }}</span>
<span class="detail-item">监测字段{{ getFieldName(rule.monitorField) }}</span>
<span class="detail-item" v-if="rule.cause">{{ rule.cause }}</span>
</div>
</div>
</div> </div>
<div class="rule-details"> </div>
<span class="detail-item">阈值{{ rule.triggerValue }}</span>
<span class="detail-item">监测字段{{ getFieldName(rule.monitorField) }}</span> <!-- 告警内容详情 -->
<span class="detail-item" v-if="rule.cause">{{ rule.cause }}</span> <div class="alarm-section" v-if="currentRealtimeAlarm.alarmContents && currentRealtimeAlarm.alarmContents.length > 0">
<h3 class="section-title">📋 告警内容详情</h3>
<div class="alarm-contents">
<div
v-for="(content, index) in currentRealtimeAlarm.alarmContents"
:key="index"
class="content-item"
>
<el-alert
:title="content"
type="error"
:closable="false"
show-icon
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </el-tab-pane>
<!-- 告警内容详情 --> <!-- 处置措施标签页 -->
<div class="alarm-section" v-if="currentRealtimeAlarm.alarmContents && currentRealtimeAlarm.alarmContents.length > 0"> <el-tab-pane label="处置措施" name="realtimeActionSteps">
<h3 class="section-title">📋 告警内容详情</h3> <div v-if="currentRealtimeAlarm">
<div class="alarm-contents"> <el-alert
<div :title="`设备:${currentRealtimeAlarm.monitorId} | 告警时间:${formatAlarmTime(currentRealtimeAlarm.recordTime)}`"
v-for="(content, index) in currentRealtimeAlarm.alarmContents" type="warning"
:key="index" :closable="false"
class="content-item" style="margin-bottom: 20px;"
> />
<el-alert
:title="content" <div v-loading="realtimeActionStepsLoading">
type="error" <div v-if="realtimeActionSteps.length === 0" class="no-steps">
:closable="false" <el-empty description="该告警规则暂无配置处置措施"></el-empty>
show-icon </div>
/>
<div v-else>
<el-timeline>
<el-timeline-item
v-for="(step, index) in realtimeActionSteps"
:key="step.objId"
:timestamp="`步骤 ${step.stepSequence}`"
placement="top"
type="primary"
size="large"
>
<el-card class="step-card">
<div slot="header" class="clearfix">
<span class="step-title">{{ step.stepSequence }}</span>
</div>
<!-- 步骤描述 -->
<div class="step-description">
<p>{{ step.description }}</p>
</div>
<!-- 步骤图片 -->
<div v-if="step.stepImages && step.stepImages.length > 0" class="step-images">
<div class="images-title">参考图片</div>
<div class="image-gallery">
<div
v-for="image in step.stepImages"
:key="image.objId"
class="image-item"
@click="previewImage(getFullImageUrl(image.imageUrl))"
>
<img :src="getFullImageUrl(image.imageUrl)" :alt="image.description" />
<div v-if="image.description" class="image-desc">{{ image.description }}</div>
</div>
</div>
</div>
<!-- 步骤备注 -->
<div v-if="step.remark" class="step-remark">
<el-tag type="info" size="small">备注{{ step.remark }}</el-tag>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</div> </div>
</div> </div>
</div> </el-tab-pane>
</div> </el-tabs>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button <el-button
@ -423,7 +491,12 @@ export default {
alarmQueueStatusText: '', alarmQueueStatusText: '',
queueStatusColor: '', queueStatusColor: '',
alarmQueueLength: 0, alarmQueueLength: 0,
queueStatusTimer: null queueStatusTimer: null,
//
realtimeActiveTab: 'alarmDetail',
realtimeActionSteps: [],
realtimeActionStepsLoading: false
} }
}, },
created() { created() {
@ -617,12 +690,18 @@ export default {
this.alarmProcessedCallback = onProcessed this.alarmProcessedCallback = onProcessed
this.alarmTimeoutCallback = onTimeout this.alarmTimeoutCallback = onTimeout
//
this.realtimeActiveTab = 'alarmDetail'
// //
this.realtimeAlarmDialog = true this.realtimeAlarmDialog = true
// //
this.playAlarmSound() this.playAlarmSound()
//
this.loadRealtimeActionSteps(alarm)
console.log('告警弹窗已显示告警ID:', alarm.id, '优先级:', alarm.priority) console.log('告警弹窗已显示告警ID:', alarm.id, '优先级:', alarm.priority)
}, },
// //
@ -812,6 +891,11 @@ export default {
this.currentAlarmId = null this.currentAlarmId = null
this.alarmProcessedCallback = null this.alarmProcessedCallback = null
this.alarmTimeoutCallback = null this.alarmTimeoutCallback = null
//
this.realtimeActiveTab = 'alarmDetail'
this.realtimeActionSteps = []
this.realtimeActionStepsLoading = false
}, },
// //
formatAlarmTime(time) { formatAlarmTime(time) {
@ -926,6 +1010,29 @@ export default {
} }
} }
} }
},
//
async loadRealtimeActionSteps(alarm) {
if (!alarm || !alarm.alarmRules || alarm.alarmRules.length === 0) {
this.realtimeActionSteps = []
return
}
this.realtimeActionStepsLoading = true
try {
// 使
const firstRule = alarm.alarmRules[0]
const fieldName = this.getFieldName(firstRule.monitorField)
const response = await getEmsAlarmActionStepsByAlarmInfo(alarm.monitorId, fieldName)
this.realtimeActionSteps = response.data || []
console.log('实时告警处置措施加载成功:', this.realtimeActionSteps.length, '个步骤')
} catch (error) {
console.error('加载实时告警处置措施失败:', error)
this.realtimeActionSteps = []
} finally {
this.realtimeActionStepsLoading = false
}
} }
} }
} }

Loading…
Cancel
Save