|
|
@@ -135,7 +135,10 @@
|
|
|
<span class="alarm-dev">{{ a.deviceName || a.deviceCode }}<span class="alarm-code">{{ a.deviceCode }}</span></span>
|
|
|
<span class="alarm-level" :style="{ color: levelColor(a.alarmLevel) }">{{ levelName(a.alarmLevel) }}</span>
|
|
|
</div>
|
|
|
- <div class="alarm-time">{{ a.createTime }}</div>
|
|
|
+ <div class="alarm-time">
|
|
|
+ <span v-if="a.alarmNo" class="alarm-no">{{ a.alarmNo }}</span>
|
|
|
+ <span>{{ a.createTime }}</span>
|
|
|
+ </div>
|
|
|
<div class="alarm-desc">{{ a.alarmContent || "—" }} <span v-if="a.actualValue != null">实测 {{ a.actualValue }}</span></div>
|
|
|
</div>
|
|
|
<el-empty v-if="!alarms.length" description="当前无未解除报警" :image-size="60" />
|
|
|
@@ -176,7 +179,7 @@
|
|
|
append-to-body
|
|
|
direction="rtl"
|
|
|
@opened="startProfileTimer"
|
|
|
- @closed="stopProfileTimer"
|
|
|
+ @closed="onDrawerClosed"
|
|
|
>
|
|
|
<template #header>
|
|
|
<div class="drawer-title" v-if="profile.base">
|
|
|
@@ -207,11 +210,23 @@
|
|
|
v-for="c in recordViewColumns"
|
|
|
:key="c.key"
|
|
|
:label="c.label + (c.unit ? '(' + c.unit + ')' : '')"
|
|
|
- :min-width="c.key === 'create_time' ? 168 : 104"
|
|
|
- show-overflow-tooltip
|
|
|
+ :min-width="c.key === 'create_time' ? 168 : (c.key === 'filepath' ? 180 : 104)"
|
|
|
>
|
|
|
- <template #default="{ row }">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
<span v-if="c.key === 'create_time'">{{ row.createTime || '—' }}</span>
|
|
|
+ <span v-else-if="c.key === 'filepath'">
|
|
|
+ <el-button
|
|
|
+ v-if="valueOf(row, c.key) && valueOf(row, c.key) !== '—'"
|
|
|
+ class="rec-play-btn"
|
|
|
+ size="small"
|
|
|
+ circle
|
|
|
+ :type="isPlayingCell($index, c.key) ? 'primary' : 'default'"
|
|
|
+ @click="toggleAudio($index, c.key, row)"
|
|
|
+ >
|
|
|
+ <el-icon><VideoPause v-if="isPlayingCell($index, c.key)" /><VideoPlay v-else /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <span v-else>—</span>
|
|
|
+ </span>
|
|
|
<span v-else :class="{ 'cell-alarm': alarmOf(row, c.key) }">{{ valueOf(row, c.key) }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
@@ -228,6 +243,7 @@
|
|
|
small
|
|
|
v-model:current-page="recordPage"
|
|
|
/>
|
|
|
+ <audio ref="recordAudio" class="rec-audio-single" @ended="onAudioEnded"></audio>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -251,6 +267,7 @@ import {
|
|
|
getAreaRank,
|
|
|
getAlarmTypeDist
|
|
|
} from "@/api/pipeNetwork/perception";
|
|
|
+import { buildMinioAttachmentUrl } from "@/utils/minioAttachment";
|
|
|
|
|
|
const loading = ref(false);
|
|
|
const stats = ref({});
|
|
|
@@ -387,6 +404,40 @@ function alarmOf(row, key) {
|
|
|
return c ? !!c.alarm : false;
|
|
|
}
|
|
|
|
|
|
+// 采集记录音频播放:单例 audio,一次只播一条
|
|
|
+const recordAudio = ref(null);
|
|
|
+const playingKey = ref("");
|
|
|
+
|
|
|
+function cellPlayKey(index, key) {
|
|
|
+ return index + ":" + key;
|
|
|
+}
|
|
|
+function isPlayingCell(index, key) {
|
|
|
+ return playingKey.value === cellPlayKey(index, key);
|
|
|
+}
|
|
|
+function toggleAudio(index, key, row) {
|
|
|
+ const el = recordAudio.value;
|
|
|
+ if (!el) return;
|
|
|
+ const k = cellPlayKey(index, key);
|
|
|
+ if (playingKey.value === k) {
|
|
|
+ el.pause();
|
|
|
+ playingKey.value = "";
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const v = valueOf(row, key);
|
|
|
+ if (!v || v === "—") return;
|
|
|
+ el.src = buildMinioAttachmentUrl(v);
|
|
|
+ el.play().catch(() => {});
|
|
|
+ playingKey.value = k;
|
|
|
+}
|
|
|
+function onAudioEnded() {
|
|
|
+ playingKey.value = "";
|
|
|
+}
|
|
|
+function onDrawerClosed() {
|
|
|
+ stopProfileTimer();
|
|
|
+ if (recordAudio.value) recordAudio.value.pause();
|
|
|
+ playingKey.value = "";
|
|
|
+}
|
|
|
+
|
|
|
// 报警等级配色(与系统 alarm 模块保持一致:1红 2橙 3蓝 4绿)
|
|
|
function levelColor(level) {
|
|
|
return ({ 1: "#f56c6c", 2: "#e6a23c", 3: "#409eff", 4: "#67c23a" }[level] || "#909399");
|
|
|
@@ -705,7 +756,8 @@ onBeforeUnmount(() => {
|
|
|
.alarm-item { padding: 11px 16px; border-bottom: 1px solid #eef2f7; border-left: 3px solid #ef4444; }
|
|
|
.alarm-item:hover { background: #f8fbff; }
|
|
|
.alarm-top { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 4px; }
|
|
|
-.alarm-time { font-size: 12px; color: #97a3b7; font-variant-numeric: tabular-nums; margin-bottom: 4px; }
|
|
|
+.alarm-time { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-size: 12px; color: #97a3b7; font-variant-numeric: tabular-nums; margin-bottom: 4px; }
|
|
|
+.alarm-no { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: #8a96a8; white-space: nowrap; }
|
|
|
.alarm-dev { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; min-width: 0; }
|
|
|
.alarm-code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; font-weight: 600; color: #5e6d82; background: #f1f5f9; border: 1px solid #e3e9f2; border-radius: 4px; padding: 1px 5px; margin-left: 6px; flex: none; white-space: nowrap; }
|
|
|
.alarm-level { font-size: 12px; font-weight: 600; flex: none; }
|
|
|
@@ -748,6 +800,7 @@ onBeforeUnmount(() => {
|
|
|
.hist-wrap :deep(.el-table__inner-wrapper::after) { display: none; }
|
|
|
.hist-wrap :deep(.el-table .el-table__row:last-child td.el-table__cell) { border-bottom: none; }
|
|
|
.rec-empty { color: #9aa7b8; font-size: 12px; padding: 28px 0; }
|
|
|
+.rec-audio-single { display: none; }
|
|
|
.rec-pager { margin-top: 10px; justify-content: flex-end; }
|
|
|
|
|
|
@keyframes pulse {
|