964 lines
38 KiB
HTML
964 lines
38 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="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
.header {
|
||
padding: 10px;
|
||
text-align: center;
|
||
border-bottom: 1px solid #ddd;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 15px;
|
||
}
|
||
|
||
.controls {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.control-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
select, input, button {
|
||
padding: 5px 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
button {
|
||
background-color: #007bff;
|
||
color: white;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
button:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
|
||
.chart-container {
|
||
margin-bottom: 20px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 15px;
|
||
background-color: #fff;
|
||
}
|
||
|
||
canvas {
|
||
width: 100%;
|
||
max-height: 400px;
|
||
}
|
||
|
||
.table-container {
|
||
overflow-x: auto;
|
||
margin-top: 20px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background-color: #fff;
|
||
padding: 15px;
|
||
}
|
||
h2{
|
||
text-align:center;
|
||
color: #333;
|
||
margin-top: 0;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
th, td {
|
||
border: 1px solid #ddd;
|
||
padding: 12px 8px;
|
||
text-align: left;
|
||
}
|
||
|
||
th {
|
||
background-color: #f8f9fa;
|
||
font-weight: bold;
|
||
}
|
||
|
||
tr:nth-child(even) {
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
tr:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.footer {
|
||
text-align: center;
|
||
padding: 10px;
|
||
border-top: 1px solid #ddd;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.controls {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.control-group {
|
||
width: 100%;
|
||
}
|
||
|
||
select, input {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<h1>测斜仪数据</h1>
|
||
<div id="connectionStatus" style="display: inline-block; padding: 5px 15px; border-radius: 4px; margin-left: 10px; background-color: red; color: white; min-width: 300px; text-align: center; cursor: pointer; font-size: 14px;">
|
||
未连接
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<div class="controls">
|
||
<div class="control-group">
|
||
<label for="sensorSelect">选择探头:</label>
|
||
<select id="sensorSelect">
|
||
<option value="all">所有探头</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="limitSelect">显示记录数:</label>
|
||
<select id="limitSelect">
|
||
<option value="10">10</option>
|
||
<option value="50">50</option>
|
||
<option value="100">100</option>
|
||
<option value="200">200</option>
|
||
<option value="500">500</option>
|
||
<option value="1000">1000</option>
|
||
<option value="all" selected>全部</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="startDate">开始日期:</label>
|
||
<input type="datetime-local" id="startDate">
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="endDate">结束日期:</label>
|
||
<input type="datetime-local" id="endDate">
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<button id="queryBtn">查询数据</button>
|
||
<button id="resetBtn">重置筛选</button>
|
||
<button id="queryLatestBtn">查询最新数据</button>
|
||
<button id="exportBtn">导出CSV</button>
|
||
</div>
|
||
|
||
<!-- <div class="control-group">-->
|
||
<!-- <label>-->
|
||
<!-- <input type="checkbox" id="autoRefresh"> -->
|
||
<!-- 自动刷新 (10分钟周期)-->
|
||
<!-- </label>-->
|
||
<!-- </div>-->
|
||
</div>
|
||
|
||
<!-- 最新数据显示区域 -->
|
||
<div class="latest-data" style="margin-bottom: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 4px; background-color: #f8f9fa;">
|
||
<h3 style="margin-top: 0; margin-bottom: 10px; color: #333;">最新传感器数据 <span id="latest-time" style="font-weight: normal; font-size: 14px;"></span></h3>
|
||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px;">
|
||
<div style="padding: 8px; border: 1px solid #eee; border-radius: 4px; background-color: white;">
|
||
<div style="font-weight: bold; color: #555; font-size: 12px;">传感器ID</div>
|
||
<div style="font-size: 20px; color: #555;" id="latest-sensor-id">--</div>
|
||
</div>
|
||
<div style="padding: 8px; border: 1px solid #eee; border-radius: 4px; background-color: white;">
|
||
<div style="font-weight: bold; color: #555; font-size: 12px;">X轴值</div>
|
||
<div style="font-size: 20px; color: #555;" id="latest-x">--</div>
|
||
</div>
|
||
<div style="padding: 8px; border: 1px solid #eee; border-radius: 4px; background-color: white;">
|
||
<div style="font-weight: bold; color: #555; font-size: 12px;">Y轴值</div>
|
||
<div style="font-size: 20px; color: #555;" id="latest-y">--</div>
|
||
</div>
|
||
<div style="padding: 8px; border: 1px solid #eee; border-radius: 4px; background-color: white;">
|
||
<div style="font-weight: bold; color: #555; font-size: 12px;">Z轴值</div>
|
||
<div style="font-size: 20px; color: #555;" id="latest-z">--</div>
|
||
</div>
|
||
<div style="padding: 8px; border: 1px solid #eee; border-radius: 4px; background-color: white;">
|
||
<div style="font-weight: bold; color: #555; font-size: 12px;">温度</div>
|
||
<div style="font-size: 20px; color: #555;" id="latest-temperature">--</div>
|
||
<div style="font-size: 12px; color: #777;">℃</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 图表区域 -->
|
||
<div class="chart-container">
|
||
<!-- <h2>传感器数据图表</h2>-->
|
||
<canvas id="sensorChart"></canvas>
|
||
</div>
|
||
|
||
<!-- 数据表格 -->
|
||
<div class="table-container">
|
||
<!-- <h2>传感器数据表格</h2>-->
|
||
|
||
<table id="dataTable">
|
||
<thead>
|
||
<tr>
|
||
<th>数据编号</th>
|
||
<th>探头地址</th>
|
||
<th>时间</th>
|
||
<th>X</th>
|
||
<th>Y</th>
|
||
<th>Z</th>
|
||
<th>温度(°C)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tableBody">
|
||
<!-- 数据将通过JavaScript动态添加 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 内联JavaScript -->
|
||
<script>
|
||
// 全局变量
|
||
let sensorChart = null;
|
||
let refreshInterval = null;
|
||
let allSensors = [];
|
||
let currentSensorData = [];
|
||
let connectionCheckTimer = null;
|
||
|
||
// 页面加载完成后执行
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 初始化日期选择器为今天
|
||
initializeDatePickers();
|
||
|
||
// 加载所有传感器ID
|
||
loadSensors();
|
||
|
||
// 添加事件监听器
|
||
setupEventListeners();
|
||
|
||
// 设置自动刷新
|
||
setupAutoRefresh();
|
||
|
||
// 检查连接状态
|
||
checkConnectionStatus();
|
||
connectionCheckTimer = setInterval(checkConnectionStatus, 30000); // 每30秒检查一次
|
||
|
||
// 显示数据库中的最新数据(不发送TCP查询指令)
|
||
fetchLatestData();
|
||
});
|
||
|
||
// 初始化日期选择器
|
||
function initializeDatePickers() {
|
||
// 获取当前时间(使用本地时间)
|
||
const now = new Date();
|
||
|
||
// 格式化日期和时间为HTML datetime-local输入格式 YYYY-MM-DDThh:mm
|
||
const year = now.getFullYear();
|
||
const month = (now.getMonth() + 1).toString().padStart(2, '0'); // 月份从0开始
|
||
const day = now.getDate().toString().padStart(2, '0');
|
||
const hours = now.getHours().toString().padStart(2, '0');
|
||
const minutes = now.getMinutes().toString().padStart(2, '0');
|
||
|
||
const today = `${year}-${month}-${day}`;
|
||
const currentTime = `${hours}:${minutes}`;
|
||
|
||
// 设置默认的开始时间为当天00:00
|
||
document.getElementById('startDate').value = `${today}T00:00`;
|
||
|
||
// 设置默认的结束时间为当前时间
|
||
document.getElementById('endDate').value = `${today}T${currentTime}`;
|
||
|
||
console.log("当前设置的开始时间:", document.getElementById('startDate').value);
|
||
console.log("当前设置的结束时间:", document.getElementById('endDate').value);
|
||
}
|
||
// 设置事件监听器
|
||
function setupEventListeners() {
|
||
// 查询按钮
|
||
document.getElementById('queryBtn').addEventListener('click', function() {
|
||
loadData();
|
||
});
|
||
|
||
// 重置按钮
|
||
document.getElementById('resetBtn').addEventListener('click', function() {
|
||
resetFilters();
|
||
});
|
||
|
||
// 查询最新数据按钮
|
||
document.getElementById('queryLatestBtn').addEventListener('click', function() {
|
||
queryLatestData();
|
||
});
|
||
|
||
// 传感器选择变化
|
||
document.getElementById('sensorSelect').addEventListener('change', function() {
|
||
loadData();
|
||
});
|
||
|
||
// 记录数限制变化
|
||
document.getElementById('limitSelect').addEventListener('change', function() {
|
||
loadData();
|
||
});
|
||
|
||
// 导出CSV按钮
|
||
document.getElementById('exportBtn').addEventListener('click', function() {
|
||
exportToCSV();
|
||
});
|
||
}
|
||
|
||
// 设置自动刷新
|
||
function setupAutoRefresh() {
|
||
const autoRefreshCheckbox = document.getElementById('autoRefresh');
|
||
|
||
// 如果自动刷新复选框不存在,直接返回
|
||
if (!autoRefreshCheckbox) {
|
||
console.log('自动刷新功能未启用(元素不存在)');
|
||
return;
|
||
}
|
||
|
||
// 初始化自动刷新
|
||
if (autoRefreshCheckbox.checked) {
|
||
startAutoRefresh();
|
||
}
|
||
|
||
// 监听复选框变化
|
||
autoRefreshCheckbox.addEventListener('change', function() {
|
||
if (this.checked) {
|
||
startAutoRefresh();
|
||
} else {
|
||
stopAutoRefresh();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 开始自动刷新
|
||
function startAutoRefresh() {
|
||
if (refreshInterval) {
|
||
clearInterval(refreshInterval);
|
||
}
|
||
|
||
refreshInterval = setInterval(loadData, 1000 * 60 * 10);
|
||
}
|
||
|
||
// 停止自动刷新
|
||
function stopAutoRefresh() {
|
||
if (refreshInterval) {
|
||
clearInterval(refreshInterval);
|
||
refreshInterval = null;
|
||
}
|
||
}
|
||
|
||
// 重置筛选条件
|
||
function resetFilters() {
|
||
initializeDatePickers();
|
||
document.getElementById('sensorSelect').value = 'all';
|
||
document.getElementById('limitSelect').value = '500';
|
||
loadData();
|
||
}
|
||
|
||
// 加载所有传感器ID
|
||
function loadSensors() {
|
||
fetch('/api/sensors')
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('获取传感器列表失败');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
allSensors = data;
|
||
updateSensorSelect(data);
|
||
|
||
// 加载数据
|
||
loadData();
|
||
})
|
||
.catch(error => {
|
||
console.error('加载传感器列表出错:', error);
|
||
alert('加载传感器列表出错: ' + error.message);
|
||
});
|
||
}
|
||
|
||
// 更新传感器选择下拉框
|
||
function updateSensorSelect(sensors) {
|
||
const select = document.getElementById('sensorSelect');
|
||
|
||
// 保留"所有传感器"选项
|
||
const allOption = select.querySelector('option[value="all"]');
|
||
select.innerHTML = '';
|
||
select.appendChild(allOption);
|
||
|
||
if (sensors.length === 0) {
|
||
const option = document.createElement('option');
|
||
option.value = '';
|
||
option.textContent = '没有可用的传感器';
|
||
select.appendChild(option);
|
||
return;
|
||
}
|
||
|
||
sensors.forEach(id => {
|
||
const option = document.createElement('option');
|
||
option.value = id;
|
||
option.textContent = `探头 ${id}`;
|
||
select.appendChild(option);
|
||
});
|
||
}
|
||
|
||
// 加载传感器数据
|
||
function loadData() {
|
||
const sensorID = document.getElementById('sensorSelect').value;
|
||
const limit = document.getElementById('limitSelect').value;
|
||
const startDate = document.getElementById('startDate').value;
|
||
const endDate = document.getElementById('endDate').value;
|
||
|
||
let url = '/api/data?';
|
||
let params = [];
|
||
|
||
// 添加查询参数
|
||
if (sensorID !== 'all') {
|
||
params.push(`sensor_id=${sensorID}`);
|
||
}
|
||
|
||
// 始终发送limit参数,即使是'all'
|
||
params.push(`limit=${limit}`);
|
||
|
||
if (startDate) {
|
||
params.push(`start_date=${encodeURIComponent(startDate)}`);
|
||
}
|
||
|
||
if (endDate) {
|
||
params.push(`end_date=${encodeURIComponent(endDate)}`);
|
||
}
|
||
|
||
url += params.join('&');
|
||
|
||
// 显示加载状态
|
||
document.getElementById('queryBtn').textContent = '加载中...';
|
||
|
||
fetch(url)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('获取传感器数据失败');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
currentSensorData = data;
|
||
updateTable(data);
|
||
updateChart(data);
|
||
document.getElementById('queryBtn').textContent = '查询数据';
|
||
})
|
||
.catch(error => {
|
||
console.error('加载数据出错:', error);
|
||
alert('加载数据出错: ' + error.message);
|
||
document.getElementById('queryBtn').textContent = '查询数据';
|
||
});
|
||
}
|
||
|
||
// 更新数据表格
|
||
function updateTable(data) {
|
||
const tableBody = document.getElementById('tableBody');
|
||
tableBody.innerHTML = '';
|
||
|
||
if (data.length === 0) {
|
||
const row = document.createElement('tr');
|
||
row.innerHTML = '<td colspan="7" style="text-align: center;">没有数据</td>';
|
||
tableBody.appendChild(row);
|
||
return;
|
||
}
|
||
|
||
data.forEach(item => {
|
||
const row = document.createElement('tr');
|
||
|
||
// 解析时间并调整为中国时间(UTC+8)
|
||
const date = new Date(item.timestamp);
|
||
// 减去8小时,因为数据库时间似乎比实际时间早了8小时
|
||
date.setHours(date.getHours() - 8);
|
||
|
||
// 格式化为中文日期时间格式
|
||
const formattedDate =
|
||
date.getFullYear() + '/' +
|
||
(date.getMonth() + 1).toString().padStart(2, '0') + '/' +
|
||
date.getDate().toString().padStart(2, '0') + ' ' +
|
||
date.getHours().toString().padStart(2, '0') + ':' +
|
||
date.getMinutes().toString().padStart(2, '0') + ':' +
|
||
date.getSeconds().toString().padStart(2, '0');
|
||
|
||
row.innerHTML =
|
||
'<td>' + item.id + '</td>' +
|
||
'<td>' + item.sensor_id + '</td>' +
|
||
'<td>' + formattedDate + '</td>' +
|
||
'<td>' + item.x.toFixed(3) + '</td>' +
|
||
'<td>' + item.y.toFixed(3) + '</td>' +
|
||
'<td>' + item.z.toFixed(3) + '</td>' +
|
||
'<td>' + item.temperature.toFixed(1) + '</td>';
|
||
|
||
tableBody.appendChild(row);
|
||
});
|
||
}
|
||
|
||
// 更新图表
|
||
function updateChart(data) {
|
||
// 准备图表数据
|
||
const chartData = prepareChartData(data);
|
||
|
||
// 如果图表已经存在,销毁它
|
||
if (sensorChart) {
|
||
sensorChart.destroy();
|
||
}
|
||
|
||
// 获取图表Canvas
|
||
const ctx = document.getElementById('sensorChart').getContext('2d');
|
||
|
||
// 创建新图表
|
||
sensorChart = new Chart(ctx, {
|
||
type: 'line',
|
||
data: chartData,
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
title: {
|
||
display: true,
|
||
text: '历史数据'
|
||
},
|
||
tooltip: {
|
||
callbacks: {
|
||
label: function(context) {
|
||
let label = context.dataset.label || '';
|
||
if (label) {
|
||
label += ': ';
|
||
}
|
||
if (context.parsed.y !== null) {
|
||
label += context.parsed.y.toFixed(3);
|
||
}
|
||
return label;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
scales: {
|
||
x: {
|
||
title: {
|
||
display: true,
|
||
text: '时间'
|
||
}
|
||
},
|
||
y: {
|
||
title: {
|
||
display: true,
|
||
text: '值'
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 准备图表数据
|
||
function prepareChartData(data) {
|
||
// 如果没有数据,返回空数据集
|
||
if (data.length === 0) {
|
||
return {
|
||
labels: [],
|
||
datasets: []
|
||
};
|
||
}
|
||
|
||
// 反转数据以便按时间先后顺序显示
|
||
const sortedData = [...data].sort((a, b) => {
|
||
return new Date(a.timestamp) - new Date(b.timestamp);
|
||
});
|
||
|
||
// 获取所有传感器ID
|
||
let sensorIDs = [...new Set(sortedData.map(item => item.sensor_id))];
|
||
|
||
// 按传感器ID分组数据
|
||
let datasets = [];
|
||
let labels = [];
|
||
|
||
// 准备时间标签(使用第一个传感器的数据)
|
||
if (sensorIDs.length > 0) {
|
||
const firstSensorData = sortedData.filter(item => item.sensor_id === sensorIDs[0]);
|
||
labels = firstSensorData.map(item => {
|
||
const date = new Date(item.timestamp);
|
||
date.setHours(date.getHours() - 8); // 调整时区
|
||
return date.toLocaleString('zh-CN', {
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit'
|
||
});
|
||
});
|
||
}
|
||
|
||
// 定义颜色
|
||
const colors = [
|
||
'rgb(75, 192, 192)',
|
||
'rgb(255, 99, 132)',
|
||
'rgb(54, 162, 235)',
|
||
'rgb(255, 205, 86)',
|
||
'rgb(153, 102, 255)',
|
||
'rgb(255, 159, 64)'
|
||
];
|
||
|
||
// 为X, Y, Z, 温度创建不同的数据集
|
||
const dataTypes = [
|
||
{ key: 'x', label: 'X' },
|
||
{ key: 'y', label: 'Y' },
|
||
{ key: 'z', label: 'Z' },
|
||
{ key: 'temperature', label: '温度(°C)' }
|
||
];
|
||
|
||
sensorIDs.forEach((sensorID, sensorIndex) => {
|
||
const sensorData = sortedData.filter(item => item.sensor_id === sensorID);
|
||
|
||
dataTypes.forEach((type, typeIndex) => {
|
||
const colorIndex = (sensorIndex * dataTypes.length + typeIndex) % colors.length;
|
||
|
||
// 默认隐藏Z轴和温度数据
|
||
const isHidden = type.key === 'z' || type.key === 'temperature';
|
||
|
||
datasets.push({
|
||
label: `探头${sensorID} - ${type.label}`,
|
||
data: sensorData.map(item => item[type.key]),
|
||
fill: false,
|
||
borderColor: colors[colorIndex],
|
||
tension: 0.1,
|
||
hidden: isHidden
|
||
});
|
||
});
|
||
});
|
||
|
||
return {
|
||
labels: labels,
|
||
datasets: datasets
|
||
};
|
||
}
|
||
|
||
// 导出到CSV文件
|
||
function exportToCSV() {
|
||
if (currentSensorData.length === 0) {
|
||
alert('没有数据可导出');
|
||
return;
|
||
}
|
||
|
||
// 准备CSV内容
|
||
let csvContent = "数据编号,探头地址编号,X,Y,Z,温度,时间\n";
|
||
|
||
currentSensorData.forEach(item => {
|
||
// 解析时间并调整为中国时间
|
||
const date = new Date(item.timestamp);
|
||
date.setHours(date.getHours() - 8);
|
||
|
||
// 格式化日期
|
||
const formattedDate =
|
||
date.getFullYear() + '/' +
|
||
(date.getMonth() + 1).toString().padStart(2, '0') + '/' +
|
||
date.getDate().toString().padStart(2, '0') + ' ' +
|
||
date.getHours().toString().padStart(2, '0') + ':' +
|
||
date.getMinutes().toString().padStart(2, '0') + ':' +
|
||
date.getSeconds().toString().padStart(2, '0');
|
||
|
||
csvContent += item.id + "," +
|
||
item.sensor_id + "," +
|
||
item.x.toFixed(3) + "," +
|
||
item.y.toFixed(3) + "," +
|
||
item.z.toFixed(3) + "," +
|
||
item.temperature.toFixed(1) + "," +
|
||
formattedDate + "\n";
|
||
});
|
||
|
||
// 创建Blob对象
|
||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||
|
||
// 创建下载链接
|
||
const link = document.createElement("a");
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
// 设置下载属性
|
||
link.setAttribute("href", url);
|
||
link.setAttribute("download", "sensor_data.csv");
|
||
|
||
// 添加到文档并点击
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
|
||
// 清理
|
||
document.body.removeChild(link);
|
||
}
|
||
|
||
// 检查连接状态
|
||
function checkConnectionStatus() {
|
||
fetch('/api/clients')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
const statusElem = document.getElementById('connectionStatus');
|
||
if (data && data.length > 0) {
|
||
const onlineClients = data.filter(client => client.isOnline);
|
||
if (onlineClients.length > 0) {
|
||
statusElem.style.backgroundColor = 'green';
|
||
const client = onlineClients[0];
|
||
const lastSeenTime = new Date(client.lastSeen).toLocaleString('zh-CN');
|
||
if (onlineClients.length > 1) {
|
||
statusElem.textContent = `${client.connectionStatus}: ${client.ip}:${client.port} (${client.lastSeenFormatted}) [共${onlineClients.length}个设备]`;
|
||
statusElem.title = `最后连接时间: ${lastSeenTime}`;
|
||
} else {
|
||
statusElem.textContent = `${client.connectionStatus}: ${client.ip}:${client.port} (${client.lastSeenFormatted})`;
|
||
statusElem.title = `最后连接时间: ${lastSeenTime}`;
|
||
}
|
||
} else {
|
||
// 显示已掉线的设备信息
|
||
const offlineClients = data.filter(client => !client.isOnline);
|
||
if (offlineClients.length > 0) {
|
||
statusElem.style.backgroundColor = 'orange';
|
||
const client = offlineClients[0];
|
||
const lastSeenTime = new Date(client.lastSeen).toLocaleString('zh-CN');
|
||
statusElem.textContent = `${client.connectionStatus}: ${client.ip}:${client.port} (${client.lastSeenFormatted})`;
|
||
statusElem.title = `最后连接时间: ${lastSeenTime}`;
|
||
} else {
|
||
statusElem.style.backgroundColor = 'orange';
|
||
statusElem.textContent = '设备离线';
|
||
}
|
||
}
|
||
} else {
|
||
statusElem.style.backgroundColor = 'red';
|
||
statusElem.textContent = '未连接';
|
||
statusElem.title = '';
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('获取连接状态失败:', error);
|
||
const statusElem = document.getElementById('connectionStatus');
|
||
statusElem.style.backgroundColor = 'red';
|
||
statusElem.textContent = '状态未知';
|
||
statusElem.title = '';
|
||
});
|
||
}
|
||
|
||
// 获取最新传感器数据
|
||
function fetchLatestData() {
|
||
console.log('=== 开始获取最新传感器数据 ===');
|
||
fetch('/api/latest')
|
||
.then(response => {
|
||
console.log('API响应状态:', response.status, response.statusText);
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP错误: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
console.log('=== API返回的原始数据 ===');
|
||
console.log('数据类型:', typeof data);
|
||
console.log('数据内容:', data);
|
||
console.log('是否为数组:', Array.isArray(data));
|
||
console.log('数组长度:', data ? data.length : 'null');
|
||
|
||
if (data && Array.isArray(data) && data.length > 0) {
|
||
const latest = data[0];
|
||
console.log('=== 最新数据详情 ===');
|
||
console.log('latest对象:', latest);
|
||
console.log('sensor_id:', latest.sensor_id);
|
||
console.log('x:', latest.x);
|
||
console.log('y:', latest.y);
|
||
console.log('z:', latest.z);
|
||
console.log('temperature:', latest.temperature);
|
||
console.log('timestamp:', latest.timestamp);
|
||
|
||
updateLatestDataDisplay(latest);
|
||
console.log('=== 更新显示完成 ===');
|
||
} else {
|
||
console.log('=== 数据为空,显示空状态 ===');
|
||
updateLatestDataDisplayEmpty();
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('=== 获取最新数据出错 ===');
|
||
console.error('错误详情:', error);
|
||
updateLatestDataDisplayError(error.message);
|
||
});
|
||
}
|
||
|
||
// 更新最新数据显示
|
||
function updateLatestDataDisplay(data) {
|
||
console.log('=== 开始更新最新数据显示 ===');
|
||
console.log('输入数据:', data);
|
||
|
||
try {
|
||
const date = new Date(data.timestamp);
|
||
console.log('解析时间戳:', data.timestamp);
|
||
console.log('原始时间:', date.toISOString());
|
||
|
||
date.setHours(date.getHours() - 8);
|
||
console.log('调整后时间:', date.toISOString());
|
||
|
||
const formattedDate =
|
||
date.getFullYear() + '/' +
|
||
(date.getMonth() + 1).toString().padStart(2, '0') + '/' +
|
||
date.getDate().toString().padStart(2, '0') + ' ' +
|
||
date.getHours().toString().padStart(2, '0') + ':' +
|
||
date.getMinutes().toString().padStart(2, '0') + ':' +
|
||
date.getSeconds().toString().padStart(2, '0');
|
||
|
||
console.log('格式化时间:', formattedDate);
|
||
|
||
// 检查DOM元素是否存在
|
||
const timeElem = document.getElementById('latest-time');
|
||
const sensorIdElem = document.getElementById('latest-sensor-id');
|
||
const xElem = document.getElementById('latest-x');
|
||
const yElem = document.getElementById('latest-y');
|
||
const zElem = document.getElementById('latest-z');
|
||
const tempElem = document.getElementById('latest-temperature');
|
||
|
||
console.log('DOM元素检查:');
|
||
console.log('- latest-time:', timeElem ? '存在' : '不存在');
|
||
console.log('- latest-sensor-id:', sensorIdElem ? '存在' : '不存在');
|
||
console.log('- latest-x:', xElem ? '存在' : '不存在');
|
||
console.log('- latest-y:', yElem ? '存在' : '不存在');
|
||
console.log('- latest-z:', zElem ? '存在' : '不存在');
|
||
console.log('- latest-temperature:', tempElem ? '存在' : '不存在');
|
||
|
||
if (timeElem) timeElem.textContent = `(${formattedDate})`;
|
||
if (sensorIdElem) sensorIdElem.textContent = data.sensor_id;
|
||
if (xElem) xElem.textContent = data.x.toFixed(3);
|
||
if (yElem) yElem.textContent = data.y.toFixed(3);
|
||
if (zElem) zElem.textContent = data.z.toFixed(3);
|
||
if (tempElem) tempElem.textContent = data.temperature.toFixed(1);
|
||
|
||
console.log('=== 数据更新成功 ===');
|
||
console.log('更新的值:');
|
||
console.log('- 时间:', formattedDate);
|
||
console.log('- 传感器ID:', data.sensor_id);
|
||
console.log('- X:', data.x.toFixed(3));
|
||
console.log('- Y:', data.y.toFixed(3));
|
||
console.log('- Z:', data.z.toFixed(3));
|
||
console.log('- 温度:', data.temperature.toFixed(1));
|
||
|
||
} catch (error) {
|
||
console.error('=== 更新最新数据显示时发生错误 ===');
|
||
console.error('错误详情:', error);
|
||
console.error('错误堆栈:', error.stack);
|
||
updateLatestDataDisplayError('数据格式错误');
|
||
}
|
||
}
|
||
|
||
// 显示空数据状态
|
||
function updateLatestDataDisplayEmpty() {
|
||
document.getElementById('latest-time').textContent = '(暂无数据)';
|
||
document.getElementById('latest-sensor-id').textContent = '--';
|
||
document.getElementById('latest-x').textContent = '--';
|
||
document.getElementById('latest-y').textContent = '--';
|
||
document.getElementById('latest-z').textContent = '--';
|
||
document.getElementById('latest-temperature').textContent = '--';
|
||
}
|
||
|
||
// 显示错误状态
|
||
function updateLatestDataDisplayError(errorMsg) {
|
||
document.getElementById('latest-time').textContent = `(错误: ${errorMsg})`;
|
||
document.getElementById('latest-sensor-id').textContent = '--';
|
||
document.getElementById('latest-x').textContent = '--';
|
||
document.getElementById('latest-y').textContent = '--';
|
||
document.getElementById('latest-z').textContent = '--';
|
||
document.getElementById('latest-temperature').textContent = '--';
|
||
}
|
||
|
||
// 查询最新数据(触发设备查询)
|
||
function queryLatestData() {
|
||
const latestDataElement = document.querySelector('.latest-data');
|
||
const queryBtn = document.getElementById('queryLatestBtn');
|
||
|
||
latestDataElement.style.opacity = 0.6;
|
||
|
||
// 显示查询状态
|
||
const originalText = queryBtn.innerHTML;
|
||
queryBtn.textContent = '查询中...';
|
||
queryBtn.disabled = true;
|
||
|
||
// 先获取一次当前最新数据
|
||
fetchLatestData();
|
||
|
||
// 发送TCP查询指令
|
||
fetch('/api/trigger-query', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
console.log(`查询指令已发送到 ${data.sent_count} 个设备`);
|
||
|
||
if (data.sent_count === 0) {
|
||
// 没有设备在线
|
||
latestDataElement.style.opacity = 1;
|
||
queryBtn.innerHTML = originalText;
|
||
queryBtn.disabled = false;
|
||
alert('当前没有设备在线,无法发送查询指令');
|
||
return;
|
||
}
|
||
|
||
// 等待10秒后获取最新数据(给设备响应时间)
|
||
setTimeout(() => {
|
||
fetchLatestData();
|
||
loadData(); // 也刷新一下表格数据
|
||
latestDataElement.style.opacity = 1;
|
||
queryBtn.innerHTML = originalText;
|
||
queryBtn.disabled = false;
|
||
}, 10000);
|
||
} else {
|
||
console.error('发送查询指令失败:', data.message);
|
||
latestDataElement.style.opacity = 1;
|
||
queryBtn.innerHTML = originalText;
|
||
queryBtn.disabled = false;
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('发送查询指令出错:', error);
|
||
latestDataElement.style.opacity = 1;
|
||
queryBtn.innerHTML = originalText;
|
||
queryBtn.disabled = false;
|
||
});
|
||
}
|
||
|
||
// 页面卸载时清除定时器
|
||
window.addEventListener('beforeunload', function() {
|
||
if (connectionCheckTimer) {
|
||
clearInterval(connectionCheckTimer);
|
||
}
|
||
if (refreshInterval) {
|
||
clearInterval(refreshInterval);
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|