Files
ndm-web-platform/src/components/station/alarm-detail-modal/alarm-detail-modal.vue
yangsy 37781216b2 refactor: 重构项目结构
- 优化 `车站-设备-告警`  轮询机制
- 改进设备卡片的布局
- 支持修改设备
- 告警轮询中获取完整告警数据
- 车站告警详情支持导出完整的 `今日告警列表`
- 支持将状态持久化到 `IndexedDB`
- 新增轮询控制 (调试模式)
- 新增离线开发模式 (调试模式)
- 新增 `IndexedDB` 数据控制 (调试模式)
2025-12-11 13:42:22 +08:00

145 lines
5.9 KiB
Vue

<script setup lang="ts">
import type { NdmDeviceAlarmLogResultVO, Station } from '@/apis';
import { ALARM_TYPES, DEVICE_TYPE_LITERALS, DEVICE_TYPE_NAMES, FAULT_LEVELS, tryGetDeviceType } from '@/enums';
import { renderAlarmDateCell, renderAlarmTypeCell, renderDeviceTypeCell, renderFaultLevelCell } from '@/helpers';
import { useAlarmStore } from '@/stores';
import { downloadByData } from '@/utils';
import dayjs from 'dayjs';
import { NButton, NDataTable, NFlex, NGrid, NGridItem, NModal, NStatistic, NTag, type DataTableBaseColumn, type DataTableRowData, type PaginationProps } from 'naive-ui';
import { storeToRefs } from 'pinia';
import { computed, h, reactive, ref, toRefs } from 'vue';
const props = defineProps<{
station?: Station;
}>();
const show = defineModel<boolean>('show', { default: false });
const { station } = toRefs(props);
const alarmStore = useAlarmStore();
const { lineAlarms } = storeToRefs(alarmStore);
const classifiedAlarmCounts = computed<{ label: string; count: number }[]>(() => {
const stationCode = station.value?.code;
if (!stationCode) return [];
const stationAlarms = lineAlarms.value[stationCode];
if (!stationAlarms) return [];
return Object.values(DEVICE_TYPE_LITERALS).map<{ label: string; count: number }>((deviceType) => {
return {
label: DEVICE_TYPE_NAMES[deviceType],
count: stationAlarms[deviceType].length,
};
});
});
const tableColumns = ref<DataTableBaseColumn<NdmDeviceAlarmLogResultVO>[]>([
{ title: '告警流水号', key: 'alarmNo' },
{ title: '告警时间', key: 'alarmDate', render: renderAlarmDateCell },
{ title: '设备类型', key: 'deviceType', render: renderDeviceTypeCell },
{ title: '设备名称', key: 'deviceName' },
{ title: '告警类型', key: 'alarmType', align: 'center', render: renderAlarmTypeCell },
{ title: '故障级别', key: 'faultLevel', align: 'center', render: renderFaultLevelCell },
// { title: '故障编码', key: 'faultCode', align: 'center' },
// { title: '故障位置', key: 'faultLocation' },
{ title: '故障描述', key: 'faultDescription' },
{ title: '修复建议', key: 'alarmRepairSuggestion' },
{ title: '是否恢复', key: 'alarmCategory', align: 'center', render: (rowData) => (rowData.alarmCategory === '2' ? '是' : '否') },
{ title: '恢复时间', key: 'updatedTime' },
{
title: '告警确认',
key: 'alarmConfirm',
align: 'center',
render: (rowData) => (rowData.alarmConfirm === '1' ? h(NTag, { type: 'default' }, { default: () => '已确认' }) : h(NTag, { type: 'warning' }, { default: () => '未确认' })),
},
// { title: '设备ID', key: 'deviceId' },
]);
const DEFAULT_PAGE_SIZE = 10;
const pagination = reactive<PaginationProps>({
size: 'small',
showSizePicker: true,
page: 1,
pageSize: DEFAULT_PAGE_SIZE,
pageSizes: [5, 10, 20, 50, 80, 100],
prefix: ({ itemCount }) => {
return h('div', {}, { default: () => `${itemCount}` });
},
onUpdatePage: (page: number) => {
pagination.page = page;
},
onUpdatePageSize: (pageSize: number) => {
pagination.pageSize = pageSize;
pagination.page = 1;
},
});
const tableData = computed<DataTableRowData[]>(() => {
const stationCode = station.value?.code;
if (!stationCode) return [];
const stationAlarms = lineAlarms.value[stationCode];
if (!stationAlarms) return [];
return stationAlarms['unclassified'];
});
const onAfterLeave = () => {
pagination.page = 1;
pagination.pageSize = 10;
};
const exportAlarms = () => {
const keys = tableColumns.value.map((column) => column.key);
const csvHeader = `${tableColumns.value.map((column) => column.title).join(',')}\n`;
let csvRows = '';
for (const row of tableData.value) {
const alarm = row as NdmDeviceAlarmLogResultVO;
const csvRow = `${keys
.map((key) => {
const fieldKey = key as keyof NdmDeviceAlarmLogResultVO;
if (fieldKey === 'alarmDate') return dayjs(Number(alarm[fieldKey])).format('YYYY-MM-DD HH:mm:ss');
if (fieldKey === 'deviceType') {
const deviceType = tryGetDeviceType(alarm[fieldKey]);
if (!deviceType) return '-';
return DEVICE_TYPE_NAMES[deviceType];
}
if (fieldKey === 'alarmType') return ALARM_TYPES[alarm[fieldKey] ?? ''];
if (fieldKey === 'faultLevel') return FAULT_LEVELS[alarm[fieldKey] ?? ''];
if (fieldKey === 'alarmCategory') return alarm[fieldKey] === '2' ? '是' : '否';
if (fieldKey === 'alarmConfirm') return alarm[fieldKey] === '1' ? '已确认' : '未确认';
return alarm[fieldKey];
})
.join(',')}\n`;
csvRows = csvRows.concat(csvRow);
}
const csvContent = csvHeader.concat(csvRows);
const time = dayjs().format('YYYY-MM-DD_HH-mm-ss');
downloadByData(csvContent, `${station.value?.name}_设备告警记录_${time}.csv`, 'text/csv;charset=utf-8', '\ufeff');
};
</script>
<template>
<NModal v-model:show="show" preset="card" style="width: 100vw; height: 100vh" :close-on-esc="false" :mask-closable="false" @after-leave="onAfterLeave">
<template #header>
<span>{{ `${station?.name} - 设备告警详情` }}</span>
</template>
<template #default>
<NFlex vertical :size="12" style="height: 100%">
<NGrid cols="9" style="flex: 0 0 auto">
<NGridItem v-for="item in classifiedAlarmCounts" :key="item.label" span="1">
<NStatistic :label="item.label + '告警'" :value="item.count" />
</NGridItem>
</NGrid>
<NFlex align="center" style="flex: 0 0 auto">
<div style="font-size: medium">今日设备告警列表</div>
<NButton type="primary" style="margin-left: auto" @click="exportAlarms">导出</NButton>
</NFlex>
<NDataTable flex-height style="height: 100%; min-height: 0; flex: 1 1 auto" :single-line="false" :columns="tableColumns" :data="tableData" :pagination="pagination" />
</NFlex>
</template>
</NModal>
</template>
<style scoped lang="scss"></style>