diff --git a/.env.development b/.env.development index 3737274..0bbc5cc 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,6 @@ # 页面标题 -VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统 -VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus +VITE_APP_TITLE = HaiWei-Plus能源管理系统 +VITE_APP_LOGO_TITLE = HaiWei-Plus # 开发环境配置 VITE_APP_ENV = 'development' diff --git a/src/views/ems/base/emsAlarmActionStep/index.vue b/src/views/ems/base/emsAlarmActionStep/index.vue index 75470d3..2116024 100644 --- a/src/views/ems/base/emsAlarmActionStep/index.vue +++ b/src/views/ems/base/emsAlarmActionStep/index.vue @@ -56,8 +56,9 @@ - + @@ -90,11 +91,47 @@ + + + +
+
+
步骤 {{ step.stepSequence || '-' }}
+
{{ step.description || '-' }}
+
{{ step.remark }}
+
+ +
+
+
+
+ + +
+ SOP 参考图预览 +
+
+ + diff --git a/src/views/ems/base/monitorMetricThreshold/index.vue b/src/views/ems/base/monitorMetricThreshold/index.vue index 8dfa8bc..a102663 100644 --- a/src/views/ems/base/monitorMetricThreshold/index.vue +++ b/src/views/ems/base/monitorMetricThreshold/index.vue @@ -1,8 +1,30 @@ @@ -327,20 +314,9 @@ - - - - - - - - - - - - - - + + + @@ -352,6 +328,12 @@ + + +
+ SOP 参考图预览 +
+
@@ -359,6 +341,7 @@ import { getCurrentInstance } from 'vue'; import { useDict } from '@/utils/dict'; import { parseTime } from '@/utils/ruoyi'; +import { getEmsAlarmActionStepsByAlarmInfo } from '@/api/ems/base/emsAlarmActionStep'; import { addRecordAlarmData, delRecordAlarmData, @@ -368,9 +351,8 @@ import { listRecordAlarmData, updateRecordAlarmData } from '@/api/ems/record/recordAlarmData'; -import { listAlarmPushLog } from '@/api/ems/base/alarmPushLog'; -import type { AlarmPushLogVO } from '@/api/ems/base/alarmPushLog/types'; -import type { AlarmOverviewSummaryVO, EmsRecordAlarmDataVO } from '@/api/ems/types'; +import { useAlarmRealtimeBus } from '@/utils/alarmRealtime'; +import type { AlarmOverviewSummaryVO, EmsAlarmActionStepVO, EmsRecordAlarmDataVO } from '@/api/ems/types'; defineOptions({ name: 'RecordAlarmData', @@ -386,22 +368,8 @@ const tableRef = ref(); const queryFormRef = ref(); const formRef = ref(); const detailRequestToken = ref(0); - -const pushStatusOptions = [ - { label: '待推送', value: 'PENDING', type: 'info' }, - { label: '推送中', value: 'PROCESSING', type: 'warning' }, - { label: '推送成功', value: 'SUCCESS', type: 'success' }, - { label: '推送失败', value: 'FAIL', type: 'danger' } -]; - -const pushStatusMetaMap: Record = { - PENDING: { label: '待推送', type: 'info' }, - WAIT: { label: '待推送', type: 'info' }, - PROCESSING: { label: '推送中', type: 'warning' }, - SUCCESS: { label: '推送成功', type: 'success' }, - FAIL: { label: '推送失败', type: 'danger' }, - FAILED: { label: '推送失败', type: 'danger' } -}; +const previewImageUrl = ref(''); +const imagePreviewVisible = ref(false); const createQueryParams = () => ({ pageNum: 1, @@ -425,7 +393,8 @@ const createQueryParams = () => ({ beginOperationTime: null, endOperationTime: null, recoverTime: null, - pushStatus: null, + // 推送状态不属于当前标准版告警主链展示范围。 + // pushStatus: null, confirmRemark: null, cause: null, notifyUser: null, @@ -452,8 +421,9 @@ const createFormData = () => ({ operationName: null, operationTime: null, recoverTime: null, - pushStatus: null, - pushCount: null, + // 推送状态与推送次数不再开放前端编辑,避免把扩展通知能力误当成主流程。 + // pushStatus: null, + // pushCount: null, confirmUserId: null, confirmRemark: null, cause: null, @@ -478,7 +448,7 @@ const state = reactive({ loading: true, overviewLoading: false, detailLoading: false, - pushLogLoading: false, + sopLoading: false, ids: [] as Array, single: true, multiple: true, @@ -486,8 +456,8 @@ const state = reactive({ total: 0, recordAlarmDataList: [] as EmsRecordAlarmDataVO[], overviewSummary: createOverviewSummary() as AlarmOverviewSummaryVO, - pushLogList: [] as AlarmPushLogVO[], selectedAlarm: null as EmsRecordAlarmDataVO | null, + actionSteps: [] as EmsAlarmActionStepVO[], title: '', open: false, queryParams: createQueryParams() as any, @@ -501,7 +471,7 @@ const { loading, overviewLoading, detailLoading, - pushLogLoading, + sopLoading, ids, single, multiple, @@ -509,8 +479,8 @@ const { total, recordAlarmDataList, overviewSummary, - pushLogList, selectedAlarm, + actionSteps, title, open, queryParams, @@ -526,13 +496,6 @@ const currentAlarmStatusLabel = computed(() => { return matched?.label || '状态未知'; }); -const currentPushStatusLabel = computed(() => { - if (!selectedAlarm.value) { - return '未选中推送状态'; - } - return getPushStatusMeta(selectedAlarm.value.pushStatus).label; -}); - const formatDateTime = (value?: string | Date | number | null) => { if (!value) { return '-'; @@ -547,12 +510,44 @@ const formatValue = (value?: string | number | null) => { return value; }; -const getPushStatusMeta = (status?: string | null) => pushStatusMetaMap[String(status ?? '')] ?? { label: status || '未知', type: 'info' }; - const resolveMonitorName = (row?: EmsRecordAlarmDataVO | null) => row?.monitorName || row?.deviceName || row?.collectDeviceName || row?.monitorId || '-'; -const loadDetailAndPushLogs = async (row: EmsRecordAlarmDataVO) => { +const resolveImageUrl = (imageUrl?: string | null) => { + if (!imageUrl) { + return ''; + } + if (/^https?:\/\//.test(imageUrl)) { + return imageUrl; + } + return `${window.location.origin}${imageUrl}`; +}; + +const previewImage = (imageUrl?: string | null) => { + if (!imageUrl) { + return; + } + previewImageUrl.value = resolveImageUrl(imageUrl); + imagePreviewVisible.value = true; +}; + +const loadAlarmActionSteps = async (row?: EmsRecordAlarmDataVO | null) => { + if (!row?.monitorId || !row?.cause) { + actionSteps.value = []; + return; + } + sopLoading.value = true; + try { + const response = await getEmsAlarmActionStepsByAlarmInfo(row.monitorId, row.cause); + actionSteps.value = ((response as any).data ?? []) as EmsAlarmActionStepVO[]; + } catch { + actionSteps.value = []; + } finally { + sopLoading.value = false; + } +}; + +const loadAlarmDetail = async (row: EmsRecordAlarmDataVO) => { if (!row?.objId) { return; } @@ -560,39 +555,23 @@ const loadDetailAndPushLogs = async (row: EmsRecordAlarmDataVO) => { const requestToken = ++detailRequestToken.value; // 先把当前选中行同步到右侧,避免旧请求还没结束时详情面板停留在上一条记录。 selectedAlarm.value = { ...row } as EmsRecordAlarmDataVO; - pushLogList.value = []; detailLoading.value = true; - pushLogLoading.value = true; try { - const [detailRes, pushRes] = await Promise.allSettled([ - // 先查主记录,是为了让右侧详情优先对齐最新业务字段。 - getRecordAlarmData(row.objId), - // 推送日志独立查询,失败时也不能影响主列表和详情骨架展示。 - listAlarmPushLog({ - alarmObjId: row.objId, - pageNum: 1, - pageSize: 20 - } as any) - ]); + // 这里仅刷新告警主记录详情,避免页面继续把通知派生数据当成告警主链的一部分。 + const detailRes = await getRecordAlarmData(row.objId); if (requestToken !== detailRequestToken.value) { return; } - if (detailRes.status === 'fulfilled') { - selectedAlarm.value = { - ...row, - ...((detailRes.value as any).data ?? {}) - } as EmsRecordAlarmDataVO; - } - - if (pushRes.status === 'fulfilled') { - pushLogList.value = (((pushRes.value as any).rows ?? (pushRes.value as any).data ?? []) as AlarmPushLogVO[]) || []; - } + selectedAlarm.value = { + ...row, + ...((detailRes as any).data ?? {}) + } as EmsRecordAlarmDataVO; + await loadAlarmActionSteps(selectedAlarm.value); } finally { if (requestToken === detailRequestToken.value) { detailLoading.value = false; - pushLogLoading.value = false; } } }; @@ -601,7 +580,6 @@ const syncCurrentRow = () => { if (!recordAlarmDataList.value.length) { detailRequestToken.value++; selectedAlarm.value = null; - pushLogList.value = []; return; } @@ -610,7 +588,7 @@ const syncCurrentRow = () => { if (tableRef.value?.setCurrentRow) { tableRef.value.setCurrentRow(targetRow); } - void loadDetailAndPushLogs(targetRow as EmsRecordAlarmDataVO); + void loadAlarmDetail(targetRow as EmsRecordAlarmDataVO); }; const getList = async () => { @@ -649,6 +627,7 @@ const getList = async () => { const reset = () => { // 这里显式重建表单对象,避免弹窗复用时残留上一次的确认信息。 form.value = createFormData(); + actionSteps.value = []; formRef.value?.resetFields(); }; @@ -675,7 +654,7 @@ const handleSelectionChange = (selection: EmsRecordAlarmDataVO[]) => { const handleRowClick = async (row: EmsRecordAlarmDataVO) => { tableRef.value?.setCurrentRow?.(row); - await loadDetailAndPushLogs(row); + await loadAlarmDetail(row); }; const refreshCurrentAlarm = async () => { @@ -684,17 +663,28 @@ const refreshCurrentAlarm = async () => { } const matched = recordAlarmDataList.value.find((item) => item.objId === selectedAlarm.value?.objId); if (matched) { - await loadDetailAndPushLogs(matched); + await loadAlarmDetail(matched); } }; +const refreshAlarmActionSteps = async () => { + await loadAlarmActionSteps(selectedAlarm.value); +}; + const handleConfirmCurrentAlarm = async () => { if (!selectedAlarm.value?.objId) { return; } await proxy?.$modal.confirm(`是否确认将告警“${selectedAlarm.value.alarmTitle || resolveMonitorName(selectedAlarm.value)}”标记为已处理?`); - await handleExceptions(selectedAlarm.value.objId as any); - proxy?.$modal.msgSuccess('确认处理成功'); + const response = await handleExceptions(selectedAlarm.value.objId as any); + const result = ((response as any).data ?? {}) as any; + const updatedCount = Number(result.updatedCount ?? 0); + const alreadyHandledCount = Number(result.alreadyHandledCount ?? 0); + if (updatedCount <= 0 && alreadyHandledCount <= 0) { + proxy?.$modal.msgWarning((response as any).msg || '告警状态未更新,请刷新后重试'); + return; + } + proxy?.$modal.msgSuccess(updatedCount > 0 ? '确认处理成功' : '告警已是已处理状态'); await getList(); }; @@ -748,8 +738,17 @@ const handleExport = () => { ); }; +let stopRealtimeBus: (() => void) | undefined; + onMounted(() => { getList(); + stopRealtimeBus = useAlarmRealtimeBus().on(() => { + void getList(); + }); +}); + +onUnmounted(() => { + stopRealtimeBus?.(); }); @@ -885,40 +884,116 @@ onMounted(() => { font-weight: 600; } -.section-title { - margin: 8px 0 12px; - font-size: 14px; - font-weight: 600; - color: var(--el-text-color-primary); -} - -.push-log-card { - margin-bottom: 8px; -} - -.push-log-wrap { - min-height: 120px; -} - .detail-actions { display: flex; gap: 10px; margin-bottom: 16px; } -.push-log-title { - display: flex; +.sop-panel { + border-top: 1px solid var(--el-border-color-lighter); + padding-top: 16px; +} + +.sop-panel-header { align-items: center; + display: flex; + gap: 12px; justify-content: space-between; - gap: 8px; - margin-bottom: 6px; + margin-bottom: 12px; +} + +.sop-panel-title { + font-size: 16px; font-weight: 600; } -.push-log-line { - margin-top: 4px; +.sop-panel-tip { color: var(--el-text-color-secondary); - line-height: 20px; + font-size: 13px; + line-height: 1.7; + margin-top: 4px; +} + +.sop-skeleton-row + .sop-skeleton-row { + margin-top: 10px; +} + +.sop-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.sop-card { + background: linear-gradient(180deg, rgba(250, 245, 240, 0.9), rgba(255, 251, 248, 0.96)); + border: 1px solid rgba(196, 157, 140, 0.18); + border-radius: 16px; + padding: 14px 16px; +} + +.sop-card-sequence { + color: #8d5a47; + font-size: 12px; + letter-spacing: 0.12em; +} + +.sop-card-desc { + font-size: 15px; + font-weight: 600; + line-height: 1.7; + margin-top: 8px; +} + +.sop-card-remark { + color: var(--el-text-color-secondary); + font-size: 13px; + line-height: 1.7; + margin-top: 8px; +} + +.sop-card-images { + display: grid; + gap: 10px; + grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); + margin-top: 12px; +} + +.sop-card-image { + background: #fff; + border: 1px solid rgba(196, 157, 140, 0.22); + border-radius: 12px; + cursor: pointer; + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px; + text-align: left; +} + +.sop-card-image img { + border-radius: 8px; + height: 96px; + object-fit: cover; + width: 100%; +} + +.sop-card-image span { + color: var(--el-text-color-secondary); + font-size: 12px; + line-height: 1.5; +} + +.image-preview-container { + align-items: center; + display: flex; + justify-content: center; + min-height: 320px; +} + +.image-preview-container img { + max-height: 70vh; + max-width: 100%; } @media (max-width: 1200px) { diff --git a/src/views/ems/record/recordAlarmRule/index.vue b/src/views/ems/record/recordAlarmRule/index.vue index 6de95ea..0a1662b 100644 --- a/src/views/ems/record/recordAlarmRule/index.vue +++ b/src/views/ems/record/recordAlarmRule/index.vue @@ -1,124 +1,172 @@