<!DOCTYPE html>
|
<html lang="zh-CN">
|
<head>
|
<meta charset="UTF-8">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>CTU小车数据监控</title>
|
<script src="https://cdn.tailwindcss.com"></script>
|
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
|
<script>
|
tailwind.config = {
|
theme: {
|
extend: {
|
colors: {
|
primary: '#3b82f6',
|
secondary: '#10b981',
|
warning: '#f59e0b',
|
danger: '#ef4444',
|
dark: '#1e293b',
|
},
|
fontFamily: {
|
inter: ['Inter', 'sans-serif'],
|
},
|
}
|
}
|
}
|
</script>
|
<style type="text/tailwindcss">
|
@layer utilities {
|
.content-auto {
|
content-visibility: auto;
|
}
|
.card-shadow {
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
}
|
.animate-pulse-slow {
|
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
}
|
}
|
</style>
|
</head>
|
<body class="bg-gray-50 font-inter">
|
<div class="min-h-screen">
|
<!-- 顶部导航栏 -->
|
<header class="bg-white card-shadow sticky top-0 z-10">
|
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
|
<div class="flex items-center space-x-2">
|
<i class="fa fa-car text-primary text-2xl"></i>
|
<h1 class="text-xl font-bold text-dark">CTU小车数据监控</h1>
|
</div>
|
<div class="flex items-center space-x-4">
|
<div class="relative">
|
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
|
<i class="fa fa-search text-gray-400"></i>
|
</span>
|
<input type="text" placeholder="搜索设备..." class="pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
</div>
|
<button class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition-colors">
|
<i class="fa fa-refresh mr-1"></i> 刷新
|
</button>
|
</div>
|
</div>
|
</header>
|
|
<!-- 主内容区 -->
|
<main class="container mx-auto px-4 py-6">
|
<!-- 状态概览 -->
|
<section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
<div class="bg-white rounded-lg p-4 card-shadow">
|
<div class="flex items-center justify-between">
|
<div>
|
<p class="text-gray-500 text-sm">总设备数</p>
|
<h3 class="text-2xl font-bold text-dark">24</h3>
|
</div>
|
<div class="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center">
|
<i class="fa fa-microchip text-primary text-xl"></i>
|
</div>
|
</div>
|
</div>
|
<div class="bg-white rounded-lg p-4 card-shadow">
|
<div class="flex items-center justify-between">
|
<div>
|
<p class="text-gray-500 text-sm">在线设备</p>
|
<h3 class="text-2xl font-bold text-secondary">18</h3>
|
</div>
|
<div class="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center">
|
<i class="fa fa-check-circle text-secondary text-xl"></i>
|
</div>
|
</div>
|
</div>
|
<div class="bg-white rounded-lg p-4 card-shadow">
|
<div class="flex items-center justify-between">
|
<div>
|
<p class="text-gray-500 text-sm">离线设备</p>
|
<h3 class="text-2xl font-bold text-danger">6</h3>
|
</div>
|
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center">
|
<i class="fa fa-exclamation-circle text-danger text-xl"></i>
|
</div>
|
</div>
|
</div>
|
<div class="bg-white rounded-lg p-4 card-shadow">
|
<div class="flex items-center justify-between">
|
<div>
|
<p class="text-gray-500 text-sm">今日数据量</p>
|
<h3 class="text-2xl font-bold text-warning">1.2k</h3>
|
</div>
|
<div class="w-12 h-12 rounded-full bg-yellow-100 flex items-center justify-center">
|
<i class="fa fa-database text-warning text-xl"></i>
|
</div>
|
</div>
|
</div>
|
</section>
|
|
<!-- 数据图表 -->
|
<section class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
<!-- 上行数据图表 -->
|
<div class="bg-white rounded-lg p-4 card-shadow">
|
<div class="flex justify-between items-center mb-4">
|
<h2 class="text-lg font-semibold text-dark">上行数据趋势</h2>
|
<div class="flex space-x-2">
|
<button class="px-3 py-1 text-sm bg-blue-100 text-primary rounded-md">小时</button>
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">天</button>
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">周</button>
|
</div>
|
</div>
|
<div class="h-64">
|
<canvas id="upDataChart"></canvas>
|
</div>
|
</div>
|
<!-- 下行数据图表 -->
|
<div class="bg-white rounded-lg p-4 card-shadow">
|
<div class="flex justify-between items-center mb-4">
|
<h2 class="text-lg font-semibold text-dark">下行数据趋势</h2>
|
<div class="flex space-x-2">
|
<button class="px-3 py-1 text-sm bg-blue-100 text-primary rounded-md">小时</button>
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">天</button>
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">周</button>
|
</div>
|
</div>
|
<div class="h-64">
|
<canvas id="downDataChart"></canvas>
|
</div>
|
</div>
|
</section>
|
|
<!-- 设备数据表格 -->
|
<section class="bg-white rounded-lg p-4 card-shadow">
|
<div class="flex justify-between items-center mb-4">
|
<h2 class="text-lg font-semibold text-dark">CTU小车数据列表</h2>
|
<div class="flex space-x-2">
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">
|
<i class="fa fa-filter mr-1"></i> 筛选
|
</button>
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">
|
<i class="fa fa-download mr-1"></i> 导出
|
</button>
|
</div>
|
</div>
|
<div class="overflow-x-auto">
|
<table class="min-w-full divide-y divide-gray-200">
|
<thead>
|
<tr>
|
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">设备ID</th>
|
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">设备名称</th>
|
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
|
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">上行数据</th>
|
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">下行数据</th>
|
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">最后通信</th>
|
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
</tr>
|
</thead>
|
<tbody class="bg-white divide-y divide-gray-200" id="deviceTableBody">
|
<!-- 表格数据将通过JavaScript动态生成 -->
|
</tbody>
|
</table>
|
</div>
|
<div class="flex justify-between items-center mt-4">
|
<p class="text-sm text-gray-500">显示 1-10 条,共 24 条</p>
|
<div class="flex space-x-1">
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md disabled:opacity-50" disabled>上一页</button>
|
<button class="px-3 py-1 text-sm bg-primary text-white rounded-md">1</button>
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">2</button>
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">3</button>
|
<button class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md">下一页</button>
|
</div>
|
</div>
|
</section>
|
|
<!-- 实时数据更新 -->
|
<section class="bg-white rounded-lg p-4 card-shadow mt-6">
|
<h2 class="text-lg font-semibold text-dark mb-4">实时数据更新</h2>
|
<div class="h-40 overflow-y-auto p-2 border border-gray-200 rounded-lg" id="realtimeData">
|
<!-- 实时数据将通过JavaScript动态生成 -->
|
</div>
|
</section>
|
</main>
|
|
<!-- 页脚 -->
|
<footer class="bg-white card-shadow mt-6 py-4">
|
<div class="container mx-auto px-4 text-center text-gray-500 text-sm">
|
<p>© 2026 物联网设备数据监控系统 | 版本 1.0.0</p>
|
</div>
|
</footer>
|
</div>
|
|
<script>
|
// 模拟CTU小车数据
|
const devices = [
|
{ id: 'CTU-001', name: '配送小车1号', status: 'online', upData: '2.4KB', downData: '0.8KB', lastComm: '2分钟前' },
|
{ id: 'CTU-002', name: '配送小车2号', status: 'online', upData: '1.8KB', downData: '0.5KB', lastComm: '5分钟前' },
|
{ id: 'CTU-003', name: '巡检小车1号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '2小时前' },
|
{ id: 'CTU-004', name: '配送小车3号', status: 'online', upData: '3.2KB', downData: '1.2KB', lastComm: '1分钟前' },
|
{ id: 'CTU-005', name: '巡检小车2号', status: 'online', upData: '1.5KB', downData: '0.6KB', lastComm: '3分钟前' },
|
{ id: 'CTU-006', name: '配送小车4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '5小时前' },
|
{ id: 'CTU-007', name: '巡检小车3号', status: 'online', upData: '2.1KB', downData: '0.9KB', lastComm: '4分钟前' },
|
{ id: 'CTU-008', name: '配送小车5号', status: 'online', upData: '2.8KB', downData: '1.1KB', lastComm: '2分钟前' },
|
{ id: 'CTU-009', name: '巡检小车4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '1天前' },
|
{ id: 'CTU-010', name: '配送小车6号', status: 'online', upData: '1.9KB', downData: '0.7KB', lastComm: '6分钟前' }
|
];
|
|
// 生成表格数据
|
function generateTableData() {
|
const tbody = document.getElementById('deviceTableBody');
|
tbody.innerHTML = '';
|
devices.forEach(device => {
|
const row = document.createElement('tr');
|
row.innerHTML = `
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${device.id}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${device.name}</td>
|
<td class="px-6 py-4 whitespace-nowrap">
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${device.status === 'online' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">
|
${device.status === 'online' ? '在线' : '离线'}
|
</span>
|
</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${device.upData}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${device.downData}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${device.lastComm}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<button class="text-primary hover:text-blue-700 mr-3">
|
<i class="fa fa-eye"></i>
|
</button>
|
<button class="text-warning hover:text-yellow-700 mr-3">
|
<i class="fa fa-edit"></i>
|
</button>
|
<button class="text-danger hover:text-red-700">
|
<i class="fa fa-trash"></i>
|
</button>
|
</td>
|
`;
|
tbody.appendChild(row);
|
});
|
}
|
|
// 初始化图表
|
function initCharts() {
|
// 上行数据图表
|
const upCtx = document.getElementById('upDataChart').getContext('2d');
|
const upDataChart = new Chart(upCtx, {
|
type: 'line',
|
data: {
|
labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
|
datasets: [{
|
label: '上行数据 (KB)',
|
data: [12, 19, 15, 25, 22, 30, 28, 35],
|
borderColor: '#3b82f6',
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
tension: 0.4,
|
fill: true
|
}]
|
},
|
options: {
|
responsive: true,
|
maintainAspectRatio: false,
|
plugins: {
|
legend: {
|
display: false
|
}
|
},
|
scales: {
|
y: {
|
beginAtZero: true
|
}
|
}
|
}
|
});
|
|
// 下行数据图表
|
const downCtx = document.getElementById('downDataChart').getContext('2d');
|
const downDataChart = new Chart(downCtx, {
|
type: 'line',
|
data: {
|
labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
|
datasets: [{
|
label: '下行数据 (KB)',
|
data: [5, 8, 6, 12, 10, 15, 13, 18],
|
borderColor: '#10b981',
|
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
tension: 0.4,
|
fill: true
|
}]
|
},
|
options: {
|
responsive: true,
|
maintainAspectRatio: false,
|
plugins: {
|
legend: {
|
display: false
|
}
|
},
|
scales: {
|
y: {
|
beginAtZero: true
|
}
|
}
|
}
|
});
|
}
|
|
// 模拟实时数据更新
|
function simulateRealtimeData() {
|
const realtimeContainer = document.getElementById('realtimeData');
|
const messages = [
|
'CTU-001 配送小车1号: 运行中,位置: A1区',
|
'CTU-002 配送小车2号: 待机中,位置: B2区',
|
'CTU-004 配送小车3号: 充电中,电量: 85%',
|
'CTU-005 巡检小车2号: 巡检中,已完成3/5任务',
|
'CTU-007 巡检小车3号: 待机中,位置: C3区',
|
'CTU-008 配送小车5号: 运行中,位置: D4区'
|
];
|
|
setInterval(() => {
|
const message = messages[Math.floor(Math.random() * messages.length)];
|
const timestamp = new Date().toLocaleTimeString();
|
const div = document.createElement('div');
|
div.className = 'py-1 border-b border-gray-100';
|
div.innerHTML = `<span class="text-gray-500 text-xs">${timestamp}</span> <span class="text-gray-900">${message}</span>`;
|
realtimeContainer.prepend(div);
|
// 限制显示条数
|
if (realtimeContainer.children.length > 20) {
|
realtimeContainer.removeChild(realtimeContainer.lastChild);
|
}
|
}, 2000);
|
}
|
|
// 页面加载完成后初始化
|
document.addEventListener('DOMContentLoaded', function() {
|
generateTableData();
|
initCharts();
|
simulateRealtimeData();
|
});
|
</script>
|
</body>
|
</html>
|