Files
fe.ems.vue3/src/views/faultManage/alarm-overview/index.vue

1339 lines
32 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 {
ref,
onMounted,
reactive,
onBeforeUnmount,
computed,
nextTick,
watch,
} from 'vue';
import {
ExclamationCircleOutlined,
WarningOutlined,
InfoCircleOutlined,
BellOutlined,
} from '@ant-design/icons-vue';
import * as echarts from 'echarts';
import { useRouter } from 'vue-router';
import { origGet } from '@/api/faultManage/actAlarm';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n';
import { useFullscreen } from '@vueuse/core';
const { t } = useI18n();
const router = useRouter();
const viewportDom = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(viewportDom);
const trendRange = ref('1'); // '1':今天,'7'7天'30'30天
// 加载状态
const loading = ref(false);
// 定时器引用
let refreshTimer: number | null = null;
// 最新告警列表
const allLatestAlarmsAct: any = ref([]);
const allLatestAlarmsHis: any = ref([]);
const newHistoryAlarms = ref(new Set());
const newActiveAlarms = ref(new Set());
const alarmTypeType = ref<any>([
{
value: 0,
name: t('views.index.Critical'),
},
{
value: 0,
name: t('views.index.Major'),
},
{
value: 0,
name: t('views.index.Minor'),
},
{
value: 0,
name: t('views.index.Warning'),
},
// {
// value: 0,
// name: t('views.index.Event'),
// },
]);
// 使用计算属性确保显示固定数量的告警
const latestAlarmsAct: any = computed(() => {
return allLatestAlarmsAct.value.slice(0, 10); // 始终显示前5条
});
const latestAlarmsHis: any = computed(() => {
return allLatestAlarmsHis.value.slice(0, 10); // 始终显示前5条
});
// 告警数据
const alarmData = reactive({
critical: 0,
major: 0,
minor: 0,
warning: 0,
});
// 图表引用
const trendChart = ref(null);
const typeChart = ref(null);
let trendChartInstance: echarts.ECharts | null = null;
// 跳转到告警页面
function fnToRouter(name: string, query?: any) {
router.push({ name, query });
}
function getFullHourAxis() {
const hours: string[] = [];
for (let i = 0; i < 24; i++) {
hours.push(i.toString().padStart(2, '0') + ':00');
}
return hours;
}
function getFullDayAxis(days: number) {
const daysArr: string[] = [];
const today = new Date();
for (let i = days - 1; i >= 0; i--) {
const d = new Date(today);
d.setDate(today.getDate() - i);
daysArr.push(d.toISOString().slice(0, 10));
}
return daysArr;
}
function getFull4HourAxis() {
const hours: string[] = [];
for (let i = 0; i < 24; i += 4) {
hours.push(i.toString().padStart(2, '0') + ':00');
}
return hours;
}
function fillTrendData(rawData: any[], axis: string[]) {
const map = new Map(rawData.map(item => [item.time, item]));
return axis.map(time => ({
time,
Critical: map.get(time)?.Critical ?? 0,
Major: map.get(time)?.Major ?? 0,
Minor: map.get(time)?.Minor ?? 0,
Warning: map.get(time)?.Warning ?? 0,
}));
}
const isFirstLoad = ref(true);
// 修改获取最新活动告警的方法
async function fetchLatestAlarmsAct() {
try {
const res = { code: 0, rows: [] }; // await alarmDashGetAct();
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 只有非首次加载才检查新告警
if (!isFirstLoad.value) {
// 获取当前告警ID集合
const currentIds = new Set(
allLatestAlarmsAct.value.map((alarm: any) => alarm.alarmId)
);
console.log('currentIds', currentIds);
// 找出新告警
res.rows.forEach((alarm: any) => {
if (!currentIds.has(alarm.alarmId)) {
console.log('新活动告警:', alarm.alarmId, alarm.alarmTitle);
newActiveAlarms.value.add(alarm.alarmId);
// 3秒后移除新告警标记
setTimeout(() => {
newActiveAlarms.value.delete(alarm.alarmId);
}, 3000);
}
});
}
// 更新告警列表
allLatestAlarmsAct.value = res.rows;
}
} catch (error) {
console.error('Get Act-Data List error:', error);
}
}
async function fetchLatestAlarmsHis() {
try {
const res = { code: 0, rows: [] }; //await alarmDashGetHis();
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 只有非首次加载才检查新告警
if (!isFirstLoad.value) {
// 获取当前告警ID集合
const currentIds = new Set(
allLatestAlarmsHis.value.map((alarm: any) => alarm.alarmId)
);
// 找出新告警
res.rows.forEach((alarm:any) => {
if (!currentIds.has(alarm.alarmId)) {
console.log('新历史告警:', alarm.alarmId, alarm.alarmTitle);
newHistoryAlarms.value.add(alarm.alarmId);
// 3秒后移除新告警标记
setTimeout(() => {
newHistoryAlarms.value.delete(alarm.alarmId);
}, 3000);
}
});
}
// 更新告警列表
allLatestAlarmsHis.value = res.rows;
}
} catch (error) {
console.error('Get His-Data List error:', error);
}
}
// 获取告警数据
async function fetchAlarmData() {
try {
loading.value = true;
const res = await origGet();
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 重置告警数据
alarmData.critical = 0;
alarmData.major = 0;
alarmData.minor = 0;
alarmData.warning = 0;
console.log('Get Data Success:', res.data);
// 解析告警数据
res.data.forEach((item: any) => {
if (item.severity === 'Critical') {
alarmData.critical = item.total;
} else if (item.severity === 'Major') {
alarmData.major = item.total;
} else if (item.severity === 'Minor') {
alarmData.minor = item.total;
} else if (item.severity === 'Warning') {
alarmData.warning = item.total;
}
});
await fetchLatestAlarmsAct();
await fetchLatestAlarmsHis();
if (isFirstLoad.value) {
isFirstLoad.value = false;
}
}
} catch (error) {
console.error('Get Data Er r:', error);
} finally {
loading.value = false;
}
}
const trendData: any = ref([]);
async function fetchAlarmTrend() {
try {
const res = { code: 0, rows: [] }; //await getAlarmTrend({ days: trendRange.value }); // 你需要让后端支持days参数
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
trendData.value = res.rows;
updateTrendChart();
}
} catch (e) {
console.error('Get Trend Data Error:', e);
}
}
function updateTrendChart() {
if (!trendChartInstance) return;
let xAxisData: string[];
if (trendRange.value === '1') {
xAxisData = getFullHourAxis();
} else {
xAxisData = getFullDayAxis(Number(trendRange.value));
}
const filledData = fillTrendData(trendData.value, xAxisData);
const critical = filledData.map(item => item.Critical);
const major = filledData.map(item => item.Major);
const minor = filledData.map(item => item.Minor);
const warning = filledData.map(item => item.Warning);
trendChartInstance.setOption({
xAxis: {
data: xAxisData,
axisLabel: {
color: '#00fcff',
interval: xAxisData.length > 12 ? 2 : 0, // 超过12个点就隔2个显示
// rotate: 45 // 如果你想斜着显示
},
},
series: [
{
name: t('views.index.Critical'),
type: 'line',
data: critical,
showSymbol: true,
itemStyle: { color: '#f5222d' },
},
{
name: t('views.index.Major'),
type: 'line',
data: major,
showSymbol: true,
itemStyle: { color: '#fa8c16' },
},
{
name: t('views.index.Minor'),
type: 'line',
data: minor,
showSymbol: true,
itemStyle: { color: '#faad14' },
},
{
name: t('views.index.Warning'),
type: 'line',
data: warning,
showSymbol: true,
itemStyle: { color: '#52c41a' },
},
],
});
}
watch(trendRange, () => {
fetchAlarmTrend();
});
// 初始化图表
onMounted(() => {
fetchAlarmData();
fetchAlarmTrend(); // 定时刷新趋势数据
// 设置定时刷新 - 每10秒刷新一次
refreshTimer = window.setInterval(() => {
fetchAlarmData();
fetchAlarmTrend(); // 定时刷新趋势数据
}, 10000);
// 初始化告警趋势图
nextTick(() => {
trendChartInstance = echarts.init(trendChart.value);
const trendOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
},
},
legend: {
data: alarmTypeType.value.map((item: any) => item.name),
textStyle: {
color: '#00fcff',
},
right: 10,
top: 10,
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00'],
axisLine: {
lineStyle: {
color: '#0a73ff',
},
},
axisLabel: {
color: '#00fcff',
},
},
yAxis: {
type: 'value',
min: 0, // 设置最小值为0
minInterval: 1, // 最小间隔为1确保刻度是整数
axisLine: {
lineStyle: {
color: '#0a73ff',
},
},
axisLabel: {
color: '#00fcff',
},
splitLine: {
lineStyle: {
color: 'rgba(10, 115, 255, 0.2)',
},
},
},
series: [
{
name: t('views.index.Critical'),
type: 'line',
emphasis: {
focus: 'series',
},
data: [0, 0, 0, 0, 0, 0],
itemStyle: {
color: '#f5222d',
},
symbol: 'none',
symbolSize: 6,
smooth: 0.6,
showSymbol: true,
sampling: 'lttb',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(250,140,22,0.4)' },
{ offset: 1, color: 'rgba(250,140,22,0.05)' },
]),
},
},
{
name: t('views.index.Major'),
type: 'line',
emphasis: {
focus: 'series',
},
data: [0, 0, 0, 0, 0, 0],
itemStyle: {
color: '#fa8c16',
},
symbol: 'none',
symbolSize: 6,
smooth: 0.6,
showSymbol: true,
sampling: 'lttb',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(250,140,22,0.4)' },
{ offset: 1, color: 'rgba(250,140,22,0.05)' },
]),
},
},
{
name: t('views.index.Minor'),
type: 'line',
emphasis: {
focus: 'series',
},
data: [0, 0, 0, 0, 0, 0],
itemStyle: {
color: '#faad14',
},
symbol: 'none',
symbolSize: 6,
smooth: 0.6,
showSymbol: true,
sampling: 'lttb',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(250,173,20,0.4)' },
{ offset: 1, color: 'rgba(250,173,20,0.05)' },
]),
},
},
{
name: t('views.index.Warning'),
type: 'line',
emphasis: {
focus: 'series',
},
data: [0, 0, 0, 0, 0, 0],
itemStyle: {
color: '#52c41a',
},
symbol: 'none',
symbolSize: 6,
smooth: 0.6,
showSymbol: true,
sampling: 'lttb',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(82,196,26,0.4)' },
{ offset: 1, color: 'rgba(82,196,26,0.05)' },
]),
},
},
],
};
trendChartInstance.setOption(trendOption);
trendChartInstance.resize();
});
// 响应式调整
window.addEventListener('resize', handleResize);
});
// 处理窗口大小变化
function handleResize() {
trendChartInstance?.resize();
}
// 组件销毁前清理
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
// 清除定时器
if (refreshTimer !== null) {
clearInterval(refreshTimer);
refreshTimer = null;
}
// 销毁图表实例
trendChartInstance?.dispose();
});
</script>
<template>
<div class="viewport" ref="viewportDom">
<div class="alarm-dashboard">
<div class="dashboard-header">
<div
class="header-title"
@click="toggle"
:title="t('views.dashboard.overview.fullscreen')"
>
告警监控大屏
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</div>
</div>
<div class="dashboard-content">
<!-- 告警概览卡片 -->
<div class="alarm-overview">
<div class="alarm-card Critical">
<div class="card-border-top"></div>
<div class="card-border-right"></div>
<div class="card-border-bottom"></div>
<div class="card-border-left"></div>
<div class="alarm-icon">
<ExclamationCircleOutlined />
</div>
<div class="alarm-info">
<div class="alarm-count">{{ alarmData.critical || 0 }}</div>
<div class="alarm-title">{{ t('views.index.Critical') }}</div>
</div>
</div>
<div class="alarm-card Major">
<div class="card-border-top"></div>
<div class="card-border-right"></div>
<div class="card-border-bottom"></div>
<div class="card-border-left"></div>
<div class="alarm-icon">
<WarningOutlined />
</div>
<div class="alarm-info">
<div class="alarm-count">{{ alarmData.major || 0 }}</div>
<div class="alarm-title">{{ t('views.index.Major') }}</div>
</div>
</div>
<div class="alarm-card Minor">
<div class="card-border-top"></div>
<div class="card-border-right"></div>
<div class="card-border-bottom"></div>
<div class="card-border-left"></div>
<div class="alarm-icon">
<InfoCircleOutlined />
</div>
<div class="alarm-info">
<div class="alarm-count">{{ alarmData.minor || 0 }}</div>
<div class="alarm-title">{{ t('views.index.Minor') }}</div>
</div>
</div>
<div class="alarm-card Warning">
<div class="card-border-top"></div>
<div class="card-border-right"></div>
<div class="card-border-bottom"></div>
<div class="card-border-left"></div>
<div class="alarm-icon">
<BellOutlined />
</div>
<div class="alarm-info">
<div class="alarm-count">{{ alarmData.warning || 0 }}</div>
<div class="alarm-title">{{ t('views.index.Warning') }}</div>
</div>
</div>
</div>
<div class="dashboard-main">
<div class="main-left">
<!-- 告警趋势图 -->
<div class="alarm-trend panel">
<div class="panel-header">
<div class="panel-title">历史告警趋势分析</div>
<a-radio-group
v-model:value="trendRange"
size="small"
class="trend-range-radio"
>
<a-radio-button value="1">今天</a-radio-button>
<a-radio-button value="3">3</a-radio-button>
<a-radio-button value="7">7</a-radio-button>
</a-radio-group>
</div>
<div class="panel-content">
<div ref="trendChart" class="trend-chart"></div>
</div>
<div class="panel-corner panel-corner-lt"></div>
<div class="panel-corner panel-corner-rt"></div>
<div class="panel-corner panel-corner-lb"></div>
<div class="panel-corner panel-corner-rb"></div>
</div>
</div>
<div class="main-right">
<!-- 告警分布图 -->
<div class="alarm-distribution">
<div class="distribution-right panel">
<div class="panel-header">
<div class="panel-title">最新活动告警</div>
<div
class="header-more"
@click="fnToRouter('HistoryAlarm_2097')"
>
更多
</div>
</div>
<div class="panel-content">
<div class="alarm-list">
<transition-group name="alarm-fade">
<div
v-for="(item, index) in latestAlarmsAct"
:key="item.alarmId || index"
class="alarm-item"
:class="[
item.origSeverity,
{ 'new-alarm': newActiveAlarms.has(item.alarmId) },
]"
>
<div class="alarm-time">{{ item.eventTime }}</div>
<a-tooltip
style="
max-width: 300px;
overflow: hidden;
display: block;
"
:title="item.neType + '-' + item.alarmTitle"
placement="top"
>
<div class="alarm-content">
{{ item.neType + '-' + item.alarmTitle }}
</div>
</a-tooltip>
<div class="alarm-level">{{ item.origSeverity }}</div>
</div>
</transition-group>
</div>
</div>
<div class="panel-corner panel-corner-lt"></div>
<div class="panel-corner panel-corner-rt"></div>
<div class="panel-corner panel-corner-lb"></div>
<div class="panel-corner panel-corner-rb"></div>
</div>
<div class="distribution-right panel">
<div class="panel-header">
<div class="panel-title">最新历史告警</div>
<div
class="header-more"
@click="fnToRouter('HistoryAlarm_2097')"
>
更多
</div>
</div>
<div class="panel-content">
<div class="alarm-list">
<transition-group name="alarm-fade">
<div
v-for="(item, index) in latestAlarmsHis"
:key="item.alarmId || index"
class="alarm-item"
:class="[
item.origSeverity,
{ 'new-alarm': newHistoryAlarms.has(item.alarmId) },
]"
>
<div class="alarm-time">{{ item.eventTime }}</div>
<a-tooltip
style="
max-width: 300px;
overflow: hidden;
display: block;
"
:title="item.neType + '-' + item.alarmTitle"
placement="top"
>
<div class="alarm-content">
{{ item.neType + '-' + item.alarmTitle }}
</div>
</a-tooltip>
<div class="alarm-level">{{ item.origSeverity }}</div>
</div>
</transition-group>
</div>
</div>
<div class="panel-corner panel-corner-lt"></div>
<div class="panel-corner panel-corner-rt"></div>
<div class="panel-corner panel-corner-lb"></div>
<div class="panel-corner panel-corner-rb"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="less">
@keyframes borderFlash {
0% {
box-shadow: 0 0 5px #0a73ff;
}
50% {
box-shadow: 0 0 15px #0a73ff;
}
100% {
box-shadow: 0 0 5px #0a73ff;
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.alarm-dashboard {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
color: #00fcff;
background-image: url(./images/bg.png);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 10, 50, 0.8);
z-index: 0;
}
}
.dashboard-header {
position: relative;
z-index: 1;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
.header-title {
font-size: 28px;
font-weight: bold;
color: #00fcff;
text-shadow: 0 0 10px rgba(0, 252, 255, 0.5);
position: relative;
&::before,
&::after {
content: '';
position: absolute;
top: 50%;
width: 100px;
height: 2px;
background: linear-gradient(to right, transparent, #0a73ff, transparent);
}
&::before {
right: 120%;
}
&::after {
left: 120%;
}
}
}
.dashboard-content {
position: relative;
z-index: 1;
flex: 1;
display: flex;
flex-direction: column;
padding: 10px 20px;
}
.alarm-overview {
display: flex;
justify-content: space-between;
gap: 15px;
margin-bottom: 20px;
}
.alarm-card {
flex: 1;
display: flex;
align-items: center;
padding: 15px;
border-radius: 4px;
background-color: rgba(0, 26, 87, 0.7);
box-shadow: 0 0 10px rgba(10, 115, 255, 0.3);
transition: all 0.3s;
position: relative;
overflow: hidden;
.card-border-top,
.card-border-right,
.card-border-bottom,
.card-border-left {
position: absolute;
background-color: #0a73ff;
}
.card-border-top,
.card-border-bottom {
height: 2px;
width: 30%;
}
.card-border-left,
.card-border-right {
width: 2px;
height: 30%;
}
.card-border-top {
top: 0;
left: 0;
}
.card-border-right {
top: 0;
right: 0;
}
.card-border-bottom {
bottom: 0;
right: 0;
}
.card-border-left {
bottom: 0;
left: 0;
}
&:hover {
transform: translateY(-3px);
box-shadow: 0 0 15px rgba(10, 115, 255, 0.5);
.card-border-top,
.card-border-right,
.card-border-bottom,
.card-border-left {
background-color: #00fcff;
}
}
.alarm-icon {
font-size: 28px;
margin-right: 15px;
}
.alarm-info {
.alarm-count {
font-size: 28px;
font-weight: bold;
line-height: 1.2;
}
.alarm-title {
font-size: 14px;
opacity: 0.9;
}
}
&.Critical {
.alarm-icon {
color: #f5222d;
}
}
&.Major {
.alarm-icon {
color: #fa8c16;
}
}
&.Minor {
.alarm-icon {
color: #faad14;
}
}
&.Warning {
.alarm-icon {
color: #52c41a;
}
}
}
.dashboard-main {
flex: 1;
display: flex;
gap: 20px;
}
.main-left {
flex: 2;
display: flex;
flex-direction: column;
}
.main-right {
flex: 1;
display: flex;
flex-direction: column;
}
.panel {
background-color: rgba(0, 26, 87, 0.7);
border: 1px solid rgba(10, 115, 255, 0.3);
border-radius: 4px;
box-shadow: 0 0 10px rgba(10, 115, 255, 0.3);
display: flex;
flex-direction: column;
position: relative;
margin-bottom: 20px;
.panel-corner {
position: absolute;
width: 10px;
height: 10px;
border-color: #0a73ff;
&.panel-corner-lt {
top: -1px;
left: -1px;
border-top: 2px solid;
border-left: 2px solid;
}
&.panel-corner-rt {
top: -1px;
right: -1px;
border-top: 2px solid;
border-right: 2px solid;
}
&.panel-corner-lb {
bottom: -1px;
left: -1px;
border-bottom: 2px solid;
border-left: 2px solid;
}
&.panel-corner-rb {
bottom: -1px;
right: -1px;
border-bottom: 2px solid;
border-right: 2px solid;
}
}
.panel-header {
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 15px;
border-bottom: 1px solid rgba(10, 115, 255, 0.3);
.panel-title {
font-size: 16px;
font-weight: bold;
color: #00fcff;
position: relative;
padding-left: 15px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background-color: #0a73ff;
}
}
}
.panel-content {
flex: 1;
width: 100%;
max-width: 100%;
padding: 15px;
overflow: hidden;
}
}
.alarm-trend {
flex: 1;
min-height: 300px;
.trend-chart {
width: 100%;
height: 100%;
}
}
.alarm-distribution {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
.distribution-left {
flex: 1;
min-height: 300px;
.type-chart {
width: 100%;
height: 100%;
}
}
.distribution-right {
flex: 1;
min-height: 300px;
min-width: 0;
max-width: 100%;
.header-more {
color: #00fcff;
cursor: pointer;
font-size: 14px;
&:hover {
text-decoration: underline;
}
}
.alarm-list {
height: calc(100% - 40px); // 固定高度,减去标题高度
max-height: 300px; // 设置最大高度
width: 100%;
max-width: 100%;
overflow-y: auto;
overflow-x: hidden; // 防止水平滚动
padding-right: 6px; // 为滚动条预留空间
margin-right: -2px; // 补偿滚动条空间
position: relative; // 确保滚动条位置固定
/* 始终显示滚动条,防止出现/消失导致的抖动 */
&::-webkit-scrollbar {
width: 4px;
background-color: rgba(0, 26, 87, 0.3);
}
&::-webkit-scrollbar-thumb {
background-color: rgba(10, 115, 255, 0.5);
border-radius: 2px;
}
&::-webkit-scrollbar-track {
background-color: rgba(0, 26, 87, 0.3);
border-radius: 2px;
}
.alarm-item {
display: flex;
justify-content: space-between;
align-items: center; // 新增这一行
padding: 0 10px 0 0; // 只保留右侧padding
border-radius: 2px;
margin-bottom: 8px;
background-color: rgba(0, 26, 87, 0.5);
border-left: 3px solid transparent;
transition: all 0.3s;
height: 40px; // 固定每个告警项的高度
box-sizing: border-box; // 确保padding不会增加元素实际高度
min-width: 0; //防止flex子项撑开
&:last-child {
margin-bottom: 0; // 最后一项不需要底部间距
}
&:hover {
background-color: rgba(10, 115, 255, 0.2);
transform: translateX(3px);
}
&.Critical {
border-left-color: #f5222d;
}
&.Major {
border-left-color: #fa8c16;
}
&.Minor {
border-left-color: #faad14;
}
&.Warning {
border-left-color: #52c41a;
}
.alarm-time {
flex: 0 0 100px;
padding-left: 10px; // 只给时间内容加左间距
color: #0a73ff;
min-width: 100px;
}
.alarm-content {
flex: 1;
margin: 0 10px;
min-width: 0;
max-width: 300px; // 限制最大宽度,和 tooltip max-width 保持一致
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.alarm-level {
flex: 0 0 50px;
text-align: right;
min-width: 40px;
}
}
}
}
}
.trend-range-radio {
margin-left: 16px;
.ant-radio-button-wrapper {
background: rgba(0, 26, 87, 0.7);
color: #00fcff;
border: 1px solid #0a73ff;
transition: all 0.2s;
font-weight: bold;
&:not(:last-child) {
border-right: none;
}
&:hover {
color: #fff;
background: #0a73ff;
border-color: #00fcff;
}
}
.ant-radio-button-wrapper-checked {
background: linear-gradient(90deg, #0a73ff 0%, #00fcff 100%);
color: #001a57;
border-color: #00fcff !important;
box-shadow: 0 0 8px #00fcff;
}
}
/* 确保新告警动画样式足够明显 */
.alarm-item.new-alarm {
position: relative;
z-index: 3;
animation: newAlarmAppear 1.2s ease-out;
box-shadow: 0 0 15px rgba(0, 252, 255, 0.7);
transform: translateX(0) !important;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
rgba(10, 115, 255, 0.2),
rgba(0, 252, 255, 0.3)
);
z-index: -1;
border-radius: 2px;
animation: gradientShift 3s infinite alternate;
}
&::after {
content: '';
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
border: 1px solid rgba(0, 252, 255, 0.7);
border-radius: 3px;
animation: borderPulse 1.5s infinite;
z-index: -1;
}
.alarm-content {
font-weight: bold;
color: #ffffff;
text-shadow: 0 0 5px rgba(0, 252, 255, 0.7);
}
.alarm-time,
.alarm-level {
color: #00fcff;
text-shadow: 0 0 3px rgba(0, 252, 255, 0.5);
}
}
/* 告警项目进入动画 */
.alarm-fade-enter-active {
transition: all 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.alarm-fade-leave-active {
transition: all 0.5s cubic-bezier(0.36, 0, 0.66, -0.56);
position: absolute;
width: 100%;
}
.alarm-fade-enter-from {
opacity: 0;
transform: translateX(-30px) scale(0.9);
}
.alarm-fade-leave-to {
opacity: 0;
transform: translateX(30px) scale(0.9);
}
/* 新告警出现动画 */
@keyframes newAlarmAppear {
0% {
transform: translateX(-20px) scale(0.95);
opacity: 0;
box-shadow: 0 0 0 rgba(0, 252, 255, 0);
}
30% {
transform: translateX(0) scale(1.05);
opacity: 1;
box-shadow: 0 0 20px rgba(0, 252, 255, 0.8);
}
100% {
transform: translateX(0) scale(1);
box-shadow: 0 0 15px rgba(0, 252, 255, 0.7);
}
}
/* 边框脉冲动画 */
@keyframes borderPulse {
0% {
box-shadow: 0 0 0 0 rgba(0, 252, 255, 0.7);
}
70% {
box-shadow: 0 0 0 5px rgba(0, 252, 255, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(0, 252, 255, 0);
}
}
/* 渐变背景动画 */
@keyframes gradientShift {
0% {
background: linear-gradient(
90deg,
rgba(10, 115, 255, 0.2),
rgba(0, 252, 255, 0.3)
);
}
100% {
background: linear-gradient(
90deg,
rgba(0, 252, 255, 0.3),
rgba(10, 115, 255, 0.2)
);
}
}
@media (max-width: 1400px) {
.dashboard-main {
flex-direction: column;
}
.main-left,
.main-right {
width: 100%;
}
.alarm-distribution {
flex-direction: row;
.distribution-left,
.distribution-right {
min-height: 250px;
}
}
}
@media (max-width: 768px) {
.alarm-overview {
flex-wrap: wrap;
.alarm-card {
flex: 0 0 calc(50% - 8px);
}
}
.alarm-distribution {
flex-direction: column;
}
}
</style>