解决了websock频繁连接的问题

This commit is contained in:
zjk
2025-05-17 08:39:18 +08:00
parent ac4301f99e
commit 8585f0dd51
7 changed files with 34 additions and 3008 deletions
+1 -1
View File
@@ -143,7 +143,7 @@ bool PowerManger::Iterate()
if (m_ticker++ % 5 == 0) if (m_ticker++ % 5 == 0)
{ {
m_CcuColCmd.heartbeat++; m_CcuColCmd.heartbeat++;
m_lowerCommManager->processPeriodicTasks(); // m_lowerCommManager->processPeriodicTasks();
m_upperCommManager->processPeriodicTasks(); m_upperCommManager->processPeriodicTasks();
} }
return(true); return(true);
+33 -5
View File
@@ -4,6 +4,7 @@
#include "mongoose.h" #include "mongoose.h"
#include <iostream> #include <iostream>
#include "json/json.h" #include "json/json.h"
#include <sys/resource.h>
using json = Json::Value; using json = Json::Value;
@@ -11,26 +12,53 @@ using json = Json::Value;
void HttpServer::handleWebSocketEvent(struct mg_connection* c, int ev, void* ev_data, void* fn_data) { void HttpServer::handleWebSocketEvent(struct mg_connection* c, int ev, void* ev_data, void* fn_data) {
HttpServer* server = static_cast<HttpServer*>(fn_data); HttpServer* server = static_cast<HttpServer*>(fn_data);
if (ev == MG_EV_OPEN) { if (ev == MG_EV_WS_OPEN) {
// 新连接建立 // WebSocket连接成功建立
std::lock_guard<std::mutex> lock(server->ws_connections_mutex_); std::lock_guard<std::mutex> lock(server->ws_connections_mutex_);
server->ws_connections_.push_back(c); server->ws_connections_.push_back(c);
LOG_F(INFO, "WebSocket connection opened: id=%lu fd=%d", c->id, c->fd); LOG_F(INFO, "WebSocket connection established: id=%lu fd=%d", c->id, c->fd);
} else if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) { } else if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
// 连接关闭或错误 // 连接关闭或错误
std::lock_guard<std::mutex> lock(server->ws_connections_mutex_); std::lock_guard<std::mutex> lock(server->ws_connections_mutex_);
auto it = std::find(server->ws_connections_.begin(), server->ws_connections_.end(), c); auto it = std::find(server->ws_connections_.begin(), server->ws_connections_.end(), c);
if (it != server->ws_connections_.end()) { if (it != server->ws_connections_.end()) {
LOG_F(ERROR, "WebSocket %s: id=%lu fd=%d", if (ev == MG_EV_ERROR) {
ev == MG_EV_ERROR ? "error" : "closed", c->id, c->fd); struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (errno != 0) {
LOG_F(ERROR, "WebSocket error: id=%lu fd=%d, errno=%d",
c->id, c->fd, errno);
// 打印socket错误信息
char buf[256];
strerror_r(errno, buf, sizeof(buf));
LOG_F(ERROR, "Socket error: %s", buf);
} else {
LOG_F(ERROR, "WebSocket connection closed abnormally: id=%lu fd=%d",
c->id, c->fd);
}
} else {
LOG_F(INFO, "WebSocket closed: id=%lu fd=%d", c->id, c->fd);
}
server->ws_connections_.erase(it); server->ws_connections_.erase(it);
// 检查系统资源限制
static int fd_limit_checked = 0;
if (!fd_limit_checked) {
fd_limit_checked = 1;
struct rlimit lim;
if (getrlimit(RLIMIT_NOFILE, &lim) == 0) {
LOG_F(INFO, "File descriptor limits: soft=%lu hard=%lu",
lim.rlim_cur, lim.rlim_max);
}
}
} }
} else if (ev == MG_EV_HTTP_MSG) { } else if (ev == MG_EV_HTTP_MSG) {
// HTTP请求处理 // HTTP请求处理
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ws"), NULL)) { if (mg_match(hm->uri, mg_str("/ws"), NULL)) {
// 升级到WebSocket连接 // 升级到WebSocket连接
LOG_F(INFO, "Upgrading to WebSocket: id=%lu fd=%d", c->id, c->fd);
mg_ws_upgrade(c, hm, nullptr); mg_ws_upgrade(c, hm, nullptr);
LOG_F(INFO, "WebSocket upgrade completed: id=%lu fd=%d", c->id, c->fd);
} else { } else {
// 返回主页HTML // 返回主页HTML
mg_http_reply(c, 200, "Content-Type: text/html; charset=utf-8\r\n", INDEX_HTML.c_str()); mg_http_reply(c, 200, "Content-Type: text/html; charset=utf-8\r\n", INDEX_HTML.c_str());
@@ -1,31 +0,0 @@
#!/bin/bash
# 输出文件名
OUTPUT_FILE="web_resources.h"
# 创建输出文件并添加头文件保护
echo "// Auto-generated web resources header" > "$OUTPUT_FILE"
echo "#ifndef WEB_RESOURCES_H" >> "$OUTPUT_FILE"
echo "#define WEB_RESOURCES_H" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
# 遍历当前目录下的文件
for file in *.html *.js *.css; do
if [ -f "$file" ]; then
# 获取文件名和扩展名
filename=$(basename "$file")
extension="${filename##*.}"
varname="${filename%.*}_${extension}"
# 使用xxd生成内容并追加到输出文件
echo "// Content from $filename" >> "$OUTPUT_FILE"
xxd -i "$file" | sed "s/unsigned char .*\[\]/unsigned char ${varname}_content\[\]/" >> "$OUTPUT_FILE"
xxd -i "$file" | grep -E "unsigned int .*_len" | sed "s/unsigned int .*_len/unsigned int ${varname}_len/" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
fi
done
# 添加文件结尾
echo "#endif // WEB_RESOURCES_H" >> "$OUTPUT_FILE"
echo "Web resources header generation complete. Output: $OUTPUT_FILE"
-196
View File
@@ -1,196 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>H100能源系统仪表盘</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<div class="dashboard-container">
<header class="dashboard-header">
<h1><i class="fas fa-bolt"></i> 能源管理系统实时监控</h1>
<div class="system-status" id="system-status">
<span class="status-indicator"></span>
<span>系统运行中</span>
<span id="last-update">最后更新: 正在加载...</span>
<span class="status-separator">|</span>
<span id="device-status">设备: 离线</span>
<span class="status-separator">|</span>
<span id="comm-status">通信: 断开</span>
</div>
</header>
<div class="dashboard-content">
<div class="status-cards">
<div class="status-card" id="fuel-cell-card">
<h2><i class="fas fa-fire"></i> 燃料电池</h2>
<div class="card-content">
<div class="status-item">
<span>状态:</span>
<span id="fc-status">正在加载...</span>
</div>
<div class="status-item">
<span>输出功率:</span>
<span id="output-power-limit">正在加载...</span>
</div>
</div>
</div>
<div class="status-card" id="battery-card">
<h2><i class="fas fa-battery-three-quarters"></i> 锂电池状态</h2>
<div class="card-content">
<div class="status-item">
<span>动力电池:</span>
<span id="dyn-soc">正在加载...</span>
</div>
<div class="status-item">
<span>仪表电池:</span>
<span id="ins-soc">正在加载...</span>
</div>
</div>
</div>
<div class="status-card" id="alarm-card">
<h2><i class="fas fa-exclamation-triangle"></i> 报警状态</h2>
<div class="card-content">
<div class="status-item">
<span>燃料电池:</span>
<span id="fc-fault-level">正在加载...</span>
</div>
<div class="status-item">
<span>动力电池:</span>
<span id="dyn-alarm-flag1">正在加载...</span>
</div>
<div class="status-item">
<span>仪表电池:</span>
<span id="ins-alarm-flag1">正在加载...</span>
</div>
<div class="status-item">
<span>配电系统:</span>
<span id="dis-alarm-flag1">正在加载...</span>
</div>
</div>
</div>
</div>
<div class="data-sections">
<button class="collapsible"><i class="fas fa-fire"></i> 燃料电池系统</button>
<div class="content">
<div class="system-data-grid">
<div class="data-item">
<span class="data-label">工作模式:</span>
<span class="data-value" id="fc-mode">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">运行状态:</span>
<span class="data-value" id="fc-status">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">输出功率限制:</span>
<span class="data-value" id="output-power-limit">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">故障等级:</span>
<span class="data-value" id="fc-fault-level">正在加载...</span>
</div>
</div>
</div>
<button class="collapsible"><i class="fas fa-battery-full"></i> 动力电池系统</button>
<div class="content">
<div class="system-data-grid">
<div class="data-item">
<span class="data-label">SOC:</span>
<span class="data-value" id="dyn-soc">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">母线电压:</span>
<span class="data-value" id="dyn-voltage-link">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">正极继电器:</span>
<span class="data-value" id="dyn-pos-relay">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">负极继电器:</span>
<span class="data-value" id="dyn-neg-relay">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">报警标志1:</span>
<span class="data-value" id="dyn-alarm-flag1">正在加载...</span>
</div>
</div>
</div>
<button class="collapsible"><i class="fas fa-battery-quarter"></i> 仪表电池系统</button>
<div class="content">
<div class="system-data-grid">
<div class="data-item">
<span class="data-label">SOC:</span>
<span class="data-value" id="ins-soc">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">母线电压:</span>
<span class="data-value" id="ins-voltage-link">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">正极继电器:</span>
<span class="data-value" id="ins-pos-relay">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">负极继电器:</span>
<span class="data-value" id="ins-neg-relay">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">报警标志1:</span>
<span class="data-value" id="ins-alarm-flag1">正在加载...</span>
</div>
</div>
</div>
<button class="collapsible"><i class="fas fa-plug"></i> 配电系统</button>
<div class="content">
<div class="system-data-grid">
<div class="data-item">
<span class="data-label">高压配电状态:</span>
<span class="data-value" id="dis-sys-state">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">A相电压:</span>
<span class="data-value" id="dis-high-a-vol">正在加载...</span>
</div>
<div class="data-item">
<span class="data-label">B相电压:</span>
<span class="data-value" id="dis-high-b-vol">正在加载...</span>
</div>
</div>
</div>
</div>
</div>
<button class="collapsible"><i class="fas fa-chart-line"></i> 电流电压曲线</button>
<div class="content">
<div class="chart-row">
<div class="chart-section">
<h3>燃料电池电流电压</h3>
<div class="chart-container">
<canvas id="fcVoltageCurrentChart"></canvas>
</div>
</div>
<div class="chart-section">
<h3>电池系统电流电压</h3>
<div class="chart-container">
<canvas id="batteryVoltageCurrentChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="script.js"></script>
</body>
</html>
-365
View File
@@ -1,365 +0,0 @@
// 系统状态常量
const STATUS = {
NORMAL: 'normal',
WARNING: 'warning',
DANGER: 'danger'
};
// WebSocket连接
let ws = null;
let reconnectAttempts = 0;
const MAX_RECONNECT_ATTEMPTS = 5;
const RECONNECT_DELAY = 5000; // 5秒
// 初始化WebSocket连接
function initWebSocket() {
ws = new WebSocket(`ws://${window.location.host}/ws`);
ws.onopen = () => {
console.log('WebSocket连接已建立');
reconnectAttempts = 0;
updateSystemStatus(STATUS.NORMAL, '系统连接正常');
updateCommStatus(true);
updateDeviceStatus(true);
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
updateSystemData(data);
updateLastUpdateTime();
} catch (error) {
console.error('解析数据错误:', error);
}
};
ws.onclose = () => {
console.log('WebSocket连接已关闭');
updateCommStatus(false);
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
setTimeout(initWebSocket, RECONNECT_DELAY);
reconnectAttempts++;
updateSystemStatus(STATUS.WARNING, `连接断开,尝试重新连接 (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
} else {
updateSystemStatus(STATUS.DANGER, '连接失败,请刷新页面');
updateDeviceStatus(false);
}
};
ws.onerror = (error) => {
console.error('WebSocket错误:', error);
updateSystemStatus(STATUS.DANGER, '连接错误');
};
}
// 更新设备状态
function updateDeviceStatus(isOnline) {
const element = document.getElementById('device-status');
if (element) {
element.textContent = `设备: ${isOnline ? '在线' : '离线'}`;
element.className = isOnline ? 'status-normal' : 'status-danger';
}
}
// 更新通信状态
function updateCommStatus(isConnected) {
const element = document.getElementById('comm-status');
if (element) {
element.textContent = `通信: ${isConnected ? '正常' : '断开'}`;
element.className = isConnected ? 'status-normal' : 'status-danger';
}
}
// 更新系统状态显示
function updateSystemStatus(status, message) {
const statusElement = document.getElementById('system-status');
const indicator = statusElement.querySelector('.status-indicator');
statusElement.querySelector('span:last-child').textContent = message;
// 移除所有状态类
indicator.classList.remove('status-normal', 'status-warning', 'status-danger');
// 添加新状态类
switch(status) {
case STATUS.NORMAL:
indicator.classList.add('status-normal');
break;
case STATUS.WARNING:
indicator.classList.add('status-warning');
break;
case STATUS.DANGER:
indicator.classList.add('status-danger');
break;
}
}
// 更新最后更新时间
function updateLastUpdateTime() {
const now = new Date();
document.getElementById('last-update').textContent = `最后更新: ${now.toLocaleTimeString()}`;
}
// 更新系统数据
function updateSystemData(data) {
// 燃料电池数据
updateDataElement('fc-mode', data.fc_mode, getFCModeText);
updateDataElement('fc-status', data.fc_status, getFCStatusText);
updateDataElement('output-power-limit', data.output_power_limit, value => `${value} W`);
updateDataElement('fc-fault-level', data.fc_fault_level);
// 电池数据
updateDataElement('dyn-soc', data.dyn_soc, value => `${value.toFixed(1)}%`);
updateDataElement('ins-soc', data.ins_soc, value => `${value.toFixed(1)}%`);
updateDataElement('dyn-voltage-link', data.dyn_voltage_link, value => `${value.toFixed(2)} V`);
updateDataElement('ins-voltage-link', data.ins_voltage_link, value => `${value.toFixed(2)} V`);
// 更新图表数据
updateChartData(
data.fc_voltage,
data.fc_current,
data.dyn_voltage_link,
data.dyn_current
);
// 报警数据
updateDataElement('dyn-alarm-flag1', data.dyn_alarm_flag1, getAlarmStatusText);
updateDataElement('ins-alarm-flag1', data.ins_alarm_flag1, getAlarmStatusText);
updateDataElement('dis-alarm-flag1', data.dis_alarm_flag1, getAlarmStatusText);
// 继电器状态
updateRelayStatus('ins-pos-relay', data.ins_realy_state?.pos_relay);
updateRelayStatus('ins-neg-relay', data.ins_realy_state?.neg_relay);
updateRelayStatus('dyn-pos-relay', data.dyn_realy_state?.pos_relay);
updateRelayStatus('dyn-neg-relay', data.dyn_realy_state?.neg_relay);
// 其他数据更新...
// 可以根据需要添加更多数据更新逻辑
}
// 通用数据更新函数
function updateDataElement(elementId, value, formatter = null) {
const element = document.getElementById(elementId);
if (element && value !== undefined) {
element.textContent = formatter ? formatter(value) : value;
element.classList.add('updated');
setTimeout(() => element.classList.remove('updated'), 1000);
}
}
// 更新继电器状态
function updateRelayStatus(elementId, status) {
const element = document.getElementById(elementId);
if (element && status !== undefined) {
element.textContent = getRelayStatusText(status);
element.className = '';
if (status === 1) {
element.classList.add('status-normal');
} else {
element.classList.add('status-danger');
}
}
}
// FC模式文本转换
function getFCModeText(mode) {
const modes = {
0: '无效',
1: '调试',
2: '自动',
3: '补给'
};
return modes[mode] || '未知模式';
}
// FC状态文本转换
function getFCStatusText(status) {
const statuses = {
0: '无效',
1: '初始化',
2: '待机',
3: '自检异常',
4: '就绪',
5: '启动',
6: '运行',
7: '停机',
8: '故障',
9: '补给',
10: '制氢预热',
11: '制氢预热完成',
12: '停机维护',
13: '维护完成'
};
return statuses[status] || '未知状态';
}
// 继电器状态文本转换
function getRelayStatusText(status) {
const statuses = {
0: '无效',
1: '闭合',
2: '断开'
};
return statuses[status] || '未知状态';
}
// 折叠面板功能
function setupCollapsibles() {
const coll = document.getElementsByClassName("collapsible");
for (let i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
const content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
}
// 图表初始化
let fcChart = null;
let batteryChart = null;
const MAX_DATA_POINTS = 60; // 保留60个数据点(约1分钟数据)
function initCharts() {
// 燃料电池图表
const fcCtx = document.getElementById('fcVoltageCurrentChart').getContext('2d');
fcChart = new Chart(fcCtx, {
type: 'line',
data: {
labels: Array(MAX_DATA_POINTS).fill(''),
datasets: [
{
label: '输出电压 (V)',
data: [],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1,
yAxisID: 'y'
},
{
label: '输出电流 (A)',
data: [],
borderColor: 'rgb(255, 99, 132)',
tension: 0.1,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: '电压 (V)'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: '电流 (A)'
},
grid: {
drawOnChartArea: false
}
}
}
}
});
// 电池系统图表
const batteryCtx = document.getElementById('batteryVoltageCurrentChart').getContext('2d');
batteryChart = new Chart(batteryCtx, {
type: 'line',
data: {
labels: Array(MAX_DATA_POINTS).fill(''),
datasets: [
{
label: '母线电压 (V)',
data: [],
borderColor: 'rgb(54, 162, 235)',
tension: 0.1,
yAxisID: 'y'
},
{
label: '电池电流 (A)',
data: [],
borderColor: 'rgb(255, 159, 64)',
tension: 0.1,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: '电压 (V)'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: '电流 (A)'
},
grid: {
drawOnChartArea: false
}
}
}
}
});
}
// 更新图表数据
function updateChartData(fcVoltage, fcCurrent, batteryVoltage, batteryCurrent) {
if (fcChart) {
fcChart.data.datasets[0].data.push(fcVoltage);
fcChart.data.datasets[1].data.push(fcCurrent);
if (fcChart.data.datasets[0].data.length > MAX_DATA_POINTS) {
fcChart.data.datasets[0].data.shift();
fcChart.data.datasets[1].data.shift();
}
fcChart.update();
}
if (batteryChart) {
batteryChart.data.datasets[0].data.push(batteryVoltage);
batteryChart.data.datasets[1].data.push(batteryCurrent);
if (batteryChart.data.datasets[0].data.length > MAX_DATA_POINTS) {
batteryChart.data.datasets[0].data.shift();
batteryChart.data.datasets[1].data.shift();
}
batteryChart.update();
}
}
// 页面加载完成后初始化
window.addEventListener('load', () => {
initWebSocket();
setupCollapsibles();
initCharts();
updateSystemStatus(STATUS.NORMAL, '系统初始化完成');
});
-253
View File
@@ -1,253 +0,0 @@
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--danger-color: #e74c3c;
--warning-color: #f39c12;
--dark-color: #2c3e50;
--light-color: #ecf0f1;
--gray-color: #95a5a6;
--white-color: #ffffff;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa;
color: var(--dark-color);
line-height: 1.6;
}
.dashboard-container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 1px solid #ddd;
}
.dashboard-header h1 {
color: var(--dark-color);
font-size: 24px;
}
.dashboard-header h1 i {
margin-right: 10px;
color: var(--primary-color);
}
.system-status {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: var(--secondary-color);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.status-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.status-card {
background-color: var(--white-color);
border-radius: 8px;
padding: 20px;
box-shadow: var(--shadow);
transition: var(--transition);
}
.status-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.status-card h2 {
font-size: 18px;
margin-bottom: 15px;
color: var(--dark-color);
display: flex;
align-items: center;
gap: 10px;
}
.status-item {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.status-item:last-child {
border-bottom: none;
margin-bottom: 0;
}
/* 折叠面板样式 */
.collapsible {
background-color: var(--primary-color);
color: white;
cursor: pointer;
padding: 15px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 16px;
margin: 10px 0;
border-radius: 5px;
display: flex;
justify-content: space-between;
align-items: center;
transition: var(--transition);
}
.collapsible:hover {
background-color: #2980b9;
}
.collapsible:after {
content: '\002B';
font-weight: bold;
font-size: 18px;
}
.active:after {
content: '\2212';
}
.content {
padding: 0 15px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: var(--white-color);
border-radius: 0 0 5px 5px;
box-shadow: var(--shadow);
}
.system-data-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 15px;
padding: 15px;
}
.data-item {
background-color: #f8f9fa;
padding: 12px;
border-radius: 5px;
border-left: 4px solid var(--primary-color);
}
.data-label {
font-weight: 600;
color: var(--dark-color);
}
.data-value {
color: #555;
}
/* 图表样式 */
.chart-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
gap: 20px;
margin-top: 30px;
}
.chart-section {
background: var(--white-color);
border-radius: 8px;
padding: 20px;
box-shadow: var(--shadow);
}
.chart-container {
position: relative;
height: 350px;
width: 100%;
}
@media (max-width: 1200px) {
.chart-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 15px;
padding: 15px 0;
}
.chart-section {
background: var(--white-color);
border-radius: 8px;
padding: 15px;
box-shadow: var(--shadow);
}
.chart-section h3 {
margin: 0 0 10px 0;
font-size: 16px;
color: var(--dark-color);
display: flex;
align-items: center;
gap: 8px;
}
.chart-section h3 i {
color: var(--primary-color);
font-size: 14px;
}
.chart-container {
position: relative;
height: 300px;
width: 100%;
}
.status-cards {
grid-template-columns: 1fr;
}
}
/* 状态颜色 */
.status-normal {
color: var(--secondary-color);
}
.status-warning {
color: var(--warning-color);
}
.status-danger {
color: var(--danger-color);
}
File diff suppressed because it is too large Load Diff