|
|
|
@ -192,6 +192,158 @@
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 实时告警弹窗 -->
|
|
|
|
|
<el-dialog
|
|
|
|
|
title="⚠️ 实时告警通知"
|
|
|
|
|
:visible.sync="realtimeAlarmDialog"
|
|
|
|
|
width="800px"
|
|
|
|
|
append-to-body
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
:close-on-press-escape="false"
|
|
|
|
|
class="realtime-alarm-dialog"
|
|
|
|
|
>
|
|
|
|
|
<div v-if="currentRealtimeAlarm" class="alarm-content">
|
|
|
|
|
<!-- 设备信息 -->
|
|
|
|
|
<div class="alarm-section">
|
|
|
|
|
<h3 class="section-title">📟 设备信息</h3>
|
|
|
|
|
<el-row :gutter="16">
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">设备ID:</span>
|
|
|
|
|
<span class="value">{{ currentRealtimeAlarm.monitorId }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">告警时间:</span>
|
|
|
|
|
<span class="value">{{ formatAlarmTime(currentRealtimeAlarm.recordTime) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 设备当前数据 -->
|
|
|
|
|
<div class="alarm-section" v-if="currentRealtimeAlarm.deviceParam">
|
|
|
|
|
<h3 class="section-title">📊 设备当前数据</h3>
|
|
|
|
|
<el-row :gutter="16">
|
|
|
|
|
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.temperature !== null">
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
<span class="data-label">温度:</span>
|
|
|
|
|
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.temperature }}°C</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.humidity !== null">
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
<span class="data-label">湿度:</span>
|
|
|
|
|
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.humidity }}%</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.illuminance !== null">
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
<span class="data-label">照度:</span>
|
|
|
|
|
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.illuminance }}lx</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.noise !== null">
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
<span class="data-label">噪声:</span>
|
|
|
|
|
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.noise }}dB</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.concentration !== null">
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
<span class="data-label">气体浓度:</span>
|
|
|
|
|
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.concentration }}ppm</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationSpeed !== null">
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
<span class="data-label">振动速度:</span>
|
|
|
|
|
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationSpeed || currentRealtimeAlarm.deviceParam.VibrationSpeed }}mm/s</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationDisplacement !== null || currentRealtimeAlarm.deviceParam.VibrationDisplacement !== null">
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
<span class="data-label">振动位移:</span>
|
|
|
|
|
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationDisplacement || currentRealtimeAlarm.deviceParam.VibrationDisplacement }}um</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationAcceleration !== null || currentRealtimeAlarm.deviceParam.VibrationAcceleration !== null">
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
<span class="data-label">振动加速度:</span>
|
|
|
|
|
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationAcceleration || currentRealtimeAlarm.deviceParam.VibrationAcceleration }}g</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="8" v-if="currentRealtimeAlarm.deviceParam.vibrationTemp !== null || currentRealtimeAlarm.deviceParam.VibrationTemp !== null">
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
<span class="data-label">振动温度:</span>
|
|
|
|
|
<span class="data-value">{{ currentRealtimeAlarm.deviceParam.vibrationTemp || currentRealtimeAlarm.deviceParam.VibrationTemp }}℃</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 触发的告警规则 -->
|
|
|
|
|
<div class="alarm-section" v-if="currentRealtimeAlarm.alarmRules && currentRealtimeAlarm.alarmRules.length > 0">
|
|
|
|
|
<h3 class="section-title">🚨 触发的告警规则</h3>
|
|
|
|
|
<div class="alarm-rules">
|
|
|
|
|
<div
|
|
|
|
|
v-for="(rule, index) in currentRealtimeAlarm.alarmRules"
|
|
|
|
|
:key="index"
|
|
|
|
|
class="rule-item"
|
|
|
|
|
>
|
|
|
|
|
<div class="rule-header">
|
|
|
|
|
<span class="rule-name">{{ rule.ruleName }}</span>
|
|
|
|
|
<el-tag :type="rule.triggerRule === 0 ? 'danger' : 'warning'" size="small">
|
|
|
|
|
{{ rule.triggerRule === 0 ? '大于阈值' : '小于阈值' }}
|
|
|
|
|
</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="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 slot="footer" class="dialog-footer">
|
|
|
|
|
<el-button
|
|
|
|
|
@click="closeRealtimeAlarmDialog"
|
|
|
|
|
:loading="alarmProcessing"
|
|
|
|
|
>
|
|
|
|
|
稍后处理
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
type="primary"
|
|
|
|
|
@click="processRealtimeAlarm"
|
|
|
|
|
:loading="alarmProcessing"
|
|
|
|
|
>
|
|
|
|
|
确认知晓
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
@ -208,6 +360,7 @@ import RuoYiDoc from '@/components/RuoYi/Doc'
|
|
|
|
|
import settings from '@/settings'
|
|
|
|
|
import { handleExceptions, listRecordAlarmData } from '@/api/ems/record/recordAlarmData'
|
|
|
|
|
import { getEmsAlarmActionStepsByRuleId, getEmsAlarmActionStepsByAlarmInfo } from '@/api/ems/base/emsAlarmActionStep'
|
|
|
|
|
import { saveWebSocketAlarmData } from '@/api/ems/record/recordAlarmData'
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
dicts: ['alarm_type', 'alarm_status'],
|
|
|
|
@ -239,16 +392,27 @@ export default {
|
|
|
|
|
actionStepsLoading: false,
|
|
|
|
|
activeTab: 'alarmList',
|
|
|
|
|
imagePreviewVisible: false,
|
|
|
|
|
previewImageUrl: ''
|
|
|
|
|
previewImageUrl: '',
|
|
|
|
|
|
|
|
|
|
// WebSocket告警相关
|
|
|
|
|
realtimeAlarmDialog: false,
|
|
|
|
|
currentRealtimeAlarm: null,
|
|
|
|
|
alarmProcessing: false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
localStorage.setItem('this.alarmDataTotal', 0)
|
|
|
|
|
|
|
|
|
|
// 监听WebSocket告警事件
|
|
|
|
|
this.$bus.$on('websocket-alarm', this.handleRealtimeAlarm)
|
|
|
|
|
},
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
// 移除事件监听
|
|
|
|
|
this.$bus.$off('websocket-alarm', this.handleRealtimeAlarm)
|
|
|
|
|
},
|
|
|
|
|
mounted() {
|
|
|
|
|
// 定时获取提示路由信息
|
|
|
|
|
// 初始获取告警数据,后续完全依赖WebSocket推送
|
|
|
|
|
this.getAlarmData()
|
|
|
|
|
setInterval(() => this.getAlarmData(), 1000 * 60);
|
|
|
|
|
},
|
|
|
|
|
components: {
|
|
|
|
|
Breadcrumb,
|
|
|
|
@ -393,6 +557,215 @@ export default {
|
|
|
|
|
// 动态拼接当前环境的baseURL
|
|
|
|
|
const baseURL = process.env.VUE_APP_BASE_API || '';
|
|
|
|
|
return baseURL + relativePath;
|
|
|
|
|
},
|
|
|
|
|
// 处理WebSocket实时告警
|
|
|
|
|
handleRealtimeAlarm(alarmData) {
|
|
|
|
|
console.log('收到实时告警:', alarmData)
|
|
|
|
|
this.currentRealtimeAlarm = alarmData
|
|
|
|
|
this.realtimeAlarmDialog = true
|
|
|
|
|
|
|
|
|
|
// 同时播放提示音(可选)
|
|
|
|
|
this.playAlarmSound()
|
|
|
|
|
|
|
|
|
|
// 注意:这里不立即保存,等用户操作后再保存
|
|
|
|
|
// this.saveRealtimeAlarmData(alarmData)
|
|
|
|
|
},
|
|
|
|
|
// 播放告警提示音
|
|
|
|
|
playAlarmSound() {
|
|
|
|
|
try {
|
|
|
|
|
// 可以添加音频文件播放
|
|
|
|
|
// const audio = new Audio('/static/alarm.mp3')
|
|
|
|
|
// audio.play()
|
|
|
|
|
console.log('播放告警提示音')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('播放提示音失败:', error)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 保存WebSocket告警数据到数据库
|
|
|
|
|
async saveRealtimeAlarmData(alarmData, alarmStatus = 1) {
|
|
|
|
|
try {
|
|
|
|
|
// 构建符合EmsRecordAlarmData实体的数据列表
|
|
|
|
|
// 每个触发的告警规则对应一条EmsRecordAlarmData记录
|
|
|
|
|
const alarmDataList = []
|
|
|
|
|
|
|
|
|
|
if (!alarmData.alarmRules || alarmData.alarmRules.length === 0) {
|
|
|
|
|
console.warn('告警数据中没有告警规则')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取当前时间并格式化为后端期望的格式
|
|
|
|
|
const getCurrentTimeForBackend = () => {
|
|
|
|
|
const now = new Date()
|
|
|
|
|
const year = now.getFullYear()
|
|
|
|
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
|
|
|
const day = String(now.getDate()).padStart(2, '0')
|
|
|
|
|
const hours = String(now.getHours()).padStart(2, '0')
|
|
|
|
|
const minutes = String(now.getMinutes()).padStart(2, '0')
|
|
|
|
|
const seconds = String(now.getSeconds()).padStart(2, '0')
|
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 为每个触发的告警规则创建一条记录
|
|
|
|
|
for (const rule of alarmData.alarmRules) {
|
|
|
|
|
// 获取对应字段的实际数值
|
|
|
|
|
const actualValue = this.getActualValueFromDeviceParam(alarmData.deviceParam, rule.monitorField)
|
|
|
|
|
|
|
|
|
|
const alarmRecord = {
|
|
|
|
|
// 设备相关信息
|
|
|
|
|
monitorId: alarmData.monitorId,
|
|
|
|
|
collectTime: getCurrentTimeForBackend(), // 使用当前时间并格式化为后端期望格式
|
|
|
|
|
|
|
|
|
|
// 告警类型:根据规则的triggerRule设置(0=大于阈值,1=小于阈值)
|
|
|
|
|
alarmType: rule.triggerRule || 0,
|
|
|
|
|
|
|
|
|
|
// 告警状态:根据用户操作设置(0=已处理,1=未处理)
|
|
|
|
|
alarmStatus: alarmStatus,
|
|
|
|
|
|
|
|
|
|
// 告警数据:实际触发告警的数值
|
|
|
|
|
alarmData: actualValue ? String(actualValue) : '',
|
|
|
|
|
|
|
|
|
|
// 告警原因:使用字段名称
|
|
|
|
|
cause: this.getFieldName(rule.monitorField),
|
|
|
|
|
|
|
|
|
|
// 其他字段可以为空,后端会设置默认值
|
|
|
|
|
operationName: null,
|
|
|
|
|
operationTime: null,
|
|
|
|
|
notifyUser: null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
alarmDataList.push(alarmRecord)
|
|
|
|
|
|
|
|
|
|
console.log('构建告警记录:', {
|
|
|
|
|
设备ID: alarmRecord.monitorId,
|
|
|
|
|
告警字段: alarmRecord.cause,
|
|
|
|
|
实际数值: alarmRecord.alarmData,
|
|
|
|
|
告警类型: alarmRecord.alarmType === 0 ? '大于阈值' : '小于阈值',
|
|
|
|
|
规则阈值: rule.triggerValue,
|
|
|
|
|
记录时间: alarmRecord.collectTime,
|
|
|
|
|
处理状态: alarmRecord.alarmStatus === 0 ? '已处理' : '未处理'
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (alarmDataList.length === 0) {
|
|
|
|
|
console.warn('没有有效的告警记录可保存')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 发送到后端保存
|
|
|
|
|
const response = await saveWebSocketAlarmData(alarmDataList)
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
console.log('告警数据保存成功:', response.msg)
|
|
|
|
|
// 刷新告警数据列表,获取最新数据
|
|
|
|
|
this.getAlarmDataList()
|
|
|
|
|
return true
|
|
|
|
|
} else {
|
|
|
|
|
console.error('告警数据保存失败:', response.msg)
|
|
|
|
|
this.$message.error('告警数据保存失败: ' + response.msg)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('保存告警数据异常:', error)
|
|
|
|
|
this.$message.error('保存告警数据异常')
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 从设备参数中获取指定字段的实际数值
|
|
|
|
|
getActualValueFromDeviceParam(deviceParam, monitorField) {
|
|
|
|
|
if (!deviceParam || monitorField === null || monitorField === undefined) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (monitorField) {
|
|
|
|
|
case 0: // 温度
|
|
|
|
|
return deviceParam.temperature
|
|
|
|
|
case 1: // 湿度
|
|
|
|
|
return deviceParam.humidity
|
|
|
|
|
case 2: // 振动-速度(mm/s)
|
|
|
|
|
return deviceParam.vibrationSpeed || deviceParam.VibrationSpeed
|
|
|
|
|
case 3: // 振动-位移(um)
|
|
|
|
|
return deviceParam.vibrationDisplacement || deviceParam.VibrationDisplacement
|
|
|
|
|
case 4: // 振动-加速度(g)
|
|
|
|
|
return deviceParam.vibrationAcceleration || deviceParam.VibrationAcceleration
|
|
|
|
|
case 5: // 振动-温度(℃)
|
|
|
|
|
return deviceParam.vibrationTemp || deviceParam.VibrationTemp
|
|
|
|
|
case 6: // 照度
|
|
|
|
|
return deviceParam.illuminance
|
|
|
|
|
case 7: // 噪声
|
|
|
|
|
return deviceParam.noise
|
|
|
|
|
case 8: // 气体浓度
|
|
|
|
|
return deviceParam.concentration
|
|
|
|
|
default:
|
|
|
|
|
console.warn('未知的监测字段:', monitorField)
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 稍后处理实时告警(保存为未处理状态)
|
|
|
|
|
async closeRealtimeAlarmDialog() {
|
|
|
|
|
if (this.currentRealtimeAlarm) {
|
|
|
|
|
this.alarmProcessing = true
|
|
|
|
|
try {
|
|
|
|
|
// 保存告警数据,状态为1(未处理)
|
|
|
|
|
const success = await this.saveRealtimeAlarmData(this.currentRealtimeAlarm, 1)
|
|
|
|
|
if (success) {
|
|
|
|
|
this.$message.info('告警已记录,状态为未处理')
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('保存告警数据失败:', error)
|
|
|
|
|
this.$message.error('保存告警数据失败')
|
|
|
|
|
} finally {
|
|
|
|
|
this.alarmProcessing = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.realtimeAlarmDialog = false
|
|
|
|
|
this.currentRealtimeAlarm = null
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 确认知晓实时告警(保存为已处理状态)
|
|
|
|
|
async processRealtimeAlarm() {
|
|
|
|
|
this.alarmProcessing = true
|
|
|
|
|
try {
|
|
|
|
|
// 保存告警数据,状态为0(已处理)
|
|
|
|
|
const success = await this.saveRealtimeAlarmData(this.currentRealtimeAlarm, 0)
|
|
|
|
|
if (success) {
|
|
|
|
|
this.$message.success('告警已确认知晓并标记为已处理')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 关闭弹窗
|
|
|
|
|
this.realtimeAlarmDialog = false
|
|
|
|
|
this.currentRealtimeAlarm = null
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('处理告警失败:', error)
|
|
|
|
|
this.$message.error('处理告警失败')
|
|
|
|
|
} finally {
|
|
|
|
|
this.alarmProcessing = false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 格式化告警时间
|
|
|
|
|
formatAlarmTime(time) {
|
|
|
|
|
if (!time) return '--'
|
|
|
|
|
try {
|
|
|
|
|
const date = new Date(time)
|
|
|
|
|
return date.toLocaleString('zh-CN')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return time
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 获取监测字段名称
|
|
|
|
|
getFieldName(fieldCode) {
|
|
|
|
|
const fieldMap = {
|
|
|
|
|
0: '温度',
|
|
|
|
|
1: '湿度',
|
|
|
|
|
2: '振动-速度(mm/s)',
|
|
|
|
|
3: '振动-位移(um)',
|
|
|
|
|
4: '振动-加速度(g)',
|
|
|
|
|
5: '振动-温度(℃)',
|
|
|
|
|
6: '照度',
|
|
|
|
|
7: '噪声',
|
|
|
|
|
8: '气体浓度'
|
|
|
|
|
}
|
|
|
|
|
return fieldMap[fieldCode] || '未知字段'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -582,4 +955,126 @@ export default {
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 实时告警弹窗样式
|
|
|
|
|
.realtime-alarm-dialog {
|
|
|
|
|
.el-dialog__header {
|
|
|
|
|
background: linear-gradient(135deg, #ff4757, #ff6b7a);
|
|
|
|
|
color: white;
|
|
|
|
|
padding: 15px 20px;
|
|
|
|
|
|
|
|
|
|
.el-dialog__title {
|
|
|
|
|
color: white;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.el-dialog__close {
|
|
|
|
|
color: white;
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
color: #f1f1f1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.alarm-content {
|
|
|
|
|
max-height: 60vh;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
|
|
|
|
.alarm-section {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
padding: 15px;
|
|
|
|
|
background: #f9f9f9;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
border-left: 4px solid #ff4757;
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
margin: 0 0 15px 0;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-item, .data-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
|
|
|
|
.label, .data-label {
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #666;
|
|
|
|
|
min-width: 80px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.value, .data-value {
|
|
|
|
|
color: #333;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.alarm-rules {
|
|
|
|
|
.rule-item {
|
|
|
|
|
background: white;
|
|
|
|
|
border: 1px solid #e6e6e6;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
.rule-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
|
|
|
|
.rule-name {
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: #333;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rule-details {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 15px;
|
|
|
|
|
|
|
|
|
|
.detail-item {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: #666;
|
|
|
|
|
|
|
|
|
|
&:before {
|
|
|
|
|
content: "• ";
|
|
|
|
|
color: #ff4757;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.alarm-contents {
|
|
|
|
|
.content-item {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
.el-alert {
|
|
|
|
|
.el-alert__title {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dialog-footer {
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 20px 0 10px 0;
|
|
|
|
|
|
|
|
|
|
.el-button {
|
|
|
|
|
min-width: 100px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</style>
|
|
|
|
|