|
|
|
|
@ -110,7 +110,8 @@
|
|
|
|
|
type="text"
|
|
|
|
|
icon="el-icon-view"
|
|
|
|
|
@click.stop="viewActionSteps(scope.row)"
|
|
|
|
|
>查看措施</el-button>
|
|
|
|
|
>查看措施
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
@ -132,12 +133,12 @@
|
|
|
|
|
:closable="false"
|
|
|
|
|
style="margin-bottom: 20px;"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div v-loading="actionStepsLoading">
|
|
|
|
|
<div v-if="actionSteps.length === 0" class="no-steps">
|
|
|
|
|
<el-empty description="该告警规则暂无配置处置措施"></el-empty>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else>
|
|
|
|
|
<el-timeline>
|
|
|
|
|
<el-timeline-item
|
|
|
|
|
@ -152,12 +153,12 @@
|
|
|
|
|
<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>
|
|
|
|
|
@ -168,12 +169,12 @@
|
|
|
|
|
class="image-item"
|
|
|
|
|
@click="previewImage(getFullImageUrl(image.imageUrl))"
|
|
|
|
|
>
|
|
|
|
|
<img :src="getFullImageUrl(image.imageUrl)" :alt="image.description" />
|
|
|
|
|
<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>
|
|
|
|
|
@ -204,7 +205,7 @@
|
|
|
|
|
center
|
|
|
|
|
>
|
|
|
|
|
<div class="image-preview-container">
|
|
|
|
|
<img :src="previewImageUrl" style="max-width: 100%; max-height: 70vh;" />
|
|
|
|
|
<img :src="previewImageUrl" style="max-width: 100%; max-height: 70vh;"/>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
@ -278,36 +279,48 @@
|
|
|
|
|
<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>
|
|
|
|
|
<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">
|
|
|
|
|
<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>
|
|
|
|
|
<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">
|
|
|
|
|
<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>
|
|
|
|
|
<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">
|
|
|
|
|
<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>
|
|
|
|
|
<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">
|
|
|
|
|
<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"
|
|
|
|
|
<div
|
|
|
|
|
v-for="(rule, index) in currentRealtimeAlarm.alarmRules"
|
|
|
|
|
:key="index"
|
|
|
|
|
class="rule-item"
|
|
|
|
|
>
|
|
|
|
|
@ -327,11 +340,12 @@
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 告警内容详情 -->
|
|
|
|
|
<div class="alarm-section" v-if="currentRealtimeAlarm.alarmContents && currentRealtimeAlarm.alarmContents.length > 0">
|
|
|
|
|
<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"
|
|
|
|
|
<div
|
|
|
|
|
v-for="(content, index) in currentRealtimeAlarm.alarmContents"
|
|
|
|
|
:key="index"
|
|
|
|
|
class="content-item"
|
|
|
|
|
>
|
|
|
|
|
@ -356,12 +370,12 @@
|
|
|
|
|
:closable="false"
|
|
|
|
|
style="margin-bottom: 20px;"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div v-loading="realtimeActionStepsLoading">
|
|
|
|
|
<div v-if="realtimeActionSteps.length === 0" class="no-steps">
|
|
|
|
|
<el-empty description="该告警规则暂无配置处置措施"></el-empty>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else>
|
|
|
|
|
<el-timeline>
|
|
|
|
|
<el-timeline-item
|
|
|
|
|
@ -376,12 +390,12 @@
|
|
|
|
|
<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>
|
|
|
|
|
@ -392,12 +406,12 @@
|
|
|
|
|
class="image-item"
|
|
|
|
|
@click="previewImage(getFullImageUrl(image.imageUrl))"
|
|
|
|
|
>
|
|
|
|
|
<img :src="getFullImageUrl(image.imageUrl)" :alt="image.description" />
|
|
|
|
|
<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>
|
|
|
|
|
@ -412,14 +426,14 @@
|
|
|
|
|
</el-tabs>
|
|
|
|
|
|
|
|
|
|
<div slot="footer" class="dialog-footer">
|
|
|
|
|
<el-button
|
|
|
|
|
<el-button
|
|
|
|
|
@click="closeRealtimeAlarmDialog"
|
|
|
|
|
:loading="alarmProcessing"
|
|
|
|
|
>
|
|
|
|
|
稍后处理
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
type="primary"
|
|
|
|
|
<el-button
|
|
|
|
|
type="primary"
|
|
|
|
|
@click="processRealtimeAlarm"
|
|
|
|
|
:loading="alarmProcessing"
|
|
|
|
|
>
|
|
|
|
|
@ -432,7 +446,7 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import { mapGetters } from 'vuex'
|
|
|
|
|
import {mapGetters} from 'vuex'
|
|
|
|
|
import Breadcrumb from '@/components/Breadcrumb'
|
|
|
|
|
import TopNav from '@/components/TopNav'
|
|
|
|
|
import Hamburger from '@/components/Hamburger'
|
|
|
|
|
@ -442,9 +456,9 @@ import Search from '@/components/HeaderSearch'
|
|
|
|
|
import RuoYiGit from '@/components/RuoYi/Git'
|
|
|
|
|
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'
|
|
|
|
|
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'],
|
|
|
|
|
@ -477,7 +491,7 @@ export default {
|
|
|
|
|
activeTab: 'alarmList',
|
|
|
|
|
imagePreviewVisible: false,
|
|
|
|
|
previewImageUrl: '',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// WebSocket告警相关
|
|
|
|
|
realtimeAlarmDialog: false,
|
|
|
|
|
currentRealtimeAlarm: null,
|
|
|
|
|
@ -492,7 +506,7 @@ export default {
|
|
|
|
|
queueStatusColor: '',
|
|
|
|
|
alarmQueueLength: 0,
|
|
|
|
|
queueStatusTimer: null,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 实时告警处置措施相关
|
|
|
|
|
realtimeActiveTab: 'alarmDetail',
|
|
|
|
|
realtimeActionSteps: [],
|
|
|
|
|
@ -501,7 +515,7 @@ export default {
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
localStorage.setItem('this.alarmDataTotal', 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 监听新的队列化WebSocket告警事件
|
|
|
|
|
this.$bus.$on('websocket-alarm-with-callback', this.handleQueuedRealtimeAlarm)
|
|
|
|
|
// 监听自动超时保存事件
|
|
|
|
|
@ -518,7 +532,7 @@ export default {
|
|
|
|
|
this.$bus.$off('websocket-connected', this.onWebSocketConnected)
|
|
|
|
|
this.$bus.$off('websocket-disconnected', this.onWebSocketDisconnected)
|
|
|
|
|
this.$bus.$off('websocket-max-retries-reached', this.onWebSocketMaxRetriesReached)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 清理定时器
|
|
|
|
|
if (this.queueStatusTimer) {
|
|
|
|
|
clearInterval(this.queueStatusTimer)
|
|
|
|
|
@ -528,7 +542,7 @@ export default {
|
|
|
|
|
mounted() {
|
|
|
|
|
// 初始获取告警数据,后续完全依赖WebSocket推送
|
|
|
|
|
this.getAlarmData()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 定期更新告警队列状态
|
|
|
|
|
this.queueStatusTimer = setInterval(() => {
|
|
|
|
|
this.updateQueueStatus()
|
|
|
|
|
@ -668,12 +682,12 @@ export default {
|
|
|
|
|
// 获取完整的图片URL
|
|
|
|
|
getFullImageUrl(relativePath) {
|
|
|
|
|
if (!relativePath) return '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经是完整URL,直接返回(兼容历史数据)
|
|
|
|
|
if (relativePath.startsWith('http')) {
|
|
|
|
|
return relativePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 动态拼接当前环境的baseURL
|
|
|
|
|
const baseURL = process.env.VUE_APP_BASE_API || '';
|
|
|
|
|
return baseURL + relativePath;
|
|
|
|
|
@ -681,27 +695,27 @@ export default {
|
|
|
|
|
// 处理队列化的WebSocket实时告警
|
|
|
|
|
handleQueuedRealtimeAlarm(data) {
|
|
|
|
|
console.log('收到队列化实时告警:', data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 解构获取告警数据和回调函数
|
|
|
|
|
const { alarm, onProcessed, onTimeout } = data
|
|
|
|
|
|
|
|
|
|
const {alarm, onProcessed, onTimeout} = data
|
|
|
|
|
|
|
|
|
|
this.currentRealtimeAlarm = alarm
|
|
|
|
|
this.currentAlarmId = alarm.id
|
|
|
|
|
this.alarmProcessedCallback = onProcessed
|
|
|
|
|
this.alarmTimeoutCallback = onTimeout
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 重置标签页到告警详情
|
|
|
|
|
this.realtimeActiveTab = 'alarmDetail'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 显示告警弹窗
|
|
|
|
|
this.realtimeAlarmDialog = true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 播放提示音
|
|
|
|
|
this.playAlarmSound()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 自动加载第一个告警规则的处置措施
|
|
|
|
|
this.loadRealtimeActionSteps(alarm)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('告警弹窗已显示,告警ID:', alarm.id, '优先级:', alarm.priority)
|
|
|
|
|
},
|
|
|
|
|
// 播放告警提示音
|
|
|
|
|
@ -721,12 +735,12 @@ export default {
|
|
|
|
|
// 构建符合EmsRecordAlarmData实体的数据列表
|
|
|
|
|
// 每个触发的告警规则对应一条EmsRecordAlarmData记录
|
|
|
|
|
const alarmDataList = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!alarmData.alarmRules || alarmData.alarmRules.length === 0) {
|
|
|
|
|
console.warn('告警数据中没有告警规则')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前时间并格式化为后端期望的格式
|
|
|
|
|
const getCurrentTimeForBackend = () => {
|
|
|
|
|
const now = new Date()
|
|
|
|
|
@ -738,37 +752,37 @@ export default {
|
|
|
|
|
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,
|
|
|
|
|
@ -779,12 +793,12 @@ export default {
|
|
|
|
|
处理状态: alarmRecord.alarmStatus === 0 ? '已处理' : '未处理'
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (alarmDataList.length === 0) {
|
|
|
|
|
console.warn('没有有效的告警记录可保存')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 发送到后端保存
|
|
|
|
|
const response = await saveWebSocketAlarmData(alarmDataList)
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
@ -803,13 +817,13 @@ export default {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 从设备参数中获取指定字段的实际数值
|
|
|
|
|
getActualValueFromDeviceParam(deviceParam, monitorField) {
|
|
|
|
|
if (!deviceParam || monitorField === null || monitorField === undefined) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch (monitorField) {
|
|
|
|
|
case 0: // 温度
|
|
|
|
|
return deviceParam.temperature
|
|
|
|
|
@ -843,7 +857,7 @@ export default {
|
|
|
|
|
const success = await this.saveRealtimeAlarmData(this.currentRealtimeAlarm, 1)
|
|
|
|
|
if (success) {
|
|
|
|
|
this.$message.info('告警已记录,状态为未处理')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 调用回调函数通知App.vue处理完成
|
|
|
|
|
if (this.alarmProcessedCallback) {
|
|
|
|
|
this.alarmProcessedCallback(this.currentAlarmId, 'later')
|
|
|
|
|
@ -856,10 +870,10 @@ export default {
|
|
|
|
|
this.alarmProcessing = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.closeAlarmDialog()
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 确认知晓实时告警(保存为已处理状态)
|
|
|
|
|
async processRealtimeAlarm() {
|
|
|
|
|
this.alarmProcessing = true
|
|
|
|
|
@ -868,13 +882,13 @@ export default {
|
|
|
|
|
const success = await this.saveRealtimeAlarmData(this.currentRealtimeAlarm, 0)
|
|
|
|
|
if (success) {
|
|
|
|
|
this.$message.success('告警已确认知晓并标记为已处理')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 调用回调函数通知App.vue处理完成
|
|
|
|
|
if (this.alarmProcessedCallback) {
|
|
|
|
|
this.alarmProcessedCallback(this.currentAlarmId, 'processed')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭弹窗
|
|
|
|
|
this.closeAlarmDialog()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
@ -891,7 +905,7 @@ export default {
|
|
|
|
|
this.currentAlarmId = null
|
|
|
|
|
this.alarmProcessedCallback = null
|
|
|
|
|
this.alarmTimeoutCallback = null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 清理实时告警处置措施数据
|
|
|
|
|
this.realtimeActiveTab = 'alarmDetail'
|
|
|
|
|
this.realtimeActionSteps = []
|
|
|
|
|
@ -944,8 +958,8 @@ export default {
|
|
|
|
|
// 处理WebSocket自动超时保存事件
|
|
|
|
|
handleAutoSaveAlarm(data) {
|
|
|
|
|
console.log('收到自动超时保存事件:', data)
|
|
|
|
|
const { alarm, status } = data
|
|
|
|
|
|
|
|
|
|
const {alarm, status} = data
|
|
|
|
|
|
|
|
|
|
// 自动保存告警数据
|
|
|
|
|
this.saveRealtimeAlarmData(alarm, status).then((success) => {
|
|
|
|
|
if (success) {
|
|
|
|
|
@ -982,9 +996,9 @@ export default {
|
|
|
|
|
// 从App.vue获取告警队列状态
|
|
|
|
|
if (this.$root.getAlarmQueueStatus) {
|
|
|
|
|
const status = this.$root.getAlarmQueueStatus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.alarmQueueLength = status.queueLength
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 根据队列长度设置状态
|
|
|
|
|
if (status.queueLength === 0) {
|
|
|
|
|
this.alarmQueueStatusText = '告警队列为空'
|
|
|
|
|
@ -996,7 +1010,7 @@ export default {
|
|
|
|
|
this.alarmQueueStatusText = `告警队列:${status.queueLength}个待处理(队列较长)`
|
|
|
|
|
this.queueStatusColor = '#F56C6C' // 红色
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化WebSocket状态(如果还未设置)
|
|
|
|
|
if (!this.websocketStatusText) {
|
|
|
|
|
if (status.isConnected) {
|
|
|
|
|
@ -1017,13 +1031,13 @@ export default {
|
|
|
|
|
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, '个步骤')
|
|
|
|
|
@ -1146,37 +1160,37 @@ export default {
|
|
|
|
|
// 措施步骤相关样式
|
|
|
|
|
.step-card {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.step-title {
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
color: #409EFF;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.step-description {
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
p {
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: #606266;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.step-images {
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.images-title {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.image-gallery {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.image-item {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
border: 2px solid #f0f0f0;
|
|
|
|
|
@ -1184,19 +1198,19 @@ export default {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
max-width: 200px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
border-color: #409EFF;
|
|
|
|
|
transform: scale(1.02);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
img {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 150px;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.image-desc {
|
|
|
|
|
padding: 8px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
@ -1207,7 +1221,7 @@ export default {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.step-remark {
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
}
|
|
|
|
|
@ -1228,57 +1242,57 @@ export default {
|
|
|
|
|
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;
|
|
|
|
|
@ -1286,29 +1300,29 @@ export default {
|
|
|
|
|
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;
|
|
|
|
|
@ -1318,11 +1332,11 @@ export default {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.alarm-contents {
|
|
|
|
|
.content-item {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.el-alert {
|
|
|
|
|
.el-alert__title {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
@ -1333,11 +1347,11 @@ export default {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.dialog-footer {
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 20px 0 10px 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.el-button {
|
|
|
|
|
min-width: 100px;
|
|
|
|
|
}
|
|
|
|
|
@ -1349,7 +1363,7 @@ export default {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
i {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
@ -1362,16 +1376,16 @@ export default {
|
|
|
|
|
position: relative;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
transform: scale(1.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
i {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.queue-count {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: -8px;
|
|
|
|
|
|