Files
ljwx-health/ljwx-bigscreen-fastapi/templates/alert_test.html
T
BrunoGaoandClaude 18377b7b89 feat: 新增Ubuntu 22.04部署初始化脚本和FastAPI重构
## 新增功能

### Ubuntu 22.04初始化脚本
- 🚀 完整的系统环境初始化脚本 `ubuntu-22.04-init.sh`
- 🛠️ 支持Docker、Java 21、Node.js、Python3、MySQL、Redis等全套环境
- 🔧 自动配置防火墙、系统参数优化
- 📁 标准化项目目录结构 `/opt/ljwx`
- 📋 生成配置文件模板和维护脚本

### ljwx-bigscreen FastAPI重构
- 🏗️ 新的FastAPI架构设计,支持异步高性能
- 📊 完整的业务逻辑分层:API -> Service -> Repository
- 🎯 支持大屏主界面、个人界面、健康数据可视化
- 🔄 Redis缓存集成和数据库连接池
- 🎨 保留所有原有HTML模板和静态资源

### 文档完善
- 📖 新增FastAPI重构进度文档
- 🏗️ 详细的项目架构分析
- 📋 完整的部署组件清单和依赖关系

## 技术亮点

- **系统兼容性**: 专为Ubuntu 22.04优化的完整部署方案
- **架构现代化**: FastAPI异步框架替代传统Flask同步架构
- **企业级部署**: 13个服务组件的完整CI/CD + 业务系统
- **多架构支持**: linux/amd64 + linux/arm64双架构Docker支持

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-19 13:18:59 +08:00

433 lines
18 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>告警图表测试</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
body {
margin: 0;
padding: 20px;
background: #001529;
color: #fff;
font-family: Arial, sans-serif;
}
.container {
width: 350px;
height: 400px;
background: rgba(0, 21, 41, 0.7);
border: 1px solid rgba(0, 228, 255, 0.2);
border-radius: 4px;
padding: 12px;
}
.panel-header {
color: #00e4ff;
font-size: 16px;
margin-bottom: 10px;
}
.panel-content {
height: calc(100% - 30px);
}
#alertList {
width: 100%;
height: 100%;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
}
.alert-stat-item {
display: flex;
align-items: baseline;
transition: all 0.3s ease;
}
.alert-stat-item:hover {
transform: translateY(-2px);
}
#alertBadge {
cursor: pointer;
transition: all 0.3s ease;
}
#alertBadge:hover {
transform: scale(1.1);
box-shadow: 0 0 10px rgba(0,228,255,0.5);
}
</style>
</head>
<body>
<h1>告警图表优化效果测试</h1>
<div class="container">
<div class="panel-header">
<span>告警信息</span>
</div>
<div class="panel-content">
<div id="alertList"></div>
</div>
</div>
<script>
// 告警类型翻译
const ALERT_TYPE_MAP = {
heart_rate: '心率',
blood_pressure: '血压',
stress: '压力',
blood_oxygen: '血氧',
temperature: '体温'
};
function translateAlertType(type) {
return ALERT_TYPE_MAP[type] || type;
}
// 告警类型颜色
const ALERT_TYPE_COLOR = {
heart_rate: '#ff6b6b',
blood_pressure: '#ffb347',
stress: '#f7b731',
blood_oxygen: '#00e4ff',
temperature: '#ffd700'
};
function getAlertTypeColor(type) {
return ALERT_TYPE_COLOR[type] || '#7ecfff';
}
// 模拟告警数据
const mockAlertData = {
alert_info: {
alertLevelCount: {
critical: 1,
medium: 8
},
alertStatusCount: {
pending: 1,
responded: 8
},
alertTypeCount: {
blood_oxygen: 1,
blood_pressure: 1,
heart_rate: 1,
stress: 4,
temperature: 2
},
alerts: [
{
alert_desc: "体温低于35或者高于37.5告警",
alert_id: "1209",
alert_status: "responded",
alert_timestamp: "2025-02-14 19:35:31",
alert_type: "temperature",
dept_id: "1",
dept_name: "煤矿集团总部",
device_sn: "A5GTQ24603000537",
health_id: "240014",
severity_level: "critical",
user_id: "1877884715116072962",
user_name: "user7"
},
{
alert_desc: "",
alert_id: "1921075162350915586",
alert_status: "pending",
alert_timestamp: "2025-05-10 13:29:45",
alert_type: "stress",
dept_id: "1",
dept_name: "煤矿集团总部",
device_sn: "A5GTQ24A17000135",
severity_level: "medium",
user_id: "1904782222680711169",
user_name: "test1"
}
]
}
};
// 初始化告警信息图表 - 专业版
function initAlertChart(data) {
const alertContainer = document.getElementById('alertList');
if (!alertContainer) return;
// 清空容器并创建专业布局
alertContainer.innerHTML = `
<div style="position: relative; height: 100%; padding: 8px;">
<!-- 告警状态总览 -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding: 8px 12px; background: rgba(0,228,255,0.1); border-radius: 6px; border-left: 4px solid #00e4ff;">
<div style="display: flex; align-items: center; gap: 15px;">
<div class="alert-stat-item">
<span style="color: #ff4444; font-size: 20px; font-weight: bold;" id="criticalCount">0</span>
<span style="color: #fff; font-size: 12px; margin-left: 4px;">严重</span>
</div>
<div class="alert-stat-item">
<span style="color: #ffbb00; font-size: 18px; font-weight: bold;" id="mediumCount">0</span>
<span style="color: #fff; font-size: 12px; margin-left: 4px;">中等</span>
</div>
<div class="alert-stat-item">
<span style="color: #ff6666; font-size: 16px; font-weight: bold;" id="pendingCount">0</span>
<span style="color: #fff; font-size: 12px; margin-left: 4px;">待处理</span>
</div>
</div>
<div style="background: rgba(255,68,68,0.2); padding: 4px 8px; border-radius: 12px; animation: pulse 2s infinite;" id="alertBadge">
<span style="color: #ff4444; font-size: 11px; font-weight: bold;">🚨 实时监控</span>
</div>
</div>
<!-- 图表区域 -->
<div style="display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 8px; height: calc(100% - 60px);">
<div id="alertTypeChart" style="background: rgba(0,21,41,0.4); border: 1px solid rgba(0,228,255,0.3); border-radius: 6px; position: relative;">
<div style="position: absolute; top: 5px; left: 8px; color: #00e4ff; font-size: 12px; font-weight: bold; z-index: 10;">告警类型分布</div>
</div>
<div id="alertLevelChart" style="background: rgba(0,21,41,0.4); border: 1px solid rgba(0,228,255,0.3); border-radius: 6px; position: relative;">
<div style="position: absolute; top: 5px; left: 8px; color: #00e4ff; font-size: 12px; font-weight: bold; z-index: 10;">严重程度分析</div>
</div>
<div id="alertStatusChart" style="background: rgba(0,21,41,0.4); border: 1px solid rgba(0,228,255,0.3); border-radius: 6px; position: relative;">
<div style="position: absolute; top: 5px; left: 8px; color: #00e4ff; font-size: 12px; font-weight: bold; z-index: 10;">处理状态</div>
</div>
<div id="alertTrendChart" style="background: rgba(0,21,41,0.4); border: 1px solid rgba(0,228,255,0.3); border-radius: 6px; position: relative;">
<div style="position: absolute; top: 5px; left: 8px; color: #00e4ff; font-size: 12px; font-weight: bold; z-index: 10;">24小时趋势</div>
</div>
</div>
</div>
`;
const alertInfo = data.alert_info || {};
const alerts = alertInfo.alerts || [];
// 更新统计数据
const criticalCount = alertInfo.alertLevelCount?.critical || 0;
const mediumCount = alertInfo.alertLevelCount?.medium || 0;
const pendingCount = alertInfo.alertStatusCount?.pending || 0;
document.getElementById('criticalCount').textContent = criticalCount;
document.getElementById('mediumCount').textContent = mediumCount;
document.getElementById('pendingCount').textContent = pendingCount;
// 更新告警徽章
const badge = document.getElementById('alertBadge');
if (criticalCount > 0) {
badge.innerHTML = '<span style="color: #ff4444; font-size: 11px; font-weight: bold;">🔴 严重告警</span>';
badge.style.background = 'rgba(255,68,68,0.3)';
} else if (pendingCount > 0) {
badge.innerHTML = '<span style="color: #ffbb00; font-size: 11px; font-weight: bold;">⚠️ 待处理</span>';
badge.style.background = 'rgba(255,187,0,0.3)';
} else {
badge.innerHTML = '<span style="color: #00ff9d; font-size: 11px; font-weight: bold;">✅ 正常</span>';
badge.style.background = 'rgba(0,255,157,0.3)';
}
// 1. 告警类型分布图
const typeChart = echarts.init(document.getElementById('alertTypeChart'));
const alertTypes = Object.keys(alertInfo.alertTypeCount || {});
const alertValues = Object.values(alertInfo.alertTypeCount || {});
const typeOption = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,21,41,0.95)',
borderColor: '#00e4ff',
borderWidth: 1,
textStyle: { color: '#fff', fontSize: 11 }
},
grid: { top: 25, left: 45, right: 15, bottom: 15 },
xAxis: {
type: 'value',
axisLabel: { color: '#7ecfff', fontSize: 10 },
splitLine: { lineStyle: { color: 'rgba(126,207,255,0.1)' } },
axisLine: { show: false }
},
yAxis: {
type: 'category',
data: alertTypes.map(t => translateAlertType(t)),
axisLabel: { color: '#fff', fontSize: 10 },
axisLine: { show: false },
axisTick: { show: false }
},
series: [{
type: 'bar',
data: alertTypes.map((type, index) => ({
value: alertValues[index],
itemStyle: {
color: getAlertTypeColor(type),
borderRadius: [0, 3, 3, 0]
}
})),
barWidth: '65%',
label: {
show: true,
position: 'right',
color: '#fff',
fontSize: 10,
formatter: '{c}'
}
}]
};
typeChart.setOption(typeOption);
// 2. 告警级别分布图
const levelChart = echarts.init(document.getElementById('alertLevelChart'));
const levelData = Object.entries(alertInfo.alertLevelCount || {}).map(([level, count]) => ({
name: level === 'critical' ? '严重' : '中等',
value: count,
itemStyle: {
color: level === 'critical' ? '#ff4444' : '#ffbb00'
}
}));
const levelOption = {
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(0,21,41,0.95)',
borderColor: '#00e4ff',
textStyle: { color: '#fff', fontSize: 11 }
},
series: [{
type: 'pie',
radius: ['35%', '65%'],
center: ['50%', '55%'],
data: levelData,
label: {
show: true,
position: 'outside',
color: '#fff',
fontSize: 10,
formatter: function(params) {
return `${params.name}\n${params.value}次`;
}
}
}]
};
levelChart.setOption(levelOption);
// 3. 告警状态仪表盘
const statusChart = echarts.init(document.getElementById('alertStatusChart'));
const totalAlerts = (alertInfo.alertStatusCount?.pending || 0) + (alertInfo.alertStatusCount?.responded || 0);
const pendingPercent = totalAlerts > 0 ? ((alertInfo.alertStatusCount?.pending || 0) / totalAlerts * 100).toFixed(1) : 0;
const statusOption = {
series: [{
type: 'gauge',
radius: '75%',
center: ['50%', '55%'],
startAngle: 180,
endAngle: 0,
min: 0,
max: 100,
axisLine: {
lineStyle: {
width: 8,
color: [
[0.3, '#00e4ff'],
[0.7, '#ffbb00'],
[1, '#ff4444']
]
}
},
pointer: {
icon: 'circle',
length: '60%',
width: 4,
itemStyle: { color: '#fff' }
},
axisTick: { show: false },
splitLine: { show: false },
axisLabel: {
show: true,
distance: -25,
color: '#7ecfff',
fontSize: 9,
formatter: function(value) {
if (value === 0) return '正常';
if (value === 50) return '中等';
if (value === 100) return '严重';
return '';
}
},
detail: {
valueAnimation: true,
formatter: `{value|${pendingPercent}%}\n{name|待处理率}`,
rich: {
value: {
fontSize: 16,
fontWeight: 'bold',
color: pendingPercent > 70 ? '#ff4444' : pendingPercent > 30 ? '#ffbb00' : '#00e4ff'
},
name: {
fontSize: 10,
color: '#7ecfff',
padding: [5, 0, 0, 0]
}
},
offsetCenter: [0, '20%']
},
data: [{ value: pendingPercent }]
}]
};
statusChart.setOption(statusOption);
// 4. 24小时趋势图
const trendChart = echarts.init(document.getElementById('alertTrendChart'));
const hourlyData = {};
alerts.forEach(alert => {
const hour = new Date(alert.alert_timestamp).getHours();
hourlyData[hour] = (hourlyData[hour] || 0) + 1;
});
const hours = Array.from({length: 24}, (_, i) => i);
const hourlyValues = hours.map(h => hourlyData[h] || 0);
const trendOption = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,21,41,0.95)',
borderColor: '#00e4ff',
textStyle: { color: '#fff', fontSize: 11 }
},
grid: { top: 25, left: 25, right: 15, bottom: 20 },
xAxis: {
type: 'category',
data: hours.map(h => h.toString().padStart(2, '0')),
axisLabel: { color: '#7ecfff', fontSize: 9, interval: 3 },
axisLine: { lineStyle: { color: 'rgba(126,207,255,0.3)' } }
},
yAxis: {
type: 'value',
axisLabel: { color: '#7ecfff', fontSize: 9 },
splitLine: { lineStyle: { color: 'rgba(126,207,255,0.1)', type: 'dashed' } }
},
series: [{
type: 'line',
data: hourlyValues,
smooth: true,
lineStyle: { color: '#00e4ff', width: 2 },
itemStyle: { color: '#00e4ff' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(0,228,255,0.4)' },
{ offset: 1, color: 'rgba(0,228,255,0.05)' }
])
},
symbol: 'circle',
symbolSize: 4
}]
};
trendChart.setOption(trendOption);
return { typeChart, levelChart, statusChart, trendChart };
}
// 初始化图表
document.addEventListener('DOMContentLoaded', () => {
initAlertChart(mockAlertData);
});
</script>
</body>
</html>