2026-03-18 17:11:02 +08:00
|
|
|
|
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',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 11:42:50 +08:00
|
|
|
|
/** 告警趋势折线图(每种类型独立一条线,都从0开始) */
|
2026-03-18 17:11:02 +08:00
|
|
|
|
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',
|
2026-03-19 11:42:50 +08:00
|
|
|
|
axisPointer: { type: 'line' },
|
2026-03-18 17:11:02 +08:00
|
|
|
|
},
|
|
|
|
|
|
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,
|
2026-03-19 11:42:50 +08:00
|
|
|
|
symbol: 'circle',
|
|
|
|
|
|
symbolSize: 6,
|
2026-03-18 17:11:02 +08:00
|
|
|
|
emphasis: { focus: 'series' },
|
|
|
|
|
|
itemStyle: { color: TYPE_COLORS[type] },
|
2026-03-19 11:42:50 +08:00
|
|
|
|
lineStyle: { width: 2 },
|
2026-03-18 17:11:02 +08:00
|
|
|
|
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,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|