功能:告警数据看板替换原告警汇总页

- 5 个 KPI 卡片(今日告警/待处理/已处理/平均响应/在线设备)
- 告警趋势面积图(7天/30天切换,按类型堆叠)
- 告警类型分布环形图(中心显示总数)
- 设备告警 Top10 横向条形图
- 告警级别分布柱状图
- 24小时时段分布图(高亮高峰时段)
- 最近告警滚动列表
- 60秒自动刷新

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 17:11:02 +08:00
parent 58db3c7eb4
commit d2a77079af
3 changed files with 712 additions and 60 deletions

View File

@@ -0,0 +1,201 @@
import type { AiotAlarmApi } from '#/api/aiot/alarm';
const TYPE_NAMES: Record<string, string> = {
leave_post: '离岗检测',
intrusion: '周界入侵',
illegal_parking: '车辆违停',
vehicle_congestion: '车辆拥堵',
};
const TYPE_COLORS: Record<string, string> = {
leave_post: '#1890ff',
intrusion: '#f5222d',
illegal_parking: '#fa8c16',
vehicle_congestion: '#722ed1',
};
const LEVEL_NAMES: Record<number, string> = {
0: '紧急',
1: '重要',
2: '普通',
3: '轻微',
};
const LEVEL_COLORS: Record<number, string> = {
0: '#f5222d',
1: '#fa8c16',
2: '#1890ff',
3: '#8c8c8c',
};
/** 告警趋势面积图 */
export function getTrendChartOptions(data: AiotAlarmApi.TrendItem[]): any {
const dates = data.map((d) => d.date.slice(5)); // MM-DD
const types = Object.keys(TYPE_NAMES);
return {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } },
},
legend: {
data: types.map((t) => TYPE_NAMES[t]),
bottom: 0,
},
grid: { left: '3%', right: '4%', bottom: '12%', top: '8%', containLabel: true },
xAxis: {
type: 'category',
boundaryGap: false,
data: dates,
},
yAxis: { type: 'value', minInterval: 1 },
series: types.map((type) => ({
name: TYPE_NAMES[type],
type: 'line',
smooth: true,
stack: 'total',
areaStyle: { opacity: 0.25 },
emphasis: { focus: 'series' },
itemStyle: { color: TYPE_COLORS[type] },
data: data.map((d) => (d[type] as number) || 0),
})),
};
}
/** 告警类型环形图 */
export function getTypePieChartOptions(
byType: Record<string, number>,
): any {
const data = Object.entries(byType)
.map(([type, count]) => ({
name: TYPE_NAMES[type] || type,
value: count,
itemStyle: { color: TYPE_COLORS[type] },
}))
.filter((d) => d.value > 0);
const total = data.reduce((s, d) => s + d.value, 0);
return {
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { bottom: 0, left: 'center' },
graphic: {
type: 'text',
left: 'center',
top: '42%',
style: {
text: `${total}`,
fontSize: 28,
fontWeight: 'bold',
fill: '#333',
textAlign: 'center',
},
},
series: [
{
type: 'pie',
radius: ['45%', '70%'],
center: ['50%', '48%'],
avoidLabelOverlap: false,
label: { show: false },
data,
},
],
};
}
/** 设备告警 Top10 横向条形图 */
export function getDeviceTopChartOptions(
data: AiotAlarmApi.DeviceTopItem[],
): any {
const sorted = [...data].reverse(); // 最多的在上面
return {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: { left: '3%', right: '8%', bottom: '3%', top: '3%', containLabel: true },
xAxis: { type: 'value', minInterval: 1 },
yAxis: {
type: 'category',
data: sorted.map((d) => {
const name = d.deviceName || d.deviceId;
return name.length > 12 ? `${name.slice(0, 12)}...` : name;
}),
axisLabel: { fontSize: 11 },
},
series: [
{
type: 'bar',
data: sorted.map((d) => d.count),
itemStyle: {
color: {
type: 'linear',
x: 0, y: 0, x2: 1, y2: 0,
colorStops: [
{ offset: 0, color: '#1890ff' },
{ offset: 1, color: '#36cfc9' },
],
},
borderRadius: [0, 4, 4, 0],
},
barMaxWidth: 20,
label: { show: true, position: 'right', fontSize: 11 },
},
],
};
}
/** 告警级别分布柱状图 */
export function getLevelBarChartOptions(
byLevel: Record<string, number>,
): any {
const levels = [0, 1, 2, 3];
return {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: { left: '3%', right: '4%', bottom: '3%', top: '8%', containLabel: true },
xAxis: {
type: 'category',
data: levels.map((l) => LEVEL_NAMES[l]),
},
yAxis: { type: 'value', minInterval: 1 },
series: [
{
type: 'bar',
data: levels.map((l) => ({
value: byLevel[l] || 0,
itemStyle: { color: LEVEL_COLORS[l], borderRadius: [4, 4, 0, 0] },
})),
barMaxWidth: 40,
label: { show: true, position: 'top', fontSize: 11 },
},
],
};
}
/** 24小时时段分布柱状图 */
export function getHourDistChartOptions(
data: AiotAlarmApi.HourDistItem[],
): any {
const counts = data.map((d) => d.count);
const maxCount = Math.max(...counts, 1);
return {
tooltip: { trigger: 'axis', formatter: '{b}时: {c}次' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '8%', containLabel: true },
xAxis: {
type: 'category',
data: data.map((d) => `${d.hour}`),
axisLabel: { formatter: '{value}时', fontSize: 10 },
},
yAxis: { type: 'value', minInterval: 1 },
series: [
{
type: 'bar',
data: counts.map((c) => ({
value: c,
itemStyle: {
color: c >= maxCount * 0.8 ? '#f5222d' : c >= maxCount * 0.5 ? '#fa8c16' : '#1890ff',
borderRadius: [3, 3, 0, 0],
},
})),
barMaxWidth: 16,
},
],
};
}