- 优化 `车站-设备-告警` 轮询机制 - 改进设备卡片的布局 - 支持修改设备 - 告警轮询中获取完整告警数据 - 车站告警详情支持导出完整的 `今日告警列表` - 支持将状态持久化到 `IndexedDB` - 新增轮询控制 (调试模式) - 新增离线开发模式 (调试模式) - 新增 `IndexedDB` 数据控制 (调试模式)
145 lines
5.9 KiB
Vue
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>
|