Files
ljwx-health/ljwx-bigscreen-fastapi/templates/device_dashboard.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

1029 lines
37 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能手表设备监控分析中心</title>
<script src="{{ url_for('static', filename='js/echarts.min.js') }}"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
background: linear-gradient(135deg, #0c1426 0%, #1a2332 50%, #0f1419 100%);
color: #fff;
overflow: hidden;
height: 100vh;
}
/* 大屏容器 */
.dashboard-container {
display: grid;
grid-template-rows: 80px 200px 1fr;
height: 100vh;
gap: 10px;
padding: 10px;
}
/* 顶部标题栏 */
.header {
background: linear-gradient(135deg, rgba(0,228,255,0.1) 0%, rgba(0,168,255,0.05) 100%);
border: 1px solid rgba(0,228,255,0.3);
border-radius: 12px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30px;
backdrop-filter: blur(10px);
box-shadow: 0 4px 20px rgba(0,228,255,0.1);
}
.header h1 {
color: #00e4ff;
font-size: 24px;
font-weight: 600;
text-shadow: 0 0 20px rgba(0,228,255,0.5);
display: flex;
align-items: center;
gap: 15px;
}
.header-controls {
display: flex;
align-items: center;
gap: 15px;
}
.header-time {
font-family: 'Courier New', monospace;
font-size: 16px;
color: #00e4ff;
text-shadow: 0 0 10px rgba(0,228,255,0.3);
}
/* 统计总览区域 */
.stats-overview {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 15px;
height: 100%;
}
.stat-card {
background: linear-gradient(135deg, rgba(1,19,38,0.8) 0%, rgba(0,33,64,0.6) 100%);
border: 1px solid rgba(0,228,255,0.2);
border-radius: 12px;
padding: 20px;
text-align: center;
position: relative;
transition: all 0.3s ease;
overflow: hidden;
}
.stat-card:hover {
border-color: rgba(0,228,255,0.4);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0,228,255,0.15);
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #00e4ff, #0099ff);
opacity: 0.8;
}
.stat-icon {
font-size: 24px;
margin-bottom: 8px;
opacity: 0.8;
}
.stat-number {
font-size: 28px;
font-weight: bold;
margin-bottom: 5px;
text-shadow: 0 0 10px currentColor;
}
.stat-label {
font-size: 12px;
opacity: 0.7;
margin-bottom: 3px;
}
.stat-change {
font-size: 11px;
opacity: 0.6;
}
.stat-positive { color: #52c41a; }
.stat-negative { color: #ff4d4f; }
.stat-neutral { color: #faad14; }
.stat-info { color: #1890ff; }
.stat-warning { color: #fa8c16; }
.stat-primary { color: #00e4ff; }
/* 主内容区域 */
.main-content {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 15px;
height: 100%;
}
/* 左侧图表区域 */
.charts-area {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 15px;
}
/* 右侧详情区域 */
.details-area {
display: grid;
grid-template-rows: 1fr 1fr;
gap: 15px;
}
/* 图表容器 */
.chart-panel {
background: linear-gradient(135deg, rgba(1,19,38,0.8) 0%, rgba(0,33,64,0.6) 100%);
border: 1px solid rgba(0,228,255,0.2);
border-radius: 12px;
padding: 20px;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.chart-panel:hover {
border-color: rgba(0,228,255,0.4);
box-shadow: 0 8px 25px rgba(0,228,255,0.15);
}
.chart-panel::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #00e4ff, #0099ff);
opacity: 0.8;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.chart-title {
color: #00e4ff;
font-size: 16px;
font-weight: 600;
text-shadow: 0 0 10px rgba(0,228,255,0.3);
display: flex;
align-items: center;
gap: 8px;
}
.chart-subtitle {
font-size: 12px;
color: #7ecfff;
opacity: 0.8;
}
.chart-content {
width: 100%;
height: calc(100% - 60px);
}
/* 列表面板 */
.list-panel {
background: linear-gradient(135deg, rgba(1,19,38,0.8) 0%, rgba(0,33,64,0.6) 100%);
border: 1px solid rgba(0,228,255,0.2);
border-radius: 12px;
padding: 20px;
position: relative;
overflow: hidden;
}
.list-panel::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #00e4ff, #0099ff);
opacity: 0.8;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
border-bottom: 1px solid rgba(0,228,255,0.1);
padding-bottom: 10px;
}
.list-title {
color: #00e4ff;
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.list-content {
height: calc(100% - 60px);
overflow-y: auto;
}
.list-item {
padding: 8px 0;
border-bottom: 1px solid rgba(0,228,255,0.05);
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.2s ease;
}
.list-item:hover {
background: rgba(0,228,255,0.05);
padding-left: 10px;
margin-left: -10px;
margin-right: -10px;
padding-right: 10px;
}
.list-item-label {
font-size: 13px;
color: #fff;
}
.list-item-value {
font-size: 13px;
font-weight: bold;
}
.list-item-status {
padding: 2px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: bold;
}
.status-online { background: rgba(82, 196, 26, 0.2); color: #52c41a; }
.status-offline { background: rgba(255, 77, 79, 0.2); color: #ff4d4f; }
.status-charging { background: rgba(24, 144, 255, 0.2); color: #1890ff; }
.status-low-battery { background: rgba(250, 140, 22, 0.2); color: #fa8c16; }
.status-worn { background: rgba(250, 173, 20, 0.2); color: #faad14; }
/* 实时数据流效果 */
.data-stream {
position: absolute;
top: 0;
right: 0;
width: 100px;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(0,228,255,0.1), transparent);
animation: dataFlow 3s linear infinite;
pointer-events: none;
}
@keyframes dataFlow {
0% { transform: translateX(-100px); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateX(100%); opacity: 0; }
}
/* 告警闪烁效果 */
.alert-blink {
animation: alertBlink 1s ease-in-out infinite alternate;
}
@keyframes alertBlink {
0% { opacity: 0.6; }
100% { opacity: 1; }
}
/* 滚动条样式 */
.list-content::-webkit-scrollbar {
width: 4px;
}
.list-content::-webkit-scrollbar-track {
background: rgba(0,228,255,0.1);
}
.list-content::-webkit-scrollbar-thumb {
background: rgba(0,228,255,0.3);
border-radius: 2px;
}
.list-content::-webkit-scrollbar-thumb:hover {
background: rgba(0,228,255,0.5);
}
/* 时间选择器 */
.time-selector {
display: flex;
gap: 8px;
background: rgba(0,228,255,0.1);
border-radius: 8px;
padding: 4px;
}
.time-btn {
padding: 6px 12px;
border: none;
background: transparent;
color: #7ecfff;
font-size: 12px;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease;
}
.time-btn.active {
background: rgba(0,228,255,0.3);
color: #00e4ff;
}
.time-btn:hover {
background: rgba(0,228,255,0.2);
color: #00e4ff;
}
/* 自动刷新指示器 */
.refresh-indicator {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: #7ecfff;
}
.refresh-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #52c41a;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* 响应式设计 */
@media (max-width: 1920px) {
.header h1 { font-size: 20px; }
.stat-number { font-size: 24px; }
.chart-title { font-size: 14px; }
}
@media (max-width: 1366px) {
.main-content { grid-template-columns: 1fr; }
.charts-area { grid-template-columns: 1fr 1fr; }
.stats-overview { grid-template-columns: repeat(3, 1fr); }
}
</style>
</head>
<body>
<div class="dashboard-container">
<!-- 顶部标题栏 -->
<div class="header">
<h1>
<span style="font-size: 28px;">⌚</span>
智能手表设备监控分析中心
</h1>
<div class="header-controls">
<div class="time-selector">
<button class="time-btn" onclick="changeTimeRange('1h')">1小时</button>
<button class="time-btn" onclick="changeTimeRange('6h')">6小时</button>
<button class="time-btn active" onclick="changeTimeRange('24h')">24小时</button>
<button class="time-btn" onclick="changeTimeRange('7d')">7天</button>
</div>
<div class="refresh-indicator">
<div class="refresh-dot"></div>
<span>自动刷新</span>
</div>
<div class="header-time" id="currentTime"></div>
</div>
</div>
<!-- 统计总览区域 -->
<div class="stats-overview">
<div class="stat-card">
<div class="stat-icon">📱</div>
<div class="stat-number stat-primary" id="totalDevices">0</div>
<div class="stat-label">设备总数</div>
<div class="stat-change" id="totalDevicesChange">较昨日 --</div>
</div>
<div class="stat-card">
<div class="stat-icon">🟢</div>
<div class="stat-number stat-positive" id="onlineDevices">0</div>
<div class="stat-label">在线设备</div>
<div class="stat-change" id="onlineRate">在线率 --</div>
</div>
<div class="stat-card">
<div class="stat-icon">🔋</div>
<div class="stat-number stat-warning" id="lowBatteryDevices">0</div>
<div class="stat-label">低电量设备</div>
<div class="stat-change" id="batteryHealth">电池健康度 --</div>
</div>
<div class="stat-card">
<div class="stat-icon">⚡</div>
<div class="stat-number stat-info" id="chargingDevices">0</div>
<div class="stat-label">充电设备</div>
<div class="stat-change" id="chargingRate">充电率 --</div>
</div>
<div class="stat-card">
<div class="stat-icon">👤</div>
<div class="stat-number stat-neutral" id="wornDevices">0</div>
<div class="stat-label">佩戴设备</div>
<div class="stat-change" id="wearRate">佩戴率 --</div>
</div>
<div class="stat-card">
<div class="stat-icon">🚨</div>
<div class="stat-number stat-negative alert-blink" id="alertCount">0</div>
<div class="stat-label">设备告警</div>
<div class="stat-change" id="alertTrend">告警趋势 --</div>
</div>
</div>
<!-- 主内容区域 -->
<div class="main-content">
<!-- 左侧图表区域 -->
<div class="charts-area">
<!-- 电池状态趋势图 -->
<div class="chart-panel">
<div class="chart-header">
<div>
<div class="chart-title">🔋 电池状态趋势</div>
<div class="chart-subtitle">Battery Status Trends</div>
</div>
</div>
<div class="chart-content" id="batteryTrendChart"></div>
</div>
<!-- 设备在线状态变化 -->
<div class="chart-panel">
<div class="chart-header">
<div>
<div class="chart-title">📊 设备状态分布</div>
<div class="chart-subtitle">Device Status Distribution</div>
</div>
</div>
<div class="chart-content" id="deviceStatusChart"></div>
</div>
<!-- 充电状态分析 -->
<div class="chart-panel">
<div class="chart-header">
<div>
<div class="chart-title">⚡ 充电行为分析</div>
<div class="chart-subtitle">Charging Behavior Analysis</div>
</div>
</div>
<div class="chart-content" id="chargingAnalysisChart"></div>
</div>
<!-- 佩戴状态统计 -->
<div class="chart-panel">
<div class="chart-header">
<div>
<div class="chart-title">👥 佩戴状态统计</div>
<div class="chart-subtitle">Wearing Status Statistics</div>
</div>
</div>
<div class="chart-content" id="wearingStatusChart"></div>
</div>
</div>
<!-- 右侧详情区域 -->
<div class="details-area">
<!-- 设备详情列表 -->
<div class="list-panel">
<div class="list-header">
<div class="list-title">
<span>🏷️</span>
设备详情
</div>
<span style="font-size: 12px; color: #7ecfff;">共 <span id="deviceListCount">0</span> 台设备</span>
</div>
<div class="list-content" id="deviceList">
<!-- 设备列表将通过JS动态生成 -->
</div>
</div>
<!-- 系统版本分布 -->
<div class="list-panel">
<div class="list-header">
<div class="list-title">
<span>💻</span>
系统版本分布
</div>
<span style="font-size: 12px; color: #7ecfff;">版本统计</span>
</div>
<div class="list-content" id="versionList">
<!-- 版本列表将通过JS动态生成 -->
</div>
</div>
</div>
</div>
<!-- 数据流效果 -->
<div class="data-stream"></div>
</div>
<script>
const charts = {};
let currentData = null;
let currentTimeRange = '24h';
// 初始化图表
function initCharts() {
const chartConfigs = [
{ id: 'batteryTrendChart', title: '电池状态趋势' },
{ id: 'deviceStatusChart', title: '设备状态分布' },
{ id: 'chargingAnalysisChart', title: '充电行为分析' },
{ id: 'wearingStatusChart', title: '佩戴状态统计' }
];
chartConfigs.forEach(config => {
charts[config.id] = echarts.init(document.getElementById(config.id));
});
}
// 获取设备数据
async function fetchDeviceData() {
try {
const response = await fetch(`/api/devices/analysis?orgId=1&timeRange=${currentTimeRange}`);
const result = await response.json();
return result.success ? result.data : null;
} catch (error) {
console.error('获取设备数据失败:', error);
// 返回模拟数据以确保页面正常显示
return generateMockData();
}
}
// 生成模拟数据(当API失败时使用)
function generateMockData() {
return {
devices: Array.from({length: 50}, (_, i) => ({
deviceSn: `Device_${String(i + 1).padStart(3, '0')}`,
status: Math.random() > 0.2 ? 'ACTIVE' : 'OFFLINE',
batteryLevel: Math.floor(Math.random() * 80) + 20,
chargingStatus: Math.random() > 0.8 ? 'CHARGING' : 'NOT_CHARGING',
wearableStatus: Math.random() > 0.4 ? 'WORN' : 'NOT_WORN',
lastSeen: new Date().toISOString(),
version: `v2.${Math.floor(Math.random() * 5)}.${Math.floor(Math.random() * 10)}`
})),
statistics: {
totalDevices: 1076,
onlineDevices: 1076,
chargingDevices: 0,
wornDevices: 544,
lowBatteryDevices: 20,
alertCount: 20
},
trends: {
deviceChange: '+5台',
onlineRate: '100.0%',
batteryHealth: '87%',
chargingRate: '0.0%',
wearRate: '50.6%',
alertTrend: '稳定'
},
statusDistribution: {
online: 1076,
offline: 0,
standby: 0,
error: 0
},
batteryTrends: generateTimeSeriesData('battery'),
chargingAnalysis: generateChargingData(),
wearingTrends: generateTimeSeriesData('wearing'),
versionDistribution: {
'v2.1.0': 300,
'v2.0.8': 250,
'v2.0.6': 200,
'v1.9.2': 180,
'v1.8.5': 146
}
};
}
// 生成时间序列数据
function generateTimeSeriesData(type) {
const points = [];
const now = new Date();
for (let i = 11; i >= 0; i--) {
const time = new Date(now - i * 2 * 60 * 60 * 1000);
const timeStr = `${String(time.getMonth() + 1).padStart(2, '0')}-${String(time.getDate()).padStart(2, '0')} ${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`;
if (type === 'battery') {
points.push({
time: timeStr,
avgBattery: 75 + Math.floor(Math.random() * 20) - i,
lowBatteryCount: Math.floor(Math.random() * 25)
});
} else if (type === 'wearing') {
const hour = time.getHours();
const baseRate = (8 <= hour && hour <= 18) ? 75 : 45;
points.push({
time: timeStr,
wornRate: baseRate + Math.floor(Math.random() * 20),
activeRate: (baseRate + Math.floor(Math.random() * 20)) * 0.8
});
}
}
return points;
}
// 生成充电数据
function generateChargingData() {
const data = [];
for (let hour = 0; hour < 24; hour++) {
const peakFactor = (22 <= hour || hour <= 6) ? 1.5 : 1.0;
data.push({
hour: `${String(hour).padStart(2, '0')}:00`,
chargingCount: Math.floor(50 * peakFactor * Math.random()),
completedCount: Math.floor(30 * peakFactor * Math.random())
});
}
return data;
}
// 更新统计卡片
function updateStatCards(data) {
const { devices, statistics, trends } = data;
document.getElementById('totalDevices').textContent = statistics.totalDevices || 0;
document.getElementById('onlineDevices').textContent = statistics.onlineDevices || 0;
document.getElementById('lowBatteryDevices').textContent = statistics.lowBatteryDevices || 0;
document.getElementById('chargingDevices').textContent = statistics.chargingDevices || 0;
document.getElementById('wornDevices').textContent = statistics.wornDevices || 0;
document.getElementById('alertCount').textContent = statistics.alertCount || 0;
// 更新变化趋势
document.getElementById('totalDevicesChange').textContent = `较昨日 ${trends.deviceChange || '--'}`;
document.getElementById('onlineRate').textContent = `在线率 ${trends.onlineRate || '--'}`;
document.getElementById('batteryHealth').textContent = `电池健康度 ${trends.batteryHealth || '--'}`;
document.getElementById('chargingRate').textContent = `充电率 ${trends.chargingRate || '--'}`;
document.getElementById('wearRate').textContent = `佩戴率 ${trends.wearRate || '--'}`;
document.getElementById('alertTrend').textContent = `告警趋势 ${trends.alertTrend || '--'}`;
// 添加告警闪烁效果
const alertElement = document.getElementById('alertCount');
if (statistics.alertCount > 0) {
alertElement.classList.add('alert-blink');
} else {
alertElement.classList.remove('alert-blink');
}
}
// 更新电池趋势图
function updateBatteryTrendChart(data) {
const { batteryTrends } = data;
const timeLabels = batteryTrends.map(item => item.time);
const avgBattery = batteryTrends.map(item => item.avgBattery);
const lowBatteryCount = batteryTrends.map(item => item.lowBatteryCount);
charts.batteryTrendChart.setOption({
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.8)',
borderColor: '#00e4ff',
textStyle: { color: '#fff' }
},
legend: {
data: ['平均电量', '低电量设备数'],
textStyle: { color: '#fff' }
},
grid: { left: '10%', right: '10%', bottom: '15%', top: '20%' },
xAxis: {
type: 'category',
data: timeLabels,
axisLabel: { color: '#7ecfff', fontSize: 10 },
axisLine: { lineStyle: { color: '#00e4ff' } }
},
yAxis: [
{
type: 'value',
name: '电量(%)',
position: 'left',
axisLabel: { color: '#7ecfff' },
splitLine: { lineStyle: { color: 'rgba(0,228,255,0.1)' } }
},
{
type: 'value',
name: '设备数',
position: 'right',
axisLabel: { color: '#7ecfff' }
}
],
series: [
{
name: '平均电量',
type: 'line',
yAxisIndex: 0,
data: avgBattery,
itemStyle: { color: '#52c41a' },
areaStyle: { color: 'rgba(82,196,26,0.1)' },
smooth: true
},
{
name: '低电量设备数',
type: 'bar',
yAxisIndex: 1,
data: lowBatteryCount,
itemStyle: { color: '#fa8c16' }
}
]
});
}
// 更新设备状态分布图
function updateDeviceStatusChart(data) {
const { statusDistribution } = data;
charts.deviceStatusChart.setOption({
tooltip: {
trigger: 'item',
formatter: '{b}: {c}台 ({d}%)',
backgroundColor: 'rgba(0,0,0,0.8)',
borderColor: '#00e4ff',
textStyle: { color: '#fff' }
},
legend: {
orient: 'vertical',
left: 'left',
textStyle: { color: '#fff', fontSize: 11 }
},
series: [{
type: 'pie',
radius: ['40%', '70%'],
center: ['65%', '50%'],
data: [
{ name: '在线', value: statusDistribution.online, itemStyle: { color: '#52c41a' } },
{ name: '离线', value: statusDistribution.offline, itemStyle: { color: '#f5222d' } },
{ name: '待机', value: statusDistribution.standby, itemStyle: { color: '#faad14' } },
{ name: '故障', value: statusDistribution.error, itemStyle: { color: '#ff4d4f' } }
],
label: { show: false },
emphasis: {
itemStyle: {
shadowBlur: 20,
shadowColor: 'rgba(0,228,255,0.5)'
}
}
}]
});
}
// 更新充电分析图
function updateChargingAnalysisChart(data) {
const { chargingAnalysis } = data;
const hours = chargingAnalysis.map(item => item.hour);
const chargingCount = chargingAnalysis.map(item => item.chargingCount);
const completedCount = chargingAnalysis.map(item => item.completedCount);
charts.chargingAnalysisChart.setOption({
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.8)',
borderColor: '#00e4ff',
textStyle: { color: '#fff' }
},
legend: {
data: ['充电中', '充电完成'],
textStyle: { color: '#fff' }
},
grid: { left: '10%', right: '10%', bottom: '15%', top: '20%' },
xAxis: {
type: 'category',
data: hours,
axisLabel: { color: '#7ecfff', fontSize: 10 },
axisLine: { lineStyle: { color: '#00e4ff' } }
},
yAxis: {
type: 'value',
axisLabel: { color: '#7ecfff' },
splitLine: { lineStyle: { color: 'rgba(0,228,255,0.1)' } }
},
series: [
{
name: '充电中',
type: 'bar',
stack: 'charging',
data: chargingCount,
itemStyle: { color: '#1890ff' }
},
{
name: '充电完成',
type: 'bar',
stack: 'charging',
data: completedCount,
itemStyle: { color: '#52c41a' }
}
]
});
}
// 更新佩戴状态图
function updateWearingStatusChart(data) {
const { wearingTrends } = data;
const timeLabels = wearingTrends.map(item => item.time);
const wornRate = wearingTrends.map(item => item.wornRate);
const activeRate = wearingTrends.map(item => item.activeRate);
charts.wearingStatusChart.setOption({
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.8)',
borderColor: '#00e4ff',
textStyle: { color: '#fff' }
},
legend: {
data: ['佩戴率', '活跃率'],
textStyle: { color: '#fff' }
},
grid: { left: '10%', right: '10%', bottom: '15%', top: '20%' },
xAxis: {
type: 'category',
data: timeLabels,
axisLabel: { color: '#7ecfff', fontSize: 10 },
axisLine: { lineStyle: { color: '#00e4ff' } }
},
yAxis: {
type: 'value',
max: 100,
axisLabel: { color: '#7ecfff', formatter: '{value}%' },
splitLine: { lineStyle: { color: 'rgba(0,228,255,0.1)' } }
},
series: [
{
name: '佩戴率',
type: 'line',
data: wornRate,
itemStyle: { color: '#faad14' },
areaStyle: { color: 'rgba(250,173,20,0.1)' },
smooth: true
},
{
name: '活跃率',
type: 'line',
data: activeRate,
itemStyle: { color: '#1890ff' },
areaStyle: { color: 'rgba(24,144,255,0.1)' },
smooth: true
}
]
});
}
// 更新设备列表
function updateDeviceList(data) {
const { devices } = data;
const deviceList = document.getElementById('deviceList');
const deviceCount = document.getElementById('deviceListCount');
deviceCount.textContent = devices.length;
deviceList.innerHTML = devices.slice(0, 15).map(device => `
<div class="list-item">
<div class="list-item-label">${device.deviceSn}</div>
<div>
<span class="list-item-status ${getStatusClass(device.status)}">${getStatusText(device.status)}</span>
<span class="list-item-value" style="margin-left: 8px;">${device.batteryLevel}%</span>
</div>
</div>
`).join('');
}
// 更新版本列表
function updateVersionList(data) {
const { versionDistribution } = data;
const versionList = document.getElementById('versionList');
versionList.innerHTML = Object.entries(versionDistribution)
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
.map(([version, count]) => `
<div class="list-item">
<div class="list-item-label">${version}</div>
<div class="list-item-value stat-primary">${count}台</div>
</div>
`).join('');
}
// 获取状态样式类
function getStatusClass(status) {
const statusMap = {
'ACTIVE': 'status-online',
'OFFLINE': 'status-offline',
'CHARGING': 'status-charging',
'LOW_BATTERY': 'status-low-battery',
'WORN': 'status-worn'
};
return statusMap[status] || 'status-offline';
}
// 获取状态文本
function getStatusText(status) {
const statusMap = {
'ACTIVE': '在线',
'OFFLINE': '离线',
'CHARGING': '充电',
'LOW_BATTERY': '低电量',
'WORN': '佩戴中'
};
return statusMap[status] || '未知';
}
// 切换时间范围
function changeTimeRange(range) {
currentTimeRange = range;
// 更新按钮状态
document.querySelectorAll('.time-btn').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
// 重新加载数据
updateDashboard();
}
// 更新当前时间
function updateTime() {
const now = new Date();
const timeString = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
document.getElementById('currentTime').textContent = timeString;
}
// 更新所有组件
async function updateDashboard() {
const data = await fetchDeviceData();
if (data) {
currentData = data;
updateStatCards(data);
updateBatteryTrendChart(data);
updateDeviceStatusChart(data);
updateChargingAnalysisChart(data);
updateWearingStatusChart(data);
updateDeviceList(data);
updateVersionList(data);
}
}
// 窗口大小调整时重新渲染图表
window.addEventListener('resize', () => {
Object.values(charts).forEach(chart => chart.resize());
});
// 初始化
document.addEventListener('DOMContentLoaded', async () => {
initCharts();
await updateDashboard();
// 每30秒更新一次数据
setInterval(updateDashboard, 30000);
// 每秒更新时间
setInterval(updateTime, 1000);
updateTime();
});
</script>
</body>
</html>