esp网页监控面板框架已成,通讯解决
This commit is contained in:
157
esp8266/web/index.html
Normal file
157
esp8266/web/index.html
Normal file
File diff suppressed because one or more lines are too long
BIN
esp8266/web/index.html.gz
Normal file
BIN
esp8266/web/index.html.gz
Normal file
Binary file not shown.
267
esp8266/web/script.js
Normal file
267
esp8266/web/script.js
Normal file
@@ -0,0 +1,267 @@
|
||||
// JavaScript for page navigation
|
||||
const page1Link = document.getElementById('page1-link');
|
||||
const page2Link = document.getElementById('page2-link');
|
||||
const page1 = document.getElementById('page1');
|
||||
const page2 = document.getElementById('page2');
|
||||
|
||||
page1Link.addEventListener('click', () => {
|
||||
page1.classList.add('active');
|
||||
page2.classList.remove('active');
|
||||
});
|
||||
|
||||
page2Link.addEventListener('click', () => {
|
||||
page2.classList.add('active');
|
||||
page1.classList.remove('active');
|
||||
});
|
||||
|
||||
const uploadButton = document.getElementById('upload-button');
|
||||
const firmwareFile = document.getElementById('firmware-file');
|
||||
const uploadForm = document.getElementById('upload-form');
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const progressBar = document.getElementById('upload-progress');
|
||||
|
||||
// 点击拖放区域触发文件选择
|
||||
dropZone.addEventListener('click', () => {
|
||||
firmwareFile.click();
|
||||
});
|
||||
|
||||
// 文件拖入拖放区域
|
||||
['dragenter', 'dragover'].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dropZone.classList.add('highlight');
|
||||
});
|
||||
});
|
||||
|
||||
// 文件拖离拖放区域
|
||||
['dragleave', 'drop'].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dropZone.classList.remove('highlight');
|
||||
});
|
||||
});
|
||||
|
||||
// 文件放入拖放区域
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length) {
|
||||
firmwareFile.files = files;
|
||||
dropZone.textContent = files[0].name; // 更新文本内容为文件名
|
||||
}
|
||||
});
|
||||
|
||||
// 更新文本内容为文件名
|
||||
firmwareFile.addEventListener('change', () => {
|
||||
if (firmwareFile.files.length) {
|
||||
dropZone.textContent = firmwareFile.files[0].name;
|
||||
}
|
||||
});
|
||||
|
||||
// 上传文件并显示进度
|
||||
uploadButton.addEventListener('click', () => {
|
||||
const file = firmwareFile.files[0];
|
||||
if (!file) {
|
||||
alert('请选择一个文件!');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('update', file);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/update', true);
|
||||
|
||||
xhr.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable) {
|
||||
const percentComplete = (e.loaded / e.total) * 100;
|
||||
progressBar.value = percentComplete;
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200) {
|
||||
alert('上传成功!');
|
||||
} else {
|
||||
alert('上传失败,请重试。');
|
||||
}
|
||||
progressBar.value = 0;
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
alert('上传过程中发生错误。错误码:' + xhr.status);
|
||||
progressBar.value = 0;
|
||||
};
|
||||
|
||||
xhr.timeout = 100000; // 设置超时时间为100秒
|
||||
xhr.ontimeout = function () {
|
||||
alert('服务器无响应,页面将刷新。');
|
||||
location.reload();
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
});
|
||||
|
||||
|
||||
var history_data = [
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []},
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []},
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []},
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []},
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []},
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []},
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []},
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []},
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []},
|
||||
{voltageHistory: [], currentHistory: [], powerHistory: []}
|
||||
];
|
||||
|
||||
|
||||
function addToHistory(index, voltage, current, power) {
|
||||
history_data[index].voltageHistory.push(voltage);
|
||||
history_data[index].currentHistory.push(current);
|
||||
history_data[index].powerHistory.push(power);
|
||||
|
||||
history_data[index].voltageHistory = history_data[index].voltageHistory.slice(-500);
|
||||
history_data[index].currentHistory = history_data[index].currentHistory.slice(-500);
|
||||
history_data[index].powerHistory = history_data[index].powerHistory.slice(-500);
|
||||
}
|
||||
|
||||
function updateStatus() {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/status', true);
|
||||
|
||||
xhr.onload = function () {
|
||||
const cards = document.querySelectorAll('.status-card');
|
||||
|
||||
if (xhr.status === 200) {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
data = data.data;
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
view.setFloat32(0, data[50], true);
|
||||
var alert_tmp = new Uint8Array(buffer);
|
||||
var index_convert = [8,9,0,1,2,3,4,5,6,7];
|
||||
cards.forEach((card, index) => {
|
||||
const voltage = card.querySelector('.voltage');
|
||||
const current = card.querySelector('.current');
|
||||
const power = card.querySelector('.power');
|
||||
const alert = card.querySelector('.alert');
|
||||
const canvas = card.querySelector('.chart');
|
||||
|
||||
// Update text content
|
||||
voltage.textContent = data[index_convert[index]*2].toFixed(2);
|
||||
current.textContent = data[index_convert[index]*2+1].toFixed(2);
|
||||
power.textContent = data[index_convert[index]*3+20].toFixed(2);
|
||||
// alert.textContent = data[index*2].alert;
|
||||
|
||||
addToHistory(index_convert[index], data[index_convert[index]*2], data[index_convert[index]*2+1], data[index_convert[index]*3+20]);
|
||||
|
||||
// Draw chart
|
||||
drawChart(canvas, history_data[index_convert[index]]);
|
||||
});
|
||||
} else {
|
||||
console.error('Failed to fetch status data');
|
||||
|
||||
cards.forEach((card) => {
|
||||
const voltage = card.querySelector('.voltage');
|
||||
const current = card.querySelector('.current');
|
||||
const power = card.querySelector('.power');
|
||||
const canvas = card.querySelector('.chart');
|
||||
|
||||
// Display NaN for text content
|
||||
voltage.textContent = 'NaN';
|
||||
current.textContent = 'NaN';
|
||||
power.textContent = 'NaN';
|
||||
|
||||
addToHistory(index, 0, 0, 0);
|
||||
|
||||
// Draw chart with 0 values
|
||||
drawChart(canvas, history_data[index]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
console.error('Error during status update request');
|
||||
|
||||
const cards = document.querySelectorAll('.status-card');
|
||||
cards.forEach((card) => {
|
||||
const voltage = card.querySelector('.voltage');
|
||||
const current = card.querySelector('.current');
|
||||
const power = card.querySelector('.power');
|
||||
const canvas = card.querySelector('.chart');
|
||||
|
||||
// Display NaN for text content
|
||||
voltage.textContent = 'NaN';
|
||||
current.textContent = 'NaN';
|
||||
power.textContent = 'NaN';
|
||||
|
||||
addToHistory(index, 0, 0, 0);
|
||||
// Draw chart with 0 values
|
||||
drawChart(canvas, history_data[index]);
|
||||
});
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function drawChart(canvas, data) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 获取历史数据
|
||||
const voltageData = data.voltageHistory || [0, 0, 0, 0, 0];
|
||||
const currentData = data.currentHistory || [0, 0, 0, 0, 0];
|
||||
const powerData = data.powerHistory || [0, 0, 0, 0, 0];
|
||||
|
||||
const allData = [...voltageData, ...currentData, ...powerData];
|
||||
const maxValue = Math.max(...allData);
|
||||
const minValue = Math.min(...allData);
|
||||
|
||||
const colors = {
|
||||
voltage: 'blue',
|
||||
current: 'green',
|
||||
power: 'orange'
|
||||
};
|
||||
|
||||
const maxPoints = voltageData.length;
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
const stepX = width / (maxPoints - 1);
|
||||
|
||||
function drawLine(data, color) {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 2;
|
||||
data.forEach((value, index) => {
|
||||
const x = index * stepX;
|
||||
const y = height - ((value - minValue) / (maxValue - minValue)) * height; // 自动缩放
|
||||
if (index === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
});
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
drawLine(voltageData, colors.voltage);
|
||||
drawLine(currentData, colors.current);
|
||||
drawLine(powerData, colors.power);
|
||||
}
|
||||
|
||||
setInterval(updateStatus, 500);
|
||||
|
||||
const toggleSidebarButton = document.getElementById('toggle-sidebar');
|
||||
const sidebar = document.querySelectorAll('.sidebar');
|
||||
const sideButtonContainer = document.getElementById('side-button');
|
||||
|
||||
toggleSidebarButton.addEventListener('click', () => {
|
||||
for(let i = 0; i < sidebar.length; i++) {
|
||||
sidebar[i].classList.toggle('hidden');
|
||||
}
|
||||
sideButtonContainer.classList.toggle('hide-arrow');
|
||||
sideButtonContainer.classList.toggle('show-arrow');
|
||||
});
|
||||
BIN
esp8266/web/script.js.gz
Normal file
BIN
esp8266/web/script.js.gz
Normal file
Binary file not shown.
304
esp8266/web/styles.css
Normal file
304
esp8266/web/styles.css
Normal file
@@ -0,0 +1,304 @@
|
||||
/* General styles */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Sidebar styles */
|
||||
.sidebar {
|
||||
position: fixed; /* 固定在页面左侧 */
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%; /* 占满页面高度 */
|
||||
overflow-y: auto; /* 如果内容过多,允许滚动 */
|
||||
width: 200px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
transition: width 0.3s, padding 0.3s;
|
||||
z-index: 1; /* 设置侧边栏层级为 1 */
|
||||
}
|
||||
|
||||
.sidebar nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar nav ul li {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.sidebar nav ul li a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
/* .sidebar nav ul li a:hover {
|
||||
text-decoration: underline;
|
||||
} */
|
||||
|
||||
.sidebar a {
|
||||
display: block; /* 整行可点击 */
|
||||
padding: 10px 15px;
|
||||
text-decoration: none; /* 去掉下划线 */
|
||||
color: #ecf0f1; /* 默认文字颜色 */
|
||||
background-color: #34495e; /* 默认背景颜色 */
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s, color 0.3s; /* 添加过渡效果 */
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background-color: #1abc9c; /* 悬停时背景颜色 */
|
||||
color: #ffffff; /* 悬停时文字颜色 */
|
||||
text-decoration: none; /* 确保悬停时无下划线 */
|
||||
cursor: pointer; /* 光标变为手型 */
|
||||
}
|
||||
|
||||
.sidebar.hidden {
|
||||
width: 0px; /* 保留切换按钮的宽度 */
|
||||
padding-left: 0px; /* 去掉内边距 */
|
||||
padding-right: 0px; /* 去掉内边距 */
|
||||
overflow: hidden; /* 隐藏其他内容 */
|
||||
/* display: none; */
|
||||
}
|
||||
|
||||
/* Content styles */
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Page 2 styles */
|
||||
#drop-zone {
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
#drop-zone:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
#upload-progress {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#upload-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #007BFF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
#upload-button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
/* Status grid styles */
|
||||
.status-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr); /* 默认每排 4 个卡片 */
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 1650px) {
|
||||
.status-grid {
|
||||
grid-template-columns: repeat(2, 1fr); /* 宽度小于 1650px 时每排 2 个卡片 */
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.status-grid {
|
||||
grid-template-columns: repeat(1, 1fr); /* 宽度小于 1000px 时每排 1 个卡片 */
|
||||
}
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
#drop-zone {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#upload-button {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
background-color: #3d3d3d; /* 深色背景 */
|
||||
color: #ecf0f1; /* 浅色文字 */
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
background-color: #e5e5e5; /* 深色卡片背景 */
|
||||
border: 2px solid #1abc9c; /* 突出的边框颜色 */
|
||||
min-width: 250px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影效果 */
|
||||
padding: 5px;
|
||||
color: #ecf0f1;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.status-card:hover {
|
||||
transform: translateY(-5px); /* 悬停时上移效果 */
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4); /* 悬停时更深的阴影 */
|
||||
}
|
||||
|
||||
.status-card h2 {
|
||||
margin-top: 0;
|
||||
color: #1abc9c; /* 标题颜色 */
|
||||
}
|
||||
|
||||
.settings-icon {
|
||||
text-align: right;
|
||||
font-size: 1.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.status-grid .status-card .status-card-info {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
grid-template-columns: repeat(2, 1fr); /* 每行 2 个 */
|
||||
}
|
||||
|
||||
.top-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr); /* 默认每排 2 个卡片 */
|
||||
gap: 20px;
|
||||
padding: 5px 20px 5px 20px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.top-grid {
|
||||
grid-template-columns: repeat(1, 1fr); /* 宽度小于 1000px 时每排 1 个卡片 */
|
||||
}
|
||||
}
|
||||
|
||||
.top-grid .status-card .status-card-info{
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
grid-template-columns: repeat(4, 1fr); /* 电压、电流、功率、警报在同一行 */
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1650px) {
|
||||
.top-grid .status-card .status-card-info{
|
||||
grid-template-rows: auto auto auto; /* 默认布局 */
|
||||
grid-template-columns: repeat(2, 1fr); /* 每行 2 个 */
|
||||
}
|
||||
}
|
||||
|
||||
.status-card canvas {
|
||||
width: 90%; /* 根据卡片宽度的 90% 自动调整 */
|
||||
display: block;
|
||||
margin: 0 auto; /* 居中对齐 */
|
||||
}
|
||||
|
||||
.top-grid canvas {
|
||||
height: 140px; /* 固定高度 */
|
||||
}
|
||||
|
||||
.status-grid canvas {
|
||||
height: 130px; /* 固定高度 */
|
||||
}
|
||||
|
||||
#toggle-sidebar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: #1abc9c;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.3s, transform 0.3s;
|
||||
/* position: fixed; */
|
||||
/* top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%); */
|
||||
|
||||
}
|
||||
|
||||
#toggle-sidebar:hover {
|
||||
background-color: #16a085;
|
||||
}
|
||||
|
||||
#side-button{
|
||||
position: fixed; /* 悬浮于页面 */
|
||||
top: 50%; /* 垂直居中 */
|
||||
left: 5px; /* 固定在页面左侧 */
|
||||
transform: translateY(-50%); /* 调整垂直居中位置 */
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2; /* 设置按钮层级为 2,确保在侧边栏之上 */
|
||||
}
|
||||
|
||||
.hide-arrow #toggle-sidebar {
|
||||
transform: rotate(180deg); /* 收起时箭头旋转 */
|
||||
}
|
||||
|
||||
.show-arrow #toggle-sidebar {
|
||||
transform: rotate(0deg); /* 展开时箭头恢复 */
|
||||
}
|
||||
BIN
esp8266/web/styles.css.gz
Normal file
BIN
esp8266/web/styles.css.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user