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

165 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type { Station, StationAlarms, StationDevices } from '@/apis';
import { DEVICE_TYPE_LITERALS } from '@/enums';
import { EllipsisOutlined, MoreOutlined } from '@vicons/antd';
import axios from 'axios';
import { isFunction } from 'es-toolkit';
import { NButton, NCard, NCheckbox, NDropdown, NFlex, NIcon, NTag, NTooltip, useThemeVars, type DropdownOption } from 'naive-ui';
import { computed, toRefs } from 'vue';
const themeVars = useThemeVars();
const props = defineProps<{
station: Station;
devices: StationDevices;
alarms: StationAlarms;
selectable?: boolean;
}>();
const selected = defineModel<boolean>('selected', { default: false });
const emit = defineEmits<{
clickDetail: [type: 'device' | 'alarm', station: Station];
clickConfig: [station: Station];
}>();
const { station, devices, alarms, selectable } = toRefs(props);
const onlineDeviceCount = computed(() => {
return Object.values(DEVICE_TYPE_LITERALS).reduce((count, deviceType) => {
const onlineDevices = devices.value[deviceType].filter((device) => device.deviceStatus === '10');
return count + onlineDevices.length;
}, 0);
});
const offlineDeviceCount = computed(() => {
return Object.values(DEVICE_TYPE_LITERALS).reduce((count, deviceType) => {
const offlineDevices = devices.value[deviceType].filter((device) => device.deviceStatus === '20');
return count + offlineDevices.length;
}, 0);
});
const deviceCount = computed(() => {
return Object.values(DEVICE_TYPE_LITERALS).reduce((count, deviceType) => {
return count + devices.value[deviceType].length;
}, 0);
});
const alarmCount = computed(() => {
return alarms.value.unclassified.length;
});
const openVideoPlatform = async () => {
try {
const response = await axios.get<Record<string, string>>('/minio/ndm/ndm-vimps.json');
const url = response.data[station.value.code];
if (!url) {
window.$message.warning(`未找到车站编码 ${station.value.code} 对应的视频平台URL`);
return;
}
window.open(url, '_blank');
} catch (error) {
console.error('获取视频平台URL失败:', error);
window.$message.error('获取视频平台URL失败');
}
};
const openDeviceConfigModal = () => {
if (!station.value.online) {
window.$message.error('当前车站离线,无法查看');
return;
}
emit('clickConfig', station.value);
};
const dropdownOptions: DropdownOption[] = [
{
label: '视频平台',
key: 'video-platform',
onSelect: openVideoPlatform,
},
{
label: '设备配置',
key: 'device-config',
onSelect: openDeviceConfigModal,
},
];
const onSelectDropdownOption = (key: string, option: DropdownOption) => {
const onSelect = option['onSelect'];
if (isFunction(onSelect)) {
onSelect();
}
};
</script>
<template>
<NCard bordered hoverable size="medium" :header-style="{ padding: `6px` }" :content-style="{ padding: `0px 6px 6px 6px` }">
<template #header>
<template v-if="station.ip">
<NTooltip trigger="click">
<template #trigger>
<span style="font-size: smaller">{{ station.name }}</span>
</template>
<span>{{ station.ip }}</span>
</NTooltip>
</template>
<template v-else>
<span style="font-size: smaller">{{ station.name }}</span>
</template>
</template>
<template #header-extra>
<NFlex align="center" :size="4">
<NCheckbox v-if="selectable" v-model:checked="selected" :disabled="!station.online" />
<NTag :type="station.online ? 'success' : 'error'" size="small">
{{ station.online ? '在线' : '离线' }}
</NTag>
<NDropdown trigger="click" :options="dropdownOptions" @select="onSelectDropdownOption">
<NButton quaternary size="tiny" :focusable="false">
<template #icon>
<NIcon :component="MoreOutlined" />
</template>
</NButton>
</NDropdown>
</NFlex>
</template>
<template #default>
<NFlex vertical :size="6" :style="{ opacity: station.online ? '1' : '0.5' }">
<NFlex vertical :size="4">
<NFlex justify="flex-end" align="center" :size="2">
<span>{{ deviceCount }} 台设备</span>
<NButton quaternary size="tiny" :focusable="false" @click="() => emit('clickDetail', 'device', station)">
<template #icon>
<NIcon :component="EllipsisOutlined" />
</template>
</NButton>
</NFlex>
<NFlex justify="flex-end" align="center" :size="2">
<div>
<span :style="{ color: onlineDeviceCount > 0 ? themeVars.successColor : '' }">在线{{ onlineDeviceCount }}</span>
<span> · </span>
<span :style="{ color: offlineDeviceCount > 0 ? themeVars.errorColor : '' }">离线 {{ offlineDeviceCount }}</span>
</div>
<!-- 占位按钮对齐布局 -->
<NButton quaternary size="tiny" :focusable="false" style="visibility: hidden">
<template #icon>
<NIcon :component="EllipsisOutlined" />
</template>
</NButton>
</NFlex>
</NFlex>
<NFlex justify="flex-end" align="center" :size="2">
<span :style="{ color: alarmCount > 0 ? themeVars.warningColor : '' }">今日 {{ alarmCount }} 条告警</span>
<NButton quaternary size="tiny" :focusable="false" @click="() => emit('clickDetail', 'alarm', station)">
<template #icon>
<NIcon :component="EllipsisOutlined" />
</template>
</NButton>
</NFlex>
</NFlex>
</template>
</NCard>
</template>
<style scoped lang="scss"></style>