修复完成配电操作,且网页可用
This commit is contained in:
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/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"
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
<!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>
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
// 系统状态常量
|
||||||
|
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, '系统初始化完成');
|
||||||
|
});
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
: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
Reference in New Issue
Block a user